From 096664342dbc06e4ea1cc3720aa19d7336604b25 Mon Sep 17 00:00:00 2001 From: bvandekerkhof Date: Thu, 8 Feb 2024 16:35:04 +0100 Subject: [PATCH] Initial commit Signed-off-by: bvandekerkhof --- .github/bump_version.py | 19 + .github/pull_request_template.md | 46 + .github/workflows/build_package.yml | 34 + .github/workflows/code_formatting.yml | 42 + .github/workflows/publish_docs.yml | 26 + .github/workflows/pydocstyle_check.yml | 28 + .github/workflows/pylint_check.yml | 38 + .github/workflows/release_tagging.yml | 52 + .github/workflows/reuse_compliance.yml | 13 + .gitignore | 139 ++ .pylintrc | 569 ++++++ CODEOWNERS.md | 12 + CONTRIBUTING.md | 48 + LICENSE.txt | 202 ++ LICENSES/Apache-2.0.txt | 73 + README.md | 38 + docs/index.md | 7 + docs/openmcmc/distribution/distribution.md | 9 + docs/openmcmc/distribution/location_scale.md | 9 + docs/openmcmc/gmrf.md | 9 + docs/openmcmc/mcmc.md | 9 + docs/openmcmc/model.md | 9 + docs/openmcmc/parameter.md | 9 + docs/openmcmc/sampler/metropolis_hastings.md | 9 + docs/openmcmc/sampler/reversible_jump.md | 9 + docs/openmcmc/sampler/sampler.md | 9 + examples/1_model_distributions.ipynb | 235 +++ examples/1_model_distributions.ipynb.license | 3 + examples/2_samplers.ipynb | 528 +++++ examples/2_samplers.ipynb.license | 3 + examples/3_linear_regression.ipynb | 1865 ++++++++++++++++++ examples/3_linear_regression.ipynb.license | 3 + examples/4_GMRF_smoother.ipynb | 331 ++++ examples/4_GMRF_smoother.ipynb.license | 3 + mkdocs.yml | 113 ++ pyproject.toml | 78 + src/openmcmc/__init__.py | 14 + src/openmcmc/distribution/__init__.py | 10 + src/openmcmc/distribution/distribution.py | 519 +++++ src/openmcmc/distribution/location_scale.py | 417 ++++ src/openmcmc/gmrf.py | 517 +++++ src/openmcmc/mcmc.py | 110 ++ src/openmcmc/model.py | 111 ++ src/openmcmc/parameter.py | 536 +++++ src/openmcmc/sampler/__init__.py | 11 + src/openmcmc/sampler/metropolis_hastings.py | 372 ++++ src/openmcmc/sampler/reversible_jump.py | 376 ++++ src/openmcmc/sampler/sampler.py | 355 ++++ tests/test_distribution.py | 298 +++ tests/test_grmf.py | 342 ++++ tests/test_mcmc.py | 138 ++ tests/test_model.py | 75 + tests/test_parameter.py | 326 +++ tests/test_reversible_jump.py | 427 ++++ tests/test_sampler.py | 366 ++++ 55 files changed, 9949 insertions(+) create mode 100644 .github/bump_version.py create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/build_package.yml create mode 100644 .github/workflows/code_formatting.yml create mode 100644 .github/workflows/publish_docs.yml create mode 100644 .github/workflows/pydocstyle_check.yml create mode 100644 .github/workflows/pylint_check.yml create mode 100644 .github/workflows/release_tagging.yml create mode 100644 .github/workflows/reuse_compliance.yml create mode 100644 .gitignore create mode 100644 .pylintrc create mode 100644 CODEOWNERS.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.txt create mode 100644 LICENSES/Apache-2.0.txt create mode 100644 README.md create mode 100644 docs/index.md create mode 100644 docs/openmcmc/distribution/distribution.md create mode 100644 docs/openmcmc/distribution/location_scale.md create mode 100644 docs/openmcmc/gmrf.md create mode 100644 docs/openmcmc/mcmc.md create mode 100644 docs/openmcmc/model.md create mode 100644 docs/openmcmc/parameter.md create mode 100644 docs/openmcmc/sampler/metropolis_hastings.md create mode 100644 docs/openmcmc/sampler/reversible_jump.md create mode 100644 docs/openmcmc/sampler/sampler.md create mode 100644 examples/1_model_distributions.ipynb create mode 100644 examples/1_model_distributions.ipynb.license create mode 100644 examples/2_samplers.ipynb create mode 100644 examples/2_samplers.ipynb.license create mode 100644 examples/3_linear_regression.ipynb create mode 100644 examples/3_linear_regression.ipynb.license create mode 100644 examples/4_GMRF_smoother.ipynb create mode 100644 examples/4_GMRF_smoother.ipynb.license create mode 100644 mkdocs.yml create mode 100644 pyproject.toml create mode 100644 src/openmcmc/__init__.py create mode 100644 src/openmcmc/distribution/__init__.py create mode 100644 src/openmcmc/distribution/distribution.py create mode 100644 src/openmcmc/distribution/location_scale.py create mode 100644 src/openmcmc/gmrf.py create mode 100644 src/openmcmc/mcmc.py create mode 100644 src/openmcmc/model.py create mode 100644 src/openmcmc/parameter.py create mode 100644 src/openmcmc/sampler/__init__.py create mode 100644 src/openmcmc/sampler/metropolis_hastings.py create mode 100644 src/openmcmc/sampler/reversible_jump.py create mode 100644 src/openmcmc/sampler/sampler.py create mode 100644 tests/test_distribution.py create mode 100644 tests/test_grmf.py create mode 100644 tests/test_mcmc.py create mode 100644 tests/test_model.py create mode 100644 tests/test_parameter.py create mode 100644 tests/test_reversible_jump.py create mode 100644 tests/test_sampler.py diff --git a/.github/bump_version.py b/.github/bump_version.py new file mode 100644 index 0000000..f946d95 --- /dev/null +++ b/.github/bump_version.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +import re + +with open("pyproject.toml", "r") as file: + version_content = file.read() +# Match regex for pattern +old_semantic_version = re.findall(r'version = "(\d+\.\d+\.[a-zA-Z0-9]+)"', version_content) +major_version, minor_version, patch_version = old_semantic_version[0].split(".") +patch_version = int(re.findall(r"\d+", patch_version)[0]) +new_semantic_version = f"{major_version}.{minor_version}.{patch_version + 1}" +regex_bumped_patch_version = f"\g<1>{new_semantic_version}" +# Match regex for pattern +bumped_version_content = re.sub(r'(version = ")\d+\.\d+\.[a-zA-Z0-9]+', regex_bumped_patch_version, version_content) +with open("pyproject.toml", "w") as file: + file.write(bumped_version_content) +print(new_semantic_version) # Print is required for release in GitHub action diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..8f6b3f1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,46 @@ + + +# Description + +Please include a summary of the changes and the related issue. Please also include relevant motivation and context. +List any dependencies that are required for this change. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +# Jupyter Notebooks + +If your changes involve Jupyter notebooks please explicitly state here what the change consists of, e.g. only output +cells have changes or specific input changes. This to make sure we capture these changes correctly in the review process. + +# How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. +Please also list any relevant details for your test configuration + +- [ ] Test A +- [ ] Test B + + +# Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my code +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules + diff --git a/.github/workflows/build_package.yml b/.github/workflows/build_package.yml new file mode 100644 index 0000000..c5cd16b --- /dev/null +++ b/.github/workflows/build_package.yml @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: Building the package +on: + push: + branches: + - 'main' +jobs: + Build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ "3.11" ] + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade build + - name: Build the package + run: | + python -m build + - name: Upload build files + uses: actions/upload-artifact@v3 + with: + name: openmcmc_whl + path: ./dist/*.whl diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml new file mode 100644 index 0000000..2a2b1c8 --- /dev/null +++ b/.github/workflows/code_formatting.yml @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: Code formatting +on: + - push +jobs: + Black: + runs-on: ubuntu-latest + strategy: + matrix: + # Specify all python versions you might want to perform the actions on + python-version: [ "3.11" ] + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install black + pip install isort + - name: Run isort, black checks + run: | + isort . --check + black . --check + - name: Run isort and black when required and commit back + if: failure() + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.OPENMCMC_TOKEN }} + run: | + isort . + black . + git config --global user.name 'code_reformat' + git config --global user.email '' + git remote set-url origin "https://$GITHUB_ACCESS_TOKEN@github.com/$GITHUB_REPOSITORY" + git commit --signoff -am "Automatic reformat of code" + git push diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml new file mode 100644 index 0000000..7c2b12d --- /dev/null +++ b/.github/workflows/publish_docs.yml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: publish documentation +on: + push: + branches: + - main +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - uses: actions/cache@v2 + with: + key: ${{ github.ref }} + path: .cache + - run: pip install mkdocs-material + - run: pip install mkdocstrings-python + - run: mkdocs gh-deploy --force diff --git a/.github/workflows/pydocstyle_check.yml b/.github/workflows/pydocstyle_check.yml new file mode 100644 index 0000000..dca346c --- /dev/null +++ b/.github/workflows/pydocstyle_check.yml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: pydocstyle +on: + - push +jobs: + pydocstyle: + runs-on: ubuntu-latest + strategy: + matrix: + # Specify all python versions you might want to perform the actions on + python-version: [ "3.11" ] + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pydocstyle + - name: Run PydocStyle check + run: | + pydocstyle . diff --git a/.github/workflows/pylint_check.yml b/.github/workflows/pylint_check.yml new file mode 100644 index 0000000..d657a94 --- /dev/null +++ b/.github/workflows/pylint_check.yml @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +on: + - push + +name: Pylint Check +jobs: + Pylint: + # Specify the operating system GitHub has to use to perform the checks (ubuntu seems to be default) + runs-on: ubuntu-latest + strategy: + matrix: + # Specify all python versions you might want to perform the actions on + python-version: ["3.11"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + pip install . + - name: Analysing the code with pylint + if: ${{ always() }} + # Run through the src/openmcmc/ directory and check all .py files with pylint + run: | + python -m pylint `find -regextype egrep -regex '(.*src/openmcmc/.*.py)$'` --output-format=parseable:pylint_report.out + - name: Upload pylint results + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: pylint_report + path: pylint_report.out \ No newline at end of file diff --git a/.github/workflows/release_tagging.yml b/.github/workflows/release_tagging.yml new file mode 100644 index 0000000..c9a75ab --- /dev/null +++ b/.github/workflows/release_tagging.yml @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: ReleaseTag + +# Trigger when a python file is changed on main branch either from pull request or push +# but not when only pyproject.toml is changed due to version bump +on: + push: + branches: + - 'main' + paths: + - '**.py' + - '!pyproject.toml' + - 'requirements.txt' + +jobs: + # Releases new Python version when Pull Requests are merged into "main" + Release: + runs-on: ubuntu-latest + strategy: + matrix: + # Specify all python versions you might want to perform the actions on + python-version: [ "3.11" ] + steps: + # Checkout + - name: Checkout + uses: actions/checkout@v3 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Bump version and commit bumped version back to branch + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.OPENMCMC_TOKEN }} + id: version + run: | + version=$(python .github/bump_version.py) + git config --global user.name 'bump_version' + git config --global user.email 'action@github.com' + git remote set-url origin "https://$GITHUB_ACCESS_TOKEN@github.com/$GITHUB_REPOSITORY" + git commit --signoff -am "Bumped minor version" + git push + echo "BUMPED_VERSION=$(echo v$version)" >> $GITHUB_ENV + echo "New version: $version" + - name: Create Release + run: gh release create ${{ env.BUMPED_VERSION }} --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.OPENMCMC_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/reuse_compliance.yml b/.github/workflows/reuse_compliance.yml new file mode 100644 index 0000000..5431ee6 --- /dev/null +++ b/.github/workflows/reuse_compliance.yml @@ -0,0 +1,13 @@ +name: REUSE Compliance Check + +on: + - push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + - name: REUSE Compliance Check + uses: fsfe/reuse-action@v2 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f26eccd --- /dev/null +++ b/.gitignore @@ -0,0 +1,139 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +*.pyc + + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock +poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +.idea + +.vscode +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ \ No newline at end of file diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..f31fb96 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,569 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=9.0 + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the ignore-list. The +# regex matches against paths and can be in Posix or Windows format. +ignore-paths= + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.7 + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +argument-rgx=^[a-zA-Z][a-z0-9]*((_[a-z0-9]+)*)?$ + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +variable-rgx=^[a-zA-Z][a-z0-9]*((_[a-z0-9]+)*)?$ + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=no + +# Signatures are removed from the similarity computation +ignore-signatures=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear and the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# class is considered mixin if its name matches the mixin-class-rgx option. +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins ignore-mixin- +# members is set to 'yes' +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/CODEOWNERS.md b/CODEOWNERS.md new file mode 100644 index 0000000..78d4b2f --- /dev/null +++ b/CODEOWNERS.md @@ -0,0 +1,12 @@ + + + +| Name | GitHub ID | +|--------------------| ----------------:| +| Bas van de Kerkhof | bvandekerkhof | +| Matthew Jones | mattj89 | +| David Randell | davidrandell84 | \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8f05dc9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ + + +# Getting started with contributing +We're happy for everyone to contribute to the package by proposing new features, implementing them in a new branch and +creating a pull request. In order to keep the codebase consistent we use some common standards and tools for formatting +of the code. We are using poetry to keep our development environment up to date. Please follow the instructions here +https://python-poetry.org/docs/ to install poetry. Next, pull the repo to your local machine, open a terminal window +and navigate to the top directory of this package. Run the commands `poetry install --all-extras` and +`poetry install --with contributor` to install all required tools and dependencies for contributing to this package. + +We list the various tools below: +- pylint: Tool to help with the formatting of the code, can be used as a linter in most IDEs, all relevant settings are +contained in the .pylintrc file and additionally controlled through the pyproject.toml file. +- isort: Sorts the inputs, can be used from the command line `isort .`, use the `--check` flag if you do not want to +reformat the import statements in place but just want to check if imports need to be reformatted. +- black: Formats the code based on PEP standards, can be used from the command line: `black .`, use the `--check` flag +if you do not want to reformat the code in place but just check if files need to be reformatted. +- pydocstyle: Checks if the docstrings for all files and functions are present and follow the same style as specified +in the pyproject.toml file. Used in order to get consistent documentation, can be used as a check from the command line +but will not be able to replace any text, `pydocstyle .` + +In case you're unfamiliar with the tools, don't worry we have set up GitHub actions accordingly to format the code to +standard automatically on each push. + +When you implement a new feature you also need to write additional (unit) tests to show the feature you've implemented +is also working as it should. Do so by creating a file in the appropriate test folder and call that file +test_.py. Use pytest to see if your test is passing and use pytest-cov to check the coverage of your +test. The settings in the pyproject.toml file are such that we automatically test for coverage. You can run all tests +through the command line `pytest .`, use the `--cov-report term-missing` flag to show which lines are missing in the +coverage. All test are required to pass before merging into main. + +Whenever we merge new code into main, the release version gets automatically incremented as a micro version update. +Minor and major version releases need to be labeled manually. Version release convention used is major.minor.micro. + +# Notice + +The [codeowners](https://github.com/sede-open/openMCMC/blob/main/CODEOWNERS.md) reserve the right to deny applications +for ‘maintainer’ status or contributions if +the prospective maintainer or contributor is a national of and/or located in a ‘Restricted Jurisdiction’. +(A Restricted Jurisdiction is defined as a country, state, territory or region which is subject to comprehensive +trade sanctions or embargoes namely: Iran, Cuba, North Korea, Syria, the Crimea region of Ukraine (including +Sevastopol) and non-Government controlled areas of Donetsk and Luhansk). For anyone to be promoted to 'maintainer' +status, the prospective maintainer will be required to provide information on their nationality, location, and +affiliated organizations \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 0000000..137069b --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b5e5af --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ + + +# openMCMC +openMCMC is a package for constructing Bayesian models from distributional components, and then doing parameter +estimation using Markov Chain Monte Carlo (MCMC) methods. The package supports a number of standard distributions used +in Bayesian modelling (e.g. Normal, gamma, uniform), and a number of simple functional forms for the parameters of +these distributions. For a model constructed in the toolbox, a number of different MCMC algorithms are available, +including simple random walk Metropolis-Hastings, manifold MALA, exact samplers for conjugate distribution choices, +and reversible-jump MCMC for parameters with an unknown dimensionality. +*** + +# Installing openMCMC as a package +Suppose you want to use this openMCMC package in a different project. You can install it just like a Python package. +After activating the environment you want to install openMCMC in, open a terminal, move to the main openMCMC folder +where pyproject.toml is located and run `pip install .`, optionally you can pass the `-e` flag is for editable mode. +All the main options, info and settings for the package are found in the pyproject.toml file which sits in this repo +as well. + +*** + +# Examples +For some examples on how to use this package please check out these [Examples](https://github.com/sede-open/openMCMC/blob/main/examples) + +*** +# Contribution +This project welcomes contributions and suggestions. If you have a suggestion that would make this better you can +simply open an issue with a relevant title. Don't forget to give the project a star! Thanks again! + +For more details on contributing to this repository, see the [Contributing guide](https://github.com/sede-open/openMCMC/blob/main/CODEOWNERS.md). + +*** +# Licensing + +Distributed under the Apache License Version 2.0. See the [license file](https://github.com/sede-open/openMCMC/blob/main/LICENSE.txt) for more information. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..5a934a9 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,7 @@ + + +--8<-- "README.md" \ No newline at end of file diff --git a/docs/openmcmc/distribution/distribution.md b/docs/openmcmc/distribution/distribution.md new file mode 100644 index 0000000..89c56a3 --- /dev/null +++ b/docs/openmcmc/distribution/distribution.md @@ -0,0 +1,9 @@ + + +# Distribution + +::: openmcmc.distribution.distribution diff --git a/docs/openmcmc/distribution/location_scale.md b/docs/openmcmc/distribution/location_scale.md new file mode 100644 index 0000000..1b42d39 --- /dev/null +++ b/docs/openmcmc/distribution/location_scale.md @@ -0,0 +1,9 @@ + + +# Location Scale + +::: openmcmc.distribution.location_scale \ No newline at end of file diff --git a/docs/openmcmc/gmrf.md b/docs/openmcmc/gmrf.md new file mode 100644 index 0000000..e111fc9 --- /dev/null +++ b/docs/openmcmc/gmrf.md @@ -0,0 +1,9 @@ + + +# GMRF + +::: openmcmc.gmrf \ No newline at end of file diff --git a/docs/openmcmc/mcmc.md b/docs/openmcmc/mcmc.md new file mode 100644 index 0000000..c1ccbd3 --- /dev/null +++ b/docs/openmcmc/mcmc.md @@ -0,0 +1,9 @@ + + +# MCMC + +::: openmcmc.mcmc \ No newline at end of file diff --git a/docs/openmcmc/model.md b/docs/openmcmc/model.md new file mode 100644 index 0000000..ec88017 --- /dev/null +++ b/docs/openmcmc/model.md @@ -0,0 +1,9 @@ + + +# Model + +::: openmcmc.model \ No newline at end of file diff --git a/docs/openmcmc/parameter.md b/docs/openmcmc/parameter.md new file mode 100644 index 0000000..7884301 --- /dev/null +++ b/docs/openmcmc/parameter.md @@ -0,0 +1,9 @@ + + +# Parameter + +::: openmcmc.parameter \ No newline at end of file diff --git a/docs/openmcmc/sampler/metropolis_hastings.md b/docs/openmcmc/sampler/metropolis_hastings.md new file mode 100644 index 0000000..199d598 --- /dev/null +++ b/docs/openmcmc/sampler/metropolis_hastings.md @@ -0,0 +1,9 @@ + + +# Metropolis Hastings + +::: openmcmc.sampler.metropolis_hastings \ No newline at end of file diff --git a/docs/openmcmc/sampler/reversible_jump.md b/docs/openmcmc/sampler/reversible_jump.md new file mode 100644 index 0000000..303873d --- /dev/null +++ b/docs/openmcmc/sampler/reversible_jump.md @@ -0,0 +1,9 @@ + + +# Reversible Jump + +::: openmcmc.sampler.reversible_jump \ No newline at end of file diff --git a/docs/openmcmc/sampler/sampler.md b/docs/openmcmc/sampler/sampler.md new file mode 100644 index 0000000..eda6cae --- /dev/null +++ b/docs/openmcmc/sampler/sampler.md @@ -0,0 +1,9 @@ + + +# Sampler + +::: openmcmc.sampler.sampler \ No newline at end of file diff --git a/examples/1_model_distributions.ipynb b/examples/1_model_distributions.ipynb new file mode 100644 index 0000000..5eaec9a --- /dev/null +++ b/examples/1_model_distributions.ipynb @@ -0,0 +1,235 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Defining a Model in open_mcmc\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple Bayesian Model\n", + "\n", + "Taking a Bayesian approach to modelling, we have prior beliefs about a parameter $h$, summarized by a prior distribution $f(h)$. Given $h$, observed data values are believed to be distributed according to $f(y|h)$ (the likelihood).\n", + "\n", + "Using Bayes theorem, the posterior distribution for $h$ is then\n", + "\n", + "$$ f(h | y ) \\propto f(y | h) f(h)$$\n", + "\n", + "In this example, we assume that both the prior and the likelihood are Normal distributions, with known precisions: i.e.\n", + "$$f(y | h) \\sim N( h, \\tau^{-1}) $$\n", + "and \n", + "$$ f(h) \\sim N( \\mu, \\lambda^{-1} )$$\n", + "where\n", + "* $\\tau$ is the measurement precision for observations $y$\n", + "* $\\mu$ is the prior mean for $h$\n", + "* $\\lambda$ is the prior precision for $h$\n", + "\n", + "## Setting up the model.\n", + "\n", + "In the openmcmc package, a number of different types of distribution are available- in this example, we use only the Normal distribution.\n", + "\n", + "The mean and precision parameters of the Normal distribution can be passed either as strings or as Parameter objects (these will be covered in later examples). Variables corresponding to the strings passed as distribution parameters must also be present in any state dictionary that is used for evaluation or estimation (see below)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# import modules required for the example\n", + "import numpy as np\n", + "from openmcmc.model import Model\n", + "from openmcmc.distribution.location_scale import Normal" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell below defines a single `distribution.Normal` object." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "my_dist = Normal('y', mean='h', precision='tau')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the cell below, we define a `Model` object by passing multiple distributions as a list (corresponding to the likelihood and the prior)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "mdl = Model([Normal('y', mean='h', precision='tau'),\n", + " Normal('h', mean='mu', precision='lambda')])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The objects above define distributions or models, but in order to evaluate likelihoods or estimate parameters, we must pass a `state` dictionary which contains specific values for the parameters. A suitable `state` object for this example is defined below.\n", + "\n", + "All items in the `state` dictionary are expected to have strings as keys, and values are expected to be `np.ndarray` objects which are at least 2D. The sizes used must be compatible for the desired operations." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'y': array([[150, 155, 190, 160, 173]]),\n", + " 'h': array([[180]]),\n", + " 'tau': array([[0.005]]),\n", + " 'mu': array([[160]]),\n", + " 'lambda': array([[0.01]])}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state = {}\n", + "state['y']=np.array([150, 155, 190, 160, 173], ndmin=2)\n", + "state['h'] = np.array(180, ndmin=2)\n", + "state['tau'] = np.array(1 / 200, ndmin=2)\n", + "state['mu'] = np.array(160, ndmin=2)\n", + "state['lambda'] = np.array(1 / 100, ndmin=2)\n", + "\n", + "state" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Making function calls" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Having set up `my_dist` as the likelihood distribution above, we can generate random samples from it conditional on parameter values passed in `state`, as below:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[181.30730587, 179.18346712, 178.72074165, 171.80306536,\n", + " 183.87886684]])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "my_dist.rvs(state, n=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Having set up `mdl` as a model containing both the likelihood and prior distributions, we can evaluate the log-posterior distribution (up to an additive constant) for the parameter values passed in `state`, as below:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-28.24700970859217" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mdl.log_p(state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the same way, we can use the `grad_log_p` function (defined both for individual distributions and for models which combine distributions) to evaluate the gradient and the Hessian of the log-density/log-posterior at the `state` parameter values, as below:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[-0.56]]\n", + "[[0.035]]\n" + ] + } + ], + "source": [ + "gradient, hessian = mdl.grad_log_p(state, param='h')\n", + "\n", + "print(gradient)\n", + "print(hessian)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/1_model_distributions.ipynb.license b/examples/1_model_distributions.ipynb.license new file mode 100644 index 0000000..e25c5d4 --- /dev/null +++ b/examples/1_model_distributions.ipynb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/examples/2_samplers.ipynb b/examples/2_samplers.ipynb new file mode 100644 index 0000000..2be65fb --- /dev/null +++ b/examples/2_samplers.ipynb @@ -0,0 +1,528 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using MCMC Samplers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook demonstrates the use of the available MCMC samplers to do parameter estimation in a simple Bayesian model. We will use the same simple model defined in the previous notebook. The cell below imports some required package, and sets up the model." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from openmcmc.model import Model\n", + "from openmcmc.distribution.location_scale import Normal\n", + "\n", + "\n", + "mdl = Model([Normal('y', mean='h', precision='tau'),\n", + " Normal('h', mean='mu', precision='lambda')])\n", + "\n", + "state = {}\n", + "state['y'] = np.array([150, 155, 190, 160, 173], ndmin=2)\n", + "state['h'] = np.array(200, ndmin=2)\n", + "state['tau'] = np.array(1 / 200, ndmin=2)\n", + "state['mu'] = np.array(160, ndmin=2)\n", + "state['lambda'] = np.array(1 / 100, ndmin=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this example, the aim is to estimate the value of $h$, given our prior beliefs about its value, and that we have 5 observations drawn from a normal distribution with mean $h$ and standard deviation $\\sqrt{200}$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Metropolis-Hastings sampler with random walk proposal\n", + "\n", + "The simplest available type of sampler is the Metropolis-Hastings sampler with a random walk proposal. An introduction to the Metropolis-Hastings algorithm can be found on [this](https://en.wikipedia.org/wiki/Metropolis%E2%80%93Hastings_algorithm) wikipedia page. At each iteration of the algorithm, a new value of $h$ is proposed by perturbiung the current value of $h$ as follows:\n", + "$$h^* = h + \\sigma\\epsilon$$\n", + "The proposed value is then accepted with probability $A(h^*, h)$: in this instance\n", + "$$A(h^*, h) = \\frac{f(h^*|y)}{f(h|y)}$$\n", + "which is simply the ratio of the posterior densities at the proposed and current values of $h$, owing to the symmetry of the proposal mechanism. $\\sigma$ is a step-size parameter (which the user can specify as the `step` argument of the constructor), that can be tuned to achieve a desired acceptance rate." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell below sets up the list of samplers required to do parameter estimation in this model (in this case, just a single `RandomWalk` sampler)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from openmcmc.sampler.metropolis_hastings import RandomWalk\n", + "\n", + "sampler= [RandomWalk('h', model=mdl, step=5.0)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This sampler list is then passed as an input to an `MCMC` object, which controls the overall running of the MCMC chain." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from openmcmc.mcmc import MCMC\n", + "\n", + "m = MCMC(state, sampler, model=mdl, n_burn=0, n_iter=1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The argument `n_burn` controls the number of burn-in iterations of the chain (these give the sampler time to converge to sampling from the target distribution). The argument `n_iter` controls the number of main iterations of the chain (run once the chain has converged to the target distribution, used for calculating parameter estimates etc.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The MCMC algorithm is then run simply using the following command:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1000/1000 [00:00<00:00, 6678.80it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "h: Acceptance rate 70%\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "m.run_mcmc()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The acceptance rate for the RandomWalk sampler is printed out. An optimal Random Walk sampler has between 25-50% acceptance rate. If the step size is too small, we will get a high acceptance rate, but the chain will be slow to explore the target distribution; but if the step size is too large, the steps will traverse the distribution quicker, but stand a greater chance of getting rejected." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can examine the results of the mcmc run by looking into the store property which has a dictionary for each estimated parameter. Only iterations post burn in will be kept so the here we have n_iter samples" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[200. , 200. , 196.12591055, 196.12591055,\n", + " 196.12591055, 196.12591055, 196.12591055, 193.15416805,\n", + " 193.15416805, 193.15416805, 193.50094715, 193.50094715,\n", + " 193.50094715, 193.50094715, 193.50094715, 193.50094715,\n", + " 193.50094715, 189.66220689, 181.90096982, 176.78061051,\n", + " 176.73400354, 171.61472827, 168.28827923, 170.1610793 ,\n", + " 170.1610793 , 170.1610793 , 168.91514529, 163.90744937,\n", + " 159.30872409, 161.22889992, 166.4618311 , 164.82014259,\n", + " 166.54627525, 170.59062474, 166.89672584, 171.37230818,\n", + " 172.56836864, 174.91502189, 169.45461566, 164.03686578,\n", + " 162.86718127, 159.35024636, 159.35024636, 159.35024636,\n", + " 159.35024636, 164.79330115, 166.21123607, 164.53818332,\n", + " 166.59387229, 165.21029973, 165.21029973, 153.75652149,\n", + " 153.70601754, 156.30120648, 154.99847213, 154.06528216,\n", + " 152.38049465, 157.21714042, 155.37844415, 155.37844415,\n", + " 166.3532031 , 162.2219225 , 162.2219225 , 165.92241687,\n", + " 162.91693804, 161.54345862, 165.25969747, 161.41173921,\n", + " 160.12729216, 160.04671375, 160.04671375, 155.10054115,\n", + " 155.10054115, 151.54277574, 156.20414034, 156.20414034,\n", + " 158.28972063, 159.0961433 , 159.0961433 , 160.2542329 ,\n", + " 160.2542329 , 167.15862721, 161.53538756, 168.86996609,\n", + " 170.59331558, 160.34763811, 162.85526991, 162.85526991,\n", + " 162.85526991, 162.85526991, 162.85526991, 164.73393069,\n", + " 157.24693901, 164.76236388, 164.76236388, 162.19041439,\n", + " 163.25832419, 170.36938556, 169.65233338, 168.37571103,\n", + " 168.37571103, 158.27510305, 152.84301021, 152.84301021,\n", + " 148.70101444, 153.40124522, 156.09668121, 156.09668121,\n", + " 156.09668121, 160.86969805, 160.0161932 , 160.0161932 ,\n", + " 162.55405109, 164.72470761, 163.55966295, 159.94591411,\n", + " 158.38256804, 158.38256804, 159.02530303, 159.02530303,\n", + " 161.24694192, 161.09861966, 161.09861966, 161.09861966,\n", + " 161.09861966, 158.8929399 , 158.55867851, 158.55867851,\n", + " 163.34453048, 160.86853344, 160.86853344, 163.76257534,\n", + " 161.73214202, 166.53917701, 166.53917701, 172.88518503,\n", + " 172.88518503, 160.33426836, 163.13619036, 159.64915134,\n", + " 160.27881114, 161.36101841, 156.88682773, 161.30590736,\n", + " 160.16749935, 161.95684958, 162.89021729, 164.04725968,\n", + " 163.83782041, 157.21293026, 156.12096805, 155.38964207,\n", + " 157.24206286, 161.62641236, 157.13369476, 153.71414083,\n", + " 156.91847865, 156.27450737, 153.41309565, 153.33037232,\n", + " 160.02702647, 160.02702647, 165.94419359, 161.85453091,\n", + " 159.95495632, 164.6572024 , 164.6572024 , 171.6812486 ,\n", + " 171.6812486 , 171.6812486 , 171.6812486 , 163.73926087,\n", + " 162.96438118, 165.86366226, 165.7683476 , 172.6545079 ,\n", + " 171.53994244, 174.70176178, 174.70176178, 174.70176178,\n", + " 173.45447327, 173.45447327, 173.45447327, 173.45447327,\n", + " 170.43134523, 163.62679901, 163.62679901, 164.10961595,\n", + " 164.10961595, 164.10961595, 168.22669599, 172.38346727,\n", + " 167.3648038 , 167.26394025, 167.26394025, 164.13947706,\n", + " 167.67265939, 168.07638243, 171.63302849, 171.63302849,\n", + " 160.74345134, 161.22209867, 162.92264318, 161.52548994,\n", + " 161.06285511, 161.06285511, 161.06285511, 168.44030482,\n", + " 168.44030482, 165.76907877, 165.76907877, 162.35335898,\n", + " 158.98383436, 163.3323463 , 168.08246485, 170.37119289,\n", + " 172.23778205, 172.3273829 , 165.13961689, 165.13961689,\n", + " 165.13961689, 165.45061349, 165.7004434 , 165.7004434 ,\n", + " 165.7004434 , 171.22402769, 162.7129329 , 162.7129329 ,\n", + " 163.90187761, 163.90187761, 163.90187761, 163.90187761,\n", + " 167.87563872, 167.8691694 , 168.14538714, 166.22936559,\n", + " 160.17763312, 163.30187011, 160.3532131 , 161.92996218,\n", + " 160.66440192, 159.95967934, 159.95967934, 159.70976427,\n", + " 160.67884396, 165.73480601, 165.70324755, 164.2265909 ,\n", + " 164.2265909 , 160.44386082, 160.44386082, 160.44386082,\n", + " 163.95230192, 163.95230192, 167.83652781, 163.51078966,\n", + " 159.16038174, 154.50419389, 154.50419389, 158.13292387,\n", + " 158.13292387, 159.33065938, 161.39116373, 169.56402857,\n", + " 172.99501592, 172.99501592, 172.99501592, 172.99501592,\n", + " 169.49542693, 167.76505645, 164.37983239, 159.36390034,\n", + " 159.59761487, 165.48577321, 166.90190428, 169.29849968,\n", + " 171.51490121, 171.51490121, 177.93355206, 177.93355206,\n", + " 165.40665244, 164.09112016, 157.48746073, 160.57539486,\n", + " 160.57539486, 160.57539486, 164.99458225, 163.40561081,\n", + " 163.40561081, 166.18867696, 164.7502877 , 164.7502877 ,\n", + " 156.57048814, 162.83421632, 162.83421632, 157.91470416,\n", + " 157.91470416, 157.37399237, 157.37399237, 157.37399237,\n", + " 157.37399237, 168.76822492, 170.45974892, 170.45974892,\n", + " 168.1975803 , 170.13960248, 166.02910699, 166.57769994,\n", + " 173.46629566, 173.7024555 , 174.78569176, 171.56185946,\n", + " 171.16027208, 171.16027208, 169.42823893, 166.4952631 ,\n", + " 166.4952631 , 163.74940122, 163.74940122, 169.24272514,\n", + " 162.89931869, 165.51961332, 165.51961332, 162.12665412,\n", + " 158.66253372, 169.79110754, 162.68634969, 165.05554593,\n", + " 161.75139098, 164.26456392, 164.26456392, 163.96468708,\n", + " 163.96468708, 166.75169374, 166.75169374, 166.75169374,\n", + " 166.75169374, 165.49335825, 165.49335825, 167.36856486,\n", + " 166.89038163, 163.18718746, 165.89128487, 172.01590253,\n", + " 164.50854842, 167.45815085, 167.45815085, 169.83194224,\n", + " 158.12983636, 154.44339609, 159.55928253, 157.20855897,\n", + " 159.29093311, 159.29093311, 152.92278154, 152.92278154,\n", + " 152.98887096, 155.42590417, 156.00335888, 155.47746187,\n", + " 155.47746187, 155.47746187, 155.47746187, 157.29776618,\n", + " 157.29776618, 161.64553636, 161.64553636, 161.20540197,\n", + " 159.33804766, 159.33804766, 159.33804766, 164.41789954,\n", + " 164.41789954, 162.4851756 , 159.82033018, 159.82033018,\n", + " 155.95144546, 154.5083379 , 159.13710415, 164.87728244,\n", + " 165.07549497, 168.58591417, 168.31559964, 168.31559964,\n", + " 165.72677456, 165.72677456, 164.21904217, 165.11102767,\n", + " 167.97093691, 166.64420739, 165.0857398 , 169.75229922,\n", + " 165.19986687, 165.19986687, 165.8825144 , 166.69761132,\n", + " 163.68503125, 163.68503125, 168.8891204 , 168.8891204 ,\n", + " 168.8891204 , 170.98042585, 171.23742378, 171.23742378,\n", + " 165.95224509, 161.47625215, 161.47625215, 161.09922879,\n", + " 161.09922879, 159.87430048, 167.17324278, 165.08385323,\n", + " 165.06195185, 165.10380897, 165.10380897, 161.03305948,\n", + " 160.98059438, 159.46683136, 155.89176908, 159.8093935 ,\n", + " 162.14181906, 168.46782469, 170.93926352, 158.2205651 ,\n", + " 153.96684313, 162.28987989, 166.64698632, 166.64698632,\n", + " 166.64698632, 160.85044876, 160.85044876, 159.83272437,\n", + " 159.83272437, 159.48705699, 153.74012832, 144.73083568,\n", + " 146.97375507, 147.77334718, 147.77334718, 153.40456628,\n", + " 160.7742944 , 159.67852012, 152.86918466, 157.59614551,\n", + " 156.75712409, 156.75712409, 161.03281728, 162.83626266,\n", + " 162.83626266, 166.34951689, 166.34951689, 166.34951689,\n", + " 166.34951689, 167.52599377, 158.27663711, 158.27663711,\n", + " 157.92849313, 162.35929667, 162.35929667, 159.34259726,\n", + " 166.24611904, 166.24611904, 166.24611904, 166.46300417,\n", + " 167.42057871, 166.2234577 , 156.84124281, 159.83546443,\n", + " 171.74431711, 166.53781319, 169.21018938, 167.8192931 ,\n", + " 167.8192931 , 167.8192931 , 168.5189595 , 168.5189595 ,\n", + " 168.61772945, 158.20883765, 158.20883765, 166.04677991,\n", + " 171.6466151 , 168.39697678, 169.85385153, 162.15980549,\n", + " 155.89425515, 155.04490151, 162.74965621, 163.36238118,\n", + " 161.51324669, 167.22708837, 170.73111449, 170.73111449,\n", + " 171.9191686 , 160.81820656, 160.81820656, 156.15546078,\n", + " 153.86559347, 152.1149543 , 151.00863382, 150.87590269,\n", + " 149.44894301, 151.22750114, 154.20835417, 161.10878487,\n", + " 165.68761958, 165.68761958, 165.68761958, 167.51794455,\n", + " 168.2178574 , 166.92442422, 166.92442422, 169.4809118 ,\n", + " 171.12021344, 170.92781186, 165.62875104, 165.62875104,\n", + " 165.62875104, 165.62875104, 165.39100721, 167.00480234,\n", + " 165.70406098, 164.31339709, 165.23376533, 166.30263099,\n", + " 165.98326995, 165.98326995, 165.98326995, 160.51445528,\n", + " 159.09059547, 165.96534394, 168.00123584, 165.16885674,\n", + " 165.16885674, 167.18373716, 167.6605456 , 158.59315168,\n", + " 157.94606178, 152.86931183, 152.86931183, 153.81745193,\n", + " 153.47587837, 153.47587837, 160.50793342, 164.30014234,\n", + " 167.56897546, 162.50696984, 156.60166117, 156.27242333,\n", + " 156.27242333, 158.51629058, 159.16645787, 159.84655013,\n", + " 159.45016867, 155.76563256, 160.64390153, 161.01058529,\n", + " 162.53634289, 162.53634289, 162.17359019, 159.65263672,\n", + " 154.34354752, 154.34354752, 154.34354752, 164.73668223,\n", + " 164.73668223, 159.86910889, 157.60382139, 159.34970312,\n", + " 162.75657918, 167.95715086, 165.48788645, 166.77472952,\n", + " 166.77472952, 164.69757832, 168.93163975, 163.79266418,\n", + " 169.04981983, 169.04981983, 160.74446454, 160.74446454,\n", + " 159.27586659, 162.33149513, 169.7008955 , 169.70494669,\n", + " 169.20902743, 166.62702028, 165.57177257, 166.6411088 ,\n", + " 165.63550186, 165.63550186, 166.47831464, 168.1958733 ,\n", + " 168.1958733 , 172.50222179, 168.29182792, 168.29182792,\n", + " 160.6338896 , 160.6338896 , 159.02983321, 158.87824091,\n", + " 158.56439769, 163.0874263 , 163.0874263 , 163.08160482,\n", + " 163.08160482, 163.08160482, 163.08160482, 163.08160482,\n", + " 163.08160482, 166.7127585 , 161.11526686, 167.46673912,\n", + " 167.46673912, 164.14364387, 164.14364387, 160.29283972,\n", + " 161.23550551, 161.23550551, 161.28704182, 160.70781436,\n", + " 159.66947154, 159.51627788, 160.24441264, 154.28112687,\n", + " 154.28112687, 156.16026781, 156.64720201, 156.64720201,\n", + " 155.01656155, 155.01656155, 158.0734365 , 152.86937441,\n", + " 152.86937441, 156.7363079 , 160.07895158, 164.6490485 ,\n", + " 164.6490485 , 164.6490485 , 164.6490485 , 167.78852182,\n", + " 163.51397021, 163.51397021, 163.51397021, 163.51397021,\n", + " 167.93615163, 164.88309164, 164.88309164, 162.08941964,\n", + " 162.08941964, 163.66673198, 165.59184644, 164.91554413,\n", + " 164.90237629, 164.90237629, 164.37155363, 164.37155363,\n", + " 165.67189944, 166.08512869, 168.11979935, 166.28543934,\n", + " 167.6670334 , 171.02106722, 172.33015886, 172.33015886,\n", + " 172.33015886, 171.02340324, 171.02340324, 169.24938969,\n", + " 162.26184445, 161.97933596, 165.73213052, 165.73213052,\n", + " 165.86996899, 163.52848624, 160.24516252, 159.18614195,\n", + " 157.58053904, 158.53712732, 158.53712732, 158.53712732,\n", + " 158.53712732, 155.78253432, 161.60008456, 164.81454903,\n", + " 162.7100312 , 162.7100312 , 171.3551271 , 165.8360687 ,\n", + " 162.7703061 , 162.7703061 , 162.7703061 , 164.15559658,\n", + " 164.15559658, 165.71188363, 162.92834257, 162.92834257,\n", + " 164.01966302, 160.80100164, 160.80100164, 160.80100164,\n", + " 167.4897206 , 167.4897206 , 167.4897206 , 170.20456657,\n", + " 172.63206538, 168.51067186, 168.51067186, 167.89707637,\n", + " 158.98604629, 160.85320498, 161.37281917, 161.37281917,\n", + " 158.28134081, 166.16988824, 163.26727379, 163.26727379,\n", + " 163.77869581, 167.7633058 , 167.7633058 , 168.15748694,\n", + " 162.14036104, 163.01663463, 163.01663463, 169.19058004,\n", + " 169.19058004, 170.39142135, 171.31128067, 171.31128067,\n", + " 164.43299296, 167.10818197, 167.10906249, 165.72987253,\n", + " 165.72987253, 168.08697987, 168.08697987, 160.74280421,\n", + " 161.65770046, 156.57984497, 158.28544522, 157.12058585,\n", + " 157.49315026, 157.49315026, 159.72811589, 159.72811589,\n", + " 159.3664148 , 161.316131 , 161.316131 , 168.06578563,\n", + " 171.11527309, 171.11527309, 171.04936833, 171.89149477,\n", + " 166.00750622, 172.02675219, 156.99301855, 156.99301855,\n", + " 156.99301855, 159.63805922, 163.62971956, 161.41860126,\n", + " 161.41860126, 161.41860126, 161.41860126, 163.06202192,\n", + " 163.06202192, 163.06202192, 163.06202192, 172.83991426,\n", + " 172.83991426, 172.83991426, 172.83991426, 176.2047974 ,\n", + " 176.2047974 , 176.2047974 , 176.50610197, 176.3043219 ,\n", + " 176.3043219 , 175.2331223 , 174.70389089, 169.83849447,\n", + " 172.06541324, 172.06541324, 172.06541324, 165.72911607,\n", + " 160.74611821, 166.42910351, 157.68179303, 157.68179303,\n", + " 157.68179303, 172.79725727, 172.79725727, 172.79725727,\n", + " 169.20336706, 159.85115561, 157.58243593, 163.04249279,\n", + " 165.43860709, 165.43860709, 165.43860709, 165.43860709,\n", + " 164.82095725, 168.07448345, 163.67311147, 165.67186979,\n", + " 165.44907095, 165.44907095, 164.12819281, 164.79212185,\n", + " 165.85350577, 161.12132938, 159.51864276, 159.51864276,\n", + " 157.24341442, 158.47432603, 158.47432603, 156.50082285,\n", + " 154.5283718 , 154.44133097, 156.08038776, 163.33363849,\n", + " 165.19400307, 155.46329405, 155.46329405, 158.14095281,\n", + " 158.14095281, 167.38964641, 166.79297668, 166.80888074,\n", + " 163.08276177, 164.10706063, 163.11938597, 162.55787323,\n", + " 162.87193378, 160.71914503, 167.97913516, 168.18518569,\n", + " 167.88397254, 167.88397254, 166.08799778, 167.44310974,\n", + " 162.38177721, 166.99183281, 171.04453448, 171.04453448,\n", + " 164.15461027, 159.53724905, 161.19909322, 168.05966951,\n", + " 167.748911 , 168.26642117, 162.88139483, 167.45015664,\n", + " 161.7374061 , 167.16219245, 167.16219245, 162.31781819,\n", + " 173.77673233, 169.94432338, 166.42511498, 168.22421933,\n", + " 162.28917547, 164.05635366, 164.05635366, 164.05635366,\n", + " 161.98724108, 161.98724108, 165.55182713, 163.78438112,\n", + " 163.78438112, 164.12542001, 160.96716119, 160.96716119,\n", + " 160.96716119, 157.95110017, 164.83445612, 163.61647654,\n", + " 163.61647654, 163.61647654, 163.61647654, 163.61647654,\n", + " 166.37998911, 161.58755578, 163.45288961, 163.45288961,\n", + " 166.76940546, 166.929816 , 172.06708465, 172.57118014,\n", + " 172.57118014, 169.57906019, 167.30935556, 167.53155224,\n", + " 170.28177585, 171.99827985, 169.3999945 , 168.62110802,\n", + " 165.50885347, 168.21254188, 170.82088625, 170.82088625,\n", + " 170.82088625, 170.82088625, 163.35300948, 162.23277708,\n", + " 167.82413416, 171.48030206, 165.81905954, 161.21342676,\n", + " 164.0011153 , 165.93712419, 165.93712419, 161.60966657,\n", + " 171.00853133, 171.00853133, 171.00853133, 167.25569701,\n", + " 164.4916193 , 166.88653184, 167.07713068, 165.11172741,\n", + " 165.11172741, 165.11172741, 165.11172741, 165.11172741,\n", + " 165.81577077, 165.81577077, 160.50566138, 170.93076967,\n", + " 166.357395 , 166.357395 , 162.81905688, 165.66828762,\n", + " 159.79184678, 151.7425887 , 153.9614215 , 153.68535131,\n", + " 152.03309085, 152.03309085, 152.03309085, 152.33051898,\n", + " 152.33051898, 157.03494537, 157.03494537, 157.03494537,\n", + " 152.76640543, 154.03250681, 153.07539225, 163.10905579,\n", + " 161.73397683, 160.45636514, 168.63050941, 167.84714361,\n", + " 167.84714361, 167.84714361, 169.20717299, 160.89297466,\n", + " 156.16648795, 156.16648795, 156.16648795, 161.16717766,\n", + " 159.68509581, 162.30887465, 161.83826549, 161.83826549,\n", + " 169.43689959, 169.51836708, 165.91130719, 158.1094393 ,\n", + " 162.8626906 , 162.8626906 , 163.54700279, 162.85060379,\n", + " 165.00736551, 165.00736551, 169.23854291, 162.2396477 ,\n", + " 156.46659379, 155.93265726, 155.93265726, 164.2970733 ,\n", + " 164.2970733 , 164.2970733 , 164.2970733 , 170.90421275,\n", + " 168.36240671, 168.36240671, 162.8383561 , 162.8383561 ,\n", + " 166.97759037, 166.97759037, 166.97759037, 161.57692308,\n", + " 165.96866268, 162.15872942, 161.32927194, 159.69783567,\n", + " 168.23862214, 168.23862214, 161.63330599, 161.3934487 ]])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.store['h']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can look at a trace plot of the history of the markov chain" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "plt.plot(m.store['h'].flatten())\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conjugate sampler\n", + "\n", + "When the prior distribution chosen for a parameter is conjugate to the likelihood distribution (see [here](https://en.wikipedia.org/wiki/Conjugate_prior) for more detail), this special structure can be exploited in the MCMC sampler. In this example, the posterior for $h$ will also be a Normal distribution (and in more general models, the conditional distribution given fixed values of the other parameters would still be Normal).\n", + "\n", + "The `NormalNormal` sampler accommodates this situation. Exploiting this structure in the model generally gives rise to a much more efficient sampler, with no step size parameter that needs to be tuned." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A `NormalNormal` sampler for this situation can be set up and run as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1000/1000 [00:00<00:00, 7239.76it/s]\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAigAAAGdCAYAAAA44ojeAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAACmYElEQVR4nO2deZwUxdnHfz277HLuwoLLsnKKyiGHCIggIggR0KBRkqhBg0fk1XhCogkajcYYiBpNYohHoqLxIDEq8UhQRA4PbjlEkUsU5BQQ9oA9p98/hpnpo6q7qrt6umd5vp+PMttHVXV1ddVTz/PUU5qu6zoIgiAIgiAiRCzsAhAEQRAEQVghAYUgCIIgiMhBAgpBEARBEJGDBBSCIAiCICIHCSgEQRAEQUQOElAIgiAIgogcJKAQBEEQBBE5SEAhCIIgCCJy5IZdAC/E43Hs3LkTLVq0gKZpYReHIAiCIAgBdF1HeXk5SktLEYs560iyUkDZuXMnOnToEHYxCIIgCILwwPbt29G+fXvHa7JSQGnRogWAxAMWFBSEXBqCIAiCIEQoKytDhw4dUuO4E1kpoCTNOgUFBSSgEARBEESWIeKeQU6yBEEQBEFEDhJQCIIgCIKIHCSgEARBEAQROUhAIQiCIAgicpCAQhAEQRBE5CABhSAIgiCIyCEtoCxatAjjxo1DaWkpNE3D7NmzTecrKipw4403on379mjSpAl69uyJxx9/3HTN8OHDoWma6b/rrrvO14MQBEEQBNFwkI6DUllZib59++Lqq6/GxRdfbDs/ZcoUvPfee3j++efRuXNnvPPOO/jpT3+K0tJSXHDBBanrrr32WvzmN79J/d20aVOPj0AQBEEQRENDWkAZO3Ysxo4dyz3/0UcfYeLEiRg+fDgAYNKkSXjiiSewbNkyk4DStGlTlJSUyJeYIAiCIIgGj3IflCFDhuD111/Hjh07oOs65s+fj40bN+Lcc881XffCCy+gTZs26NWrF6ZOnYrDhw9z06yurkZZWZnpP4IgCIIgGi7KQ90/+uijmDRpEtq3b4/c3FzEYjH87W9/w7Bhw1LX/OhHP0KnTp1QWlqKtWvX4he/+AU2bNiAV199lZnmtGnTcO+996ouKkEQBEEQESUQAWXJkiV4/fXX0alTJyxatAg33HADSktLMWrUKAAJs0+S3r17o127dhg5ciS2bNmCrl272tKcOnUqpkyZkvo7udkQQRAEQRANE6UCypEjR3DHHXfgtddew/nnnw8A6NOnD1avXo2HHnooJaBYGTRoEABg8+bNTAElPz8f+fn5KovaoPj3yq9RUtAYQ09qE3ZRCIIgCEIJSgWU2tpa1NbWIhYzu7bk5OQgHo9z71u9ejUAoF27diqLc0ywcU85fv7yGgDAl9PPD7k0BEEQBKEGaQGloqICmzdvTv29detWrF69GkVFRejYsSPOPvts3HbbbWjSpAk6deqEhQsX4rnnnsPDDz8MANiyZQtefPFFnHfeeWjdujXWrl2LyZMnY9iwYejTp4+6JztG2HWoKuwiEARBEIRypAWUFStWYMSIEam/k74hEydOxMyZMzFr1ixMnToVEyZMwIEDB9CpUyfcf//9qUBseXl5ePfdd/HHP/4RlZWV6NChA8aPH49f/epXih6JIAiCIIhsR1pAGT58OHRd554vKSnBM888wz3foUMHLFy4UDZbgiAIgiCOIWgvHoIgCIIgIgcJKARBEARBRA4SUAiCIAiCiBwkoBAEQRAEETlIQCEIgiAIInKQgEIQBEEQROQgAYUgCIIgiMhBAgpBEARBEJGDBBSCIAiCICIHCSgEQRAEQUQOElAIgiAIgogcJKAQBEEQBBE5SEAhCIIgCCJykIBCEARBEETkIAGFIAiCIIjIQQIKQRAEQRCRgwQUgiAIgiAiBwkoBEEQBEFEDhJQCIIgCIKIHCSgZDla2AUgCIIgiAAgAYUgCIIgiMhBAgpBEARBEJGDBBSCIAiCICIHCSgNCF3Xwy4CQRAEQSiBBBSCIAiCICIHCSgNCFKgEARBEA0FElAaECSfEARBEA0FElAIgiAIgogcJKA0IMhJliAIgmgokIBCEARBEETkIAGlAUH6E4IgCKKhQAIKQRAEQRCRgwSUBgS5oBAEQRANBRJQCIIgCIKIHCSgNCB08kIhCIIgGggkoDQgyMRDEARBNBSkBZRFixZh3LhxKC0thaZpmD17tul8RUUFbrzxRrRv3x5NmjRBz5498fjjj5uuqaqqwg033IDWrVujefPmGD9+PPbs2ePrQQiCIAiCaDhICyiVlZXo27cvZsyYwTw/ZcoUzJkzB88//zzWr1+PW2+9FTfeeCNef/311DWTJ0/GG2+8gZdffhkLFy7Ezp07cfHFF3t/CoIgCIIgGhS5sjeMHTsWY8eO5Z7/6KOPMHHiRAwfPhwAMGnSJDzxxBNYtmwZLrjgAhw6dAhPPfUUXnzxRZxzzjkAgGeeeQY9evTAkiVLcMYZZ3h7EoIgCIIgGgzKfVCGDBmC119/HTt27ICu65g/fz42btyIc889FwCwcuVK1NbWYtSoUal7unfvjo4dO2Lx4sXMNKurq1FWVmb6j7BDPigEQRBEQ0G5gPLoo4+iZ8+eaN++PfLy8jBmzBjMmDEDw4YNAwDs3r0beXl5aNmypem+tm3bYvfu3cw0p02bhsLCwtR/HTp0UF1sIgO8v+kb3PDixzhQWRN2UQiCIIiII23icePRRx/FkiVL8Prrr6NTp05YtGgRbrjhBpSWlpq0JjJMnToVU6ZMSf1dVlZGQgqDqC8zvuKpZQCA/NwYHv7hqeEWhiAIgog0SgWUI0eO4I477sBrr72G888/HwDQp08frF69Gg899BBGjRqFkpIS1NTU4ODBgyYtyp49e1BSUsJMNz8/H/n5+SqL2iDJFhPProNVYReBIAiCiDhKTTy1tbWora1FLGZONicnB/F4HADQv39/NGrUCPPmzUud37BhA7Zt24bBgwerLE5WMXvVDryy8uuwi0EQBEEQkUBag1JRUYHNmzen/t66dStWr16NoqIidOzYEWeffTZuu+02NGnSBJ06dcLChQvx3HPP4eGHHwYAFBYW4pprrsGUKVNQVFSEgoIC3HTTTRg8ePAxu4LnSE09bv3nagDAqJ5tUdikkad0skSBQhAEQRCuSAsoK1aswIgRI1J/J31DJk6ciJkzZ2LWrFmYOnUqJkyYgAMHDqBTp064//77cd1116XueeSRRxCLxTB+/HhUV1dj9OjR+Otf/6rgcbKT6rr69O/aesCjgEIQBEEQDQVpAWX48OHQHZwdSkpK8Mwzzzim0bhxY8yYMYMb7I3whtN7iRKaFnYJCIIgiKhDe/FEAA3H1ohNAgpBEAThBgkoEcDP8mDjYJ8d+hOCIAiCcIcElKghqV3IEquOiWNNY0QQBEHIQwJK1PAhcGSLsEImHoIgCMINElAaElkioBAEQRCEGySgRA3SLhAEQRAECSgNiajvxUMQBEEQopCAQhyT/OW9Tbjt5TVZEzuGIAjiWIMElIjx4tJtqKqtd7+QQbaMtVoEvGQfemcjXl75NVZtPxh2UQiCIAgGJKBEAKNg8cd3N+GRuRvDK8wxhldhkCAIIgzWfn0QG/eUh12MjEACSgR5f9M+4WuzMVBb+PoTA9lSaQRBHPPsr6jGBX/5EOc+sijsomQEElAIgiAIIgvYdagq7CJkFBJQIoCqSXy2OHxGwAUlTZTKQhAE4YBJY54l/b0fSECJAKoaWrY0V5IJCIIg/HEMyCckoGQ7x0IjJQiCIMzEj4HOnwSUCGBtZl5NINnSXqOwzJggCCKbyZLu3hckoEQAP4LFsdBICYIgCPNO8KRBIbIKCnVPEARxbHAMyCckoEQBP4JFNnpyh23giUqdrd5+EEu/2B92MQiCyBLMq3jCK0emyA27AAQUrjNWlE4DJwofdjyu43szPgQArLrrO2jVLC/kEhEEkU0cCxpz0qBEAD/NLBubaNg+slGos3qDlHTgcE2IJSEIIhuJR6EjCxgSUBoQx0B7VUJUTDwE0ZDYtKcc72/6JiN5xeM6Pty8D4eO1GYkvyhyLPRjJKBEAF/tzHBv9rTXcFUoUaim7HlXBCHGdx5ZhCueWoYNu4PfyO6FpV9hwt+X4qK/fhh4XlHCqH0OSoMSj+v4+/tfYHUEdnonASUC+HKSjcRwK0foJp4IVFk2vjeCECETO+2+vmYnAOCLbyoDzytKGJcZB9WFvLpqB3771vqUj1yYkIASAVQNmDToiRGFejK+87BXNREEkX0EFQclEwKmKCSgZDlR0AZkG1GoM5OAErZK6RiiPq7j891liB8LHoZEgyaoFhwl3xYSUCLAsRbqPmyiUE9R0OKooLK6LlIdmhu/fn0dxvzxffxx3qawi0IQvqBIskRG8NPBZ2MbDVtfEAXhIBvfm5Vt+w/jlF+/jatnLg+7KMI8v2QbAODPJKAQWYix72oIfYgbJKBEEK8NL1vaa9gWjSh82BEogm9mLU8M9vM3ZGZp6bHM/opq7Dx4JOxiCBH2992QMfZd2aS59AoJKBFA1WaBx0KDVUEUasn4rqg/J9zo/9t3MWT6eyirOnbjfhDmseJYcKMiAYWDruuY+upaPEqqYOVoYcdBiYAgdyx0LoR6tu0/HHYRXInA59VgMZl4IjHVChYSUDh8urMMLy3bjj/M3Rh4Xr40KPqxZZNUQSSqKRKFSLPki/3YU1YVdjEIF+gbP7bJhAYlSm2MBBQOR2rrwy5Cg+Hz3WW44qmlqb/DtlEbP8CFG7/BT55dgb3lmR2cozT7WbxlPy59cgkG/W5e2EUhGBgnIdmwciPs7zuTxOM6Nuwuz9iydfJBIQBkVor0F0nWPxv3lGPMHxdhzrrdClKzM+FvS/H+pn2pv0PvwAyV9sTCL/Du+j245/VPM1sEBS/uSE09fvHvtZj/+V5f6Sz+Yr//whCBYRqUGOdfW/U1Lv/7UhykTSczzoPvbMDoPy7Cb99an5H8aBUPASCz0mnYDe3ml1bh893luO75lYGkv78yWh0nSyDcfSjTGhT/PL5wC/65YjuuyqJlvoQ8bm1l8j/X4IPN+/CnDPvLrdtxCJc9uSQSe7aExWMLtgAAnv5wa0byM2tQMpJlqEgLKIsWLcK4ceNQWloKTdMwe/Zs03lN05j/Pfjgg6lrOnfubDs/ffp03w+jkkw6MfraK1BBgy2vqvNRgsxTXefP/Maqp0x/66ZVPB41SrsOZceyUyLB3vIqrNtxSPq+uMnPjN9SM/0dX/a3JVj8xf5I7Nnil6raerzz6W5UVke7LzS+/Www9/lFWkCprKxE3759MWPGDOb5Xbt2mf57+umnoWkaxo8fb7ruN7/5jem6m266ydsTNECCNIF8W1mD9bvKgssgYP65fBu6/WoOZq/a4TmNKHzWcYZgWV1XT8tIGzCn3z8P3330A+ndfkUdI2MZNp2GObFRPTbf8eonmPSPlbj1n6vVJqwYk7AaYjkyRa7sDWPHjsXYsWO550tKSkx//+c//8GIESNwwgknmI63aNHCdm2UyKQToz9zktiys7KqWnz81be48pmEOeDNm4ai1/GFPvL1jp9lxr945RMAwK3/XI3v9TveUxqs+ma9gnhcRyygXp/1roY9MB97yqqx5u5zUdi0kWsaoS/XDjX37GXFVwfQraSF8PXmtsKv9Vjozl3Zy6tHJzxzP9sTckmcMQurwXyBUfquA/VB2bNnD9566y1cc801tnPTp09H69at0a9fPzz44IOoq+NL49XV1SgrKzP9Fzict7RpTzkenbcJh2vUzR5UNQin9jrhb0tTwgkAfLh5H//ioAl7FY/Asc93l6Hvve/gyUVbAi9E8ueesmoAwKrt3waTJwca1qKNqBn3WJJPjqVnNXNsOclKa1BkePbZZ9GiRQtcfPHFpuM333wzTjvtNBQVFeGjjz7C1KlTsWvXLjz88MPMdKZNm4Z77703yKLa4L377zyyCADw7eFa3D2uZ+YKxEG0kX5isX0fA22bi0id3TV7Hcqr6/C7/36OScO6qi+DqTzmAonOjI7dTppPTV0cX+6vxEnFzRvkLtFOLSMqzxu2Zq8hQ8uMFfL0009jwoQJaNy4sen4lClTMHz4cPTp0wfXXXcd/vCHP+DRRx9FdXU1M52pU6fi0KFDqf+2b98eZLEBuA9iKme5qtqZyua6cU85vv/YR/goAE1L2N0X0xSW4Y/daeloPJ7RojQoJj69DOc+sgivr9kZdlGUIapBybQPCuGf+riOvy36AmsEV0KJGfv8ESW5JzAB5f3338eGDRvwk5/8xPXaQYMGoa6uDl9++SXzfH5+PgoKCkz/BY2bD4raVT7hxkFhMem5FVjx1bf40d+Xul+cZUThA3SKZxB17/x/r/waf12wOexiMEnGdPnH4q9CLok6RFfx5EREgxKlIIReqKmL48Wl2zKyrcDLK7bj/v+ux4VHV0Lpus4NGlkf1zPigxIlAjPxPPXUU+jfvz/69u3reu3q1asRi8VQXFwcVHF8oeu6TX2qUr2mTIOisEz7KoKLXRK2KjoKy4zNAq7VxCOWRhjVWFsfx89fXgMAGNUjmt9rQ0N01hz2d9VQ+Nv7X+DBtzcAAL6cfn6gea352mx6v/3fa/Hyyq/x1wmn4bze7VLHN+8txwV/+RCndmiZOnYMyCfyAkpFRQU2b07PnrZu3YrVq1ejqKgIHTt2BACUlZXh5Zdfxh/+8Afb/YsXL8bSpUsxYsQItGjRAosXL8bkyZNx+eWXo1WrVj4eRS1Wtar124+K9BqRYkgRdjcahRme0x5KUbYtj/3T+6nfh47QkuhMILrfVlTkk2z3QVmSwcjKVZYtVV5e+TUA4E/vbjIJKNP++zkO19Tjoy3psgU1BkWlHQEeBJQVK1ZgxIgRqb+nTJkCAJg4cSJmzpwJAJg1axZ0Xcdll11muz8/Px+zZs3CPffcg+rqanTp0gWTJ09OpRMV3F5+vUI/AX+LjINZFx+lRqqaKIz/Vh8U834roqlk/iVt3luR+h2FejwWcHKoNkLLjLOPIzViQSdZrzao7y9K37W0gDJ8+HDXGd6kSZMwadIk5rnTTjsNS5Yskc02VJjLUiNo4lFJkF1d2P0o+32qS//rbw/jj+9uwjVDu6BHO3d/KV0351+f4Qbh9X1EsNmmCLuNqUS0OZCTrJ2PNu/Dln2VuOKMTsL3ZPLz87MpbRTHDdUEusw4m7HPWsxfv0r1mq/NAgU9/J3uY9GQ7dlBm1Cuf/5jfLLjEGav2oHNvzuPUwbDb+jCjpBRIsrljHDR5E0gDiu+TOk24G/Wiuj7TTr5d2vbAqd3KQqwRN6wmnj42N9tFEzVQUObBfJw6RSC3KvHuw1XXaEacl/HdpJVV3ef704EEqxzaCTWVTzGS6Pi30REA7Pwyr+uIX+zfvn6W/EVOZkc+KvqvPsKBDUGRUnwIQ0KB/dlxtEw8QTVlAI18QSYtiqClhGse/EY21O2xEGJTjfWcNl+4LAp6rNTv0M+KHyiWjVVgj4oLI6FiQwJKBzcTCcq24a6ZcZq0gHM6mJd17Fg4zc4pV0BigsaO9yVHQT9PsXKYHRulotvsKesCv9e+TUOVLIDG2aKY6B/DJ2zHphv+tupysP0Qcm0uU9W4JDRSkfRByWTTrJRgkw8HKw+AlaiIr0aO4a95eoGLOP38PqanbjqmeUY+fBCNWmHHQdFYO7vtYi19XHU1runb71CVI0PJKKlPvj2Brz9abQ3NuPx7EdfYsi0efhyX2VgeUTj61SPkyAQ5vLeiHSHSoiigMIiyj5gqiABhYPZSdZ+PipOskYmBBT1NbnDp6rt1cPWtgapQRn36AfSZdB1c3tzW8Xz+e5yDyVTj9cq+/Xrn2LnoSrc88anSsvjh2zp7J2K+cyHW3HP65+G/ixRNaeIklEfFD8CisJyRBUSUDi4feQq/QSi2DcaO5lqgyPXDx9fjPogPYQzAHOZsaK0xYUHc44mH5RMq8u9iow+y1knoGnKBNV19Rj9x0WpCLlRxqltVNbUY+ZHX9qik2YCtwld2PCEpuq6ejw6b5PpWCbLX10rNpCwih/P8n5YBBJQOLi9+rBnKX5xmyUYzTBGAWXZlwew4ssD/jIPOw6Kx3f33ud78PDcjUo6BpsGxdBPZUu/47eYUVktsHDDN9i4pwL/PhrFM8pY2warLZeFEOE3W/vDv7+/FX+YuzG0/P1MRoKq8Si9SnKS5eDmJBuVQcRrY3KNg2L4bVVD+n72kOuOqUERqMirZ64AAPRs1wJjerVzudoZ0yoehXFQDh6uQYvGjZCTBVG7wugINc2eb1S+ZRGsAxpLm1mrMsy1IMZSZJOJ57NdZbZjmWwOonXFui4qfpBBQhoUAYJ2ko1iO+OZeAD/M9+gH9dtgPdb37sOsXcbrZMYGOxxUNJ/ezWhfbmvEqf+Zi7GP/aRp/tl8VuPQbZ7XhvI9qW4Vu0dy19JxElbNVHsw4xwHfMjXm5HsrnsgpCAwsV5VYXXWVd5Va0taJCvSLIBtVKjX0K1D0cuFkFK/p98fQgD75+Hl1ds517jVx3NG+IOS9STdS8eY3vyKqDMXr0DALB6+0FP98vi9z2GMQPMbvHE3u+wfOHqQgikExVzHQ/ee2e2QYdHmf/5Xpzz0AJ8vO1bJeUSheUnJtNNxOM6Nuwuzzq/FRJQOLj1nV4HuX6/mYuhv5+P7QfSQoo1qShM8oxlqLFGO3R49J0Hj7gOsMnnPVyjZlWQkRtf+hj7Kqpx27/X8vP3mQdvNna42qOAouum9uQUgdaJLOt7QjPxhI2fMthMPEwNSpZE+vNBWLGjrpq5HF/sq8TEp5epKYAgzDgoEj3Z/f9dj9F/XIQH39mgsFTBQwIKB53zO4nX2V9y8Fnu19H0KEF18sbvwWri4fHOp7sxZPp7uOGFjx2v0wE8v+Qr9Lz7bfxrOV/T4QWRlSEiy4y9hBSvlBC4rLtQi2hQXlj6FX7s1DFmeMT3beIJYdYdZqyQJH7qTcgHpY5MPKKw2qBIuzzsIwKsKmQmJE99sBUA8NiCLQGVJhhIQOFgneFaUbnUNurfdnWd2Mf4xKIvAABzPt3teJ2u6/jV7HUAgNtf4Ws6vCCi2fI7MHJNPJ41KOaBhydk3fnaOiza+A03zUzvguzbVzpIHxTOcZZwGQWtiihWAYWlsq/Nlr0SMgjvHXvtxlWtWvIjMGfryikZSEDhYJ3h2s4rdBD009BUtFF2p230QbE6yXLSEcxP9WdVWV2H9z7fg+q6eqG02WZn80GnQYtn4pHSoJiy0y1Ost4GGK+drdcB2m8HGUb3GgUnWV8mHkvTYAmlYcSXyfRYqeo1RmHbCxGO1VD3tMyYg+tePJkrSuhYO0HehyHcaSiuvJ++8DEWbvwGEwd3EsveZ6fEe84jEmpf6yoeY/5efVCyrcMKYwYYAfmES119HDM/+hKDu7bGKaWFzGuENCihLDNW/y6T7cPr1hjG9sXTVHhtg1H41KLumKwC0qBwcHv1akPdh3NvKg1GIsY+IcfSQfA+DFF1peoPa+FRs8dLy7YLDdL+TTzs55RpE/ZVPP6dZLNN5RukUy+vJUZBg8LjhaXb8Nu31uP8P3+Auvo4Jvx9ie0aaxtjtZWakJ1kVdSwruu45MkluOKpZd6FCMNtMquMvWphg4S5iieg1xylfoQEFA66dQSx0NDjoBg7ctV9erC+B+6J7zxoj2MiUyQVMdCs+RnHGa8q+mwL3ORW2tr6OF5Y+hW++KZCWZ7RFU+AdTvSIeo/2LwPH27eb7vGKo+wfOEagoln16EqLNt6AB9s3ocyj3uAiXwPURqMZcnekotDJh4B2IHa1Obg+c6APjCTBkV0RBa8LNgAXc7nl395ANc+t8JXHtzZmMRzGd+bPVBbZn1QvOL7Pbok8OxHX+K3b60HAHw5/Xy5pDnHI6xAMZWZJ2TYTDxMH5RwI8lGBeP3wI+DYj+WUaFF4ZLzhghpUARg+yxEo3GoKAXTSdbw26oW9/voQX5YbinPWsZe1qzifcqkYLxWXRyUzLbJoCMKq1qKbyQW4S0ARF6fiAalNoSAOKr7Q1OX4zFpIQ2K4LHQyaCTrFefnyAgAYWDi4VH7TLjCH4RmsnEI9Zg/frIllXVoqzK30ZnQQXYM6LC4c6kQYF1bx5vZLodGfPbtKcct8xahS0S5phQArW5nA9z4iEi8FmdYlmDcK1g3CKVqK61TMWricpE0wtBlT1KdUICCgc36VvlJMVXUh5vlmmEOZZW4ld9zsq6rj6OPve8gz73vONzFYLzc/HOWo97CdQmg1McFK8dhFub3VtWhccXbsG3lTWm414fx5jbD55YjP+s3okr/r5U4v7Md4Ruwnao0XiFNCjmi1ifSkOPJLtt/2Es//Jb1+uMdSVjllWxok81rGy8tNUIKUeEIAFFgKAlSmvyUWhDTiYe/j3266w7ISew12e5wRHu4GHvWhS3V8UdxKU6JY4GRTwJS6Ri3eSRby3iO5/udgzQlsRNQLn2uRWY/r/PcdNLqyRKKkbyne3kbKTIws3Vxs9nx10K73pf8BIKrwzGnHmf3FMfbMWQafOwbX9iq4zomHjEr62orsN/Vu9AuaC21CrIDntwvtB95mrgfbPR0RY4wepzvJQ9yqvYWJCAwsHNxOM7fYdza74+hPc+3yOYTlCGyPRPuw8KO09W23/wbfveD8bbky4BmeomVPTd3AFGyknW+IdZuDD+3l9RjUn/WOkc4j51n/P5NV8nVol8sHmfeEEdCDpQWxBtIgoaFK4WT6A+v/72CHYeqsJv3/oMQHScZGVe1s/+tRq3zFqNW2et5l5jfE1em5kuoEFhCcky2fkd7v3c76WtirhgRUlkIwGFg1nlHmxerI7p6pn+Vpr4xaRBETTxsFiwYa/tmPF+lkQvI+RbNzJ0HfQ4L9N61DmSLDd1l9zZ5dBhN/kkOXhEXJukxNFXxo8mg3nxeH/TN/hqf6Xw9W5tK8wZtUzOSc0JU4MSxjJjidK//Wli8jXvc3vfkMT4mrw6fzsN4B9u3of3N30TugbFT+5evp8oOcCKQMuMOWTST8hfI1VWDBPGhixs4hH2QUkXOpm2l49t5VffYvxjH3HTZuctnY0NFWpSkwLFpkFJn5PJSUXgJl13stfrtmuDxC39Vdu+xRVPJTRLosuQWTNI04KRDHz3KjRwSbyu+FKNsez/Wb0TY3u3U5e21/tMkWTTHKmpx4SjvlK9ji9g3egxx+BgtRkvxYzwIjYmJKBwMO/FE2yD3bxXXSAqVRgbsjWSrN/qcDMNs76hZz7civ9+sst07J7XP3VOm5k3+4qt+ypRVVuPxo1y2Pd5dLizcrimDk3zcs0aOstePMYyysx43GaaMc1dLex02pq8bxOPz3a0ZvtB6XvchMtMjE1cE4/ht+hrZ5lzwliFYcxxzqe78U15NY5rka8kPa8aFHMk2XSFHjbsmcXa4FPKxKNpvhqNH3mBfFCOYUwdecDfe3JnXy8EVTSjw6to7Ai2uYbh3OXBB+XeNz6zee7byiXQVzhpGZJbkrMQ6YPcLpkxfzN63v025qzbbbo4oUFh5yWlQXEpQBOO8GXEaXCznuHl952HF2LXoSPueRlSXLjxG/z85TWoqBaPGuokvPGewq2DVhVLpqq2XkhQqKuPo/LoM3sRLFjmHKdUdh48gqmvrsWG3eXSeclw6EiN+0UOqDCx896lq5AukZ+f4X7nwSOoFlwSzmq2XjSmJKA0EMwz2oaH20dobMdWOUBGcmeqJk3nxYQaFjnM4EUuJh6Hsn9TXs09Z16yyIuD4ph1ymF46qtrLat4LD4pAtoadv7OBWiSxxZQTA6JEunzYgFt2luBB+bYnaP3V1Rjx8G04GK8feLTy/DvlV/j0XmbjDk6lMbbkkmjPxWrvlR861u+qUD3u+bgZ/9awzxvLPaYP72PU379Nr6trJGcuSf+rZUcpX756id4adl2jP3TIqn73LC3Db/ppX+r8EHhfV9+vVy9jvflVbUYMv09X3l7qZUsk09IQOHBc1qMGpkom2qp2+yDkjwmnw4rBL+ricfhAqfn9OoXwsvHWo64zv4tE7DKrSPnma+MOCVhPeUkEFXX2VXn/X/7Ls40dMqs+78+6K55SeLlPRjNlSz5yq8GZfPeCoz8w0IAwKurdghdDwAfbdnvacRhBWVzeoQtR/NT7bpiTc5vIEurj5a3NNiTTB28P+z3AYlneebDrfhsZ5lrnpv2lOODTe6r5LYfEG/nPLy0VZFvJkrjHfmgcIhSNL0gkLF3WzUG3BgTgoKM2cRz1ElWlT3VzcTjcN6p+EJBnwSfQdM0c+epW31QvOF2n5CJxyEVmw+KQJmc83I+yA6ipePTnWXoelxzT9NBY5upj+s2IdfvZz/q4YVC1+m6bor3o2nevgHZFTttWuSbtFiqsAvcfv2T0ver8EHxM+GctXwb7n3jM6Frv/NIQjM1d/IwnNS2Bfc62WditnQP1RLlrR5YkAaFgzn0uH9h5YNN+7BxT9ruq0oAUlE2VlFMoe5952DJz1hmLXXQUB6xZ/KmQeFfYUzO7hCa/s0Pde+SefJ+i6+MDn4kWZkx2G3Wmt/I/XN3eoYVX5n3xpHpZK0h2hOZsQ45C2qzV+/Adx/9AJf+bYmndmnsoFn1lYmJSXVdHFP+tQb97ptryTv9W1RzVscw8Tg9QZtmeULpJnnqg60464H3XIUaaz/kd3WRCg22uX2yBR5W0tb81u3ga05478lt4YOKZuZFcJPVhs93WAqeCUhA4WDdbdYPm/dW4PKnluLcR9TafYPEtPTSwz2pY0w/kfTvtAZFPr+Ptti3o3f3QeHj9PEaO2DeJESmnuIWCUVFh+x2n9G8wY0H45DGj/5mDmMvMwaxBiyRwcHKP5cnNntcs/2gNx8Uwz31LB+UDChOf/36p3iNYf7xkrc1DlAiHX5CrQwCCjvKs5n73vwM2w8cwe//97lUufxrUPynxdWgGK5hpa2iDbi1TbdnEmnbXoop+8k889GXHnJRBwkoHFRs3pZk897gPOZlZu0ymCM5mjPhm3jE0mau4uF0JvVxXW4DOpfzTh2Dk4nK5BfiU6UU0zSLQOa0zFg8XTdtmpv2QCQN07VO5jJLV8jKz21wcBM2vWwoZzLxMMwjQe0ILaKZyYSJJ9fQBmokPFldfUqsJh7fPij+J4g8TYmxbL7fN6cJukcsdplIWU6z0vPkgyLZeYVtECIfFA4qNm/LZpzaMa82RBuzWRuRuIs3QN/+77V45eOvBVN2x6nfNM6urc9v7gx4Jh6xdhLTYPO1MDnJelwB4TaIGAfn2noduQyXFJmmLvNdME0RLiaeIDB20EkNihftnSyuq+YErmHBqlcnjG1El7nV5eO2Ft23k2yQGhSXb00mN161uPWFstUTVqC2sFf9SGtQFi1ahHHjxqG0tBSapmH27Nmm85qmMf978MEHU9ccOHAAEyZMQEFBAVq2bIlrrrkGFRXRClamUiYJQr55a+0u/Gr2J8L7bljbWV1cx2c7y6DrOrMROpo7OA/Eks5Zs1yTrZ0hoBh7CFnhxK2unX1QHJ7ZUM1+P1q7kyxfQJNpO26dntHEY5w9G9+bTFOVGTjqmPE6/KnX/b6H5OCuYjD0S8JJ1njA9Q4AHBOPw10mAUWhOGatNqP5rKK6Tiq+DWA1wzhfu/brg65lMj4rb98rHt5Mic43uWoHrbd7CKnAQsQHxaTBlc5BLdICSmVlJfr27YsZM2Ywz+/atcv039NPPw1N0zB+/PjUNRMmTMCnn36KuXPn4s0338SiRYswadIk708RACpUjEFyw4sf4/kl25i2bBH+PG8Tzvvz+/jLe5uZ54NsmCZdBMvE4yttOdWpEafZhWkVj4e0jbCcZM2rFsTSkc7fUHCeYCvT6fn2QWFqUNi/WTjOBrk+NoZ6TlWBosbnVJxgkmWbeBwy422p4IZbf8BamgsAtfVx9Pr12+j167elNjHkxi1hcMFfPmQKQLxn1TnXiOYngpsc4KZhEimCl1LKa1DCFVGkTTxjx47F2LFjuedLSkpMf//nP//BiBEjcMIJJwAA1q9fjzlz5mD58uUYMGAAAODRRx/Feeedh4ceegilpaWyRQqEiGxx4cq+CnPExoRGRLxR/WX+ZhQXMEJSmxwqzaf8mniMCbj5oMjiqkFx+KydfVB0oeuszJi/GTeMONF0zBoHRdd1xOPs+lZpcjGe5/kuBKVBYa+YYeQvNWj66zyTGhReDBoV/Gv5dvxwYAcxHxQvJh7JiGj1pmcVz9Ctzds0KEcr8tvKdP9UXlVnctJlUV5Vi5/9aw16HV+YTlugfPsrqtE83zyc8cz0XoU0FrxqcY9Y7C9fwO7nU1MXx97yKrRv1ZR7T7b5oATqJLtnzx689dZbuOaaa1LHFi9ejJYtW6aEEwAYNWoUYrEYli5dykomFDK5m7EfbDsNc8rKjX4qkId4fA+xY2YVoj0Oih/Vs9udjo6djhoUkbztFyWjxxpJ7IljfF41Pk9uA45RSKjlDGwsx1EeosXUdR3LvjzgfmHiauH8vfSeLP8DnilABbe/svZoum6YHZNEH433HnkYBRpdT9x/8LB7WHrZqk62Rdna/Mv8zXjnsz14eO5GW1pOyJi6jAN7YP6Fbj47kvkyTeWWv3/wxGIM/f18LNvK/9as4wW7bIZ8s80HRYZnn30WLVq0wMUXX5w6tnv3bhQXF5uuy83NRVFREXbv3s1Mp7q6GmVlZab/gkapD4rgMS9YN/Lzrd3weL0MxsE+qUHh7UUjQ01dnNlRmfPmJy66zJh7DeeS/RXmEPq2VTw6XwUtM2C6CVFG2YO3guOaZ5dL5CdWtjnrduPml1YJ3S+jPfLSRo31mdagqJtR+4Hlm+VGjaBvTxKj5kzXdZz/5/dx6m/musY5cSuONcd6pvDnzr5yu7CUFCTnf74Xb6zZybyPtacNL1SE8R37deY1YhR8eH1JWVUtpvxrNRZs/MYxLZHXXx/X8eLSbbjvzc+g63pq88x/r9zOTzdSI4E7ga7iefrppzFhwgQ0btzYVzrTpk3Dvffeq6hUYpgad8ArC/xg/RAS5WY4q/KWw3HSNS8zNp/jDxxijdkciEyzHQuytkV9UJyeWTaGSP/fvov/3HBm+oBmvVbnOmrKOck6XxwX0KB8vO2gcH6iZXvLsgu10/3GQwtdO3EngZJznFHPZmExmNan2qcg+eg8E8/6XWVY8dW3mHB6R8vy8vT1cR3YuCexOGHup7tx5Zld+Pm5lIe3T5OxTYoIA6y+NpnGVTP5wjNL4OYF25Rd4uuEccAX8VN7ZO5GvPqxu9+gSBmq6+L47VufAADO7dnW/QZk3yqewASU999/Hxs2bMA///lP0/GSkhLs3WuOTldXV4cDBw7Y/FeSTJ06FVOmTEn9XVZWhg4dOqgvtAEVM3pV9zthXw6rKF3Db9EkheOgMO7hbeylGmcTj5gPipfSvbD0q9TvhFBpFkJ47U0mL7dqMzqqslbVyOLY0ZuWbPPMizq2HziMmYZgUMl3v8hFOLFkIYyxxMn6UBmUkZ+vc8IJx2n5zHmC5tg/vQ8gsb3B9/u3Tx03tgG3qL3m8nnzQTEKJdb2whwsOQVxi6vCDljH/q1Sa2KsFuPKJZ4GZafHbQZYyZVV1Rp+p52EnbQkIqt4jJeE7YMSmIDy1FNPoX///ujbt6/p+ODBg3Hw4EGsXLkS/fv3BwC89957iMfjGDRoEDOt/Px85OczHDkDxG2QCFuyTGLbS4TzhWsWG7cbzh0SL480r6/ZyQ2rbazb9AcT/CABeDfxiAisosWO2TQo/KWPcqtqxDUoMkG6eIgWjRt5Vwcu+9sSfP1tutNOJrn6qLrairHztQYTFDGLGOuzPiWgmMsUBnJfZxqmicdw6LOdZUD/9N9GgSZIc1ZysDYKA9aVXKzvjVWkuK67hs5nmXh4mkg3U5q1DxXt6lX6bojcX3ZEbum2aLpR8kGRFlAqKiqweXN6aerWrVuxevVqFBUVoWPHjgASGo6XX34Zf/jDH2z39+jRA2PGjMG1116Lxx9/HLW1tbjxxhtx6aWXRmIFz/YDh/F//1iJ8uq0dOp3Rh+kichu4lGTrjFV68CX7Cvumr0OS7fux+s3DrXtlJv0OeheYt8wy5gaywclSJyycVxmLKKedqh848BqjyTLFy5kqsXVSda4isfFV8dvfgcP12DG/M24qN/x/GXZgEk4AdLtly8EGtTohoTjOpAj0vkafietHe98lvZ9Cy6SrPs1Xr4BlonHaYAxaTQMv92qTtpJ9mjaRsHC6oDN3gaDZeJx13qw2jNP68na4sArxkeoF6hPUR8QaxFZd5UbNCimax2y2PJNJQ5U1qBIcE8mvyvl/CLtJLtixQr069cP/fr1AwBMmTIF/fr1w9133526ZtasWdB1HZdddhkzjRdeeAHdu3fHyJEjcd5552Ho0KF48sknPT6CWu5941N8tqvMdTvsiChQAlunbhR8rINz8uP5x5KvsHFPBd5au+toWQQTZ6hCMzWL9ewka7pNXphwikyr6/znl/NBcTlv8kHxpqUx4nTXh5v348G3N+DSJ5dw65WVr576171MPPu/E8bL6uKJFSz//SR4AUUEk2+W4D1uq3is6ciGxucmZMFabXUME8/3/voh/rogPbm1Dn41dXEww7rouqtQwdII8vy6VPqgGLGGIvjHkq8w9k/vY295Veq4yCoaUYxmHZlv+I5XPxG+NmwNinR1DR8+HLqu2/6bOXNm6ppJkybh8OHDKCwsZKZRVFSEF198EeXl5Th06BCefvppNG/e3PNDqIQV8GfmR1/aOgKjYPD0B1tx5vT3sG3/YWaaTm3Hr3bGOuvnfnwODY0pJXNsq4B98Eh2Qqx02CpU4/nEv7xIqqpxGsRNtlfD7zfW7MSHW/al/vbyyozp2eKgQHfoQMUzc2tLJg2KZbmpF0QG820HDnPbHtNJ9uhBEW2CWYPCFqJ56SfvKa8yf+9BtDxd103+AiphCRxOeznVmZxkxZ/WbSZt7xPiR8uXzu9AZQ0emJNedm8sW1VtPQbe/y5zlU5cd1/+Xl1n3/iQ9xm5LTOWaQPG/s26X9dds9dh/a4yPPxOesm0qEZiw55yfP0tezxJUnZEXoMCAJ/uOuR4ntU/hwVtFijAc4u/wj8Wf2U6Znxvv3nzM+w4eAS/++/6jJTH+IHZfFDk5RNXgcC6X4V18Eje72WzQC3DGhSnxHnaqJteWoWphlnHF/sq8ev/rLOvMhEsdyxmvtTRSVahBoUXB8VrdYuWjdcps25PpclIfPaqHVhlWGV0uCY9KImWxXhZXb1uWuGSSEd947vn9U9x+v3zHK/RLEKrKOxNGM3pGjE6R0tFknX5tld8+a05H4YGxYpRs7b260M4xBlwRTQoVbVuJh72c7PboLc2wFtmfMS4a7TEgD/09/PTt7k4yUoJVZZCLN6yH7/773qmkBe2iYc2CxTkkx3OUicg6Udw9KCbdoU1aBrtutbz1uR2HjyCP7yzkelExi0bnH1QrB9wsjiiNmXzZoH2PIKUT0Q3C3TiT/M2oaYujmcXf4Uvp5+P55d8hYrqOrRq2sjhLosPiuV5eZEuZepCLlCbcRYpkYkHePspObUN63tavf0gbv3natMxo9DoxcRTr+u2dx5EXTxrmdywSDjJymg0ErjFkrE26ToX7YFXfvbyGtPf9XEd2/Yfxrz1e7j3iH5vcd19U8TqWoYGxfjb+N45fjis+wBn4YzXT/LCNHgd7lmCQqXk/kap8lie8LK/LQEAtG6Wh/87u6s141AhAUUQ66DCarRWbYYITl0Ez/HP+CHYO1hzije9tAorvzLPbkQwLZ/j+KCwymOFdc7Y1zA9+QMcMR1D3QumYVzSqOs6fjV7HQDgpnNO5N3iuHQvYSY1ltF4TrBQ8KNBCVhC4cDKNRXd1XJ26z7nzUStz84bVIzp1sd1W8cfnUBtYvewy8sfEes8ruIxJlNVW++6XLauXsewB+c7XmOcXDk9r67rrk6yVRJOskqXlXNM4ZlwZTJWySOGyLte+eeK7TYBJWxfSzLxCJITs0rC9lcn4wwoco73UdZxVImAvdP5fJdz1F0Rb3ObBsUyeCRPs+qEuQcLI79MaVAcNVY+0xPdsZWlzuctLRYVHpK+YE4Y86hR4IPiF6YPCkeD4lZGcR+U9O/6OEOD4qP1TbZoeGTx8h54q16SPLHwC8w2bCjq1Tna2M1899EPcM4fFjpe76bxAMx9j9NAKLKKp4qlQeEIDG4mHq9NwJjHxKeXMa/xuqjBTTv9+e7y1O+Xlm131Fzx+OKbSnzytbulIJOQgCJITNNc1cjqNSjss0aHMWuoe2uCXmeEThoUa9+TOsvU9tiPGT+szK/iETsnWgYvqz5se/Ho/NmXaPK19TrWunQuqn1Q/OJknpCtVl1w1bRN1W91MhdIZ8b8zfjR35aYbPbVdfWedxZPlU3iTXy6swznPrIQ8z7faztnFTyMpjFjG5CpYuPkY/NeZ20WwN692pbm0SQffmcDrn1uBfc6EQ2K22aUJs0Z1yHdH8YyGP1OzA7yyrJz7MuueZZfn06IuDJkEjLxCBKLmWNXsAZjGQEl+cE4fR+8j8c4O7HLJ+Z7RNb8u3vou/ztQQuUypvhgxLkkOlVm8W9x3Q//zrTbJG1iocTcVO0SBsMMygexndR70UaUw1Tg3L0X0GNSJKyqlr8bx07pD4PtnbPvS6SG0DOXrUDlwxMxH7i+XiJkogkK3690945TskY+w6ZiKqyE3+RTSeTDsp/fm+z43UJHxTn9Fh1x2vibkuOrUec+kcnXz236/0iKlyJxHBKYm2HQYWxEIU0KIJYo3+yXhvX7u1oVpAf3J1mQda8vIZ1Ni3htPmgWDUqib9Zj+82s0lpUDjn/VBVW4+bX1qF1w1LF52dkuXzMGs7+AlYfVCsz8t7p6Kz6rxc90/ZlIebmjsDOKnX3YRiKz9/eQ1+KRDfwRZJ1pLw+X/+ADM/3OqaDgAcMawiYpkYZFH1Hpw+eaOJR2XIdysiGhSRsOuAXYBnYR2s/7l8G374xOJ0GkahxOQkK1QEIYyrbnioNPGICh5ymrJoQQKKIDkWEw+rweRyNChOkq6jBoXz8Th54lvz8rpBl5OZwfb30X9ZHx97hpI+lqyyIDYL/Mt7m/H6mp2mnXQd38XRnOes243PXHx3WOmJljumaaYt7nXdPKgEZWoym5HktTSqcVrFY9egOBdyqcMW8+b009TrbPHvnjc+k06rmrHMVRrTBMj7UOFUV0YnWREhIlUeyYFV1gfFicrqevx+zueO11if5BevfMI9b/7WWH1fcPWiUgAQLaaMNlJUaMwUJKAIYo2XwIJn4mH7Ybjn+Z81bJu2eQMuS7ruyUpjn81ahaLEv140KEHGQWGtXnJKOq4Da7YfxHXPrzTF2BDF2cRjXrHw27fSMXN0WDpKDxVgrOd8jjbFOGaYHQXDkVBYuSYf3e/knvdMNh8UP3kYbverQdG0zLwHU+h5CfWB7LglErFWNM1H39uEd9fbfW2MyESH9eqHwyJz47nY5I+F1DNqjn9mHBJQBLFpUASjpgJidk4Wd//nU+bxOpOAIi4dy+DUkdtXWCQOMNWQLgVi7cWjqqNe/MV+qfLEdR0b9rj7chjhOeJZsUaStcITOkXf52VPLjGUg41Jg2I4bs3j5y+vwRffuDtC+oW9iod9zk+LmPvZHvzfP1bg28oa0/t/c+0uR18ON4xlOuJXQLH0J36+Aac27rSj9eGaOlzx1FI8v8Q9bosbrLgkVkS1D0LaTJfqMmsM+f2nQFL+UDjii+4pJDMmhC2QWCEnWUFiMc0y87dfwzXxMKM9JlXZ8mUxznzsHblcgjrYwpY58qKzEOT0DG7LjNMalAyZHBzS1nU59W4iObFyG2vYuh+HrutcAU30fZa7LHG2roRwMqn9e+XX+HDzPiyeOlIob684qddt53y0ieQKkTbNN5iSmfvZHsz9zL4ck/cdWzHWISuSqSxBTC7Mx81twGriefajr/D+pn14f9M+XH5GJ9M52YFLxGlYdE2BSL24alAMv2Xilfz6P+vcM5dAZWRWUQWYVABAMvFkJ9YdaHnXJHl5xXZc9uQSHDxc42ji8TJTcvJBCaKTsyZp3934qAaFkQ47FLdRE5U8xs5bNc7+QLp03k7mNpF7ALuJx9jxeKoLxj1OcUVYeew6VGU/qBimiYd7rf9Gsa+iWqg+c0W2RYZaEw/grNWSSodzr9XsUmMRIni74wLypgxr2ixU+ju41ZfxvNt3arz22cVfYTtnT5y/LfpCWtzwusyYVVXBaVAM/XPI8goJKILkxNgDqxHjB3fbv9di8Rf78ed5m5kNiafKFsFpPw3Z9Hjtz9xZ2gdUp7+NuPnfJKvMi9ZAhnte//Ro2nziurzvg3kWw7/ZODOxdd660yoeeVj1ZxWKyqtq8dX+Su71GcGhbfiNT8G6PfF9uqebK7jlrLHelAgoihzFufGTLG3gqpnLhdOUnfmLaFDULrl1Pm88LbPsFuDvGH2/h73XVA74QazCUrnbsgoiVpzoYt2BlqUKy2HUZkV1LdtskDTxeChLvZMPiqrBxmnGoZujlqY0KC6+FU7HgnCSNTLzoy9d09Z1+frzEt7a2nnrcDC/eKgM1i3WdvLn9zbj7AcXJIJuhSafiAvuKtqE1UzLQ1SDYoQVal0Kzfwa9ldUe06K9Yh3vPYJdh3yFjvFC6yN56yoNCe4fbduficyaRmRX8XjcZmxp7u85BMtEw/5oAhi3YuHeQ1Df5ebE2MOyHf951PsOFiFG0Z0tZ3jMfezPThSW4/jWzZJHQuiIwecfVCsmobkaaaJh1EglrNxoH4nnLxZ52QnJXUO/kBGVm8/mPpt1aBYfV+czGte4ZVt3vo9Yckn7DKlBHdnrZ0XcgTMtICMD0r6t+9VPJb0pvxrjffEGA/54tJt+Gwn39m0Pu48LMvKEkIaFIVjoYiJ55vyaqzbcchT0De/fFtZg/kb9uKwx3bip65kxghboDbv2SqBBBRBbBqUo/8aBxZb2HkkV/+w03x84Rb8VFBAicf1lLPfYxNOSx/n+IP4xUmjkXDqZMzyBYMJuc3wgxRWHDUokNdYGE08Ox1mqEYBpcoyu9RhXWGj47OdZVi6dT96HV8oVZ5kerZycp6rLu6+h09QOK2g8Gu6ZJEItqjSxJNGZNWKTHp+4L3rdQ5hzN36jWCcZFX6oLhrRUb+YQHKquowsHMrZfmKPIGuA1fOXI41hj6AfV0w32E2O8mSgGKBp+LSLCrY5GXGjpQVByUnpknbPFkYpf5vD6ed2YLSoDhrGthOZ6y6Y+5m7HIsSJ8It1D3su/KKFi8v2mf0D3WoF66DtRbYpSc9+f3AQDjT2svVZ5EeonnMGr0eO+ztj6eMe2VFQcFSiBlsm1XwcHJxHPoSPrbM9apilU8qh6al4qT5sD6TQPA1n2Vqd+yA1emNSgijq9lVYmVbsu//Nb1Wqe/jYg+g5tw4pqPr8B9nm8NHfJBscAbHOMWrUF5VR0qq+tMKn7WjCA35rzJoGjjMaZxx2ufMI+rxClV3ZKvmzBjP2ZXRRkvC/KDcvJ892LiEdlzxEpNvd0HhbebsWhEWyNxHTjrgfmm3ZV5z+Wm2g8SpmvW0dLYHbPlSslKO4exizQLJxPPdx5eyMxDJiorC03Q/CSCl5m41Qy9p6wKIx5a4LkMQnFQPKdux9UHxU/aGfpAgsrGsS+3PFxMs2zLQXvxZAesGcb3Znxo+rCZGpQczXk5mGCr5Hlsqw7UNuaPi/D57jIXZ1LzctyUhUewLRsfhWUqC7I/4DjkHy2DfN6iS/2MsDQoRs2NMUmW47UIOw4ewRumPYjY5TxcUy/UZjJlBuJpUFRknxNz9yMDEn5jPPaWe3dedUOd/5g8Vs3hNc+Kr/BhkellxnE9sTLtT+9uwhZGkMFs0CIEZuJxSNc6rmgQE+IzBZl4BIkzbPWb9laYYguwBJRcl5UDojND3kBoPSqrUbFe/fnucvz0hY/RonEj/j26tz1okrj5pQQ5GLqbriRNPB5mztaoozr4UV7X7ZDXoCQRidFy6EgtWjR27wYyNos8mo99dZocrLFP0+zaKxZeArWp0EOp0mV5UeYk9iVK32htd0E5yar61nUduP+t9Zi1fDseeXej/bxE3cqVKTMaBl9Osg7nrJq/iLmgkAZFlLjObuLGQYD1cnM0zXEQE+1MeL4RtpmmWHIpauri2HbAHIiovKrOcUTSLZ978oMWDULE2nTRHEk1OJzehZdAbfPW2yORSmPxfVFltntk7kbsKas6mgU7zYqqOqFnDsqUaOWLfRWoqq0PpA28tGy7kK9II0G1lZMjuReUVbGHhNx8r4KIgxJzWEAgi67rWMHYeyt9XiItBeXxQmAmHoeErd+1tQ8PW14hAUWQes7gdbgmbedPL5k1alVirktbeXQoSi8n5tm4ZXd9FUHnCGNJ4hYNSjqSrFhzNj7Kx9sO2rRTQY6FTh2xDvmBeNr/nHdZFSGRr+FvRc+/v7IGVz2TUNXzHlt0ZhlATCgmVbVxfLqTYWKU1Qz6KK/IxqCA2gHFi3DMTcvDPW7vV16DIhIHRZ0GxX2zwGAacCaXSntPmH/KPq5Ey8RDAooF3iAbj+vMj2DKP9PxCpKdvfGl5+Y4r+JxFFBaNTXlz77f/LeKtqXrzh+LrgO6j3DsxmeuqYvjqQ+2mp7j3yu/BgDM3+C8g6kXHFcyxOWdZFVRzxD4VJB0suV10KJmrUy60pZV2YMbRqjPTKHSLKlD3TN6aT9uvlRBLDPeuKcCX+5nh5GXxe2RpTQotj6Vf3OmHH295rO/ohrX/mMF97x1XPEaij8oSECxwF/Fw+5Aln15IH3v0QuMoZFzYs5qTDdBIAmvA7E7yaqwhTt/LNYVTallxh43//rHkq9grN2Xlm0DgNTsXyWOK6pczgeFk5OsCqpq6/ntR3AVTyar5apnlqPMsi9MRl+LYGaiG0UKJqZsli/gZmMjzuvgPCK6XP+ml1apyc+l/qIo4GaCh97ZgGVbD3DP231QomXjISdZQerj7jEykh+I0YM9N+a8ikfUPsjznQhitUNc1113ytRNv+UyZcdBkUrCM27mtjCCljntNKyC7nfN4QaniguaFjItuK108CcIGuuTHqmpx+GaOrRunm++TuffEyZe4i7Vx3WTk39OzOw7J2r2SiJagp0H+cEN5fJz65vVpSWL14mbCvZV1DieVxGjK0hIQBFESA1+9BLrSgHnWbuY1M8XUNSrwnWXyZQ1kmyykYt+iG578STzCAJnJ9kMz9ST+SJ4J2FecKrExNk5x8cWbDEFKMsEdZb4MpkUHK1Z9f/tXByuqcfKX43ilslv8XSo80HxsvS9XtcRM0yXcy0CiuxEOtOCvquJJyARMlNP6TUeSZ6Lw7dNg+Ipl+AgAUUQnpOskeS7Ni49tqrvefewMH7k/Dgo1nucyyiC7qJJsDrJpuUx+T1MANgClSXSDObTd6vvMCYUuqU+M6mtcNOgVFbX4fdz/DsCy2IPZidHeXUtHluwBd/t0046b+tgdrgm4fC52hINVOVb0nVgw55yJWl5mRVb20CjnJiQHwk3PeF81dSia6h7iWxk+p5vFMbFCeKzd9v40vqs1iKEvXkgCSgWuE6yuvvAkezYag0fdr3LoOe4qsRwipd3EHvxCDmcMcomrEFhZGA94mUW6BcRbUIQ6DB3FFbtQaB5u2jLwvDJARgdp2Qxth84gt/P+RxPLtoinTcvLydzqt92o9Kk5UmDYllJ18g6sEmOU5luNq6h7iXSEgkyFwSOTrIe5QTWknmniW9iBWd0zD7kJCtIPK67fvhpDUrccEx3NSvw2FdRjX8t347DNXVcx7dANCgu6VhDwiefT/QbYm4SZxW0QugjvIS690On1olVWrpubluZ7CDdNChhhbr2Gzo+iXHfKlG4Aortb3UmnqSWRgVeBFxr3+YUTTeKuD6xxAuyae+iM15L4xTTZ866XRj+0ALTsag9KmlQLPCkx1nLt7uHudZ1/PSFlVi17aDxkIu5hH/uy/2Hcfsra/HZrjL8YABv0ziris5/E6uorsPh2jrueasPSnIwEVfruh8LZTUNMms7N+5+bcy32ssyDI/EreqwiJLJEvLysscccr9HlCZ5OT5TSOPl20nEIkr/3cjiFJvYzV3H9BDMfSLwAmmmz4unVWvzf/JWJlmCyCfPwcRz3fMfu5Yh7Miy2SUmh8x7nzvH5SirqsN/P9mNXYeqUsfqXWJriHQmc9bt5moUgtCgAAkVOY8/v7cZH23Zny5DUkDxmHdNXdzugxLSappMZptcGaHrZlVrZjUozu8tjFVNLDLrJMvOy65BMd3kK0+Vqym8+G9Z72mUax4aNCR8cJ5Y+IWfogWHq4lHvE7sfhn+3o1o03C6zIsvyOrtB/Hs4q8k78psH+gGCSgKqWXMfB+eu9EW08GISF/SLD+HvxePw6wuSH7+cjpAXbps3jLfU1aNX77yielYEH4YIo50mdTcJCepOnRPsStUsGzrAcxavp17PkJ9VegEqUFR6RTuSYNiuYW1H9HBAFZyqXpq90iyftL2fq8MqoXw78340EMZlBbBNySgKIQ3qP5n9U7mcUCsUW75phJ7y6qY56wfz9Kt+03h9zNBvU8NCmC3+9YFMGK7dTSZ9kFJ7ubqZgYME5XF8hOlMgrVw1p9xjsni0qNoRdhx74ni/llqQxLb6SiWk1f5Wap9FPysBzFk2w/cBgvLpPVhHgjrodv1jFCAooFP8uqWBoUN0Sb/qR/rGQet348v31rfSoCa1BLda2I7BArS20AZXd7P152M/ZDSkBBOCYtEVTWx80jT/J8bybrR9hJ9uiBeFzHgo3+tmVQ+XheTTzGu6wpaAHt0aIqzUA1KBnqR3m5jHx4odAml2rKYDbxhC2rkICiEC9xA/xK56zblx4NbSyyYZcKksuq7bEUvDfvIDQobh13pk08ORwflCihqjrOPLE1SgubuF/IIZPvhedzYF9mnDjwwtKvsG5Hma88Vb5/FT4o1vpOaFB8FStQ3Irmx48kU8IxLxvVPmlOT+MWdiDTkICiEE8Cis+25zTDrQ5Q6jbaqJOaCWsn0DjX+8oEqye9CtyWruq6rmx5qwhGk0fYamQeqkqlQfM1Hctk9Zh8Swx/8Aa5xF5S/lBq4vGQ1rYDlk37LEnsq6iObBsFkvE7nC7wnnbG/MMiUL06oiWISgsoixYtwrhx41BaWgpN0zB79mzbNevXr8cFF1yAwsJCNGvWDAMHDsS2bdtS54cPHw5N00z/XXfddb4eRBV+JG0vpg6/H73TeOonEqQbxgiFNfVxlFXV4l8rvjZdk9/Iu4BSF0AglMcXOgftcotZo5rUKh64730UBm4RhWXQNLtfgwyZ3DPEmJNpCwKbBiXxr3HVnldUPp+XtrR+VxmO1KY1rl2Lm5vOv7RsOzZ/U+G3aIHh1kz91O76Xf60Y6JEIUCaNVBb2P4o0gJKZWUl+vbtixkzZjDPb9myBUOHDkX37t2xYMECrF27FnfddRcaN25suu7aa6/Frl27Uv898MAD3p4gQtR4MKn47f+dBJwgTTzGAEC1dToeX2Af/Bs38q6gC2IVz2OMMhrRkVlTS47BSTaKPigJk5e69Pz0dZn1QdHZv63XHf23vMq/o6dK7YQX4b4urmN/RTrOU36u/dv93ye7fZUrSNzqL+qb4kWKCFWVdKC2sWPHYuzYsdzzd955J8477zyTwNG1a1fbdU2bNkVJSYls9pHGi60wqDX2Ty7aguHdin2l7YRRQKmpjzOXIDb2pUHJ/FcSz7AviHGH2Ch2oH9+b5PjCjQZNE1DzIdBOZPVw9egmAsx86MvcZJF0+AVN+XrjB+dhhtetAfWYuG1roxlYCURVT8pwD3IYnRLniYKAeGiNk9S6oMSj8fx1ltv4eSTT8bo0aNRXFyMQYMGMc1AL7zwAtq0aYNevXph6tSpOHz4sD3Bo1RXV6OsrMz0X1D4WcXjzcTjObuj97MT+N1/P1caPtuK1QeFNcCyZmGi1Idg88i4iScZB0V330YhDP747iZs3VepJC0N/r6tjC7DPprV0i/249evr0sdZm1o+ctXzfF7vPLKx187ni9s0khJPk6Yno9R3VH2QREJIRAWojk/9M6GQMshgjUib9ibBSoVUPbu3YuKigpMnz4dY8aMwTvvvIOLLroIF198MRYuXJi67kc/+hGef/55zJ8/H1OnTsU//vEPXH755dx0p02bhsLCwtR/HTp0UFlsZXhxSvW9isfh3JEABZQ8g/BRUxdnajz8aFCCcJJ1JcMaFOMqnghPTpXhx56d2VU8CS55cgleWpYOYpfJTRytZMIXwNj2WfUdZQHFTYANU0MpKly/sHSb+0UKcGpLUYvJpHQvnvjRWe+FF16IyZMnAwBOPfVUfPTRR3j88cdx9tlnAwAmTZqUuqd3795o164dRo4ciS1btjDNQVOnTsWUKVNSf5eVlUVGSLnu7K7IjWn4y/zNnjQoQfqgVNUGJ6AYs+VpUPJ8bDgWxmAQ56ziyY1pgZicjHFQomjiUYmbk+xPh3fFXx18hDIZaZfXQYdp4sjEPNbYl7CqIAyzqyhuy2PDLHuExnsALiYeRMscplSD0qZNG+Tm5qJnz56m4z169DCt4rEyaNAgAMDmzZuZ5/Pz81FQUGD6LyhkfUJiWloi9aJB8SutOt0epIBiXCFUW882UeT5MPHUhmDi0XV2MDc/q0+cyIY4KKrQ4Dxzu/i09uh9fCH3fEb34uEcD3OQy8Su0iYBhVELURtojbj122F+X1HWPFmx7keWdat4nMjLy8PAgQOxYYPZlrZx40Z06tSJe9/q1asBAO3atVNZnIwQO7pMGvDmg/LGGjVOiCyOBCigGFcsVXNMPH4ElLA0KCzTUlAfaVqDomdVJ+YFTdMcBT23MPiZqJ+bzjkx9ZsVKDDMd5R5E4/z+ajhNp8JU7jMpm87ahoUaRNPRUWFSdOxdetWrF69GkVFRejYsSNuu+02XHLJJRg2bBhGjBiBOXPm4I033sCCBQsAJJYhv/jiizjvvPPQunVrrF27FpMnT8awYcPQp08fZQ/mFVmnoJgh/pSXVTzyu02KE6iAUm/UoMRRzxjY/QkoYTjJspdoBqVBMe7Fk02dmBc0OJsp3Oo46PGlfasmGN6tGI++txm6Dnx72L4qLVQflAzkYWz6rOYYZQElyhqUsDYC9UTEfFCkR5AVK1agX79+6NevHwBgypQp6NevH+6++24AwEUXXYTHH38cDzzwAHr37o2///3veOWVVzB06FAACS3Lu+++i3PPPRfdu3fHz372M4wfPx5vvPGGwsfKIJqWNvEoijtyx3ndlaRTG2Cgtpo6i4DCaNR+VvEEsRePOzrTxBPU7DXpoqMj2p2/CjTN2UzhJqAEXT95ObHUe9ah40BlTcbL4ETMz06Lgpi/4exyknV7NWFqUN5dvye0vGWxCnphm3ikNSjDhw93lbCuvvpqXH311cxzHTp0MK3oyXZiBuc/VStPBnVprSSdICd8xu89iGXGYWlQmCaegPJLDcoZXt4cDppjZ+fWEQY+qzNoQnWdrX0Mcyl4JsaJbDbxuNklwghbkI3QXjwNjJimfqW4KpNCpj7K2nqdqfHwtYonlEBtekYFI+N7DjJmTRRwW8XjpiGY82mwUUwTTrxpkxtLkxaG0JwkEzNZ8yoe+/cX5VU8cYtzp5UwzXNRw8kcFrc5oTSgOCgNAS+reFSrX1V1RpnsT1krhrLNSVbnaFCCIr0XD1BR7T9cepTR4OwI6/YJ7Smrdr7AJ5plosESUB56Z2OgZXAiE6t4jBoS1lcQpoDmxrqdhxz77khrfxxY8sX+jOanI8v34iHMBNFxqEpyzrpdahISoFq1gBJSJFnWwBQUyf0W6+P6MaFBcWrXQTkii2JcBq1zVnOFheZvI2hh3Ew8UdagVNXGsf3AEe75KJfdiUufXJLR/BKB2jKapSMkoFiQNdi4dbyZKAOPNV8fUpKOCKwBNi/Hx148IQ0QmezIkhqUSkHtya/O7xFkcQJFg+YozIc9U0sIAWmNVpAO5rIktE8ZjoPCMvFESGiTJcoOvlFCBwkoDYqYS3wHT2lm4VvZzdhyPgwNip9X8f6mfdi2374nVFDq9WS7Ed0Nt0ubZoGUIxO4aQEyrUH53UW9TX9rBideng9KWMQ0ZwdjVZg3RrSf9xLnKSpks3AVFMw2pVtMPJkrDpMsHAqjRSwA9WvYGzR5oZyhBfAVSdZjh+LHMRfIbCfcuFGirLvLEsKdW31lYqlpULg6yWZYQDm9SyvT38bsdeiBt4PLz+gofK1RuxMkRi1DQ9M4hGEyjjLWiLGp4yANSoMiiNlNFo9DJvwtM/b2lfjJM9P0Kk2Edv9oS8IRrrhFPprm8c1iYftp+EFzWWbs9NzBwC+Mrgc/4x7StY3wtVqmNCgumwVmM9nqgxIUvPqI2mvPnt48Q8iu4nEL4e2FLB6HTPgSUDzOePzsoJxpTu3Y0vR38/xc/GIMP0hfTpY3DN538sr1g0N/b0YhQEfwJp5GEpo+t32MVGGM89LQFA7ZuoonCPaUVWPRxm+Y5+K6eQQMu8shAcUnQby/TCwpzAShmHg85HlKaXCbTzrRtJE5TmJ+oxzH8mejb1IKB2fyU45qkjKJtSyJUPzOcVBUkiuhJk3EWsqAicfwyA1Ng0ICiplrnl3BPE6reCKOl714lGtQlKYWHn40KF6DzMnMTJP0aBeOgGIVOHI0Zx+aKGhQcjzaH40CQBSwlsS6Gq8maKdKiapIxFpSl/X3+7dnHjcO4mFGzQ2CKDk9Rxnd8P8oQAKKT2KxIHxQotOR+yEMDYqXmVKjnHDq2zrYxzQN+Y0cBJQIOCc19vhOE6ZQ3jkfBfIIS0uZPhR8PByZR9YUa1DuHtcTVw7pnPo7qc0xmXiiM0YpobLaf5whY501VKzOs2FPKkhA8Yk1AqWaNBUnGBK+4qB41KB4EVByQ7KdWDUiMU1z1KBEwfR3YtsWnu4zhpK3n5N/rge/72/nc6YGxWDiCTpqqsy7VB1rKS8nhk6tm6b+Tvr/GJ1ko7SjrQoqa/xHas7mZdaiHK6px/ub94VdjBQkoPgkiFD3pEHxvorCmwYlnM/ANkhpznUWBQ3KHy85Fef1LpG+L7HMmH9OlnaFTeRvcsjTFAcFwZt4pDQoULuyr1FOzOQDk1zubtSgJL+jdoWN1WUcIocVaFCiFLwvKB6eu9G0U33YkIBioLquHnvL7QHHnMiM+1p2EoaJx8tywrBMPNZBJ+YmoERAcO1U1BR/ndBf+j6nlShenspvVVi/WmMgOT0DWx7IlD8xAVLz7nNi2tH/0u0sPzehQTEK95/uLAMANMuX3vA+kqjQfhyLfixhdzkkoBhYvGU/tnxT6XhNE9ZySOWRZMMfiFTgZ0D1buKRvy8szYRVgxLTNEfH4iis4vHaNhPLeDkmHg/tRGYVDDtPy9+GY5kIdS8zrfGrQXnx2kGp30lhnKVBYVl1sr0reu7q05WldSyYeKJGBLq86OA2UN10zolYcsdI23HVH3GW9wkp/AyoXgMrebkt18XEE9T7sKab8EHh++2EbeK567s9Pd+bGGR5PijyuL0zabS0lkLXgw/sJaVBcRDuROhYlPY3Sfo4xUwCil2DYsw7ytx5nvP+VEXN8pTlFSXTx7ECCSgG3Gb8rZvlMSNeqjbyRL1TEMXPgMraHVmEuBcTT2gaFPvfjnFQQmwXx7dsgmuGduGeLylw8VXQ+IKIl8fyu6WBowZFDz7UvdwqHn9CslG4SbYvoxNsSkBhqFCi4JjtREETvgnq7u/2RBOFEYoDX3oeQcJ++w3DwKgIN/U163wQW6FHvE8Qxo+J59vDtZ7u8zLzVT4bF8Qq2LqaeEJsGG5Zu54HP+KyJxOPT78ha57mvXgy4BApUXy/0aqNdyYdwo2B2JJtjiXcR93E47QCr0e7AqVbKBwLTrJRgzQoBtzs2rwlxarHjYYioPjxpfn2cA0AoFXTRlL3/eSs9Cz/ttHdhO7x68/gGYaTbGET/vOqMPF4fVa3NnlRv+Nd71fZrv06NtuWGRuPZSCSrKwPip+6M96bFlDSx5w0KGGbFd1o5CDQa5o9WrNXCps0wuTvnMw972VlG+EOCSgGXDUoGifAk2IdSkNZF+RLg1KZEFBaNhW3IS+8bTjG9monnZff2bhXrNUT0zQUOAkoCkZ4r0Kj0wx+6R0j0et453D1fgdZK35j19jNa2k/Dx3AwSPeNHhe83fCr+bM2J8kTTxGDUpSaGU5yUbdxONkno1pmjITz8d3fQed2zTlno96PbGYNemMsIvgCgkoBtwGAJ7gQLsZs/Ez+0qaeFpKaFA6tW5m+lv0vYRn4rH8rTnXmYpVPJ41KA7n2rr5n8AcCE0FTjNnEWzLjGFeZvxNebWv9N3zF8dvrCWmBsWgQnFscxHvi5xiGLn5dMng1pdl2vyqQrMlEuMmbMGLBBQDbi+d52+i+iWG3ShUEYtpKGjsT8Xa0kGjwMJYdaIDYnhOslY/iOA7Qa9aGP4S4aP/ut0PTekyab/vjOWgbDwWuIAiFUnWn2hnvDcpoBpNPE7tKuoO+06CqvrVldER5FTkF9M0/Ov/BuPifsdj9Clt/ScYACSgGHD7GHWd4/2vvByKEwwQpxl5jqZh8dSRePEng7jXuOHkk+FGtmlQ3N67illTjkdzFu+u5HGRunYbZnWJTcr8vjO7D0o65GK9rmP/URNjULhpy6zX+hIUDLcmszSaeJy0M1Hvi5x9kRLnVMlYTtrcbNSgaBpwepciPHzJqWjTPF9BqdRDAooBL74IOtQH0ArbB0Wm7bsFFmuWn4sORXzbrRvNJTUwGqMzdiMsJ1mWD4oTXjvBxy9PR3717Mfis4pUO8n69huySyip8lXVxj1tmSCbvWi7c6s3YyA2dl5GteJRIczwfE7FiLo218nEk3wuVcJDo5wY1t5zLvOcbBY/GtTRV1mU+KMZ0ojqayYBxYCrBgV6ZpxkQ34rMuGtgw7N3jhXzsnN+C5E34t1sGumcGmiE6xlxgDwxBXsUPJeZ01GR8EgnGQTuJtH3ZD5jnzHQWH4oGQSTRPfAyqmadxVRU9NHIAhXdu45pVOK/FvicH/wNnEI1TE0HAS8pJ9tcpHKGjM1qLICkF92xe6xw5yQIm5NybfV2YaElAMuA0ATC93BLDMWG1ygeYf9OZ2+Y28N1HR92IdKFib+AWx1TrLDwIARp9Swoza6rU6jYO5V98NroknOQi4Jqu2VbtpH9xDBtj/zuwsUhPWAmngL3sWE/zsv8/r1Q7/d/YJePKK/o6DXVQHriQiDr6ZeK+yn5Vxc0pP+Sky8bB+RwkSUAyodiD0CqvDkI0H4geZ53GMfBrzbwN206CUWjzRveRltWOzkrjnglNwsUusD7+YVK6s8x47JeM78uyDosDEoxI34XfZnaMczzN9UDLYS2uauIknpmno0qYZhp18HI5vad7FWUSAYD1XLKZh6tgeOPeUEhcTj1ARQ0NEuMrEe5XWaPgskion2ST8CYj/fPxAAooBN18SnlVa9B3+6dJTbR0MMz1Ggn07tBTMxT8yjd9JTZ2j+e8gGrM2ZzRgfSem2aJgvrZOnnOb6ngpPA0KwH4HXgVoowDmNX4Id4m95V/+/e7XyDjJur1btz1YrPfvr6zOqK5Ag8S70BLlfe7q0/Hny061JySQlxNOwl5QA1TbAjGnzNYu7zHOUmsfRXSFmQpk+zi/ZVKhnTb7oERTEiUBxYDrS+d8DKLS8/CTi3H3OPcN18Je2ifTWPMdNBwqPqLGDiYet+S9Zm+r/6Ov3aEv9ATPBwVgvwMv9fnydYNN6Xp9J1HToPjN03qqOsNhzDVNE34XTu1CJAWzKt9+h9P3HpSJR1Q4c4tj4uTLnBJQImji8dvHqxgjou5fBJCAYsLVB4VzXLStxGLhCx8iyDTcPAetQvJZvT5xTHPz0md1tobfohkxNBn/uCa9TXvyvate2CGrQfHSdhI74ab/5pkV3N65m9Dqel7EFKF4MHRU/1tOxXU9o0KUBvFw/carrM+U+sYchTHnfJx8dYOqE1HhzE1AcVptlTLxZECHIvtt+q1XFeOI8Zvl7zQe7nhFAooBNxU6OxS0+EAY0zShwT98IUY8f6eOJsenD0p+bo5zx6s5azU0TcObNw11zceahQbgrJOOS/2dVCPLmCB4DOpSxM3XTYPi1nZYz5pon2wNyqgebQ3XuQkYnOOCavTMO6G6LJ+1lFjXM9sZa5p4LJeYaSCxp2O9xp6ZePqZQtT/xm2lk+7QASSVNJnQFEg7yUqMG17ycwr/wEoj9CGHAwkoBryrvwVnQppYZxB2Y5HJ36mTTXnRe/wU8xvFHO/VNI0hNBgHeTD3iNE088oW6/uzvqNUH6hAg2L0Y7Hnay6jFbf2yXrWmGaO4GocGIzRI0UEDOZxwXcbRpN2LJvlFCsIo7G6e7vsNSSLBs1THBRr20xrCcTuZ13n7McRzJsT7WvdBBRHE08GnWTlfVA0X92JW34iexCZlxlHExJQDLitkuBJ6zJtU+TasDUock6yzgJE4l9v5XBbwRNjaFBETDw/7N/B7mzISQMwalD84+Q5b54p20uvafJLEzVLWrkOgplT2m5t0q1cIh24m4ZK+tldtG9G4rpuex/GwVF52HRN3OnaWHc8s6DzapY0rBreduCI0L0qUWXicRKu0hOk4Mm0iceNJi6LCwCxQG1hT5ZJQDHgeZmxwDVnndQGTfNyxTQojGNOpoxmeTkY1aNYoBRiyHxsOarD6BrIbxRzrFz3QZN9PrGtvNPswaJBOfqvU2foBftg4z6jkW2jVrOicWCwmQsc0nEXQKSK5QnZcNwyVo94wsZjwknLpgJRR1Hje7JrUKw/WPc7l728ir9zs9OtF55a6piuE6ICSr6LBqVTa6cdhs3/BkmmHU7dnkkkCKCb83QUIAHFgFcnWZEB/cHv9xW+VratXHVmF4jOE7qXtJBL3AWRwF9em35+bszx3phmV5Mar+fVYzyuWz5OYOZVAw3pmq9Pas5UyCdGIcdJg8FrJ7KxUBLJGDQoJgHFPIMSnYWLnXAuBzsp5/NDurYWy+wozk6y5nNxhg+KcSM61f23pkk4yTq1i5QGRex+1mX7KvgbIzqV8OaRJzmcdUbU/6ZRrnMdtStsgleuH4I/XXqq7Vxagxv84Cv7XQa9ikfEfCgSByVspAWURYsWYdy4cSgtLYWmaZg9e7btmvXr1+OCCy5AYWEhmjVrhoEDB2Lbtm2p81VVVbjhhhvQunVrNG/eHOPHj8eePXt8PYgKvPigiEYEjAl0JKk0PagLRW+Zc+swnNPdWdsi8/G0bu4cpwCA59bvtIQZYD+zWQvB16CYBBmYl3xa003KFCr0J06rDkSc1rw44/E0KDxzAYvt37LNAFrqX3/ChxtXndkZfdq3lLrHUSNk+Zvlg2IUIFR34Bo0YY2Qk5Ns8pyfaLD7yvkbIzovQfaOKidZAOjfqRW6MSZeqbaZgdFXpflRBBUbi8bcJFf+4YwhLaBUVlaib9++mDFjBvP8li1bMHToUHTv3h0LFizA2rVrcdddd6Fx43TEz8mTJ+ONN97Ayy+/jIULF2Lnzp24+OKLvT+FIrys4gFEtSLBSfOZDhJkJCcWQ9/2Lg6EHkf2RjnO0T29+kXEdbtdP+Yg2KR8UBSoUOKWcBu82THvuWVNPBo0sw8KT4MCzXEgO6Bgd1+3ojv5oGjQ8OPBnTBp2AmmJeBOyCwz1l18UFSjacCvx50idq3pPo15TuZZrZyj0DwsirAPiuA7YH0XXsMciATTtCIrgPsV2N36fDEBRV15gkJuq1gAY8eOxdixY7nn77zzTpx33nl44IEHUse6du2a+n3o0CE89dRTePHFF3HOOecAAJ555hn06NEDS5YswRlnnCFbJGW4OslyjqvWoMiiQewjvPvo/i5u5ZV1K/nHTwZh5Vff4qpnljPPe40fEtOcPxumkyznt6k8cftgZB4EzOdSi3gUqFA6tm6KZV8eMOWbTNZpppw6LqtKtsTeMfoMaSKVJYrL/ZoGtJDYhJJFo5wY7jivh7IyGdFh7/T9bkjohKYlNuwb17cUb6zZ6XItv13IbojH+vZ/973eeGvtLub1Qe10LCpouznJJmF9FyIOxCK4RbNN5CGXpt95qtvtIg7YTu0qKij9AuPxON566y2cfPLJGD16NIqLizFo0CCTGWjlypWora3FqFHpvTK6d++Ojh07YvHixcx0q6urUVZWZvovSrRsKtKAj6piA2gJoiaetBrU+WLZD7qgcSOM6KZ+FuZWV66B2ji311t9UGD9WM03pk08/iSUy07vYDOJ8VZo8Moua4a0miCLDSHGj9TUW66VJ+WI6FoOoLigMQZ2buUhF28duozZw20VD+vNX35GR/lCWfIXcbx28kFJ/ekoSDinX9i0Ecb15Tm8OtWhd0RXMIkKiSyTkUgQOxFevNZ90iz/XfrD7ZmkNa2cy8P2nVUqoOzduxcVFRWYPn06xowZg3feeQcXXXQRLr74YixcuBAAsHv3buTl5aFly5ame9u2bYvdu3cz0502bRoKCwtT/3Xo0EFlsYXhqfiLW7jbktO2Yo95u6QttmmY+V/udeLFEsLrwJ6jOfv3JAZ3+RgOtkFBUMDzq0HpelxznNm1jTXrFG7LjAGgqcDyQVP6lmfr0rpZ6ndVbVpA0eCtMxKOg3L0sgmDOvlOSxSZTfBY79booMk6/9vv9fZYsnT+ImbDoSem2wxvFY/fHYmbeNg13M/gZfw2jcELrTiZ2R6bcFrqt/OkSq6g1nfC8m/xl4MCDYoCE48pvYiaeJRrUADgwgsvxOTJk3Hqqafil7/8Jb773e/i8ccf95zu1KlTcejQodR/27dvV1VkJRwnIKBoqaiGYg3h5esG428/HsA9f49hTx/RwSW1eZ/LdTIalCAH9twcZwGF9Q0aPzSuicfiEGn10+Dl6VdAyYlpGHbycXj+mkFYPPUcW15mJ1l2IVo0lt/V2iT4xDQ8fvlpGNe3FBecat6d2U8n5TcUvmv6jGM3n3OisjxZoe4DNfEc/VekTd0wIv2cdsdm94mPta2z4IULCEr1b0x2hkHQsOJk4hnbu13qN2tATpt4pIsnjfzCBn+Fcnsm2U1Bw9aU8FD6BbZp0wa5ubno2dO8IV6PHj1Sq3hKSkpQU1ODgwcPmq7Zs2cPSkpKmOnm5+ejoKDA9F+mePEng5wv0MSc6XJSHYlYSxjYuQjf6dmWe94YKVDUxCMcel6isYpc6nVcd6urmKa5hrpnkVDnmwUSJzV6Er8mnmT9Dz2pDdoVJhzxzOVwF64Kmsj7cVh9W8b0aodHL+uHwiZmYceTBkXCxOOWh6OTLOO+Ked2w/rfjOHeI6tBsS0zdjHx+CGtQUkf69y6Kcaf1t503agexaYdva0DsYifhchr5XVhjhpMRbNup1REHZWDNPGIIB2ozWd+7vsrqSlP2PFRlAooeXl5GDhwIDZs2GA6vnHjRnTqlFDt9u/fH40aNcK8efNS5zds2IBt27Zh8ODBKoujBKN/iZ8ZtMhyQBnMvguiJp6kBiWzjc7r6pecmPNzMTUoptkim/q4PSiXyBJfvxoU5rvn5MtbhimrQdF1/jJjUzE09aY9cwbBJOsc0lvc7KGzArUZZ++qt7Jm+KC897Ph+MMP+6KPYVWc1cGcZ+JxN4U64yVIJeuWUT3aokjAqdT4WE7lU+EkK9vfeXnT8k6yPjWKLrc3k3VIj6gKRXo6VlFRgc2bN6f+3rp1K1avXo2ioiJ07NgRt912Gy655BIMGzYMI0aMwJw5c/DGG29gwYIFAIDCwkJcc801mDJlCoqKilBQUICbbroJgwcPDnUFDw9NAy4Z0AHzPt+DHwxobzs/+pSE1uf+i3rhztfWOaZj/NcvIiYJr2VQHWrfa98ec9EMuZsV2MetDpGa4f/mX2acHiM3pqHOZbkSUw1t+G2s93yOT4DVsdUNHbqjA3C6HM5Lut1w92vy2yHL3y+jQbGa/YDMaFCMTSY5yNbVpw9a4+bwVvE4C2O8P+x52++VNV3IT0ic3pPTbulGHJcZZ0KDknEnWecUWjSWG9qjKZ54EFBWrFiBESNGpP6eMmUKAGDixImYOXMmLrroIjz++OOYNm0abr75ZnTr1g2vvPIKhg5N77T6yCOPIBaLYfz48aiursbo0aPx17/+VcHjqEfTgN9/vw/q47ptgLmo3/EpNXnrZs5+KH41KNaP3jzAik1/RT9Yp2+tUY6G2nrV3TUbNzVlLObuPMwi4W9g1kA5hRNPwut4rz2rC344oAO+88gix/KyOlGeDwovSJ1T1E8W1gBkXAHFowYlPTx6ExbNafkzU8jkaT2lw76KRzSYmBfSKdvblFGrYnXo5sdBcchL4DF4GpSgBnfjt+T03kVNPDkMQSZdN8EPv7JZsK7Py4mhpj5uO37b6G548G2zVcKtaTaX1KDwJy7hIm3iGT58OHRdt/03c+bM1DVXX301Nm3ahCNHjmD16tW48MILTWk0btwYM2bMwIEDB1BZWYlXX32V638SNsmPhzVYmoNeOaeTioPiUuNPXNGfe+7SgYnVS1cO6WxKR3RwER5MOOd/MaY75k4+23ytQMbHt2yCfh1bYkjX1txO//P77L4ErgKKptkFN43920i9JQ5KwgfFeCP7Pt7E8M7ze+Kktu6e/qxZFm/lDm+79JLCxszjjvmKats89EaOkUbdqzRQpEPdW46ZNCiKZfJkXqx0jZo463lPkWRFTDwOpj9+uoxj8KBtMqRTUtDY5HsnqplgC/+Za3UqNgvkbX3QvpU9cJxbds1lNShhSyIcaC8eF0Q/UBGHTrfrNv52bMpkxOK+7/XCK9cPxq/O72HbR0HkY0zd43IpL6n/G3YCOrdpxj7plG9Mw6vXD8ELPxnETPu1nw5B40Y5uOx0c1yJmOYeSda2F49APdiiucIaVZGNlzHKuH8Me9WR4bdJQGFrUO67sBdGdi/Gy9eJ+WtZfVCcaqe8qk4oTRasajcKo2nzIr8Ejk7IXoQnmXN2FxSTecWvgzQvf1aqxnytGhReHBRRZQ/vMrUmHvfrjNcYH6kuHjdrVwSzd1rFIzv4ehFGZZVtrHGgEWdSwoz35PJeeBMcHhGVT0hAcUP0xblpRkQ6EjeHsEY5MfTvVGTbaMst4qq1DG7X8gYR9oxJrIa0o8IG6/p+HVsBAH77vV54+9ZhqeNuGpS7vuscVdRxFY/lFG81jRFZ2/qau8/FJQPTMXvYnShbC8fzQencphmeunIgBnbmx45wysP6aEkBylhOVRiXOvr2QfFwv6h2B2C3icqatMCmetfutA+KvU3VGSRoNwElnZ6/+uWasxwnaGqGNeMz1dTFTQKa6HtnfVuqFyY4IZsHaxkwz5zFfja15eFeHrLkQgKKC6LvWTQORBAfdcJEIX4vqwznG2IKhNUmc2KaKShSjoPg9caNQ3FO97aeQt3Xuy4zZt8nG7K/sGkjVwdVc3mNGhQ1n2bCSZadBwA8+eMB+PuPB+CXY7t7Sl+z/GvEGC00DBWys/bTfFKHvW4qqtMCiqizpijpSLL2c0YNn03bZ2kWXrUEVrz4ILDOabCbXd0wplMX1z1tjcE08ST/zUDbk13Wy7qeF3eHvfrPZbyRKk1mzWEykIDiitiLE5VY1S0ztvwtkbf12p+fezJ+OiK9XxJvsqikEVuSOK8336QVi/EDtfFsrCJmt3jcOlvWHAfxJF6U/MaUmJ0YRzBy28lZBqddS5vn52JUz7be83OQUEwmHqGkxDUeIsiMk0wNikFAkQ185UYyL9ZgLqNBSdaZ+ESKfZzvgyL3TjRN7DsxmsyM6dTWx03PLPpcbv5dTjjFm2Lx7pRh+Nl3TsZ3+6QndTwNVDuOzxgr1D/PB4Ud48W5jEFsqRIGJKC4IPyBKL7OPR13k4QV3mzLanqRUafLDhzGy7/fvz3+OoHvFCzriW7Li1O2eusyY03MkdRLPBc3gcl4xNipNLaYeIydoQwyPiiqsZoi3VDl5/GD/olwADLvi3VpZXV6Sbfo3jEqcPZBMV+blJtEB2NelchqAFRiLHttvW4WUPwkfPTmr/YfdrzM+m25tcMTi1vgppEnoVleun/i+/CwYdX3YMs2GE7XGuusf6dWDqUVg9d8wg6BTwKKhe+dat40S/T1ZFqDYl4WKyak8DQoCWdUdtqTR53so5R2REwpd3+3J7qXtMCN55xo+kD+dOmp6XSO/mtffu0+a4/H7SdNSgaF6k5jedx8UIzZGjUaXY9rhod+0NdT/u1bNcnMMktGbZtmfj7LIHP3+UcHHK+7aCcx+qCoDnuf1qDYz5kFFPM5ngbF7zt20jby4A5ekk6yRprn55p9UAxZPH3lAKEgcElEZS7RpcxWjGUztnVjhGZeX2LVijw1cQCGncQWUJhB6Ay/zzQ44gNAE8n9uhLpRVPjQgKKhUcuORVPTUzvgSOrnVB1nUBKpl9CKvSUBkVjHucfECmF6PXumpqrh3bBnFuHoU3zfFNRjBoVkSLyrrHFloBFg8JJT2TnWSvGvo/VD/I0LEaH6dGnlJjCnYvy3s/ORsumeVKrzUS5+swuAIA7zks4KrOSNfmgHP2X1wm7IVPs5DPKvi9HE49qHxSHMtY5aFDs2s+j/wrnyz7OG6Od6tCPicdqxnnx2kHo0a4Az11zOlew7Nu+JQ7XiK80E+27vWqPzAJKugJF/LmseZ510nH8d8PSvHImVJOGnYCFtw2X/s6NG4dGCRJQLGiaZhoMnF6zcYANU4OiaZpQDxXjdGbWiK1BytKmwVKg9ZlMIIy6sy8z5t2dJjfHYtKyOOPyTTxOJWXTzCRUOc+EeCHpvfqHnHBc86N5sLU0frjruz2wZOpI29JwHsl8WzbNw2e/GY3OrZuqKQiAK84w75DsWUCxtBdjQEJZc5V7XglYZZRaZpz81+d75QcnlEvH02oraBjStQ3+d8tZOK1jK65pTtM0VNXaA5nx0xXD6vsh/szp+1jCuBOsPZV4AhWrn+Q535/TvRjFBY2l24NRGI8SJKAwMDYecR+UYAUUe8AmYwMV6xhSjdo6C4NmEbaM90gXVRhZU4pR1cl7Xt6An6S0sDEe/H5fm0AmEg7ei4BSYNg7xy2YFC9f3pJjUUQniC9eOwj3fa8X+hr2guGhaZopaJxbFsb31TQvV+l28MNOPs70dzJpWROPtfqNwldoJh7LeGxtI8lvIqhlxo4bOHKOi/j+dCpKx1OyZm0N7++WHw/RKmE5QIs0T2P6smNGbsy+0Snv+3daoZTI256GrFKogiOg9D7evS8IEhJQGBgbm7jgIZa2Kl80U7EsPiTce1L/2qV3I0H6LBhVibK5MMNxO8X2Ylz/0dSR6NGugOEka/6bhbGz/vHgTuyLLBj3xHDbi4eH3yXHopFkh3Rtk9BGKHr/Tpob+e3pnfJhpy2vQUnT9bhm+PW49K7somHv2zR33vIinVciPflAbeZrk4KT6GZ/PEGPJzCyZIXxp7XH/245i914NWcTz5NX9MdvLjwFQ05M+02wovom4QV0U4n13eoQ6wONV+QKTJ6MWOPqaOD3BU5B6BK/7ZNLWU0WK0jjz8892XGVZSYgAYWBF5ukuK+Kmq/MFklWIm/WYGGaDUsUUfZ5TJujCdxr7Oys4f3ZBWL+tF9m88NxV9EaO0tRxzrj7sPM/txwkFcfMkHZWJi1RXIdr3gezncFqIizqcCdtBNOGJ9hRLdik6lX1MTTo537lgdAuo9hLzPmh7q31nNSu/aHH/ZFq6ZyO10b4a1CYZXvon7Ho0e7Aub1bu95cNfW+PHgzo73cE08AbUi1rsVElBMGhRj52S+7tHL+tkG+lxGCAV+eAfGMY7zvdt3yBO0yxkalB8O7KB0wYAXSEBhYJyNOM7cTIOLWNrKVvO5zPhPKm5uO5b07rbNOJHwPH9n8jAs+Plw86xFokgPfr+PxNVqwkNbEfW3sA7aprLwTDymfMQwalCOMB3R7LOfJO/fPgL/nHQGevlUszpFkmXhpY3yHCZ551V2e9ZONNlO/AQMs5Y3L0fDfRee4qV4TJJ+D6wiGp30612eIT8n8U33Or4QH9/1HXQs8ubbw9PAtGpqXzXjNkt3KjJzqb3lEPeZDdeJaLSSphu3Nu8Wn4iH8flzOVp3TQPG9S3FpGFdTffafVD423qwjhu1udYVncl8WRhXGBmpqKq1HcvE6j83SEBhICORJsm8k6z5g7Ame/9FvVO/G+VouGXkSSktid2kk/j35LYtpPfa6Xpc+vofDOjA/QBYiNSt8QpWR+LUfYuaBRI+xnxBgZWZ6Gs0zsIra+wCitlp2Jxoh6KmGHRCa2SaIGZNrivH3O6XOOd1EuCkzcrNieGKwZ1d49GIft/JWTvLDDWyRzpwmJuZyrjaSxMw9fLOszQo1w/vilasZb0Og6CmaY5+K2nh0XyPEcaGvrb8muQ5O47fMvKkVN24vRNWgDSRt8j1QWFda/nbKmBpDnmK7vNm/M37hgs5WjaWD0r44gkJKEzcGhuLUFfxMM4bs7n2rBMw+Tv8eCZ+BiTrKg6ZWatItiYTD0MT4LybsfhzaS71CViXRsrXWRVLQDGVIfguQVYoFE5X+npZCYV/vX0DvaODoGyZTO3LKqA4d/zp+8TySg5QbmV0+5ys+3d57V9YGpTBJ7Rmfs+pVVIeAs2IFG9c34QQeKJFC6wBaFuQ8PEZ2b3Ydp8xIuygE9JmUTeB1eokmwhuKPedsDbGNGJNL0fCSZZ11HgvS0AxR6VOP19LzgTyzvN72o6RBiWiiHpkO6mwufcoqnERU0ZyKed5vd1mfQ75OJwb0KmVzX4ro1WX/QBYmi3bMmPObyvWdyci2HiN+3XJgA5oV9gYYxgOZ17MhH6QnRkKp8uaTTucV6lBsa1sSf7pI1Cb9V00OjqIXXza8QCA7iViviY8GqU0KM7X8Va0JLGZCjyWxy1aKSuPGoaqQ4O8icfKpLNOwN9/PAD/ZuzY/e/rhuAXY7rjvu/1sp17+IfeghmyYtyIreJJX+Q2ZliPsZcZs/NhVad54mbPRzOdT//VkmGyA4CzTz4O798+wnQsCgKKv1jiDRSzBkWtZsTzMmNLMzVrUNgqyjm3DsM35dXo4GaX9ql+NyIzJsj7oMhd74x1UDPOQth3mLaBl8jp99/vg3hc50SEtM9+wkaVM6LZ8Ti4Z7ObLL1pUFhpJClokugqR3QrxtzJw9y/KRdSfYyLRC8dHNClmmVMPNwNjo8mwhKeNM253t18JICE+WsUY38cTdPQoagprh/elXGX9++HvdeNXFrmOCjuk0cZgZCF8Uq3VafGQzwNCpAwKf94cCc8t/irxH0RUF9EoAjRQ9RJ1qRmE6xJdcuM3cvYuFGOUEeqcuiQM/HI5SwS7ZUZDM1lRqPBw2aBssIVdxmnon1HRMshUueeCuJ8k12DIpeJqCYT8O4ky0rzV+f3wOATWuOKMzqnzp3UtgU3si+vmHk5MUwYZA9q56ZBkTWjqBykNc1uhkjkkfiX65AroEHx8mrcnoz3DbvlxYqDIvSZGK6xLht2S8+ap5P/EKsd83x4UnszcdwUbhl1Eho3iuGqMzsz8zq9S9o0FoXpEmlQGPj1QWnZtBEOHrZ7RVuv84PVJCHzwVsHYJUzd5n+VDZblhrV6blTy6rhYgrSLHXAKZfx2VRpBPaWV6d+Z2TPnIDkE7d0radl83Cqb7sPimTirPyOJvKTs07AT846wX+CYGsc3Damkw42J3d5Clbb42tQkv9qmH5xb/zy1U9M+TutPJJ9N6aVc65tzCCUGO50q0K7iYet7XTKzyjgsbRe1vabw1hm3CKfrd1gm3jSR1kaFHP/lv6rU+tmWPvr0TbfJRZR0OiSBoVBTsx9sAL4/gMPfr8vlt4xEmcYHLXS16l56W7aBJlsvBaJdZ/MjrR+fFB4iKZo1UCZPmjeTR62gZchAv0BgOgISsJpcdL2Y+LxWjwnzVAdS0Bx06D43E/of7ecZT7PeTKmY2dMY9ahecZuFQ41R78ZP47gbpMCGQ2EEZYPijE8gEh+xjTc/IYAdiTZXscXMFcUsYpvdpK1/3aK6+QknPCcb8OCBBQGslEBActHqwFtCxo7qkdlsQdsYv/2gi2SosMSQDfuuzDhvHbzOSe6XitbbJbfjVUgYilC3PbAsV7DDXUvXFJvsJwOVSNS556cZF2O+XWSdcI6SLLMCAUCAw4rDZXUMd6vq4lHstEZy33lkM7cgGpWZPoq42GRCLbGiLyp/Dx8TUGNlywTz+OX98eJxc3x5BX9+eUxpWHQ3jA0rXWWPQuY/miahru+a68rVs9j1ixptt+mulI4Ac00ZOJhEGOYElzvERjgnM5d3O94sYxS6binKYox2qkIpYWNsfNQFUafYl+V8oMBHTCyR1uhbdFlyy17PcujnXmd1QeFOxMz3qMeVrhp1QiprlU9HEOg5P3tmpTD5dZHSjvJpl/YqrvPRe973sZhxlJv2fy8wtageHeSZc22VSISOExEq9lFMraSV7g+KC73WTUoug6cUlqId6ecLZyf0QeF9c6MG086wfI5YjUBo2CYIzj2yEIalIhifOHOKlZ2I0keZqtH7cdO71yEP0gukXN3khVvXFZ1plu7fOOmofj7jwfgyiGdmedFhBNAXpsk4oPCXNHEyMcqkIh8jNYt4lVTzojmqIpLBnRAj3YFGN7tONdrPe1Ia6iQpLbi+/3bG85br5dMX+Isa4GM7PYVQSz5Zvqg+DDxsJ07nQcrmXrnfROmyZHAMmdVmkcZHxRT/q5Ost5etrGuzT4oxmsS/7K0ZyxYcoz10KgexTixbTpGDCvKvsiCAjciIJ+QgMIixlHXOcH0sWTcq2maqeMGgNbN8+RXNVj+9tMJNMuXU6S1bp6PUT3b+t6CXuSZeTZR7p0Os3bzZea0mCYKh+IFsWy27EhwGpTff78P/nfLWcjPdY7ACfg38fx94kCs/80YtDPs7+R0vV+s4wsvRo6UI7nH3tnpLtYs2tVJ1sHGw9KgiGx6yYYVkI1dZxpvYpY4KZK0eKkkHNO9DqgsQU8EkxXF8AdLqGRpz1hc3O94tGici3F9S5nnJ486GX+fONCUNzOSrFBuzpAGJaIYP3IRhyfAahayq5iNPPQDs7ZEpOM8ua05MJQ5qqq/htRcUkBRhWyxTYKjVD7OM0meBsV6zOTT0ER9nVUx9+rJPnJiiVDk5ngQ/lQoziYetgbFz8CownHcGsytPi7vg+LUN7A2rGROlDwS09hh682mjcwNYu4aFG941aCYJkMuWvdaQQ1Kq2Z5WHXXd/DoZf1Sx4zJsd4HK8K2ximbG7wAcGFBAgqDnJhzY0tifO8sT2ofYRhSvH7jmfi/YSfg56O7mY5bA4v5yUvEYz0IZHcMZX0wtuXDjBmk0HdmuChZl9b8jG3hijM6Y3i343D/RfaIll656ZyTPN/rdZM4Fl5mTuZbNNsxa4oq+z5rcXnlb9NCzPQIqNGQPWFwsNShK/dByWesxhDdLFME3v1OAkqgAfnczruavdlYzVSiXalVC5uEpfUqbsHXJlqxR+c2LJk++tPcLxrGglhSg+L/PYS9kzFATrJM8gwNhBca2IrZ/MBWMfNwUvP2ad8Sfdq3tB3nOYSxzrudc9KgBNlGpX1QDIUREcickrcOqOwNTTXw3mKTvBzMvOp090JI0LG1NyGjZdNGeO2nQ5SVw5uJxywwe0mzX4dWWLejjFMmfgK8OCjW7+rvPx6IO177BD9z2JcqnabrJRzs/UAS1izarRk7xRRpzphYmE08/j5evg+KwcRjkZFYtxQf3T/HL677IPlIu6hZHg5U1kiWh33caMlLXtKztADHtcjHN4a4R6LonN9JjO8g9f79K4UiAWlQGOTmxPDO5GF46+ahwuYP1sxdNJKlp6iKLrZmmYbW1GVn0KCQ9kFhabZsTrKsfNzK4W4GspYlSozuWYLWzc2DgB/Vu99OKr3UkT3DTJyz3/eLsd1x+Rn2aKvu+Zn/5kUr7VbSAq9cPwRDTmxjS8O6DFmF/d2ahIyT7I8HdwIA/PzcbuwLwPEd86hFYNEoJ8b2QeHM3nmcUlqI336vF565cmDqmOi3ZBQy3TUoYmmyWDz1HOl7jNkZ8+b1+9aNVUU5ybhpIisIHMMHxfuWKtGCNCgcrD4fbrAcOD1s9imM24zVCava2ElQyOQeKm4Y61hP/ev+LOy9iszvy1iHyTS7lbTA2q8P2Y5HDVa5nPbccINVh25CLMsUZ+7AraYAO83zc3HzOSfh+SXbHNO3nbMuYU5pUMR55XqzBkq0bY4/rT1e+fhroftYJh6eCeeecafgmqFdHE13rMmTqc4lvl1WMVgmJGu6opsVXn5GJ+Gy8HCfaPAFYjdEnMedymOsE947lS3Til+NwuHqetvkwwpraxae8JRtkAbFB8b3LhIqXSVmx3O5DGvq0qrmWZPOUFMgD8j6oJhNPPrRf51uFktX0zRmZz7jR6fhon7H482bhgIArjs7sUnZ2F72+C9Ro9CPgGL4/fvxvfHjwZ3w+o1nStyf1KAYjlmqV37VmoOJx9KLye7Fc/uYbjjJMiERLd9DP+iD5XeOYp6zDmB1rFU8nCLGYho6tW7mWA6W75j3VTx2eBFHY5xvMgiCnCDxEG03vP6LNzGVrao2zfNtZt9k0qYJqsnEY//2shnSoPiA6/HMtj5w+cXY7tJ5szy3RTFGLD3jhNbSeatCpNi8DRl5fYioiUdkUUmHoqZ45JJTU39ffFp79O/UCu1bqXNIVQGrLgp8aVDSv0sKm+CSgXKq6fQszsHEw7nXS7ntq3iOCiiC97MGQVGtpKZpaNlUrMxMJ1kfWjmmBsVFiyAjGOZxwgg4OskGODAG4bQpG2GYh3HJtzleUvq4n0lDErdl3w1NQCENiiKMDTHVhgQk8ZZNG6Hrcc1dr7Plx/nNKo8VowYlTPxEkuUKKAy1K7N+rHFQBIvSqXWzjC6tFIFVFaKDJht58yFz9ZSDnplX340b5UivjLImJbuKzq8PF+9a6zfICtblx6+pOWNzOa5W14Xh3YrRvlUT0zGeBkU2kiwL0cE6aLPqP64Z5PleY1ybRoa64sWuuez0jhjZvRi//Z73lX9u9ZE2r0arj/IKaVB8YO4M7OdFPi2vKlKrY5TMh1wdEQHFjw9KcpbiaOFJDZT2jKwDqojw40azvBxUCoZSDxo/Pih+t1FgCYZ2DQo/3dLCJrZjzqvSrMKPrPnIjsiWAKz8nJ7ZSyRZJ07t2NJ2zKtmtUleDhbeNgI3v7QKb32yC0BCQGGZO0z9nsdlxiO6FWPCoI7ofXyheCEVM/uGM9G3Q0vbcdFXYoxr08gU6j59jbE2GjfKwVMGR2EvuLUX1l48Ml+DqHkrU5CAogjWACe0FNajoOvH1hwVDYrsQGKcrTVuxHZqE97c0XKPivnG/J8Px+m/m6cgJTmM7axJoxwcqa1n7pMkipfOjWUTd3y9DudYQfCckrJHknW4mFUUhRoUnt+NDh292xfii32VpntldysGgDdvGorV2w9iXJ92joVhaw755MTMEx1eADOTBsVjBxaLabj/ot6e7vVDr+MLMGlYV2zbX4lTGcKJDCYNCsfEoxpmyqYJxdFDJkE1e7UpJKAowrzCRDf9GwRuQYnaOsQecNs1V2WwJydktcO5scRun4er61ByNIy6VeJnDa4/HtwJf12wBSM4+9BYNSheKS5ojEFdirB06wHfaclgbGfzfz4c63eXYfjJ7nvu8HCMAMu7h2Fag0M7ckr1tI6tcM3QLujSphl+NXudQN7WWbwc7DhCMhoUsXTvveAUtCtsgvGnpTcG9TKW9Tq+EL04modlxrbnoU0bg93y6sDkJJtBHxQV6DpwASeMfOtmedhfWYMzuoj55Rl3KDYH9/RXRidYgdqMqAx1HwVIQFGEeR28+d8giDEHhATPXDkQ7Rhq8iRR0aB42Z34mqFdpPOZ8p2TMfSkNujXoZUpLWvaDYGSwsYp4c0rrCXDMqRnccZj4gOZcdv5pIDidL1flyARJ2rn+40mHr5Q1rJpHn5pcYgPdBLj4R6R8rCcMlPnFH9HXvtQnobVKb3ZN5yJ11btSMWgccO4KssU6j5ACYW59YDhd/J9NMsPJ7aVashJ1gdOHTAg+nF59EFx6AhHdC92vNdNQMlUvA8VvqbOPiiJDHJzYhjStQ2amGJ58P0WomWFFUBxga1LsOXvOfqv4bzVFKDSic8+SPpPW0mgNoFrgpxte9nN2NpnsYpnTCOXsWFhmEz5zsn44YD26NOerWFyqu4ORU1x88iThKOHW1dlJVcEmfrfDFdP8t2cfXIxzu/dDreN5gf6ywakBZRFixZh3LhxKC0thaZpmD17tun8lVdemYgrYfhvzJgxpms6d+5su2b69Om+HiRszIG+gsfLIJLEzcRjykcqZUmkNSjyGheRc7brfLzAMIQb1Xl6CQLIMgs5mSGl/UQkWqJsmz2x2L6KLlPjSpBOiV5kLBGByVnDFq7AcvPIk/DA9/ty+wqV9W1dlfX+L87Bf28+C/07teLcoYCjxTfuwMyKJJsT0zBjwmm4YcSJUm+Et7Q8LKRNPJWVlejbty+uvvpqXHzxxcxrxowZg2eeeSb1d36+3R/iN7/5Da699trU3y1ayEVujRpmJ1n3FSZJPDvJGndPlrw3OiYe9WmK1qfxMttyUMaus1FG+SDnJLxJ3G7SoPj0VXA08XhM+9WfDsH6XWU4m+Gvo2SreYEkgjQDe3sCc4HYoe7FTTzP+1jGGwQq67vWIs0VNmmkJNaJE8kcrx/eFXPX78b3T+tgOu+3Tx3Vsy1O71KEfj4diFUhLaCMHTsWY8eOdbwmPz8fJSXOqwhatGjhek3U4ZlZUmHYg5wdGX9rkJpGuwkomXOSdU/czdzUvlUTbD9wJPW38Coeh7xZy0GPJcxLVeVNPKxgUdZ3fdvo7vhw84eefIqs2FbxCLaB0zq2wmkd2bNdr+2e7SzMJ8iWxtpo0K1EIl2W6DL041rkY+hJ9n2PwkSl+bqeERk4aJJjynEt8vH+7Yn9gxZu/CZ13q9g3Sgnhn/932BfaagkEH3OggULUFxcjG7duuH666/H/v37bddMnz4drVu3Rr9+/fDggw+irq6Om151dTXKyspM/0UNVkfud8ddJ2IOsxg3oqJBUSH7PHPlQAzvdhxzN1+n9DXOb4Ad8VOYBiDbsJzupO7XzP8Cdi3HqR1a4vP7xqScYf1gEwQUNKwgNgtkEeQkxouzpvUOtlOmgwbF8Ju3TDlMVM49nDStxS0SVoNzujn7A8rCjiRr+M2octau19mC8pKPGTMGF198Mbp06YItW7bgjjvuwNixY7F48WLk5CScFG+++WacdtppKCoqwkcffYSpU6di165dePjhh5lpTps2Dffee6/qogZHKtR9cJ2PeRWPHDI+KEEitBePy9OdWNwCM686PX29B/OE9bowNCh+xkPlPige2hZLqHHzZeGttGCXiV8SY9rFLfJ9hS8vbpGPveXVOJOx47EsYTvJsjQobogITCYNimWK67ZwIGxUCoS1DhqUN24aigUb9uLCU4/nXuMFt9KzvpPHJvTHrf9c5bgzdlRRLqBceumlqd+9e/dGnz590LVrVyxYsAAjR44EAEyZMiV1TZ8+fZCXl4f/+7//w7Rp05j+KlOnTjXdU1ZWhg4dOtiuiwqpOCgBBmpzVAG4kJ8bE44mG/ZuxrJCnvCA6qCK9yOgeBVK/dSy6km4F+0c8zLDsSA3lTN2yk9fOdDXKp5Ft49AZXWd6w6yPIzvQqQc5/UuwUvLtqN7iXofPC/zENmFh07tI2orfAC1wryTBqVtQWPXPay89BVsnyDne3qWFuCdyWdL5xUFAnfZPeGEE9CmTRts3ryZe82gQYNQV1eHL7/8knk+Pz8fBQUFpv+iTLIRXTIwIUQ5eXV7FQBMfgKSaTxz1UB0bt0Uz/gMu+yX4CMc8tN38rMJR4PivS5Ul9bPRpRA2pyjMY4FgRetGY/GjXI8CydWRIpy13d74qEf9MULP1HvTOolounEwZ0BAGcJ+o6w9uLpdzQE/w/6t5fOP2hUCvOs3amD4vzeiajBVwjGaGkoBG6c+vrrr7F//360a8cIy3yU1atXIxaLobhYrb0ubK4+swv6tG+JU0rVC1Rudkcn+ncqwoLbRigtjxeCWcXjY8Z/FC+qcb94qYqe7Qrw2a4yU2RSFXjbRkGz/XJa7SGL090q9lFSheysuGleLr4f0EDuZQAd0b0Y798+QjjYn235ODQ8d/XpWLXtIIZ0DW+ndB5Klxn7nMjITCz/8qN+eKi2ryWWU8NHWkCpqKgwaUO2bt2K1atXo6ioCEVFRbj33nsxfvx4lJSUYMuWLbj99ttx4oknYvTo0QCAxYsXY+nSpRgxYgRatGiBxYsXY/Lkybj88svRqhVf05BNJL+BWEzD6V2KHK/1vMzY0ilno2+mCh8U+/WG36I+KJa//cyMvPZ/XtrBqz8dgh0Hj3jaDdsJvyYeVrhtv+EVHJcZR8+SACDYFXAisDQoIgJ8h6Kmqd83jjgJc9btxnd6luClZdts17KWGbdo3AjDfGy1ECRKTTwZ9OXTNI0rnDSUnYtZSAsoK1aswIgR6dl30jdk4sSJeOyxx7B27Vo8++yzOHjwIEpLS3HuuefivvvuS/mW5OfnY9asWbjnnntQXV2NLl26YPLkySYfk2yB961nQliIogOaLEH4oHjKOwomHg+dTONGOcqFE8ASY8dDM2Ot4vFrznO621h3oWtQTDvZhvuNqti0rqSwMZbfOQoHKmuYAkqQvkVBoNTEE5FwBKUt/W1tEWWkBZThw4c7qsnefvttx/tPO+00LFmyRDbbrEJGjej5886ufoFJED4ooitQePunAOGYeKL0PkXjXBhhyXvGeg3USdagncnUNg1ChPxOVQ2gziuoLBoUJTkGh8qdhv36oKhqqycc1xwzfnQa2jQXC9GfTWTvAukIk5cbfLhgc2j9aIXLFk47uKQT6Xv0R/G3iscbUerYjY6PXswn6VD36WO+fVAkBskwMb7/sIvFioOiukj2ZcZqc1AdJ2b6xX2UpVUboYjT5/fh+3hmM9EKvJ/l3HTOiRjZvRhnnSRuf+1Zyt7Uyo0odcpeCeIRhIUSh3OZtC0nidLrjDnZvziw9t0xr+LxVyan+jGeCtvEYyTscgVlqnQK1BZ1VEa2PaFNM1/3h20CzAZIg+IDa/P6mUQgnP/dchb+uXw7bjrnRG95m3o/T0mETpiDiZMpyJcGxeNDRamzMms+5O+PMSSUIAcyk8N4YLlkH5kwVbKWGR8r/O6i3mjVdAMmnOFt6W+kzJERhQQUHxQXeHdO6tGuAPdccIrn+wOduTSQPkf0Maxal1CWGUeozo3+IqLtzCiYMX1QMhQHJcjQ8SLIBmoLEqaJx2ORWjXNQ4eiJtB1oKhZ2tchSu020xQXNMaDP+gbdjEaNCSgeODJK/rjnc/2KNnoTBVhd8yilBY2xs5DVRnJy+sy43BW8UQHL6t4jDUmslmgLM4Oz+xyhE3Y71RlHLFYTMP8nw0HYPVRsi8zVkmWdGtEQJAPigfOPaUED/2gr9ReIqoJVK0d4CzwrZvPYmUTKfysfvDsJBuhqagXwcI0kLB8UPw+nqCTbPgDmkGTFPIrrVfsxJmbE0OuJaCNdXVWlEyVRPZDAkqWYlrFE3qnLE4mhboSQROcdSA5xlcZe1wSnK60ZNs0Cl1BmnjMQlR0Pgan7RQyAVsTqLYgYQthRMOGTDxZSpRm3DLI7pTrRVh4/cYzUVFVJ+wjFIlZXwSKkMQUSdaDYBHIMmPBc2EL62YflPDKAXjbLFAWaz8U9jMTDQvSoGQpmXKeV51NJvwF+rRviSEnSiwnVPiQvzq/BwDg+uFdpe6LUr9uDtQmdo9xYE5pUExp+o2DInYuOvqT8FEZlEyUKLVjIvshDUqWopl8UBR3RAH2MrIDVSZmZCrz6N+pCJ/fN0balBUljZjJSVawMZhdUBgalACnQry6O71zEZZ9eQADHHYTV01uDrssUYmDEqFmRhCukAaFcOT7A9rj+JZNcPkZHZWkJyugBNWxB2nW8eJnE6WBw7xZoNg9bNOGwQfFt4lH3ln3sctPw6/O74EnrujvK28RJg07AQM6tcKoHm1Tx8J+p2FoUAhCJaRBaSAE1RUVNG6ED34xQtkM3+zcSx1okgjJJ2ahRHiZsX31isrNAsUFpXQ5WjfPx0/OOsFXvqLccV7CtDd71Y7UsbB9m0JZLh+lhhxxqPtzhzQohCsqzQ9RMWWwzGKPXtbP9G8miUq9AGYTj5dlxqk4KIbzXlfxXDmkM04qbo4LTz1e6PqIbDALIPzBulXTzG8eF6V2HHVIw+UOaVAaANTO/ZHsUsf1LcWYXiVolJN5uT1KEcO97LViMvEk/9X8m3j8RFsOm7Bf6d3jeoZcAv805K4tDA1XtkEaFMJG62YNb9tuJ4zdRBjCSYKwh7M0skvBAbNGiqVBydTEOr9RuF2a2dSV+Xf6yvWDMbzbcXjvZ2ejLWOZfbO8YOMQRacVRx8SUNwhDQph454LTkFZVS1+PLhz2EUJjLD9A6xESTPud3kwywcl6E3lJo86GV8dqES/Di0DzUcG4xO3a+l93y4Z+ncqwsyrTrcdf2B8Hzy35Ev8cmyPYAsQoXYcdUg+cYcElAaA6nbetqAxXvjJGYpTJZyIar+uCSokWJvkeTEVeeWWUScFmr5XXrl+MP747ibc/d1wzS0/HNgBPxzYIdQyEGZIg+IOmXgaACe3bR52EQifREqD4sHE45ZOkHFQooqmJTQa/7hmEE5q2yLs4mQE1ZpJFf51t43uBgD45dju/hNTSBi7pmcbpEHJYhZPPQdlR+rQrrBJg3WUPbE4eOErCnUXJZOTXydZFkFrUKICS5NEhMsNI07ED/q3F976IlPESYPiCgkoWUy7wiZoV5j43VCbeo92BXhq4gC0K2yiNN22Bfk4vUsR8nJiKGgc/mcQ1bFMtFwnlzRHi/xcHNcin3neb6A2IjsY17dd2EVgEjXhBCANigjh98wE4cJIQ3ROVWiahn9OOiP1O2zCL0EaL5v85efmYOVd3zE5wxrjPByLGpRjjbNOaoNTSgvDLkbWcCy3FVFIQCFCI+wPNAqCSdSRqaK8XLOjiSl42zHog3KsUdxCvZZC+T5jRFZB3QZBRIAoCUvGsvjxjTFqUIJeZkyED21dQaiGBBSCIEyY46B4T8c4XB0rJp5jmSDEkyg5jxOZhwQUIjS6lRwbSy9FiOr47Uew0I9BH5RjGdpbhlAN+aA0ELJJvfrulGHYdagKPdoVhF2UyBCl8du8C7H3dMwbCHpPJ5vInq9QPUGsmiUflGMbElCIjHNicQucWEzaEyNRUmWb99Dx44OS/k0+KA2fbJokEdkBmXgIIgI0DXgTNxlUOewaB6woOQEHybE8SAfx6Gee2EZ9okTWQBoUggiRv/yoHx6ZuxF/urRf2EVJoUqWoECZxxZB+KAM7FyEV64fgg5FagM1EtkBCSgEESLf7VOK7/YpDbsYgXAsaxOORYJyku3fqVUg6RLRh0w8BEGYUGWMIfHk2II0ZoRqSEAhCMKMIhsPLTs9tiCNGaEaElAaCNQ1EFGjX8eEar59q2PHf+BY/g5JPiFUQz4oBEGYUGXiaZ6fi89+MxqNcmgedCxAGjNCNSSgEARhQuWK4KZ5x1gXcwyP0eSDQqhGemqzaNEijBs3DqWlpdA0DbNnzzadv/LKK6Fpmum/MWPGmK45cOAAJkyYgIKCArRs2RLXXHMNKioqfD0IQRAEER4UjM8bVG18pAWUyspK9O3bFzNmzOBeM2bMGOzatSv130svvWQ6P2HCBHz66aeYO3cu3nzzTSxatAiTJk2SLz1BEMqJUlRbIvrcM64n2hU2xt3f7Rl2UbISEuz4SOtfx44di7Fjxzpek5+fj5KSEua59evXY86cOVi+fDkGDBgAAHj00Udx3nnn4aGHHkJpacOMCUEQ2cIxEvSVUMSVZ3bBlWd2CbsYWQttpMknEO+1BQsWoLi4GN26dcP111+P/fv3p84tXrwYLVu2TAknADBq1CjEYjEsXbqUmV51dTXKyspM/xEEQUQN2tyOkIU0KHyUCyhjxozBc889h3nz5uH3v/89Fi5ciLFjx6K+vh4AsHv3bhQXF5vuyc3NRVFREXbv3s1Mc9q0aSgsLEz916FDB9XFzn6oXyQUQd0lQWQO0qDwUe5if+mll6Z+9+7dG3369EHXrl2xYMECjBw50lOaU6dOxZQpU1J/l5WVkZBCEAFB/SVBZA7SoPAJPEDBCSecgDZt2mDz5s0AgJKSEuzdu9d0TV1dHQ4cOMD1W8nPz0dBQYHpP4IggoGcZL1DoUAIWUhA4RO4gPL1119j//79aNeuHQBg8ODBOHjwIFauXJm65r333kM8HsegQYOCLg5BEC40zssJuwgEccxAJh4+0iaeioqKlDYEALZu3YrVq1ejqKgIRUVFuPfeezF+/HiUlJRgy5YtuP3223HiiSdi9OjRAIAePXpgzJgxuPbaa/H444+jtrYWN954Iy699FJawUMQEWBsrxIM73YcBtAusgQRGMe3bIIdB4/gnO7HhV2UyCItoKxYsQIjRoxI/Z30DZk4cSIee+wxrF27Fs8++ywOHjyI0tJSnHvuubjvvvuQn5+fuueFF17AjTfeiJEjRyIWi2H8+PH485//rOBxCILwS6OcGGZedXrYxSCIBs2/rx+Mdz7dg+/3bx92USKLtIAyfPhwx10r3377bdc0ioqK8OKLL8pmTRAEEWnIBYUQpV1hE0wc0jnsYkQa2sWrgUDxFwiCIIiGBAkoBEEQiqBVPAShDhJQGgjn9044GHcoahJySQiCIAjCP8fYXugNl/N6l+DVnw7BicXNwy4KQRAEQfiGBJQGgqZpOK0jLQslCIIgGgZk4iEIgiAIInKQgEIQBKEIWk1HEOogAYUgCEIRJx5HPmAEoQryQSEIglDEoBNa45FL+qIrCSoE4RsSUAiCIBRyUT8KXU4QKiATD0EQBEEQkYMEFIIgCIIgIgcJKARBEARBRA4SUAiCIAiCiBwkoBAEQRAEETlIQCEIgiAIInKQgEIQBEEQROQgAYUgCIIgiMhBAgpBEARBEJGDBBSCIAiCICIHCSgEQRAEQUQOElAIgiAIgogcJKAQBEEQBBE5snI3Y13XAQBlZWUhl4QgCIIgCFGS43ZyHHciKwWU8vJyAECHDh1CLglBEARBELKUl5ejsLDQ8RpNFxFjIkY8HsfOnTvRokULaJqmNO2ysjJ06NAB27dvR0FBgdK0iTRUz5mB6jlzUF1nBqrnzBBUPeu6jvLycpSWliIWc/YyyUoNSiwWQ/v27QPNo6CggBp/BqB6zgxUz5mD6jozUD1nhiDq2U1zkoScZAmCIAiCiBwkoBAEQRAEETlIQLGQn5+PX//618jPzw+7KA0aqufMQPWcOaiuMwPVc2aIQj1npZMsQRAEQRANG9KgEARBEAQROUhAIQiCIAgicpCAQhAEQRBE5CABhSAIgiCIyEECioEZM2agc+fOaNy4MQYNGoRly5aFXaSsYtq0aRg4cCBatGiB4uJifO9738OGDRtM11RVVeGGG25A69at0bx5c4wfPx579uwxXbNt2zacf/75aNq0KYqLi3Hbbbehrq4uk4+SVUyfPh2apuHWW29NHaN6VsOOHTtw+eWXo3Xr1mjSpAl69+6NFStWpM7ruo67774b7dq1Q5MmTTBq1Chs2rTJlMaBAwcwYcIEFBQUoGXLlrjmmmtQUVGR6UeJNPX19bjrrrvQpUsXNGnSBF27dsV9991n2q+F6lqeRYsWYdy4cSgtLYWmaZg9e7bpvKo6Xbt2Lc466yw0btwYHTp0wAMPPKDmAXRC13VdnzVrlp6Xl6c//fTT+qeffqpfe+21esuWLfU9e/aEXbSsYfTo0fozzzyjr1u3Tl+9erV+3nnn6R07dtQrKipS11x33XV6hw4d9Hnz5ukrVqzQzzjjDH3IkCGp83V1dXqvXr30UaNG6atWrdL/+9//6m3atNGnTp0axiNFnmXLlumdO3fW+/Tpo99yyy2p41TP/jlw4IDeqVMn/corr9SXLl2qf/HFF/rbb7+tb968OXXN9OnT9cLCQn327Nn6mjVr9AsuuEDv0qWLfuTIkdQ1Y8aM0fv27asvWbJEf//99/UTTzxRv+yyy8J4pMhy//33661bt9bffPNNfevWrfrLL7+sN2/eXP/Tn/6UuobqWp7//ve/+p133qm/+uqrOgD9tddeM51XUaeHDh3S27Ztq0+YMEFft26d/tJLL+lNmjTRn3jiCd/lJwHlKKeffrp+ww03pP6ur6/XS0tL9WnTpoVYquxm7969OgB94cKFuq7r+sGDB/VGjRrpL7/8cuqa9evX6wD0xYsX67qe+KBisZi+e/fu1DWPPfaYXlBQoFdXV2f2ASJOeXm5ftJJJ+lz587Vzz777JSAQvWshl/84hf60KFDuefj8bheUlKiP/jgg6ljBw8e1PPz8/WXXnpJ13Vd/+yzz3QA+vLly1PX/O9//9M1TdN37NgRXOGzjPPPP1+/+uqrTccuvvhifcKECbquU12rwCqgqKrTv/71r3qrVq1M/cYvfvELvVu3br7LTCYeADU1NVi5ciVGjRqVOhaLxTBq1CgsXrw4xJJlN4cOHQIAFBUVAQBWrlyJ2tpaUz13794dHTt2TNXz4sWL0bt3b7Rt2zZ1zejRo1FWVoZPP/00g6WPPjfccAPOP/98U30CVM+qeP311zFgwAD84Ac/QHFxMfr164e//e1vqfNbt27F7t27TfVcWFiIQYMGmeq5ZcuWGDBgQOqaUaNGIRaLYenSpZl7mIgzZMgQzJs3Dxs3bgQArFmzBh988AHGjh0LgOo6CFTV6eLFizFs2DDk5eWlrhk9ejQ2bNiAb7/91lcZs3KzQNXs27cP9fX1ps4aANq2bYvPP/88pFJlN/F4HLfeeivOPPNM9OrVCwCwe/du5OXloWXLlqZr27Zti927d6euYb2H5DkiwaxZs/Dxxx9j+fLltnNUz2r44osv8Nhjj2HKlCm44447sHz5ctx8883Iy8vDxIkTU/XEqkdjPRcXF5vO5+bmoqioiOrZwC9/+UuUlZWhe/fuyMnJQX19Pe6//35MmDABAKiuA0BVne7evRtdunSxpZE816pVK89lJAGFCIQbbrgB69atwwcffBB2URoc27dvxy233IK5c+eicePGYRenwRKPxzFgwAD87ne/AwD069cP69atw+OPP46JEyeGXLqGxb/+9S+88MILePHFF3HKKadg9erVuPXWW1FaWkp1fQxDJh4Abdq0QU5Ojm2Vw549e1BSUhJSqbKXG2+8EW+++Sbmz5+P9u3bp46XlJSgpqYGBw8eNF1vrOeSkhLme0ieIxImnL179+K0005Dbm4ucnNzsXDhQvz5z39Gbm4u2rZtS/WsgHbt2qFnz56mYz169MC2bdsApOvJqd8oKSnB3r17Tefr6upw4MABqmcDt912G375y1/i0ksvRe/evXHFFVdg8uTJmDZtGgCq6yBQVadB9iUkoADIy8tD//79MW/evNSxeDyOefPmYfDgwSGWLLvQdR033ngjXnvtNbz33ns2tV///v3RqFEjUz1v2LAB27ZtS9Xz4MGD8cknn5g+irlz56KgoMA2WByrjBw5Ep988glWr16d+m/AgAGYMGFC6jfVs3/OPPNM2zL5jRs3olOnTgCALl26oKSkxFTPZWVlWLp0qameDx48iJUrV6auee+99xCPxzFo0KAMPEV2cPjwYcRi5uEoJycH8XgcANV1EKiq08GDB2PRokWora1NXTN37lx069bNl3kHAC0zTjJr1iw9Pz9fnzlzpv7ZZ5/pkyZN0lu2bGla5UA4c/311+uFhYX6ggUL9F27dqX+O3z4cOqa6667Tu/YsaP+3nvv6StWrNAHDx6sDx48OHU+ufz13HPP1VevXq3PmTNHP+6442j5qwvGVTy6TvWsgmXLlum5ubn6/fffr2/atEl/4YUX9KZNm+rPP/986prp06frLVu21P/zn//oa9eu1S+88ELmMs1+/frpS5cu1T/44AP9pJNOOqaXvrKYOHGifvzxx6eWGb/66qt6mzZt9Ntvvz11DdW1POXl5fqqVav0VatW6QD0hx9+WF+1apX+1Vdf6bqupk4PHjyot23bVr/iiiv0devW6bNmzdKbNm1Ky4xV8+ijj+odO3bU8/Ly9NNPP11fsmRJ2EXKKgAw/3vmmWdS1xw5ckT/6U9/qrdq1Upv2rSpftFFF+m7du0ypfPll1/qY8eO1Zs0aaK3adNG/9nPfqbX1tZm+GmyC6uAQvWshjfeeEPv1auXnp+fr3fv3l1/8sknTefj8bh+11136W3bttXz8/P1kSNH6hs2bDBds3//fv2yyy7TmzdvrhcUFOhXXXWVXl5ensnHiDxlZWX6Lbfconfs2FFv3LixfsIJJ+h33nmnaekq1bU88+fPZ/bJEydO1HVdXZ2uWbNGHzp0qJ6fn68ff/zx+vTp05WUX9N1Q6g+giAIgiCICEA+KARBEARBRA4SUAiCIAiCiBwkoBAEQRAEETlIQCEIgiAIInKQgEIQBEEQROQgAYUgCIIgiMhBAgpBEARBEJGDBBSCIAiCICIHCSgEQRAEQUQOElAIgiAIgogcJKAQBEEQBBE5SEAhCIIgCCJy/D9TMnhsFcBL2QAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from openmcmc.sampler.sampler import NormalNormal\n", + "\n", + "sampler= [NormalNormal('h', model=mdl)]\n", + "m = MCMC(state, sampler, model=mdl, n_burn=0, n_iter=1000)\n", + "\n", + "m.run_mcmc()\n", + "\n", + "plt.plot(m.store['h'].flatten())\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/2_samplers.ipynb.license b/examples/2_samplers.ipynb.license new file mode 100644 index 0000000..e25c5d4 --- /dev/null +++ b/examples/2_samplers.ipynb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/examples/3_linear_regression.ipynb b/examples/3_linear_regression.ipynb new file mode 100644 index 0000000..06aa06c --- /dev/null +++ b/examples/3_linear_regression.ipynb @@ -0,0 +1,1865 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Bayesian Linear Regression" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this notebook, we consider a more complex example, in which we want to generate samples from the joint distribution of multiple parameters. This is a Bayesian linear regression model, in which the relationship between the matrix $\\mathbf{X}$ of model input variables, model parameters $\\boldsymbol\\beta$ and response vector $\\mathbf{y}$ is as follows:\n", + "$$ \\mathbf{y} = \\mathbf{X}\\boldsymbol\\beta + \\boldsymbol\\epsilon $$\n", + "where $\\boldsymbol\\epsilon$ is a vector of independent, normally distributed errors.\n", + "\n", + "The above specification gives rise to a multivariate Normal likelihood distribtuion:\n", + "$$ \\mathbf{y} \\sim N( \\mathbf{X}\\boldsymbol\\beta, (\\tau \\mathbf{I})^{-1}) $$\n", + "We supplement this with a multivariate Normal prior distribution for the regression parameters $\\boldsymbol\\beta$, and Gamma prior distributions for the measurement error precision $\\tau{}$ and $\\lambda$ the prior precision for $\\boldsymbol\\beta$:\n", + "$$\n", + "\\begin{align*} \n", + "\\beta &\\sim N( \\mu, (\\lambda P_\\lambda)^{-1}) \\\\\n", + "\\tau &\\sim \\Gamma( a_\\tau, b_\\tau) \\\\\n", + "\\lambda &\\sim \\Gamma( a_\\lambda, b_\\lambda) \\\\\n", + "\\end{align*}\n", + "$$\n", + "\n", + "In the remainder of this notebook, we generate a synthetic dataset from the model, for a given set of $\\boldsymbol\\beta$ values, and then use the conjugate sampler functionality within the openmcmc package to sample from the joint posterior distribution of the parameters.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from scipy.stats import norm\n", + "from openmcmc.mcmc import MCMC\n", + "from scipy import sparse\n", + "from openmcmc.sampler.sampler import NormalNormal, NormalGamma\n", + "from openmcmc.model import Model\n", + "from openmcmc.distribution.distribution import Gamma\n", + "from openmcmc.distribution.location_scale import Normal\n", + "from openmcmc.parameter import ScaledMatrix, LinearCombination" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generating some synthetic data\n", + "\n", + "The below cell generates some synthetic data from the underlying model, using particular values for the parameters $\\boldsymbol\\beta$ and $\\tau$. We assume a single input covariate, with an intercept, and add on Gaussian random noise with a given precision value. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "N = 100\n", + "\n", + "true_beta = np.array([2, 0.5])\n", + "x = np.sort(np.random.rand(N))\n", + "X = np.stack([np.ones(N), x], 1)\n", + "true_tau = 100.0\n", + "\n", + "y = X @ true_beta + norm.rvs(loc=0, scale=np.sqrt(1/true_tau), size=N)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell below plots the response as a function of the input variable:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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CEYAAICjCEYAAICjCEYAAICjCEYAAICjCEYAAICjCEYAAICjCEYAAICjCEbgOuFwWMFgUOFw2OmhAABsQDACVwkEAiotLVVlZaVKS0sVCAScHhIAwGJZhmEYTg9iIB0dHSosLFQkEtGQIUOcHg4sEg6HVVpaqp6enuhzOTk5CoVCKi4udnBkAIBExHv/ZmYErtHS0tIrEJGk7u5utba2OjQiAIAdCEbgGuXl5crO7v0jmZOTo7KyModGBACwA8EIXKO4uFj19fXKycmRdCIQef7551miAQCPI2cErhMOh9Xa2qqysjICEQBIY/Hev8+ycUxAXIqLiwlCACCDsEwDAAAcRTACAAAcRTACAAAcRTACAAAcZToY2bx5s6ZPn65Ro0YpKytLr7766oDnPPvss7rooouUn5+vcePG6T//8z8TGSsAAPAg09U0R48e1WWXXabvf//7+od/+IcBj1+6dKnq6uq0bNkyTZgwQdu3b9cdd9yhoUOHavr06QkNGgAAeIfpYOTGG2/UjTfeGPfxv/71r1VTU6NbbrlFknTeeefprbfe0qJFiwhG0kA4HFZLS4vKy8sptwUAWMLynJHOzk4NHjy413P5+fnavn27urq6Yp7T0dHR6wH7sYMuAMAOlgcjVVVVWr58uXbs2CHDMPT2229r+fLl6urq0ieffNLnOX6/X4WFhdFHSUmJ1cPEacLhsO68887oxnU9PT2qqalROBx2eGQAAK+xPBh5+OGHdeONN+qaa67RoEGDdPPNN2v27Nknvnl239++rq5OkUgk+mhra7N6mDgNO+gCAOxieTCSn5+vX/3qVzp27JhCoZAOHDigMWPGqKCgQF/96lf7PCcvL09Dhgzp9YC92EEXAGAX2/qMDBo0SMXFxcrJyVFjY6P+/u//PubMCJzHDroAALuYrqb57LPPek3V79+/X7t27dKwYcP0ta99TXV1dfrwww+jvUTef/99bd++XVdffbU+/fRTLVmyRLt379YLL7yQuncBS1RXV6uqqooddAEAljIdjLz99tu64YYbol8/8MADkqTZs2eroaFBBw8e1IEDB6L/v7u7W0899ZT27t2rQYMG6YYbbtCbb76pMWPGJD96WI4ddAEAVssyDMNwehAD6ejoUGFhoSKRCPkjAACkiXjv3yRtAAAARxGMAAAARxGMAAAARxGMAAAARxGMAAAA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x+kl8QQjP/rRj4yLL76413O33HKLUVVVZcmYPLdMc/z4ce3YsUNTpkyJPpedna0pU6Zo69atfZ6zdevWXsdLUlVVVczjcUIi1/p0x44dU1dXl4YNG2bVMD0h0Wv96KOPqqioSNXV1XYM0xMSuda/+93vNHHiRPl8Pg0fPlzjx4/X448/ru7ubruGnZYSudbXXnutduzYEV3K2bdvn9auXatp06bZMuZMYfd9MS02yjPjk08+UXd3t4YPH97r+eHDh+u9997r85z29vY+j29vb7dsnF6QyLU+3YMPPqhRo0ad8UOP3hK51lu2bFEgENCuXbtsGKF3JHKt9+3bp40bN+q73/2u1q5dq9bWVt1zzz3q6urS/Pnz7Rh2WkrkWt9222365JNP9Hd/93cyDENffvml7rrrLv3kJz+xY8gZI9Z9saOjQ59//rny8/NT+v08NzOC9LFw4UI1NjaqqalJgwcPdno4nnLkyBHNnDlTy5Yt07nnnuv0cDyvp6dHRUVFqq+v1xVXXKFbbrlF8+bN03PPPef00Dxn06ZNevzxx/Uf//Ef+uMf/6hVq1bptdde04IFC5weGpLguZmRc889Vzk5OTp06FCv5w8dOqQRI0b0ec6IESNMHY8TErnWJy1evFgLFy7UH/7wB1166aVWDtMTzF7rP//5zwqFQpo+fXr0uZ6eHknSWWedpb179+r888+3dtBpKpGf65EjR2rQoEHKycmJPnfRRRepvb1dx48fV25urqVjTleJXOuHH35YM2fO1O233y5JuuSSS3T06FHdeeedmjdvnrKz+Rs7FWLdF4cMGZLyWRHJgzMjubm5uuKKK/T6669Hn+vp6dHrr7+uiRMn9nnOxIkTex0vSRs2bIh5PE5I5FpL0hNPPKEFCxZo3bp1uvLKK+0Yatoze60vvPBC/elPf9KuXbuij29961u64YYbtGvXLpWUlNg5/LSSyM/1pEmT1NraGg34JOn999/XyJEjCUT6kci1Pnbs2BkBx8kg0GCrtZSx/b5oSVqswxobG428vDyjoaHB2LNnj3HnnXca55xzjtHe3m4YhmHMnDnT+PGPfxw9/o033jDOOussY/Hixca7775rzJ8/n9LeOJm91gsXLjRyc3ONV155xTh48GD0ceTIEafeQtowe61PRzVN/Mxe6wMHDhgFBQVGbW2tsXfvXmPNmjVGUVGR8dhjjzn1FtKG2Ws9f/58o6CgwPiv//ovY9++fcZ///d/G+eff77xne98x6m3kBaOHDli7Ny509i5c6chyViyZImxc+dO44MPPjAMwzB+/OMfGzNnzowef7K099/+7d+Md99913j22Wcp7U3EM888Y3zta18zcnNzjauuusrYtm1b9P9df/31xuzZs3sd/9vf/ta44IILjNzcXOPiiy82XnvtNZtHnL7MXOvS0lJD0hmP+fPn2z/wNGT25/pUBCPmmL3Wb775pnH11VcbeXl5xnnnnWf8/Oc/N7788kubR52ezFzrrq4u46c//alx/vnnG4MHDzZKSkqMe+65x/j000/tH3gaCQaDff7be/Lazp4927j++uvPOOfrX/+6kZuba5x33nnGihUrLBtflmEwrwUAAJzjuZwRAACQXghGAACAowhGAACAowhGAACAowhGAACAowhGAACAowhGAACAowhGAACAowhGAACAowhGAACAowhGAACAowhGAACAo/4f5+4Dt3l8w5sAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(x, y,'k.')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the model\n", + "\n", + "We now set up a model using building blocks from the openmcmc toolbox. In this instance, we must use `Parameter` objects to transform the raw input values from the state into predictors that can be used in a multivariate Normal distribution." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `LinearCombination` parameter is used to define parameter transformations of the form $\\mathbf{X}\\boldsymbol\\beta$, where $\\mathbf{X}$ is an $n \\times{} p$ matrix and $\\boldsymbol\\beta{}$ is a $p \\times{} 1$ vector. Thus, we use it to define the mean parameter of the response distribution for the regression model." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "mean_form = LinearCombination(form={'beta': 'X'})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `ScaledMatrix` parameter is used to define parameter transformations of the form $\\tau{}\\mathbf{P}$, where $\\tau$ is a scalar parameter, and $\\mathbf{P}$ is a square matrix. This is used to define the precision parameters for the response distribution and the parameter prior distribution, where we assume that each response/parameter is independently Normally distributed with the same precision." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "tau_predictor = ScaledMatrix(matrix='P_tau', scalar='tau')\n", + "lambda_predictor = ScaledMatrix(matrix='P_lambda', scalar='lambda')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using these parameters, we can then define the full model object in terms of ints individual distributions." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "mdl = Model([Normal('y', mean=mean_form, precision=tau_predictor),\n", + " Normal('beta', mean='mu', precision=lambda_predictor),\n", + " Gamma('tau', shape='a_tau', rate='b_tau'),\n", + " Gamma('lambda', shape='a_lambda', rate='b_lambda')], response= {'y': 'mean'})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up and running the MCMC sampler\n", + "\n", + "Because of the choices of conjugate Normal-Normal and Normal-Gamma pairs of distributions, all of the parameters in this model can be sampled using conjugate conditional samplers. The below cell specifies a conjugate sampler for each of $\\boldsymbol\\beta$, $\\tau$ and $\\lambda$. Under this specification, the MCMC sampler will be a Gibbs sampler which iterates through each of these parameters in turn, and samples from its (known) conditional distribution given the current values of the other variables." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "sampler = [NormalNormal('beta', mdl),\n", + " NormalGamma('tau', mdl),\n", + " NormalGamma('lambda', mdl)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell below sets up the initial state for the sampler. Note that we set up $\\mathbf{P}_{\\tau}$ and $\\mathbf{P}_{\\lambda}$ to be sparse matrices: the sampling methods within the package support the use of sparse matrices, and so using them can substantially speed up inference." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "initial_state = {'y': y, 'X': X, 'beta': [0, 0],\n", + " 'P_tau': sparse.csc_matrix(np.eye(N)), 'tau': 1,\n", + " 'P_lambda': sparse.csc_matrix(np.eye(2)), 'mu': [0, 0], 'lambda': 0.01,\n", + " 'a_tau': 1e-3, 'b_tau': 1e-3, 'a_lambda': 1e-3, 'b_lambda': 1e-3}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The MCMC engine can now be set up using the model, samplers and initial state." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2000/2000 [00:01<00:00, 1244.80it/s]\n" + ] + }, + { + "data": { + "text/plain": [ + "{'beta': array([[1.97900425, 1.99602964, 1.97950813, ..., 1.95818562, 2.00012132,\n", + " 2.00471731],\n", + " [0.51252331, 0.51497128, 0.56345231, ..., 0.54778293, 0.51560912,\n", + " 0.49494672]]),\n", + " 'tau': array([[ 86.73426381, 77.41848066, 63.72378612, 91.71589913,\n", + " 95.93886833, 73.48229178, 92.4055814 , 67.63270169,\n", + " 93.49734212, 85.93477649, 80.59978445, 79.22199681,\n", + " 100.16576752, 103.04752358, 70.36262151, 102.04026826,\n", + " 94.99302038, 92.78483829, 98.19723132, 58.84024746,\n", + " 77.08928414, 88.06292532, 69.688374 , 88.43490414,\n", + " 91.98902105, 74.62727023, 95.39177009, 84.17512505,\n", + " 67.39893396, 79.45729287, 81.93312546, 83.00330543,\n", + " 94.98401222, 66.42812495, 95.89922073, 87.58846741,\n", + " 79.39561974, 80.53917661, 114.31917027, 69.76501328,\n", + " 82.11568032, 77.6658376 , 60.69766437, 93.14979873,\n", + " 77.13947267, 85.68472111, 68.25272671, 102.14174677,\n", + " 76.54241822, 74.02699063, 96.66154167, 87.53618339,\n", + " 86.21077126, 79.89316212, 68.90388471, 77.83832473,\n", + " 101.39689161, 81.90876272, 89.53213123, 85.79956585,\n", + " 86.05822573, 103.43466735, 87.05783944, 81.46192481,\n", + " 85.48485735, 65.99812316, 73.49198576, 103.34607749,\n", + " 68.88456652, 88.12199014, 84.58866875, 101.96519576,\n", + " 82.19517807, 80.15252688, 86.93626915, 86.86430395,\n", + " 113.5628305 , 80.25588464, 92.13903324, 84.20858466,\n", + " 76.95382351, 87.28527682, 75.21148647, 65.26047085,\n", + " 102.74331424, 86.0713911 , 81.53816432, 70.53315012,\n", + " 110.48919613, 84.36675069, 98.2919112 , 81.54126966,\n", + " 108.2942728 , 75.89200281, 105.51835107, 95.53891996,\n", + " 79.31242296, 81.85143545, 79.68190187, 81.77211486,\n", + " 71.12919015, 77.37449774, 83.19625234, 85.17423512,\n", + " 91.40722031, 77.12921129, 64.18459309, 85.62251792,\n", + " 79.92037873, 78.38437485, 90.76654733, 88.21350013,\n", + " 71.69466451, 94.86543887, 88.94153901, 87.72966825,\n", + " 76.50162268, 97.60427518, 52.83885292, 97.71707296,\n", + " 82.75391 , 85.4630738 , 86.01188306, 66.67043887,\n", + " 74.00450705, 103.99810432, 100.48713961, 74.35629669,\n", + " 67.85611502, 86.32860518, 90.4017727 , 84.27775619,\n", + " 72.70984446, 68.09544562, 88.4946514 , 67.89304114,\n", + " 86.64783907, 92.93666101, 86.59494735, 85.46080025,\n", + " 90.50269054, 79.6756953 , 99.88229501, 86.35183626,\n", + " 111.41853185, 77.39930263, 90.00209412, 88.10915098,\n", + " 71.85982159, 72.14197424, 103.44259833, 71.44988156,\n", + " 70.49678928, 113.65539574, 92.80133062, 85.55261654,\n", + " 82.16214552, 80.00081992, 58.97658762, 82.35704538,\n", + " 74.324128 , 61.68338678, 83.91560059, 100.07967845,\n", + " 92.18109313, 67.45515741, 118.82342201, 83.80532406,\n", + " 78.94649591, 93.09949303, 84.37499119, 72.96445395,\n", + " 83.32217911, 67.81962152, 104.0972235 , 105.21201946,\n", + " 102.93518148, 84.54558991, 81.2000057 , 71.93825449,\n", + " 87.53037801, 70.28071619, 87.82701626, 66.76121357,\n", + " 75.85214397, 79.72563978, 89.06230625, 70.30563487,\n", + " 69.92676284, 72.50104447, 67.47545855, 102.24225614,\n", + " 82.51031914, 80.76246068, 63.82462128, 99.53671644,\n", + " 103.13033531, 99.85437282, 89.15150171, 72.85865934,\n", + " 81.13495906, 64.82969237, 90.12651049, 98.96715938,\n", + " 77.65088139, 85.73516918, 86.7953082 , 80.44838906,\n", + " 87.10201415, 69.0868037 , 76.30742002, 87.5882752 ,\n", + " 97.79069063, 68.20215495, 76.69292729, 92.60952134,\n", + " 78.80229608, 76.18153902, 74.07998885, 78.86438462,\n", + " 83.51935443, 62.22689422, 79.49029532, 110.91066938,\n", + " 92.40217969, 63.85051158, 78.22162954, 98.68395922,\n", + " 94.1614566 , 77.64592042, 85.37944685, 84.35200008,\n", + " 83.14775121, 67.62608791, 61.8805706 , 79.69668408,\n", + " 78.0095964 , 95.81711556, 84.44321952, 99.78449865,\n", + " 91.50101524, 96.12431906, 71.37142955, 84.19012011,\n", + " 69.91907229, 70.27978842, 72.21465625, 65.63233723,\n", + " 63.1228232 , 80.52611306, 114.92439756, 102.96279575,\n", + " 87.70015752, 94.94873655, 92.55977574, 86.78048165,\n", + " 72.3237708 , 83.01797815, 58.32313666, 77.87513864,\n", + " 81.76332166, 84.13579838, 90.95610445, 86.07611483,\n", + " 70.36544276, 61.65525306, 100.81260485, 82.85478649,\n", + " 62.21820597, 96.34291957, 75.26341098, 80.08259823,\n", + " 76.42295765, 84.29709633, 91.85888068, 79.30612953,\n", + " 90.97185947, 76.78138802, 73.9472241 , 82.36231894,\n", + " 85.93390718, 98.08680856, 71.36406352, 109.82016024,\n", + " 83.06826286, 71.24850164, 74.1683169 , 80.312532 ,\n", + " 70.94115955, 71.6047214 , 84.19362349, 79.49913838,\n", + " 104.26298725, 92.08468085, 91.97733061, 94.30626975,\n", + " 75.8874777 , 89.86624641, 91.62312241, 77.1071822 ,\n", + " 84.3396664 , 93.70429361, 75.59455028, 85.97514291,\n", + " 85.19095279, 82.31419756, 94.42384426, 70.13477116,\n", + " 85.25262867, 93.32232486, 110.08458838, 67.56053458,\n", + " 80.32151898, 84.55594773, 104.9107227 , 69.17503475,\n", + " 77.25883874, 86.19155333, 88.81565537, 80.9260413 ,\n", + " 81.81906754, 92.78661929, 79.13438524, 97.78316646,\n", + " 82.3087807 , 79.0972658 , 102.35180957, 94.15569523,\n", + " 86.09012338, 86.14755208, 86.80716059, 76.41000171,\n", + " 72.29731159, 81.56218871, 73.76736939, 89.16633055,\n", + " 81.62392522, 93.18922878, 96.7406435 , 73.97821971,\n", + " 104.95095783, 91.64869672, 96.35456544, 93.33351095,\n", + " 87.85032576, 89.07853813, 114.17659437, 71.26370045,\n", + " 72.48284742, 73.37387 , 80.99956077, 67.8955508 ,\n", + " 110.52895818, 77.4676701 , 80.19871968, 64.78794093,\n", + " 64.9279128 , 73.01669694, 74.90113877, 78.59021634,\n", + " 106.10461861, 81.71542234, 93.92519599, 106.24602235,\n", + " 95.72226489, 75.30340339, 73.12909608, 87.49439897,\n", + " 84.10300169, 70.22625762, 94.98653338, 107.90853272,\n", + " 99.29512979, 95.45413353, 106.3847441 , 83.86103413,\n", + " 82.3398814 , 92.47848918, 76.94765281, 84.8702799 ,\n", + " 106.76771363, 87.418671 , 67.78419139, 76.99555981,\n", + " 84.98488366, 73.58437975, 78.2530104 , 53.01261778,\n", + " 74.24926722, 80.20143352, 67.93566934, 91.32854668,\n", + " 76.61102039, 77.67955103, 89.24634768, 108.41694618,\n", + " 81.57903202, 74.14575749, 81.02753764, 85.7072819 ,\n", + " 94.98169024, 90.13536179, 76.18790396, 60.22090655,\n", + " 96.55937887, 87.10794922, 76.02887898, 104.94170917,\n", + " 90.44711524, 73.45691188, 72.44457699, 62.59112889,\n", + " 85.0144242 , 59.5355853 , 80.66816404, 100.80797667,\n", + " 69.27389779, 94.11889392, 73.94311893, 87.23654915,\n", + " 105.60716744, 79.01319954, 71.03743622, 72.74894514,\n", + " 93.37959577, 114.13145512, 81.19686658, 84.77858712,\n", + " 72.46391389, 84.63237998, 73.13213946, 68.1634472 ,\n", + " 76.30598291, 122.4448817 , 76.12062829, 75.12677415,\n", + " 95.99518569, 106.17462592, 85.90203155, 82.96235648,\n", + " 81.31079136, 87.77759814, 93.89173711, 68.23592792,\n", + " 61.10073251, 74.20744551, 85.49674219, 85.30908385,\n", + " 93.48689576, 114.85300173, 74.17272526, 63.44036154,\n", + " 62.6996048 , 83.61560332, 78.63347487, 84.62337354,\n", + " 60.40511687, 65.39563453, 79.85661742, 92.13238909,\n", + " 94.35688203, 88.52407074, 95.49363899, 70.17308835,\n", + " 73.42782358, 111.81809789, 96.11018081, 83.4717451 ,\n", + " 91.67721721, 72.23610359, 69.15230332, 96.4900921 ,\n", + " 95.8282054 , 88.28833463, 80.59608148, 79.04441081,\n", + " 80.61883156, 106.03639063, 82.01835706, 84.71660856,\n", + " 81.18646382, 76.16083404, 84.44030143, 90.8238936 ,\n", + " 86.565802 , 95.36381394, 78.64076863, 84.08102282,\n", + " 101.78119995, 75.11009381, 64.75365634, 102.02528262,\n", + " 83.89111969, 76.93405602, 85.80264122, 70.38176068,\n", + " 65.76256197, 86.61408809, 112.88306653, 93.56499443,\n", + " 78.05658379, 84.36613394, 101.75168178, 76.06826141,\n", + " 91.92528847, 73.01103414, 86.95994717, 66.35444135,\n", + " 82.37081993, 72.24440076, 71.3700809 , 98.88979887,\n", + " 63.38558924, 80.31140454, 61.54526948, 70.4443362 ,\n", + " 92.58709449, 66.63199181, 79.90372509, 61.658635 ,\n", + " 102.37056692, 73.2839158 , 91.16587025, 81.50284242,\n", + " 64.62873139, 80.5949988 , 89.7609851 , 65.54976395,\n", + " 84.09803354, 90.9866442 , 86.70165803, 85.65408866,\n", + " 80.66812437, 87.3240553 , 70.74940957, 98.75471523,\n", + " 84.54047829, 75.72553795, 82.78658974, 77.97106783,\n", + " 98.12881593, 99.14109558, 93.9288375 , 65.43181329,\n", + " 75.75162199, 78.70828489, 92.91449397, 109.69854524,\n", + " 78.10447995, 79.25223483, 66.21514771, 81.57354142,\n", + " 79.29391151, 98.64279077, 66.75766175, 83.31647613,\n", + " 78.21366634, 97.60998711, 86.30210816, 78.28168 ,\n", + " 76.63307336, 80.96829021, 66.64759243, 88.02412497,\n", + " 63.83346553, 87.94730633, 74.11863531, 75.2997207 ,\n", + " 98.08604357, 103.94944572, 89.27248878, 70.47044743,\n", + " 83.48330053, 68.27711089, 106.70280822, 93.67383267,\n", + " 69.13845979, 69.98510472, 97.64273507, 67.95006422,\n", + " 97.04649072, 83.99935154, 91.9025034 , 70.55152462,\n", + " 96.27986285, 87.53576521, 96.77903496, 96.26591826,\n", + " 72.60847258, 80.71261039, 139.08500424, 77.92003637,\n", + " 83.51518291, 57.57183584, 67.07896101, 77.25152115,\n", + " 80.45124966, 83.16463457, 79.66186462, 75.32988572,\n", + " 76.47062759, 86.41944255, 110.33069538, 73.74233379,\n", + " 102.79708857, 68.39653575, 96.88186168, 63.48502338,\n", + " 80.79259936, 72.39632102, 83.26757994, 80.35404243,\n", + " 86.96079164, 79.72743337, 72.10762871, 95.29727075,\n", + " 89.74182065, 61.48087663, 83.18866011, 79.64926803,\n", + " 78.05246059, 71.45051085, 82.85467835, 81.19438919,\n", + " 80.0206756 , 71.28726813, 81.20556208, 85.69535769,\n", + " 69.80374824, 82.69238054, 80.17228493, 65.47733913,\n", + " 93.58725102, 75.35474807, 51.95135869, 91.01602356,\n", + " 85.68391762, 74.26796132, 76.25501318, 97.72339863,\n", + " 85.72844652, 91.14476952, 71.10021765, 71.93571381,\n", + " 106.23003851, 97.50869608, 101.31010492, 78.8230843 ,\n", + " 75.18107957, 83.19031551, 99.06614611, 80.42340107,\n", + " 82.0593273 , 88.00399095, 95.60550948, 87.73291923,\n", + " 86.51493657, 103.85275918, 88.6778671 , 87.48534056,\n", + " 79.81741031, 81.99816273, 78.31811682, 67.96187037,\n", + " 94.79490618, 94.04118516, 81.32330636, 76.73897854,\n", + " 89.99318222, 64.89100281, 84.8821067 , 91.31313967,\n", + " 70.30108526, 103.21696275, 96.97725784, 94.4157107 ,\n", + " 79.33289335, 85.11489031, 99.20578288, 83.71302888,\n", + " 79.94743695, 108.40646899, 86.89134778, 76.72948205,\n", + " 108.0113588 , 79.83836028, 85.07714618, 80.21666385,\n", + " 84.5790893 , 85.70289633, 67.10854581, 88.00536715,\n", + " 98.15651104, 87.81049131, 88.9291491 , 76.46040038,\n", + " 108.90754718, 82.57197731, 70.57342032, 73.26997241,\n", + " 66.87561678, 64.84795171, 84.88403268, 73.4445563 ,\n", + " 85.42886512, 67.44469403, 91.65860382, 76.29987764,\n", + " 63.67904944, 78.81727114, 77.74944354, 96.47902626,\n", + " 70.93542674, 66.24861722, 89.07518505, 76.04823367,\n", + " 75.78076143, 82.95942713, 96.56260751, 84.12404777,\n", + " 77.33272303, 63.21963216, 85.70967966, 83.10642267,\n", + " 90.6782025 , 99.38641372, 70.16301917, 77.58440967,\n", + " 68.50361665, 71.31288375, 75.62785609, 85.83728266,\n", + " 82.0078006 , 84.76981115, 93.44023664, 97.04189382,\n", + " 86.67303264, 82.4207315 , 68.82607886, 59.79517109,\n", + " 104.50522832, 83.78113013, 104.19596606, 102.06461848,\n", + " 81.58754249, 90.73250869, 83.21435937, 72.30996413,\n", + " 93.40581514, 79.15677399, 92.50609459, 85.98510937,\n", + " 63.52520968, 83.57955522, 52.36726895, 95.34593707,\n", + " 71.0967419 , 74.83037655, 106.51622895, 76.77199703,\n", + " 97.59738705, 94.98494337, 85.45475357, 92.26294221,\n", + " 91.89627992, 88.47184413, 83.73841206, 101.1186592 ,\n", + " 68.2888375 , 72.40805197, 72.97948924, 71.09895495,\n", + " 88.85878339, 91.80687277, 67.91097781, 77.81118129,\n", + " 71.14216403, 68.67951585, 74.8599286 , 86.61028454,\n", + " 82.29785614, 73.40734303, 77.09009366, 93.41466641,\n", + " 71.7994937 , 95.48159332, 94.51927606, 84.60964837,\n", + " 79.46162174, 91.54409293, 76.31470029, 84.35164691,\n", + " 92.40488151, 117.1774144 , 108.43468629, 69.73793049,\n", + " 99.66177188, 71.8759299 , 68.49309519, 94.7289133 ,\n", + " 86.58836833, 76.09714829, 83.12432093, 68.70219176,\n", + " 80.34941015, 59.48767512, 85.55782091, 70.77212343,\n", + " 95.07860822, 73.75734593, 65.10197352, 89.49290709,\n", + " 90.79417644, 91.39807532, 98.28565623, 65.46821198,\n", + " 94.91637653, 125.52283035, 114.95398944, 73.24853873,\n", + " 79.88653112, 102.24574612, 71.98075993, 84.35696293,\n", + " 98.60307342, 95.31185919, 71.95572127, 69.45588584,\n", + " 63.74847341, 84.13968519, 93.8010402 , 96.22367504,\n", + " 95.3143891 , 114.76671095, 64.21696764, 84.89486367,\n", + " 62.81106115, 70.9587731 , 87.77281959, 84.32458085,\n", + " 99.04550108, 83.70072281, 104.64392072, 90.61576763,\n", + " 90.82515247, 84.27337034, 108.06860475, 82.35812203,\n", + " 60.32649969, 87.21474002, 98.99684225, 115.79844011,\n", + " 67.3383323 , 80.2753484 , 77.94709807, 79.89026765,\n", + " 90.19603131, 100.27026347, 76.21082653, 57.34443085,\n", + " 84.15546826, 76.52911195, 89.04406215, 86.60299351,\n", + " 70.80669965, 80.95072788, 69.58461165, 79.52020667,\n", + " 71.49301845, 80.14709747, 74.76771232, 72.040575 ,\n", + " 80.5358742 , 84.26855985, 78.79948759, 93.30837115,\n", + " 88.50046896, 109.75295527, 80.8043143 , 83.45659872,\n", + " 58.47691729, 67.64196187, 76.78081516, 72.47459661,\n", + " 87.95855898, 88.39815373, 91.02893101, 70.99028741,\n", + " 74.9592676 , 96.12649006, 88.55987614, 75.66815511,\n", + " 85.87418528, 102.12928249, 84.99179989, 81.88236876,\n", + " 107.277764 , 83.51847352, 73.87755458, 92.92322151,\n", + " 63.05511374, 83.81046865, 63.29781712, 73.87907599,\n", + " 97.4772285 , 86.64205131, 70.60486198, 82.37589306,\n", + " 76.86037778, 87.05174398, 70.45321673, 92.31148532,\n", + " 81.01297074, 90.85428679, 93.91894298, 84.40170927,\n", + " 62.12502332, 89.11778781, 70.75657339, 86.38192694,\n", + " 86.45514076, 84.70819186, 81.35983481, 79.51277221,\n", + " 92.27063372, 87.05671392, 70.27455433, 80.06717756,\n", + " 111.31896603, 70.6298008 , 80.81209182, 84.73993443,\n", + " 86.49243212, 93.70537656, 94.93006752, 76.09962411,\n", + " 102.39121135, 119.70504011, 92.59008654, 86.20408413,\n", + " 91.98518494, 68.39771471, 60.49616361, 66.29366343,\n", + " 72.92547474, 79.58235811, 61.50734523, 77.20961502,\n", + " 81.38515512, 74.10364357, 95.78592608, 75.60864633,\n", + " 91.89981238, 91.81092283, 93.35365591, 74.77461467,\n", + " 99.44697926, 89.76586911, 80.06601941, 72.28449128,\n", + " 93.11677209, 93.54248913, 85.65619272, 78.76312085,\n", + " 66.55328059, 75.49884806, 73.46755445, 92.21501711,\n", + " 51.97702344, 100.84779081, 102.61167351, 113.834926 ,\n", + " 93.82855311, 83.16044334, 92.86323941, 67.79419977,\n", + " 83.77069848, 86.09904078, 76.71665395, 114.66993404,\n", + " 89.5639721 , 89.28070557, 116.52313169, 60.74531275,\n", + " 81.3221944 , 84.69300523, 87.98104513, 114.62429386,\n", + " 103.81396191, 79.36363139, 73.88013124, 70.09369068,\n", + " 75.29039911, 75.26825479, 82.05914148, 89.19233613,\n", + " 59.21753445, 73.76518131, 89.32402652, 87.70334133]]),\n", + " 'lambda': array([[2.78835914e-01, 2.46256166e-01, 5.01672943e-01, 4.45743133e-01,\n", + " 2.23581261e-01, 1.21398088e-01, 1.45575579e-01, 2.05019002e-01,\n", + " 1.38210862e-02, 5.78975640e-01, 5.93637297e-02, 3.61109289e-01,\n", + " 5.50519914e-01, 8.96266081e-01, 1.83240339e-02, 1.61135854e-01,\n", + " 1.41458849e-02, 3.13784284e-02, 1.17544396e+00, 3.06644418e-01,\n", + " 6.00984897e-01, 2.22827433e-01, 1.48058696e-02, 2.53659005e-01,\n", + " 6.27216911e-01, 2.18499723e+00, 3.69389382e-03, 3.28632307e-01,\n", + " 1.10745437e+00, 4.13038311e-01, 6.42922584e-01, 1.79732799e-01,\n", + " 1.85714912e-01, 4.16914348e-01, 8.27190998e-01, 2.33523166e-01,\n", + " 2.10713865e-01, 4.02745437e-02, 1.77793171e-02, 8.50512299e-01,\n", + " 2.19011946e-02, 7.70690295e-02, 1.03718973e+00, 1.46809725e+00,\n", + " 2.55172360e-01, 4.13876410e-01, 6.66843559e-01, 3.56892197e-01,\n", + " 1.34012891e+00, 9.90964112e-01, 1.61987869e-01, 7.49242065e-01,\n", + " 1.25446539e+00, 1.37561128e+00, 6.14387394e-02, 2.73776496e-01,\n", + " 8.19546446e-01, 2.97391506e-01, 6.78404741e-02, 8.20891185e-01,\n", + " 1.83818744e+00, 2.09412402e-01, 8.46494921e-01, 4.49437736e-01,\n", + " 1.12631167e+00, 1.16410651e+00, 4.69396105e-01, 8.93805226e-01,\n", + " 2.84327347e-01, 6.14346301e-02, 5.91802984e-01, 2.47738986e-01,\n", + " 3.10632582e-01, 1.81595173e-01, 1.29442912e-01, 1.39048298e-01,\n", + " 2.05617452e-01, 4.12438006e-01, 3.66135538e-02, 2.59769058e-01,\n", + " 1.72355814e-01, 2.97200029e-01, 2.98018554e-01, 1.46870153e+00,\n", + " 2.83954926e-02, 6.59710823e-01, 1.10830421e+00, 7.94229069e-02,\n", + " 2.68455019e-02, 5.39205354e-01, 1.92018578e-01, 9.52130665e-02,\n", + " 1.09936939e+00, 4.12136208e-01, 7.18607979e-01, 1.38247070e-01,\n", + " 3.45710717e-01, 3.30209108e-01, 2.19042996e-01, 6.48070899e-01,\n", + " 1.52718559e-01, 7.29260270e-01, 8.45628110e-01, 1.54685990e-01,\n", + " 1.86084610e-01, 1.74924102e-01, 1.42804474e-01, 7.55746545e-01,\n", + " 1.96988233e+00, 7.25059485e-01, 1.70914951e+00, 1.27728910e-01,\n", + " 3.41064967e-01, 2.37003868e-01, 8.96375048e-01, 1.22698276e+00,\n", + " 6.52847981e-01, 7.56908459e-02, 3.77133688e-01, 2.50524281e-02,\n", + " 1.23211671e+00, 1.07399795e-01, 2.21938240e-01, 2.65020479e-01,\n", + " 1.59781607e+00, 3.31855774e-01, 1.39796978e-01, 1.30866800e+00,\n", + " 1.18686023e+00, 2.08596577e-01, 6.41717726e-01, 1.05771202e-01,\n", + " 5.81385305e-01, 2.83994801e-01, 1.31081545e-01, 3.30222437e-01,\n", + " 1.27169050e-01, 3.44483996e-01, 9.97366585e-01, 5.72314522e-02,\n", + " 1.35074470e-01, 6.63355733e-02, 5.16816485e-01, 1.25399067e+00,\n", + " 5.35725743e-02, 1.90732060e-01, 9.79435402e-01, 4.94218005e-01,\n", + " 1.82799112e+00, 1.14137880e+00, 1.63310462e-01, 1.11421840e-01,\n", + " 1.12349158e+00, 2.40466062e-01, 8.70524473e-02, 2.80911272e-01,\n", + " 2.15473515e-01, 1.79947107e-01, 2.32719490e-02, 6.48983270e-01,\n", + " 9.72740617e-01, 2.26429754e-02, 3.15242982e-01, 3.15512705e-01,\n", + " 2.13286283e+00, 9.88119876e-02, 1.33148043e+00, 4.46314367e-01,\n", + " 1.04630886e-01, 3.44042195e-01, 4.56311056e-02, 8.33275589e-02,\n", + " 5.87232457e-01, 5.43840894e-01, 3.94569207e-01, 1.73474281e-01,\n", + " 3.66947961e-01, 2.04361367e-01, 2.17600947e-01, 6.50099242e-01,\n", + " 1.79640018e-01, 1.74565398e-02, 2.42041354e-01, 8.35560783e-01,\n", + " 1.14480368e+00, 2.35375189e-01, 1.02526422e+00, 4.55833582e-01,\n", + " 7.74534598e-02, 1.14269757e-01, 2.19314060e-01, 6.94057863e-01,\n", + " 2.97820112e-01, 6.14409425e-01, 1.02989205e-02, 6.47252663e-01,\n", + " 8.59052281e-01, 3.08088206e-01, 1.14505702e-01, 1.59367085e-01,\n", + " 2.52251105e+00, 5.91650780e-01, 5.23124182e-02, 5.10238731e-02,\n", + " 4.51017643e-01, 4.29128876e-01, 4.68574896e-02, 7.26810423e-01,\n", + " 1.06271821e+00, 1.30582033e-01, 4.77984000e-02, 7.81750452e-02,\n", + " 1.61335682e-01, 4.47065174e-02, 2.17557175e-01, 5.51666898e-01,\n", + " 6.29233257e-01, 7.30175976e-02, 3.02803640e-01, 3.74866935e-02,\n", + " 1.41841656e+00, 1.73293637e-01, 8.54884771e-01, 1.08619241e+00,\n", + " 1.77960113e-01, 1.32772759e+00, 7.14085916e-02, 5.20713327e-01,\n", + " 4.07701583e-01, 8.51059542e-02, 2.63838818e-01, 2.42229629e-01,\n", + " 4.59173893e-01, 1.66997860e-01, 3.05920644e-01, 3.28187687e-01,\n", + " 2.82297045e-02, 1.71761519e-01, 4.88217102e-01, 1.46990670e-01,\n", + " 1.40032264e-01, 8.44483267e-02, 1.73383954e+00, 3.69591079e-01,\n", + " 2.10511245e-01, 1.13661312e+00, 3.10904822e-02, 1.52580278e+00,\n", + " 2.19952495e-01, 5.21483320e-01, 1.64929730e+00, 1.09293100e-01,\n", + " 4.06903962e-01, 2.37634840e-02, 1.57506808e-01, 1.17684162e+00,\n", + " 1.48258288e+00, 6.37381292e-01, 2.51845537e-01, 7.40382313e-01,\n", + " 3.57167994e-01, 9.43762167e-01, 7.17018268e-01, 4.40333277e-01,\n", + " 3.06435261e-01, 9.12184286e-01, 1.01987723e+00, 5.34225022e-01,\n", + " 1.72698874e-01, 4.22117239e-01, 1.77178397e-01, 6.43558101e-02,\n", + " 1.63014901e-01, 1.88588307e+00, 2.95709224e-01, 1.25167216e+00,\n", + " 1.16021321e-01, 3.53735194e-01, 2.59480844e-01, 3.15527588e-01,\n", + " 6.89885217e-01, 3.46374345e-01, 1.44496260e+00, 4.16523629e-01,\n", + " 6.08998212e-01, 9.45031080e-01, 1.29671690e-01, 6.73471369e-01,\n", + " 2.42463541e-01, 9.10164112e-01, 5.28148883e-01, 8.63845706e-01,\n", + " 2.39446073e+00, 2.45647125e-01, 2.81669108e-01, 3.43171479e-01,\n", + " 1.46919203e+00, 1.09533417e+00, 6.33213419e-01, 1.03640915e+00,\n", + " 7.03070144e-02, 4.74149961e-01, 7.62959855e-02, 1.98817860e-01,\n", + " 6.33659626e-01, 7.17180714e-01, 2.47674864e-01, 5.96131602e-02,\n", + " 1.19869263e-02, 1.37220201e-01, 4.50033267e-01, 5.90158863e-01,\n", + " 2.58648306e-01, 3.57218078e-01, 1.37793796e-02, 7.06159995e-02,\n", + " 3.27483454e-01, 7.37611837e-02, 3.09633951e-01, 5.23202960e-01,\n", + " 3.07251049e-01, 1.62972884e-01, 3.28150980e-01, 2.26610312e-01,\n", + " 2.91873657e-01, 2.81478613e-02, 5.99450038e-01, 1.38812285e-01,\n", + " 4.99138406e-01, 1.71861050e-01, 4.61144472e-01, 3.28190292e-01,\n", + " 7.73113066e-01, 7.86564208e-01, 1.65852917e+00, 2.71515004e-02,\n", + " 5.17591023e-01, 6.25007545e-01, 1.75962062e-01, 3.58983742e-01,\n", + " 1.08719099e-01, 1.78365760e+00, 1.98961252e-01, 9.21160291e-01,\n", + " 8.82852651e-02, 1.72620365e-01, 9.37964561e-02, 1.69863596e-01,\n", + " 1.56069125e+00, 9.84947019e-02, 4.20838265e-01, 3.17835207e-01,\n", + " 8.41679657e-02, 4.00411440e-01, 1.74684729e-01, 1.76636127e-01,\n", + " 5.98730662e-01, 4.20474422e-01, 1.35714193e+00, 1.10582446e+00,\n", + " 7.24310346e-01, 1.06284824e+00, 1.56475774e-01, 7.71711444e-01,\n", + " 4.05553911e-01, 1.01298422e-01, 9.31893682e-01, 2.27233007e-02,\n", + " 4.81125646e-01, 3.19202697e-01, 9.24920061e-01, 3.42164490e-01,\n", + " 1.48210504e+00, 3.65527180e-01, 8.07215919e-01, 2.93569372e-01,\n", + " 4.34871016e-01, 2.48983451e-01, 9.61053744e-02, 2.21720926e-01,\n", + " 1.13612385e-01, 1.90548888e-01, 3.89119928e-02, 8.35603565e-01,\n", + " 2.33407877e-01, 8.64554309e-01, 1.60892059e-01, 2.21058600e-01,\n", + " 2.79364074e-02, 2.10412334e+00, 3.33026170e-01, 3.73433055e-01,\n", + " 3.00373444e-01, 1.27386305e+00, 3.65250699e-01, 6.06158317e-01,\n", + " 1.28517854e+00, 1.21307851e-01, 1.04057179e+00, 3.29811598e-01,\n", + " 1.10812242e+00, 1.15275383e+00, 1.05212520e-02, 7.36217875e-02,\n", + " 9.84616912e-02, 4.62010563e-01, 5.35394747e-01, 6.08623481e-01,\n", + " 1.45205337e-01, 8.41107537e-01, 8.51647780e-01, 4.79096833e-02,\n", + " 1.24627714e+00, 4.90539250e-02, 5.82219622e-01, 3.40592319e-01,\n", + " 4.79239473e-01, 1.10594583e+00, 1.11633751e+00, 4.58371032e-01,\n", + " 1.43643392e-01, 1.78088124e-01, 7.58135732e-01, 8.93478660e-01,\n", + " 1.54067345e+00, 5.53188752e-01, 4.13314084e-01, 1.62226993e-01,\n", + " 1.69657067e-01, 5.55666137e-02, 4.74737123e-02, 4.22278502e-01,\n", + " 3.77732647e-01, 5.51417827e-01, 1.14172968e-01, 9.39790000e-02,\n", + " 1.82621925e-01, 2.68641450e-01, 1.08727658e-01, 5.94860164e-01,\n", + " 2.41129162e-02, 1.56926798e-01, 6.04635105e-01, 9.01149849e-02,\n", + " 4.25756329e-01, 3.60033890e-01, 6.52096789e-01, 6.48516174e-01,\n", + " 1.80095473e-01, 8.27758823e-01, 2.04979275e-01, 5.60198081e-01,\n", + " 6.72430059e-02, 6.97854600e-02, 3.56376492e-01, 3.80831208e-01,\n", + " 7.98725511e-01, 4.23056036e-01, 1.19606734e+00, 1.76206651e-01,\n", + " 2.60617438e-01, 5.11765417e-02, 2.62804072e-01, 5.35368298e-01,\n", + " 1.24468260e-02, 3.18001213e-01, 1.28150789e+00, 2.19087336e-01,\n", + " 2.90008985e-01, 9.98868968e-03, 1.12141124e-01, 4.56459922e-01,\n", + " 2.41795643e-01, 1.29575721e+00, 2.29830396e-01, 7.22912594e-01,\n", + " 2.10272305e-01, 2.90779344e-01, 2.28588634e-01, 1.86345847e-01,\n", + " 1.98243899e-01, 1.07265797e-01, 8.68000282e-01, 4.23599881e-01,\n", + " 4.23176066e-01, 4.28321224e-01, 5.04635592e-01, 9.25273976e-02,\n", + " 7.96949300e-02, 3.28048071e-01, 3.26229755e+00, 5.59487088e-01,\n", + " 1.06670812e+00, 1.57497162e+00, 1.13699773e+00, 1.97328795e-01,\n", + " 3.08014199e-01, 3.11038671e-01, 5.52137180e-01, 1.97927632e-02,\n", + " 9.99686362e-02, 2.17395435e-01, 8.51976346e-02, 1.94585315e-01,\n", + " 4.13039015e-01, 3.47927056e-01, 1.02613687e+00, 1.93952937e-01,\n", + " 1.55684211e-01, 4.07621946e-01, 2.49933917e-02, 3.31898645e-01,\n", + " 6.25739456e-02, 1.59542652e-01, 5.53520623e-01, 8.35113302e-02,\n", + " 1.08958974e+00, 7.56474240e-01, 3.10412281e-01, 4.84156521e-01,\n", + " 1.17970278e+00, 5.40905792e-01, 6.76465004e-02, 1.19918325e-01,\n", + " 6.77907693e-02, 3.49351184e-01, 1.04418300e-01, 6.42770788e-01,\n", + " 3.51061210e-01, 4.43132976e-02, 6.25511075e-01, 4.79006757e-01,\n", + " 2.44851449e-01, 2.56141614e-01, 1.08997986e+00, 6.76422916e-02,\n", + " 2.33724613e-02, 3.53752731e-01, 1.26882615e-01, 1.08782051e+00,\n", + " 4.56599168e-01, 3.16849161e+00, 7.84181209e-01, 3.53548953e-01,\n", + " 6.03870531e-02, 5.12785792e-01, 2.96836942e-01, 2.24604344e-02,\n", + " 1.78263037e-02, 2.26121740e-01, 6.81696785e-02, 1.98073342e-01,\n", + " 9.82669221e-01, 1.96117189e-01, 1.71973690e-01, 1.15523852e-03,\n", + " 7.63322506e-01, 1.89876416e+00, 5.81885260e-01, 1.17588363e-01,\n", + " 2.87735875e-02, 6.23267788e-01, 3.77929473e-01, 2.41840577e-01,\n", + " 5.28161932e-01, 6.73061367e-01, 1.66790494e-01, 1.63976529e+00,\n", + " 1.40160909e+00, 3.37657393e-01, 1.97506944e-01, 3.95894172e-01,\n", + " 2.09036145e-01, 1.38626934e+00, 3.10158489e-01, 1.41648503e-01,\n", + " 3.86605827e-01, 1.07814586e+00, 4.74381762e-02, 3.46145931e-01,\n", + " 1.63608590e-01, 2.81728575e-01, 8.19095024e-01, 8.83284336e-01,\n", + " 2.22779482e-01, 7.18615063e-01, 8.06211599e-01, 1.46510543e+00,\n", + " 5.61655523e-02, 1.32917692e-01, 5.13201533e-01, 2.65401424e-01,\n", + " 6.52932583e-02, 2.95745880e-01, 6.53669786e-02, 1.28860926e-01,\n", + " 6.11475835e-01, 4.14069741e-02, 4.58054817e-01, 5.58007265e-01,\n", + " 1.18993355e-01, 2.81306475e-01, 5.56780210e-02, 5.16309760e-01,\n", + " 2.72478427e-01, 6.74139670e-01, 7.22085955e-02, 4.25865872e-02,\n", + " 5.89628165e-01, 4.96264817e-01, 1.04325527e-01, 4.61582952e-02,\n", + " 2.74853705e-01, 1.29008561e-01, 8.97129780e-01, 2.70007640e-01,\n", + " 2.96381664e-01, 3.19331428e-01, 4.79892881e-01, 7.66882917e-01,\n", + " 2.17417963e+00, 8.72244004e-02, 2.88096908e-01, 2.89254105e-01,\n", + " 2.26341766e-01, 1.39724718e-01, 4.31414036e-01, 7.89869506e-02,\n", + " 1.07514602e-01, 7.12245213e-01, 3.50767517e-01, 6.56085659e-01,\n", + " 3.45249964e-01, 3.95773277e-01, 5.11492803e-03, 3.26501954e-01,\n", + " 7.46782659e-02, 2.15697104e-01, 9.54723603e-01, 2.16056631e-01,\n", + " 7.39386811e-01, 1.19877625e+00, 5.53576217e-01, 1.10163617e-01,\n", + " 3.40977727e-02, 6.60154659e-02, 1.58701980e-01, 2.26506532e+00,\n", + " 4.33832523e-01, 1.88052288e-01, 7.95795835e-01, 2.57478761e-01,\n", + " 1.02483495e-01, 4.39828436e-01, 4.21125336e-02, 3.03161090e-02,\n", + " 6.62183599e-01, 4.73015201e-01, 3.18964740e-01, 1.81247067e-01,\n", + " 1.56458665e-01, 1.76892482e+00, 5.91879451e-02, 2.98359811e-01,\n", + " 3.55846199e-01, 1.01747703e+00, 3.64201424e-01, 1.62619960e+00,\n", + " 4.38646172e-01, 9.30367302e-01, 9.35715923e-02, 1.93830460e-01,\n", + " 8.84470299e-01, 5.43694860e-01, 9.03605787e-02, 4.45158115e-01,\n", + " 5.32336949e-01, 5.40342439e-01, 7.60571817e-01, 1.07732911e+00,\n", + " 4.44717777e-01, 6.51656910e-01, 5.86112468e-01, 3.33583796e-01,\n", + " 5.21833714e-01, 1.52494695e-01, 6.22533569e-01, 3.62899874e-01,\n", + " 1.23670340e+00, 1.40855499e-01, 3.25012859e-01, 5.45207026e-01,\n", + " 3.46943519e-01, 4.26188208e-02, 9.92818898e-02, 2.88304140e-01,\n", + " 9.11369421e-01, 1.34538275e-01, 1.46201209e+00, 2.70690067e-01,\n", + " 6.95256289e-02, 2.89716277e-01, 7.57355878e-03, 5.93113478e-01,\n", + " 4.47055909e-01, 2.52785556e-02, 6.45634401e-01, 1.87019383e-01,\n", + " 1.18634355e-02, 1.34323177e+00, 1.10232757e-01, 8.26306040e-02,\n", + " 2.47200520e-01, 6.26908480e-02, 2.79604314e-01, 6.37123725e-02,\n", + " 5.89544689e-02, 8.17384226e-01, 3.89297363e-01, 5.49879242e-01,\n", + " 2.74649306e-01, 5.50638734e-02, 1.64225312e-01, 1.88309551e-03,\n", + " 2.20263102e-01, 4.32398203e-01, 2.59662661e-02, 7.68910051e-01,\n", + " 4.43535956e-01, 2.46091606e-01, 6.74677631e-01, 2.47153599e-01,\n", + " 9.64030826e-01, 1.79027703e-01, 5.45730932e-02, 7.67469065e-02,\n", + " 3.12202586e-01, 1.08133098e-01, 4.13207572e-01, 3.70428020e-01,\n", + " 1.27218103e-01, 1.83758330e-01, 8.30832812e-02, 1.46367213e-01,\n", + " 3.62863485e-01, 2.24392820e-01, 5.52114000e-01, 4.14808162e-01,\n", + " 3.01986688e+00, 4.24097723e-02, 3.96013314e-01, 1.62033769e-01,\n", + " 8.04971440e-01, 1.60690075e-01, 6.81610604e-01, 4.07255285e-01,\n", + " 2.93859174e-01, 2.13904993e-01, 5.84870739e-01, 1.04559865e+00,\n", + " 4.61291902e-01, 6.57369204e-02, 7.77114850e-03, 2.35317379e-01,\n", + " 1.89076073e-01, 4.84644135e-01, 3.22859022e-01, 5.45656766e-01,\n", + " 9.62468020e-01, 2.81088248e-01, 1.22587104e+00, 1.56378660e-01,\n", + " 2.02219066e-01, 8.58866189e-01, 2.34726762e-02, 9.45400137e-01,\n", + " 2.60390288e-01, 8.09095829e-01, 1.64125369e+00, 2.02202247e-01,\n", + " 7.54981571e-01, 1.05736414e-02, 1.56481930e-01, 1.35692922e+00,\n", + " 3.54101196e-01, 1.94091338e-01, 3.59045380e-01, 1.66912233e-01,\n", + " 1.06394006e-01, 4.24041486e-02, 3.59856306e-01, 1.95534444e-01,\n", + " 6.44563891e-01, 8.41639020e-01, 6.90308299e-02, 3.24897707e-01,\n", + " 1.68661083e+00, 4.63927194e-01, 3.50054622e-01, 8.87344521e-01,\n", + " 8.03977718e-02, 4.42011836e-01, 6.94383146e-01, 7.87733166e-01,\n", + " 3.16143517e-01, 1.11050011e+00, 2.34833807e-02, 1.95558315e-01,\n", + " 9.88552256e-01, 2.99599348e-01, 5.57391646e-01, 3.18373581e-01,\n", + " 5.17919746e-01, 3.33011836e-01, 1.77037180e-01, 1.76862905e-01,\n", + " 6.77272523e-01, 7.28340077e-01, 9.02327661e-01, 4.83539983e-01,\n", + " 5.82488865e-01, 6.56250856e-01, 6.82119987e-02, 4.27381896e-01,\n", + " 5.28874995e-01, 1.33486165e+00, 2.42882468e-02, 1.75712168e-01,\n", + " 1.36960126e+00, 1.97180366e-01, 3.15017874e-01, 2.88494252e-01,\n", + " 3.70036747e-01, 1.40238295e-01, 3.63830722e-01, 1.68732917e-01,\n", + " 1.80298740e-01, 4.09777851e-01, 3.56407765e-01, 5.11848440e-01,\n", + " 1.29067964e-01, 1.42977071e-01, 1.34120211e+00, 1.51252704e-01,\n", + " 1.72260160e-01, 3.41859866e-01, 1.43299924e+00, 4.15884239e-01,\n", + " 6.81355036e-01, 3.02446360e-02, 6.60796389e-01, 5.91567428e-01,\n", + " 2.43038924e-01, 4.56336262e-01, 2.25820621e+00, 5.64590565e-02,\n", + " 3.16828489e-02, 7.63206387e-01, 2.13173024e-02, 1.61479324e+00,\n", + " 6.95426026e-01, 3.62375246e-01, 1.86907302e-02, 1.11640305e+00,\n", + " 8.06569959e-01, 5.75296197e-01, 6.12648185e-02, 5.91314985e-02,\n", + " 6.08374879e-01, 1.36264087e+00, 8.74536745e-03, 1.97239252e-01,\n", + " 4.24148193e-01, 2.28458298e-01, 8.71495864e-01, 4.85058379e-01,\n", + " 2.19961139e-02, 1.99629291e+00, 1.61726210e-01, 9.75204730e-01,\n", + " 1.31199633e-01, 3.61562763e-01, 1.82340006e-01, 6.85972746e-03,\n", + " 4.99576711e-01, 6.27996358e-01, 5.28443625e-02, 3.43875628e+00,\n", + " 1.03897326e+00, 2.26469493e-02, 6.81856570e-02, 4.50782832e-01,\n", + " 1.09790076e+00, 3.36192223e-02, 5.08643728e-01, 1.79274030e-01,\n", + " 4.75627812e+00, 3.29706895e-01, 2.59898235e-01, 4.48844634e-01,\n", + " 2.28122856e-01, 1.26592848e+00, 7.45143049e-02, 9.34046280e-01,\n", + " 4.88301766e-02, 1.31823773e-01, 1.91116937e-01, 2.25821853e-01,\n", + " 9.98044944e-01, 3.14540234e-01, 1.11951510e-01, 6.81562179e-01,\n", + " 1.73176251e-01, 5.78391832e-02, 3.61974886e-02, 3.80295728e-01,\n", + " 1.44503453e-02, 1.56068163e+00, 2.06053240e-01, 2.31418235e-01,\n", + " 1.93243890e-03, 3.87311002e-01, 3.70035366e-01, 1.96298972e-02,\n", + " 1.12847806e-01, 8.28898538e-01, 3.68010710e-02, 9.15672448e-01,\n", + " 1.55343634e-01, 1.33089985e-01, 6.03728571e-02, 4.17881007e-03,\n", + " 4.90828542e-02, 1.46535887e+00, 1.17980871e+00, 3.23613881e-01,\n", + " 2.41787739e-01, 8.24116300e-02, 1.56218996e-01, 7.62106432e-01,\n", + " 1.68997354e-01, 1.33557087e-01, 1.27331079e-01, 1.97327473e-01,\n", + " 2.30467115e-01, 4.29268818e-01, 1.65928036e-01, 3.77858938e-01,\n", + " 3.90580745e-02, 1.18741243e+00, 9.43797395e-01, 2.02878584e-01,\n", + " 5.06299539e-01, 1.11224775e+00, 1.25085427e-01, 4.60037879e-01,\n", + " 5.87086794e-01, 6.29695248e-01, 2.08651895e-01, 4.89251984e-01,\n", + " 2.05616323e-02, 4.21630067e-01, 1.40397069e+00, 1.70204732e-01,\n", + " 5.33380130e-01, 2.13819799e-01, 1.08961540e+00, 9.31135887e-01,\n", + " 5.78484755e-01, 3.04192599e-01, 1.12385866e+00, 3.08950730e-01,\n", + " 4.91902963e-01, 4.73152147e-01, 2.07294929e+00, 3.87634787e-01,\n", + " 1.91928447e+00, 1.97394194e+00, 3.91179108e-01, 3.12224872e-01,\n", + " 2.21402874e-01, 1.32887255e+00, 6.01986602e-03, 2.40447433e-01,\n", + " 6.04210421e-01, 7.29802266e-01, 5.23314072e-02, 3.51312731e-01,\n", + " 1.89097075e-01, 4.54840199e-01, 1.65383242e-02, 3.04522633e-01,\n", + " 2.12009200e-01, 1.40119415e+00, 4.99497480e-01, 1.03772896e-01,\n", + " 9.38459079e-01, 3.86900722e-01, 3.68186535e-01, 4.21901744e-01]]),\n", + " 'y': array([[1.981316 , 1.99835243, 1.9820496 , ..., 1.96065641, 2.00244699,\n", + " 2.00694978],\n", + " [1.98808598, 2.00515474, 1.9894923 , ..., 1.96789213, 2.00925772,\n", + " 2.01348758],\n", + " [2.00032157, 2.01744877, 2.00294373, ..., 1.98096948, 2.02156698,\n", + " 2.02530356],\n", + " ...,\n", + " [2.48631383, 2.50576229, 2.53722863, ..., 2.50039614, 2.51048532,\n", + " 2.4946291 ],\n", + " [2.48764191, 2.50709671, 2.53868868, ..., 2.50181559, 2.5118214 ,\n", + " 2.49591164],\n", + " [2.48994121, 2.509407 , 2.54121646, ..., 2.50427307, 2.51413454,\n", + " 2.49813209]]),\n", + " 'log_post': array([[58.30365723],\n", + " [59.19627786],\n", + " [55.81211612],\n", + " [57.07810936],\n", + " [57.88872628],\n", + " [58.95379523],\n", + " [59.24653032],\n", + " [58.36900761],\n", + " [56.71731996],\n", + " [58.42277785],\n", + " [59.45777343],\n", + " [58.00232978],\n", + " [57.56967413],\n", + " [56.063947 ],\n", + " [58.774979 ],\n", + " [57.27057687],\n", + " [58.80972568],\n", + " [58.72360522],\n", + " [56.43061408],\n", + " [56.17782435],\n", + " [58.42524195],\n", + " [56.8916261 ],\n", + " [58.97957817],\n", + " [58.90592072],\n", + " [58.36816547],\n", + " [53.01798196],\n", + " [56.91233869],\n", + " [59.26516054],\n", + " [55.55307104],\n", + " [58.70329789],\n", + " [58.26503748],\n", + " [57.59730775],\n", + " [58.47859174],\n", + " [56.52054395],\n", + " [57.11629607],\n", + " [59.16578031],\n", + " [58.70241209],\n", + " [59.3294222 ],\n", + " [56.98721963],\n", + " [56.86195154],\n", + " [59.22905907],\n", + " [58.54082005],\n", + " [54.01153264],\n", + " [54.89940057],\n", + " [55.51253333],\n", + " [58.7790389 ],\n", + " [56.98249008],\n", + " [57.76113973],\n", + " [55.90478187],\n", + " [57.26012901],\n", + " [57.80836734],\n", + " [58.24373955],\n", + " [55.38199227],\n", + " [56.4207928 ],\n", + " [59.01885377],\n", + " [58.48241287],\n", + " [56.6738913 ],\n", + " [59.28462817],\n", + " [58.91137985],\n", + " [56.53315986],\n", + " [55.49797433],\n", + " [57.13226206],\n", + " [57.86093327],\n", + " [58.03827084],\n", + " [57.15800939],\n", + " [55.75661968],\n", + " [57.99559814],\n", + " [56.3685008 ],\n", + " [58.50223612],\n", + " [59.19847581],\n", + " [58.65093902],\n", + " [58.20030121],\n", + " [59.00877997],\n", + " [58.00018284],\n", + " [59.20507614],\n", + " [59.33304759],\n", + " [56.78014753],\n", + " [58.82767691],\n", + " [59.61205483],\n", + " [59.30229776],\n", + " [58.39175331],\n", + " [59.1596681 ],\n", + " [57.4964483 ],\n", + " [55.30700418],\n", + " [57.50396218],\n", + " [58.0627054 ],\n", + " [57.57357703],\n", + " [58.70859274],\n", + " [57.48470498],\n", + " [58.42988931],\n", + " [57.76070903],\n", + " [59.35518451],\n", + " [51.90889868],\n", + " [58.86872458],\n", + " [56.04588482],\n", + " [58.3543717 ],\n", + " [59.15641009],\n", + " [58.6518868 ],\n", + " [56.59901215],\n", + " [58.35563509],\n", + " [57.64345635],\n", + " [57.33534451],\n", + " [57.48586407],\n", + " [58.22850799],\n", + " [57.93477297],\n", + " [58.72738768],\n", + " [56.64947089],\n", + " [57.40045522],\n", + " [55.49023637],\n", + " [57.9815587 ],\n", + " [56.02330542],\n", + " [56.82854881],\n", + " [56.35052583],\n", + " [57.37669711],\n", + " [55.20029055],\n", + " [56.56843268],\n", + " [55.82314817],\n", + " [58.55754383],\n", + " [54.08514418],\n", + " [55.83921632],\n", + " [56.8141975 ],\n", + " [59.32717586],\n", + " [56.2759231 ],\n", + " [58.2437699 ],\n", + " [56.20783788],\n", + " [57.70795859],\n", + " [57.42854331],\n", + " [54.70807455],\n", + " [54.11851569],\n", + " [58.3856262 ],\n", + " [57.93309249],\n", + " [56.97870347],\n", + " [57.55923147],\n", + " [57.17180743],\n", + " [58.45439416],\n", + " [57.23433133],\n", + " [59.09421723],\n", + " [55.03055664],\n", + " [57.35477262],\n", + " [58.24583535],\n", + " [59.0054448 ],\n", + " [59.63936164],\n", + " [57.47301282],\n", + " [56.48810022],\n", + " [56.50626882],\n", + " [58.30064008],\n", + " [55.98467332],\n", + " [57.61714574],\n", + " [55.0712137 ],\n", + " [54.86641481],\n", + " [53.96329086],\n", + " [58.91177428],\n", + " [55.89352494],\n", + " [56.10044488],\n", + " [59.10876586],\n", + " [58.41305521],\n", + " [59.18647224],\n", + " [57.61309106],\n", + " [56.38822653],\n", + " [56.59379721],\n", + " [54.92841091],\n", + " [57.4297551 ],\n", + " [58.70578861],\n", + " [58.18284096],\n", + " [54.67867586],\n", + " [58.50213082],\n", + " [53.46554346],\n", + " [58.10129226],\n", + " [59.42986226],\n", + " [58.78144919],\n", + " [56.61058637],\n", + " [59.00670673],\n", + " [58.2853369 ],\n", + " [57.29084271],\n", + " [55.34762967],\n", + " [58.02381591],\n", + " [57.11526797],\n", + " [57.18545893],\n", + " [58.66471298],\n", + " [56.51163615],\n", + " [59.23224932],\n", + " [59.19707196],\n", + " [58.28873004],\n", + " [56.57548617],\n", + " [55.69096532],\n", + " [58.40781531],\n", + " [57.38622535],\n", + " [57.25437859],\n", + " [55.19971405],\n", + " [59.1427383 ],\n", + " [58.26168087],\n", + " [55.93542854],\n", + " [59.12862729],\n", + " [56.77483474],\n", + " [58.32802296],\n", + " [55.69232594],\n", + " [56.45131446],\n", + " [57.74449396],\n", + " [59.53483091],\n", + " [59.02358998],\n", + " [53.38151638],\n", + " [56.70301363],\n", + " [57.59807386],\n", + " [58.1589081 ],\n", + " [58.89608605],\n", + " [58.27047903],\n", + " [59.60126832],\n", + " [56.23775656],\n", + " [55.19429656],\n", + " [57.00826332],\n", + " [59.64929878],\n", + " [58.73218837],\n", + " [57.00525879],\n", + " [58.45685606],\n", + " [57.44115848],\n", + " [58.41308976],\n", + " [57.99053209],\n", + " [59.58503284],\n", + " [54.34216943],\n", + " [59.42198194],\n", + " [56.1668159 ],\n", + " [57.31454283],\n", + " [57.82821032],\n", + " [51.55893485],\n", + " [57.7619148 ],\n", + " [54.11755434],\n", + " [58.79171784],\n", + " [56.03652367],\n", + " [55.09214882],\n", + " [59.23606677],\n", + " [59.34203756],\n", + " [59.43487387],\n", + " [58.78169003],\n", + " [58.58799922],\n", + " [54.47503985],\n", + " [59.13313776],\n", + " [58.78303448],\n", + " [58.52425527],\n", + " [57.21774812],\n", + " [55.19247782],\n", + " [56.07656439],\n", + " [58.95061429],\n", + " [53.29785892],\n", + " [58.70814082],\n", + " [58.32510846],\n", + " [56.26721618],\n", + " [58.86729286],\n", + " [53.4407412 ],\n", + " [57.37005942],\n", + " [57.54184136],\n", + " [53.52634951],\n", + " [58.01473147],\n", + " [57.31930718],\n", + " [59.21246341],\n", + " [56.93667048],\n", + " [56.33887537],\n", + " [56.13430184],\n", + " [58.48404137],\n", + " [56.47882349],\n", + " [56.44596251],\n", + " [58.10118124],\n", + " [57.26727336],\n", + " [58.11637873],\n", + " [58.89262712],\n", + " [58.16571086],\n", + " [55.49348839],\n", + " [56.10697836],\n", + " [58.80888642],\n", + " [57.52044116],\n", + " [57.63821109],\n", + " [58.76184264],\n", + " [57.83944356],\n", + " [59.03304338],\n", + " [52.83115956],\n", + " [58.89830944],\n", + " [56.69984523],\n", + " [59.41866834],\n", + " [58.73996208],\n", + " [58.52949828],\n", + " [58.89199368],\n", + " [57.80866544],\n", + " [58.21412621],\n", + " [56.3609811 ],\n", + " [56.82846522],\n", + " [58.02414937],\n", + " [55.94133744],\n", + " [59.11673766],\n", + " [56.66479973],\n", + " [58.7974512 ],\n", + " [56.39952238],\n", + " [58.33055899],\n", + " [56.67690813],\n", + " [52.53680088],\n", + " [58.38943597],\n", + " [56.79676564],\n", + " [58.79821597],\n", + " [55.96120126],\n", + " [56.58963633],\n", + " [57.92777906],\n", + " [57.395514 ],\n", + " [54.32594361],\n", + " [57.21048391],\n", + " [58.92414308],\n", + " [59.51376832],\n", + " [58.59691966],\n", + " [58.02625034],\n", + " [58.49133298],\n", + " [58.73558216],\n", + " [58.35270524],\n", + " [59.11975108],\n", + " [55.99108751],\n", + " [57.25327316],\n", + " [59.30552509],\n", + " [56.20240613],\n", + " [57.45482375],\n", + " [58.55271591],\n", + " [56.96973861],\n", + " [59.58696066],\n", + " [58.53972353],\n", + " [58.30429677],\n", + " [58.99671006],\n", + " [59.29424982],\n", + " [55.60685607],\n", + " [58.61588268],\n", + " [58.10052162],\n", + " [59.40756757],\n", + " [55.04428484],\n", + " [57.81926473],\n", + " [57.90082005],\n", + " [56.4357217 ],\n", + " [58.74456181],\n", + " [58.6925741 ],\n", + " [56.45460173],\n", + " [56.04661702],\n", + " [54.83622598],\n", + " [58.48036207],\n", + " [57.16650491],\n", + " [57.85443138],\n", + " [58.68170278],\n", + " [58.60574822],\n", + " [57.12889716],\n", + " [55.79378352],\n", + " [58.93783799],\n", + " [57.32111888],\n", + " [59.47032148],\n", + " [58.5918485 ],\n", + " [55.90636822],\n", + " [58.51741329],\n", + " [55.9027773 ],\n", + " [58.69268321],\n", + " [56.75456905],\n", + " [57.17711063],\n", + " [56.95797752],\n", + " [57.92771084],\n", + " [59.37663669],\n", + " [56.98396559],\n", + " [56.74154359],\n", + " [58.35689001],\n", + " [56.66434432],\n", + " [57.02557848],\n", + " [56.09617622],\n", + " [56.58073677],\n", + " [58.27623028],\n", + " [55.66756064],\n", + " [57.96309522],\n", + " [59.15730461],\n", + " [55.37907188],\n", + " [59.71063674],\n", + " [58.86172911],\n", + " [57.61708237],\n", + " [56.33693553],\n", + " [56.76811804],\n", + " [55.14025598],\n", + " [57.90514453],\n", + " [56.28699093],\n", + " [58.97167716],\n", + " [58.6843882 ],\n", + " [58.34447188],\n", + " [57.32358661],\n", + " [59.38783045],\n", + " [57.8530579 ],\n", + " [58.52722697],\n", + " [57.90608282],\n", + " [57.56366509],\n", + " [58.22460312],\n", + " [55.7094628 ],\n", + " [58.52602566],\n", + " [52.85431941],\n", + " [59.07493951],\n", + " [52.46086778],\n", + " [57.61586656],\n", + " [58.56076232],\n", + " [59.07337747],\n", + " [55.51972628],\n", + " [57.54619216],\n", + " [56.67614446],\n", + " [56.84639239],\n", + " [58.75999031],\n", + " [56.60900298],\n", + " [59.08757981],\n", + " [56.82504936],\n", + " [56.70644669],\n", + " [59.22082016],\n", + " [55.16823912],\n", + " [59.14732641],\n", + " [58.03781719],\n", + " [58.5982274 ],\n", + " [56.53327822],\n", + " [57.711941 ],\n", + " [57.26189018],\n", + " [57.54345699],\n", + " [57.06834212],\n", + " [56.57314788],\n", + " [57.17095711],\n", + " [52.42387367],\n", + " [56.37542847],\n", + " [58.0537406 ],\n", + " [56.69587117],\n", + " [57.25249857],\n", + " [58.81129001],\n", + " [58.07304482],\n", + " [59.33950801],\n", + " [55.3394045 ],\n", + " [54.93369325],\n", + " [55.943732 ],\n", + " [55.6714369 ],\n", + " [58.88122067],\n", + " [55.63708395],\n", + " [59.12017065],\n", + " [59.48062718],\n", + " [57.68307545],\n", + " [55.62983698],\n", + " [55.20590149],\n", + " [54.40823158],\n", + " [59.02828338],\n", + " [57.95438127],\n", + " [58.85086036],\n", + " [57.20457613],\n", + " [58.72916294],\n", + " [58.01331086],\n", + " [58.08649966],\n", + " [56.87094755],\n", + " [56.84715191],\n", + " [57.79895113],\n", + " [54.53177016],\n", + " [57.12032891],\n", + " [55.24326902],\n", + " [56.45377932],\n", + " [57.93187838],\n", + " [52.53284884],\n", + " [59.0408642 ],\n", + " [56.17868246],\n", + " [57.69985495],\n", + " [58.8685212 ],\n", + " [58.34487113],\n", + " [58.86555815],\n", + " [54.97050767],\n", + " [55.11823017],\n", + " [56.696864 ],\n", + " [59.29540541],\n", + " [58.54352688],\n", + " [59.46638349],\n", + " [58.66883851],\n", + " [55.97811345],\n", + " [58.0065487 ],\n", + " [56.811515 ],\n", + " [56.51883717],\n", + " [58.99362555],\n", + " [57.95832754],\n", + " [57.57286497],\n", + " [56.5980077 ],\n", + " [58.32934813],\n", + " [58.24467715],\n", + " [56.98449594],\n", + " [58.9917663 ],\n", + " [55.76612808],\n", + " [59.13462808],\n", + " [57.57752337],\n", + " [58.96774424],\n", + " [59.4131386 ],\n", + " [59.18973344],\n", + " [59.36929671],\n", + " [57.23883533],\n", + " [58.36670511],\n", + " [58.50511424],\n", + " [58.36217682],\n", + " [58.68756587],\n", + " [57.3693659 ],\n", + " [56.21630648],\n", + " [57.4661615 ],\n", + " [50.56566254],\n", + " [53.21414115],\n", + " [57.16634099],\n", + " [55.88279545],\n", + " [55.45089248],\n", + " [57.80037514],\n", + " [57.53743188],\n", + " [59.02261012],\n", + " [54.84568363],\n", + " [59.30557288],\n", + " [59.62818175],\n", + " [59.41334881],\n", + " [58.46787017],\n", + " [57.84454132],\n", + " [58.5944707 ],\n", + " [54.58945764],\n", + " [57.14779323],\n", + " [58.33351873],\n", + " [58.57669677],\n", + " [58.48634593],\n", + " [59.20876895],\n", + " [58.03985922],\n", + " [57.6252171 ],\n", + " [58.93033058],\n", + " [56.54211096],\n", + " [58.86989942],\n", + " [53.88227347],\n", + " [55.91740236],\n", + " [57.38592762],\n", + " [54.61086352],\n", + " [54.63168665],\n", + " [57.34476189],\n", + " [59.28070459],\n", + " [57.14261998],\n", + " [57.46291924],\n", + " [58.17469366],\n", + " [57.33894007],\n", + " [56.83819078],\n", + " [59.15521136],\n", + " [59.65889242],\n", + " [58.58075261],\n", + " [56.84356561],\n", + " [57.69229793],\n", + " [59.27672378],\n", + " [54.87217241],\n", + " [57.65278975],\n", + " [59.21952145],\n", + " [58.13159516],\n", + " [58.48449289],\n", + " [56.77150098],\n", + " [57.90067112],\n", + " [51.77752834],\n", + " [56.01362232],\n", + " [57.59949369],\n", + " [58.41481327],\n", + " [58.73697641],\n", + " [58.79659744],\n", + " [57.21037203],\n", + " [58.95887222],\n", + " [58.93027584],\n", + " [56.31096818],\n", + " [59.10011818],\n", + " [56.04713662],\n", + " [57.99935928],\n", + " [57.93863638],\n", + " [58.47911003],\n", + " [55.33121209],\n", + " [53.64269593],\n", + " [58.05757507],\n", + " [59.45555797],\n", + " [58.44090876],\n", + " [58.51938239],\n", + " [57.8699574 ],\n", + " [59.11990469],\n", + " [57.00466617],\n", + " [57.55903611],\n", + " [59.0473277 ],\n", + " [56.21057347],\n", + " [55.62386582],\n", + " [57.20912163],\n", + " [59.06685424],\n", + " [57.20602615],\n", + " [58.98493977],\n", + " [55.81226146],\n", + " [57.46278588],\n", + " [58.31899842],\n", + " [57.98480099],\n", + " [56.57129485],\n", + " [58.89565566],\n", + " [56.66787083],\n", + " [58.6511087 ],\n", + " [57.82135437],\n", + " [55.95213919],\n", + " [56.59970809],\n", + " [58.60119763],\n", + " [57.42128777],\n", + " [55.53116342],\n", + " [56.22860745],\n", + " [57.50022061],\n", + " [59.26202933],\n", + " [49.76884993],\n", + " [58.88047988],\n", + " [59.69878922],\n", + " [55.80021198],\n", + " [58.33966062],\n", + " [59.54535463],\n", + " [55.18512399],\n", + " [59.42115164],\n", + " [58.55212748],\n", + " [57.35236 ],\n", + " [56.93653585],\n", + " [58.78099077],\n", + " [57.08204251],\n", + " [57.83542102],\n", + " [58.03202431],\n", + " [53.9815983 ],\n", + " [57.73011348],\n", + " [58.18284153],\n", + " [54.79513626],\n", + " [57.91181634],\n", + " [59.39614365],\n", + " [57.16689922],\n", + " [58.13846857],\n", + " [58.23183137],\n", + " [57.40443264],\n", + " [58.76811055],\n", + " [58.42133136],\n", + " [57.18786531],\n", + " [57.76767468],\n", + " [57.64490779],\n", + " [55.16387014],\n", + " [47.54229407],\n", + " [57.71443365],\n", + " [57.78098997],\n", + " [59.0185098 ],\n", + " [59.10232903],\n", + " [58.53501119],\n", + " [58.18019426],\n", + " [57.69406675],\n", + " [57.25742809],\n", + " [58.85197257],\n", + " [55.18575817],\n", + " [58.47161128],\n", + " [57.77787376],\n", + " [53.8095351 ],\n", + " [58.47420005],\n", + " [57.33402438],\n", + " [58.53384837],\n", + " [57.18051875],\n", + " [57.92870475],\n", + " [58.21058061],\n", + " [57.16777094],\n", + " [58.0944069 ],\n", + " [57.11804954],\n", + " [56.23497778],\n", + " [57.65355435],\n", + " [57.56555953],\n", + " [54.20479352],\n", + " [57.01791425],\n", + " [56.61671397],\n", + " [56.80998833],\n", + " [59.18068424],\n", + " [58.35738799],\n", + " [58.81286343],\n", + " [58.06280517],\n", + " [59.50293498],\n", + " [57.74117567],\n", + " [57.05603622],\n", + " [58.95192513],\n", + " [58.6856246 ],\n", + " [58.71268358],\n", + " [52.0586066 ],\n", + " [58.6761496 ],\n", + " [58.38709633],\n", + " [55.91818596],\n", + " [55.19770921],\n", + " [58.79782994],\n", + " [55.27735558],\n", + " [57.94466326],\n", + " [56.59125243],\n", + " [59.52332778],\n", + " [58.3542008 ],\n", + " [57.34672441],\n", + " [57.08227658],\n", + " [57.72685256],\n", + " [58.55869293],\n", + " [58.39482161],\n", + " [57.00894848],\n", + " [56.13855558],\n", + " [57.12658342],\n", + " [57.96707928],\n", + " [53.32503598],\n", + " [57.82317662],\n", + " [57.23550895],\n", + " [54.56271155],\n", + " [59.4774382 ],\n", + " [58.5843695 ],\n", + " [58.83943351],\n", + " [56.48905388],\n", + " [58.36464951],\n", + " [58.09393472],\n", + " [57.68634475],\n", + " [57.10236999],\n", + " [59.10788023],\n", + " [59.52284377],\n", + " [58.40708578],\n", + " [55.30418406],\n", + " [58.61389835],\n", + " [55.30596196],\n", + " [57.94293482],\n", + " [58.05213902],\n", + " [56.12162661],\n", + " [59.6889612 ],\n", + " [58.28319314],\n", + " [58.91972736],\n", + " [58.39778079],\n", + " [57.31101817],\n", + " [58.8369334 ],\n", + " [57.56346226],\n", + " [55.72322283],\n", + " [59.08060976],\n", + " [58.53999704],\n", + " [58.41504387],\n", + " [57.23535114],\n", + " [57.86545026],\n", + " [58.88117272],\n", + " [59.2183737 ],\n", + " [58.20450697],\n", + " [58.14546521],\n", + " [57.16153655],\n", + " [58.81641487],\n", + " [58.09972689],\n", + " [59.14340846],\n", + " [59.84617273],\n", + " [57.8462704 ],\n", + " [57.99512845],\n", + " [58.95481253],\n", + " [56.18584586],\n", + " [56.65491727],\n", + " [57.80766982],\n", + " [56.72863556],\n", + " [57.70137711],\n", + " [57.83780191],\n", + " [58.11664708],\n", + " [58.86034428],\n", + " [58.94314728],\n", + " [58.57895525],\n", + " [58.19977646],\n", + " [57.64852504],\n", + " [55.94462104],\n", + " [57.74701958],\n", + " [58.61912458],\n", + " [56.96556529],\n", + " [57.93211027],\n", + " [58.65009713],\n", + " [56.83980676],\n", + " [58.6451949 ],\n", + " [58.37118849],\n", + " [52.70215857],\n", + " [59.67418097],\n", + " [58.79301291],\n", + " [56.65381569],\n", + " [56.61613118],\n", + " [59.42272997],\n", + " [53.61538884],\n", + " [55.72633129],\n", + " [57.50964626],\n", + " [58.7096299 ],\n", + " [56.04809885],\n", + " [57.17180438],\n", + " [56.87755991],\n", + " [59.29199888],\n", + " [59.78118538],\n", + " [57.52419201],\n", + " [58.33195259],\n", + " [58.04025106],\n", + " [58.3714922 ],\n", + " [55.96635605],\n", + " [56.4404043 ],\n", + " [57.0435925 ],\n", + " [56.77355169],\n", + " [58.91211374],\n", + " [57.43426987],\n", + " [57.72326681],\n", + " [58.97099697],\n", + " [57.15997807],\n", + " [58.27373748],\n", + " [57.0726035 ],\n", + " [54.15480853],\n", + " [57.86195353],\n", + " [58.13547062],\n", + " [58.23713324],\n", + " [59.27879923],\n", + " [56.36212505],\n", + " [58.56863903],\n", + " [59.00117551],\n", + " [57.80156723],\n", + " [59.23816625],\n", + " [59.05295117],\n", + " [58.66425751],\n", + " [58.4980978 ],\n", + " [58.00145662],\n", + " [58.21362064],\n", + " [54.28284525],\n", + " [57.86854933],\n", + " [58.05730423],\n", + " [54.83471864],\n", + " [58.10226316],\n", + " [57.23870705],\n", + " [57.53494453],\n", + " [59.06856497],\n", + " [57.25236856],\n", + " [57.89903931],\n", + " [57.3208863 ],\n", + " [56.562613 ],\n", + " [54.5636918 ],\n", + " [59.62721267],\n", + " [57.46298796],\n", + " [56.47920321],\n", + " [57.75072378],\n", + " [57.27380705],\n", + " [58.45030876],\n", + " [57.75643457],\n", + " [58.65223504],\n", + " [58.62311659],\n", + " [58.05336347],\n", + " [56.75051195],\n", + " [52.8618556 ],\n", + " [55.0701639 ],\n", + " [57.39718905],\n", + " [56.79786943],\n", + " [57.09702479],\n", + " [58.25552796],\n", + " [56.65469413],\n", + " [57.36857896],\n", + " [55.4338809 ],\n", + " [58.83149333],\n", + " [58.58977249],\n", + " [53.29655947],\n", + " [59.31568508],\n", + " [58.2797191 ],\n", + " [58.72421536],\n", + " [58.41222986],\n", + " [55.31913792],\n", + " [57.49276664],\n", + " [59.35968429],\n", + " [56.8050908 ],\n", + " [57.57932441],\n", + " [58.587638 ],\n", + " [58.49420222],\n", + " [58.53758762],\n", + " [58.55272689],\n", + " [55.07082825],\n", + " [58.99791079],\n", + " [57.47989068],\n", + " [57.37374391],\n", + " [52.88173853],\n", + " [58.61888329],\n", + " [56.14776504],\n", + " [58.6222748 ],\n", + " [57.45497638],\n", + " [53.79564453],\n", + " [58.08903652],\n", + " [58.46126254],\n", + " [53.44545821],\n", + " [56.65304272],\n", + " [56.65566045],\n", + " [57.02127304],\n", + " [58.8583538 ],\n", + " [53.56117973],\n", + " [56.59321453],\n", + " [57.67116113],\n", + " [59.54868665],\n", + " [57.47120995],\n", + " [55.36625987],\n", + " [57.93057114],\n", + " [58.16705163],\n", + " [57.95420981],\n", + " [55.78681 ],\n", + " [55.32938979],\n", + " [59.6279587 ],\n", + " [57.6595165 ],\n", + " [57.96947207],\n", + " [56.89650369],\n", + " [54.55354998],\n", + " [57.50180769],\n", + " [57.62397982],\n", + " [52.78396424],\n", + " [59.55620145],\n", + " [57.3484232 ],\n", + " [56.43861942],\n", + " [58.04799378],\n", + " [55.12024112],\n", + " [56.02394614],\n", + " [58.50956914],\n", + " [57.92867414],\n", + " [59.58483443],\n", + " [51.45061151],\n", + " [56.33661661],\n", + " [58.28196988],\n", + " [58.92576777],\n", + " [54.43816149],\n", + " [57.14481139],\n", + " [58.22835033],\n", + " [57.49198956],\n", + " [58.88920878],\n", + " [46.75994049],\n", + " [58.00187651],\n", + " [58.85792666],\n", + " [58.46471069],\n", + " [56.84386881],\n", + " [53.03878924],\n", + " [57.17834336],\n", + " [55.21163468],\n", + " [59.04792962],\n", + " [57.33702239],\n", + " [58.90466569],\n", + " [56.5402633 ],\n", + " [56.5575765 ],\n", + " [59.23138919],\n", + " [56.31616599],\n", + " [57.84505873],\n", + " [59.53173874],\n", + " [58.09454988],\n", + " [56.45211079],\n", + " [57.71501642],\n", + " [57.99376075],\n", + " [53.22588361],\n", + " [56.80934462],\n", + " [59.052542 ],\n", + " [58.91092361],\n", + " [58.99699604],\n", + " [58.65411991],\n", + " [59.04377903],\n", + " [58.74739546],\n", + " [56.5317523 ],\n", + " [59.22633992],\n", + " [56.35168658],\n", + " [56.52920573],\n", + " [58.43091656],\n", + " [57.38188782],\n", + " [56.33306978],\n", + " [59.7444875 ],\n", + " [54.57957252],\n", + " [54.78223225],\n", + " [58.7982737 ],\n", + " [55.00980028],\n", + " [55.0513306 ],\n", + " [59.29120209],\n", + " [57.54877271],\n", + " [57.92097606],\n", + " [56.8518455 ],\n", + " [56.13518287],\n", + " [56.71992241],\n", + " [58.61009625],\n", + " [58.17513563],\n", + " [55.85557679],\n", + " [58.91090877],\n", + " [57.10617413],\n", + " [56.24490973],\n", + " [57.2742736 ],\n", + " [58.84957958],\n", + " [56.454923 ],\n", + " [56.50147784],\n", + " [58.66319811],\n", + " [58.59572259],\n", + " [57.78678192],\n", + " [57.17278202],\n", + " [57.66178079],\n", + " [56.29333037],\n", + " [59.21913224],\n", + " [57.64798533],\n", + " [56.88965679],\n", + " [57.29377145],\n", + " [57.4657063 ],\n", + " [58.72808956],\n", + " [56.98656233],\n", + " [56.65464395],\n", + " [52.7520821 ],\n", + " [56.26275746],\n", + " [54.55092967],\n", + " [56.29448343],\n", + " [58.45870501],\n", + " [57.57311022],\n", + " [54.06308749],\n", + " [57.06347451],\n", + " [54.53037869],\n", + " [53.87590452],\n", + " [55.76459768],\n", + " [55.16961011],\n", + " [58.7373396 ],\n", + " [56.67662145],\n", + " [56.77999353],\n", + " [57.24119663],\n", + " [58.62694187],\n", + " [58.37079417],\n", + " [58.22828062],\n", + " [55.4884829 ],\n", + " [57.98243808],\n", + " [58.29432319],\n", + " [56.98371701],\n", + " [56.69080027],\n", + " [58.94695313],\n", + " [56.75438258],\n", + " [58.6271671 ],\n", + " [58.40435227],\n", + " [55.44249325],\n", + " [56.80875939],\n", + " [58.66907152],\n", + " [58.96703408]])}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "M = MCMC(initial_state, sampler, model=mdl, n_burn=1000, n_iter=1000)\n", + "M.run_mcmc()\n", + "M.store" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plotting the results\n", + "\n", + "The cell below generates trace plots of the MCMC results, with the true parameters used to generate the data shown as red lines." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.subplot(2, 2, 1)\n", + "plt.plot(M.store['beta'][0,:].T,'k.')\n", + "plt.hlines(true_beta[0], 0, 1000,'r')\n", + "plt.xlabel('iterations')\n", + "plt.ylabel('intercept')\n", + "\n", + "plt.subplot(2, 2, 2)\n", + "plt.plot(M.store['beta'][1,:].T,'k.')\n", + "plt.hlines(true_beta[1], 0, 1000,'r')\n", + "plt.xlabel('iterations')\n", + "plt.ylabel('slope')\n", + "\n", + "plt.subplot(2, 2, 3)\n", + "plt.plot(M.store['tau'].T,'k.')\n", + "plt.hlines(true_tau, 0, 1000,'r')\n", + "plt.xlabel('iterations')\n", + "plt.ylabel('tau')\n", + "\n", + "plt.subplot(2, 2, 4)\n", + "plt.plot(M.store['lambda'].T,'k.')\n", + "plt.xlabel('iterations')\n", + "plt.ylabel('lambda')\n", + "\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell below plots the posterior quantiles of the regression line, based on the MCMC samples." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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HcL+/fuNjxdeeAHt2rXD/v374e/v78ju5yuW/F63bNkSMTExxoAPAE6ePIkKFSowEMmBJZ/13bt3Hwk40oNA4VZrNuPw+6Jd0mKdbOnSpeLp6SmLFi2So0ePSr9+/aREiRISHx8vIiI9evSQkSNHGs/funWrFCpUSKZPny7Hjh2T8ePHc2mvicz9rCdPniweHh7y66+/ysWLF42PW7duOest5BvmftYP42oa05n7WZ87d068vb1l0KBBcuLECVm1apX4+vrKp59+6qy3kG+Y+1mPHz9evL295ccff5TTp0/L33//LdWrV5fXXnvNWW8hX7h165bs27dP9u3bJwBk5syZsm/fPjl79qyIiIwcOVJ69OhhPD99ae/7778vx44dk9mzZ3NpryW+/PJLqVy5snh4eEjTpk1lx44dxu+1adNGevXqlen8n3/+WWrWrCkeHh5Sr149+fPPPx3c4/zLnM86ICBAADzyGD9+vOM7ng+Z+3v9IAYj5jH3s962bZs0a9ZMPD09pVq1ajJx4kRJS0tzcK/zJ3M+69TUVPnoo4+kevXqUrhwYfH395eBAwfKjRs3HN/xfCQqKirL//emf7a9evWSNm3aPPKaxo0bi4eHh1SrVk0WLlxot/5pRDiuRURERM7jcjkjRERElL8wGCEiIiKnYjBCRERETsVghIiIiJyKwQgRERE5FYMRIiIicioGI0RERORUDEaIiIjIqRiMEBERkVMxGCEiIiKnYjBCRERETsVghIiIiJzq/wHmJo8rwfiCpAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(x, y,'k.')\n", + "\n", + "q = np.quantile(M.store['y'], [0.025, 0.5, 0.975], axis=1)\n", + "true_line = X @ true_beta\n", + "\n", + "plt.plot(x, true_line,'b-')\n", + "plt.plot(x,q[0,:],'r--')\n", + "plt.plot(x,q[1,:],'r-')\n", + "plt.plot(x,q[2,:],'r--')\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/3_linear_regression.ipynb.license b/examples/3_linear_regression.ipynb.license new file mode 100644 index 0000000..e25c5d4 --- /dev/null +++ b/examples/3_linear_regression.ipynb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/examples/4_GMRF_smoother.ipynb b/examples/4_GMRF_smoother.ipynb new file mode 100644 index 0000000..d555fc7 --- /dev/null +++ b/examples/4_GMRF_smoother.ipynb @@ -0,0 +1,331 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Gaussian Markov random field\n", + "\n", + "In this notebook we estimate a simple time-series model using a Gaussian Markov random field model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We assume that our data consists of noisy observations of a full time-series vector\n", + "$$ \\mathbf{y} = \\mathbf{b} + \\boldsymbol\\epsilon $$\n", + "Assuming independenty normally-distributed errors, the response distribution is a multivariate normal:\n", + "$$ \\mathbf{y} \\sim N(\\mathbf{b}, (\\tau \\mathbf{P}_\\tau)^{-1}) $$\n", + "For the parameter prior distributions, we assume the following:\n", + "$$\n", + "\\begin{align*} \n", + "\\mathbf{b} &\\sim N(\\boldsymbol\\mu, (\\lambda \\mathbf{P}_\\lambda)^{-1}) \\\\\n", + "\\lambda &\\sim \\Gamma( a_\\lambda, b_\\lambda) \\\\\n", + "\\tau &\\sim \\Gamma( a_\\tau, b_\\tau) \\\\\n", + "\\end{align*}\n", + "$$\n", + "where $\\mathbf{P}_{\\lambda}$ is a precision matrix which imposes time-series correlation structure on the parameter vector $\\mathbf{b}$.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "from scipy.stats import norm\n", + "from scipy import sparse\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from openmcmc.distribution.location_scale import Normal\n", + "from openmcmc.distribution.distribution import Gamma\n", + "from openmcmc.sampler.sampler import NormalNormal, NormalGamma\n", + "from openmcmc import gmrf\n", + "\n", + "from openmcmc.model import Model\n", + "from openmcmc.mcmc import MCMC\n", + "from openmcmc.parameter import ScaledMatrix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The code below sets up a precision matrix that imposes a correlation structure suitable for a time-series model." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1.651, -1.65 , 0. , ..., 0. , 0. , 0. ],\n", + " [-1.65 , 3.3 , -1.65 , ..., 0. , 0. , 0. ],\n", + " [ 0. , -1.65 , 3.3 , ..., 0. , 0. , 0. ],\n", + " ...,\n", + " [ 0. , 0. , 0. , ..., 3.3 , -1.65 , 0. ],\n", + " [ 0. , 0. , 0. , ..., -1.65 , 3.3 , -1.65 ],\n", + " [ 0. , 0. , 0. , ..., 0. , -1.65 , 1.65 ]])" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# generate GMRF temporal precision matrix\n", + "n_time = 100\n", + "TIME = pd.date_range(start=\"2022-04-01T01:00:00\", end=\"2022-04-01T01:01:00\", periods=n_time)\n", + "P_lambda = gmrf.precision_temporal(time=TIME)\n", + "\n", + "P_lambda[0, 0] = P_lambda[0, 0] + 0.001 #make full rank\n", + "\n", + "P_lambda.toarray()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generate Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell below generates a simple time-series signal (from a deterministic model), and adds Gaussian noise to make the observed values." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAioAAAGdCAYAAAA8F1jjAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAABCqElEQVR4nO3de3xU1bn/8W9mIOGaCHI3IaJJURALykUUNUYttXjB/tTWogWNghy81VYF6xHUIni3Xksgih6rWKxYe6p4UIKoQEEERUCaoIGMXEVNACVAZv3+WGYgkMBksvfMnpnP+/XaL51hsvfKyiT7mbWe9awUY4wRAACAB/li3QAAAID6EKgAAADPIlABAACeRaACAAA8i0AFAAB4FoEKAADwLAIVAADgWQQqAADAs5rEugGHEgwGtWHDBrVu3VopKSmxbg4AAAiDMUbbt29Xly5d5PM1bkzE04HKhg0blJWVFetmAACACJSXlyszM7NR5/B0oNK6dWtJ9htNT0+PcWsAAEA4KisrlZWVFbqPN4anA5Wa6Z709HQCFQAA4owTaRsk0wIAAM8iUAEAAJ5FoAIAADyLQAUAAHgWgQoAAPAsAhUAAOBZBCoAAMCzCFQAAIBnEagAAADPIlABAACeRaCCRgkEAiouLlYgEIh1UwAACYhABRErKipSdna28vPzlZ2draKiolg3CQCQYFKMMSbWjahPZWWlMjIyVFFRwaaEHhMIBJSdna1gMBh6zu/3q6ysrNFbegMA4puT929GVBCRkpKSWkGKJFVXV6u0tDRGLQIAJCICFUQkNzdXPl/tt4/f71dOTk6MWgQASEQEKohIZmamCgsL5ff7JdkgZcqUKUz7AAAcRY4KGiUQCKi0tFQ5OTkEKQAASc7ev5s41CYkqczMTAIUAIBrmPoBAACeRaACAAA8i0AFAAB4FoEKAADwLAIVAADgWQQqAADAswhUAACAZxGoAAAAzyJQAQAAnkWgAgAAPItABQAAeBaBCgAA8CwCFQAA4FkEKgAAwLMIVAAAgGe5Hqh89dVXuuKKK3TkkUeqefPm6tWrlz766CO3LwsAABJAEzdP/u233+q0007TWWedpbfeekvt27dXSUmJ2rRp4+ZlAQBAgnA1ULn//vuVlZWl5557LvRct27d3LwkAABIIK5O/bzxxhvq27evLr30UnXo0EF9+vTR1KlT6319VVWVKisrax0AACB5uRqofPHFF3rmmWeUm5urt99+W6NHj9aNN96o559/vs7XT5o0SRkZGaEjKyvLzeYBAACPSzHGGLdOnpqaqr59+2rBggWh52688UYtWbJECxcuPOj1VVVVqqqqCj2urKxUVlaWKioqlJ6e7lYzAQCAgyorK5WRkeHI/dvVEZXOnTurR48etZ47/vjjtX79+jpfn5aWpvT09FoHAABIXq4GKqeddprWrFlT67n//Oc/ys7OdvOyAAAgQbgaqPzud7/TokWLdN9996m0tFQvvfSSCgsLNWbMGDcvCwAAEoSrgUq/fv00a9YsvfzyyzrhhBN077336rHHHtOwYcPcvCwAAEgQribTNpaTyTgAACA64iaZFgAAoDEIVAAAgGcRqAAAAM8iUAEAAJ5FoBIjgUBAxcXFCgQCsW4KAACeRaASA0VFRcrOzlZ+fr6ys7NVVFQU6yYBAOBJLE+OskAgoOzsbAWDwdBzfr9fZWVlyszMjGHLAABwBsuT41hJSUmtIEWSqqurVVpaGqMWAQDgXQQqUZabmyufr3a3+/1+5eTkxKhFAAB4F4FKlGVmZqqwsFB+v1+SDVKmTJnCtA8AAHVI2hyVQCCgkpIS5ebmxiRICAQCKi0tVU5ODkEKACChkKPSSF5YdZOZmam8vDyCFAAADiHpRlRYdQMAgLsYUWkEVt0AABA/ki5QYdUNAADxI+kCFVbdAAAQP5IuR6UGq24AAHCHk/fvJg61Ke5kZmYSoAAA4HFJN/UDAADiB4EKAADwLAIVAADgWQQqAADAswhUAACAZxGoAAAAzyJQAQAAnkWgAgAAPItABQAAeBaBCgAA8CwCFQAA4FkEKgAAwLMIVAAAgGcRqAAAAM8iUIHrAoGAiouLFQgEYt0UAECccTVQmTBhglJSUmodxx13nJuXhMcUFRUpOztb+fn5ys7OVlFRUaybBACII66PqPTs2VMbN24MHR988IHbl4RHBAIBjRw5UsFgUJIUDAY1atQoRlYAAGFr4voFmjRRp06d3L4MPKikpCQUpNSorq5WaWmpMjMzY9QqAEA8cX1EpaSkRF26dNExxxyjYcOGaf369fW+tqqqSpWVlbUOxK/c3Fz5fLXfYn6/Xzk5OTFqEQAg3rgaqAwYMEDTp0/X7Nmz9cwzz+jLL7/U6aefru3bt9f5+kmTJikjIyN0ZGVludk8uCwzM1OFhYXy+/2SbJAyZcoURlMAAGFLMcaYaF3su+++U3Z2th555BEVFBQc9O9VVVWqqqoKPa6srFRWVpYqKiqUnp4erWbCYYFAQKWlpcrJySFIAYAkUFlZqYyMDEfu367nqOzviCOO0E9+8hOVlpbW+e9paWlKS0uLZpMQBZmZmQQoAICIRLWOyo4dO7R27Vp17tw5mpcFAABxytVA5Q9/+IPee+89lZWVacGCBbr44ovl9/t1+eWXu3lZAACQIFyd+gkEArr88su1bds2tW/fXoMGDdKiRYvUvn17Ny8LAAAShKuByowZM9w8PQAASHDs9QMAADyLQAUAAHgWgQokscMxAMCbCFTADscAAM+KamXahnKysh3qFggElJ2dXWvzQL/fr7KyMoq0AQAi4uT9mxGVJHeoHY4BAIg1ApUkxw7HAAAvI1BJcuxwDADwMnJUIIkdjgEAzonb3ZPhXexwDADwIqZ+AACAZxGoAAAAzyJQAQAAnkWgAgAAPItApZHYIwcAAPcQqDQCe+QAAOAu6qhEiD1yAACoG3v9eAB75AAA4D4ClQixRw4AAO4jUIkQe+QAAOA+clQaiT1yAACojb1+PIQ9cgAA8SAQCKikpES5ublxdd9i6gcAgAQXz+U0mPoBACCBxaKcBsuTY4QqtMmDnzWARBHv5TQIVMIUz8NmaBh+1gASSbyX02DqJwxUoU0e/KwBJKKioiKNGjVK1dXVoXIaBQUFrl2PVT9RdqhhM25eiYWfNYBEVFBQoMGDB8dlOQ0ClTDUDJsd+Ck73GGzeF0Slowa+7MGAK+K13Ia5KiEoTFVaMl3iC9UHAYAbyFHpQEaWoWWfIf4RcVhAIgcOSox0tBhM/Id4lddP2um8AAg+ghUIrVzp7RunT02b5a++Wbf8d13UlWVTqms1GxJTX/8kipJuyX1f/hh6bnnpDZtpCOPtEe7dlKHDlJ2tpSZKTVtWu+lEX1FRUUaOXKkgsGgfD6fCgsLXc2YBwBYUZv6mTx5ssaNG6ebbrpJjz32WFhfE/Opn2BQ+vJLaeVKe3z2mfT551JZmQ1I3OLz2WAlO1vq3l064QR79OwpdewopaS4d20chCk8AGiYuJv6WbJkiaZMmaITTzwxGpeL3Nat0r//LS1aZI/Fi6Xt2+t/fUaGDSY6d7ajIm3b2uOII6RmzeyoSGqqtlVWavPmzerUpo3atmwp7d4t7dolffut9PXX0rZt9r8bN0rr10tVVfa/69dL779f+5rt2kn9+0sDBtijf387MgPXMIUHALHjeqCyY8cODRs2TFOnTtWf/vQnty/XMDt2SPPmSe+8I82ZI61adfBr0tKk44+3oxk9e0o9ekjdutkAJSMjrMsc+eMRlmDQTiWtWyd98YUdwfnsM3usXWsDmjfftEeNnj2ls8+2x5lnht0uhIclywAQO64HKmPGjNGQIUN0zjnnHDZQqaqqUlVVVehxZWWlO42aN0+66y5p4UJp797a/3b88dIpp0gDB9r/Hn+81CSKqTw+nx2h6dzZXn9/P/wgrVhhR31qRn7Wrt03NfX445LfL/XrJw0ZIl10kZ0yYqqoUWqWLB9Y1ZHRFABwn6s5KjNmzNDEiRO1ZMkSNWvWTHl5eerdu3e9OSoTJkzQ3XfffdDzjueozJsnnXWW/f9u3aRzz7XHWWfZKZx4snWr/X7mzpXefVcqKan970cfLV14oXTxxdLpp9tABhFhyTIAhMfJHBXXApXy8nL17dtXc+bMCeWmHC5QqWtEJSsry/lAparKrro591zp2GOdO6/DIloOu3699Pbb0j//aaezdu3a92+dO0u/+pX061/b3BZGWgAALoiLQOX111/XxRdfHKrwKdkExJSUFPl8PlVVVdX6t7rEfNVPFB0YlDiyHPb776V33tHOv/5VqW+9pab7JwYfc4w0fLh01VVSVpaz3wwAIKk5ef92rYT+2WefrRUrVmj58uWho2/fvho2bJiWL19+2CAlmRxYZv/BBx8MBSmSFAwGNWrUKAUCgYaduEULFW3dqvRXX1XL7dt1UUqK1vbvL7VoYRN1x4+3ScHnnSf9/e92NRIAAB4S1RL6h5v6OVAyjKjUVaPjwBUmNYqLi5WXl9eoc/v9fq1btUpHLV4sFRXZ/JYaHTpI111nj86dI/l2qN4KAIiPERWEp64aHTXTPfuLZDlsffU/SjZskK64Qioutsm348ZJnTpJW7ZI99wjde0qDRtmVxY1ABswAgCcxqaEMVbfqMfkyZM1duzYWsthG5qj0qCKqnv2SLNm2SXOH3647/mBA6WxY6Xzz7dLp524FgAgoTGikkBqanTU5OzUBCV/+MMfVFZWpuLiYpWVlUW0r0x9564zcGjaVLrsMumDD6SlS6URI6TUVFtr5qKLpF69pOefrzeP5VDVWwEAiBQjKocQzXwLN2t0RHzujRulP/9ZeuYZqab4XlaWdMcd0tVX20Bmv2swogIAkBhRiYpo51tkZmYqLy/PlZt6xOfu3FmaPNnWZpk82eaxlJdLo0dLublSYWFohKVBozcOCgQCKi4ubviKKABAXGBEpQ6MDtRj1y5p6lRp0iQ72iLZ5c133WVrsvj9EY/eRDJ65UitGQCA4xhRcRn5FvVo1ky64Qa7v9Bjj9kRlnXrpIIC6cQTpTfeUOZRRzV49CaS0atAIOBMrRkAgKcRqNShZrfc/bFb7n6aN5duuskWjXvoIaltW7vz9EUX2f2EFiwI+1SRBhwEkwCQHAhU6hCrfIu407y59Pvf2xGWcePs4w8/lE47za4gKis77CkiDTgIJgEgORCo1KOgoKDRy4OTxhFHSPfdZ4vHXXONrbcyc6Z03HHSnXdKO3bU+6WRBhwEkwCQHEimhfM+/VS6+WZb+Vayq4fuv99Ww61jx+aioiKNGjUqouJ2kSTvUuYfANwVF7snO4FAJY4ZI73xxr6pIUkaNEh6+mlbPO4AbtaR2R8rhQDAfQQqiB9VVXaF0D33SN9/L/n90o03ShMmSFH+mbLsHACig+XJSSoui5ulpUm33y59/rl0ySVSdbX06KM2f2XmTDvyEiWsFAKSS1z+zcRBCFTiRNzvTJyVZQOT2bNtVduNG+3KoAsvtJVvo4CVQkDyiPu/mQghUIkDCVXcbPBgacUKafx4uxHi//6v1KOH3VOoutrVS7NSyJv41AunJdTfTBCoxIOEm7JIS7M5Kp98YhNsd+60q4QGDpQ++8zVS7Ps3Fv41As3JNzfzCRHMm0cSOgk0GBQKiqSbr1Vqqiwoyzjx0u33Wb/Hwkrod/XiCneW7FHMm2SSbQpi1pD/T6fdO21tgT/BRdIe/bYInEDBtgRFyQsPvXCLYn2NzPZMaISR6JVa8RNh6xjYoz00kt2+fI330hNmkj//d+2PD+jKwmHT71wWyL8zYxX1FFBXAr7xrRpk/Rf/yXNmmUf9+snvfCCXdKMhNKYqsQA9vFaxW2mfhCXwh7q79RJ+vvfpb/+1e4jtGSJ1KeP9PjjNqelgVhV4l0kNwONl+hJ6Yyo/Mhr0Wgiimio/6uvpKuvlv7v/+zj/Hxp+nRblyUM8VIyn/cfgEh4dQqVERWHJXo06hURJbgddZQtEvf001KLFtLcudKJJ9ricYcRL7UUeP8BiFQyJKUn/YiKV6PRRBZxgltJiTRsmJ0KkqThw6UnnpBat67z5cXFxcrPz6/z+by8vAha7jzefwAaw6t/QxhRcVAyRKNek5mZqby8vIb/EuXmSh9+KP3xj3ZZ8/PPS717S4sW1fNy75fM5/0HoDGSYSl20gcq8XAzw36aNpX+9Cdp3jwpO1v64gtb3Xby5IMSbePhF5j3H4DGSvSk9KQPVOLhZoY6nH66LQj361/bPYLGjbP7CG3cWOtlXv8F5v0HwAkRj1THgaTPUalBYaA4ZYz03HPSDTdI338vtW9vp4TOOy/WLWsQ3n8AEgkF34ADff65HV2pKbt/2212ioiKtgCigBIDtZFMCxzouONsUu3119vHDzwg5eVJ5eUxbRaAxEeJAXcxooLE89prtkhcRYXUtq0tvz9kSKxbBSABeXV5cKwxooKoiHXp+Yiv/8tfSh9/LPXtazc3PP986fbbpb173WkogKRFiQH3Eai4INY3eCfEeiizrus3qF+POUb64AO7E7Nkp4LOPvugVUEA0BgNKTGQCPeGmDAeVlFRYSSZioqKWDclbNOmTTM+n89IMj6fz0ybNi3WTWqw8vLy0PdQc/j9flNeXh6z66ekpETerzNnGtO6tTGSMR07GlNc7FrbASSfadOmGb/fH/pbWdffp0S4NzSEk/dvVwOVp59+2vTq1cu0bt3atG7d2pxyyinmzTffDPvr4y1QicUNvry83MydO9fRa8ydO7fW91BzFEfpBl/f9RvVr2vWGNOrlw1WfD5jJk0yprravW8CQFIpLy83xcXFdf5divWHv1hw8v7t6tRPZmamJk+erKVLl+qjjz5Sfn6+LrroIq1cudLNy8ZMtOcq3ZqeiXW11Lquf6AG9+tPfmJXBQ0fbivYjhsnXXyx9N13jWssAOjQBdfIY2kkBwKnBmnTpk3YQ16MqMTuWuEMZbpp/+v7fD6TkpLizPcaDBozdaoxaWl2dOXYY4355BPnvwEA+BEjKo0TtWTa6upqzZgxQzt37tTAgQPrfE1VVZUqKytrHfEkmuXQ3Y7QY116fv/rr1u3TlOnTnWmX1NSpGuusZsbZmdLa9dKp5wivfiiw98BAFhsldE4rtdRWbFihQYOHKhdu3apVatWeumll/SLX/yiztdOmDBBd99990HPx1sdlWiUQ0/GtfuO9+u2bdKwYdLbb9vH118vPfywlJra+HMDwAGSaauMuCqhv3v3bq1fv14VFRV69dVXNW3aNL333nvq0aPHQa+tqqpSVVVV6HFlZaWysrLiLlCJlqKiIo0aNUrV1dWhCD3ckQ/KPf+oulq6+27p3nvt40GDpJkzpU6dYtuuCPFzBRJfPPyeO1qwtdGTRw109tlnm5EjR4b12njLUYmFQ2Wa1yfZlsmF5Z//NCY93eatdO5szIIFsW5Rg/FzBRJfvPyeO3n/jnoJ/fz8fHXt2lXTp08/7Gspoe+8ZJwyCltJiTR0qLRqld3M8PHHpVGjbF6Lx/FzBRJfQ3/PYznyEjcl9MeNG6f58+errKxMK1as0Lhx4zRv3jwNGzbMzcviEFgmdwi5udK//y1dcom0Z480erR07bXSftORXsXPFUh8Dfk9j3V1cSe5Gqhs2bJFv/3tb9W9e3edffbZWrJkid5++22de+65bl4WhxDrGime16qV9Le/SfffL/l8UlGR3YV5w4ZYt+yQ+LkCiS/c3/NAIKCRI0eGgppgMKhRo0bFbel+VwOVoqIilZWVqaqqSlu2bNE777xDkBJjLJMLQ0qKdNtt0ltvSW3a2EJxJ58sLVgQ65bVi5+ru9ijBV4Q7u95oo2wRj1HpSHIUXFPMi2Ta5S1a20F2xUrbN7Kk09KI0fGulX1iubPNR5WHjihqKgo9OnU5/OpsLAw6nWFgP0d7vfcCzlrcbU8uTEIVLwlWW5MB9mxQ7rqKunVV+3j666T/vznpK63kiw3by/8wQci0ZjyFU6Im2RaJI5ESsxqsJq8lfvus9NCf/mLdM450ubNsW5ZTCTa/PehJNoQOpJHrKuLO4lABYeVTDemeqWk2I0M//lPKT1dev99qW9faenSWLcs6pLp5k2SMuLZoTZKjCcEKjisZLgxhZ0sOWSItHix1L27FAjInHaaVt55Z1IFbcl08yZJGQlv717JuxkgkghUEIZEvzE1eFqre3fp3//W+hNPVEpVlXpOnKiXsrL07NSp0WlwjCXbzTuRhtCBWj791G7K+txzsW7JIZFMi7DEOjHLLZEmSwYCAXXr2lV3G6M7fnzuLUknfvaZjurZ091GewQrx4A4tXu3zbmbONGOqHTrJv3nP1KTJo5dwsn7t3Otgie4tTKnoKBAgwcPTrgb06GmtQ71PZaUlGivMfqjpE8lPSvpPEnfn3eeNGeOHXVJcJmZmQnzPgCSxkcfSVdfbUsuSHbbkKefdjRIcRpTPwnE7ZU5iZKYtb9Ip7X2/7pXJA2SVC6pRXm51L+/LRYHCqUBDnDk92jXLmnsWGnAABuktGsnvfKK9NprUufOzjXWBQQqCYKVOZGJNN/iwK/71O/X/Icflk47TaqstEm3Dz7o+SS1+jjxhzGpl7QDDnHk92jJEltd+/77pWBQ+vWv7earl10WF5uuqtH7L7vIyW2iE93cuXONpIOO4uLiWDctLpSXl5vi4mJTXl7euK+rqjLmmmuMsSGKMVdcYcz337vQYvc4sY18eXl56Bw1h9/vb3D/Asms0b9Hu3YZM3asMT6f/XvUsaMxr73mbqN/5OT9mxGVBJHoK3PcFum01kFfl5oqFRZKTzwh+f3Siy9KZ5whffWV4212Y1rFqZG5ZFjSDritUb9HS5faUZTJk+0oyuWXSytX2i1B4gyBSoJItiWjnpaSIl1/vfR//ye1bWuT1/r2tZsbOsStaRWnAgwCZ7gtGfKfIvo92rNHGj/e5qKsXCl16CD9/e/SSy9JRx7pcotd4sAIj2uY+mm4SKcw4JK1a4054QQ77Jqaasz06Y0+pZvTKk6ee9q0acbv94fOEckUElAXJ6Ynvaq8vNzMnTs39DvXoN+jFSuM6dNn39TzpZcas3VrlFpem5P3bwIVwG2VlcYMHbrvj8cttxizZ0/Ep3M7H8nJAIPAGU5L5Pyn+gKww/4e7d1rzP332w9DkjFt2xozY0YUW34wJ+/fFHwDoiEYlCZMkO691z4ePFh6+WWpTZsGnyoaO/pSzA1eVVxcrPz8/Dqfz8vLi36DHBLx7/UXX0jDh0sffGAfn3++zZOL8ZJjdk8G4o3PJ91zj92FuXlz6e237Rzy6tUNPlU08pESsWYOEkOi5j81OD/MGGnqVOnEE22Q0rq1VFQkvfFGzIMUpxGoANF06aXSggVS165SSYndZ+Nf/2rwadh/BskqURcONCgA27RJuuACaeRIaedOu7Lw009txdl4qIvSQEz9ALGwZYt0ySXS++/bPyyTJkm33ZaQf2QANyTi9GRYe6q99poNULZts+UQ7rtP+t3v7Kithzh5/yZQAWJl927pxhulKVPs48svl6ZNk1q0iG27AMRMvQFYZaV0003S9On28U9/aus0nXBCTNp5OAQqQCL5y1+kG26wu5j26SO9/rqdGgIASZo/X/rtb6V16+yo6+23S3ffbUdUPIpkWsCjIipCdd110rvv2k3Cli2zxeHef9+9RgKID1VVNijJy7NBSrduNmiZNMnTQYrTCFQAhzSqWuwZZ9gKtr17S1u3Svn5dqQFQHJaudKuDHzgAbvC5+qrpU8+kQYNinXLoo5ABXCAI3vkZGfbZYaXXWangUaPlkaNsrksSCrJUB4e9QgGpT//2e7T88kndqR11iy79Lh161i3LiYIVAAHOLYJX8uW0owZNpM/JcUWbjrrLGnjRgdbCy9zax8nxIGvvrLFIG++2U77/OIX0ooV0tChsW5ZTJFMCzjAlWqxb74p/eY3UkWF1KWL/VTVv79DLYYX1fc+WrhwoXbs2KHc3NyEWYqLA8ycaUdQv/3WFoV85BH7OE5LFpBMC3iMK0WofvELafFi6fjjpQ0bpNNPl557zqEWw4vqG5k75ZRTGGFJVBUVtgT+ZZfZIKVvX5tUf911cRukOI0RFcBBrhShqqyUrrzSlsaWpDFj7KetJMr6TxZ1jagcyOl9nRBD779vlx2XldmCbXfcId11l9S0aaxb1miMqAAeVdceOY1OjExPt9M+EybYx089JZ19ti2jjYRy4MjcgSXVpQhzn+Atu3dL48ZJZ55pg5Ru3WzQcu+9CRGkOI1ABXCRY4mRPp80frwdVUlPt6uDTj5Z+ve/nW0wYm7/fZwWLVqUkBvwJbXVq+0eX5Mn22XHV10lLV8unXpqrFvmWQQqgEscWbJ8oAsuqJ23csYZdgdVJJSakbl+/fol5AZ8SckY6cknpZNOsjkobdtKf/+79Oyz9sMH6kWgArjEsSXLB+re3Y6kXHyxHUIeOVK65hpp167GnReexE7ZCWDDBum88+xWGbt22SXIK1ZIv/xlrFsWFwhUAJc0aNv2hmrd2n4amzTJTgsVFdlVQevWNf7c8Jy6cp8QJ/7+d6lXL+ntt6VmzaTHH7elB7p0iXXL4oargcqkSZPUr18/tW7dWh06dNDQoUO1Zs0aNy8JeIYrS5b3l5IijR0rzZ4tHXmkLcF/8snSnDnOnB9Ji8q4DqistPknl1wiffON3XD044/tqEodSdKon6u99d5772nMmDFatGiR5syZoz179uhnP/uZdu7c6eZlAc+IyrD9uedKS5faIGXbNjusPHGiLcUNNBCVcR0wf7504onS9Ok2KBk3Tlq0yOaWocGiWkdl69at6tChg9577z2dccYZh309dVSABti1S7r+ejsNJElDhkj/8z9SmzaxbRfihisVlpNJVZX03/8tPfSQTZ7t1k164YWk3EgwbuuoVFRUSJLatm0bzcsCyaFZM2naNHukpUn/+pddYfDxx7FuGeKEawngyWDFCrvFxYMP2iCloCBpdzt2WtQClWAwqJtvvlmnnXaaTjjhhDpfU1VVpcrKyloHgAYqKJAWLpSOOcYWkzr1VLu5oXeLUMMjXE0AT1TV1dIDD9jS959+KrVvL73+uv3AkKS7HTstaoHKmDFj9Nlnn2nGjBn1vmbSpEnKyMgIHVlZWdFqHuB5DUpw7NPHJtdecIEdjh41ypbh37HD/YYibrmeAJ5o1q611WVvv92WCrjgAjuyctFFsW5ZQolKjsr111+vf/zjH5o/f766detW7+uqqqpUVVUVelxZWamsrCxyVJD0ioqKQsXjfD6fCgsLw0vMDQbtfPkdd9hPfscdJ736qtSzp/uNRtxyZc+qRGKMLbR4yy3Szp125OTPf5ZGjGAjwR85maPiaqBijNENN9ygWbNmad68ecrNzW3Q15NMCziU4PjBB9KvfmULTzVvLj3zjN2xFUDDfPWVLbA4e7Z9fOaZdnXP0UfHslWeEzfJtGPGjNGLL76ol156Sa1bt9amTZu0adMm/fDDD25eFkgojiQ4Dhpky3afe670ww/2k9/w4UwFAQeod4rVGOnFF6UTTrBBSlqa9PDD0ty5BCkuczVQeeaZZ1RRUaG8vDx17tw5dLzyyituXhZIKI4lOHboIL31lnTPPba2wwsv7EsABFB/DZktW6T/9/9sntd330n9+tnA/5ZbKN4WBa72sDGmzmPEiBFuXhaIe/t/qnM0wdHvt3UeiottCe81a+ySyr/8hVVBSGr1bSK67S9/sTlds2ZJTZtKf/qTtGABxduiiFAQ8Ji6PtU5XuH2jDNsjYchQ+yqoNGjpUsvlb791plvAgkv0crsHzjF2l7Sy9XVOnL0aOnrr22l2SVLpD/+UWrSJHYNTUJRrUzbUCTTItlEvTJoMCg99phdXrl3r5SVJf31r3aDQ6AeEa9Ci7FAIKCSkhLl5uYe9Pu0/+/eJZKelg1WTJMmSrnjDhugpKbGotlxKW6SaQE0TNQrg/p8dp594UIpJ0cqL5fy8qS77rKBC3CA+qZIvD6ycrg9jDIzM/XCgw9qpqSZskHKtsxMpSxeLN19N0FKDBGoAB4Ss8qgffva5MCrrrKjLPfea6eH1q5t0GkSbToAB4vHMvuHDa5+XNEzbOJEXSLJ+HyqvOkmHbl2rS2eiJgiUAE8JKaVQVu1kp59Vnr5ZSkjw46y9O5tNzkMY4Y4kXbdJeCqn9PBdDT6+pDBVXm5dP75dkXPN99IffooZelSpT/2WFRGUXivhcF4WEVFhZFkKioqYt0UIKrKy8tNcXGxKS8vd+Xcc+fOPfS5y8qMOeMMY2yIYszQocZs2XLIc/p8PiMpdPj9flfa77Zp06aFvhefz2emTZsW6yZ5zrRp04zf7w/9nCPto2j1dV3vzyY+n/lm4kRjWre27/HUVGPuu8+Y3btdaUNdEvm95uT9m0AFSCIN+sO4d68x999vTNOm9g95x47G/O//1vnSuXPn1roJ1BzFxcXufCMuSaSAy22NDaaj3df7B1cn+HxmY07OvkD8lFOMWbXKlevWJ9Hfa07ev5n6AZJEg5Mg/X7pttukxYttHYnNm+0Q+dVXSxUVtV6aKLvuxmP+RWNFOvWQmZmpvLy8iKclo93XBQUFKluzRl+MGKFP/X51Ki21051PPGG3mIhyXZRkfK9FikAFSBIR/2Hs3dvuxHzLLXbDteeek3r1kt55J/SSRNl1N54CLidyG2KZVxT1vn7vPWWef766TZ+ulD17bA2hVauk66+3QXmUxdN7LeYcGOFxDVM/gHMcGWqeP9+YY4/dN2Q+erQxlZW1ruFWbk20OJV/4SYnchu8MPUQlb7eutWYESP2vWc7dDBmxgxjgkHnr9VA8fBeixQ5KgAi4sgfxh07jBkzZt8f/q5djZk92/nGxpCXAy6nAgyv5BW51tfV1cY8+6wxRx657706apQx33zjyOnDSkoP8zxefa81BoEKgIg59ofx3XeN6dZt301g+HBjtm1zpI2on1MBhhdGVFyzfLkxp522773Zq5cxCxY4dvpEXq3jFJJpgXpQk+DwGpsEGZKfL61YId18s81def55qUcP6dVX2eDQRU7lNiRKXlEtFRXSTTdJJ50kffih1LKl9MAD0tKl0sCBjlwiXivzxjMCFSSMRCo4FjdatpQefdTeFI4/3q4MuvRS6YILpLKyWLcuITkZYDi+2WWsBIPSCy9I3btLjz9uH196qbR6tXTrrXbXY4ewWif62JQQCSHqm/nhYFVV0sSJ0uTJ0p49UosWdo+Um25y9EYBKxAIqLS0VDk5Ocn9Hl+8WLrxRunf/7aPf/IT6cknpXPPdeVy/K0JD5sSAgfgU44HpKVJ99wjffKJ3Sfo++/tp9l+/aQFC2LduoTj2BRevNq40e5NNWCADVJatbJB8qefuhakSAk6ZeZxjKggIfApx2OMkaZPl/7wB7t/iiT99rfS/fdLnTrFtGmIcz/8YKcbJ02Sduywzw0fbh937hy1ZjCidWiMqAAH4FOOx6Sk2E+7n38uXXONfVyTQ/DYY3ZqCGiIYFD6n/+xUzt//KMNUvr3lxYtskFxFIMUiRGtaGJEBQmFTzketXixNGaMrXAr2dVBDz8s/fznsW1XIwQCAZWUlCg3N5f3mtuKi+3o3Mcf28ddu9oRlF//WvLxeduLGFEB6sGnHPc0aul3//42j2DqVOnII23p8vPOs8eqVc431mWsMIuSZctsMJufb4OU9HSbh7JmjfSb3xCkJAl+ykCC88yeMD6fnQYqKZF+9zu7Emj2bOnEE+1oy5YtEbcvmqijEQWlpdLll9t6KG+/LTVpYt8jpaXS7bdLzZrFuoWIIgIVIIE5EWA4fmNu00Z65BFp5Urpoouk6mrp6aelY4+VJkyQtm+P7LxRwgozF61bJ40caWvyzJhhc5uGDbO5Tk8+KbVvH+sWIgYIVIAE5VSA4dqNOTdXev11ae5c6eSTbXLk3XfbgOXxx21dFg9i11sXrF8vXXedfU9MnSrt3WunBZctk1580b4nkLQIVIAE5VSA4fqN+ayzbLLtK69IOTnS1q22SFz37tK0aZ5bIcQKMweVlUn/9V82QJkyxf6szznHVjp+803ppz+NdQsbhS09nEGgAiSouNoTxueTLrvMJtb+5S92qem6ddK119rlqB4LWBKm9HysrF5ta5/k5EjPPCPt3m0D1vnzpTlzpFNPjXULG42Ea+ewPBlIYEVFRRo1apSqq6tDAUakN9WoLv3+/nv7Cfv+++3+QZJ09NHS2LH2BkcyZXxavNhuEvjaa/s2rjz3XOmOO6S8vJg2zUkUoHT2/k2gAiS4uK4tU1fA0rGjXTV03XVSRkZs24fDq66W3njD1s358MN9z198sTRunN1iIcEUFxcrPz+/zufzEiggOxQCFQDJ5fvv7fTPww/bxEvJ1tS47jrp+uulrKw6v4yibDFUWSk9/7z05z9La9fa55o2tcuOb7tN6tkztu1zESMqFHwDkGxatLA75JaW2lL8PXvaG+EDD0jdutn8lg8+2DedIHIEYuazz6TRo6UuXezPbO1auyT9jjts8uzzzyd0kCKRcO00RlQAxJ9g0K4KefRRu7y5Rp8+0pgx+mrQIHXt0SPqn2iTdgTnhx+kWbPsNN38+fueP/54W6htxAipZUtXm+DFvo/raddGcvT+bTysoqLCSDIVFRWxbgoAr/r0U2OuucaYZs2MsWMqZk+LFuYZyZwkGe13FBcXu9aMadOmGZ/PZyQZn89npk2b5tq1PCEYNGbJEmNGjzYmIyPU98bvN+aSS4yZO9e+JgqSru/jgJP3b0ZUACSGbdukoiJbMGy/WjHLJL0gaabPp0Xr1rnyyba+nISFCxdqx44dnvqU32hlZbZq7EsvSStW7Hs+O9vumH3NNdJRR0WtOeSDeBM5KkAcowiUS4480iZp/uc/0ty5Wtu/v6ok9ZH0qKT1xijz6qttjktlpaOXrq+43imnnJIYOTKbN0tPPSUNGmRzgsaNs0FKWprdHPCdd6QvvpDGj49qkCKxpUEyYEQFiKKioqJQWXufz6fCwkKKhblow4oV2lFUpOwPPlDa0qX7/iE11dbv+OUvpQsvlNq1a9R16vpUf6C4+5RfWmq3OJg1S1q4cF+ickqKrXly+eXSJZfYRNkYYkTFm+ImR+W9994z559/vuncubORZGbNmtWgrydHBYmkvLw8NI9ec/j9flNeXh7rpiWH0lJj7rnHmO7d9+VTSMb4fMbk5RnzwAM23yXCvIpp06YZv98fypPQAfkxcjlHptF++MGYOXOM+cMfjOnZs3YfScb072/MI48Y89VXsW7pQfbve7/fT46KB8RNjspbb72lDz/8UCeffLJ++ctfatasWRo6dGjYX8+IChIJRaA8whhbwv211+yxbFntf+/SRfr5z+2Iy+mnN2gqo2aVR8uWLXXKKad4+1P+3r3S8uV2lc4770jz5tnVOzWaNLEjJ0OH2l2uvdLueiTzChsvisuCbykpKQQqSGoMUXvUl1/ayqlvv33wzVqyO/eefro9BgyQjjtO+rE+xqE4uX2BI7Ztk5YutWXs339fWrDA7li9v06dpMGD7fHzn8d8WgfxK2EDlaqqKlXtt7V7ZWWlsrKyCFSQMDx380Jtu3bZm/js2TZoWb7c1mzZX4sWUu/eUt++9r/HH2+POsr5x+RTfnW1TWxdtcoey5dLS5bYgOxAGRk2QfbMM21w0quXzUEBGilhA5UJEybo7rvvPuh5AhUkEoao40hFhR15eP99W/l22bKDRyFqdOliA5Zu3exS3a5d7X8zM22ybnq6M0HArl3S1q3Sli1SIGCXC9ccX3whrVkj7feBr5acHOnkk6XTTpPOOEM64YSwRoeAhkrYQIURFQCeVl1tlz8vXSp99JEtF796tbRhw+G/tmlTG7C0aye1amVHZpo3t/9NSzswddUGJDt32mPHDmn7dhugbN9++Gs1a2aDph49bDDSr5900klM5SBqnAxUmjjUJkekpaUpLS0t1s0AgLr5/fumeq64Yt/zFRU2YPn8c2ndOnusX2//u2GD3VRxzx5p40Z7NFbTplKHDlLnzlK3bvq0slKF//d/+tIYrUlJ0R2PP66rr7228dcBPMBTgQoAxKWMDOmUU+xRl++/t8msX39tj5077XPff2+Td3ftstNC+x/Nmtn9cVq2tCMwrVpJ7dvbACUjIzSNFAgE1Cc7W8GawXFjNHL0aP3svPM8N7Xoxf14kl08/ExcDVR27NhRqzrgl19+qeXLl6tt27bq2rWrm5cGAO9o0cIeWVmOn/pQlVm9dOOh2KH3xMvPxNUclXnz5umss8466Pnhw4dr+vTph/16licDwKF5edl7zaf1Vq1aeb+uTJJx+30TN3v95OXlyRhz0BFOkAIAOLzMzEwVFhbK/+PqnZpl77EOAIqKipSdna38/HwNGDCA/Xg8Jp72SGKvHwCIIrdyAry07D0h9z5KMIyoAAAOsv8og9M7KmdmZiovL88TN/66Pq1Lks9nbzleGfVJZl4diasLIyoAEAVeziVxWn3f68KFC7Vz505PjPrAcmskLmHrqABAooqX1TlOqPm0fuB2Ef369Yt103CAzMxMz7//CFQAIApyc3Pl8/kOGmXIycmJYavcU1BQoMGDB3smb6Y+8VBHJNmRowIAUeCVnIBAIKDi4mIFAgFXv0byVt5MXdzMGYJzyFEBEBcS5ZNvLFfnRFLgK16KgjVUMuUMxUJcbkoYCQIVAFLi3iyjKZIbcyLfzIuLi5Wfn1/n83l5edFvUIJheTKApBEIBEJBiiQFg0GNGjWqwdMQyS6SAl/xVBSsoWpyhvaXyDlD8YxABYCnNeRmGc38i3gTyY05kW/mXskZwuERqADwtHBvlpEkRiZTMmUkN+ZEv5kXFBSorKxMxcXFKisrYzrRo8hRAeB5RUVFB9Xk2P+mQv5F+CJJ5vVSeX7EBwq+AUgqh6vJEUkxtWQqwLa/SAp8xUNRMCQuAhUAceFQN8tIiqklWwE2IF6RowIg7pF/ASQuclQAJAzyLwBvIEcFgKMSpeor+RdA4mHqB0hyybREF0D8YeoHSGLJukQXgLsooQ/AEYlcIh1AYiBQAZJYIpdIB5AYCFSAJMYSXQBeR44KAM8t0U2UVUhAsiJHBYCjMjMzlZeX54mggFVI8Jpk2WHbqwhUAHhGIBDQyJEjQwm+wWBQo0aN4gaBmCFwjj0CFQCewSokeAmBszcQqADwDFYhwUsInL2BQAWAZ7AKCXWJVY4IgbM3EKgA8JSCggKVlZWpuLhYZWVlKigoiHWTEEOxzBEhcPYGlicDiCqWHiNcXtniwWvL9+MBy5MBxCVWUKAhvJIj4qXl+8mIQAVAVLCCAg1FjggkAhUAUeKVT8eIH+SIQIpSoPLUU0/p6KOPVrNmzTRgwAAtXrw4GpcF4CF8Ok5sbq3MIbkargcqr7zyim655RaNHz9eH3/8sX76059q8ODB2rJli9uXBuAhfDpOXG7nHpEjktxcX/UzYMAA9evXT08++aQkOy+dlZWlG264QWPHjj3k17LqB0g8rKBILF5ZmQNvcfL+3cShNtVp9+7dWrp0qcaNGxd6zufz6ZxzztHChQsPen1VVZWqqqpCjysrK91sHoAYyMzM5AaWQA6Ve8TPGU5wdern66+/VnV1tTp27Fjr+Y4dO2rTpk0HvX7SpEnKyMgIHVlZWW42DwDQSOQewW2eWvUzbtw4VVRUhI7y8vJYNwkAcAjkHsFtrk79tGvXTn6/X5s3b671/ObNm9WpU6eDXp+Wlqa0tDQ3mwQAcFhBQYEGDx5M7hFc4eqISmpqqk4++WS9++67oeeCwaDeffddDRw40M1LAwCiiJU5cIurIyqSdMstt2j48OHq27ev+vfvr8cee0w7d+7UVVdd5falAQBAnHM9UPnVr36lrVu36q677tKmTZvUu3dvzZ49+6AEWwAAgAOxezIAAHAUuycDAICkQKACAAA8i0AFAAB4FoEKAADwLAIVAADgWQQqAADAswhUAACAZxGoAAAAzyJQAQAAnkWgAgAAPItABQAAeBaBCgAA8CwCFQCApwQCARUXFysQCMS6KfAAAhUASYWboLcVFRUpOztb+fn5ys7OVlFRUaybhBgjUAGQNLgJelsgENDIkSMVDAYlScFgUKNGjSKoTHIEKgCSgldvgozw7FNSUhL6+dSorq5WaWlpjFoELyBQAZAUvHgTZISnttzcXPl8tW9Lfr9fOTk5MWoRvIBABUBS8NpN0KsjPLGUmZmpwsJC+f1+SfbnM2XKFGVmZsa4ZYglAhUAcamhUyZeuwl6cYTHCwoKClRWVqbi4mKVlZWpoKAg1k1CjKUYY0ysG1GfyspKZWRkqKKiQunp6bFuDgCPKCoqCo1G+Hw+FRYWhn1DCwQCKi0tVU5OTkw/qQcCAWVnZ9cKVvx+v8rKyhhBQNxz8v5NoAIgriTSDb6oqEijRo1SdXV1aISHEQQkAifv300cahMARMWhpkziLVApKCjQ4MGDPTHCA3gVgQqAuFKTFHvgiEq8rgzJzMwkQAEOgWRaAHHFa0mxANxFjgqAuOSVpFgAByNHBUDSY8oESA5M/QAAAM8iUAEAAJ5FoAIAADyLQAUAAHgWgQoAAPAsAhUAAOBZBCoAAMCzXAtUJk6cqFNPPVUtWrTQEUcc4dZlAABAAnMtUNm9e7cuvfRSjR492q1LAACABOdaZdq7775bkjR9+nS3LgEAABKcp0roV1VVqaqqKvS4srIyhq0BAACx5qlk2kmTJikjIyN0ZGVlxbpJABC3AoGAiouLFQgEYt0UIGINClTGjh2rlJSUQx6ff/55xI0ZN26cKioqQkd5eXnE5wKAZFZUVKTs7Gzl5+crOztbRUVFsW4SEJEUY4wJ98Vbt27Vtm3bDvmaY445RqmpqaHH06dP180336zvvvuuwY1zcptoAEgWgUBA2dnZCgaDoef8fr/KysrYcRpR4eT9u0E5Ku3bt1f79u0bdUEAgLtKSkpqBSmSVF1drdLSUgIVxB3XkmnXr1+vb775RuvXr1d1dbWWL18uScrJyVGrVq3cuiwAJL3c3Fz5fL6DRlRycnJi2CogMq4l0951113q06ePxo8frx07dqhPnz7q06ePPvroI7cuCQCQlJmZqcLCQvn9fkk2SJkyZQqjKYhLDcpRiTZyVAAgcoFAQKWlpcrJySFIQVTFLEcFABA/MjMzCVAQ9zxVRwUAAGB/BCoAAMCzCFQAAIBnEagAAADPIlABAACeRaACAAA8i0AFAAB4FoEKAADwLAIVAADgWQQqAADAswhUAACAZ3l6r5+a/RIrKytj3BIAABCumvu2E/seezpQ2b59uyQpKysrxi0BAAANtX37dmVkZDTqHCnGiXDHJcFgUBs2bFDr1q2VkpLi6LkrKyuVlZWl8vLyRm9Bnejoq/DRV+Gjr8JHXzUM/RU+t/rKGKPt27erS5cu8vkal2Xi6REVn8/n+hbl6enpvJHDRF+Fj74KH30VPvqqYeiv8LnRV40dSalBMi0AAPAsAhUAAOBZSRuopKWlafz48UpLS4t1UzyPvgoffRU++ip89FXD0F/hi4e+8nQyLQAASG5JO6ICAAC8j0AFAAB4FoEKAADwLAIVAADgWZ4KVJ566ikdffTRatasmQYMGKDFixeH/q2wsFB5eXlKT09XSkqKvvvuu7DOOW/ePJ100klKS0tTTk6Opk+f3qDr1ufTTz/V6aefrmbNmikrK0sPPPDAQa+ZOXOmjjvuODVr1ky9evXSm2++GVabwxEvfbVr1y6NGDFCvXr1UpMmTTR06NCIrx2peOmrefPm6aKLLlLnzp3VsmVL9e7dW3/9618Peh3vK2nNmjU666yz1LFjRzVr1kzHHHOM7rzzTu3Zs6fW6+ir2kpLS9W6dWsdccQRB/0bfSWVlZUpJSXloGPRokW1XudmXx2u3W711/z583XBBReoS5cuSklJ0euvvx7WedevX68hQ4aoRYsW6tChg2699Vbt3bu3Qdc+LOMRM2bMMKmpqebZZ581K1euNNdee6054ogjzObNm40xxjz66KNm0qRJZtKkSUaS+fbbbw97zi+++MK0aNHC3HLLLWbVqlXmiSeeMH6/38yePTvs69aloqLCdOzY0QwbNsx89tln5uWXXzbNmzc3U6ZMCb3mww8/NH6/3zzwwANm1apV5s477zRNmzY1K1asiLyTwmyzl/pqx44d5rrrrjOFhYVm8ODB5qKLLoro2pGKp76aOHGiufPOO82HH35oSktLzWOPPWZ8Pp/55z//GXoN7ytr7dq15tlnnzXLly83ZWVl5h//+Ifp0KGDGTduXOg19FVtu3fvNn379jXnnXeeycjIqPVv9JX15ZdfGknmnXfeMRs3bgwdu3fvDr3Gzb4Kp91u9debb75p/vjHP5rXXnvNSDKzZs067Hn37t1rTjjhBHPOOeeYZcuWmTfffNO0a9eu1u+hE3/fPROo9O/f34wZMyb0uLq62nTp0sVMmjSp1uuKi4vD/uHcdtttpmfPnrWe+9WvfmUGDx7c4Ovu7+mnnzZt2rQxVVVVoeduv/12071799Djyy67zAwZMqTW1w0YMMCMGjXqsO0+nHjqq/0NHz68zkAlnGtHKl77qsYvfvELc9VVV4Ue876q3+9+9zszaNCg0GP66uDzX3HFFea55547KFChr6yaQGXZsmX1vsbNvjImdv21v3ADlTfffNP4fD6zadOm0HPPPPOMSU9PD90fnfj77ompn927d2vp0qU655xzQs/5fD6dc845WrhwYdjnycvL04gRI0KPFy5cWOuckjR48ODQOcO97ogRI5SXl1frvGeccYZSU1NrnXfNmjX69ttvw7p2pOKtr8JBX9WvoqJCbdu2DfvakYr3viotLdXs2bN15plnhn3tSMVjX82dO1czZ87UU089VWdb6KvaLrzwQnXo0EGDBg3SG2+8Uevf3OqrhrT7cBraX+GaMGGCjj766Frn7dWrlzp27FjrvJWVlVq5cqVj1/ZEoPL111+rurq61jcrSR07dtSmTZvCPk/Xrl3VuXPn0ONNmzbVec7Kykr98MMPYV+3c+fO6tq162HPW/Nvh3pNQ76fusRbX4XjcNeOVLz31d/+9jctWbJEV1111WGvnazvq1NPPVXNmjVTbm6uTj/9dN1zzz2HvXay9dW2bds0YsQITZ8+vd5N5+grq1WrVnr44Yc1c+ZM/etf/9KgQYM0dOjQWsGKW30lxa6/wtWuXTsde+yxhz1vzb85dW1P757cUC+88IIr5500aZIr540l+ip8seir4uJiXXXVVZo6dap69uzpyvXdEO2+euWVV7R9+3Z98sknuvXWW/XQQw/ptttuc6UNTotWX1177bX6zW9+ozPOOMOV60VDtPqqXbt2uuWWW0KP+/Xrpw0bNujBBx/UhRde6Eob3OBWf11//fW6/vrrXTn3oXgiUGnXrp38fr82b95c6/nNmzerU6dOEZ+3U6dOdZ4zPT1dzZs3l9/vj+i69Z235t8O9ZrGfD9S/PWVE9eOVLz21XvvvacLLrhAjz76qH7729+Gde1kfV9lZWVJknr06KHq6mqNHDlSv//97+X3++mrH82dO1dvvPGGHnroIUmSMUbBYFBNmjRRYWGhrr76avrqEAYMGKA5c+Yc9tqN7Sspdv3VmPMeuJIq3HthQ67tiamf1NRUnXzyyXr33XdDzwWDQb377rsaOHBgxOcdOHBgrXNK0pw5c0LnjPS6AwcO1Pz582sthZwzZ466d++uNm3ahHXtSMVbXzlx7UjFY1/NmzdPQ4YM0f3336+RI0c2+NqRise+OlAwGNSePXsUDAbDunak4q2vFi5cqOXLl4eOe+65R61bt9by5ct18cUXh3XtSMVbX9Vl+fLltaZR3OorKXb91ZjzrlixQlu2bKl13vT0dPXo0cO5a4edduuyGTNmmLS0NDN9+nSzatUqM3LkSHPEEUeEsok3btxoli1bZqZOnWokmfnz55tly5aZbdu2hc5x5ZVXmrFjx4Ye1yyLuvXWW83q1avNU089VecStkNd1xhjxo4da6688srQ4++++8507NjRXHnlleazzz4zM2bMMC1atDhoeXKTJk3MQw89ZFavXm3Gjx/v6HK/eOkrY4xZuXKlWbZsmbngggtMXl6eWbZsWa2s+nCunQx9NXfuXNOiRQszbty4Wksj928L7yvrxRdfNK+88opZtWqVWbt2rXnllVdMly5dzLBhw+irOn4H91fXqh/6ypo+fbp56aWXzOrVq83q1avNxIkTjc/nM88++2xU+iqW/bV9+/bQ32ZJ5pFHHjHLli0z69atC73miSeeMPn5+aHHNcuTf/azn5nly5eb2bNnm/bt29e5PLkxf989E6gYYzuha9euJjU11fTv398sWrQo9G/jx483kg46nnvuudBrzjzzTDN8+PBa5ywuLja9e/c2qamp5phjjqn1+nCua4xdVnvmmWfWeu6TTz4xgwYNMmlpaeaoo44ykydPPui8f/vb38xPfvITk5qaanr27Gn+9a9/NbhP6hNPfZWdnV1nexp67UjFS18NHz68zrYc2J+8r+wf85NOOsm0atXKtGzZ0vTo0cPcd9995ocffqj1dfTVweoKVIyhr4yxgcrxxx9vWrRoYdLT003//v3NzJkzDzqvm311uHa71V81y50PPPY/z/jx4012dnatrysrKzPnnXeead68uWnXrp35/e9/b/bs2dOgax9OijHGhD/+AgAAED2eyFEBAACoC4EKAADwLAIVAADgWQQqAADAswhUAACAZxGoAAAAzyJQAQAAnkWgAgAAPItABQAAeBaBCgAA8CwCFQAA4FkEKgAAwLP+P9+HzjIO+lIZAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Generate data\n", + "true_tau = 1\n", + "\n", + "t = (TIME-TIME[0]).total_seconds()\n", + "\n", + "b = np.sin(t/20) + 2 * np.cos(t/12)+2\n", + "\n", + "y = b + norm.rvs(loc=0, scale=np.sqrt(1/true_tau), size=n_time)\n", + "\n", + "plt.plot(TIME, y, 'k.')\n", + "plt.plot(TIME, b, 'r-')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the model\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We create the model from distribution blocks available in the openmcmc code. `ScaledMatrix` parameter objects are used for the Normal distribution matrices." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "tau_predictor = ScaledMatrix(matrix='P_tau', scalar='tau')\n", + "lambda_predictor = ScaledMatrix(matrix='P_lambda', scalar='lambda')\n", + "\n", + "mdl = Model(\n", + " [\n", + " Normal(\"y\", mean=\"b\", precision=tau_predictor),\n", + " Normal(\"b\", mean=\"mu\", precision=lambda_predictor),\n", + " Gamma(\"lambda\", shape=\"a_lam\", rate=\"b_lam\"),\n", + " Gamma(\"tau\", shape=\"a_tau\", rate=\"b_tau\"),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup the initial state" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All of the model components are assigned to the initial state below." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "initial_state = {\n", + " \"y\": y,\n", + " \"b\": y,\n", + " \"mu\": np.zeros(n_time),\n", + " \"lambda\": 100,\n", + " \"P_lambda\": P_lambda,\n", + " \"a_lam\": 10,\n", + " \"b_lam\": 1,\n", + " \"tau\": 1,\n", + " \"P_tau\": sparse.csc_matrix(np.eye(n_time)),\n", + " \"a_tau\": 1,\n", + " \"b_tau\": 1,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup the samplers and run MCMC\n", + "\n", + "In this case we estimate 3 parameters:\n", + "1. the smoother $b$\n", + "2. the level of smoothness $\\lambda$\n", + "3. the precision $\\tau$ of the measurement noise\n", + "\n", + "Because of the use of conjugate normal-gamma pairs of distributions, the full MCMC routine is a Gibbs sampler with exact samplers for the individual conditional distributions." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 700/700 [00:08<00:00, 78.30it/s]\n" + ] + } + ], + "source": [ + "\n", + "samplers = [\n", + " NormalNormal(\"b\", mdl),\n", + " NormalGamma(\"lambda\", mdl),\n", + " NormalGamma(\"tau\", mdl),\n", + "]\n", + "\n", + "M = MCMC(initial_state, samplers, model=mdl, n_burn=200, n_iter=500)\n", + "M.run_mcmc()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The results of the MCMC are plotted below." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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+rW1tbB7926ora2Fp59+GpYtW5YUWwRBBAtyqxEEEQq6du0KGRkZkJ2dneLWmj17NowdOzYZh1RUVARz586Fq666ChYsWADt27cHAIARI0bAlClTUs75n//5n8n/P/vss6GiogKWL18O9913H3To0AE6d+4M7dq1E7rRnn32WTh+/Dg888wz0KlTJwAA+OMf/wjXX389PPLII9CzZ08AAOjSpQv88Y9/hIyMDDj//PNh9OjR8Prrr8MvfvELR+qHIAjnIHFEEESoeffdd+G9996D//t//2/yO0SERCIBe/bsgW9961sAAHDJJZe0+u2KFStg7ty5sHv3bjh69Ch8/fXXkJOTo3T9nTt3wuDBg5PCCADgiiuugEQiAbt27UqKo4EDB0JGRkbymF69esGOHTuUrkUQhDeQOCIIItQcPXoUysvL4e677271t759+yb/Xy9eAAA2bNgAY8eOhVmzZkFJSQnk5ubC8uXL4dFHH3WlnGeccUbKvyORCCQSCVeuRRCEPUgcEQQRGjIzM6GlpSXlu4svvhg+/PBD6N+/v9K53n77bSgsLIRp06Ylv/v0009Nr2fkW9/6FixbtgyOHTuWFGBvvfUWRKNRGDBggFKZCIIIBhSQTRBEaDj77LPhzTffhM8++ww+//xzADi94+ztt9+GCRMmwPbt26GhoQFWr17dKiDaSFFREezbtw+WL18Ou3fvhrlz58LKlStbXW/Pnj2wfft2+Pzzz+HEiROtzjN27Fho3749lJaWwvvvvw+1tbVw1113wU9+8pOkS40giHBB4oggiNDw0EMPwd69e+Hcc8+F7t27AwDAhRdeCHV1dfDxxx/DlVdeCUOGDIEZM2ZA7969hee64YYbYNKkSTBhwgS46KKL4O23307uYtO45ZZb4JprroHvfe970L17d3juuedanadjx47w6quvwhdffAHf+c534NZbb4WRI0fCH//4R+dunCAIT6EM2QRBEARBEDrIckQQBEEQBKGDxBFBEARBEIQOEkcEQRAEQRA6SBwRBEEQBEHoIHFEEARBEAShg8QRQRAEQRCEDhJHBEEQBEEQOkgcEQRBEARB6CBxRBAEQRAEoYPEEUEQBEEQhA4SRwRBEARBEDr+Pzggjv3Lue6eAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "qb = np.quantile(M.store[\"b\"], [0.05,0.5,0.975], axis=1)\n", + "\n", + "plt.figure()\n", + "plt.plot(TIME, y, \".k\", label=\"data\")\n", + "plt.plot(TIME, b, \"-.b\", label=\"truth\")\n", + "plt.plot(TIME, qb[[0,2],:].T, \"--r\", label=\"posterior quantiles\")\n", + "plt.plot(TIME, qb[1,:], \"-r\", label=\"posterior median\")\n", + "plt.ylabel(\"Background\")\n", + "plt.xlabel(\"Time\")\n", + "plt.legend()\n", + "\n", + "plt.figure()\n", + "plt.subplot(3,1,1)\n", + "plt.plot(M.store['lambda'].flatten(),'k.')\n", + "plt.xlabel('iteration')\n", + "plt.ylabel('lambda')\n", + "\n", + "plt.subplot(3,1,2)\n", + "plt.plot(M.store['tau'].flatten(),'k.')\n", + "plt.hlines(true_tau,0, M.n_iter,'r')\n", + "plt.xlabel('iteration')\n", + "plt.ylabel('tau')\n", + "\n", + "plt.subplot(3,1,3)\n", + "plt.plot(M.store['log_post'],'k.')\n", + "plt.xlabel('iteration')\n", + "plt.ylabel('logposterior')\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/4_GMRF_smoother.ipynb.license b/examples/4_GMRF_smoother.ipynb.license new file mode 100644 index 0000000..e25c5d4 --- /dev/null +++ b/examples/4_GMRF_smoother.ipynb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..86c2250 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,113 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +--- +# Project information +site_name: openMCMC +site_author: openMCMC +site_description: >- + This repository contains the Markov Chain Monte Carlo implementations we call openMCMC. It is part of the pyELQ project. +# Repository +repo_name: openMCMC +repo_url: https://github.com/sede-open/general_MCMC +edit_uri: "" + +docs_dir: docs + +# Configuration +theme: + name: material + # Default values, taken from mkdocs_theme.yml + language: en + features: + - content.code.annotate + - content.code.copy + - content.code.select + - content.tabs.link + - content.tooltips + #- navigation.expand + - navigation.indexes + - navigation.instant +# - navigation.sections + - navigation.tabs + # - navigation.tabs.sticky + - navigation.top + # - navigation.tracking + - search.highlight + - search.share + - search.suggest + - toc.follow + palette: + - scheme: default + primary: custom + accent: custom + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: custom + accent: custom + toggle: + icon: material/brightness-4 + name: Switch to light mode + font: + text: Roboto + code: Roboto Mono + icon: + tag: + pipelines: fontawesome/solid/timeline + +extra: + generator: false + tags: + Pipelines: pipelines + +plugins: + - search + - autorefs + - mkdocstrings: + handlers: + python: + paths: [src] + options: + members_order: source + docstring_style: "google" + - tags + +watch: + - src/openmcmc + +markdown_extensions: + - attr_list + - md_in_html + - meta + - admonition + - pymdownx.details + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg # Page tree + - pymdownx.snippets: + url_download: true + +nav: + - Home: index.md + - openMCMC: + - Distribution: + - Distribution: openmcmc/distribution/distribution.md + - Location Scale: openmcmc/distribution/location_scale.md + - GMRF: openmcmc/gmrf.md + - MCMC: openmcmc/mcmc.md + - Model: openmcmc/model.md + - Parameter: openmcmc/parameter.md + - Sampler: + - Sampler: openmcmc/sampler/sampler.md + - Metropolis-Hastings: openmcmc/sampler/metropolis_hastings.md + - Reversible Jump: openmcmc/sampler/reversible_jump.md diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5287a9d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,78 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +name = "openmcmc" +version = "1.0.0" +description = "openMCMC tools" +authors = ["Bas van de Kerkhof", "Matthew Jones", "Ross Towe", "David Randell"] +homepage = "https://github.com/sede-open/openMCMC" +repository = "https://github.com/sede-open/openMCMC" +documentation = "https://github.com/sede-open/openMCMC" +readme = "README.md" +license = "Apache-2.0" +keywords = ["Markov Chain Monte Carlo", "MCMC"] + +[tool.poetry.dependencies] +python = "~3.11" +pandas = ">=2.1.4" +numpy = ">=1.26.2" +scipy = ">=1.11.4" +tqdm = ">=4.66.1" +matplotlib = {version = ">=3.8.2", optional = true } +pytictoc = {version = ">=1.5.3", optional = true } + +[tool.poetry.extras] +extras = ["matplotlib", "pytictoc"] + +[tool.poetry.group.contributor] +optional = true + +[tool.poetry.group.contributor.dependencies] +black = ">=23.12.1" +isort = ">=5.13.2" +pydocstyle = ">=6.3.0" +pylint = ">=3.0.3" +pytest = ">=7.4.4" +pytest-cov = ">=4.1.0" +pytest-cases = ">=3.8.1" +mkdocs-material = ">=9.5.7" +mkdocstrings-python = ">=1.8.0" + +[tool.pytest.ini_options] +addopts = "--cov=openmcmc --cov-fail-under=90" +testpaths = [ + "tests", +] + +[tool.coverage.run] +relative_files = true +source = ["src/"] + +[tool.pylint] +fail-under=9.0 +max-line-length=120 +py-version=3.11 + +[tool.black] +line-length = 120 +target-version = ['py311'] + +[tool.pydocstyle] +convention = "google" +add-ignore = ["D105", "D107"] + +[tool.isort] +profile = "black" + +[tool.docformatter] +recursive = true +wrap-summaries = 120 +wrap-descriptions = 120 +blank = true +black = true diff --git a/src/openmcmc/__init__.py b/src/openmcmc/__init__.py new file mode 100644 index 0000000..83b9ea4 --- /dev/null +++ b/src/openmcmc/__init__.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Main MCMC module.""" + +__all__ = [ + "distribution", + "sampler", + "gmrf", + "mcmc", + "model", + "parameter", +] diff --git a/src/openmcmc/distribution/__init__.py b/src/openmcmc/distribution/__init__.py new file mode 100644 index 0000000..dc1eabc --- /dev/null +++ b/src/openmcmc/distribution/__init__.py @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Distribution module.""" + +__all__ = [ + "distribution", + "location_scale", +] diff --git a/src/openmcmc/distribution/distribution.py b/src/openmcmc/distribution/distribution.py new file mode 100644 index 0000000..fea1886 --- /dev/null +++ b/src/openmcmc/distribution/distribution.py @@ -0,0 +1,519 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Collection of distributions for use with openMCMC code. + +General assumptions about code functionality: + - The first dimension of a parameter array is assumed to represent the dimensionality of the parameter vector; the + second dimension is assumed to represent independent realizations of the parameter set. For example: an array + with shape=(d, n) would be assumed to hold n replicates of a d-dimensional parameter vector. + - self.response is a string containing the name of the response parameter for the distribution. For example, when + self.response="y", all functions within the class will perform calculations using the value stored in + state["y"]. + +""" + +from abc import ABC, abstractmethod +from copy import deepcopy +from dataclasses import dataclass +from typing import Tuple, Union + +import numpy as np +from scipy import sparse, stats + +from openmcmc.parameter import Identity, LinearCombination, MixtureParameterVector + + +@dataclass +class Distribution(ABC): + """Abstract superclass for handling distribution objects. + + Attributes: + response (str): specifies the name of the response variable of the distribution. + + """ + + response: str + + @abstractmethod + def log_p(self, state: dict, by_observation: bool = False) -> Union[np.ndarray, float]: + """Compute the log of the probability density (for current parameter settings). + + Args: + state (dict): dictionary object containing the current state information. state[distribution.response] + is expected to be p x n where: p is the number of responses; n is the number of independent + replicates/observations. + by_observation (bool, optional): If True, the log-likelihood is returned for each of the p responses of + the distribution separately. Defaults to False. + + Returns: + (Union[np.ndarray, float]):: POSITIVE log-density evaluated using the supplied state dictionary. + + """ + + @abstractmethod + def rvs(self, state: dict, n: int = 1) -> np.ndarray: + """Generate random samples from the distribution. + + Args: + state (dict): dictionary object containing the current state information. + n (int, optional): specifies the number of replicate samples required. Defaults to 1. + + Returns: + (np.ndarray):: random variables generated from distribution returned as p x n where p is the + dimensionality of the response. + + """ + + @property + @abstractmethod + def _dist_params(self) -> list: + """Get list of parameter labels across all Parameter objects in distribution (EXCLUDING the response). + + Returns: + (list): list of parameter labels. + + """ + + @property + def param_list(self) -> list: + """Get list of all parameter labels in model (INCLUDING the response). + + Returns: + (list): list of parameter labels + + """ + lst = [self.response] + self._dist_params + return lst + + def grad_log_p( + self, state: dict, param: str, hessian_required: bool = True + ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: + """Generate vector of derivatives of the log-pdf with respect to a given parameter, and if required, also generate the Hessian. + + Function only defined for scalar- and vector-valued parameters param. If hessian_required=True, this function + returns a tuple of (gradient, Hessian). If hessian_required=False, this function returns a np.ndarray (just + the gradient of the log-density). + + As a default, the individual gradients are computed by finite-differencing the log_p function defined for the + distribution. Where analytical forms for the gradient exist, these will be defined in distribution-specific + subclasses. + + Args: + state (dict): current state information. + param (str): name of the parameter for which we compute derivatives. + hessian_required (bool): flag for whether the Hessian should be calculated and supplied as an output. + + Returns: + (Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]): if hessian_required=True, then a tuple of (gradient, + hessian) is returned. If hessian_required=False, then just a gradient vector is returned. The returned + values are as follows: + grad (np.ndarray): vector gradients of the POSITIVE log-pdf with respect to param. shape=(d, 1), where + d is the dimensionality of param. + hessian (np.ndarray): array of NEGATIVE second derivatives of the log-pdf with respect to param. + shape=(d, d), where d is the dimensionality of param. + + """ + grad = self.grad_log_p_diff(state=state, param=param) + if hessian_required: + hessian = self.hessian_log_p_diff(state=state, param=param) + return grad, hessian + return grad + + def grad_log_p_diff(self, state: dict, param: str, step_size: float = 1e-4) -> np.ndarray: + """Compute vector of derivatives of the POSITIVE log-pdf (with respect to param) using central differences. + + Args: + state (dict): current state information. + param (str): name of the parameter for which we compute derivatives. + step_size (float, optional): step size to use for the finite difference derivatives. Defaults to 1e-4. + + Returns: + (np.ndarray):: vector of log-pdf gradients with respect to param. shape=(d, 1), where d is the dimensionality + of param. + + """ + n_param = np.prod(state[param].shape) + grad_param = np.full(shape=n_param, fill_value=np.nan) + for k in range(n_param): + state_plus = deepcopy(state) + state_minus = deepcopy(state) + + if sparse.issparse(state[param]): + m, n = state[param].shape + step_temp = sparse.csr_array( + (np.array([step_size / 2]), np.unravel_index(np.array([k]), (m, n))), shape=(m, n) + ) + state_plus[param] = state_plus[param] + step_temp + state_minus[param] = state_minus[param] - step_temp + else: + state_plus[param][np.unravel_index(k, state[param].shape)] += step_size / 2 + state_minus[param][np.unravel_index(k, state[param].shape)] += -step_size / 2 + + log_p_plus = self.log_p(state=state_plus) + log_p_minus = self.log_p(state=state_minus) + + grad_param[k] = (log_p_plus - log_p_minus) / step_size + return grad_param.reshape(state[param].shape) + + def hessian_log_p_diff(self, state: dict, param: str, step_size: float = 1e-4) -> np.ndarray: + """Compute Hessian matrix of second derivatives of the NEGATIVE log-pdf (with respect to param) using finite differences. + + Args: + state (dict): current state information. + param (str): name of the parameter for which we compute derivatives. + step_size (float, optional): step size to use for the finite difference derivatives. Defaults to 1e-4 + + Returns: + (np.ndarray):: matrix of log-pdf second derivatives with respect to param. shape=(d, d), where d is the + dimensionality of param. + + """ + n_param = np.prod(state[param].shape) + hess_param = np.full(shape=(n_param, n_param), fill_value=np.nan) + for k in range(n_param): + state_plus = deepcopy(state) + state_minus = deepcopy(state) + + if sparse.issparse(state[param]): + m, n = state[param].shape + step_temp = sparse.csr_array( + (np.array([step_size / 2]), np.unravel_index(np.array([k]), (m, n))), shape=(m, n) + ) + state_plus[param] = state_plus[param] + step_temp + state_minus[param] = state_minus[param] - step_temp + else: + state_plus[param][np.unravel_index(k, state[param].shape)] += step_size / 2 + state_minus[param][np.unravel_index(k, state[param].shape)] += -step_size / 2 + + grad_p_plus = self.grad_log_p(state_plus, param, hessian_required=False) + grad_p_minus = self.grad_log_p(state_minus, param, hessian_required=False) + + hess_param[:, k] = (grad_p_minus - grad_p_plus).flatten() / step_size + + return hess_param + + +@dataclass +class Gamma(Distribution): + """Gamma distribution class defined using shape and rate convention. + + f(x) = x^(shape-1) * exp(-rate*x) * rate^shape / Gamma(shape) + + Attributes: + shape (Union[str, Identity, LinearCombination, MixtureParameterVector]): Gamma shape parameter. + rate (Union[str, Identity, LinearCombination, MixtureParameterVector]): Gamma rate parameter. + + """ + + shape: Union[str, Identity, LinearCombination, MixtureParameterVector] + rate: Union[str, Identity, LinearCombination, MixtureParameterVector] + + def __post_init__(self): + """Parse any str parameter inputs as Parameter.Identity, and check the parameter types.""" + if isinstance(self.shape, str): + self.shape = Identity(self.shape) + + if not isinstance(self.shape, (Identity, LinearCombination, MixtureParameterVector)): + raise TypeError("shape expected to be one of [Identity, LinearCombination, MixtureParameterVector]") + + if isinstance(self.rate, str): + self.rate = Identity(self.rate) + + if not isinstance(self.rate, (Identity, LinearCombination, MixtureParameterVector)): + raise TypeError("rate expected to be one of [Identity, LinearCombination, MixtureParameterVector]") + + @property + def _dist_params(self) -> list: + """Get list of parameter labels across all Parameter objects in distribution (EXCLUDING the response). + + Returns: + (list): list of parameter labels. + + """ + lst = self.shape.get_param_list() + self.rate.get_param_list() + return lst + + def log_p(self, state: dict, by_observation: bool = False) -> Union[np.ndarray, float]: + """Compute the log of the probability density (for current parameter settings). + + Args: + state (dict): dictionary object containing the current state information. state[distribution.response] + is expected to be p x n where: p is the number of parameters; n is the number of independent + replicates/observations. + by_observation (bool, optional): If True, the log-likelihood is returned for each of the p parameters of + the distribution separately. Defaults to False. + + Returns: + (Union[np.ndarray, float]):: POSITIVE log-density evaluated using the supplied state dictionary. + + """ + log_p = np.sum( + stats.gamma.logpdf(state[self.response], self.shape.predictor(state), scale=1 / self.rate.predictor(state)), + axis=0, + ) + if not by_observation: + log_p = np.sum(log_p) + return log_p + + def rvs(self, state, n: int = 1) -> np.ndarray: + """Generate random samples from the Gamma distribution. + + Args: + state (dict): dictionary object containing the current state information. + n (int, optional): specifies the number of replicate samples required. Defaults to 1. + + Returns: + (np.ndarray):: random variables generated from distribution returned as p x n where p is the + dimensionality of the response. + + """ + shape = self.shape.predictor(state) + rate = self.rate.predictor(state) + p = max(shape.shape[0], rate.shape[0]) + return stats.gamma.rvs(shape, scale=1 / rate, size=(p, n)) + + +@dataclass +class Categorical(Distribution): + """Categorical distribution: equivalent to a single trial of a multinomial distribution. + + A 2-category categorical distribution is equivalent to a Bernoulli distribution. + + The response of this distribution is a category index in {0, 1, 2,..., n_cat}: thus, state[self.response] is + expected to be a np.array with dtype=int. As per other distributions, the expected shape of state[self.response] + is (p, n), where p=dimensionality of response, and n=number of replicates. + + The prior probability parameter is expected to be a np.ndarray with shape=(p, n_cat). + + Attributes: + prob (Identity, str): allocation probability parameter. + + """ + + prob: Union[str, Identity] + + def __post_init__(self): + """Parse any str parameter inputs as Parameter.Identity(), and check the parameter types.""" + if isinstance(self.prob, str): + self.prob = Identity(self.prob) + + if not isinstance(self.prob, Identity): + raise TypeError("prob expected to be Identity") + + @property + def _dist_params(self) -> list: + """Get list of parameter labels across all Parameter objects in distribution (EXCLUDING the response). + + Returns: + (list): list of parameter labels. + + """ + return self.prob.get_param_list() + + def log_p(self, state: dict, by_observation: bool = False) -> np.ndarray: + """Compute the log of the probability density (for current parameter settings). + + Args: + state (dict): dictionary object containing the current state information. state[distribution.response] + is expected to be p x n where: p is the number of responses; n is the number of independent + replicates/observations. + by_observation (bool, optional): If True, the log-likelihood is returned for each of the p responses of + the distribution separately. Defaults to False. + + Returns: + (Union[np.ndarray, float]):: POSITIVE log-density evaluated using the supplied state dictionary. + + """ + n_categories = self.prob.predictor(state).shape[1] + n = state[self.response].shape[1] + + if n > 1: + x = np.atleast_3d(state[self.response]) + x = np.equal(np.transpose(x, (0, 2, 1)), np.atleast_3d(range(n_categories))) + else: + x = state[self.response] == range(n_categories) + + if by_observation: + if n > 1: + prob = np.transpose(np.atleast_3d(self.prob.predictor(state)), (0, 2, 1)) + log_p = stats.multinomial.logpmf(np.transpose(x, (0, 2, 1)), n=1, p=prob) + else: + log_p = stats.multinomial.logpmf(x, n=1, p=self.prob.predictor(state)) + else: + if n > 1: + x = np.sum(x, axis=2) + log_p = stats.multinomial.logpmf(x, n=n, p=self.prob.predictor(state)) + + return np.sum(log_p, axis=0) + + def rvs(self, state, n: int = 1) -> np.ndarray: + """Generate a random sample from the distribution. + + Args: + state (dict): dictionary object containing the current state information + n (int, optional): specifies the number of random variables required. Defaults to 1 + + Returns: + (np.ndarray):: random sample from the categorical distribution. shape=(p, n) + + """ + prob = self.prob.predictor(state) + + d, _ = prob.shape + + cat = np.empty((d, n)) + for i in range(d): + Z = stats.multinomial.rvs(n=1, p=prob[i, :], size=n) + _, cat[i, :] = np.nonzero(Z) + + return cat + + +@dataclass +class Uniform(Distribution): + """Uniform distribution class for a p-dimensional hyper-rectangle. + + Attributes: + domain_response_lower (Union[float, np.ndarray]): shape=(p, 1): lower limits for uniform distribution in each + dimension. Defaults to 0.0. + domain_response_upper (Union[float, np.ndarray]): shape=(p, 1) upper limits for uniform distribution in each + dimension. Defaults to 1.0. + + """ + + domain_response_lower: Union[float, np.ndarray] = 0.0 + domain_response_upper: Union[float, np.ndarray] = 1.0 + + def __post_init__(self): + """Convert any domain limits supplied as floats to np.ndarray.""" + self.domain_response_lower = np.array(self.domain_response_lower, ndmin=2) + if self.domain_response_lower.shape[0] == 1: + self.domain_response_lower = self.domain_response_lower.T + self.domain_response_upper = np.array(self.domain_response_upper, ndmin=2) + if self.domain_response_upper.shape[0] == 1: + self.domain_response_upper = self.domain_response_upper.T + + @property + def _dist_params(self) -> list: + """Uniform distribution doesn't have parameters, so return an empty list.""" + return [] + + def domain_range(self, state) -> np.ndarray: + """Get the domain range (upper-lower) from domain_limits. + + Args: + state (dict): dictionary with current state information. + + Returns: + (np.ndarray):: domain range. shape=(p, 1). + + """ + d = state[self.response].shape[0] + domain_range = self.domain_response_upper - self.domain_response_lower + if domain_range.size == 1: + domain_range = np.ones((d, 1)) * domain_range + return domain_range + + def log_p(self, state: dict, by_observation: bool = False) -> Union[np.ndarray, float]: + """Compute the log of the probability density (for current parameter settings). + + Args: + state (dict): dictionary object containing the current state information. state[distribution.response] + is expected to be p x n where: p is the number of responses; n is the number of independent + replicates/observations. + by_observation (bool, optional): If True, the log-likelihood is returned for each of the p responses of + the distribution separately. Defaults to False. + + Returns: + (Union[np.ndarray, float]):: POSITIVE log-density evaluated using the supplied state dictionary. + + """ + n = state[self.response].shape[1] + log_p = -np.sum(np.log(self.domain_range(state))) + if by_observation: + log_p = np.ones(n) * log_p + else: + log_p = n * log_p + return log_p + + def rvs(self, state, n: int = 1) -> np.ndarray: + """Generate random samples from the distribution. + + Args: + state (dict): dictionary object containing the current state information. + n (int, optional): specifies the number of replicate samples required. Defaults to 1. + + Returns: + (np.ndarray):: random variables generated from distribution returned as p x n where p is the + dimensionality of the response. + + """ + standard_unif = np.random.rand(state[self.response].shape[0], n) + return self.domain_response_lower + self.domain_range(state) * standard_unif + + +@dataclass +class Poisson(Distribution): + """Poisson distribution for count data. + + Attributes: + rate (Union[str, Identity, LinearCombination, MixtureParameterVector]): Poisson rate parameter. + + """ + + rate: Union[str, Identity, LinearCombination, MixtureParameterVector] + + def __post_init__(self): + """Parse any str parameter inputs as Parameter.Identity, and check the parameter types.""" + if isinstance(self.rate, str): + self.rate = Identity(self.rate) + + if not isinstance(self.rate, (Identity, LinearCombination, MixtureParameterVector)): + raise TypeError("rate expected to be one of [Identity, LinearCombination, MixtureParameterVector]") + + @property + def _dist_params(self) -> list: + """Get list of parameter labels across all Parameter objects in distribution (EXCLUDING the response). + + Returns: + (list): list of parameter labels. + + """ + return self.rate.get_param_list() + + def log_p(self, state: dict, by_observation: bool = False) -> np.ndarray: + """Compute the log of the probability density (for current parameter settings). + + Args: + state (dict): dictionary object containing the current state information. state[distribution.response] + is expected to be p x n where: p is the number of parameters; n is the number of independent + replicates/observations. + by_observation (bool, optional): If True, the log-likelihood is returned for each of the p parameters of + the distribution separately. Defaults to False. + + Returns: + (Union[np.ndarray, float]): POSITIVE log-density evaluated using the supplied state dictionary. + + """ + rate = self.rate.predictor(state) + logpmf = np.sum(stats.poisson.logpmf(state[self.response], rate), axis=0) + if not by_observation: + logpmf = np.sum(logpmf) + return logpmf + + def rvs(self, state: dict, n: int = 1) -> np.ndarray: + """Generate random samples from the Poisson distribution. + + Args: + state (dict): dictionary object containing the current state information. + n (int, optional): specifies the number of replicate samples required. Defaults to 1. + + Returns: + (np.ndarray):: random variables generated from distribution returned as p x n where p is the + dimensionality of the response. + + """ + rate = self.rate.predictor(state) + return stats.poisson.rvs(mu=rate, size=(rate.shape[0], n)) diff --git a/src/openmcmc/distribution/location_scale.py b/src/openmcmc/distribution/location_scale.py new file mode 100644 index 0000000..d00b864 --- /dev/null +++ b/src/openmcmc/distribution/location_scale.py @@ -0,0 +1,417 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +# -*- coding: utf-8 -*- +"""LocationScale module. + +This module provides a class definition of the LocationScale class an abstract base class for distributions defined by a +mean and a precision such as the Normal and Lognormal. + +""" + +from abc import ABC +from dataclasses import dataclass +from typing import Tuple, Union + +import numpy as np +from scipy import sparse + +from openmcmc import gmrf +from openmcmc.distribution.distribution import Distribution +from openmcmc.parameter import ( + Identity, + LinearCombination, + MixtureParameterMatrix, + MixtureParameterVector, + ScaledMatrix, +) + + +@dataclass +class LocationScale(Distribution, ABC): + """Abstract base class for distributions defined by a mean and a precision such as the Normal and Lognormal. + + Attributes: + mean (Union[str, Identity, LinearCombination, MixtureParameterVector]): mean parameter (of class Parameter). + precision (Union[str, Identity, ScaledMatrix, MixtureParameterMatrix]): precision parameter (of class Parameter). + + """ + + mean: Union[str, Identity, LinearCombination, MixtureParameterVector] + precision: Union[str, Identity, ScaledMatrix, MixtureParameterMatrix] + + @property + def _dist_params(self) -> list: + """Return the full list of state elements used in the mean and precision parameters.""" + lst = self.mean.get_param_list() + self.precision.get_param_list() + return lst + + def __post_init__(self): + """Parse any str parameter inputs as Parameter classes.""" + if isinstance(self.mean, str): + self.mean = Identity(self.mean) + + if not isinstance(self.mean, (Identity, LinearCombination, MixtureParameterVector)): + raise TypeError("mean expected to be one of [Identity, LinearCombination, MixtureParameterVector]") + + if isinstance(self.precision, str): + self.precision = Identity(self.precision) + + if not isinstance(self.precision, (Identity, ScaledMatrix, MixtureParameterMatrix)): + raise TypeError("precision expected to be one of [Identity, ScaledMatrix, MixtureParameterMatrix]") + + +class NullDistribution(LocationScale): + """Null distribution, which returns 0 for the log-likelihood, a zero vector for the gradient and a zero matrix for the Hessian. + + Used in prior recovery testing for reversible jump sampler. + + """ + + def log_p(self, state: dict, by_observation: bool = False) -> float: + """Null log-density function: returns 0. + + Args: + state (dict): dictionary object containing the current state information. state[distribution.response] + is expected to be p x n where: p is the number of responses; n is the number of independent + replicates/observations. + by_observation (bool, optional): If True, the log-likelihood is returned for each of the p responses of + the distribution separately. Defaults to False. + + Returns: + (float): 0.0. + + """ + return 0.0 + + def grad_log_p( + self, state: dict, param: str, hessian_required: bool = True + ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: + """Null gradient function returning an all-zero vector for the gradient, and an all-zero matrix for the Hessian. + + Args: + state (dict): current state information. + param (str): name of the parameter for which we compute derivatives. + hessian_required (bool): flag for whether the Hessian should be calculated and supplied as an output. + + Returns: + (Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]): if hessian_required=True, then a tuple of (gradient, + hessian) is returned. If hessian_required=False, then just a gradient vector is returned. The returned + values are as follows: + grad (np.ndarray): all-zero vector. shape=(d, 1), where d is the dimensionality of param. + hessian (np.ndarray): all-zero matrix. shape=(d, d), where d is the dimensionality of param. + + """ + if hessian_required: + return np.zeros(state[param].shape), np.zeros((state[param].shape[0], state[param].shape[0])) + + return np.zeros(state[param].shape) + + def rvs(self, state: dict, n: int = 1) -> None: + """Null random sampling function. + + Args: + state (dict): dictionary object containing the current state information. + n (int, optional): specifies the number of replicate samples required. Defaults to 1. + + Returns: + (None): simply returns None value. + + """ + return None + + +@dataclass +class Normal(LocationScale): + """Multivariate normal distribution class. + + Supports both standard multivariate normal and truncated normal distribution cases. By default, no truncation is + assumed. To truncate the distribution, one or both of self.domain_response_lower or self.domain_response_upper must + be specified. + + Attributes: + domain_response_lower (np.array, optional): check lower bound domain to implement truncated sampling. Defaults + to None. + domain_response_upper (np.array, optional): check upper bound domain to implement truncated sampling. Defaults + to None. + + """ + + domain_response_lower: np.ndarray = None + domain_response_upper: np.ndarray = None + + def log_p(self, state: dict, by_observation: bool = False) -> Union[np.ndarray, float]: + """Compute the log of the probability density for a given state. + + NOTE: This function simply computes the non-truncated Gaussian density: i.e. the extra normalization for the + truncation is NOT accounted for. Relative densities (differences of log-probabilities) are still valid when + comparing different response parameter values (with fixed mean and precision parameter values). Comparisons + for different mean or precision parameters are not valid, since such changes would affect the normalization. + + Args: + state (dict): dictionary object containing the current parameter information. + by_observation (bool, optional): indicates whether log-density should be computed for each individual + response in the distribution. Defaults to False (i.e. the overall log-density is computed). + + Returns: + (Union[np.ndarray, float]): log-density computed using the values in state. + + """ + Q = self.precision.predictor(state) + mu = self.mean.predictor(state) + if self.check_domain_response(state): + return -np.inf + log_p = gmrf.multivariate_normal_pdf(x=state[self.response], mu=mu, Q=Q, by_observation=by_observation) + return log_p + + def check_domain_response(self, state: dict) -> bool: + """Checks whether the distributional response lies OUTSIDE the defined limits. + + Returns True if the current value of self.response in the supplied state lies OUTSIDE the stated domain; + returns False otherwise. + + Args: + state (dict): dictionary object containing the current parameter information. + + Returns: + (bool): True when the response lies OUTSIDE the valid response domain; False when it lies INSIDE. + + """ + if self.domain_response_lower is not None: + if np.any(state[self.response] < self.domain_response_lower): + return True + if self.domain_response_upper is not None: + if np.any(state[self.response] > self.domain_response_upper): + return True + return False + + def grad_log_p( + self, state: dict, param: str, hessian_required: bool = True + ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: + """Gradient and Hessian of the log-Gaussian density, with respect to a given parameter. + + See also distribution.grad_log_p() for more information. + + Handles three possibilities: + 1) param is the response of the distribution, in which case the standard gradient of the log-density is + returned. + 2) param is a parameter used in the computation of the mean (through a parameter object) and not in the + computation of the precision, in which case the gradient is computed through application of the chain + rule. Note that the Hessian calculated in this case is only valid if the dependence of self.mean on + param is linear. + 3) neither of the above conditions is True, in which case the default finite-difference gradient is + calculated (using self.grad_log_p_diff() and self.hessian_log_p_diff()). Note that as per those + docstrings, it is only possible to compute gradients with respect to scalar or vector parameters. + + Args: + state (dict): current state information. + param (str): name of the parameter for which we compute derivatives. + hessian_required (bool): flag for whether the Hessian should be calculated and supplied as an output. + + Returns: + (Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]): if hessian_required=True, then a tuple of (gradient, + hessian) is returned. If hessian_required=False, then just a gradient vector is returned. The returned + values are as follows: + grad (np.ndarray): vector gradients of the POSITIVE log-pdf with respect to param. shape=(n_param, 1) + hessian (np.ndarray): array of NEGATIVE second derivatives of the log-pdf with respect to param. + shape=(n_param, n_param) + + """ + if param in self.response: + Q = self.precision.predictor(state) + r = state[self.response] - self.mean.predictor(state) + grad = -Q @ r + if hessian_required: + hessian = Q + if state[param].shape[1] > 1 and sparse.issparse(Q): + hessian = sparse.kron(Q, sparse.eye(state[param].shape[1])) + elif state[param].shape[1] > 1: + hessian = np.kron(Q, np.eye(state[param].shape[1])) + return grad, hessian + + elif param in self.mean.get_grad_param_list() and param not in self.precision.get_grad_param_list(): + Q = self.precision.predictor(state) + r = np.sum(state[self.response] - self.mean.predictor(state), axis=1, keepdims=True) + grad_param = self.mean.grad(state, param) + grad_times_prec = grad_param @ Q + grad = grad_times_prec @ r + if hessian_required: + hessian = state[self.response].shape[1] * grad_times_prec @ grad_param.T + return grad, hessian + + else: + grad = self.grad_log_p_diff(state, param) + if hessian_required: + hessian = self.hessian_log_p_diff(state, param) + return grad, hessian + + return grad + + def rvs(self, state: dict, n: int = 1) -> np.ndarray: + """Generate random samples from the multivariate Gaussian distribution. + + Args: + state (dict): dictionary object containing the current state information. + n (int, optional): specifies the number of replicate samples required. Defaults to 1. + + Returns: + (np.ndarray): random variables generated from distribution returned as p x n where p is the + dimensionality of the response. + + """ + mean = self.mean.predictor(state) + precision = self.precision.predictor(state) + + if self.domain_response_lower is None and self.domain_response_upper is None: + return gmrf.sample_normal(mu=mean, Q=precision, n=n) + + return gmrf.sample_truncated_normal( + mu=mean, Q=precision, lower=self.domain_response_lower, upper=self.domain_response_upper, n=n + ) + + +@dataclass +class LogNormal(LocationScale): + """Multivariate log-normal distribution class.""" + + def log_p(self, state: dict, by_observation: bool = False) -> np.ndarray: + """Compute the log of the probability density (for current parameter settings). + + Args: + state (dict): dictionary object containing the current state information. state[distribution.response] + is expected to be p x n where: p is the number of responses; n is the number of independent + replicates/observations. + by_observation (bool, optional): If True, the log-likelihood is returned for each of the p responses of + the distribution separately. Defaults to False. + + Returns: + (Union[np.ndarray, float]): POSITIVE log-density evaluated using the supplied state dictionary. + + """ + Q = self.precision.predictor(state) + mu = self.mean.predictor(state) + log_p = gmrf.multivariate_normal_pdf(x=np.log(state[self.response]), mu=mu, Q=Q, by_observation=True) - np.sum( + np.log(state[self.response]), axis=0 + ) + if not by_observation: + log_p = np.sum(log_p) + return log_p + + def grad_log_p( + self, state: dict, param: str, hessian_required: bool = True + ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: + """Generate vector of derivatives of the log-pdf with respect to a given parameter, and if required, also generate the Hessian. + + See also distribution.grad_log_p() for more information. + + Handles 3 possibilities: + 1) param is the response of the distribution, in which case the standard gradient of the log-density is + returned. + 2) param is a parameter used in the computation of the mean (through a parameter object) and not in the + computation of the precision, in which case the gradient is computed through application of the chain + rule. Note that the Hessian calculated in this case is only valid if the dependence of self.mean on + param is linear. + 3) neither of the above conditions is True, in which case the default finite-difference gradient is + calculated (using self.grad_log_p_diff() and self.hessian_log_p_diff()). Note that as per those + docstrings, it is only possible to compute gradients with respect to scalar or vector parameters. + + Args: + state (dict): current state information. + param (str): name of the parameter for which we compute derivatives. + hessian_required (bool): flag for whether the Hessian should be calculated and supplied as an output. + + Returns: + (Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]): if hessian_required=True, then a tuple of (gradient, + hessian) is returned. If hessian_required=False, then just a gradient vector is returned. The returned + values are as follows: + grad (np.ndarray): vector gradients of the POSITIVE log-pdf with respect to param. shape=(d, 1), where + d is the dimensionality of param. + hessian (np.ndarray): array of NEGATIVE second derivatives of the log-pdf with respect to param. + shape=(d, d), where d is the dimensionality of param. + + """ + Q = self.precision.predictor(state) + if param in self.response: + r = np.log(state[self.response]) - self.mean.predictor(state) + grad = -(1 / state[self.response]) * (1 + Q @ r) + elif param in self.mean.get_grad_param_list() and param not in self.precision.get_grad_param_list(): + r = np.sum(np.log(state[self.response]) - self.mean.predictor(state), axis=1, keepdims=True) + grad_param = self.mean.grad(state, param) + grad = grad_param @ Q @ r + else: + grad = self.grad_log_p_diff(state, param) + + if hessian_required: + hessian = self.hessian_log_p(state, param) + return grad, hessian + + return grad + + def hessian_log_p(self, state: dict, param: str) -> np.ndarray: + """Compute Hessian of the log-density with respect to a given parameter. + + Handles 3 possibilities: + 1) param is the response of the distribution, in which case the Hessian of the log-density is computed + directly. + 2) param is a parameter used in the computation of the mean (through a parameter object) and not in the + computation of the precision, and the dependence of the mean parameter on param is linear. The chain + rule is used to determine the Hessian. + 3) neither of the above conditions is True, in which case the default finite-difference gradient is + calculated (using self.hessian_log_p_diff()). Note that as per the docstring of + self.hessian_log_p_diff(), it is only possible to compute gradients with respect to scalar or vector + parameters. + + NOTE: sparse implementation of response hessian currently converts Q from sparse. + + Args: + state (dict): contains current state information. + param (str): name of the parameter for which we compute derivatives. + + Returns: + (np.ndarray): Hessian of log-density wrt the specified param. + + """ + if param in self.response: + Q = self.precision.predictor(state) + r = np.log(state[self.response]) - self.mean.predictor(state) + reciprocal = 1 / state[self.response] + + if sparse.issparse(Q): + hess_p = -sparse.diags((np.power(reciprocal, 2) * (1 + Q @ r)).flatten(), offsets=0) + Q = Q.toarray() + else: + hess_p = -np.diagflat(np.power(reciprocal, 2) * (1 + Q @ (r))) + + dim, n = state[self.response].shape + out = np.zeros((n, dim, n, dim)) + diag = np.einsum("ijik->ijk", out) + np.einsum("ik, ij, jk -> kij", reciprocal, Q, reciprocal, out=diag) + out = out.transpose((1, 0, 3, 2)) + out = out.reshape((n * dim, n * dim)) + hess_p = out + hess_p + + elif param in self.mean.get_grad_param_list() and param not in self.precision.get_grad_param_list(): + Q = self.precision.predictor(state) + grad_param = self.mean.grad(state, param) + hess_p = state[self.response].shape[1] * grad_param @ Q @ grad_param.T + else: + hess_p = self.hessian_log_p_diff(state, param) + + return hess_p + + def rvs(self, state: dict, n: int = 1) -> np.ndarray: + """Generate random samples from the multivariate log-Gaussian distribution. + + Args: + state (dict): dictionary object containing the current state information. + n (int, optional): specifies the number of replicate samples required. Defaults to 1. + + Returns: + (np.ndarray): random variables generated from distribution returned as p x n where p is the + dimensionality of the response. + + """ + mean = self.mean.predictor(state) + precision = self.precision.predictor(state) + return np.exp(gmrf.sample_normal(mu=mean, Q=precision, n=n)) diff --git a/src/openmcmc/gmrf.py b/src/openmcmc/gmrf.py new file mode 100644 index 0000000..07592c8 --- /dev/null +++ b/src/openmcmc/gmrf.py @@ -0,0 +1,517 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Gmrf: gaussian Markov Random Field. + +Reference: Rue, Held 2005 Gaussian Markov Random Fields + +Helper functions for sampling and dealing with Multivariate normal distributions +defined by precision matrices which avoid the need for direct inversion and efficiently +reuse cholesky factorisations with sparse implementations + +Notation: +b: conditional mean +Q: precision matrix +L: lower triangle cholesky factorisation of a precision matrix Q + +""" + +from typing import Union + +import numpy as np +from pandas.arrays import DatetimeArray +from scipy import linalg, sparse +from scipy.sparse import linalg as sparse_linalg +from scipy.stats import truncnorm + + +def sample_normal( + mu: np.ndarray, Q: Union[np.ndarray, sparse.csc_matrix] = None, L: np.ndarray = None, n: int = 1 +) -> np.ndarray: + """Generate multivariate random variables from a precision matrix Q using lower cholesky factorisation to get L. + + Note: sparse_linalg.spsolve_triangular compared to sparse_linalg.spsolve, and + it appears to be much slower. + Algorithm 2.4 from Rue, Held 2005 Gaussian Markov Random Fields + Sampling x ~ N(mu , Q^-1) + 1: Compute the lower Cholesky factorisation, Q = L @ L' + 2: Sample z ~ N(0, I) + 3: Solve L' v = z + 4: Compute x = z + v + 5: Return x + Args: + mu (np.array): p x 1 mean + Q (np.array, optional): p x p for precision matrix. Defaults to None. + L (np.array, optional): p x p for lower triangular cholesky factorisation of + precision matrix. Defaults to None. + n (int, optional): number of samples. Defaults to 1. + + Returns: + (np.array): p x n random normal values + + """ + size = [np.size(mu), n] + + z = np.random.standard_normal(size=size) + + if L is None: + L = cholesky(Q) + + return solve(L.T, z).reshape(z.shape) + mu + + +def sample_truncated_normal( + mu: np.ndarray, + Q: Union[np.ndarray, sparse.csc_matrix] = None, + L: np.ndarray = None, + lower: np.ndarray = None, + upper: np.ndarray = None, + n: np.array = 1, + method="Gibbs", +) -> np.ndarray: + """Sample from multivariate truncated normal using either rejection sampling or Gibbs sampling. + + Gibbs sampling should be faster but is generated through a markov chain so samples may not be completely independent + The Markov chain is set up for sampling from gibbs_canonical_truncated_normal which is thinned by every 10 + observations to get more i.i.d. samples + + Rejection sampling will work well for low dimensions and low amounts of truncated but will scale very poorly. + + Args: + mu (np.array): p x 1 mean + Q (np.array, optional): p x p for precision matrix. Defaults to None. + L (np.array, optional): p x p for lower triangular cholesky factorisation of + precision matrix. Defaults to None. + lower (np.array, optional): lower bound + upper (np.array, optional): upper bound + n (int, optional): number of samples. Defaults to 1. + method (str, optional): defines method to use for TN sampling Either 'Gibbs' or 'Rejection' Defaults to 'Gibbs'. + + Returns: + (np.array): p x n random truncated normal values + + """ + if method == "Gibbs": + d = mu.shape[0] + b = Q @ mu + Z = np.empty(shape=(d, n)) + Z[:, 0] = sample_truncated_normal_rejection(mu=mu, Q=Q, L=L, lower=lower, upper=upper, n=1).flatten() + thin = 10 + for i in range(n - 1): + x = Z[:, i].reshape(d, 1) + for _ in range(thin): + x = gibbs_canonical_truncated_normal(b=b, Q=Q, x=x, lower=lower, upper=upper) + Z[:, i + 1] = x.flatten() + return Z + if method == "Rejection": + return sample_truncated_normal_rejection(mu=mu, Q=Q, L=L, lower=lower, upper=upper, n=n) + + raise TypeError("method should be either Gibbs or Rejection") + + +def sample_truncated_normal_rejection( + mu: np.ndarray, + Q: Union[np.ndarray, sparse.csc_matrix] = None, + L: np.array = None, + lower: np.ndarray = None, + upper: np.ndarray = None, + n: np.array = 1, +) -> np.array: + """Sample from multivariate truncated normal using rejection sampling. + + Rejection sampling will work well for low dimensions and low amounts of truncated but will scale very poorly. + + Args: + mu (np.array): p x 1 mean + Q (np.array, optional): p x p for precision matrix. Defaults to None. + L (np.array, optional): p x p for lower triangular cholesky factorisation of + precision matrix. Defaults to None. + lower (np.array, optional): lower bound + upper (np.array, optional): upper bound + n (int, optional): number of samples. Defaults to 1. + + Returns: + (np.array): p x n random truncated normal values + + """ + if L is None: + L = cholesky(Q) + + n_bad = n + + if lower is None: + lower = -np.inf + + if upper is None: + upper = np.inf + + if np.any(lower >= upper): + raise ValueError("Error lower bound must be strictly less than upper bound") + + samples = sample_normal(mu, L=L, n=n_bad) + ind_bad = np.any(np.bitwise_or(samples < lower, samples > upper), axis=0) + n_bad = np.sum(ind_bad) + + while n_bad > 0: + sample_temp = sample_normal(mu, L=L, n=n_bad) + + samples[:, ind_bad] = sample_temp + ind_bad = np.any(np.bitwise_or(samples < lower, samples > upper), axis=0) + + n_bad = np.sum(ind_bad) + + return samples + + +def sample_normal_canonical(b: np.ndarray, Q: np.ndarray = None, L: np.ndarray = None) -> np.ndarray: + """Generate multivariate random variables canonical representation precision matrix using cholesky factorisation. + + Algorithm 2.5 from Rue, Held 2005 Gaussian Markov Random Fields: + Sampling x ~ N( Q^-1 b, Q^-1) + 1: Compute the Cholesky factorisation, Q = L @ L' + 2: Solve L w = b + 3: Solve L' mu = w + 4: Sample z ~ N(0; I) + 5: Solve L' v = z + 6: Compute x = mu + v + 7: Return x + + Steps 2 and 3 are done in the function cho_solve and the output is thus mu. + Steps 4, 5 and 6 are the algorithm 2.5 implemented in the function sample_normal + + Args: + b (np.ndarray): p x 1 conditional mean + Q (np.ndarray, optional): p x p for precision matrix. Defaults to None. + L (np.ndarray, optional): p x p for lower triangular cholesky factorisation + of precision matrix. Defaults to None. + + Returns: + (np.ndarray): p x 1 random normal values + + """ + if L is None: + L = sparse_cholesky(Q) + + mu = cho_solve((L, True), b).reshape(b.shape) + + return sample_normal(mu, L=L) + + +def gibbs_canonical_truncated_normal( + b: np.ndarray, + Q: Union[np.ndarray, sparse.csc_matrix], + x: np.ndarray, + lower: np.ndarray = -np.inf, + upper: np.ndarray = np.inf, +) -> np.ndarray: + """Generate truncated multivariate random variables from a precision matrix Q using lower cholesky factorisation to get L based on current state x using Gibbs sampling. + + subject to linear inequality constraints + lower < X < upper + + Lemma 2.1 from Rue, Held 2005 Gaussian Markov Random Fields + Sampling x ~ N_c( Q^-1 b , Q^-1) + x_a | x_b ~ N_c( b_a - Q_ab x_b, Q_aa) + + Args: + b (np.array): p x 1 mean + Q (np.array): p x p for precision matrix. Defaults to None. + x (np.array): p x 1 current state. + lower (np.array, optional): p x 1 lower bound for each dimension + upper (np.array, optional): p x 1 upper bound for each dimension + + Returns: + (np.array): p x 1 random normal values + + """ + if (lower == -np.inf or lower is None) and (upper == np.inf or upper is None): + return sample_normal_canonical(b, Q) + + if lower is None: + lower = -np.inf + if upper is None: + upper = np.inf + + p = np.size(x) + temp_limit = np.full(shape=(p, 1), fill_value=np.inf) + lower = np.maximum(lower, -temp_limit) + upper = np.minimum(upper, temp_limit) + + if p == 1: + if sparse.issparse(Q): + Q = Q.toarray() + return np.array(truncated_normal_rv(mean=b / Q, scale=1 / np.sqrt(Q), lower=lower, upper=upper), ndmin=2) + + if sparse.issparse(Q): + Q_diag = Q.diagonal() + else: + Q_diag = np.diag(Q) + + for i in range(p): + Q_ii = Q_diag[i] + v_i = 1 / Q_ii + scale_i = np.sqrt(v_i) + + if sparse.issparse(Q): + cond_mean_i = v_i * (b[i] - Q.getrow(i) @ x + Q_ii * x[i]) + else: + cond_mean_i = v_i * (b[i] - Q[i, :] @ x + Q_ii * x[i]) + + x[i] = truncated_normal_rv(mean=cond_mean_i, scale=scale_i, lower=lower[i], upper=upper[i]) + + return x + + +def truncated_normal_rv( + mean: np.ndarray, scale: np.ndarray, lower: np.ndarray, upper: np.ndarray, size=1 +) -> np.ndarray: + """Wrapper for scipy.stats.truncnorm.rvs handles cases a, b not standard form. + + Args: + mean (np.array): p x 1 mean for each dimension + scale (np.array): p x 1 standard deviation for each dimension + lower (np.array): p x 1 lower bound for each dimension + upper (np.array): p x 1 upper bound for each dimension + size (int): size of output array default = 1 + + Returns: + (np.ndarray): size x 1 truncated normal samples + + """ + if lower is None: + lower = -np.inf + + if upper is None: + upper = np.inf + + a, b = (lower - mean) / scale, (upper - mean) / scale + return truncnorm.rvs(a, b, loc=mean, scale=scale, size=size) + + +def truncated_normal_log_pdf( + x: np.ndarray, mean: np.ndarray, scale: np.ndarray, lower: np.ndarray, upper: np.ndarray +) -> np.ndarray: + """Wrapper for scipy.stats.truncnorm.logpdf handles cases a, b not standard form. + + Args: + x (np.ndarray): values + mean (np.ndarray): mean + scale (np.ndarray): standard deviation + lower (np.ndarray): lower bound + upper (np.ndarray): upper bound + + Returns: + (np.ndarray): truncated normal sample + + """ + if lower is None: + lower = -np.inf + + if upper is None: + upper = np.inf + + a, b = (lower - mean) / scale, (upper - mean) / scale + return truncnorm.logpdf(x, a, b, loc=mean, scale=scale) + + +def multivariate_normal_pdf( + x: np.ndarray, mu: np.ndarray, Q: Union[np.ndarray, sparse.csc_matrix], by_observation: bool = False +) -> Union[np.ndarray, float]: + """Compute diagonalized log-pdf of a multivariate Gaussian distribution in terms of the precision matrix, can take sparse precision matrix inputs. + + Args: + x (np.ndarray): dim x n value for the distribution response. where dim is the number of dimensions and n + is the number of observations + mu (np.ndarray): dim x 1 distribution mean vector. + Q (np.ndarray, sparse.csc_matrix): dim x dim distribution precision matrix can be sparse or np.array + by_observation (bool, optional): indicates whether we should sum over observations default= False + + Returns: + (np.ndarray): log-pdf of the Gaussian distribution either: + (1,) if by_observation = False or + (n,) if by_observation = True + + """ + L = cholesky(Q) + dim = L.shape[0] + + log_det_precision = 2 * np.sum(np.log(L.diagonal())) + Q_residual = L.T @ (x - mu) + log_p = (1 / 2) * (log_det_precision - dim * np.log(2 * np.pi) - np.sum(np.power(Q_residual, 2), axis=0)) + + if not by_observation: + log_p = np.sum(log_p) + return log_p + + +def precision_temporal( + time: DatetimeArray, unit_length: float = 1.0, is_sparse: bool = True +) -> Union[np.ndarray, sparse.csc_matrix]: + """Generate temporal difference penalty matrix. + + Details can be found on pages 97-99 of 'Gaussian Markov Random Fields' + [Rue, Held 2005], 'The first-order random walk for irregular locations'. + + Converts time to number of seconds then call precision_irregular + + Args: + time (DatetimeArray): vector of times + unit_length (float, optional): numbers seconds to define unit difference Defaults to 1 second + is_sparse (bool, optional): Flag if generated as sparse. Defaults to True. + + Returns: + P (Union[np.ndarray, sparse.csc_matrix]): un-scaled precision matrix + + """ + s = (time - time.min()).total_seconds() / unit_length + + return precision_irregular(s, is_sparse=is_sparse) + + +def precision_irregular(s: np.ndarray, is_sparse: bool = True) -> Union[np.ndarray, sparse.csc_matrix]: + """Generate penalty matrix from irregular observations using first order random walk. + + Details can be found on pages 97-99 of 'Gaussian Markov Random Fields' + [Rue, Held 2005], 'The first-order random walk for irregular locations'. + + Diagonal and off-diagonal elements of the precision found as follows: + 1/del_{i-1} + 1/del_{i}, j = i, + Q_{ij} = -1/del_{i}, j = i+1, + 0, else. + where del = [t_{i+1} - t_{i}] + + Args: + s (np.ndarray): vector of locations. + is_sparse (bool, optional): Flag if generated as sparse. Defaults to True. + + Returns: + P ( Union[np.ndarray, sparse.csc_matrix]): un-scaled precision matrix + + """ + if s.ndim > 1: + s = np.squeeze(s) + + if s.size > 1: + delta_reciprocal = 1.0 / np.diff(s) + + d_0 = np.append( + np.append(delta_reciprocal[0], delta_reciprocal[:-1] + delta_reciprocal[1:]), delta_reciprocal[-1] + ) + if is_sparse: + P = sparse.diags(diagonals=(-delta_reciprocal, d_0, -delta_reciprocal), offsets=[-1, 0, 1], format="csc") + else: + P = np.diag(d_0, k=0) - np.diag(delta_reciprocal, k=-1) - np.diag(delta_reciprocal, k=1) + else: + P = np.array(1, ndmin=2) + + return P + + +def solve( + a: Union[np.ndarray, sparse.csc_matrix], b: Union[np.ndarray, sparse.csc_matrix] +) -> Union[np.ndarray, sparse.csc_matrix]: + """Solve a linear matrix equation, or system of linear scalar equations. + + Computes the “exact” solution, x, of the well-determined, i.e., full rank, linear matrix equation ax = b. + + If inputs are sparse calls scipy.linalg.spsolve else calls np.linalg.solve + + Args: + a (Union[np.ndarray, sparse.csc_matrix]): _description_ + b (Union[np.ndarray, sparse.csc_matrix]): _description_ + + Returns + Union(np.ndarray, sparse.csc_matrix) solution to the system in same format as input + + """ + if sparse.issparse(a) or sparse.issparse(b): + return sparse_linalg.spsolve(a, b) + + return np.linalg.solve(a, b) + + +def cho_solve(c_and_lower: tuple, b: Union[np.ndarray, sparse.csc_matrix]) -> Union[np.ndarray, sparse.csc_matrix]: + """Solve the linear equations A x = b, given the Cholesky factorization of A. + + If inputs are sparse calls sparse solvers otherwise uses scipy.linalg.cho_solve + + Args: + c_and_lower ( tuple(Union(np.ndarray, sparse.csc_matrix), bool)): Cholesky factorization of A + and flag for if it is a lower Cholesky + b (Union(np.ndarray, sparse.csc_matrix)): Right-hand side + + Returns + (Union(np.ndarray, sparse.csc_matrix)) The solution to the system A x = b + + """ + if sparse.issparse(c_and_lower[0]) or sparse.issparse(b): + if c_and_lower[1]: + L = c_and_lower[0] + U = c_and_lower[0].T + else: + L = c_and_lower[0].T + U = c_and_lower[0] + + w = sparse_linalg.spsolve(L, b) + return sparse_linalg.spsolve(U, w) + + return linalg.cho_solve(c_and_lower, b) + + +def cholesky(Q: Union[np.ndarray, sparse.csc_matrix], lower: bool = True) -> Union[np.ndarray, sparse.csc_matrix]: + """Compute Cholesky factorization of input matrix. + + If it is sparse will use gmf.sparse_cholesky otherwise will use linalg.cholesky + + Args: + Q (Union[np.ndarray, sparse.csc_matrix]): precision matrix, for factorization + lower (bool, optional): flag for lower triangular matrix, default is true + + Returns + (Union[np.ndarray, sparse.csc_matrix]: Cholesky factorization of the input in the same format as the input + + """ + if sparse.issparse(Q): + L = sparse_cholesky(Q) + else: + L = np.linalg.cholesky(Q) + + if lower: + return L + + return L.T + + +def sparse_cholesky(Q: sparse.csc_matrix) -> sparse.csc_matrix: + """Compute sparse Cholesky factorization of input matrix. + + Uses the scipy.sparse functionality for LU decomposition, and converts + to Cholesky factorization. Approach taken from: + https://gist.github.com/omitakahiro/c49e5168d04438c5b20c921b928f1f5d + + If the sparse matrix is identified as unsuitable for Cholesky factorization, + the function attempts to compute the Chol of the dense matrix instead. + + Args: + Q (sparse.csc_matrix): sparse precision matrix, for factorization + + Returns: + (sparse.csc_matrix): Cholesky factorization of the input + + """ + m = Q.shape[0] + n = Q.shape[1] + if m != n: + raise ValueError("Matrix is not square") + + if sparse.issparse(Q): + if not isinstance(Q, sparse.csc_matrix): + Q = Q.tocsc() + fact_lu = sparse_linalg.splu(Q, diag_pivot_thresh=0, options={"RowPerm": False, "ColPerm": False}) + if (fact_lu.U.diagonal() > 0).all(): + return fact_lu.L.dot(sparse.diags(fact_lu.U.diagonal() ** 0.5)) + + return np.linalg.cholesky(Q.toarray()) + + return np.linalg.cholesky(Q) diff --git a/src/openmcmc/mcmc.py b/src/openmcmc/mcmc.py new file mode 100644 index 0000000..d77cc57 --- /dev/null +++ b/src/openmcmc/mcmc.py @@ -0,0 +1,110 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Main MCMC class for mcmc setup.""" + +from copy import copy +from dataclasses import dataclass, field + +import numpy as np +from scipy import sparse +from tqdm import tqdm + +from openmcmc.model import Model +from openmcmc.sampler.metropolis_hastings import MetropolisHastings +from openmcmc.sampler.sampler import MCMCSampler + + +@dataclass +class MCMC: + """Class for running Markov Chain Monte Carlo on a Model object to do parameter inference. + + Args: + state (dict): initial state of sampler any parameters not + specified will be sampler from prior distributions + samplers (list): list of the samplers to be used for each parameter to be estimated + n_burn (int, optional): number of initial burn in these iterations are not stored, default 5000 + n_iter (int, optional): number of iterations which are stored in store, default 5000 + + Attributes: + state (dict): initial state of sampler any parameters not + specified will be sampler from prior distributions + samplers (list): list of the samplers to be used for each parameter to be estimated. + n_burn (int): number of initial burn in these iterations are not stored. + n_iter (int): number of iterations which are stored in store. + store (dict): dictionary storing MCMC output as np.array for each inference parameter. + + """ + + state: dict + samplers: list[MCMCSampler] + model: Model + n_burn: int = 5000 + n_iter: int = 5000 + store: dict = field(default_factory=dict, init=False) + + def __post_init__(self): + """Convert any state values to at least 2D np.arrays and sample any missing states from the prior distributions, and set up storage arrays for the sampled values. + + Ensures that all elements of the initial state are in an appropriate format for running + the sampler: + - sparse matrices are left unchanged. + - all other data types are coerced (if possible) to np.ndarray. + - any scalars or existing np.ndarray with only one dimension are forced to be at + least 2D. + + Also initialises an item in the storage dictionary for each of the sampled values, + for any data fitted values, and for the log-posterior value. + + """ + self.state = copy(self.state) + + for key, term in self.state.items(): + if sparse.issparse(term): + continue + + if not isinstance(term, np.ndarray): + term = np.array(term, ndmin=2, dtype=np.float64) + if np.shape(term)[0] == 1: + term = term.T + elif term.ndim < 2: + term = np.atleast_2d(term).T + + self.state[key] = term + + for sampler in self.samplers: + if sampler.param not in self.state: + self.state[sampler.param] = sampler.model[sampler.param].rvs(self.state) + self.store = sampler.init_store(current_state=self.state, store=self.store, n_iterations=self.n_iter) + if self.model.response is not None: + for response in self.model.response.keys(): + self.store[response] = np.full(shape=(self.state[response].size, self.n_iter), fill_value=np.nan) + self.store["log_post"] = np.full(shape=(self.n_iter, 1), fill_value=np.nan) + + def run_mcmc(self): + """Runs MCMC routine for model specification loops for n_iter+ n_burn iterations sampling the state for each parameter and updating the parameter state. + + Runs a first loop over samplers, and generates a sample for all corresponding variables in the state. Then + stores the value of each of the sampled parameters in the self.store dictionary, as well as the data fitted + values and the log-posterior value. + + """ + for i_it in tqdm(range(-self.n_burn, self.n_iter)): + for sampler in self.samplers: + self.state = sampler.sample(self.state) + + if i_it < 0: + continue + + for sampler in self.samplers: + self.store = sampler.store(current_state=self.state, store=self.store, iteration=i_it) + + self.store["log_post"][i_it] = self.model.log_p(self.state) + if self.model.response is not None: + for response, predictor in self.model.response.items(): + self.store[response][:, [i_it]] = getattr(self.model[response], predictor).predictor(self.state) + + for sampler in self.samplers: + if isinstance(sampler, MetropolisHastings): + print(f"{sampler.param}: {sampler.accept_rate.get_acceptance_rate()}") diff --git a/src/openmcmc/model.py b/src/openmcmc/model.py new file mode 100644 index 0000000..d72f8bf --- /dev/null +++ b/src/openmcmc/model.py @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +# -*- coding: utf-8 -*- +"""Model module. + +This module provides a class definition of the Model class, a dictionary-like collection of distributions to form a +model. + +""" + +from dataclasses import dataclass +from typing import Tuple, Union + +import numpy as np + +from openmcmc.distribution.distribution import Distribution + + +@dataclass +class Model(dict): + """Dictionary-like collection of distributions to form a model. + + self.keys() indexes the responses of the distributions in the collection; self.values() contain the individual + distribution objects in the model, of type Distribution. + + Attributes: + response (dict): dictionary with keys corresponding to the data values within state, and values corresponding + to the desired predictor values within the data distributions (for storing fitted values). + + """ + + def __init__(self, distributions: list[Distribution], response: dict = None): + dist_dict = {} + for dist in distributions: + dist_dict[dist.response] = dist + super().__init__(dist_dict) + self.response = response + + def conditional(self, param: str): + """Return sub-model which consists of the subset of distributions dependent on the supplied parameter. + + Args: + param (str): parameter to find within the model distributions. + + Returns: + (Model): model object containing only distributions which have a dependence on param. + + """ + conditional_dist = [] + for dst in self.values(): + if param in dst.param_list: + conditional_dist.append(dst) + return Model(conditional_dist) + + def log_p(self, state: dict) -> Union[float, np.ndarray]: + """Compute the log-probability density for the full model. + + Args: + state (dict): dictionary with current state information. + + Returns: + (Union[float, np.ndarray]): POSITIVE log-probability density evaluated using the information in state. + + """ + log_prob = 0 + for dst in self.values(): + log_prob += dst.log_p(state) + return log_prob + + def grad_log_p( + self, state: dict, param: str, hessian_required: bool = True + ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: + """Generate vector of derivatives of the log-pdf with respect to a given parameter, as the sum of the derivatives of all the individual components of the model. If required, also generate the Hessian. + + Function only defined for scalar- and vector-valued parameters param. If hessian_required=True, this function + returns a tuple of (gradient, Hessian). If hessian_required=False, this function returns a np.ndarray (just + the gradient of the log-density). + + Args: + state (dict): current state information. + param (str): name of the parameter for which we compute derivatives. + hessian_required (bool): flag for whether the Hessian should be calculated and supplied as an output. + + Returns: + (Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]): if hessian_required=True, then a tuple of (gradient, + hessian) is returned. If hessian_required=False, then just a gradient vector is returned. The returned + values are as follows: + grad (np.ndarray): vector gradients of the POSITIVE log-pdf with respect to param. shape=(d, 1), where + d is the dimensionality of param. + hessian (np.ndarray): array of NEGATIVE second derivatives of the log-pdf with respect to param. + shape=(d, d), where d is the dimensionality of param. + + """ + grad_sum = np.zeros(shape=state[param].shape) + if hessian_required: + hessian_sum = np.zeros(shape=(state[param].shape[0], state[param].shape[0])) + + for dist in self.values(): + grad_out = dist.grad_log_p(state, param, hessian_required=hessian_required) + if hessian_required: + grad_sum += grad_out[0] + hessian_sum += grad_out[1] + else: + grad_sum += grad_out + + if hessian_required: + return grad_sum, hessian_sum + + return grad_sum diff --git a/src/openmcmc/parameter.py b/src/openmcmc/parameter.py new file mode 100644 index 0000000..4548749 --- /dev/null +++ b/src/openmcmc/parameter.py @@ -0,0 +1,536 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Collection of possible parameter specifications for the distribution objects. + +Example choices defined: + +Identity: f = x +LinearCombination: f = X @ beta + Y @ gamma +LinearCombinationWithTransform: f = X @ exp(beta) + Y @ gamma +ScaledMatrix f = lam * P +MixtureParameterVector f= X[I] +MixtureParameterMatrix f= np.diag(lam[I]) + +""" + +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import Union + +import numpy as np +from scipy import sparse + + +@dataclass +class Parameter(ABC): + """Abstract base class for parameter.""" + + @abstractmethod + def predictor(self, state: dict) -> np.ndarray: + """Create predictor from the state dictionary using the functional form defined in the specific subclass. + + Args: + state (dict): dictionary object containing the current state information + + Returns: + (np.ndarray): predictor vector + + """ + + @abstractmethod + def get_param_list(self) -> list: + """Extract list of components from parameter specification. + + Returns: + (list): parameter included as part of predictor + + """ + + @abstractmethod + def get_grad_param_list(self) -> list: + """Extract list of components from parameter specification that grad is defined for. + + Returns: + (list): parameter that grad is defined for. + + """ + + @abstractmethod + def grad(self, state: dict, param: str) -> np.ndarray: + """Compute gradient of single parameter. + + Args: + state (dict): Dictionary object containing the current state information + param (str): Compute derivatives WRT this variable + + Returns: + (np.ndarray): [n_param x n_data] array, gradient with respect to param + + """ + + +@dataclass +class Identity(Parameter): + """Class specifying a simple predictor in a single term. + + Predictor has the functional form: + f = x + + The gradient should only be used for scalar and vector inputs + + Args: + form (str): string specifying the element of state which determines the parameter + + Attributes: + form (str): string specifying the element of state which determines the parameter. + + """ + + form: str + + def predictor(self, state: dict) -> np.ndarray: + """Create predictor from the state dictionary using the functional form defined in the specific subclass. + + Args: + state (dict): dictionary object containing the current state information + + Returns: + (np.ndarray): predictor vector + + """ + return state[self.form] + + def get_param_list(self) -> list: + """Extract list of components from parameter specification that grad is defined for. + + Returns: + (list): parameter that grad is defined for. + + """ + return [self.form] + + def get_grad_param_list(self) -> list: + """Extract list of components from parameter specification that grad is defined for. + + Returns: + (list): parameter that grad is defined for. + + """ + return [self.form] + + def grad(self, state: dict, param: str) -> np.ndarray: + """Compute gradient of single parameter. + + Args: + state (dict): Dictionary object containing the current state information + param (str): Compute derivatives WRT this variable + + Returns: + (np.ndarray): [n_param x n_data] array, gradient with respect to param + + """ + if state[self.form].shape[1] > 1: + raise ValueError("Gradient in Identity should not be used for variables 2D and above.") + p = state[self.form].size + if param == self.form: + grad = np.eye(p) + else: + grad = np.zeros(shape=(p, p)) + return grad + + +@dataclass +class LinearCombination(Parameter): + """Class specifying linear combination form . + + This Parameter type is typically in the mean of a Normal distribution in a linear regression type case. + + Predictor has the form + predictor = sum_i (value[i] @ key[i]) + using the form dictionary input + + Attributes: + form (dict): dict specifying the term and prefactor in the linear combination. + example: {'beta': 'X', 'alpha': 'A'} produces linear combination X @ beta + A @ alpha. + + """ + + form: dict + + def predictor(self, state: dict) -> np.ndarray: + """Create predictor from the state dictionary using the functional form defined in the specific subclass. + + Args: + state (dict): dictionary object containing the current state information + + Returns: + (np.ndarray): predictor vector + + """ + return self.predictor_conditional(state) + + def predictor_conditional(self, state: dict, term_to_exclude: Union[str, list] = None) -> np.ndarray: + """Extract predictor from the state dictionary using the functional form defined in the specific subclass excluding parameters. + + Used when estimating conditional distributions of those parameters. + + Args: + state (dict): dictionary object containing the current state information + term_to_exclude (Union[str, list]): terms to exclude from predictor + + Returns: + (np.ndarray): predictor vector + + """ + if term_to_exclude is None: + term_to_exclude = [] + + if isinstance(term_to_exclude, str): + term_to_exclude = [term_to_exclude] + + sum_terms = 0 + for prm, prefactor in self.form.items(): + if prm not in term_to_exclude: + sum_terms += state[prefactor] @ state[prm] + return sum_terms + + def get_param_list(self) -> list: + """Extract list of components from parameter specification that grad is defined for. + + Returns: + (list): parameter that grad is defined for. + + """ + return list(self.form.keys()) + list(self.form.values()) + + def get_grad_param_list(self) -> list: + """Extract list of components from parameter specification that grad is defined for. + + Returns: + (list): parameter that grad is defined for. + + """ + return list(self.form.keys()) + + def grad(self, state: dict, param: str) -> np.ndarray: + """Compute gradient of single parameter. + + Args: + state (dict): Dictionary object containing the current state information + param (str): Compute derivatives WRT this variable + + Returns: + (np.ndarray): [n_param x n_data] array, gradient with respect to param + + """ + return state[self.form[param]].T + + +@dataclass +class LinearCombinationWithTransform(LinearCombination): + """Linear combination of parameters from the state, with optional exponential transformation for the parameter elements. + + Currently, the only allowed transformation is the exponential transform. + + This Parameter type is typically in the mean of a Normal distribution and could be + used to impose positivity of the parameters + + Predictor has the form + predictor = sum_i (value[i] @ transform(key[i])) + using the form dictionary input + + Attributes: + transform (dict): dict with logicals specifying whether exp(.) transform should + be applied to parameter + example: form={'beta': X}, transform={'beta': True} will produce X @ np.exp(beta) + + """ + + transform: dict + + def predictor_conditional(self, state: dict, term_to_exclude: Union[str, list] = None) -> np.ndarray: + """Extract predictor from the state dictionary using the functional form defined in the specific subclass excluding parameters. + + Used when estimating conditional distributions of those parameters. + + Args: + state (dict): dictionary object containing the current state information + term_to_exclude (list): terms to exclude from predictor + + Returns: + (np.ndarray): predictor vector + + """ + if term_to_exclude is None: + term_to_exclude = [] + + if isinstance(term_to_exclude, str): + term_to_exclude = [term_to_exclude] + + sum_terms = 0 + for prm, prefactor in self.form.items(): + if prm not in term_to_exclude: + param = state[prm] + if self.transform[prm]: + param = np.exp(param) + sum_terms += state[prefactor] @ param + return sum_terms + + def grad(self, state: dict, param: str) -> np.ndarray: + """Compute gradient of single parameter. + + Args: + state (dict): Dictionary object containing the current state information + param (str): Compute derivatives WRT this variable + + Returns: + (np.ndarray): [n_param x n_data] array, gradient with respect to param + + """ + if self.transform[param]: + if sparse.issparse(state[self.form[param]]): + return state[self.form[param]].multiply(np.exp(state[param]).flatten()).T + return np.exp(state[param]) * (state[self.form[param]].T) + + return state[self.form[param]].T + + +@dataclass +class ScaledMatrix(Parameter): + """Defines parameter a scalar factor in front of a matrix. + + This is often used in case where we have a scalar variance in front of an unscaled precision matrix. + Where we have a gamma distribution for the scalar parameter which wish to estimate + + Linear combinations have the form: + predictor = scalar * matrix + + Attributes: + matrix (str): variable name of the un-scaled matrix + scalar (str): variable name of the scalar term + + """ + + matrix: str + scalar: str + + def predictor(self, state: dict) -> np.ndarray: + """Create predictor from the state dictionary using the functional form defined in the specific subclass. + + Args: + state (dict): dictionary object containing the current state information + + Returns: + (np.ndarray): predictor vector + + """ + return float(state[self.scalar].item()) * state[self.matrix] + + def get_param_list(self) -> list: + """Extract list of components from parameter specification that grad is defined for. + + Returns: + (list): parameter that grad is defined for. + + """ + return [self.scalar, self.matrix] + + def get_grad_param_list(self) -> list: + """Extract list of components from parameter specification that grad is defined for. + + Returns: + (list): parameter that grad is defined for. + + """ + return [self.scalar] + + def grad(self, state: dict, param: str) -> np.ndarray: + """Compute gradient of single parameter. + + Args: + state (dict): Dictionary object containing the current state information + param (str): Compute derivatives WRT this variable + + Returns: + (np.ndarray): [n_param x n_data] array, gradient with respect to param + + """ + return state[self.matrix] + + def precision_unscaled(self, state: dict, _) -> np.ndarray: + """Return the precision matrix un-scaled by the scalar precision parameter. + + Args: + state (dict): state dictionary + _ (int): argument unused but matches with version in MixtureParameterMatrix where element is needed + + Returns: + (np.ndarray): unscaled precision matrix + + """ + return state[self.matrix] + + +@dataclass +class MixtureParameter(Parameter, ABC): + """Abstract Parameter class for a mixture distribution. + + Subclasses implemented for both: + + - vector-valued parameter (MixtureParameterVector) + - diagonal matrix-valued parameter (MixtureParameterMatrix) + where the elements of the vector or matrix diagonal are allocated based + on the allocation parameter. + + """ + + param: str + allocation: str + + def get_element_match(self, state: dict, element_index: Union[int, np.ndarray]) -> np.ndarray: + """Extract the parts of self.allocation which have given element number. + + used in the gradient function to pull out gradient for given element. + + Args: + state (dict): state vector + element_index (int, np.array): element index or set of integers + + Returns: + (np.array(dtype=int)): element matches with 1 where there is a match and 0 where there isn't + + """ + if isinstance(element_index, np.ndarray) and element_index.size > 1: + element_index = element_index.reshape((1, -1)) + + return np.array(state[self.allocation] == element_index, dtype=int, ndmin=2) + + def get_param_list(self) -> list: + """Extract list of components from parameter specification that grad is defined for. + + Returns: + (list): parameter that grad is defined for. + + """ + return [self.param, self.allocation] + + +@dataclass +class MixtureParameterVector(MixtureParameter): + """Vector parameter: elements of the vector are obtained from sub-parameter 'param' according to the allocation. + + The allocation parameter defines a mapping between a R^m and R^n where typically m<=n and m is the + number true underlying number of parameters in the model but due to the representation/algebra in + other parts of the model this is expanded out to an n parameter model where the values of m are copied + according to the index vector + + predictor = param [allocation] + + Attributes: + param (str): name of underlying state component used to generate parameter. + allocation (np.ndarray): name of allocation parameter within state dict. + + """ + + def predictor(self, state: dict) -> np.ndarray: + """Create predictor from the state dictionary using the functional form defined in the specific subclass. + + Args: + state (dict): dictionary object containing the current state information + + Returns: + (np.ndarray): predictor vector + + """ + return state[self.param][state[self.allocation].flatten()] + + def grad(self, state: dict, param: str): + """Compute gradient of single parameter. + + Args: + state (dict): Dictionary object containing the current state information + param (str): Compute derivatives WRT this variable + + Returns: + (np.ndarray): [n_param x n_data] array, gradient with respect to param + + """ + element_index = np.arange(0, state[param].size) + + return self.get_element_match(state, element_index).astype(np.float64).T + + def get_grad_param_list(self) -> list: + """Extract list of components from parameter specification that grad is defined for. + + Returns: + (list): parameter that grad is defined for. + + """ + return [self.param] + + +@dataclass +class MixtureParameterMatrix(MixtureParameter): + """Diagonal matrix parameter: elements of the diagonal are obtained from sub-parameter 'param' according to the allocation index vector. + + The allocation parameter defines a mapping between a R^m and R^n where typically m<=n and m is the + number true underlying number of parameters in the model but due to the representation/algebra in + other parts of the model this is expanded out to an n parameter model where the values of m are copied + according to the index vector + + predictor = np.diag( param [allocation] ) + + Attributes: + param (str): name of underlying state component used to generate parameter. + allocation (np.ndarray): name of allocation parameter within state dict. + + """ + + def predictor(self, state: dict) -> sparse.csc_matrix: + """Create predictor from the state dictionary using the functional form defined in the specific subclass. + + Args: + state (dict): dictionary object containing the current state information + + Returns: + (sparse.csc_matrix): predictor vector + + """ + return sparse.diags(diagonals=state[self.param][state[self.allocation]].flatten(), offsets=0, format="csc") + + def grad(self, state: dict, param: str): + """Compute gradient of single parameter. + + Args: + state (dict): Dictionary object containing the current state information + param (str): Compute derivatives WRT this variable + + Returns: + (np.ndarray): [n_param x n_data] array, gradient with respect to param + + """ + raise TypeError("Not defined in this case") + + def get_grad_param_list(self) -> list: + """Extract list of components from parameter specification that grad is defined for. + + Returns: + (list): parameter that grad is defined for. + + """ + return [] + + def precision_unscaled(self, state: dict, element_index: int) -> np.ndarray: + """Return the precision matrix un-scaled by the scalar precision parameter. + + Args: + state (dict): state dictionary + element_index (int): index of element to subset + + Returns: + (np.ndarray): unscaled precision matrix + + """ + return sparse.diags(diagonals=self.get_element_match(state, element_index).flatten(), offsets=0, format="csc") diff --git a/src/openmcmc/sampler/__init__.py b/src/openmcmc/sampler/__init__.py new file mode 100644 index 0000000..e9a92fe --- /dev/null +++ b/src/openmcmc/sampler/__init__.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Sampler module.""" + +__all__ = [ + "metropolis_hastings", + "reversible_jump", + "sampler", +] diff --git a/src/openmcmc/sampler/metropolis_hastings.py b/src/openmcmc/sampler/metropolis_hastings.py new file mode 100644 index 0000000..8c12c8c --- /dev/null +++ b/src/openmcmc/sampler/metropolis_hastings.py @@ -0,0 +1,372 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +# -*- coding: utf-8 -*- +"""MetropolisHastings module. + +This module provides a class definition of the MetropolisHastings class an abstract base class for implementation of +Metropolis-Hastings-type sampling algorithms for a model. + +""" + +from abc import abstractmethod +from copy import deepcopy +from dataclasses import dataclass, field +from typing import Callable, Tuple + +import numpy as np +from scipy.stats import norm + +from openmcmc import gmrf +from openmcmc.sampler.sampler import MCMCSampler + + +@dataclass +class AcceptRate: + """Class for dealing with calculation of acceptance rates. + + Called from MetropolisHastings-type samplers. + + Attributes: + count: counters of current number of proposals and accepted proposals from a MH chain + + """ + + def __init__(self): + self.count = {"accept": 0, "proposal": 0} + + @property + def acceptance_rate(self) -> float: + """Acceptance rate property, as calculated from counters. + + Returns: + (float): percentage proposals accepted in chain + + """ + return self.count["accept"] / self.count["proposal"] * 100 + + def get_acceptance_rate(self) -> str: + """Return acceptance rate formatted as string. + + Returns: + (str): acceptance rate string print out + + """ + if self.count["proposal"] == 0: + return "No proposals" + return f"Acceptance rate {self.acceptance_rate:.0f}%" + + def increment_accept(self): + """Increment acceptance count.""" + self.count["accept"] += 1 + + def increment_proposal(self): + """Increment proposal count.""" + self.count["proposal"] += 1 + + +@dataclass +class MetropolisHastings(MCMCSampler): + """Abstract base class for implementation of Metropolis-Hastings-type sampling algorithms for a model. + + Subclasses include RandomWalk and ManifoldMALA. + + https://en.wikipedia.org/wiki/Metropolis%E2%80%93Hastings_algorithm + + Attributes: + step (np.ndarray): step size for Metropolis-Hastings proposals. Should either have shape=(p, 1) or shape=(p, n), + where p is the dimension of the parameter, and n is the number of replicates. + accept_rate (AcceptRate): Acceptance Rate counter to keep track of proposals. + + """ + + step: np.ndarray = field(default_factory=lambda: np.array([0.2], ndmin=2), init=True) + accept_rate: AcceptRate = field(default_factory=lambda: AcceptRate(), init=False) + + @abstractmethod + def proposal(self, current_state: dict, param_index: int = None) -> Tuple[dict, float, float]: + """Method which generates proposed state from current state, and computes corresponding transition probabilities. + + Args: + current_state (dict): current state + param_index (int): subset of parameter used in proposal, If none all parameters are used + + Returns: + (Tuple[dict, np.ndarray, np.ndarray]): tuple consisting of the following elements: + prop_state (dict): updated proposal_state dictionary. + logp_pr_g_cr (float): log-density of proposed state given current state. + logp_cr_g_pr (float): log-density of current state given proposed state. + + """ + + def sample(self, current_state: dict) -> dict: + """Generate a sample from the specified Metropolis-Hastings-type method. + + https://en.wikipedia.org/wiki/Metropolis%E2%80%93Hastings_algorithm + + generate proposal state x' from current_state x and accept or reject proposal according to the probability: + A(x',x) = min(1, (P(x')g(x|x'))/(P(x)g(x'|x))) + where: + - P(x) is the probability of the state x + - g(x|x') is the probability of moving from state x to x' + + The exact method for the proposal (and therefore the form of the proposal distribution) is determined by the + specific type of MetropolisHastings Sampler used. + + Args: + current_state (dict): dictionary containing the current sampler state. + + Returns: + current_state (dict): with updated sample for self.param. + + """ + prop_state, logp_pr_g_cr, logp_cr_g_pr = self.proposal(current_state) + current_state = self._accept_reject_proposal(current_state, prop_state, logp_pr_g_cr, logp_cr_g_pr) + return current_state + + def _accept_reject_proposal( + self, current_state: dict, prop_state: dict, logp_pr_g_cr: float, logp_cr_g_pr: float + ) -> dict: + """Accept or Reject Metropolis-Hastings-type proposal. + + Computes the log posterior for the current and proposed states, and evaluates the log acceptance probability. + Accepts the proposal with probability A(x, x'), and returns either the proposed or the current state + accordingly. + + Increments self.acceptance_rate() to indicate that a proposal has been made, and also increments the acceptance + counter if the proposal is subsequently accepted. + + Args: + current_state (dict): current state dictionary + prop_state (dict): proposal_state dictionary + logp_pr_g_cr (float): log posterior of proposal given current state + logp_cr_g_pr (float): log posterior of current state given proposals + + Returns: + (dict): updated current state dictionary, after the proposal has either been accepted or rejected. + + """ + self.accept_rate.increment_proposal() + logp_cs = 0 + logp_pr = 0 + for model in self.model.values(): + logp_cs += model.log_p(current_state) + logp_pr += model.log_p(prop_state) + log_accept = logp_pr + logp_cr_g_pr - (logp_cs + logp_pr_g_cr) + + if self.accept_proposal(log_accept): + current_state = prop_state + self.accept_rate.increment_accept() + return current_state + + @staticmethod + def accept_proposal(log_accept: float) -> bool: + """Decide to accept or reject proposal based on log acceptance probability. + + Args: + log_accept (np.float64): log acceptance probability. + + Returns: + (bool): True for accept, False for Reject. + + """ + return np.log(np.random.rand()) < log_accept + + +@dataclass +class RandomWalk(MetropolisHastings): + """Subtype of MetropolisHastings sampler that uses Gaussian random Walk proposals. + + Supports both non-truncated and truncated Gaussian proposals: specifying self.domain limits leads to a truncated + proposal mechanism. + + Allows for the possibility that other elements of the model state have a dependence on the value of self.param, and + if so should change when this value changes. If supplied, the self.state_update_function() property is called by the + proposal function to update any other elements of the state as required. + + Attributes: + domain_limits (np.ndarray): array with shape=(p, 2), where p is the dimensionality of the parameter being + sampled. The first column gives the lower limits for the proposal, the second column gives the upper limits. + state_update_function (Callable): function which updates other elements of proposed state based on the proposed + value for param. + + """ + + domain_limits: np.ndarray = None + state_update_function: Callable = None + + def __post_init__(self): + """Derive conditional model instead of storing all distributions where things are simple. + + However, this should not be done in the case where a state_update_function is provided as we don't know in + general what/how parameters might change so need to keep full model to avoid incorrect conditioning. + + """ + if self.state_update_function is None: + self.model = self.model.conditional(self.param) + self.step = np.array(self.step, ndmin=2) + + def proposal(self, current_state: dict, param_index: int = None) -> Tuple[dict, float, float]: + """Updates the current value of self.param using a (truncated) Gaussian random walk proposal. + + In the non-truncated case, the proposal mechanism is symmetric, i.e. logp_pr_g_cr = logp_cr_g_pr. In this + instance, the function simply returns logp_pr_g_cr = logp_cr_g_pr = 0, since these terms would anyway cancel + in the calculation of the acceptance ratio. + + Introducing a truncation into the proposal distribution means that the proposal is no longer symmetric, and so + the log-proposal densities are computed in these cases. + + Enables 3 different possibilities for the step size: + 1) shape=(1, 1): scalar step size, identical for every element of the parameter. + 2) shape=(p, 1): step size with the same shape as the parameter being sampled (for one or many replicates). + 3) shape=(p, n): a p-dimensional step size for each of n-replicates. + + Args: + current_state (dict): dictionary containing current parameter values. + param_index (int): subset of parameter used in proposal, If none all parameters are used + + Returns: + (Tuple[dict, np.ndarray, np.ndarray]): tuple consisting of the following elements: + prop_state (dict): updated proposal_state dictionary. + logp_pr_g_cr (float): log-density of proposed state given current state. + logp_cr_g_pr (float): log-density of current state given proposed state. + + """ + prop_state = deepcopy(current_state) + + if param_index is None: + mu = prop_state[self.param] + step = self.step + else: + mu = prop_state[self.param][:, param_index] + if self.step.shape[1] == 1: + step = self.step.flatten() + else: + step = self.step[:, param_index].flatten() + + if self.domain_limits is None: + z = mu + norm.rvs(size=prop_state[self.param].shape, scale=step) + logp_pr_g_cr = logp_cr_g_pr = 0.0 + else: + lb = self.domain_limits[:, 0] + ub = self.domain_limits[:, 1] + z = gmrf.truncated_normal_rv(mean=mu, scale=step, lower=lb, upper=ub, size=len(lb)) + logp_pr_g_cr = np.sum(gmrf.truncated_normal_log_pdf(z, mu, step, lower=lb, upper=ub)) + logp_cr_g_pr = np.sum(gmrf.truncated_normal_log_pdf(mu, z, step, lower=lb, upper=ub)) + + if param_index is None: + prop_state[self.param] = z + else: + prop_state[self.param][:, param_index] = z + + if callable(self.state_update_function): + prop_state = self.state_update_function(prop_state, param_index) + + return prop_state, logp_pr_g_cr, logp_cr_g_pr + + +@dataclass +class RandomWalkLoop(RandomWalk): + """Subtype of MetropolisHastings sampler which updates each of n replicates of a parameter one-at-a-time, rather than all simultaneously.""" + + def sample(self, current_state: dict) -> dict: + """Update each of n replicates of a given parameter in a loop, rather than simultaneously. + + Args: + current_state (dict): dictionary containing the current sampler state. + + Returns: + current_state (dict): with updated sample for self.param. + + """ + for param_index in range(current_state[self.param].shape[1]): + prop_state, logp_pr_g_cr, logp_cr_g_pr = self.proposal(current_state, param_index) + current_state = self._accept_reject_proposal(current_state, prop_state, logp_pr_g_cr, logp_cr_g_pr) + return current_state + + +@dataclass +class ManifoldMALA(MetropolisHastings): + """Class implementing manifold Metropolis-adjusted Langevin algorithm (mMALA) proposal mechanism. + + Reference: Riemann manifold Langevin and Hamiltonian Monte Carlo methods, Mark Girolami, Ben Calderhead, + 03 March 2011 https://doi.org/10.1111/j.1467-9868.2010.00765.x + + """ + + def proposal(self, current_state: dict, param_index: int = None) -> Tuple[dict, np.ndarray, np.ndarray]: + """Generate mMALA proposed state from current state using gradient and hessian, and compute corresponding log-transition probabilities. + + Args: + current_state (dict): dictionary containing current parameter values. + param_index (int): required input from superclass. Not used; defaults to None. + + Returns: + (Tuple[dict, np.ndarray, np.ndarray]): tuple consisting of the following elements: + prop_state (dict): updated proposal_state dictionary. + logp_pr_g_cr (np.ndarray): log-density of proposed state given current state. + logp_cr_g_pr (np.ndarray): log-density of current state given proposed state. + + """ + prop_state = deepcopy(current_state) + + mu_cr, chol_cr = self._proposal_params(current_state) + prop_state[self.param] = gmrf.sample_normal(mu_cr, L=chol_cr) + logp_pr_g_cr = self._log_proposal_density(prop_state, mu_cr, chol_cr) + + mu_pr, chol_pr = self._proposal_params(prop_state) + logp_cr_g_pr = self._log_proposal_density(current_state, mu_pr, chol_pr) + + return prop_state, logp_pr_g_cr, logp_cr_g_pr + + def _proposal_params(self, current_state: dict) -> Tuple[np.ndarray, np.ndarray]: + """Returns the mean vector and the Cholesky factorization of the precision matrix for the mMALA proposal. + + The density for either the forward or return proposal in an mMALA scheme is a Gaussian. In the case of the + forward proposal, the density is as follows: + q(prop | theta_0) ~ N(mu*, stp^2 * H ^-1 ) + where: + mu* = theta_0 + 1/2 * stp^2 * H ^-1 @ G + H = hessian(theta_0) + G = gradient(theta_0) + + Args: + current_state (dict): dictionary containing current parameter values. + + Returns: + (Tuple[np.ndarray, np.ndarray]): with the following components: + mu_cr (np.ndarray): mean for proposal distribution, shape=(p, 1). + chol_cr (np.ndarray): lower triangular Cholesky factorization of precision matrix, shape=(p, p). + + """ + grad_cr, hessian_cr = self.model.grad_log_p(current_state, param=self.param, hessian_required=True) + precision_cr = hessian_cr / (self.step**2) + chol_cr = gmrf.cholesky(precision_cr) + mu_cr = current_state[self.param] + (1 / 2) * gmrf.cho_solve((chol_cr, True), grad_cr).reshape(grad_cr.shape) + return mu_cr, chol_cr + + def _log_proposal_density(self, state: dict, mu: np.ndarray, chol: np.ndarray) -> np.ndarray: + """Evaluate the log-proposal density for the mMALA transition. + + log determinant calculated using: + https://blogs.sas.com/content/iml/2012/10/31/compute-the-log-determinant-of-a-matrix.html + + + A quadratic form can be expressed in terms of the Cholesky factorization of the matrix as: + r' Q r = r' L L' r = w' w =sum(w^2) + where: + w = L' r + r = prm - mu + + Args: + state (dict): dictionary containing parameter values. + mu (np.ndarray): mean vector, shape=(p, 1). + chol (np.ndarray): LOWER triangular cholesky factorization of the precision matrix, shape=(p, p) + + Returns: + (np.ndarray): log-transition probability. + + """ + w = chol.transpose() @ (state[self.param] - mu) + return np.sum(np.log(chol.diagonal())) - 0.5 * w.T.dot(w) diff --git a/src/openmcmc/sampler/reversible_jump.py b/src/openmcmc/sampler/reversible_jump.py new file mode 100644 index 0000000..30d9323 --- /dev/null +++ b/src/openmcmc/sampler/reversible_jump.py @@ -0,0 +1,376 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +# -*- coding: utf-8 -*- +"""ReversibleJump module. + +This module provides a class definition of the ReversibleJump class a class for reversible jump sampling for given +parameter and associated parameters. + +""" + +from copy import deepcopy +from dataclasses import dataclass +from typing import Callable, Tuple, Union + +import numpy as np +from scipy.stats import randint, uniform + +from openmcmc import gmrf +from openmcmc.sampler.metropolis_hastings import MetropolisHastings + + +@dataclass +class ReversibleJump(MetropolisHastings): + """Reversible jump sampling for given parameter and associated parameter. + + self.param corresponds to a number of elements, which will either increase of decrease by 1. self.associated_params + corresponds to an associated set of self.param parameters, to which we either add or remove an element for a birth + or death move. + + The attributes self.state_birth_function and self.state_death_function can be used to supply functions which + implement problem-specific alterations to elements of the state on the occurrence of a birth or death move + respectively. For example, it may be required to update a basis matrix in the state after a change in the number + of knots/locations associated with the basis definition. + + The functions self.matched_birth_transition and self.matched_death_transition implement optional functionality which + can be used to ensure consistency between sets of basis parameters before and after a transition. These work by + ensuring that the basis predictions before and after the transition match, then applies Gaussian random noise (with + a given standard deviation) to the coefficient of the new element. + + Attributes: + associated_params (list or string): a list or a string associated with the dimension jump. List of additional + parameters that need to be created/removed as part of the dimension change. The default behaviour is to + sample the necessary additional values from the associated parameter prior distribution. Defaults to None. + n_max (int): upper limit on self.param (lower limit is assumed to be 1). + birth_probability (float): probability that a birth move is chosen on any given iteration of the algorithm + (death_probability = 1 - birth_probability). Defaults to 0.5. + state_birth_function (Callable): function which implements problem-specific requirements for updates to elements + of the state as part of a birth function (e.g. updates to a problem-specific basis matrix based given + additional location parameters). Defaults to None. + state_death_function (Callable): function which implements problem-specific requirements for updates to elements + of state as part of a death function. Should mirror the supplied state_birth_function. Defaults to None. + matching_params (dict): dictionary of parameters required for the matched coefficient transitions- for details + of what it should contain, see self.matched_birth_transition. + + """ + + associated_params: Union[list, str, None] = None + n_max: Union[int, None] = None + birth_probability: float = 0.5 + state_birth_function: Union[Callable, None] = None + state_death_function: Union[Callable, None] = None + matching_params: Union[dict, None] = None + + def __post_init__(self): + """Empty function to prevent super.__post_init__ from being run. + + The whole model should be attached in this instance, rather than simply those elements with a dependence on + self.param. + + """ + if isinstance(self.associated_params, str): + self.associated_params = [self.associated_params] + + def proposal(self, current_state: dict, param_index: int = None) -> Tuple[dict, float, float]: + """Make a proposal, and compute related transition probabilities for the move. + + Args: + current_state (dict): dictionary with current parameter values. + param_index (int): not used, included for compatibility with superclass. + + Returns: + (Tuple[dict, float, float]): tuple consisting of the following elements: + prop_state (dict): dictionary updated with proposed value for self.param. + logp_pr_g_cr (float): transition probability for proposed state given current state. + logp_cr_g_pr (float): transition probability for current state given proposed state. + + """ + birth = self.get_move_type(current_state) + if birth: + prop_state, logp_pr_g_cr, logp_cr_g_pr = self.birth_proposal(current_state=current_state) + else: + prop_state, logp_pr_g_cr, logp_cr_g_pr = self.death_proposal(current_state=current_state) + return prop_state, logp_pr_g_cr, logp_cr_g_pr + + def birth_proposal(self, current_state: dict) -> Tuple[dict, float, float]: + """Make a birth proposal move: INCREASES state[self.param] by 1. + + Also makes a proposal for a new element of an associated parameter, state[self.associated_params], by generating a draw + from the prior distribution for self.associated_params. + + self.state_birth_function() is a function which can be optionally specified for altering the dimensionality of + any other parameters associated with the dimension change (e.g. a basis matrix, or an allocation parameter). + + If the self.matching_params dictionary is specified, self.matched_birth_transition() is used to generate a + proposal for a set of basis parameters such that the predicted values match before and after the transition. + + NOTE: log-probability for deletion of a particular knot (-log(n + 1)) is cancelled by the contribution from + the order statistics densities, log((n + 1)! / n!) = log(n + 1). Therefore, both contributions are omitted from + the calculation. For further information, see Richardson & Green 1997, Section 3.2: + https://people.maths.bris.ac.uk/~mapjg/papers/RichardsonGreenRSSB.pdf + + NOTE: log-probability density for the full model is obtained from summing the contribution of the log-density + for the individual distributions corresponding to each jump parameter. + + Args: + current_state (dict): dictionary with current parameter values. + + Returns: + (Tuple[dict, float, float]): tuple consisting of the following elements: + prop_state (dict): dictionary updated with proposed state. + logp_pr_g_cr (float): transition probability for proposed state given current state. + logp_cr_g_pr (float): transition probability for current state given proposed state. + + """ + prop_state = deepcopy(current_state) + prop_state[self.param] = prop_state[self.param] + 1 + log_prop_density = 0 + + for associated_key in self.associated_params: + new_element = self.model[associated_key].rvs(state=current_state, n=1) + prop_state[associated_key] = np.concatenate((prop_state[associated_key], new_element), axis=1) + log_prop_density += self.model[associated_key].log_p(current_state, by_observation=True) + if callable(self.state_birth_function): + prop_state, logp_pr_g_cr, logp_cr_g_pr = self.state_birth_function(current_state, prop_state) + else: + logp_pr_g_cr, logp_cr_g_pr = 0.0, 0.0 + if self.matching_params is not None: + prop_state, logp_pr_g_cr, logp_cr_g_pr = self.matched_birth_transition( + current_state, prop_state, logp_pr_g_cr, logp_cr_g_pr + ) + + p_birth, p_death = self.get_move_probabilities(current_state, True) + logp_pr_g_cr += np.log(p_birth) + log_prop_density[-1] + logp_cr_g_pr += np.log(p_death) + + return prop_state, logp_pr_g_cr, logp_cr_g_pr + + def death_proposal(self, current_state: dict) -> Tuple[dict, float, float]: + """Make a death proposal move: DECREASES state[self.param] by 1. + + Also adjusts the associated parameter state[self.associated_params] by deleting a randomly-selected element. + + self.state_death_function() and self.matched_death_transition() can be used (optional) to specify transitions + opposite to those used in the birth move. + + NOTE: log-probability density for the full model is obtained from summing the contribution of the log-density + for the individual distributions corresponding to each jump parameter. + + For further information about the transition, see also self.birth_proposal(). + + Args: + current_state (dict): dictionary with current parameter values. + + Returns: + (Tuple[dict, float, float]): tuple consisting of the following elements: + prop_state (dict): dictionary updated with proposed state. + logp_pr_g_cr (float): transition probability for proposed state given current state. + logp_cr_g_pr (float): transition probability for current state given proposed state. + + """ + prop_state = deepcopy(current_state) + prop_state[self.param] = prop_state[self.param] - 1 + log_prop_density = 0 + deletion_index = randint.rvs(low=0, high=current_state[self.param]) + for associated_key in self.associated_params: + prop_state[associated_key] = np.delete(prop_state[associated_key], obj=deletion_index, axis=1) + log_prop_density += self.model[associated_key].log_p(current_state, by_observation=True) + + if callable(self.state_death_function): + prop_state, logp_pr_g_cr, logp_cr_g_pr = self.state_death_function( + current_state, prop_state, deletion_index + ) + else: + logp_pr_g_cr, logp_cr_g_pr = 0.0, 0.0 + if self.matching_params is not None: + prop_state, logp_pr_g_cr, logp_cr_g_pr = self.matched_death_transition( + current_state, prop_state, logp_pr_g_cr, logp_cr_g_pr, deletion_index + ) + + p_birth, p_death = self.get_move_probabilities(current_state, False) + logp_pr_g_cr += np.log(p_death) + logp_cr_g_pr += np.log(p_birth) + log_prop_density[-1] + + return prop_state, logp_pr_g_cr, logp_cr_g_pr + + def matched_birth_transition( + self, current_state: dict, prop_state: dict, logp_pr_g_cr: float, logp_cr_g_pr: float + ) -> Tuple[dict, float, float]: + """Generate a proposal for coefficients associated with a birth move, using the principle of matching the predictions before and after the move. + + The parameter vector in the proposed state is computed as: beta* = F @ beta_aug, where: + F = [G, 0 + 0', 1] + G = (X*' @ X*)^{-1} @ (X*' @ X) + where X is the original basis matrix, and X* is the augmented basis matrix. For a detailed explanation of the + approach, see: https://ygraigarw.github.io/ZnnEA1D19.pdf + + The basis matrix in the proposed state should already have been updated in self.state_birth_function(), before + the call to this function (along with any other associated parameters that need to change shape). + + The following fields should be supplied as part of the self.matching_params dictionary: + - "variable" (str): reference to the coefficient parameter vector in the state. + - "matrix" (str): reference to the associated basis matrix in state. + - "scale" (float): scale of Gaussian noise added to proposal. + - "limits" (list): [lower, upper] limit for truncated Normal proposals. + + The proposal for the additional basis parameter can be either from: + - a standard normal distribution (when self.matching_params["limits"] is passed as None). + - a truncated normal distribution (when self.matching_params["limits"] is a two-element list of the lower + and upper limits). + + Args: + current_state (dict): current parameter state as dictionary. + prop_state (dict): proposed state dictionary, with updated basis matrix. + logp_pr_g_cr (float): transition probability for proposed state given current state. + logp_cr_g_pr (float): transition probability for current state given proposed state. + + Returns: + (Tuple[dict, float, float]): tuple consisting of the following elements: + prop_state (dict): proposed state with updated parameter vector. + logp_pr_g_cr (float): updated transition probability. + logp_cr_g_pr (float): updated transition probability. + + """ + vector = self.matching_params["variable"] + matrix = self.matching_params["matrix"] + proposal_scale = self.matching_params["scale"] + proposal_limits = self.matching_params["limits"] + + current_basis = current_state[matrix] + prop_basis = prop_state[matrix] + G = np.linalg.solve( + prop_basis.T @ prop_basis + 1e-10 * np.eye(prop_basis.shape[1]), prop_basis.T @ current_basis + ) + F = np.concatenate((G, np.eye(N=G.shape[0], M=1, k=-G.shape[0] + 1)), axis=1) + mu_star = G @ current_state[vector] + prop_state[vector] = deepcopy(mu_star) + + if proposal_limits is not None: + prop_state[vector][-1] = gmrf.truncated_normal_rv( + mean=mu_star[-1], scale=proposal_scale, lower=proposal_limits[0], upper=proposal_limits[1], size=1 + ) + logp_pr_g_cr += gmrf.truncated_normal_log_pdf( + prop_state[vector][-1], mu_star[-1], proposal_scale, lower=proposal_limits[0], upper=proposal_limits[1] + ) + else: + Q = np.array(1 / (proposal_scale**2), ndmin=2) + prop_state[vector][-1] = gmrf.sample_normal(mu=mu_star[-1], Q=Q, n=1) + logp_pr_g_cr += gmrf.multivariate_normal_pdf(x=prop_state[vector][-1], mu=mu_star[-1], Q=Q) + + logp_cr_g_pr += np.log(np.linalg.det(F)) + + return prop_state, logp_pr_g_cr, logp_cr_g_pr + + def matched_death_transition( + self, current_state: dict, prop_state: dict, logp_pr_g_cr: float, logp_cr_g_pr: float, deletion_index: int + ) -> Tuple[dict, float, float]: + """Generate a proposal for coefficients associated with a death move, as the reverse of the birth proposal in self.matched_birth_transition(). + + See self.matched_birth_transition() for further details. + + Args: + current_state (dict): current parameter state as dictionary. + prop_state (dict): proposed state dictionary, with updated basis matrix. + logp_pr_g_cr (float): transition probability for proposed state given current state. + logp_cr_g_pr (float): transition probability for current state given proposed state. + deletion_index (int): index of the basis element to be deleted + + Returns: + (Tuple[dict, float, float]): tuple consisting of the following elements: + prop_state (dict): proposed state with updated parameter vector. + logp_pr_g_cr (float): updated transition probability. + logp_cr_g_pr (float): updated transition probability. + + """ + vector = self.matching_params["variable"] + matrix = self.matching_params["matrix"] + proposal_scale = self.matching_params["scale"] + proposal_limits = self.matching_params["limits"] + + current_basis = current_state[matrix] + prop_basis = prop_state[matrix] + G = np.linalg.solve( + current_basis.T @ current_basis + 1e-10 * np.eye(current_basis.shape[1]), current_basis.T @ prop_basis + ) + F = np.insert(G, obj=deletion_index, values=np.eye(N=G.shape[0], M=1, k=-deletion_index).flatten(), axis=1) + mu_aug = np.linalg.solve(F, current_state[vector]) + param_del = mu_aug[deletion_index] + prop_state[vector] = np.delete(mu_aug, obj=deletion_index, axis=0) + + logp_pr_g_cr += np.log(np.linalg.det(F)) + if proposal_limits is not None: + logp_cr_g_pr += gmrf.truncated_normal_log_pdf( + param_del, np.array(0), proposal_scale, lower=proposal_limits[0], upper=proposal_limits[1] + ) + else: + logp_cr_g_pr += gmrf.multivariate_normal_pdf( + x=param_del, mu=np.array(0.0, ndmin=2), Q=np.array(1 / (proposal_scale**2), ndmin=2) + ) + + return prop_state, logp_pr_g_cr, logp_cr_g_pr + + def get_move_type(self, current_state: dict) -> bool: + """Select the type of move (birth or death) to be made at the current iteration. + + Logic for the choice of move is as follows: + - if state[self.param]=self.n_max, it is not possible to increase self.param, so a death move is chosen. + - if state[self.param]=1, it is not possible to decrease self.param, so a birth move is chosen. + - in any other state, a birth move is chosen with probability self.birth_probability, or a death move is + chosen with probability (1 - self.birth_probability). + + Args: + current_state (dict): dictionary with current parameter values. + + Returns: + (bool): if True, make a birth proposal; if False, make a death proposal. + + """ + if current_state[self.param] == self.n_max: + return False + if current_state[self.param] == 1: + return True + + return uniform.rvs() <= self.birth_probability + + def get_move_probabilities(self, current_state: dict, birth: bool) -> Tuple[float, float]: + """Get the state-dependent probabilities of the forward and reverse moves, accounting for edge cases. + + Returns a tuple of (p_birth, p_death), where these should be interpreted as follows: + Birth move: p_birth = probability of birth from CURRENT state. + p_death = probability of death from PROPOSED state. + Death move: p_death = probability of death in CURRENT state. + p_birth = probability of birth in PROPOSED state. + + In standard cases (away from the limits, assumed to be at [1, n_max]): + p_birth = q; p_death = 1 - q + + In edge cases (either where we are at one of the limits, or where our chosen move takes us into a limiting + case), we adjust the probability of either the forward or the reverse move to account for this. E.g.: if n=2, + q=0.5 and a death is proposed (i.e. proposed value n*=1), then p_death=0.5 (equal probabilities of birth/death + in CURRENT state), and p_birth=1 (because death is not possible in PROPOSED state). + + Args: + current_state (dict): dictionary with current parameter values. + birth (bool): indicator for birth or death move. + + Returns: + p_birth (float): state-dependent probability of birth move. + p_death (float): state-dependent probability of death move. + + """ + p_birth = self.birth_probability + p_death = 1.0 - self.birth_probability + + if current_state[self.param] == self.n_max: + p_death = 1.0 + if current_state[self.param] == (self.n_max - 1) and birth: + p_death = 1.0 + + if current_state[self.param] == 1: + p_birth = 1.0 + if current_state[self.param] == 2 and not birth: + p_birth = 1.0 + return p_birth, p_death diff --git a/src/openmcmc/sampler/sampler.py b/src/openmcmc/sampler/sampler.py new file mode 100644 index 0000000..77e3ae2 --- /dev/null +++ b/src/openmcmc/sampler/sampler.py @@ -0,0 +1,355 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Collection of functions defining various MCMC samplers. + +MCMCSampler is a superclass for all MCMC sampler types. This file contains several conjugate MCMC sampling algorithms, +which can be used for specific distribution combinations, and inherit directly from MCMCSampler. + +metropolis_hastings.py contains another set of algorithms, all of Metropolis-Hastings type, where a parameter proposal +followed by an accept/reject step. + +reversible_jump.py contains a generic implementation of the reversible jump algorithm. + +""" + +from abc import ABC, abstractmethod +from copy import deepcopy +from dataclasses import dataclass +from typing import Union + +import numpy as np +from scipy import sparse +from scipy.stats import gamma, norm + +from openmcmc import gmrf +from openmcmc.distribution.location_scale import Normal +from openmcmc.model import Model +from openmcmc.parameter import ( + Identity, + MixtureParameterMatrix, + MixtureParameterVector, + ScaledMatrix, +) + + +@dataclass +class MCMCSampler(ABC): + """Abstract base class for openMCMC sampling algorithms for a model parameter. + + Attributes: + param (str): label of the parameter to be sampled. + model (Model): sub-model of overall model, containing only distributions with some dependence on self.param. + max_variable_size (Union[int, tuple]): (if required) maximum size for the variable. Only relevant in cases + (e.g. RJMCMC) where variable dimension changes as a result of MCMC proposals. + + """ + + param: str + model: Model + max_variable_size: Union[int, tuple, None] = None + + def __post_init__(self): + """Extract the sub-model of distributions with some dependence on self.param.""" + self.model = self.model.conditional(self.param) + + @abstractmethod + def sample(self, current_state: dict) -> dict: + """Generate the next sample in the chain. + + Args: + current_state (dict): dictionary containing current parameter values. + + Returns: + (dict): state with the value of self.param updated to a new sample. + + """ + + def init_store(self, current_state: dict, store: dict, n_iterations: int) -> dict: + """Initialise the field in the MCMC storage dictionary for self.param. + + Args: + current_state (dict): dictionary containing current parameter values. + store (dict): dictionary to store all samples generated by the MCMC algorithm. + n_iterations (int): total number of MCMC iterations to be run. + + Returns: + (dict): storage dictionary updated with field for self.param. + + """ + if self.max_variable_size is None: + store[self.param] = np.full(shape=(np.size(current_state[self.param]), n_iterations), fill_value=np.nan) + elif isinstance(self.max_variable_size, tuple): + store[self.param] = np.full(shape=self.max_variable_size + (n_iterations,), fill_value=np.nan) + else: + store[self.param] = np.full(shape=(self.max_variable_size, n_iterations), fill_value=np.nan) + return store + + def store(self, current_state: dict, store: dict, iteration: int) -> dict: + """Store the current state of the sampled variable in the MCMC storage dictionary. + + If self.parameter is not initialised in the MCMC state, then the function generates a random sample from the + corresponding distribution in model. + + Args: + current_state (dict): dictionary with current parameter values. + store (dict): storage dictionary for MCMC samples. + iteration (int): current MCMC iteration index. + + Returns: + dict: storage dictionary updated with values from current iteration. + + """ + current_param = current_state[self.param] + + if self.max_variable_size is None: + store[self.param][:, [iteration]] = current_param + elif isinstance(self.max_variable_size, tuple): + index_list = [] + for dim in range(current_param.ndim): + index_list.append(np.arange(current_param.shape[dim], dtype=int)) + index_list.append(np.array([iteration])) + + store[self.param][np.ix_(*index_list)] = current_param.reshape(current_param.shape + (1,)) + else: + store[self.param][range(current_param.size), [iteration]] = current_param.flatten() + + return store + + +@dataclass +class NormalNormal(MCMCSampler): + """Normal-Normal conditional sampling (exploiting conjugacy). + + Sample from f(x|{b_k}) ~= [prod_k f(y_k|x)]f(x) where the components have the following form: + - Likelihoods: f(y_k|x) ~ N(y_k | d_k + A_k*a, W_k^{-1}) + - Prior: f(x) ~ N(x |m, P^{-1}) + The following features are assumed: + - There can be multiple likelihood/response distributions, but there is only one prior distribution. + - The mean of each of the response distributions must have a linear dependence on self.param. + - The prior Gaussian can be truncated (as long as the truncation points are constant), but the response + Gaussians cannot. + + If the prior Gaussian is truncated (i.e. it has specified domain limits), then those same domain limits will be + used when generating the conditional sample. + + Attributes: + _is_response (dict): dictionary containing boolean indicators for whether self.param is the response of the + distribution. If self._is_response[key] is True, then self.param is the response of the distribution. + + """ + + def __post_init__(self): + """Identify and extract the sub-model with a dependence on self.param. + + Also identify whether self.param is the response for each of the pair of Gaussian distributions. + + """ + super().__post_init__() + self._is_response = {} + for key in self.model.keys(): + self._is_response[key] = key == self.param + + def sample(self, current_state: dict) -> dict: + """Generate a sample from a Gaussian-Gaussian conditional distribution. + + For a Gaussian-Gaussian conditional distribution, the parameters are as follows: + Conditional precision: + Q = P + sum_k [A_k'*W_k*A_k] + Conditional mean: + b = P*m + sum_k [A_k'*W_k*(y_k - d_k)] + mu = Q^{-1} * b + Where the parameters are as defined in the class docstring. + + If the supplied response parameter has a second dimension, these are interpreted as repeated draws from the same + distribution, and are thus summed. The multiplication of the precision matrix by num_replicates is handled by + the grad_log_p() function of the corresponding distribution. + + Args: + current_state (dict): dictionary containing the current sampler state. + + Returns: + (dict): state with updated value for self.param. + + """ + n_param = current_state[self.param].shape[0] + Q = sparse.csc_matrix((n_param, n_param)) + b = np.zeros(shape=(n_param, 1)) + for key, dist in self.model.items(): + Q_rsp = dist.precision.predictor(current_state) + if self._is_response[key]: + Q += Q_rsp + b += Q_rsp @ dist.mean.predictor(current_state) + else: + _, Q_dist = dist.grad_log_p(current_state, self.param) + Q += Q_dist + if isinstance(dist.mean, Identity): + b += Q_rsp @ np.sum(current_state[key], axis=1, keepdims=True) + else: + predictor_exclude = dist.mean.predictor_conditional(current_state, term_to_exclude=self.param) + A = current_state[dist.mean.form[self.param]] + b += A.T @ Q_rsp @ (current_state[key] - predictor_exclude) + + dist_param = self.model[self.param] + + if dist_param.domain_response_lower is None and dist_param.domain_response_upper is None: + current_state[self.param] = gmrf.sample_normal_canonical(b, Q) + else: + current_state[self.param] = gmrf.gibbs_canonical_truncated_normal( + b, + Q, + x=current_state[self.param], + lower=dist_param.domain_response_lower, + upper=dist_param.domain_response_upper, + ) + + return current_state + + +@dataclass +class NormalGamma(MCMCSampler): + """Normal-gamma conditional sampling (exploiting conjugacy). + + Assumes that self.param is the precision parameter for a Gaussian response distribution, and that it has a gamma + prior distribution. + + Allows for the possibility that a single Gaussian response distribution might be associated with a number of + different precision parameters, through use of a MixtureParameterMatrix precision parameters. These parameters + are sampled in a loop within the sample function. + + This class samples from f(lam|y, a, b) ~ prod_k [f(y_k|lam)]f(lam|a,b) where + - Likelihoods: f(y_k|lam) ~ N(mu_k, 1/lam) + - Prior: f(lam|a, b) ~ G(a, b) + Note that it is also possible to use a more complex (e.g. dense) precision matrix scaled by a single precision + parameter, this does not fundamentally change the approach. + + Attributes: + param (str): label of parameter name to be sampled + normal_param (str): label of corresponding normal parameter + model (Model): conditional model with distributions related to param only + + """ + + def __post_init__(self): + """Complete initialization of sampler. + + Identifies the gamma distribution and its conjugate normal distribution, and attaches copies to the sampler + object. + + """ + super().__post_init__() + + nrm_prm = list(self.model.keys()) + nrm_prm.remove(self.param) + self.normal_param = nrm_prm[0] + + precision = self.model[self.normal_param].precision + + if not isinstance(precision, (Identity, ScaledMatrix, MixtureParameterMatrix)): + raise TypeError("precision must be either Identity, ScaledMatrix or MixtureParameterMatrix") + + def sample(self, current_state: dict) -> dict: + """Generate a sample from a (series of) Gaussian-Gamma conditional distribution. + + The conditional distribution for an individual parameter is: + G(a*, b*) + where: + a* = a_0 + n/2 + b* = b_0 + (y - y_hat)' * P * (y - y_hat) + where n = [dimension of normal response], P = [un-scaled precision matrix]. + + It is assumed that the precision parameter of the Gaussian distribution has a predictor_unscaled() method, + which can be used to identify the subset of the full precision matrix which is dependent on the parameter being + sampled: this is returned un-scaled by the precision parameter. + + Args: + current_state (dict): dictionary containing the current sampler state. + + Returns: + (dict): state with updated value for self.param. + + """ + precision = self.model[self.normal_param].precision + mean = self.model[self.normal_param].mean + y = current_state[self.model[self.normal_param].response] + residual = y - mean.predictor(current_state) + + a = deepcopy(self.model[self.param].shape.predictor(current_state)) + b = deepcopy(self.model[self.param].rate.predictor(current_state)) + + for k in range(current_state[self.param].shape[0]): + precision_unscaled = precision.precision_unscaled(current_state, k) + a[k] += np.sum(precision_unscaled.diagonal() > 0) / 2 + b[k] += (residual.T @ precision_unscaled @ residual).item() / 2 + no_warning_b = np.where(b == 0, np.inf, b) + no_warning_scale = np.where(b == 0, np.inf, 1 / no_warning_b) + current_state[self.param] = gamma.rvs(a, scale=no_warning_scale).reshape(current_state[self.param].shape) + return current_state + + +@dataclass +class MixtureAllocation(MCMCSampler): + """Conditional conjugate sampling of the allocation in a mixture distribution. Can be used with any kind of mixture distribution. + + This class samples from: + f(z|y, lam, tht) ~ f(y|z, lam)f(z|tht) + where: + - Likelihood: f(y|z, lam); Gaussian distribution, with different characteristics depending on z. + - Prior: f(z|tht); categorical distribution, with prior allocation probabilities tht. + + Attributes: + response_param (str): name of the response parameter associated with the allocation parameter. + + """ + + response_param: Union[str, None] = None + + def __post_init__(self): + """Subset only model elements relevant for this sampler.""" + self.model = Model([self.model[self.param], self.model[self.response_param]]) + + if not isinstance(self.model[self.response_param], Normal): + raise TypeError("Mixture model currently only implemented for Normal case") + + if not isinstance(self.model[self.response_param].mean, MixtureParameterVector): + raise TypeError("Mean must be of type MixtureParameterVector") + + if not isinstance(self.model[self.response_param].precision, MixtureParameterMatrix): + raise TypeError("Mean must be of type MixtureParameterMatrix") + + def sample(self, current_state: dict) -> dict: + """Generate sample of a parameter allocation given current state of the sampler. + + Computes the conditional allocation probability for each element of the response to each component of the + mixture, then samples an allocation based on the probabilities. + + The conditional distribution is: + Cat([gam_1, gam_2,..., gam_m]) + where: + gam_k = f(y|z_k, lam) * tht_k / W + W = sum_k [f(y|z_k, lam) * tht_k / Z] + + Args: + current_state (dict): dictionary containing the current sampler state. + + Returns: + (dict): state with updated value for self.param. + + """ + allocation_prior = self.model[self.param].prob.predictor(current_state) + n_response = current_state[self.response_param].shape[0] + component_mean = current_state[self.model[self.response_param].mean.param] + component_precision = current_state[self.model[self.response_param].precision.param] + + allocation_prob = np.empty((n_response, allocation_prior.shape[1])) + for k in range(allocation_prior.shape[1]): + allocation_prob[:, [k]] = allocation_prior[:, [k]] * norm.pdf( + current_state[self.response_param], loc=component_mean[k], scale=1 / np.sqrt(component_precision[k]) + ) + + allocation_prob = allocation_prob / np.sum(allocation_prob, axis=1).reshape((allocation_prob.shape[0], 1)) + U = np.random.rand(n_response, 1) + current_state[self.param] = np.atleast_2d(np.sum(U > np.cumsum(allocation_prob, axis=1), axis=1)).T + + return current_state diff --git a/tests/test_distribution.py b/tests/test_distribution.py new file mode 100644 index 0000000..43ae166 --- /dev/null +++ b/tests/test_distribution.py @@ -0,0 +1,298 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Unit testing for distributions.""" + +from copy import deepcopy + +import numpy as np +import pytest +from scipy import sparse, stats + +from openmcmc.distribution.distribution import ( + Categorical, + Distribution, + Gamma, + Poisson, + Uniform, +) +from openmcmc.distribution.location_scale import LogNormal, Normal +from openmcmc.model import Model +from openmcmc.parameter import ( + Identity, + LinearCombination, + MixtureParameterMatrix, + MixtureParameterVector, + ScaledMatrix, +) + + +@pytest.fixture( + params=[(1, 1, 1), (10, 5, 7), (10, 5, 1), (10, 1, 7), (1, 5, 7)], + ids=["all_size_1", "all > 1", "p=1", "d=1", "n=1"], + name="state", +) +def fix_state(request): + """Fixture Defining a state vector which has all possible types of data for use with any parameter type. + + Args: + request.param defines tuple (n, p, p2) for different size combinations of the inputs + + n is used in to represent a "number of observations" type parameter + d is used in to represent a "number of dimensions" type parameter + p is used in to represent a different "number of coefficients" type parameters in cases + such as the linear combination where we might want two different regressor terms with different numbers of + coefficients + + Returns + state (dict) dictionary of parameter values + + """ + [n, d, p] = request.param + + state = {} + state["scalar"] = np.random.rand(1, 1) + 1 + state["scalar_2"] = np.random.rand(1, 1) + 1 + state["observation_1_n"] = np.random.rand(1, n) + 1 + state["observation_d_n"] = np.random.rand(d, n) + 1 + state["vector_d"] = np.random.rand(d, 1) + 1 + state["vector_p"] = np.random.rand(p, 1) + 1 + state["count_1"] = np.random.randint(10, size=(1, 1)) + state["count_d"] = np.random.randint(10, size=(d, 1)) + state["sparse_identity"] = sparse.eye(d, format="csr") + state["identity"] = np.eye(d) + state["matrix"] = np.random.rand(d, p) + state["allocation"] = np.mod(np.array(range(d), ndmin=2).T, p) + state["probability_d"] = stats.dirichlet.rvs(np.ones(p), size=d) + state["allocation_d_n"] = np.random.randint(p, size=(d, n)) + return state + + +# Normal Parameters +@pytest.fixture( + params=[ + Normal("observation_1_n", mean="scalar", precision="scalar_2"), + Normal("observation_d_n", mean=Identity("vector_d"), precision=Identity("identity")), + Normal("observation_d_n", mean=LinearCombination(form={"vector_p": "matrix"}), precision="sparse_identity"), + Normal( + "observation_d_n", + mean=MixtureParameterVector("vector_p", "allocation"), + precision=ScaledMatrix("sparse_identity", "scalar_2"), + ), + Normal("observation_d_n", mean="vector_d", precision=MixtureParameterMatrix("vector_p", "allocation")), + Normal("observation_1_n", mean="scalar", precision="scalar_2", domain_response_lower=np.array(-2)), + Normal("observation_d_n", mean="vector_d", precision="sparse_identity", domain_response_upper=np.array(10)), + LogNormal("observation_1_n", mean="scalar", precision="scalar_2"), + LogNormal("observation_d_n", mean="vector_d", precision="identity"), + LogNormal("observation_d_n", mean=LinearCombination(form={"vector_p": "matrix"}), precision="sparse_identity"), + LogNormal( + "observation_d_n", + mean=MixtureParameterVector("vector_p", "allocation"), + precision=ScaledMatrix("sparse_identity", "scalar_2"), + ), + LogNormal("observation_d_n", mean="vector_d", precision=MixtureParameterMatrix("vector_p", "allocation")), + Gamma("observation_1_n", shape="scalar", rate="scalar_2"), + Gamma("observation_d_n", shape=LinearCombination(form={"vector_p": "matrix"}), rate="scalar_2"), + Gamma("observation_d_n", shape="scalar", rate=MixtureParameterVector("vector_p", "allocation")), + Poisson("count_1", rate="scalar"), + Poisson("count_d", rate=LinearCombination(form={"vector_p": "matrix"})), + Poisson("count_d", rate=MixtureParameterVector("vector_p", "allocation")), + Uniform("observation_d_n", domain_response_lower=1, domain_response_upper=2), + Categorical("allocation_d_n", "probability_d"), + ], + ids=[ + "UnivariateNormal", + "MVNormal", + "LinCombSparseMVN", + "MixMeanScaledMatrixMVN", + "MixtureMatrixMVN", + "TruncateNormal", + "TruncateMVN", + "UnivariateLognormal", + "MVLogNormal", + "LinCombSparseLogNorm", + "MixMeanScaledMatrixLogNorm", + "MixtureMatrixLogNorm", + "ScalarGamma", + "LinCombGamma", + "MixRateGamma", + "ScalarPoisson", + "LinCombPoisson", + "MixRatePoisson", + "Uniform", + "Categorical", + ], + name="distribution", +) +def fix_distribution(request): + """Define distribution to test. + + Returns Distribution + + """ + return request.param + + +def test_log_p(distribution: Distribution, state: dict): + """Log_p test. + + Test 1. Check the by observation log_p is correct shape + Test 2. Check the summed log_p is size 1 + Test 3. Generate Random numbers from true distribution and check profile likelihood is + at maximum around the true parameters + Test 4. Generate Random numbers from true distribution and check gradient is +ve below true parameters and -ve above + + Generate random numbers from distribution and compute likelihood then go + through parameters in inference_param and varies up and down to check likelihood gets worse. + + Args: + distribution (Distribution): distribution object defined by fix_distribution + state (dict): state object defined by fix_state + + """ + + p, n = state[distribution.response].shape + + log_p_all = distribution.log_p(state, by_observation=True) + assert log_p_all.size == n + + log_p_tru = distribution.log_p(state) + assert log_p_tru.size == 1 + + n = 300 + state_profile = deepcopy(state) + state_profile[distribution.response] = distribution.rvs(state_profile, n=n) + + assert state_profile[distribution.response].shape == (p, n) + log_p_tru = distribution.log_p(state_profile) + + if isinstance(distribution, Categorical): + assert np.max(state_profile[distribution.response]) <= state_profile["probability_d"].shape[1] - 1 + + # shift probability not response + state_profile["probability_d"] = np.roll(state_profile["probability_d"], 1, axis=1) + + log_p_perm = distribution.log_p(state_profile) + assert log_p_tru >= log_p_perm + + else: + for param in distribution.param_list: + if param in [distribution.response, "allocation"]: + continue + + state_profile_high = deepcopy(state_profile) + state_profile_high[param] = state_profile_high[param] * 10 + log_p_high = distribution.log_p(state_profile_high) + assert log_p_tru > log_p_high + + state_profile_low = deepcopy(state_profile) + state_profile_low[param] = state_profile_low[param] * 0.1 + log_p_low = distribution.log_p(state_profile_low) + assert log_p_tru > log_p_low + + +def test_grad_log_p(distribution: Distribution, state: dict): + """grad_log_p test: Test 1. Check grad_log_p is correct size Test 2. Check grad_log_p is matches finite difference + (for cases where analytical gradients exist) + + Only perform test of calculating gradients for non-integer type parameters + + Generate random numbers from distribution and compute likelihood then go + through parameters in inference_param and varies up and down to check likelihood gets worse. + + Args: + distribution (Distribution): distribution object defined by fix_distribution + state (dict): state object defined by fix_state + + """ + + for param in distribution.param_list: + if param in ["allocation", "allocation_d_n", "count_1", "count_d"]: + continue + + grad_log_p = distribution.grad_log_p(state, param, hessian_required=False) + assert grad_log_p.shape == state[param].shape + + if isinstance(distribution, (Normal, LogNormal)): + grad_log_p_diff = distribution.grad_log_p_diff(state, param) + assert np.allclose(grad_log_p, grad_log_p_diff, rtol=1e-3) + + +def test_hessian_log_p(distribution: Distribution, state: dict): + """hessian_log_p test: Test 1. Check hessian_log_p is correct size Test 2. Check hessian_log_p is symmetric Test 3. + Check hessian_log_p is matches finite difference (for cases where analytical gradients exist) + + Generate random numbers from distribution and compute likelihood then go + through parameters in inference_param and varies up and down to check likelihood gets worse. + + Args: + distribution (Distribution): distribution object defined by fix_distribution + state (dict): state object defined by fix_state + + """ + + for param in distribution.param_list: + if param in ["allocation", "allocation_d_n", "count_1", "count_d"]: + continue + + _, hessian_log_p = distribution.grad_log_p(state, param) + p = np.prod(state[param].shape) + assert hessian_log_p.shape == (p, p) + + if sparse.issparse(hessian_log_p): + hessian_log_p = hessian_log_p.toarray() + + assert np.linalg.norm(hessian_log_p - hessian_log_p.T) < 1e-4 + + if isinstance(distribution, (Normal, LogNormal)): + hessian_log_p_diff = distribution.hessian_log_p_diff(state, param) + assert np.linalg.norm(hessian_log_p - hessian_log_p_diff) <= 1e-3 + + +def test_model_conditional(): + """Check that model conditional returns right number of elements.""" + model = Model( + [ + Normal("A", mean="B", precision="C"), + Normal("B", mean="B_mean", precision="B_precision"), + Gamma("C", rate="C_rate", shape="C_shape"), + ] + ) + + assert isinstance(model, dict) + + assert len(model.conditional("A")) == 1 + assert len(model.conditional("B")) == 2 + assert len(model.conditional("C")) == 2 + assert len(model.conditional("B_mean")) == 1 + assert len(model.conditional("B_precision")) == 1 + assert len(model.conditional("C_rate")) == 1 + assert len(model.conditional("C_shape")) == 1 + + +def test_model_log_p(state: dict): + """Test log likelihood and grad_log_p is computed correctly. + + state (dict): state object defined by fix_state + + """ + model = Model( + [ + Normal("observation_d_n", mean=LinearCombination(form={"vector_p": "matrix"}), precision="sparse_identity"), + Normal("observation_1_n", mean="scalar", precision="scalar_2"), + Gamma("observation_1_n", shape="scalar", rate="scalar_2"), + Poisson("count_1", rate="scalar"), + ] + ) + + assert model.log_p(state).size == 1 + + p = state["vector_p"].shape[0] + + grad_1, hessian = model.grad_log_p(state, "vector_p", hessian_required=True) + grad_2 = model.grad_log_p(state, "vector_p", hessian_required=False) + + assert np.allclose(grad_1, grad_2, 1e-10) + assert grad_1.shape == (p, 1) + assert hessian.shape == (p, p) diff --git a/tests/test_grmf.py b/tests/test_grmf.py new file mode 100644 index 0000000..4ee7299 --- /dev/null +++ b/tests/test_grmf.py @@ -0,0 +1,342 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Unit testing for gmrf module.""" + +from typing import Union + +import numpy as np +import pandas as pd +import pytest +from scipy import sparse +from scipy.stats import chi2, multivariate_normal, norm, ttest_ind + +from openmcmc import gmrf + + +def rand_precision(d: int = 1, is_time: bool = False, is_sparse: bool = False) -> Union[np.ndarray, sparse.csc_matrix]: + """Generate random observations locations to pass into a precision matrix and generate a precision matrix. + + observations are generated using exponential inter arrivals (Poisson process equivalent) + + Function is used in testing + + Args: + d (int, optional): dimension of precision matrix. Defaults to 1. + is_time (bool, optional): Flag if generated from timestamp. Defaults to False. + is_sparse (bool, optional): Flag if generated as sparse. Defaults to False. + + Returns: + Union[np.ndarray, sparse.csc_matrix] d x d precision matrix + + """ + + s = np.cumsum(np.random.exponential(scale=1.0, size=d)) + + if is_time: + s = pd.Timestamp.utcnow() + pd.to_timedelta(s, unit="sec") + return gmrf.precision_temporal(s, is_sparse=is_sparse) + + return gmrf.precision_irregular(s, is_sparse=is_sparse) + + +@pytest.mark.parametrize("n", [1, 100]) +@pytest.mark.parametrize("d", [1, 3, 10], ids=["d=1", "d=3", "d=10"]) +@pytest.mark.parametrize("is_sparse", [True, False], ids=["sparse", "full"]) +def test_sample_normal(d: int, is_sparse: bool, n: int): + """Test that sample_normal gives s output consistent with Mahalanobis distance against chi2 distribution with d + degrees of freedom. + + Args: + d (int): dimension of precision + is_sparse (bool): is precision generated as sparse + + """ + mu = np.random.rand(d, 1) + Q = rand_precision(d, is_sparse=is_sparse) + if is_sparse: + Q = Q + sparse.eye(d) + else: + Q = Q + np.eye(d) + + rand_norm = gmrf.sample_normal(mu=mu, Q=Q, n=n) + + rsd = rand_norm - mu + dist = np.diag(rsd.T @ Q @ rsd) + + P = 1 - chi2.cdf(dist, df=d) + alpha = 0.01 + + if n == 1: + assert P > alpha + else: + assert np.sum(P > alpha) > n * (1 - 3 * alpha) + + +@pytest.mark.parametrize("d", [1, 2, 5]) +@pytest.mark.parametrize("is_sparse", [True, False], ids=["sparse", "full"]) +@pytest.mark.parametrize("upper", [np.inf, 1.3]) +@pytest.mark.parametrize("lower", [-np.inf, -0.2]) +def test_compare_truncated_normal(d: int, is_sparse: bool, lower: np.ndarray, upper: np.ndarray): + """Test that runs both sample_truncated_normal with both methods rejection sampling and Gibbs sampling to show they + give consistent results and check both output consistent within upper and lower bounds. + + Args: + d (int): dimension of precision- + is_sparse (bool): is precision generated as sparse + lower (np.ndarray): lower bound for truncated sampling + upper (np.ndarray): upper bound for truncated sampling + + """ + n = 100 + mu = np.linspace(0, 1, d).reshape((d, 1)) + Q = rand_precision(d, is_sparse=is_sparse) + if is_sparse: + Q = Q + sparse.eye(d) + else: + Q = Q + np.eye(d) + + rand_norm_1 = gmrf.sample_truncated_normal(mu=mu, Q=Q, n=n, lower=lower, upper=upper, method="Gibbs") + rand_norm_2 = gmrf.sample_truncated_normal(mu=mu, Q=Q, n=n, lower=lower, upper=upper, method="Rejection") + + if lower != -np.inf: + assert np.all(rand_norm_1 > lower) + assert np.all(rand_norm_2 > lower) + + if upper != np.inf: + assert np.all(rand_norm_1 < upper) + assert np.all(rand_norm_2 < upper) + + # t test to compare means + [_, p_value] = ttest_ind(rand_norm_1, rand_norm_2, axis=1, equal_var=False) + + alp = 0.001 + + assert np.all(p_value < (1 - alp)) + + +@pytest.mark.parametrize("mean", [0.5, 1.3]) +@pytest.mark.parametrize("scale", [0.1, 1]) +@pytest.mark.parametrize("upper", [np.inf, None, 1.3]) +@pytest.mark.parametrize("lower", [-np.inf, None, -0.2]) +def test_truncated_normal_rv(mean: np.ndarray, scale: np.array, lower: np.ndarray, upper: np.ndarray): + """Test that checks the univariate truncated normal against known mean + https://en.wikipedia.org/wiki/Truncated_normal_distribution. + + Args: + mean (np.ndarray): mean of truncated sampling + scale (np.ndarray): scale of truncated sampling + lower (np.ndarray): lower bound for truncated sampling + upper (np.ndarray): upper bound for truncated sampling + + """ + + # Rejection Sampling version + Z = gmrf.truncated_normal_rv(mean=mean, scale=scale, lower=lower, upper=upper, size=10000) + + if lower is None: + lower = -np.inf + + if upper is None: + upper = np.inf + + alp = (lower - mean) / scale + bet = (upper - mean) / scale + true_mean = mean + (norm.pdf(alp) - norm.pdf(bet)) / (norm.cdf(bet) - norm.cdf(alp)) * scale + + assert np.isclose(np.mean(Z), true_mean, atol=1e-1 * scale) + + +@pytest.mark.parametrize("d", [1, 3, 10]) +@pytest.mark.parametrize("is_sparse", [True, False], ids=["sparse", "full"]) +def test_sample_normal_canonical(d: int, is_sparse: bool): + """Test that sample_normal_canonical gives output consistent with Mahalanobis distance against chi2 distribution + with d degrees of freedom. + + Args: + d (int): dimension of precision + is_sparse (bool): is precision generated as sparse + + """ + b = np.random.rand(d, 1) + Q = rand_precision(d, is_sparse=is_sparse) + if is_sparse: + Q = Q + sparse.eye(d) + else: + Q = Q + np.eye(d) + + mu = gmrf.solve(Q, b).reshape(b.shape) + + rand_norm = gmrf.sample_normal_canonical(b=b, Q=Q) + + dist = (rand_norm - mu).T @ Q @ (rand_norm - mu) + + P = 1 - chi2.cdf(dist, df=d) + alpha = 0.01 + + assert P > alpha + + +@pytest.mark.parametrize("d", [1, 10]) +@pytest.mark.parametrize("is_sparse", [True, False], ids=["sparse", "full"]) +@pytest.mark.parametrize("upper", [np.inf, 0.7]) +@pytest.mark.parametrize("lower", [-np.inf, 0.5]) +def test_gibbs_truncated_normal_canonical(d: int, is_sparse: bool, lower: np.ndarray, upper: np.ndarray): + """Test that gibbs_canonical_truncated_normal gives output within 5 standard deviations according to Mahalanobis + distance. + + Args: + d (int): dimension of precision + is_sparse (bool): is precision generated as sparse + lower (np.ndarray): lower bound for truncated sampling + upper (np.ndarray): upper bound for truncated sampling + + """ + b = np.random.rand(d, 1) + Q = rand_precision(d, is_sparse=is_sparse) + if is_sparse: + Q = Q + sparse.eye(d) + else: + Q = Q + np.eye(d) + + x = np.ones(shape=(d, 1)) * 0.6 + + rand_norm = gmrf.gibbs_canonical_truncated_normal(b=b, Q=Q, lower=lower, upper=upper, x=x) + + if lower != -np.inf: + assert np.all(rand_norm > lower) + + if upper != np.inf: + assert np.all(rand_norm < upper) + + +@pytest.mark.parametrize("d", [1, 2, 5]) +@pytest.mark.parametrize("n", [1, 10]) +@pytest.mark.parametrize("is_sparse", [True, False], ids=["sparse", "full"]) +def test_multivariate_normal_pdf(d: int, n: int, is_sparse: bool): + """Test multivariate normal pdf. + + Tests size of output as well as comparing with scipy.stats version + + Args: + d (int): dimension for Gaussian + n (int): _description_ + is_sparse (bool): _description_ + + """ + + mu = np.linspace(0, 1, d).reshape((d, 1)) + Q = rand_precision(d, is_sparse=is_sparse) + if is_sparse: + Q = Q + sparse.eye(d) + else: + Q = Q + np.eye(d) + x = np.random.rand(d, n) + + log_p = gmrf.multivariate_normal_pdf(x, mu=mu, Q=Q, by_observation=True) + assert log_p.size == n + + log_p = gmrf.multivariate_normal_pdf(x, mu=mu, Q=Q, by_observation=False) + assert log_p.size == 1 + + if is_sparse and d > 1: + Q = Q.toarray() + + if d == 1: + log_p_scipy = np.sum(norm.logpdf(x.T, loc=mu.flatten(), scale=np.sqrt(1 / Q))) + else: + log_p_scipy = np.sum(multivariate_normal.logpdf(x.T, mean=mu.flatten(), cov=np.linalg.inv(Q))) + + assert np.allclose(log_p, log_p_scipy, atol=1e-5) + + +@pytest.mark.parametrize("d", [1, 3, 10]) +@pytest.mark.parametrize("is_time", [True, False]) +@pytest.mark.parametrize("is_sparse", [True, False], ids=["sparse", "full"]) +def test_precision(d: int, is_time: bool, is_sparse: bool): + """Test for generation of precision matrix from first order RW. + + Check sum to 0 and symmetry + + Args: + d (int): dimension of precision + is_time (bool): is precision generated from timestamp + is_sparse (bool): is precision generated as sparse + + """ + + P = rand_precision(d, is_time=is_time, is_sparse=is_sparse) + + assert P.shape[0] == d + assert P.shape[1] == d + assert 0 == pytest.approx(np.sum(abs(P - P.T))) + + if d > 1: + assert 0 == pytest.approx(np.sum(P)) + + +@pytest.mark.parametrize("d", [1, 3, 10, 30]) +@pytest.mark.parametrize("is_sparse", [True, False], ids=["sparse", "full"]) +@pytest.mark.parametrize("lower", [True, False], ids=["L", "U"]) +def test_solve(d: int, is_sparse: bool, lower: bool): + """Test solve functions against np.linalg.solve. + + Args: + d (int): dimension of problem + is_sparse (bool): is precision matrix sparse + lower (bool) is cholesky done using lower or triangular version + + """ + + a = rand_precision(d, is_sparse=is_sparse) + + if is_sparse: + a = a + sparse.eye(d) + else: + a = a + np.eye(d) + + b = np.random.rand(d, 2) + + # Solve version + x = gmrf.solve(a, b) + + # Cholesky version + C = gmrf.cholesky(a, lower) + x_ch = gmrf.cho_solve((C, lower), b) + + # np version + if sparse.issparse(a): + a = a.toarray() + x_np = np.linalg.solve(a, b) + + assert 0 == pytest.approx(np.sum(abs(x - x_np))) + assert 0 == pytest.approx(np.sum(abs(x_ch - x_np))) + + +@pytest.mark.parametrize("d", [1, 5, 10, 50]) +def test_sparse_cholesky(d: int): + """Test sparse_cholesky function against the non-sparse version. + + Args: + d (int): dimension of precision + + """ + P = rand_precision(d, is_time=False, is_sparse=True) + P = P + sparse.eye(d) + + L = gmrf.sparse_cholesky(P) + + if sparse.issparse(L): + L = L.toarray() + if sparse.issparse(P): + P = P.toarray() + + L_np = np.linalg.cholesky(P) + + # check is a proper decomposition + assert 0 == pytest.approx(np.sum(abs(P - L @ L.T))) + # check lower triangular + assert 0 == np.sum(np.triu(L, k=1)) + # check same as non sparse version + assert 0 == pytest.approx(np.sum(abs(L - L_np))) diff --git a/tests/test_mcmc.py b/tests/test_mcmc.py new file mode 100644 index 0000000..357db55 --- /dev/null +++ b/tests/test_mcmc.py @@ -0,0 +1,138 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Testing for the main MCMC class.""" + +import numpy as np +import pytest + +from openmcmc.distribution.distribution import Gamma +from openmcmc.distribution.location_scale import Normal +from openmcmc.mcmc import MCMC +from openmcmc.model import Model +from openmcmc.parameter import LinearCombination, ScaledMatrix +from openmcmc.sampler.sampler import NormalGamma, NormalNormal + + +@pytest.fixture(name="model") +def fix_model(): + """Fix the model structure to be used in the tests.""" + model = Model( + [ + Normal( + "y", mean=LinearCombination(form={"beta": "X"}), precision=ScaledMatrix(matrix="P_tau", scalar="tau") + ), + Normal("beta", mean="mu", precision="sigma"), + Gamma("tau", shape="a", rate="b"), + Gamma("sigma", shape="c", rate="d"), + ] + ) + return model + + +@pytest.fixture(params=[1, 2, 3], ids=["n_smp=1", "n_smp=2", "n_smp=3"], name="sampler") +def fix_sampler(request, model): + """Define the set of models to be used in MCMC class.""" + n_samplers = request.param + sampler = [NormalNormal("beta", model)] + + if n_samplers >= 2: + sampler.append(NormalGamma("tau", model)) + if n_samplers >= 3: + sampler.append(NormalGamma("sigma", model)) + return sampler + + +@pytest.fixture( + params=[ + (int(0), int(0)), + (int(2.5), int(1.5)), + (float(2.5), float(1.5)), + (np.array([1.1, 3.2, 5.3]), np.array([2.4, 4.1, 6.2])), + ([1.1, 3.2, 5.3], [2.4, 4.1, 6.2]), + ], + ids=["all_zero", "tau_beta_integer", "tau_beta_float", "tau_beta_np_array", "tau_beta_list"], + name="state", +) +def fix_state(request): + """Define the initial state for the MCMC.""" + [beta, tau] = request.param + state = {"count": 0, "beta": beta, "tau": tau, "sigma": 10, "P_tau": np.eye(np.array(beta, ndmin=2).shape[0])} + return state + + +@pytest.fixture( + params=[(0, 4000), (2000, 4000), (0, 6000), (2000, 6000)], + ids=["n_burn=0,n_iter=4000", "n_burn=non-zero,n_iter=4000", "n_burn=0,n_iter=6000", "n_burn=non-zero,n_iter=6000"], + name="nburn_niter", +) +def fix_nburn_niter(request): + """Define the initial state for the MCMC.""" + [n_burn, n_iter] = request.param + nburn_niter = {"nburn": n_burn, "niter": n_iter} + + return nburn_niter + + +def test_run_mcmc(state: dict, sampler: list, model: Model, nburn_niter: dict, monkeypatch): + """Test run_mcmc function Checks size is correct for the output parameters of the function (state and store) based + on the number of iterations (n_iter) and number of burn (n_burn), i.e., + + Args: + state: dictionary + model: Model input + nburn_niter: dictionary of mcmc settings + monkeypatch object for avoiding computationally expensive mcmc sampler. + + """ + + # set up samplers + def mock_sample(self, state_in): + state_in["count"] = state_in["count"] + 1 + return state_in + + def mock_store(self, current_state, store, iteration): + store["count"] = store["count"] + 1 + return store + + def mock_log_p(self, current_state): + return 0 + + monkeypatch.setattr(NormalNormal, "sample", mock_sample) + monkeypatch.setattr(NormalNormal, "store", mock_store) + monkeypatch.setattr(NormalGamma, "sample", mock_sample) + monkeypatch.setattr(NormalGamma, "store", mock_store) + monkeypatch.setattr(Model, "log_p", mock_log_p) + + M = MCMC(state, sampler, model, n_burn=nburn_niter["nburn"], n_iter=nburn_niter["niter"]) + M.store["count"] = 0 + M.run_mcmc() + assert M.state["count"] == (M.n_iter + M.n_burn) * len(sampler) + assert M.store["count"] == M.n_iter * len(sampler) + + +def test_post_init(state: dict, sampler: list, model: Model, nburn_niter: dict): + """This function test __pos__init function to check returned store and state parameters are np.array of the + dimension n * 1 + + Args: + state: dictionary + nburn_niter: integer + model: + + """ + M = MCMC(state, sampler, model, n_iter=nburn_niter["niter"]) + + assert isinstance(M.state["count"], np.ndarray) + assert M.state["count"].ndim == 2 + + assert isinstance(M.state["beta"], np.ndarray) + assert M.state["beta"].ndim == 2 + assert (len(M.store) - 1) * (M.store["beta"]).shape[1] == len(sampler) * nburn_niter["niter"] + assert M.store["log_post"].size == nburn_niter["niter"] + + if len(sampler) > 1: + assert isinstance(M.state["tau"], np.ndarray) + assert isinstance(M.store["tau"], np.ndarray) + assert M.state["tau"].ndim == 2 diff --git a/tests/test_model.py b/tests/test_model.py new file mode 100644 index 0000000..943e1ae --- /dev/null +++ b/tests/test_model.py @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Test for Model class, which combines multiple distributions.""" + +import numpy as np +import pytest + +from openmcmc.distribution.location_scale import Normal +from openmcmc.model import Model +from openmcmc.parameter import LinearCombination, ScaledMatrix + + +@pytest.fixture( + params=[(1, 1), (1, 7), (13, 1), (13, 7)], ids=["n=1 p=1", "n=1 p=7", "n=13 p=1", "n=13 p=7"], name="state" +) +def fix_state(request): + """Fix state for use in the tests.""" + [n, p] = request.param + state = {} + state["theta"] = np.random.rand(p, 1) + state["Q_response"] = (1 / 0.01**2) * np.eye(n) + state["basis_matrix"] = np.random.rand(n, p) + state["response"] = state["basis_matrix"] @ state["theta"] + np.linalg.solve( + np.sqrt(state["Q_response"]), np.random.normal(size=(n, 1)) + ) + state["prior_mean"] = np.zeros(shape=(p, 1)) + state["tau"] = np.array([1 / 10**2], ndmin=2) + state["prior_matrix"] = np.eye(p) + return state + + +@pytest.fixture(name="model") +def fix_model(): + """Fix the model for testing. + + Model consists of a normal distribution for response given parameter, and a normal prior distribution for the + parameter. Measurement error precision and prior normal parameters are all fixed. + + """ + response_mean = LinearCombination(form={"theta": "basis_matrix"}) + prior_precision = ScaledMatrix(matrix="prior_matrix", scalar="tau") + return Model( + [ + Normal(response="response", mean=response_mean, precision="Q_response"), + Normal(response="theta", mean="prior_mean", precision=prior_precision), + ] + ) + + +def test_gradient(model, state): + """Test the combined gradient function for the model. + + Checks that the gradient and Hessian returned by Model.grad_log_p() are indeed the sum of the gradients from the two + components of the supplied model. + + """ + grad_from_model, hess_from_model = model.grad_log_p(state, param="theta", hessian_required=True) + grad_resp, hess_resp = model["response"].grad_log_p(state, param="theta", hessian_required=True) + grad_prior, hess_prior = model["theta"].grad_log_p(state, param="theta", hessian_required=True) + assert np.allclose(grad_from_model, grad_resp + grad_prior) + assert np.allclose(hess_from_model, hess_resp + hess_prior) + + +def test_log_p(model, state): + """Test the combined log-density function for the model. + + Checks that Model.log_p() returns the same as the sum of the two components of the supplied model. + + """ + log_p_model = model.log_p(state) + log_p_resp = model["response"].log_p(state) + log_p_prior = model["theta"].log_p(state) + assert np.allclose(log_p_model, log_p_resp + log_p_prior) diff --git a/tests/test_parameter.py b/tests/test_parameter.py new file mode 100644 index 0000000..ec2f505 --- /dev/null +++ b/tests/test_parameter.py @@ -0,0 +1,326 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Unit testing for the parameter module. + +There are two fixtures for settings up a parameter object and a state dictionary for setting up testing + +Not yet tested is parameters to exclude + +""" + +from copy import deepcopy +from typing import Union + +import numpy as np +import pytest +from scipy import sparse + +from openmcmc.parameter import ( + Identity, + LinearCombination, + LinearCombinationWithTransform, + MixtureParameter, + MixtureParameterMatrix, + MixtureParameterVector, + Parameter, + ScaledMatrix, +) + + +@pytest.fixture( + params=[(1, 1, 1), (10, 9, 7), (10, 9, 1), (10, 1, 7), (1, 9, 7)], + ids=["all_size_1", "all > 1", "p2=1", "p=1", "n=1"], + name="state_tuple", +) +def fix_state(request): + """Fixture Defining a state vector which has all possible types of data for use with any parameter type. + + Args: + request.param defines tuple (n, p, p2) for different size combinations of the inputs + + n is used in to represent a "number of observations" type parameter + p is used in to represent a "number of coefficients" type parameter + p2 is used in to represent a different "number of coefficients" type parameters in cases + such as the linear combination where we might want two different regressor terms with different numbers of + coefficients + + Returns + tuple of (state, n, p) + + """ + [n, p, p2] = request.param + + state = {} + state["scalar"] = np.random.rand(1, 1) + state["vector"] = np.random.rand(p, 1) + state["matrix"] = np.random.rand(n, p) + state["vector_2"] = np.random.rand(p2, 1) + state["matrix_2"] = np.random.rand(n, p2) + state["diagonal_matrix"] = np.diag(np.random.rand(p)) + state["square_matrix"] = np.random.rand(p, p) + state["square_matrix_2"] = np.random.rand(p2, p2) + # 0:p-1 repeated up to length n + state["allocation"] = np.mod(np.array(range(n), ndmin=2).T, p) + + return state, n, p + + +@pytest.fixture( + params=[ + Identity(form="scalar"), + Identity(form="vector"), + Identity(form="matrix"), + LinearCombination(form={"vector": "matrix"}), + LinearCombination(form={"vector": "matrix", "vector_2": "matrix_2"}), + LinearCombinationWithTransform(form={"vector": "matrix"}, transform={"vector": True}), + LinearCombinationWithTransform(form={"vector": "matrix"}, transform={"vector": False}), + LinearCombinationWithTransform( + form={"vector": "matrix", "vector_2": "matrix_2"}, transform={"vector": True, "vector_2": True} + ), + LinearCombinationWithTransform( + form={"vector": "matrix", "vector_2": "matrix_2"}, transform={"vector": True, "vector_2": False} + ), + LinearCombinationWithTransform( + form={"vector": "matrix", "vector_2": "matrix_2"}, transform={"vector": False, "vector_2": True} + ), + LinearCombinationWithTransform( + form={"vector": "matrix", "vector_2": "matrix_2"}, transform={"vector": False, "vector_2": False} + ), + ScaledMatrix(scalar="scalar", matrix="matrix"), + ScaledMatrix(scalar="scalar", matrix="diagonal_matrix"), + ScaledMatrix(scalar="scalar", matrix="square_matrix"), + MixtureParameterVector(param="vector", allocation="allocation"), + MixtureParameterMatrix(param="vector", allocation="allocation"), + ], + ids=[ + "Identity_scalar", + "Identity_vector", + "Identity_matrix", + "LinearCombination_1term", + "LinearCombination_2terms", + "LinearCombinationTransform_1term_T", + "LinearCombinationTransform_1term_F", + "LinearCombinationTransform_2terms_TT", + "LinearCombinationTransform_2terms_TF", + "LinearCombinationTransform_2terms_FT", + "LinearCombinationTransform_2terms_FF", + "ScaledMatrix_matrix", + "ScaledMatrix_diagonal_matrix", + "ScaledMatrix_square_matrix", + "MixtureParameterVector", + "MixtureParameterMatrix", + ], + name="parameter", +) +def fix_parameter(request): + """Fixture for defining different parameter types. + + Returns: + Parameter: particular parameter type + + """ + return request.param + + +def is_diag(A: Union[np.ndarray, sparse.csr_matrix]) -> bool: + """Checks if a matrix is diagonal. + + Args: + A (Union[np.ndarray, sparse.csr_matrix]): Matrix + + Returns: + bool: True if matrix is diagonal + + """ + if A.size == 1: + return True + + if sparse.issparse(A): + A = A.toarray() + + return np.count_nonzero(A - np.diag(np.diagonal(A))) == 0 + + +def test_predictor(parameter: Parameter, state_tuple: tuple): + """Compute predictor given parameter and state object. + + Test size is as expected. This is different for each class, so is defined per case. + + Args: + parameter : Parameter choice defined by fix_parameter + state_tuple (tuple): a tuple (dict, n , p) where dict is a dictionary of state values, + n and p are sizes. For more detail see fix_state + + """ + state, n, _ = state_tuple + + predictor = parameter.predictor(state) + + if isinstance(parameter, Identity): + assert predictor.shape == state[parameter.form].shape + + elif isinstance(parameter, ScaledMatrix): + assert predictor.shape == state[parameter.matrix].shape + + elif isinstance(parameter, (LinearCombination, LinearCombinationWithTransform, MixtureParameterVector)): + assert predictor.shape == (n, 1) + + elif isinstance(parameter, MixtureParameterMatrix): + assert predictor.shape == (n, n) + assert is_diag(predictor) + + else: + raise TypeError("parameter type not recognised") + + +def test_predictor_conditional(parameter: Parameter, state_tuple: tuple): + """Test predictor condition in LinearCombination cases. + + Returns immediately if the parameter is not of LinearCombination type. + + Performs 2 tests: + 1. Excludes all terms in the linear combination and tests that the + predict function returns a vector of zeros as expected. + 2. For a case where there are exactly more than one term in the linear combination: + tests that when we exclude each parameter in turn and sum the with the predictor + with all other parameter except the excluded one, we recover the full predictor. + + Args: + parameter : Parameter choice defined by fix_parameter + state_tuple (tuple): a tuple (dict, n , p) where dict is a dictionary of state values, + n and p are sizes. For more detail see fix_state + + """ + + if not isinstance(parameter, LinearCombination): + return + + state, _, _ = state_tuple + + exclude_terms = list(parameter.form.keys()) + predictor = parameter.predictor_conditional(state, term_to_exclude=exclude_terms) + assert predictor == 0 + + if len(exclude_terms) > 1: + for param in exclude_terms: + predictor_exclude = 0.0 + predictor_exclude += parameter.predictor_conditional(state, term_to_exclude=param) + + full_keys = deepcopy(exclude_terms) + full_keys.remove(param) + predictor_exclude += parameter.predictor_conditional(state, term_to_exclude=full_keys) + + assert np.all(predictor_exclude == parameter.predictor(state)) + + +def test_get_param_list(parameter): + """Compute parameter list. + + Test size is as expected. This is different for each class, so is defined per case. + + Args: + parameter (Parameter): Parameter choice defined by fix_parameter + + """ + + param_list = parameter.get_param_list() + + assert isinstance(param_list, list) + + if isinstance(parameter, Identity): + assert len(param_list) == 1 + + elif isinstance(parameter, (LinearCombination, LinearCombinationWithTransform)): + assert len(param_list) == len(parameter.form) * 2 + + elif isinstance(parameter, (ScaledMatrix, MixtureParameterVector, MixtureParameterMatrix)): + assert len(param_list) == 2 + else: + raise TypeError("parameter type not recognised") + + +def test_grad(parameter: Parameter, state_tuple: tuple): + """Compute predictor given parameter and state object. + + Test size is as expected. This is different for each class so the test is defined by case. + + Also checks the values of the gradient for some of the cases where this is simple to achieve. + + Args: + parameter (Parameter): Parameter choice defined by fix_parameter + state_tuple (tuple): a tuple (dict, n , p) where dict is a dictionary of state values, + n and p are sizes. For more detail see fix_state + + """ + state, n, p = state_tuple + + if isinstance(parameter, Identity) and (state[parameter.form].shape[1] > 1): + with pytest.raises(ValueError): + parameter.grad(state, parameter.form) + elif isinstance(parameter, Identity): + gradient = parameter.grad(state, parameter.form) + q = state[parameter.form].size + assert gradient.shape == (q, q) + assert np.all(gradient == np.eye(q)) + elif isinstance(parameter, (LinearCombination, LinearCombinationWithTransform)): + for param, matrix in parameter.form.items(): + gradient = parameter.grad(state, param) + assert gradient.shape == state[matrix].T.shape + + if isinstance(parameter, LinearCombinationWithTransform) and parameter.transform[param]: + g = np.multiply(np.exp(state[param]), state[matrix].T) + assert np.all(gradient == g) + else: + assert np.all(gradient == state[matrix].T) + elif isinstance(parameter, ScaledMatrix): + gradient = parameter.grad(state, parameter.scalar) + assert gradient.shape == state[parameter.matrix].shape + assert np.all(gradient == state[parameter.matrix]) + elif isinstance(parameter, MixtureParameterVector): + gradient = parameter.grad(state, parameter.param) + assert gradient.shape == (p, n) + elif isinstance(parameter, MixtureParameterMatrix): + with pytest.raises(TypeError): + parameter.grad(state, parameter.param) + else: + raise TypeError("parameter type not recognised") + + +@pytest.mark.parametrize( + "parameter", + [ + MixtureParameterVector(param="vector", allocation="allocation"), + MixtureParameterMatrix(param="vector", allocation="allocation"), + ], +) +def test_get_element_match(parameter: MixtureParameter, state_tuple: tuple): + """Test get element match function. + + Checks size is correct and that over all matches exactly n matches are found. i.e. every data point has an + allocation. + + In the test, the allocation is defined as mod(0:n-1, p); there is then also a test to check that the matches are + found in the right place. + + Args: + parameter (MixtureParameter): parameter object of mixture type + state_tuple (tuple): a tuple (dict, n , p) where dict is a dictionary of state values, n and p are sizes. For + more detail see fix_state + + """ + state, n, p = state_tuple + + total = 0 + for i in range(p): + match = parameter.get_element_match(state, i) + assert match.shape == (n, 1) + assert np.sum(match) >= np.floor(n / p) + assert np.sum(match) <= np.ceil(n / p) + if n >= p: + assert match[i] + + total = total + sum(match) + + assert total == n diff --git a/tests/test_reversible_jump.py b/tests/test_reversible_jump.py new file mode 100644 index 0000000..8d08fb8 --- /dev/null +++ b/tests/test_reversible_jump.py @@ -0,0 +1,427 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Bespoke tests for the reversible jump MCMC sampler.""" + +from typing import Tuple + +import numpy as np +import pytest +from scipy import sparse +from scipy.stats import chisquare, gamma, norm, poisson, randint, truncnorm, uniform + +from openmcmc import parameter +from openmcmc.distribution.distribution import Gamma, Poisson, Uniform +from openmcmc.distribution.location_scale import Normal, NullDistribution +from openmcmc.mcmc import MCMC +from openmcmc.model import Model +from openmcmc.sampler.metropolis_hastings import ManifoldMALA, RandomWalkLoop +from openmcmc.sampler.reversible_jump import ReversibleJump + + +def make_basis(data_locations: np.ndarray, knots: np.ndarray, scales: np.ndarray) -> np.ndarray: + """Create a Gaussian kernel basis from the data locations and knots supplied as inputs. + + Args: + data_locations (np.ndarray): locations of observed data values. + knots (np.ndarray): knot locations for basis formation. + scales (np.ndarray): scales for each of the Gaussian basis functions. + + Returns: + np.ndarray: [n_data x n_knot] basis matrix, with one column per Gaussian kernel. + + """ + basis_matrix = np.full(shape=(data_locations.shape[0], knots.shape[1]), fill_value=np.nan) + for k in range(knots.shape[1]): + basis_matrix[:, [k]] = norm.pdf(data_locations, loc=knots[:, k], scale=scales[:, k]) + return basis_matrix + + +def move_function(state: dict, update_column: int) -> dict: + """Update the basis matrix in the state to take account of the relocation of a knot. + + Assumes that the supplied state has at least the following elements: + "X": locations of the observed data points. + "theta": locations of the basis knots. + "omega": widths (standard deviations) of the Gaussian kernels. + + Args: + state (dict): dictionary containing current state. + update_column (int): defunct parameter. + + Returns: + state (dict): state dictionary with updated basis matrix + + """ + state["B"] = make_basis(state["X"], knots=state["theta"], scales=state["omega"]) + return state + + +def birth_multiple_jump_function(current_state: dict, prop_state: dict) -> Tuple[dict, float, float]: + """Augment the basis and update the allocation parameters in response to a birth move for the situation in which + multiple jump parameters need to be updated. + + Assumes that the supplied state has at least the following elements: + "theta": locations of the basis knots. + "omega": widths (standard deviations) of the Gaussian kernels. + "alloc_beta": null allocation vector. + + Args: + current_state (dict): dictionary containing the current state information. + prop_state (dict): dictionary containing the proposed state information. + + Returns: + Tuple[dict, float, float]: tuple consisting of the following elements: + prop_state (dict): proposed state with updated basis matrix and basis parameters. + logp_pr_g_cr (float): transition probability for move from current state to proposed state. + logp_cr_g_pr (float): transition probability for move from proposed state to current state. + + """ + prop_state["B"] = make_basis(prop_state["X"], prop_state["theta"], prop_state["omega"]) + prop_state["alloc_beta"] = np.concatenate((prop_state["alloc_beta"], np.array([0], ndmin=2)), axis=0) + logp_pr_g_cr = 0.0 + logp_cr_g_pr = 0.0 + return prop_state, logp_pr_g_cr, logp_cr_g_pr + + +def death_multiple_jump_function( + current_state: dict, prop_state: dict, deletion_index: int +) -> Tuple[dict, float, float]: + """Update basis matrix and allocation parameter in reponse to a death move for the situation in which multiple jump + parameters need to be updated. + + Assumes that the supplied state has at least the following elements: + "theta": locations of the basis knots. + "B": basis matrix. + "alloc_beta": null allocation vector. + + Args: + current_state (dict): dictionary containing the current state information. + prop_state (dict): dictionary containing the proposed state information. + deletion_index (int): index of the basis component to be deleted in the overall set of components. + + Returns: + Tuple[dict, float, float]: tuple consisting of the following elements: + prop_state (dict): proposed state with updated basis matrix and basis parameters. + logp_pr_g_cr (float): transition probability for move from current state to proposed state. + logp_cr_g_pr (float): transition probability for move from proposed state to current state. + + """ + prop_state["B"] = np.delete(prop_state["B"], obj=deletion_index, axis=1) + prop_state["alloc_beta"] = np.delete(prop_state["alloc_beta"], obj=deletion_index, axis=0) + logp_pr_g_cr = 0.0 + logp_cr_g_pr = 0.0 + return prop_state, logp_pr_g_cr, logp_cr_g_pr + + +@pytest.fixture(name="basis_limits") +def fix_basis_limits(): + """Fix the basis limits to be used for the tests.""" + return np.array([-10, 10]) + + +@pytest.fixture(name="scale_limits") +def fix_scale_limits(): + """Fix the scale limits to be used for the tests.""" + return np.array([0.5, 2]) + + +@pytest.fixture(name="state") +def fix_state(basis_limits): + """Define the state for the tests.""" + n_basis = 4 + n_data = 50 + + basis_knots = basis_limits[0] + (basis_limits[1] - basis_limits[0]) * uniform.rvs(size=n_basis).reshape( + (1, n_basis) + ) + data_locations = basis_limits[0] + (basis_limits[1] - basis_limits[0]) * np.sort( + uniform.rvs(size=n_data).reshape((n_data, 1)), axis=0 + ) + basis_scales = 1.0 * np.ones(shape=(1, n_basis)) + B = make_basis(data_locations=data_locations, knots=basis_knots, scales=basis_scales) + + tau_beta = 1.0 / (2.0**2) + tau_y = 1.0 / (0.1**2) + beta_real = np.sqrt(1.0 / tau_beta) * norm.rvs(size=(n_basis, 1)) + y = B @ beta_real + np.sqrt(1.0 / tau_y) * norm.rvs(size=(n_data, 1)) + + state = { + "y": y, + "beta": np.ones((n_basis, 1)), + "tau_y": tau_y, + "P": sparse.eye(n_data), + "B": B, + "n_basis": n_basis, + "X": data_locations, + "theta": basis_knots, + "omega": basis_scales, + "mu_beta": np.zeros((1, 1)), + "tau_beta": tau_beta * np.ones((1, 1)), + "rho": 8, + "alloc_beta": np.zeros((n_basis, 1), dtype=int), + "a_omega": 3.0 * np.ones((1, 1)), + "b_omega": 2.0 * np.ones((1, 1)), + } + return state + + +@pytest.fixture(name="model") +def fix_model(basis_limits): + """Set up the model for the reversible jump unit tests. + + Model specification has the following components: + - response_distribution: a Null distribution for the response "y", which gives a 0 contribution to the + log-posterior (and to the gradient etc.). + - beta_prior: Normal prior for the basis parameters "beta", with mixture parameter priors to account for the + changing shape of the basis parameter. + - knot_num_prior: Poisson prior for the number "n_basis" of knots in the model. + - knot_loc_prior: Uniform prior for the locations "theta" of the individual knots in the model. + + """ + response_mean = parameter.LinearCombination(form={"beta": "B"}) + response_precision = parameter.ScaledMatrix(matrix="P", scalar="tau_y") + response_distribution = NullDistribution(response="y", mean=response_mean, precision=response_precision) + + beta_mean = parameter.MixtureParameterVector(param="mu_beta", allocation="alloc_beta") + beta_precision = parameter.MixtureParameterMatrix(param="tau_beta", allocation="alloc_beta") + beta_prior = Normal(response="beta", mean=beta_mean, precision=beta_precision) + + knot_num_prior = Poisson(response="n_basis", rate="rho") + knot_loc_prior = Uniform( + response="theta", + domain_response_lower=np.array([basis_limits[0]], ndmin=2), + domain_response_upper=np.array([basis_limits[1]], ndmin=2), + ) + width_prior = Gamma("omega", shape="a_omega", rate="b_omega") + + model = Model([response_distribution, beta_prior, knot_num_prior, knot_loc_prior, width_prior]) + model.response = {"y": "mean"} + return model + + +@pytest.fixture(name="samplers") +def fix_samplers(model, basis_limits, scale_limits): + """Set up the samplers for the reversible jump unit tests. + + Sampler specification has the following components: + - ManifoldMALA sampler for the basis coefficients. The Null likelihood distribution means that only the prior + contributes to the gradient and Hessian. + - RandomWalkLoop sampler for the locations of the basis knots. + - ReversibleJump sampler for the number of knots in the basis. + + """ + n_basis_max = 20 + matching_params = {"variable": "beta", "matrix": "B", "scale": 1.0, "limits": [-10.0, 10.0]} + samplers = [ + ManifoldMALA(param="beta", model=model, step=np.array(0.5), max_variable_size=n_basis_max), + RandomWalkLoop( + param="theta", + model=model, + step=np.array(0.1), + max_variable_size=n_basis_max, + domain_limits=np.array(basis_limits, ndmin=2), + state_update_function=move_function, + ), + RandomWalkLoop( + param="omega", + model=model, + step=np.array(0.1), + max_variable_size=n_basis_max, + domain_limits=np.array(scale_limits, ndmin=2), + state_update_function=move_function, + ), + ReversibleJump( + param="n_basis", + model=model, + associated_params=["theta", "omega"], + n_max=n_basis_max, + state_birth_function=birth_multiple_jump_function, + state_death_function=death_multiple_jump_function, + matching_params=matching_params, + ), + ] + return samplers + + +def test_prior_recovery(state, model, samplers): + """Run the sampler with the null likelihood (data-free). + + Checks that with the null likelihood, the sampler approximately recovers the prior distribution for the number of + knots. This is checked by using a chi-squared goodness of fit test for the correspondence between the true Poisson + prior and the MCMC samples, for bins where the expected count is at least 5. + + """ + solver = MCMC(state=state, samplers=samplers, model=model, n_burn=0, n_iter=5000) + solver.run_mcmc() + + idx_thin = np.arange(start=0, stop=solver.n_iter, step=50) + sample_n_knot = solver.store["n_basis"][:, idx_thin] + + num = np.arange(start=1, stop=21, step=1) + bin_edges = np.linspace(start=0.5, stop=20.5, num=21) + expected_count = sample_n_knot.size * poisson.pmf(num, state["rho"]) + observed_count, bin_edges = np.histogram(sample_n_knot.flatten(), bins=bin_edges) + + big_enough = expected_count >= 5 + observed_count_test = observed_count[big_enough] + expected_count_test = expected_count[big_enough] * np.sum(observed_count_test) / np.sum(expected_count[big_enough]) + _, p_val = chisquare(observed_count_test, expected_count_test) + assert p_val >= 0.001 + + +@pytest.fixture +def mock_gmrf_normal_sampler(monkeypatch): + """Replace np.random.normal with a function that just returns the mean, so that gmrf.sample_normal will also do the + same.""" + + def sample_zeros(size: tuple) -> np.ndarray: + return np.zeros(shape=size) + + monkeypatch.setattr(np.random, "standard_normal", sample_zeros) + + +@pytest.fixture +def mock_gmrf_truncated_sampler(monkeypatch): + """Replace truncnorm with a function that just returns the mean, so that gmrf.sample_normal will also do the + same.""" + + def sample_zeros(a, b, loc, scale, size): + return loc * np.ones(shape=size) + + monkeypatch.setattr(truncnorm, "rvs", sample_zeros) + + +@pytest.fixture +def mock_gamma_sampler(monkeypatch): + """Replace gamma with a function which always returns the 1, so the birth move always return an omega equalling + one.""" + + def sample_ones(shape, scale, size): + return 1 * np.ones(shape=size) + + monkeypatch.setattr(gamma, "rvs", sample_ones) + + +@pytest.fixture +def mock_knot_midpoint(monkeypatch): + """Replace the uniform random sampler with a function which always returns 0.5, so that the birth move always + returns a knot in the centre of the domain.""" + + def sample_midpoint(size: int, n=1): + return 0.5 * np.ones((size, n)) + + monkeypatch.setattr(np.random, "rand", sample_midpoint) + + +@pytest.fixture(name="mock_knot_endpoint") +def fix_mock_knot_endpoint(monkeypatch): + """Replace the uniform random sampler with a function which always returns 0.5, so that the birth move always + returns a knot at the upper end of the domain.""" + + def sample_endpoint(size: int, n=1): + return 1.0 * np.ones((size, n)) + + monkeypatch.setattr(np.random, "rand", sample_endpoint) + + +@pytest.fixture(name="mock_knot_selection") +def fix_mock_knot_selection(monkeypatch): + """Replace the numpy.random.randint with something that always selects the highest integer, to always select + the final knot for deletion.""" + + def select_final_knot(low: int, high: int, size=1): + return high - 1 + + monkeypatch.setattr(randint, "rvs", select_final_knot) + + +def test_birth_overlap(state, samplers, mock_knot_endpoint, mock_gmrf_truncated_sampler, mock_gamma_sampler): + """Test the functionality which matches the predictions before and after the birth transition. + + Create a new knot in exactly the same location as one of the existing ones: the coefficient at the existing location + should have 50% assigned to each of the concurrent locations in the new state. + + The initial state has knots at x=[-10, -5, 5, 10]. The mock_knot_endpoint patch forces np.random.rand to return 1.0 + in order that the proposed knot coincides the existing one at x=10. The mock_gmrf_truncated_sampler patch ensures + that there is no randomness on the returned parameter. + + The parameters in the current state are all set to be 1, so that the in the proposed state, the two parameters + associated with the knot at x=10 should both be 0.5. + + Also checks that the log-transition densities are returned as expected: + - log(p(theta*|theta)) = logp_pr_g_cr = truncated Gaussian density evaluated at central point. + - log(p(theta|theta*)) = logp_cr_g_pr = log(|F|) = log(0.5) in this situation + + """ + state["theta"] = np.array([-10, -5, 5, 10], ndmin=2) + state["omega"] = np.array([1, 1, 1, 1], ndmin=2) + state["B"] = make_basis(state["X"], state["theta"], state["omega"]) + prop_state, _, _ = samplers[3].birth_proposal(state) + assert np.allclose(prop_state["beta"][-1], 0.5) + assert np.allclose(prop_state["beta"][-2], 0.5) + assert np.allclose(np.sum(prop_state["beta"]), state["theta"].size) + + prop_state, logp_pr_g_cr, logp_cr_g_pr = samplers[3].matched_birth_transition(state, prop_state, 0.0, 0.0) + assert np.allclose(logp_pr_g_cr, -0.5 * np.log(2.0 * np.pi) * samplers[3].matching_params["scale"]) + assert np.allclose(logp_cr_g_pr, np.log(0.5)) + + +def test_birth_no_overlap(state, samplers, mock_knot_midpoint, mock_gmrf_truncated_sampler, mock_gamma_sampler): + """Test the functionality which matches the predictions before and after the birth transition. + + Create a new knot which doesn't overlap with any of the others, then we expect the existing knots to have no + influence over the value of the new one. + + """ + state["theta"] = np.array([-10, -5, 5, 10], ndmin=2) + state["omega"] = np.array([1, 1, 1, 1], ndmin=2) + state["B"] = make_basis(state["X"], state["theta"], state["omega"]) + prop_state, logp_pr_g_cr, logp_cr_g_pr = samplers[3].birth_proposal(state) + assert np.allclose(prop_state["beta"][-1], 0.0) + assert np.allclose(np.sum(prop_state["beta"]), state["theta"].size) + + prop_state, logp_pr_g_cr, logp_cr_g_pr = samplers[3].matched_birth_transition(state, prop_state, 0.0, 0.0) + assert np.allclose(logp_pr_g_cr, -0.5 * np.log(2.0 * np.pi) * samplers[3].matching_params["scale"]) + assert np.allclose(logp_cr_g_pr, 0.0) + + +def test_death_overlap(state, samplers, mock_knot_selection): + """Test the functionality which matches the predictions before and after a death transition, in the edge case where + there are overlapping basis knots. + + Test is effectively the opposite of the one run by test_death_overlap(). See that function for further information. + + """ + state["theta"] = np.array([-10, -5, 10, 10], ndmin=2) + state["B"] = make_basis(state["X"], state["theta"], state["omega"]) + prop_state, _, _ = samplers[3].death_proposal(state) + assert np.allclose(prop_state["beta"][-1], 2.0) + assert np.allclose(np.sum(prop_state["beta"]), state["theta"].size) + + prop_state, logp_pr_g_cr, logp_cr_g_pr = samplers[3].matched_death_transition( + state, prop_state, 0.0, 0.0, deletion_index=3 + ) + assert np.allclose(logp_pr_g_cr, np.log(0.5)) + assert np.allclose(logp_cr_g_pr, -0.5 * np.log(2.0 * np.pi) * samplers[3].matching_params["scale"]) + + +def test_death_no_overlap(state, samplers, mock_knot_selection): + """Test the functionality which matches the predictions before and after a death transition, in the edge case where + the basis knots are fully spatially distinct. + + Test is effectively the opposite of the one run by test_birth_no_overlap(). + + """ + state["theta"] = np.array([-10, -5, 5, 10], ndmin=2) + state["beta"] = np.array([1, 1, 1, 0], ndmin=2).T + state["B"] = make_basis(state["X"], state["theta"], state["omega"]) + prop_state, logp_pr_g_cr, logp_cr_g_pr = samplers[3].death_proposal(state) + assert np.allclose(prop_state["beta"], state["beta"][:-1]) + + prop_state, logp_pr_g_cr, logp_cr_g_pr = samplers[3].matched_death_transition( + state, prop_state, 0.0, 0.0, deletion_index=3 + ) + assert np.allclose(logp_pr_g_cr, 0.0) + assert np.allclose(logp_cr_g_pr, -0.5 * np.log(2.0 * np.pi) * samplers[3].matching_params["scale"]) diff --git a/tests/test_sampler.py b/tests/test_sampler.py new file mode 100644 index 0000000..6e764d4 --- /dev/null +++ b/tests/test_sampler.py @@ -0,0 +1,366 @@ +# SPDX-FileCopyrightText: 2024 Shell Global Solutions International B.V. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Unit testing for the sampler module. + +For the main sampler tests, a standard model is created (form chosen so that we can test the majority of the conjugate +and non-conjugate sampler types). Then we perform both sampler-agnostic tests, and sampler-specific tests for each case. + +""" + +from copy import deepcopy + +import numpy as np +import pytest +from scipy.stats import gamma, norm + +from openmcmc import parameter +from openmcmc.distribution.distribution import Categorical, Gamma +from openmcmc.distribution.location_scale import Normal +from openmcmc.model import Model +from openmcmc.sampler.metropolis_hastings import AcceptRate, ManifoldMALA, RandomWalk +from openmcmc.sampler.sampler import ( + MCMCSampler, + MixtureAllocation, + NormalGamma, + NormalNormal, +) + + +@pytest.fixture(name="accept_rate") +def fix_accept_rate(): + """Fix the acceptance counter.""" + accept_rate = AcceptRate() + return accept_rate + + +def test_increment_accept(accept_rate): + """Test the increment_accept function in the AcceptRate class. + + Tests that if we initialise the acceptance count to 0 and then call increment_accept(), the resulting acceptance + count is 1. + + """ + accept_rate.count["accept"] = 0 + accept_rate.increment_accept() + assert accept_rate.count["accept"] == 1 + + +def test_increment_proposal(accept_rate): + """Test the increment_proposal function in the AcceptRate class. + + Tests that if we initialise the proposal count to 0 and then call increment_proposal(), the resulting proposal count + is 1. + + """ + accept_rate.count["proposal"] = 0 + accept_rate.increment_proposal() + assert accept_rate.count["proposal"] == 1 + + +def test_acceptance_rate(accept_rate): + """Test the acceptance_rate function of the AcceptRate class. + + Tests that we get an acceptance of 100% if both the proposal and acceptance counts are 1. + + """ + accept_rate.count["proposal"] = 1 + accept_rate.count["accept"] = 1 + assert accept_rate.acceptance_rate == 100.0 + + +def test_get_acceptance_rate(accept_rate): + """Test get_acceptance_rate function of the AcceptRate class. + + Tests that get_acceptance_rate() returns 'Acceptance rate 100%' when the proposal and acceptance counts are both set + to 1. + + """ + accept_rate.count["proposal"] = 1 + accept_rate.count["accept"] = 1 + assert accept_rate.get_acceptance_rate() == "Acceptance rate 100%" + + +@pytest.fixture( + params=[(1, 1, 1), (1, 10, 1), (10, 1, 10), (10, 10, 1), (10, 10, 10)], + ids=["n=1, p=1, c=1", "n=1, p=10, c=1", "n=10, p=1, c=10", "n=10, p=10, c=1", "n=10, p=10, c=10"], + name="state", +) +def fix_state(request): + """Fix the state for the MCMC sampler tests.""" + [n, p, n_cat] = request.param + state = {} + state["prefactor_matrix"] = np.random.rand(n, p) + state["parameter"] = np.random.rand(p, 1) + state["parameter_n"] = np.random.rand(n, 1) + state["response"] = state["prefactor_matrix"] @ state["parameter"] + state["prior_mean"] = np.random.rand(n_cat, 1) + state["precision_matrix"] = np.diag(np.random.rand(n, 1).flatten() + 0.1) + state["prior_precision_vector"] = 0.1 + np.random.rand(n_cat) + state["prior_precision_matrix"] = np.eye(p) + state["prior_precision_scalar"] = 0.1 + np.random.rand(1, 1) + state["gamma_shape"] = 1e-3 * np.ones(shape=(n_cat,)) + state["gamma_rate"] = 1e-3 * np.ones(shape=(n_cat,)) + state["allocation"] = np.random.randint(low=0, high=n_cat, size=(p, 1)) + state["prior_allocation_prob"] = np.random.rand(1, n_cat) + state["prior_allocation_prob"] = state["prior_allocation_prob"] / np.sum(state["prior_allocation_prob"]) + return state + + +@pytest.fixture(name="model") +def fix_model(state): + """Create the model to be fed into the sampler object. + + The model contains the following: + - A Normally-distributed response (with LinearCombination and ScaledMatrix parameters). + - A Normally parameter prior (with MixtureAllocation parameters). + - A gamma prior for the parameter prior precision. + - A categorical distribution prior for the mixture allocation cases. + + """ + if state["prior_mean"].shape[0] == 1: + state["prior_mean"] = state["prior_mean"] * np.ones(state["parameter"].shape) + mean_parameter = parameter.Identity(form="prior_mean") + precision_parameter = parameter.ScaledMatrix(matrix="prior_precision_matrix", scalar="prior_precision_vector") + else: + mean_parameter = parameter.MixtureParameterVector(param="prior_mean", allocation="allocation") + precision_parameter = parameter.MixtureParameterMatrix(param="prior_precision_vector", allocation="allocation") + model = Model( + [ + Normal( + response="response", + mean=parameter.LinearCombination(form={"parameter": "prefactor_matrix"}), + precision=parameter.Identity("precision_matrix"), + ), + Normal(response="parameter", mean=mean_parameter, precision=precision_parameter), + Gamma( + response="prior_precision_vector", + shape=parameter.Identity(form="gamma_shape"), + rate=parameter.Identity(form="gamma_rate"), + ), + Categorical(response="allocation", prob="prior_allocation_prob"), + ] + ) + return model + + +@pytest.fixture( + params=[ + ("parameter", NormalNormal), + ("parameter", ManifoldMALA), + ("parameter", RandomWalk), + ("prior_precision_vector", NormalGamma), + ("allocation", MixtureAllocation), + ], + ids=["mean_NormalNormal", "mean_ManifoldMALA", "mean_RandomWalk", "precision_NormalGamma", "MixtureAllocation"], + name="sampler_object", +) +def fix_sampler_object(request, model): + """Create the sampler using the model specified in the model fixture. + + The fixture parameters specify the parameter to be sampled, and the sampler class to be used. In the + MixtureAllocation sampler case, this can only be used when we have MixtureParameter classes for the parameter prior, + so the sampler is set to None and the tests skipped when the parameters are incompatible. + + """ + [param, param_sampler] = request.param + if issubclass(param_sampler, MixtureAllocation) and isinstance(model["parameter"].mean, parameter.Identity): + sampler_object = None + elif issubclass(param_sampler, MixtureAllocation) and isinstance( + model["parameter"].mean, parameter.MixtureParameterVector + ): + sampler_object = param_sampler(param=param, model=model, response_param="parameter") + else: + sampler_object = param_sampler(param=param, model=model) + return sampler_object + + +def test_sample(sampler_object: MCMCSampler, state: dict): + """Test the sample function. + + Performs the following checks: + 1) Checks that the shape of the sampled parameter is the same before and after the sample generation. + 2) Checks that the other elements of the state (apart from self.param) have not been modified. + + """ + if sampler_object is None: + return + state_before = deepcopy(state) + state = sampler_object.sample(state) + assert state_before[sampler_object.param].shape == state[sampler_object.param].shape + + remaining_keys = list(state.keys()) + remaining_keys.remove(sampler_object.param) + for key in remaining_keys: + assert np.allclose(state_before[key], state[key]) + + +def test_sampler_specific(sampler_object: MCMCSampler, state: dict, monkeypatch): + """Specific tests for each of the samplers. + + For all tests, np.random.standard_normal is patched to always generate vectors of zeros, to enable standard results + for testing. + + For details of the specific checking done in each of the sampler cases, see the relevant sub-functions. + + """ + + def mock_standard_normal(size: tuple): + """Replace numpy.random.standard_normal with a function that just generates a vector of zeros.""" + return np.zeros(shape=size) + + def mock_norm_rvs(size: tuple, scale: float): + """Replace scipy.stats.norm.rvs with a function that just generates a vector of zeros.""" + return np.zeros(shape=size) + + monkeypatch.setattr(np.random, "standard_normal", mock_standard_normal) + monkeypatch.setattr(norm, "rvs", mock_norm_rvs) + + if isinstance(sampler_object, RandomWalk): + check_randomwalk(sampler_object, state) + elif isinstance(sampler_object, ManifoldMALA): + check_manifoldmala(sampler_object, state) + elif isinstance(sampler_object, NormalNormal): + check_normalnormal(sampler_object, state, monkeypatch) + elif isinstance(sampler_object, NormalGamma): + check_normalgamma(sampler_object, state, monkeypatch) + elif isinstance(sampler_object, MixtureAllocation): + check_mixtureallocation(sampler_object, state) + + +def check_randomwalk(sampler_object: RandomWalk, state: dict): + """Bespoke checking for the RandomWalk case. + + Performs the following checks: + 1) that the shape of state[self.param] is the same before and after the proposal. + 2) that state[self.param] is the same before and after the proposal (given fixing or random variables to 0). + 3) that the log-proposal densities are both equal. + + """ + current_state = deepcopy(state) + prop_state, logp_pr_g_cr, logp_cr_g_pr = sampler_object.proposal(current_state) + assert current_state[sampler_object.param].shape == prop_state[sampler_object.param].shape + assert np.allclose(current_state[sampler_object.param], prop_state[sampler_object.param], rtol=1e-5, atol=1e-8) + assert logp_pr_g_cr == logp_cr_g_pr + + +def check_manifoldmala(sampler_object: ManifoldMALA, state: dict): + """Bespoke checking for the ManifoldMALA case. + + Performs the following checks: + 1) that the shape of state[self.param] is the same before and after the proposal. + 2) that we can recover the correct gradient from the (non-random) proposal. + + """ + current_state = deepcopy(state) + prop_state, _, _ = sampler_object.proposal(current_state) + assert current_state[sampler_object.param].shape == prop_state[sampler_object.param].shape + grad_cr, hessian_cr = sampler_object.model.grad_log_p(current_state, sampler_object.param) + r = prop_state["parameter"] - current_state["parameter"] + grad_recover = (hessian_cr @ r) * 2 / np.power(sampler_object.step, 2) + assert np.allclose(grad_cr, grad_recover, rtol=1e-5, atol=1e-8) + + +def check_normalnormal(sampler_object: NormalNormal, state: dict, monkeypatch): + """Bespoke checking for the NormalNormal case. + + Performs the following checks: + 1) that if state["prefactor_matrix"] is set to be all-zero, then the sample function (with randomness switched + off) returns the parameter prior predictor. + 2) that if state["prior_precision_vector"] is set to be all-zero, then we recover the standard regression + solution as the sample (with randomness switched off). + 3) that the expected result is returned when we set both contributions to the mean term to be zero, and the + vector of random variables to be all ones. + + """ + test_state = deepcopy(state) + test_state["prefactor_matrix"] = np.zeros(shape=test_state["prefactor_matrix"].shape) + updated_state = sampler_object.sample(test_state) + assert np.allclose(updated_state[sampler_object.param], sampler_object.model["parameter"].mean.predictor(state)) + + if state["response"].shape[0] > 1: + test_state = deepcopy(state) + test_state["prior_precision_vector"] = np.zeros(shape=test_state["prior_precision_vector"].shape) + updated_state = sampler_object.sample(test_state) + response_precision = sampler_object.model["response"].precision.predictor(state) + comparison = np.linalg.solve( + state["prefactor_matrix"].T @ response_precision @ state["prefactor_matrix"], + state["prefactor_matrix"].T @ response_precision @ state["response"], + ) + assert np.allclose(updated_state[sampler_object.param], comparison) + + def mock_sample_ones(size: tuple): + """Replace numpy.random.standard_normal with a function that just generates a vector of ones.""" + return np.ones(shape=size) + + monkeypatch.setattr(np.random, "standard_normal", mock_sample_ones) + + test_state = deepcopy(state) + test_state["response"] = np.zeros(shape=test_state["response"].shape) + test_state["prior_mean"] = np.zeros(shape=test_state["prior_mean"].shape) + updated_state = sampler_object.sample(test_state) + response_precision = sampler_object.model["response"].precision.predictor(state) + comparison = np.linalg.solve( + np.linalg.cholesky( + state["prefactor_matrix"].T @ response_precision @ state["prefactor_matrix"] + + sampler_object.model["parameter"].precision.predictor(state) + ).T, + np.ones(shape=state["parameter"].shape), + ) + assert np.allclose(updated_state["parameter"], comparison) + + +def check_normalgamma(sampler_object: NormalGamma, state: dict, monkeypatch): + """Bespoke checking for the NormalGamma case. + + Mocks scipy.stats.gamma.rvs to always return the expected value (a * scale = a / b), then checks that for each prior + precision parameter (one for each category of the allocation), the reciprocal of the sampled value is equal to the + mean of the squared residuals. + + """ + + def mock_gamma_sample(a, scale): + """Patch gamma sampler so that it always returns the expected value.""" + no_warning_scale = np.where(scale == np.inf, 1, scale) + no_warning_sample = np.where(scale == np.inf, np.inf, a * no_warning_scale) + return no_warning_sample + + monkeypatch.setattr(gamma, "rvs", mock_gamma_sample) + + test_state = deepcopy(state) + test_state["gamma_shape"] = np.zeros(shape=test_state["gamma_shape"].shape) + test_state["gamma_rate"] = np.zeros(shape=test_state["gamma_rate"].shape) + updated_state = sampler_object.sample(test_state) + + resids = test_state[sampler_object.model[sampler_object.normal_param].response] - sampler_object.model[ + sampler_object.normal_param + ].mean.predictor(test_state) + for k in range(test_state[sampler_object.param].shape[0]): + component_index = test_state["allocation"] == k + if np.sum(component_index) > 0: + assert np.allclose( + 1 / updated_state[sampler_object.param][k], np.mean(np.power(resids[component_index], 2)) + ) + + +def check_mixtureallocation(sampler_object: MixtureAllocation, state: dict): + """Bespoke checking for the MixtureAllocation case. + + Sets the prior Normal mean for each of the allocation categories to be {0, 1, 2,..., (n_cat - 1)}, and sets the + corresponding prior precision to be large. Then ensures that each element of the parameter vector is randomly + assigned one of these values. + + Under these circumstances, the conditional allocation sample should return the category corresponding to its value + with probability 1: this behaviour is checked. + + """ + test_state = deepcopy(state) + test_state["prior_mean"] = np.array( + np.arange(start=0, stop=test_state["prior_mean"].shape[0]), ndmin=2, dtype=float + ).T + test_state["parameter"] = np.random.choice(test_state["prior_mean"].flatten(), size=test_state["parameter"].shape) + test_state["prior_precision_vector"] = 1e4 * np.ones(shape=test_state["prior_precision_vector"].shape) + + updated_state = sampler_object.sample(test_state) + assert np.allclose(updated_state["allocation"], test_state["parameter"])