best-inference 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andreas Nygaard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,223 @@
1
+ Metadata-Version: 2.4
2
+ Name: best-inference
3
+ Version: 0.1.0
4
+ Summary: Batched Emulator Sampling with Tensorflow
5
+ Author-email: Andreas Nygaard <Andreas@phys.au.dk>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: numpy<2.0,>=1.23
11
+ Requires-Dist: tensorflow<2.21,>=2.16
12
+ Requires-Dist: tensorflow-probability>=0.23
13
+ Requires-Dist: tf-keras<2.21,>=2.16
14
+ Requires-Dist: hypersphere-sampler
15
+ Dynamic: license-file
16
+
17
+ # BEST
18
+
19
+ (**B**atched **E**mulator **S**ampling with **T**ensorFlow)
20
+
21
+ A TensorFlow-based inference framework for high-performance Markov Chain Monte Carlo (MCMC) sampling, including support for neural likelihood emulators ("client emulators") and adaptive covariance estimation.
22
+
23
+ ---
24
+
25
+ ## Overview
26
+
27
+ `best` provides a unified interface for sampling using multiple MCMC algorithms with GPU acceleration via TensorFlow:
28
+
29
+ ### Supported samplers
30
+
31
+ - Metropolis-Hastings (MH)
32
+ - Affine Invariant Ensemble Sampler (AIES)
33
+ - Hamiltonian Monte Carlo (HMC)
34
+ - No-U-Turn Sampler (NUTS)
35
+ - Metropolis Adjusted Langevin Algorithm (MALA)
36
+
37
+ ### Key features
38
+
39
+ - TensorFlow / GPU acceleration
40
+ - Automatic covariance matrix adaptation
41
+ - Bounded parameter inference
42
+ - Parallelized multi-chain sampling
43
+ - Pretrained neural likelihood emulators
44
+ - JIT compilation (XLA support)
45
+
46
+ ---
47
+
48
+ ## Installation
49
+
50
+ ### From PyPI
51
+
52
+ ```bash
53
+ pip install best-inference
54
+ ```
55
+ ### From source
56
+
57
+ ```bash
58
+ git clone https://github.com/AndreasNygaard/best-inference.git
59
+ cd best-inference
60
+ pip install .
61
+ ```
62
+ ---
63
+ ## Quick start
64
+
65
+ ```python
66
+ import best
67
+ import tensorflow as tf
68
+
69
+ def log_prob(x):
70
+ return -0.5 * tf.reduce_sum(x**2, axis=-1)
71
+
72
+ sampler = best.Sampler(log_prob, bounds=([-5, -5], [5, 5]))
73
+
74
+ results = sampler.sample(
75
+ method="hmc",
76
+ n_steps=2000,
77
+ n_chains=50,
78
+ initial_distribution="uniform",
79
+ num_burnin_steps=1000
80
+ )
81
+
82
+ print(results.samples.shape)
83
+ ```
84
+ ---
85
+ ## Sampler API
86
+
87
+ ### Initialisation
88
+
89
+ ```python
90
+ best.Sampler(
91
+ log_prob_fn,
92
+ bounds=None,
93
+ enforce_boundaries=True,
94
+ covmat=None,
95
+ initial_state=None,
96
+ n_chains=10,
97
+ initial_distribution="repeat"
98
+ )
99
+ ```
100
+
101
+ ### Sampling
102
+
103
+ ```python
104
+ results = sampler.sample(
105
+ method="mh" | "aies" | "hmc" | "nuts" | "mala",
106
+ n_steps=1000,
107
+ n_chains=10,
108
+ initial_state=None,
109
+ initial_distribution="repeat" | "uniform" | "gaussian",
110
+ bounds=None,
111
+ covmat=None,
112
+ num_burnin_steps=100,
113
+ num_covmat_updates=None,
114
+ sampler_kwargs={},
115
+ burnin_kwargs={},
116
+ get_individual_chains=True,
117
+ jit_compile=True
118
+ )
119
+ ```
120
+
121
+ ### Output
122
+
123
+ ```python
124
+ results.samples
125
+ results.acceptance_rate
126
+ results.evaluations
127
+
128
+ results.burnin_samples
129
+ results.burnin_acceptance_rates
130
+ results.burnin_evaluations
131
+ results.covmat_estimate
132
+ ```
133
+
134
+ ## Client emulators
135
+
136
+ BEST includes pretrained neural likelihood emulators for cosmology-inspired inference problems.
137
+
138
+ ### Available models
139
+
140
+ - lcdm
141
+ - sterile_neutrino
142
+
143
+ ### Load a model
144
+
145
+ ```python
146
+ from best.client_emulators import load_model_and_scalers
147
+
148
+ log_prob_fn, lower_bounds, upper_bounds = load_model_and_scalers("lcdm")
149
+ ```
150
+
151
+ ### Example: emulator-based inference
152
+
153
+ ```
154
+ import best
155
+ from best.client_emulators import load_model_and_scalers
156
+
157
+ log_prob_fn, lower, upper = load_model_and_scalers("lcdm")
158
+
159
+ sampler = best.Sampler(log_prob_fn, bounds=(lower, upper))
160
+
161
+ results = sampler.sample(
162
+ method="aies",
163
+ n_steps=5000,
164
+ n_chains=100,
165
+ initial_distribution="uniform",
166
+ num_burnin_steps=2000,
167
+ num_covmat_updates=1
168
+ )
169
+ ```
170
+
171
+ ## Supported Algorithms
172
+ ### Metropolis-Hastings (MH)
173
+ Random-walk MCMC with optional adaptive covariance scaling.
174
+ ### Affine Invariant Ensemble Sampler (AIES)
175
+ Efficient for highly anisotropic or correlated parameter spaces.
176
+ ### Hamiltonian Monte Carlo (HMC)
177
+ Gradient-based sampling with leapfrog integration.
178
+ ### No-U-Turn Sampler (NUTS)
179
+ Adaptive HMC variant with automatic trajectory length selection.
180
+ ### Metropolis Adjusted Langevin Algorithm (MALA)
181
+ Gradient-informed diffusion-based sampler.
182
+
183
+ ## Performance Notes
184
+ - TensorFlow enables GPU acceleration where available
185
+ - JIT compilation (XLA) improves performance for large chains
186
+ - Vectorized multi-chain execution is used throughout
187
+ - Covariance estimation is performed during burn-in when enabled
188
+
189
+ ## Example: Multi-sampler comparison
190
+
191
+ ```python
192
+ sampler.set_initial_state(initial_state=means, covmat=covmat, initial_distribution="gaussian")
193
+ res_aies = sampler.sample(method="aies", n_steps=5000, n_chains=100)
194
+ res_hmc = sampler.sample(method="hmc", n_steps=5000, n_chains=100)
195
+ res_nuts = sampler.sample(method="nuts", n_steps=5000, n_chains=100)
196
+ res_mh = sampler.sample(method="mh", n_steps=5000, n_chains=100)
197
+ res_mala = sampler.sample(method="mala", n_steps=5000, n_chains=100)
198
+ ```
199
+
200
+ ## Requirements
201
+ - Python ≥ 3.10
202
+ - TensorFlow ≥ 2.17
203
+ - TensorFlow Probability ≥ 0.24
204
+ - NumPy
205
+ - tf-keras
206
+ - hypersphere-sampler
207
+
208
+ ## Citation
209
+ If you use this package, please cite:
210
+
211
+ Citation will be added once the accompanying paper is available (arXiv preprint in preparation).
212
+
213
+ ## Contributing
214
+ Contributions are welcome.
215
+ ###Steps:
216
+ - Fork repository
217
+ - Create feature branch
218
+ - Add tests in ```tests/```
219
+ - Submit pull request
220
+
221
+ ## License
222
+ #### MIT License
223
+ Copyright (c) 2026 Andreas Nygaard
@@ -0,0 +1,207 @@
1
+ # BEST
2
+
3
+ (**B**atched **E**mulator **S**ampling with **T**ensorFlow)
4
+
5
+ A TensorFlow-based inference framework for high-performance Markov Chain Monte Carlo (MCMC) sampling, including support for neural likelihood emulators ("client emulators") and adaptive covariance estimation.
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ `best` provides a unified interface for sampling using multiple MCMC algorithms with GPU acceleration via TensorFlow:
12
+
13
+ ### Supported samplers
14
+
15
+ - Metropolis-Hastings (MH)
16
+ - Affine Invariant Ensemble Sampler (AIES)
17
+ - Hamiltonian Monte Carlo (HMC)
18
+ - No-U-Turn Sampler (NUTS)
19
+ - Metropolis Adjusted Langevin Algorithm (MALA)
20
+
21
+ ### Key features
22
+
23
+ - TensorFlow / GPU acceleration
24
+ - Automatic covariance matrix adaptation
25
+ - Bounded parameter inference
26
+ - Parallelized multi-chain sampling
27
+ - Pretrained neural likelihood emulators
28
+ - JIT compilation (XLA support)
29
+
30
+ ---
31
+
32
+ ## Installation
33
+
34
+ ### From PyPI
35
+
36
+ ```bash
37
+ pip install best-inference
38
+ ```
39
+ ### From source
40
+
41
+ ```bash
42
+ git clone https://github.com/AndreasNygaard/best-inference.git
43
+ cd best-inference
44
+ pip install .
45
+ ```
46
+ ---
47
+ ## Quick start
48
+
49
+ ```python
50
+ import best
51
+ import tensorflow as tf
52
+
53
+ def log_prob(x):
54
+ return -0.5 * tf.reduce_sum(x**2, axis=-1)
55
+
56
+ sampler = best.Sampler(log_prob, bounds=([-5, -5], [5, 5]))
57
+
58
+ results = sampler.sample(
59
+ method="hmc",
60
+ n_steps=2000,
61
+ n_chains=50,
62
+ initial_distribution="uniform",
63
+ num_burnin_steps=1000
64
+ )
65
+
66
+ print(results.samples.shape)
67
+ ```
68
+ ---
69
+ ## Sampler API
70
+
71
+ ### Initialisation
72
+
73
+ ```python
74
+ best.Sampler(
75
+ log_prob_fn,
76
+ bounds=None,
77
+ enforce_boundaries=True,
78
+ covmat=None,
79
+ initial_state=None,
80
+ n_chains=10,
81
+ initial_distribution="repeat"
82
+ )
83
+ ```
84
+
85
+ ### Sampling
86
+
87
+ ```python
88
+ results = sampler.sample(
89
+ method="mh" | "aies" | "hmc" | "nuts" | "mala",
90
+ n_steps=1000,
91
+ n_chains=10,
92
+ initial_state=None,
93
+ initial_distribution="repeat" | "uniform" | "gaussian",
94
+ bounds=None,
95
+ covmat=None,
96
+ num_burnin_steps=100,
97
+ num_covmat_updates=None,
98
+ sampler_kwargs={},
99
+ burnin_kwargs={},
100
+ get_individual_chains=True,
101
+ jit_compile=True
102
+ )
103
+ ```
104
+
105
+ ### Output
106
+
107
+ ```python
108
+ results.samples
109
+ results.acceptance_rate
110
+ results.evaluations
111
+
112
+ results.burnin_samples
113
+ results.burnin_acceptance_rates
114
+ results.burnin_evaluations
115
+ results.covmat_estimate
116
+ ```
117
+
118
+ ## Client emulators
119
+
120
+ BEST includes pretrained neural likelihood emulators for cosmology-inspired inference problems.
121
+
122
+ ### Available models
123
+
124
+ - lcdm
125
+ - sterile_neutrino
126
+
127
+ ### Load a model
128
+
129
+ ```python
130
+ from best.client_emulators import load_model_and_scalers
131
+
132
+ log_prob_fn, lower_bounds, upper_bounds = load_model_and_scalers("lcdm")
133
+ ```
134
+
135
+ ### Example: emulator-based inference
136
+
137
+ ```
138
+ import best
139
+ from best.client_emulators import load_model_and_scalers
140
+
141
+ log_prob_fn, lower, upper = load_model_and_scalers("lcdm")
142
+
143
+ sampler = best.Sampler(log_prob_fn, bounds=(lower, upper))
144
+
145
+ results = sampler.sample(
146
+ method="aies",
147
+ n_steps=5000,
148
+ n_chains=100,
149
+ initial_distribution="uniform",
150
+ num_burnin_steps=2000,
151
+ num_covmat_updates=1
152
+ )
153
+ ```
154
+
155
+ ## Supported Algorithms
156
+ ### Metropolis-Hastings (MH)
157
+ Random-walk MCMC with optional adaptive covariance scaling.
158
+ ### Affine Invariant Ensemble Sampler (AIES)
159
+ Efficient for highly anisotropic or correlated parameter spaces.
160
+ ### Hamiltonian Monte Carlo (HMC)
161
+ Gradient-based sampling with leapfrog integration.
162
+ ### No-U-Turn Sampler (NUTS)
163
+ Adaptive HMC variant with automatic trajectory length selection.
164
+ ### Metropolis Adjusted Langevin Algorithm (MALA)
165
+ Gradient-informed diffusion-based sampler.
166
+
167
+ ## Performance Notes
168
+ - TensorFlow enables GPU acceleration where available
169
+ - JIT compilation (XLA) improves performance for large chains
170
+ - Vectorized multi-chain execution is used throughout
171
+ - Covariance estimation is performed during burn-in when enabled
172
+
173
+ ## Example: Multi-sampler comparison
174
+
175
+ ```python
176
+ sampler.set_initial_state(initial_state=means, covmat=covmat, initial_distribution="gaussian")
177
+ res_aies = sampler.sample(method="aies", n_steps=5000, n_chains=100)
178
+ res_hmc = sampler.sample(method="hmc", n_steps=5000, n_chains=100)
179
+ res_nuts = sampler.sample(method="nuts", n_steps=5000, n_chains=100)
180
+ res_mh = sampler.sample(method="mh", n_steps=5000, n_chains=100)
181
+ res_mala = sampler.sample(method="mala", n_steps=5000, n_chains=100)
182
+ ```
183
+
184
+ ## Requirements
185
+ - Python ≥ 3.10
186
+ - TensorFlow ≥ 2.17
187
+ - TensorFlow Probability ≥ 0.24
188
+ - NumPy
189
+ - tf-keras
190
+ - hypersphere-sampler
191
+
192
+ ## Citation
193
+ If you use this package, please cite:
194
+
195
+ Citation will be added once the accompanying paper is available (arXiv preprint in preparation).
196
+
197
+ ## Contributing
198
+ Contributions are welcome.
199
+ ###Steps:
200
+ - Fork repository
201
+ - Create feature branch
202
+ - Add tests in ```tests/```
203
+ - Submit pull request
204
+
205
+ ## License
206
+ #### MIT License
207
+ Copyright (c) 2026 Andreas Nygaard
@@ -0,0 +1,13 @@
1
+ import os
2
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
3
+ os.environ["ABSL_MIN_LOG_LEVEL"] = "3"
4
+
5
+ from .run_sampling import Sampler
6
+ from . import mcmc_methods, tools, client_emulators
7
+
8
+ __all__ = [
9
+ "Sampler",
10
+ "mcmc_methods",
11
+ "tools",
12
+ "client_emulators"
13
+ ]
@@ -0,0 +1,7 @@
1
+ from .load_client_network import load_model_and_scalers
2
+ from . import custom_objects
3
+
4
+ __all__ = [
5
+ "load_model_and_scalers",
6
+ "custom_objects"
7
+ ]
@@ -0,0 +1,87 @@
1
+ import tensorflow as tf
2
+ import numpy as np
3
+
4
+ class CustomTanh(tf.keras.layers.Layer):
5
+ def __init__(self, initial_alpha=1.0, **kwargs):
6
+ super().__init__(**kwargs)
7
+ self.initial_alpha = initial_alpha
8
+
9
+ def build(self, input_shape):
10
+ self.alpha = self.add_weight(
11
+ name='alpha',
12
+ shape=(1,),
13
+ initializer=tf.keras.initializers.Constant(self.initial_alpha),
14
+ trainable=True
15
+ )
16
+ super().build(input_shape)
17
+
18
+ @tf.function(jit_compile=True)
19
+ def call(self, inputs):
20
+ return tf.math.tanh(self.alpha * inputs)
21
+
22
+ def get_config(self):
23
+ config = super().get_config()
24
+ config.update({
25
+ "initial_alpha": self.initial_alpha
26
+ })
27
+ return config
28
+
29
+ class Alsing(tf.keras.layers.Layer):
30
+ def __init__(self, initial_beta=1.0, initial_gamma=0.0, **kwargs):
31
+ super().__init__(**kwargs)
32
+ self.initial_beta = initial_beta
33
+ self.initial_gamma = initial_gamma
34
+
35
+ def build(self, input_shape):
36
+ self.beta = self.add_weight(
37
+ name="beta",
38
+ shape=(1,),
39
+ initializer=tf.keras.initializers.Constant(self.initial_beta),
40
+ trainable=True
41
+ )
42
+ self.gamma = self.add_weight(
43
+ name="gamma",
44
+ shape=(1,),
45
+ initializer=tf.keras.initializers.Constant(self.initial_gamma),
46
+ trainable=True
47
+ )
48
+ super().build(input_shape)
49
+
50
+ @tf.function(jit_compile=True)
51
+ def call(self, inputs):
52
+ return (self.gamma + (1 - self.gamma) / (1 + tf.exp(-self.beta * inputs))) * inputs
53
+
54
+ def get_config(self):
55
+ config = super().get_config()
56
+ config.update({
57
+ "initial_beta": self.initial_beta,
58
+ "initial_gamma": self.initial_gamma
59
+ })
60
+ return config
61
+
62
+ def delta_chi2_from_k(kappa, n):
63
+ kappa = tf.cast(kappa, tf.float32)
64
+ n = tf.cast(n, tf.float32)
65
+
66
+ mu = 1.0 - 2.0 / (9.0 * n)
67
+ sigma = tf.sqrt(2.0 / (9.0 * n))
68
+ return n * tf.pow(mu + kappa * sigma, 3.0)
69
+
70
+ def create_msre_loss(y_global_max, kappa, n, y_std):
71
+ delta_chi2_k = delta_chi2_from_k(kappa, n)
72
+ delta = -y_global_max - 0.5 * (delta_chi2_k / y_std)
73
+
74
+ def mean_square_relative_error(y_true, y_pred):
75
+ denominator = y_true + delta
76
+ relative_error = (y_pred - y_true) / denominator
77
+ return tf.reduce_mean(tf.square(relative_error))
78
+
79
+ return mean_square_relative_error
80
+
81
+
82
+ def get_loss_function(loss_name, y_global_max=None, kappa=None, n=None, y_std=None):
83
+ if loss_name == 'msre':
84
+ return create_msre_loss(y_global_max, kappa, n, y_std)
85
+ if loss_name in ['mse', 'mae']:
86
+ return loss_name
87
+ return loss_name
@@ -0,0 +1,51 @@
1
+ import os
2
+ import pickle as pkl
3
+
4
+ import tensorflow as tf
5
+ import numpy as np
6
+
7
+ from best.client_emulators.custom_objects import Alsing, CustomTanh, create_msre_loss
8
+
9
+ def load_model_and_scalers(emulator_dir, root='best/client_emulators'):
10
+ model_path = os.path.join(root, emulator_dir, 'trained_model.keras')
11
+ x_scaler_path = os.path.join(root, emulator_dir, 'x_scaler.pkl')
12
+ y_scaler_path = os.path.join(root, emulator_dir, 'y_scaler.pkl')
13
+
14
+ with open(x_scaler_path, 'rb') as f:
15
+ x_scaler = pkl.load(f)
16
+ with open(y_scaler_path, 'rb') as f:
17
+ y_scaler = pkl.load(f)
18
+
19
+ x_mean = tf.constant(x_scaler.mean_, dtype=tf.float32)
20
+ x_scale = tf.constant(x_scaler.scale_, dtype=tf.float32)
21
+ y_mean = tf.constant(y_scaler.mean_, dtype=tf.float32)
22
+ y_scale = tf.constant(y_scaler.scale_, dtype=tf.float32)
23
+
24
+ mean_square_relative_error = create_msre_loss(y_global_max=10.0, kappa=3.0, n=27.0, y_std=y_scale)
25
+ custom_objects={'Alsing': Alsing, 'CustomTanh': CustomTanh, 'mean_square_relative_error': mean_square_relative_error}
26
+
27
+ model = tf.keras.models.load_model(model_path, custom_objects=custom_objects)
28
+
29
+ ## define box function that is exponentially increasing outside limits. It should be auto-differentiable.
30
+ lower = tf.constant([
31
+ 0, 0, 0, 0, 0, 0.004, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.9
32
+ ], dtype=tf.float32)
33
+
34
+ upper = tf.constant([
35
+ 3, 0.5, 2, 5, 2, 1, 0.8, 3.0, 200, 1, 10, 400, 400, 400, 400, 10, 50, 50, 100, 400, 10, 10, 10, 10, 10, 10, 3000, 3000, 1.1
36
+ ], dtype=tf.float32)
37
+
38
+ # Box is defined for the sterile neutrino model. If LCDM is loaded instead, two dimensions should be removed
39
+ if 'lcdm' in emulator_dir:
40
+ # remove indices 6 and 7 from lower and upper bounds for LCDM model
41
+ lower = tf.concat([lower[:6], lower[8:]], axis=0)
42
+ upper = tf.concat([upper[:6], upper[8:]], axis=0)
43
+
44
+ @tf.function(reduce_retracing=True)
45
+ def log_prob_fn(x):
46
+ inp = (x - x_mean) / x_scale
47
+ y_scaled = model(inp)
48
+ log_like = tf.reshape(y_scaled * y_scale + y_mean, [-1])
49
+ return log_like
50
+
51
+ return log_prob_fn, lower, upper