grapevine-mcmc 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grapevine_mcmc-0.1.0/.github/workflows/release.yml +46 -0
- grapevine_mcmc-0.1.0/.github/workflows/run_tests.yml +42 -0
- grapevine_mcmc-0.1.0/.gitignore +164 -0
- grapevine_mcmc-0.1.0/.pre-commit-config.yaml +15 -0
- grapevine_mcmc-0.1.0/PKG-INFO +99 -0
- grapevine_mcmc-0.1.0/README.md +86 -0
- grapevine_mcmc-0.1.0/benchmarks/optimistix_example.py +191 -0
- grapevine_mcmc-0.1.0/pdm.lock +551 -0
- grapevine_mcmc-0.1.0/pyproject.toml +39 -0
- grapevine_mcmc-0.1.0/src/grapevine/__init__.py +5 -0
- grapevine_mcmc-0.1.0/src/grapevine/adaptation.py +102 -0
- grapevine_mcmc-0.1.0/src/grapevine/grapenuts.py +125 -0
- grapevine_mcmc-0.1.0/src/grapevine/integrator.py +144 -0
- grapevine_mcmc-0.1.0/src/grapevine/util.py +62 -0
- grapevine_mcmc-0.1.0/tests/__init__.py +3 -0
- grapevine_mcmc-0.1.0/tests/simple_example_problem.py +30 -0
- grapevine_mcmc-0.1.0/tests/test_adaptation.py +31 -0
- grapevine_mcmc-0.1.0/tests/test_grapenuts.py +30 -0
- grapevine_mcmc-0.1.0/tests/test_integrator.py +102 -0
- grapevine_mcmc-0.1.0/tests/test_util.py +30 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
# Sequence of patterns matched against refs/tags
|
|
6
|
+
tags:
|
|
7
|
+
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
release-build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
|
|
20
|
+
- name: build release distributions
|
|
21
|
+
run: |
|
|
22
|
+
python -m pip install pdm
|
|
23
|
+
pdm build
|
|
24
|
+
|
|
25
|
+
- name: upload windows dists
|
|
26
|
+
uses: actions/upload-artifact@v4
|
|
27
|
+
with:
|
|
28
|
+
name: release-dists
|
|
29
|
+
path: dist/
|
|
30
|
+
|
|
31
|
+
pypi-publish:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
needs:
|
|
34
|
+
- release-build
|
|
35
|
+
permissions:
|
|
36
|
+
id-token: write
|
|
37
|
+
|
|
38
|
+
steps:
|
|
39
|
+
- name: Retrieve release distributions
|
|
40
|
+
uses: actions/download-artifact@v4
|
|
41
|
+
with:
|
|
42
|
+
name: release-dists
|
|
43
|
+
path: dist/
|
|
44
|
+
|
|
45
|
+
- name: Publish release distributions to PyPI
|
|
46
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Run tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- '**'
|
|
7
|
+
tags-ignore:
|
|
8
|
+
- '**'
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
|
|
13
|
+
runs-on: ${{ matrix.os }}
|
|
14
|
+
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
os: [ubuntu-latest, windows-latest]
|
|
18
|
+
python-version: [3.12]
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
|
|
22
|
+
- name: checkout code
|
|
23
|
+
uses: actions/checkout@v2
|
|
24
|
+
|
|
25
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
26
|
+
uses: actions/setup-python@v2
|
|
27
|
+
with:
|
|
28
|
+
python-version: ${{ matrix.python-version }}
|
|
29
|
+
|
|
30
|
+
- name: Install pdm
|
|
31
|
+
run: pip install pdm
|
|
32
|
+
|
|
33
|
+
- name: pre-commit checks
|
|
34
|
+
uses: pre-commit/action@v2.0.3
|
|
35
|
+
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: |
|
|
38
|
+
pdm install --dev
|
|
39
|
+
pdm run pytest tests --cov=src
|
|
40
|
+
|
|
41
|
+
- name: Upload coverage reports to Codecov
|
|
42
|
+
uses: codecov/codecov-action@v3
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
|
|
3
|
+
# Byte-compiled / optimized / DLL files
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*$py.class
|
|
7
|
+
|
|
8
|
+
# C extensions
|
|
9
|
+
*.so
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
.eggs/
|
|
19
|
+
lib/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
share/python-wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# PyInstaller
|
|
32
|
+
# Usually these files are written by a python script from a template
|
|
33
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py,cover
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
cover/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
local_settings.py
|
|
63
|
+
db.sqlite3
|
|
64
|
+
db.sqlite3-journal
|
|
65
|
+
|
|
66
|
+
# Flask stuff:
|
|
67
|
+
instance/
|
|
68
|
+
.webassets-cache
|
|
69
|
+
|
|
70
|
+
# Scrapy stuff:
|
|
71
|
+
.scrapy
|
|
72
|
+
|
|
73
|
+
# Sphinx documentation
|
|
74
|
+
docs/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
.pybuilder/
|
|
78
|
+
target/
|
|
79
|
+
|
|
80
|
+
# Jupyter Notebook
|
|
81
|
+
.ipynb_checkpoints
|
|
82
|
+
|
|
83
|
+
# IPython
|
|
84
|
+
profile_default/
|
|
85
|
+
ipython_config.py
|
|
86
|
+
|
|
87
|
+
# pyenv
|
|
88
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
89
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
90
|
+
# .python-version
|
|
91
|
+
|
|
92
|
+
# pipenv
|
|
93
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
94
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
95
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
96
|
+
# install all needed dependencies.
|
|
97
|
+
#Pipfile.lock
|
|
98
|
+
|
|
99
|
+
# poetry
|
|
100
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
101
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
102
|
+
# commonly ignored for libraries.
|
|
103
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
104
|
+
#poetry.lock
|
|
105
|
+
|
|
106
|
+
# pdm
|
|
107
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
108
|
+
#pdm.lock
|
|
109
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
110
|
+
# in version control.
|
|
111
|
+
# https://pdm-project.org/#use-with-ide
|
|
112
|
+
.pdm.toml
|
|
113
|
+
.pdm-python
|
|
114
|
+
.pdm-build/
|
|
115
|
+
|
|
116
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
117
|
+
__pypackages__/
|
|
118
|
+
|
|
119
|
+
# Celery stuff
|
|
120
|
+
celerybeat-schedule
|
|
121
|
+
celerybeat.pid
|
|
122
|
+
|
|
123
|
+
# SageMath parsed files
|
|
124
|
+
*.sage.py
|
|
125
|
+
|
|
126
|
+
# Environments
|
|
127
|
+
.env
|
|
128
|
+
.venv
|
|
129
|
+
env/
|
|
130
|
+
venv/
|
|
131
|
+
ENV/
|
|
132
|
+
env.bak/
|
|
133
|
+
venv.bak/
|
|
134
|
+
|
|
135
|
+
# Spyder project settings
|
|
136
|
+
.spyderproject
|
|
137
|
+
.spyproject
|
|
138
|
+
|
|
139
|
+
# Rope project settings
|
|
140
|
+
.ropeproject
|
|
141
|
+
|
|
142
|
+
# mkdocs documentation
|
|
143
|
+
/site
|
|
144
|
+
|
|
145
|
+
# mypy
|
|
146
|
+
.mypy_cache/
|
|
147
|
+
.dmypy.json
|
|
148
|
+
dmypy.json
|
|
149
|
+
|
|
150
|
+
# Pyre type checker
|
|
151
|
+
.pyre/
|
|
152
|
+
|
|
153
|
+
# pytype static type analyzer
|
|
154
|
+
.pytype/
|
|
155
|
+
|
|
156
|
+
# Cython debug symbols
|
|
157
|
+
cython_debug/
|
|
158
|
+
|
|
159
|
+
# PyCharm
|
|
160
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
161
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
162
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
163
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
164
|
+
#.idea/
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v4.6.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-yaml
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: trailing-whitespace
|
|
8
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
9
|
+
# Ruff version.
|
|
10
|
+
rev: v0.6.0
|
|
11
|
+
hooks:
|
|
12
|
+
# Run the linter.
|
|
13
|
+
- id: ruff
|
|
14
|
+
# Run the formatter.
|
|
15
|
+
- id: ruff-format
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: grapevine-mcmc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Faster Hamiltonian trajectories for problems with guesses, using Blackjax
|
|
5
|
+
Author-email: Teddy Groves <tedgro@dtu.dk>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: blackjax>=1.2.4
|
|
9
|
+
Requires-Dist: jax==0.4.33
|
|
10
|
+
Provides-Extra: demos
|
|
11
|
+
Requires-Dist: optimistix>=0.0.8; extra == 'demos'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# grapevine
|
|
15
|
+
[](https://github.com/dtu-qmcm/grapevine/actions/workflows/run_tests.yml)
|
|
16
|
+
[](https://www.repostatus.org/#wip)
|
|
17
|
+
[](https://www.python.org/)
|
|
18
|
+
|
|
19
|
+
JAX/Blackjax implementation of the grapevine method for reusing the solutions of guessing problems embedded in Hamiltonian trajectories.
|
|
20
|
+
|
|
21
|
+
The grapevine method can dramatically speed up MCMC for statistical with embedded equation solving problems.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install grapevine-mcmc
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
First make a suitable log density function.
|
|
32
|
+
|
|
33
|
+
This function should have two arguments: a set of parameters (a [Pytree](https://jax.readthedocs.io/en/latest/pytrees.html)) and a guess (also a Pytree). It should return the log density of these parameters (a number) and a new guess. It should also be generally compatible with JAX, and will probalbly involve some differentiable numerical solving, for example using [optimistix](https://docs.kidger.site/optimistix/).
|
|
34
|
+
|
|
35
|
+
Here is a simple example of such a function:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from functools import partial
|
|
39
|
+
|
|
40
|
+
import jax
|
|
41
|
+
|
|
42
|
+
from jax.scipy.stats import norm
|
|
43
|
+
from jax.scipy.special import expit
|
|
44
|
+
from jax import numpy as jnp
|
|
45
|
+
|
|
46
|
+
import optimistix as optx
|
|
47
|
+
|
|
48
|
+
# equation solving problems often need 64 bit floats
|
|
49
|
+
jax.config.update("jax_enable_x64", True)
|
|
50
|
+
|
|
51
|
+
solver = optx.Newton(rtol=1e-8, atol=1e-8)
|
|
52
|
+
obs = jnp.array(0.7)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def fn(y, args):
|
|
56
|
+
"""Equation defining a root-finding problem."""
|
|
57
|
+
a = args
|
|
58
|
+
return y - jnp.tanh(y * expit(a) + 1)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def joint_logdensity(a, obs, guess):
|
|
62
|
+
"""An example log density."""
|
|
63
|
+
sol = optx.root_find(fn, solver, guess, args=a)
|
|
64
|
+
log_prior = norm.logpdf(a, loc=0.0, scale=1.0)
|
|
65
|
+
log_likelihood = norm.logpdf(obs, loc=sol.value, scale=0.5)
|
|
66
|
+
return log_prior + log_likelihood, sol.value
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
posterior_logdensity = partial(joint_logdensity, obs=obs)
|
|
70
|
+
posterior_logdensity(a=0.0, guess=0.01)
|
|
71
|
+
# (Array(-1.22095095, dtype=float64), Array(0.8952192, dtype=float64))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Now you can run MCMC on your model using GrapeNUTS, the grapevine version of the [NUTS](http://www.stat.columbia.edu/~gelman/research/published/nuts.pdf) sampler!
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from grapevine import run_grapenuts
|
|
78
|
+
|
|
79
|
+
INITIAL_POSITION = jnp.array(0.0)
|
|
80
|
+
DEFAULT_GUESS = jnp.array(0.01)
|
|
81
|
+
SEED = 1234
|
|
82
|
+
|
|
83
|
+
key = jax.random.key(SEED)
|
|
84
|
+
samples, info = run_grapenuts(
|
|
85
|
+
logdensity_fn=posterior_logdensity,
|
|
86
|
+
rng_key=key,
|
|
87
|
+
init_parameters=INITIAL_POSITION,
|
|
88
|
+
num_warmup=10,
|
|
89
|
+
num_samples=10,
|
|
90
|
+
default_guess=DEFAULT_GUESS,
|
|
91
|
+
progress_bar=False,
|
|
92
|
+
initial_step_size=0.01,
|
|
93
|
+
max_num_doublings=4,
|
|
94
|
+
is_mass_matrix_diagonal=True,
|
|
95
|
+
target_acceptance_rate=0.8,
|
|
96
|
+
)
|
|
97
|
+
jnp.quantile(samples.position, jnp.array([0.01, 0.5, 0.99]))
|
|
98
|
+
# Array([-1.26712677, 0.12950684, 0.93903677], dtype=float64)
|
|
99
|
+
```
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# grapevine
|
|
2
|
+
[](https://github.com/dtu-qmcm/grapevine/actions/workflows/run_tests.yml)
|
|
3
|
+
[](https://www.repostatus.org/#wip)
|
|
4
|
+
[](https://www.python.org/)
|
|
5
|
+
|
|
6
|
+
JAX/Blackjax implementation of the grapevine method for reusing the solutions of guessing problems embedded in Hamiltonian trajectories.
|
|
7
|
+
|
|
8
|
+
The grapevine method can dramatically speed up MCMC for statistical with embedded equation solving problems.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pip install grapevine-mcmc
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
First make a suitable log density function.
|
|
19
|
+
|
|
20
|
+
This function should have two arguments: a set of parameters (a [Pytree](https://jax.readthedocs.io/en/latest/pytrees.html)) and a guess (also a Pytree). It should return the log density of these parameters (a number) and a new guess. It should also be generally compatible with JAX, and will probalbly involve some differentiable numerical solving, for example using [optimistix](https://docs.kidger.site/optimistix/).
|
|
21
|
+
|
|
22
|
+
Here is a simple example of such a function:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from functools import partial
|
|
26
|
+
|
|
27
|
+
import jax
|
|
28
|
+
|
|
29
|
+
from jax.scipy.stats import norm
|
|
30
|
+
from jax.scipy.special import expit
|
|
31
|
+
from jax import numpy as jnp
|
|
32
|
+
|
|
33
|
+
import optimistix as optx
|
|
34
|
+
|
|
35
|
+
# equation solving problems often need 64 bit floats
|
|
36
|
+
jax.config.update("jax_enable_x64", True)
|
|
37
|
+
|
|
38
|
+
solver = optx.Newton(rtol=1e-8, atol=1e-8)
|
|
39
|
+
obs = jnp.array(0.7)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def fn(y, args):
|
|
43
|
+
"""Equation defining a root-finding problem."""
|
|
44
|
+
a = args
|
|
45
|
+
return y - jnp.tanh(y * expit(a) + 1)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def joint_logdensity(a, obs, guess):
|
|
49
|
+
"""An example log density."""
|
|
50
|
+
sol = optx.root_find(fn, solver, guess, args=a)
|
|
51
|
+
log_prior = norm.logpdf(a, loc=0.0, scale=1.0)
|
|
52
|
+
log_likelihood = norm.logpdf(obs, loc=sol.value, scale=0.5)
|
|
53
|
+
return log_prior + log_likelihood, sol.value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
posterior_logdensity = partial(joint_logdensity, obs=obs)
|
|
57
|
+
posterior_logdensity(a=0.0, guess=0.01)
|
|
58
|
+
# (Array(-1.22095095, dtype=float64), Array(0.8952192, dtype=float64))
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Now you can run MCMC on your model using GrapeNUTS, the grapevine version of the [NUTS](http://www.stat.columbia.edu/~gelman/research/published/nuts.pdf) sampler!
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from grapevine import run_grapenuts
|
|
65
|
+
|
|
66
|
+
INITIAL_POSITION = jnp.array(0.0)
|
|
67
|
+
DEFAULT_GUESS = jnp.array(0.01)
|
|
68
|
+
SEED = 1234
|
|
69
|
+
|
|
70
|
+
key = jax.random.key(SEED)
|
|
71
|
+
samples, info = run_grapenuts(
|
|
72
|
+
logdensity_fn=posterior_logdensity,
|
|
73
|
+
rng_key=key,
|
|
74
|
+
init_parameters=INITIAL_POSITION,
|
|
75
|
+
num_warmup=10,
|
|
76
|
+
num_samples=10,
|
|
77
|
+
default_guess=DEFAULT_GUESS,
|
|
78
|
+
progress_bar=False,
|
|
79
|
+
initial_step_size=0.01,
|
|
80
|
+
max_num_doublings=4,
|
|
81
|
+
is_mass_matrix_diagonal=True,
|
|
82
|
+
target_acceptance_rate=0.8,
|
|
83
|
+
)
|
|
84
|
+
jnp.quantile(samples.position, jnp.array([0.01, 0.5, 0.99]))
|
|
85
|
+
# Array([-1.26712677, 0.12950684, 0.93903677], dtype=float64)
|
|
86
|
+
```
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""An example comparing GrapeNUTS and NUTS on a representative problem.
|
|
2
|
+
|
|
3
|
+
This is supposed to be a complete example, mirroring how the grapevine method is used in practice.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from functools import partial
|
|
9
|
+
import timeit
|
|
10
|
+
|
|
11
|
+
from blackjax.util import run_inference_algorithm
|
|
12
|
+
from grapevine.grapenuts import GrapeNUTSState
|
|
13
|
+
import jax
|
|
14
|
+
import jax.numpy as jnp
|
|
15
|
+
import optimistix as optx
|
|
16
|
+
|
|
17
|
+
from blackjax import nuts
|
|
18
|
+
from blackjax import window_adaptation as nuts_window_adaptation
|
|
19
|
+
from jax.scipy.stats import norm
|
|
20
|
+
|
|
21
|
+
from grapevine import run_grapenuts
|
|
22
|
+
|
|
23
|
+
# Use 64 bit floats
|
|
24
|
+
jax.config.update("jax_enable_x64", True)
|
|
25
|
+
|
|
26
|
+
SEED = 1234
|
|
27
|
+
SD = 0.05
|
|
28
|
+
TRUE_PARAMS = {
|
|
29
|
+
"log_km_s": 2.0,
|
|
30
|
+
"log_km_p": 3.0,
|
|
31
|
+
"log_vmax": -1.0,
|
|
32
|
+
"log_k_eq": 5.0,
|
|
33
|
+
"log_s1": 2.0,
|
|
34
|
+
"log_s2": 2.9,
|
|
35
|
+
"log_s3": 0.9,
|
|
36
|
+
"log_s4": 0.1,
|
|
37
|
+
}
|
|
38
|
+
TRUE_PARAMS_ARR = jnp.array(list(TRUE_PARAMS.values()))
|
|
39
|
+
DEFAULT_GUESS = jnp.array([0.01, 0.01, 0.01, 0.01])
|
|
40
|
+
|
|
41
|
+
# hack the timeit module to not destroy the timed function's return value
|
|
42
|
+
timeit.template = """
|
|
43
|
+
def inner(_it, _timer{init}):
|
|
44
|
+
{setup}
|
|
45
|
+
_t0 = _timer()
|
|
46
|
+
for _i in _it:
|
|
47
|
+
retval = {stmt}
|
|
48
|
+
_t1 = _timer()
|
|
49
|
+
return _t1 - _t0, retval
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def rmm(p, km_s, km_p, vmax, k_eq, s):
|
|
54
|
+
num = vmax * (s - p / k_eq) / km_s
|
|
55
|
+
denom = 1 + s / km_s + p / km_p
|
|
56
|
+
return num / denom
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def fn(y, args):
|
|
60
|
+
p1, p2, p3, p4 = y
|
|
61
|
+
km_s, km_p, vmax, k_eq, s1, s2, s3, s4 = args
|
|
62
|
+
v1 = rmm(p1, km_s, km_p, vmax, k_eq, s1)
|
|
63
|
+
v2 = rmm(p2, km_s, km_p, vmax, k_eq, s2)
|
|
64
|
+
v3 = rmm(p3, km_s, km_p, vmax, k_eq, s3)
|
|
65
|
+
v4 = rmm(p4, km_s, km_p, vmax, k_eq, s4)
|
|
66
|
+
return jnp.array([v1, v2, v3, v4])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
solver = optx.Newton(rtol=1e-8, atol=1e-8)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def grapenuts_state_from_nuts_state(nuts_state, guess):
|
|
73
|
+
position, logdensity, logdensity_grad = nuts_state
|
|
74
|
+
return GrapeNUTSState(position, logdensity, logdensity_grad, guess)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def joint_logdensity_grapenuts(params, obs, guess):
|
|
78
|
+
sol = optx.root_find(fn, solver, guess, args=jnp.exp(params))
|
|
79
|
+
log_prior = norm.logpdf(
|
|
80
|
+
params,
|
|
81
|
+
loc=TRUE_PARAMS_ARR,
|
|
82
|
+
scale=jnp.full(params.shape, 1),
|
|
83
|
+
).sum()
|
|
84
|
+
log_likelihood = norm.logpdf(
|
|
85
|
+
jnp.log(obs), loc=jnp.log(sol.value), scale=jnp.full(obs.shape, SD)
|
|
86
|
+
).sum()
|
|
87
|
+
return log_prior + log_likelihood, sol.value
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def joint_logdensity_nuts(params, obs):
|
|
91
|
+
sol = optx.root_find(fn, solver, DEFAULT_GUESS, args=jnp.exp(params))
|
|
92
|
+
log_prior = norm.logpdf(
|
|
93
|
+
params,
|
|
94
|
+
loc=TRUE_PARAMS_ARR,
|
|
95
|
+
scale=jnp.full(params.shape, 1),
|
|
96
|
+
).sum()
|
|
97
|
+
log_likelihood = norm.logpdf(
|
|
98
|
+
jnp.log(obs), loc=jnp.log(sol.value), scale=jnp.full(obs.shape, SD)
|
|
99
|
+
).sum()
|
|
100
|
+
return log_prior + log_likelihood
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def simulate(key, params, guess):
|
|
104
|
+
sol = optx.root_find(fn, solver, guess, args=jnp.exp(params))
|
|
105
|
+
return sol.value, jnp.exp(
|
|
106
|
+
jnp.log(sol.value) + jax.random.normal(key, shape=sol.value.shape) * SD
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main():
|
|
111
|
+
key = jax.random.key(SEED)
|
|
112
|
+
key, sim_key = jax.random.split(key)
|
|
113
|
+
true_p, sim = simulate(sim_key, TRUE_PARAMS_ARR, DEFAULT_GUESS)
|
|
114
|
+
print("True substrate concs: " + str(true_p))
|
|
115
|
+
print("Simulated measurements: " + str(sim))
|
|
116
|
+
posterior_logdensity_gn = partial(joint_logdensity_grapenuts, obs=sim)
|
|
117
|
+
posterior_logdensity_nuts = partial(joint_logdensity_nuts, obs=sim)
|
|
118
|
+
key, grapenuts_key = jax.random.split(key)
|
|
119
|
+
key, nuts_key_warmup = jax.random.split(key)
|
|
120
|
+
key, nuts_key_sampling = jax.random.split(key)
|
|
121
|
+
|
|
122
|
+
def run_grapenuts_example():
|
|
123
|
+
return run_grapenuts(
|
|
124
|
+
posterior_logdensity_gn,
|
|
125
|
+
key,
|
|
126
|
+
init_parameters=(TRUE_PARAMS_ARR),
|
|
127
|
+
default_guess=DEFAULT_GUESS,
|
|
128
|
+
num_warmup=1000,
|
|
129
|
+
num_samples=1000,
|
|
130
|
+
initial_step_size=0.0001,
|
|
131
|
+
max_num_doublings=10,
|
|
132
|
+
is_mass_matrix_diagonal=False,
|
|
133
|
+
target_acceptance_rate=0.95,
|
|
134
|
+
progress_bar=False,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def run_nuts_example():
|
|
138
|
+
warmup = nuts_window_adaptation(
|
|
139
|
+
nuts,
|
|
140
|
+
posterior_logdensity_nuts,
|
|
141
|
+
progress_bar=False,
|
|
142
|
+
initial_step_size=0.0001,
|
|
143
|
+
max_num_doublings=10,
|
|
144
|
+
is_mass_matrix_diagonal=False,
|
|
145
|
+
target_acceptance_rate=0.95,
|
|
146
|
+
)
|
|
147
|
+
(initial_state, tuned_parameters), _ = warmup.run(
|
|
148
|
+
nuts_key_warmup,
|
|
149
|
+
TRUE_PARAMS_ARR,
|
|
150
|
+
num_steps=1000, # type: ignore
|
|
151
|
+
)
|
|
152
|
+
kernel = nuts(posterior_logdensity_nuts, **tuned_parameters)
|
|
153
|
+
return run_inference_algorithm(
|
|
154
|
+
nuts_key_sampling,
|
|
155
|
+
kernel,
|
|
156
|
+
1000,
|
|
157
|
+
initial_state,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
time_grapenuts, (state_grapenuts, _) = timeit.timeit(
|
|
161
|
+
run_grapenuts_example,
|
|
162
|
+
number=1,
|
|
163
|
+
) # type: ignore
|
|
164
|
+
time_nuts, (_, (state_nuts, _)) = timeit.timeit(
|
|
165
|
+
run_nuts_example,
|
|
166
|
+
number=1,
|
|
167
|
+
) # type: ignore
|
|
168
|
+
__import__("pdb").set_trace()
|
|
169
|
+
print("True param vals: " + str(TRUE_PARAMS_ARR))
|
|
170
|
+
print("GrapeNUTS quantiles:")
|
|
171
|
+
print(
|
|
172
|
+
jnp.quantile(
|
|
173
|
+
state_grapenuts.position,
|
|
174
|
+
jnp.array([0.01, 0.5, 0.99]),
|
|
175
|
+
axis=0,
|
|
176
|
+
).round(4)
|
|
177
|
+
)
|
|
178
|
+
print("NUTS quantiles:")
|
|
179
|
+
print(
|
|
180
|
+
jnp.quantile(
|
|
181
|
+
state_nuts.position,
|
|
182
|
+
jnp.array([0.01, 0.5, 0.99]),
|
|
183
|
+
axis=0,
|
|
184
|
+
).round(4)
|
|
185
|
+
)
|
|
186
|
+
print(f"Runtime for grapenuts: {round(time_grapenuts, 4)}")
|
|
187
|
+
print(f"Runtime for nuts: {round(time_nuts, 4)}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
main()
|