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.
@@ -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
+ [![Tests](https://github.com/dtu-qmcm/grapevine/actions/workflows/run_tests.yml/badge.svg)](https://github.com/dtu-qmcm/grapevine/actions/workflows/run_tests.yml)
16
+ [![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip)
17
+ [![Supported Python versions: 3.12 and newer](https://img.shields.io/badge/python->=3.12-blue.svg)](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
+ [![Tests](https://github.com/dtu-qmcm/grapevine/actions/workflows/run_tests.yml/badge.svg)](https://github.com/dtu-qmcm/grapevine/actions/workflows/run_tests.yml)
3
+ [![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip)
4
+ [![Supported Python versions: 3.12 and newer](https://img.shields.io/badge/python->=3.12-blue.svg)](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()