CUQIpy 1.3.0.post0.dev401__py3-none-any.whl → 1.4.0.post0.dev41__py3-none-any.whl
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.
Potentially problematic release.
This version of CUQIpy might be problematic. Click here for more details.
- cuqi/__init__.py +1 -0
- cuqi/_version.py +3 -3
- cuqi/density/_density.py +9 -1
- cuqi/distribution/_joint_distribution.py +96 -11
- cuqi/experimental/__init__.py +1 -2
- cuqi/experimental/_recommender.py +4 -4
- cuqi/legacy/__init__.py +2 -0
- cuqi/legacy/sampler/__init__.py +11 -0
- cuqi/legacy/sampler/_conjugate.py +55 -0
- cuqi/legacy/sampler/_conjugate_approx.py +52 -0
- cuqi/legacy/sampler/_cwmh.py +196 -0
- cuqi/legacy/sampler/_gibbs.py +231 -0
- cuqi/legacy/sampler/_hmc.py +335 -0
- cuqi/legacy/sampler/_langevin_algorithm.py +198 -0
- cuqi/legacy/sampler/_laplace_approximation.py +184 -0
- cuqi/legacy/sampler/_mh.py +190 -0
- cuqi/legacy/sampler/_pcn.py +244 -0
- cuqi/legacy/sampler/_rto.py +284 -0
- cuqi/legacy/sampler/_sampler.py +182 -0
- cuqi/problem/_problem.py +87 -80
- cuqi/sampler/__init__.py +120 -8
- cuqi/sampler/_conjugate.py +376 -35
- cuqi/sampler/_conjugate_approx.py +40 -16
- cuqi/sampler/_cwmh.py +132 -138
- cuqi/{experimental/mcmc → sampler}/_direct.py +1 -1
- cuqi/sampler/_gibbs.py +269 -130
- cuqi/sampler/_hmc.py +328 -201
- cuqi/sampler/_langevin_algorithm.py +282 -98
- cuqi/sampler/_laplace_approximation.py +87 -117
- cuqi/sampler/_mh.py +47 -157
- cuqi/sampler/_pcn.py +56 -211
- cuqi/sampler/_rto.py +206 -140
- cuqi/sampler/_sampler.py +540 -135
- {cuqipy-1.3.0.post0.dev401.dist-info → cuqipy-1.4.0.post0.dev41.dist-info}/METADATA +1 -1
- {cuqipy-1.3.0.post0.dev401.dist-info → cuqipy-1.4.0.post0.dev41.dist-info}/RECORD +38 -37
- cuqi/experimental/mcmc/__init__.py +0 -122
- cuqi/experimental/mcmc/_conjugate.py +0 -396
- cuqi/experimental/mcmc/_conjugate_approx.py +0 -76
- cuqi/experimental/mcmc/_cwmh.py +0 -190
- cuqi/experimental/mcmc/_gibbs.py +0 -366
- cuqi/experimental/mcmc/_hmc.py +0 -462
- cuqi/experimental/mcmc/_langevin_algorithm.py +0 -382
- cuqi/experimental/mcmc/_laplace_approximation.py +0 -154
- cuqi/experimental/mcmc/_mh.py +0 -80
- cuqi/experimental/mcmc/_pcn.py +0 -89
- cuqi/experimental/mcmc/_rto.py +0 -350
- cuqi/experimental/mcmc/_sampler.py +0 -582
- {cuqipy-1.3.0.post0.dev401.dist-info → cuqipy-1.4.0.post0.dev41.dist-info}/WHEEL +0 -0
- {cuqipy-1.3.0.post0.dev401.dist-info → cuqipy-1.4.0.post0.dev41.dist-info}/licenses/LICENSE +0 -0
- {cuqipy-1.3.0.post0.dev401.dist-info → cuqipy-1.4.0.post0.dev41.dist-info}/top_level.txt +0 -0
cuqi/experimental/mcmc/_hmc.py
DELETED
|
@@ -1,462 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import numpy as np
|
|
3
|
-
from cuqi.experimental.mcmc import Sampler
|
|
4
|
-
from cuqi.array import CUQIarray
|
|
5
|
-
from numbers import Number
|
|
6
|
-
|
|
7
|
-
class NUTS(Sampler):
|
|
8
|
-
"""No-U-Turn Sampler (Hoffman and Gelman, 2014).
|
|
9
|
-
|
|
10
|
-
Samples a distribution given its logpdf and gradient using a Hamiltonian
|
|
11
|
-
Monte Carlo (HMC) algorithm with automatic parameter tuning.
|
|
12
|
-
|
|
13
|
-
For more details see: See Hoffman, M. D., & Gelman, A. (2014). The no-U-turn
|
|
14
|
-
sampler: Adaptively setting path lengths in Hamiltonian Monte Carlo. Journal
|
|
15
|
-
of Machine Learning Research, 15, 1593-1623.
|
|
16
|
-
|
|
17
|
-
Parameters
|
|
18
|
-
----------
|
|
19
|
-
target : `cuqi.distribution.Distribution`
|
|
20
|
-
The target distribution to sample. Must have logpdf and gradient method.
|
|
21
|
-
Custom logpdfs and gradients are supported by using a
|
|
22
|
-
:class:`cuqi.distribution.UserDefinedDistribution`.
|
|
23
|
-
|
|
24
|
-
initial_point : ndarray
|
|
25
|
-
Initial parameters. *Optional*. If not provided, the initial point is
|
|
26
|
-
an array of ones.
|
|
27
|
-
|
|
28
|
-
max_depth : int
|
|
29
|
-
Maximum depth of the tree >=0 and the default is 15.
|
|
30
|
-
|
|
31
|
-
step_size : None or float
|
|
32
|
-
If step_size is provided (as positive float), it will be used as initial
|
|
33
|
-
step size. If None, the step size will be estimated by the sampler.
|
|
34
|
-
|
|
35
|
-
opt_acc_rate : float
|
|
36
|
-
The optimal acceptance rate to reach if using adaptive step size.
|
|
37
|
-
Suggested values are 0.6 (default) or 0.8 (as in stan). In principle,
|
|
38
|
-
opt_acc_rate should be in (0, 1), however, choosing a value that is very
|
|
39
|
-
close to 1 or 0 might lead to poor performance of the sampler.
|
|
40
|
-
|
|
41
|
-
callback : callable, optional
|
|
42
|
-
A function that will be called after each sampling step. It can be useful for monitoring the sampler during sampling.
|
|
43
|
-
The function should take three arguments: the sampler object, the index of the current sampling step, the total number of requested samples. The last two arguments are integers. An example of the callback function signature is: `callback(sampler, sample_index, num_of_samples)`.
|
|
44
|
-
|
|
45
|
-
Example
|
|
46
|
-
-------
|
|
47
|
-
.. code-block:: python
|
|
48
|
-
|
|
49
|
-
# Import cuqi
|
|
50
|
-
import cuqi
|
|
51
|
-
|
|
52
|
-
# Define a target distribution
|
|
53
|
-
tp = cuqi.testproblem.WangCubic()
|
|
54
|
-
target = tp.posterior
|
|
55
|
-
|
|
56
|
-
# Set up sampler
|
|
57
|
-
sampler = cuqi.experimental.mcmc.NUTS(target)
|
|
58
|
-
|
|
59
|
-
# Sample
|
|
60
|
-
sampler.warmup(5000)
|
|
61
|
-
sampler.sample(10000)
|
|
62
|
-
|
|
63
|
-
# Get samples
|
|
64
|
-
samples = sampler.get_samples()
|
|
65
|
-
|
|
66
|
-
# Plot samples
|
|
67
|
-
samples.plot_pair()
|
|
68
|
-
|
|
69
|
-
After running the NUTS sampler, run diagnostics can be accessed via the
|
|
70
|
-
following attributes:
|
|
71
|
-
|
|
72
|
-
.. code-block:: python
|
|
73
|
-
|
|
74
|
-
# Number of tree nodes created each NUTS iteration
|
|
75
|
-
sampler.num_tree_node_list
|
|
76
|
-
|
|
77
|
-
# Step size used in each NUTS iteration
|
|
78
|
-
sampler.epsilon_list
|
|
79
|
-
|
|
80
|
-
# Suggested step size during adaptation (the value of this step size is
|
|
81
|
-
# only used after adaptation).
|
|
82
|
-
sampler.epsilon_bar_list
|
|
83
|
-
|
|
84
|
-
"""
|
|
85
|
-
|
|
86
|
-
_STATE_KEYS = Sampler._STATE_KEYS.union({'_epsilon', '_epsilon_bar',
|
|
87
|
-
'_H_bar',
|
|
88
|
-
'current_target_logd',
|
|
89
|
-
'current_target_grad',
|
|
90
|
-
'max_depth'})
|
|
91
|
-
|
|
92
|
-
_HISTORY_KEYS = Sampler._HISTORY_KEYS.union({'num_tree_node_list',
|
|
93
|
-
'epsilon_list',
|
|
94
|
-
'epsilon_bar_list'})
|
|
95
|
-
|
|
96
|
-
def __init__(self, target=None, initial_point=None, max_depth=None,
|
|
97
|
-
step_size=None, opt_acc_rate=0.6, **kwargs):
|
|
98
|
-
super().__init__(target, initial_point=initial_point, **kwargs)
|
|
99
|
-
|
|
100
|
-
# Assign parameters as attributes
|
|
101
|
-
self.max_depth = max_depth
|
|
102
|
-
self.step_size = step_size
|
|
103
|
-
self.opt_acc_rate = opt_acc_rate
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def _initialize(self):
|
|
107
|
-
|
|
108
|
-
self._current_alpha_ratio = np.nan # Current alpha ratio will be set to some
|
|
109
|
-
# value (other than np.nan) before
|
|
110
|
-
# being used
|
|
111
|
-
|
|
112
|
-
self.current_target_logd, self.current_target_grad = self._nuts_target(self.current_point)
|
|
113
|
-
|
|
114
|
-
# Parameters dual averaging
|
|
115
|
-
# Initialize epsilon and epsilon_bar
|
|
116
|
-
# epsilon is the step size used in the current iteration
|
|
117
|
-
# after warm up and one sampling step, epsilon is updated
|
|
118
|
-
# to epsilon_bar for the remaining sampling steps.
|
|
119
|
-
if self.step_size is None:
|
|
120
|
-
self._epsilon = self._FindGoodEpsilon()
|
|
121
|
-
self.step_size = self._epsilon
|
|
122
|
-
else:
|
|
123
|
-
self._epsilon = self.step_size
|
|
124
|
-
|
|
125
|
-
self._epsilon_bar = "unset"
|
|
126
|
-
|
|
127
|
-
# Parameter mu, does not change during the run
|
|
128
|
-
self._mu = np.log(10*self._epsilon)
|
|
129
|
-
|
|
130
|
-
self._H_bar = 0
|
|
131
|
-
|
|
132
|
-
# NUTS run diagnostics
|
|
133
|
-
# number of tree nodes created each NUTS iteration
|
|
134
|
-
self._num_tree_node = 0
|
|
135
|
-
|
|
136
|
-
# Create lists to store NUTS run diagnostics
|
|
137
|
-
self._create_run_diagnostic_attributes()
|
|
138
|
-
|
|
139
|
-
#=========================================================================
|
|
140
|
-
#============================== Properties ===============================
|
|
141
|
-
#=========================================================================
|
|
142
|
-
@property
|
|
143
|
-
def max_depth(self):
|
|
144
|
-
return self._max_depth
|
|
145
|
-
|
|
146
|
-
@max_depth.setter
|
|
147
|
-
def max_depth(self, value):
|
|
148
|
-
if value is None:
|
|
149
|
-
value = 15 # default value
|
|
150
|
-
if not isinstance(value, int):
|
|
151
|
-
raise TypeError('max_depth must be an integer.')
|
|
152
|
-
if value < 0:
|
|
153
|
-
raise ValueError('max_depth must be >= 0.')
|
|
154
|
-
self._max_depth = value
|
|
155
|
-
|
|
156
|
-
@property
|
|
157
|
-
def step_size(self):
|
|
158
|
-
return self._step_size
|
|
159
|
-
|
|
160
|
-
@step_size.setter
|
|
161
|
-
def step_size(self, value):
|
|
162
|
-
if value is None:
|
|
163
|
-
pass # NUTS will adapt the step size
|
|
164
|
-
|
|
165
|
-
# step_size must be a positive float, raise error otherwise
|
|
166
|
-
elif isinstance(value, bool)\
|
|
167
|
-
or not isinstance(value, Number)\
|
|
168
|
-
or value <= 0:
|
|
169
|
-
raise TypeError('step_size must be a positive float or None.')
|
|
170
|
-
self._step_size = value
|
|
171
|
-
|
|
172
|
-
@property
|
|
173
|
-
def opt_acc_rate(self):
|
|
174
|
-
return self._opt_acc_rate
|
|
175
|
-
|
|
176
|
-
@opt_acc_rate.setter
|
|
177
|
-
def opt_acc_rate(self, value):
|
|
178
|
-
if not isinstance(value, Number) or value <= 0 or value >= 1:
|
|
179
|
-
raise ValueError('opt_acc_rate must be a float in (0, 1).')
|
|
180
|
-
self._opt_acc_rate = value
|
|
181
|
-
|
|
182
|
-
#=========================================================================
|
|
183
|
-
#================== Implement methods required by Sampler =============
|
|
184
|
-
#=========================================================================
|
|
185
|
-
def validate_target(self):
|
|
186
|
-
# Check if the target has logd and gradient methods
|
|
187
|
-
try:
|
|
188
|
-
current_target_logd, current_target_grad =\
|
|
189
|
-
self._nuts_target(np.ones(self.dim))
|
|
190
|
-
except:
|
|
191
|
-
raise ValueError('Target must have logd and gradient methods.')
|
|
192
|
-
|
|
193
|
-
def reinitialize(self):
|
|
194
|
-
# Call the parent reset method
|
|
195
|
-
super().reinitialize()
|
|
196
|
-
# Reset NUTS run diagnostic attributes
|
|
197
|
-
self._reset_run_diagnostic_attributes()
|
|
198
|
-
|
|
199
|
-
def step(self):
|
|
200
|
-
if isinstance(self._epsilon_bar, str) and self._epsilon_bar == "unset":
|
|
201
|
-
self._epsilon_bar = self._epsilon
|
|
202
|
-
|
|
203
|
-
# Convert current_point, logd, and grad to numpy arrays
|
|
204
|
-
# if they are CUQIarray objects
|
|
205
|
-
if isinstance(self.current_point, CUQIarray):
|
|
206
|
-
self.current_point = self.current_point.to_numpy()
|
|
207
|
-
if isinstance(self.current_target_logd, CUQIarray):
|
|
208
|
-
self.current_target_logd = self.current_target_logd.to_numpy()
|
|
209
|
-
if isinstance(self.current_target_grad, CUQIarray):
|
|
210
|
-
self.current_target_grad = self.current_target_grad.to_numpy()
|
|
211
|
-
|
|
212
|
-
# reset number of tree nodes for each iteration
|
|
213
|
-
self._num_tree_node = 0
|
|
214
|
-
|
|
215
|
-
# copy current point, logd, and grad in local variables
|
|
216
|
-
point_k = self.current_point # initial position (parameters)
|
|
217
|
-
logd_k = self.current_target_logd
|
|
218
|
-
grad_k = self.current_target_grad # initial gradient
|
|
219
|
-
|
|
220
|
-
# compute r_k and Hamiltonian
|
|
221
|
-
r_k = self._Kfun(1, 'sample') # resample momentum vector
|
|
222
|
-
Ham = logd_k - self._Kfun(r_k, 'eval') # Hamiltonian
|
|
223
|
-
|
|
224
|
-
# slice variable
|
|
225
|
-
log_u = Ham - np.random.exponential(1, size=1)
|
|
226
|
-
|
|
227
|
-
# initialization
|
|
228
|
-
j, s, n = 0, 1, 1
|
|
229
|
-
point_minus, point_plus = point_k.copy(), point_k.copy()
|
|
230
|
-
grad_minus, grad_plus = grad_k.copy(), grad_k.copy()
|
|
231
|
-
r_minus, r_plus = r_k.copy(), r_k.copy()
|
|
232
|
-
|
|
233
|
-
# run NUTS
|
|
234
|
-
acc = 0
|
|
235
|
-
while (s == 1) and (j <= self.max_depth):
|
|
236
|
-
# sample a direction
|
|
237
|
-
v = int(2*(np.random.rand() < 0.5)-1)
|
|
238
|
-
|
|
239
|
-
# build tree: doubling procedure
|
|
240
|
-
if (v == -1):
|
|
241
|
-
point_minus, r_minus, grad_minus, _, _, _, \
|
|
242
|
-
point_prime, logd_prime, grad_prime,\
|
|
243
|
-
n_prime, s_prime, alpha, n_alpha = \
|
|
244
|
-
self._BuildTree(point_minus, r_minus, grad_minus,
|
|
245
|
-
Ham, log_u, v, j, self._epsilon)
|
|
246
|
-
else:
|
|
247
|
-
_, _, _, point_plus, r_plus, grad_plus, \
|
|
248
|
-
point_prime, logd_prime, grad_prime,\
|
|
249
|
-
n_prime, s_prime, alpha, n_alpha = \
|
|
250
|
-
self._BuildTree(point_plus, r_plus, grad_plus,
|
|
251
|
-
Ham, log_u, v, j, self._epsilon)
|
|
252
|
-
|
|
253
|
-
# Metropolis step
|
|
254
|
-
alpha2 = min(1, (n_prime/n)) #min(0, np.log(n_p) - np.log(n))
|
|
255
|
-
if (s_prime == 1) and \
|
|
256
|
-
(np.random.rand() <= alpha2) and \
|
|
257
|
-
(not np.isnan(logd_prime)) and \
|
|
258
|
-
(not np.isinf(logd_prime)):
|
|
259
|
-
self.current_point = point_prime.copy()
|
|
260
|
-
# copy if array, else assign if scalar
|
|
261
|
-
self.current_target_logd = (
|
|
262
|
-
logd_prime.copy()
|
|
263
|
-
if isinstance(logd_prime, np.ndarray)
|
|
264
|
-
else logd_prime
|
|
265
|
-
)
|
|
266
|
-
self.current_target_grad = grad_prime.copy()
|
|
267
|
-
acc = 1
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
# update number of particles, tree level, and stopping criterion
|
|
271
|
-
n += n_prime
|
|
272
|
-
dpoints = point_plus - point_minus
|
|
273
|
-
s = s_prime *\
|
|
274
|
-
int((dpoints @ r_minus.T) >= 0) * int((dpoints @ r_plus.T) >= 0)
|
|
275
|
-
j += 1
|
|
276
|
-
self._current_alpha_ratio = alpha/n_alpha
|
|
277
|
-
|
|
278
|
-
# update run diagnostic attributes
|
|
279
|
-
self._update_run_diagnostic_attributes(
|
|
280
|
-
self._num_tree_node, self._epsilon, self._epsilon_bar)
|
|
281
|
-
|
|
282
|
-
self._epsilon = self._epsilon_bar
|
|
283
|
-
if np.isnan(self.current_target_logd):
|
|
284
|
-
raise NameError('NaN potential func')
|
|
285
|
-
|
|
286
|
-
return acc
|
|
287
|
-
|
|
288
|
-
def tune(self, skip_len, update_count):
|
|
289
|
-
""" adapt epsilon during burn-in using dual averaging"""
|
|
290
|
-
if isinstance(self._epsilon_bar, str) and self._epsilon_bar == "unset":
|
|
291
|
-
self._epsilon_bar = 1
|
|
292
|
-
|
|
293
|
-
k = update_count+1
|
|
294
|
-
|
|
295
|
-
# Fixed parameters that do not change during the run
|
|
296
|
-
gamma, t_0, kappa = 0.05, 10, 0.75 # kappa in (0.5, 1]
|
|
297
|
-
|
|
298
|
-
eta1 = 1/(k + t_0)
|
|
299
|
-
self._H_bar = (1-eta1)*self._H_bar +\
|
|
300
|
-
eta1*(self.opt_acc_rate - (self._current_alpha_ratio))
|
|
301
|
-
self._epsilon = np.exp(self._mu - (np.sqrt(k)/gamma)*self._H_bar)
|
|
302
|
-
eta = k**(-kappa)
|
|
303
|
-
self._epsilon_bar =\
|
|
304
|
-
np.exp(eta*np.log(self._epsilon) +(1-eta)*np.log(self._epsilon_bar))
|
|
305
|
-
|
|
306
|
-
#=========================================================================
|
|
307
|
-
def _nuts_target(self, x): # returns logposterior tuple evaluation-gradient
|
|
308
|
-
return self.target.logd(x), self.target.gradient(x)
|
|
309
|
-
|
|
310
|
-
#=========================================================================
|
|
311
|
-
# auxiliary standard Gaussian PDF: kinetic energy function
|
|
312
|
-
# d_log_2pi = d*np.log(2*np.pi)
|
|
313
|
-
def _Kfun(self, r, flag):
|
|
314
|
-
if flag == 'eval': # evaluate
|
|
315
|
-
return 0.5*(r.T @ r) #+ d_log_2pi
|
|
316
|
-
if flag == 'sample': # sample
|
|
317
|
-
return np.random.standard_normal(size=self.dim)
|
|
318
|
-
|
|
319
|
-
#=========================================================================
|
|
320
|
-
def _FindGoodEpsilon(self, epsilon=1):
|
|
321
|
-
point_k = self.current_point
|
|
322
|
-
self.current_target_logd, self.current_target_grad = self._nuts_target(
|
|
323
|
-
point_k)
|
|
324
|
-
logd = self.current_target_logd
|
|
325
|
-
grad = self.current_target_grad
|
|
326
|
-
|
|
327
|
-
r = self._Kfun(1, 'sample') # resample a momentum
|
|
328
|
-
Ham = logd - self._Kfun(r, 'eval') # initial Hamiltonian
|
|
329
|
-
_, r_prime, logd_prime, grad_prime = self._Leapfrog(
|
|
330
|
-
point_k, r, grad, epsilon)
|
|
331
|
-
|
|
332
|
-
# trick to make sure the step is not huge, leading to infinite values of
|
|
333
|
-
# the likelihood
|
|
334
|
-
k = 1
|
|
335
|
-
while np.isinf(logd_prime) or np.isinf(grad_prime).any():
|
|
336
|
-
k *= 0.5
|
|
337
|
-
_, r_prime, logd_prime, grad_prime = self._Leapfrog(
|
|
338
|
-
point_k, r, grad, epsilon*k)
|
|
339
|
-
epsilon = 0.5*k*epsilon
|
|
340
|
-
|
|
341
|
-
# doubles/halves the value of epsilon until the accprob of the Langevin
|
|
342
|
-
# proposal crosses 0.5
|
|
343
|
-
Ham_prime = logd_prime - self._Kfun(r_prime, 'eval')
|
|
344
|
-
log_ratio = Ham_prime - Ham
|
|
345
|
-
a = 1 if log_ratio > np.log(0.5) else -1
|
|
346
|
-
while (a*log_ratio > -a*np.log(2)):
|
|
347
|
-
epsilon = (2**a)*epsilon
|
|
348
|
-
_, r_prime, logd_prime, _ = self._Leapfrog(
|
|
349
|
-
point_k, r, grad, epsilon)
|
|
350
|
-
Ham_prime = logd_prime - self._Kfun(r_prime, 'eval')
|
|
351
|
-
log_ratio = Ham_prime - Ham
|
|
352
|
-
return epsilon
|
|
353
|
-
|
|
354
|
-
#=========================================================================
|
|
355
|
-
def _Leapfrog(self, point_old, r_old, grad_old, epsilon):
|
|
356
|
-
# symplectic integrator: trajectories preserve phase space volumen
|
|
357
|
-
r_new = r_old + 0.5*epsilon*grad_old # half-step
|
|
358
|
-
point_new = point_old + epsilon*r_new # full-step
|
|
359
|
-
logd_new, grad_new = self._nuts_target(point_new) # new gradient
|
|
360
|
-
r_new += 0.5*epsilon*grad_new # half-step
|
|
361
|
-
return point_new, r_new, logd_new, grad_new
|
|
362
|
-
|
|
363
|
-
#=========================================================================
|
|
364
|
-
def _BuildTree(
|
|
365
|
-
self, point_k, r, grad, Ham, log_u, v, j, epsilon, Delta_max=1000):
|
|
366
|
-
# Increment the number of tree nodes counter
|
|
367
|
-
self._num_tree_node += 1
|
|
368
|
-
|
|
369
|
-
if (j == 0): # base case
|
|
370
|
-
# single leapfrog step in the direction v
|
|
371
|
-
point_prime, r_prime, logd_prime, grad_prime = self._Leapfrog(
|
|
372
|
-
point_k, r, grad, v*epsilon)
|
|
373
|
-
Ham_prime = logd_prime - self._Kfun(r_prime, 'eval') # Hamiltonian
|
|
374
|
-
# eval
|
|
375
|
-
n_prime = int(log_u <= Ham_prime) # if particle is in the slice
|
|
376
|
-
s_prime = int(log_u < Delta_max + Ham_prime) # check U-turn
|
|
377
|
-
#
|
|
378
|
-
diff_Ham = Ham_prime - Ham
|
|
379
|
-
|
|
380
|
-
# Compute the acceptance probability
|
|
381
|
-
# alpha_prime = min(1, np.exp(diff_Ham))
|
|
382
|
-
# written in a stable way to avoid overflow when computing
|
|
383
|
-
# exp(diff_Ham) for large values of diff_Ham
|
|
384
|
-
alpha_prime = 1 if diff_Ham > 0 else np.exp(diff_Ham)
|
|
385
|
-
n_alpha_prime = 1
|
|
386
|
-
#
|
|
387
|
-
point_minus, point_plus = point_prime, point_prime
|
|
388
|
-
r_minus, r_plus = r_prime, r_prime
|
|
389
|
-
grad_minus, grad_plus = grad_prime, grad_prime
|
|
390
|
-
else:
|
|
391
|
-
# recursion: build the left/right subtrees
|
|
392
|
-
point_minus, r_minus, grad_minus, point_plus, r_plus, grad_plus, \
|
|
393
|
-
point_prime, logd_prime, grad_prime,\
|
|
394
|
-
n_prime, s_prime, alpha_prime, n_alpha_prime = \
|
|
395
|
-
self._BuildTree(point_k, r, grad,
|
|
396
|
-
Ham, log_u, v, j-1, epsilon)
|
|
397
|
-
if (s_prime == 1): # do only if the stopping criteria does not
|
|
398
|
-
# verify at the first subtree
|
|
399
|
-
if (v == -1):
|
|
400
|
-
point_minus, r_minus, grad_minus, _, _, _, \
|
|
401
|
-
point_2prime, logd_2prime, grad_2prime,\
|
|
402
|
-
n_2prime, s_2prime, alpha_2prime, n_alpha_2prime = \
|
|
403
|
-
self._BuildTree(point_minus, r_minus, grad_minus,
|
|
404
|
-
Ham, log_u, v, j-1, epsilon)
|
|
405
|
-
else:
|
|
406
|
-
_, _, _, point_plus, r_plus, grad_plus, \
|
|
407
|
-
point_2prime, logd_2prime, grad_2prime,\
|
|
408
|
-
n_2prime, s_2prime, alpha_2prime, n_alpha_2prime = \
|
|
409
|
-
self._BuildTree(point_plus, r_plus, grad_plus,
|
|
410
|
-
Ham, log_u, v, j-1, epsilon)
|
|
411
|
-
|
|
412
|
-
# Metropolis step
|
|
413
|
-
alpha2 = n_2prime / max(1, (n_prime + n_2prime))
|
|
414
|
-
if (np.random.rand() <= alpha2):
|
|
415
|
-
point_prime = point_2prime.copy()
|
|
416
|
-
# copy if array, else assign if scalar
|
|
417
|
-
logd_prime = (
|
|
418
|
-
logd_2prime.copy()
|
|
419
|
-
if isinstance(logd_2prime, np.ndarray)
|
|
420
|
-
else logd_2prime
|
|
421
|
-
)
|
|
422
|
-
grad_prime = grad_2prime.copy()
|
|
423
|
-
|
|
424
|
-
# update number of particles and stopping criterion
|
|
425
|
-
alpha_prime += alpha_2prime
|
|
426
|
-
n_alpha_prime += n_alpha_2prime
|
|
427
|
-
dpoints = point_plus - point_minus
|
|
428
|
-
s_prime = s_2prime *\
|
|
429
|
-
int((dpoints@r_minus.T)>=0) * int((dpoints@r_plus.T)>=0)
|
|
430
|
-
n_prime += n_2prime
|
|
431
|
-
|
|
432
|
-
return point_minus, r_minus, grad_minus, point_plus, r_plus, grad_plus,\
|
|
433
|
-
point_prime, logd_prime, grad_prime,\
|
|
434
|
-
n_prime, s_prime, alpha_prime, n_alpha_prime
|
|
435
|
-
|
|
436
|
-
#=========================================================================
|
|
437
|
-
#======================== Diagnostic methods =============================
|
|
438
|
-
#=========================================================================
|
|
439
|
-
|
|
440
|
-
def _create_run_diagnostic_attributes(self):
|
|
441
|
-
"""A method to create attributes to store NUTS run diagnostic."""
|
|
442
|
-
self._reset_run_diagnostic_attributes()
|
|
443
|
-
|
|
444
|
-
def _reset_run_diagnostic_attributes(self):
|
|
445
|
-
"""A method to reset attributes to store NUTS run diagnostic."""
|
|
446
|
-
# List to store number of tree nodes created each NUTS iteration
|
|
447
|
-
self.num_tree_node_list = []
|
|
448
|
-
# List of step size used in each NUTS iteration
|
|
449
|
-
self.epsilon_list = []
|
|
450
|
-
# List of burn-in step size suggestion during adaptation
|
|
451
|
-
# only used when adaptation is done
|
|
452
|
-
# remains fixed after adaptation (after burn-in)
|
|
453
|
-
self.epsilon_bar_list = []
|
|
454
|
-
|
|
455
|
-
def _update_run_diagnostic_attributes(self, n_tree, eps, eps_bar):
|
|
456
|
-
"""A method to update attributes to store NUTS run diagnostic."""
|
|
457
|
-
# Store the number of tree nodes created in iteration k
|
|
458
|
-
self.num_tree_node_list.append(n_tree)
|
|
459
|
-
# Store the step size used in iteration k
|
|
460
|
-
self.epsilon_list.append(eps)
|
|
461
|
-
# Store the step size suggestion during adaptation in iteration k
|
|
462
|
-
self.epsilon_bar_list.append(eps_bar)
|