eryn 1.2.0__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.
- eryn/CMakeLists.txt +51 -0
- eryn/__init__.py +35 -0
- eryn/backends/__init__.py +20 -0
- eryn/backends/backend.py +1150 -0
- eryn/backends/hdfbackend.py +819 -0
- eryn/ensemble.py +1690 -0
- eryn/git_version.py.in +7 -0
- eryn/model.py +18 -0
- eryn/moves/__init__.py +42 -0
- eryn/moves/combine.py +135 -0
- eryn/moves/delayedrejection.py +229 -0
- eryn/moves/distgen.py +104 -0
- eryn/moves/distgenrj.py +222 -0
- eryn/moves/gaussian.py +190 -0
- eryn/moves/group.py +281 -0
- eryn/moves/groupstretch.py +120 -0
- eryn/moves/mh.py +193 -0
- eryn/moves/move.py +703 -0
- eryn/moves/mtdistgen.py +137 -0
- eryn/moves/mtdistgenrj.py +190 -0
- eryn/moves/multipletry.py +776 -0
- eryn/moves/red_blue.py +333 -0
- eryn/moves/rj.py +388 -0
- eryn/moves/stretch.py +231 -0
- eryn/moves/tempering.py +649 -0
- eryn/pbar.py +56 -0
- eryn/prior.py +452 -0
- eryn/state.py +775 -0
- eryn/tests/__init__.py +0 -0
- eryn/tests/test_eryn.py +1246 -0
- eryn/utils/__init__.py +10 -0
- eryn/utils/periodic.py +134 -0
- eryn/utils/stopping.py +164 -0
- eryn/utils/transform.py +226 -0
- eryn/utils/updates.py +69 -0
- eryn/utils/utility.py +329 -0
- eryn-1.2.0.dist-info/METADATA +167 -0
- eryn-1.2.0.dist-info/RECORD +39 -0
- eryn-1.2.0.dist-info/WHEEL +4 -0
eryn/moves/tempering.py
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from ..state import State
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
|
|
7
|
+
__all__ = ["TemperatureControl"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def make_ladder(ndim, ntemps=None, Tmax=None):
|
|
11
|
+
"""
|
|
12
|
+
Returns a ladder of :math:`\\beta \\equiv 1/T` under a geometric spacing that is determined by the
|
|
13
|
+
arguments ``ntemps`` and ``Tmax``. The temperature selection algorithm works as follows:
|
|
14
|
+
Ideally, ``Tmax`` should be specified such that the tempered posterior looks like the prior at
|
|
15
|
+
this temperature. If using adaptive parallel tempering, per `arXiv:1501.05823
|
|
16
|
+
<http://arxiv.org/abs/1501.05823>`_, choosing ``Tmax = inf`` is a safe bet, so long as
|
|
17
|
+
``ntemps`` is also specified.
|
|
18
|
+
|
|
19
|
+
This function is originally from ``ptemcee`` `github.com/willvousden/ptemcee <https://github.com/willvousden/ptemcee>`_.
|
|
20
|
+
|
|
21
|
+
Temperatures are chosen according to the following algorithm:
|
|
22
|
+
* If neither ``ntemps`` nor ``Tmax`` is specified, raise an exception (insufficient
|
|
23
|
+
information).
|
|
24
|
+
* If ``ntemps`` is specified but not ``Tmax``, return a ladder spaced so that a Gaussian
|
|
25
|
+
posterior would have a 25% temperature swap acceptance ratio.
|
|
26
|
+
* If ``Tmax`` is specified but not ``ntemps``:
|
|
27
|
+
* If ``Tmax = inf``, raise an exception (insufficient information).
|
|
28
|
+
* Else, space chains geometrically as above (for 25% acceptance) until ``Tmax`` is reached.
|
|
29
|
+
* If ``Tmax`` and ``ntemps`` are specified:
|
|
30
|
+
* If ``Tmax = inf``, place one chain at ``inf`` and ``ntemps-1`` in a 25% geometric spacing.
|
|
31
|
+
* Else, use the unique geometric spacing defined by ``ntemps`` and ``Tmax``.`
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
ndim (int): The number of dimensions in the parameter space.
|
|
35
|
+
ntemps (int, optional): If set, the number of temperatures to generate.
|
|
36
|
+
Tmax (float, optional): If set, the maximum temperature for the ladder.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
np.ndarray[ntemps]: Output inverse temperature (beta) array.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ValueError: Improper inputs.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# make sure all inputs are okay
|
|
47
|
+
if type(ndim) != int or ndim < 1:
|
|
48
|
+
raise ValueError("Invalid number of dimensions specified.")
|
|
49
|
+
if ntemps is None and Tmax is None:
|
|
50
|
+
raise ValueError("Must specify one of ``ntemps`` and ``Tmax``.")
|
|
51
|
+
if Tmax is not None and Tmax <= 1:
|
|
52
|
+
raise ValueError("``Tmax`` must be greater than 1.")
|
|
53
|
+
if ntemps is not None and (type(ntemps) != int or ntemps < 1):
|
|
54
|
+
raise ValueError("Invalid number of temperatures specified.")
|
|
55
|
+
|
|
56
|
+
# step size in temperature based on ndim
|
|
57
|
+
tstep = np.array(
|
|
58
|
+
[
|
|
59
|
+
25.2741,
|
|
60
|
+
7.0,
|
|
61
|
+
4.47502,
|
|
62
|
+
3.5236,
|
|
63
|
+
3.0232,
|
|
64
|
+
2.71225,
|
|
65
|
+
2.49879,
|
|
66
|
+
2.34226,
|
|
67
|
+
2.22198,
|
|
68
|
+
2.12628,
|
|
69
|
+
2.04807,
|
|
70
|
+
1.98276,
|
|
71
|
+
1.92728,
|
|
72
|
+
1.87946,
|
|
73
|
+
1.83774,
|
|
74
|
+
1.80096,
|
|
75
|
+
1.76826,
|
|
76
|
+
1.73895,
|
|
77
|
+
1.7125,
|
|
78
|
+
1.68849,
|
|
79
|
+
1.66657,
|
|
80
|
+
1.64647,
|
|
81
|
+
1.62795,
|
|
82
|
+
1.61083,
|
|
83
|
+
1.59494,
|
|
84
|
+
1.58014,
|
|
85
|
+
1.56632,
|
|
86
|
+
1.55338,
|
|
87
|
+
1.54123,
|
|
88
|
+
1.5298,
|
|
89
|
+
1.51901,
|
|
90
|
+
1.50881,
|
|
91
|
+
1.49916,
|
|
92
|
+
1.49,
|
|
93
|
+
1.4813,
|
|
94
|
+
1.47302,
|
|
95
|
+
1.46512,
|
|
96
|
+
1.45759,
|
|
97
|
+
1.45039,
|
|
98
|
+
1.4435,
|
|
99
|
+
1.4369,
|
|
100
|
+
1.43056,
|
|
101
|
+
1.42448,
|
|
102
|
+
1.41864,
|
|
103
|
+
1.41302,
|
|
104
|
+
1.40761,
|
|
105
|
+
1.40239,
|
|
106
|
+
1.39736,
|
|
107
|
+
1.3925,
|
|
108
|
+
1.38781,
|
|
109
|
+
1.38327,
|
|
110
|
+
1.37888,
|
|
111
|
+
1.37463,
|
|
112
|
+
1.37051,
|
|
113
|
+
1.36652,
|
|
114
|
+
1.36265,
|
|
115
|
+
1.35889,
|
|
116
|
+
1.35524,
|
|
117
|
+
1.3517,
|
|
118
|
+
1.34825,
|
|
119
|
+
1.3449,
|
|
120
|
+
1.34164,
|
|
121
|
+
1.33847,
|
|
122
|
+
1.33538,
|
|
123
|
+
1.33236,
|
|
124
|
+
1.32943,
|
|
125
|
+
1.32656,
|
|
126
|
+
1.32377,
|
|
127
|
+
1.32104,
|
|
128
|
+
1.31838,
|
|
129
|
+
1.31578,
|
|
130
|
+
1.31325,
|
|
131
|
+
1.31076,
|
|
132
|
+
1.30834,
|
|
133
|
+
1.30596,
|
|
134
|
+
1.30364,
|
|
135
|
+
1.30137,
|
|
136
|
+
1.29915,
|
|
137
|
+
1.29697,
|
|
138
|
+
1.29484,
|
|
139
|
+
1.29275,
|
|
140
|
+
1.29071,
|
|
141
|
+
1.2887,
|
|
142
|
+
1.28673,
|
|
143
|
+
1.2848,
|
|
144
|
+
1.28291,
|
|
145
|
+
1.28106,
|
|
146
|
+
1.27923,
|
|
147
|
+
1.27745,
|
|
148
|
+
1.27569,
|
|
149
|
+
1.27397,
|
|
150
|
+
1.27227,
|
|
151
|
+
1.27061,
|
|
152
|
+
1.26898,
|
|
153
|
+
1.26737,
|
|
154
|
+
1.26579,
|
|
155
|
+
1.26424,
|
|
156
|
+
1.26271,
|
|
157
|
+
1.26121,
|
|
158
|
+
1.25973,
|
|
159
|
+
]
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if ndim > tstep.shape[0]:
|
|
163
|
+
# An approximation to the temperature step at large
|
|
164
|
+
# dimension
|
|
165
|
+
tstep = 1.0 + 2.0 * np.sqrt(np.log(4.0)) / np.sqrt(ndim)
|
|
166
|
+
else:
|
|
167
|
+
# get correct step for dimension
|
|
168
|
+
tstep = tstep[ndim - 1]
|
|
169
|
+
|
|
170
|
+
# wheter to add the infinite temperature to the end
|
|
171
|
+
appendInf = False
|
|
172
|
+
if Tmax == np.inf:
|
|
173
|
+
appendInf = True
|
|
174
|
+
Tmax = None
|
|
175
|
+
# non-infinite temperatures will now have 1 less
|
|
176
|
+
ntemps = ntemps - 1
|
|
177
|
+
|
|
178
|
+
if ntemps is not None:
|
|
179
|
+
if Tmax is None:
|
|
180
|
+
# Determine Tmax from ntemps.
|
|
181
|
+
Tmax = tstep ** (ntemps - 1)
|
|
182
|
+
else:
|
|
183
|
+
if Tmax is None:
|
|
184
|
+
raise ValueError(
|
|
185
|
+
"Must specify at least one of ``ntemps" " and " "finite ``Tmax``."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Determine ntemps from Tmax.
|
|
189
|
+
ntemps = int(np.log(Tmax) / np.log(tstep) + 2)
|
|
190
|
+
|
|
191
|
+
betas = np.logspace(0, -np.log10(Tmax), ntemps)
|
|
192
|
+
if appendInf:
|
|
193
|
+
# Use a geometric spacing, but replace the top-most temperature with
|
|
194
|
+
# infinity.
|
|
195
|
+
betas = np.concatenate((betas, [0]))
|
|
196
|
+
|
|
197
|
+
return betas
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class TemperatureControl(object):
|
|
201
|
+
"""Controls the temperature ladder and operations in the sampler.
|
|
202
|
+
|
|
203
|
+
All of the tempering features within Eryn are controlled from this class.
|
|
204
|
+
This includes the evaluation of the tempered posterior, swapping between temperatures, and
|
|
205
|
+
the adaptation of the temperatures over time. The adaptive tempering model can be
|
|
206
|
+
found in the Eryn paper as well as the paper for `ptemcee`, which acted
|
|
207
|
+
as a basis for the code below.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
effective_ndim (int): Effective dimension used to determine temperatures if betas not given.
|
|
211
|
+
nwalkers (int): Number of walkers in the sampler. Must maintain proper order of branches.
|
|
212
|
+
ntemps (int, optional): Number of temperatures. If this is provided rather than ``betas``,
|
|
213
|
+
:func:`make_ladder` will be used to generate the temperature ladder. (default: 1)
|
|
214
|
+
betas (np.ndarray[ntemps], optional): If provided, will use as the array of inverse temperatures.
|
|
215
|
+
(default: ``None``).
|
|
216
|
+
Tmax (float, optional): If provided and ``betas`` is not provided, this will be included with
|
|
217
|
+
``ntemps`` when determing the temperature ladder with :func:`make_ladder`.
|
|
218
|
+
See that functions docs for more information. (default: ``None``)
|
|
219
|
+
adaptive (bool, optional): If ``True``, adapt the temperature ladder during sampling.
|
|
220
|
+
(default: ``True``).
|
|
221
|
+
adaptation_lag (int, optional): lag parameter from
|
|
222
|
+
`arXiv:1501.05823 <http://arxiv.org/abs/1501.05823>`_. ``adaptation_lag`` must be
|
|
223
|
+
much greater than ``adapation_time``. (default: 10000)
|
|
224
|
+
adaptation_time (int, optional): initial amplitude of adjustments from
|
|
225
|
+
`arXiv:1501.05823 <http://arxiv.org/abs/1501.05823>`_. ``adaptation_lag`` must be
|
|
226
|
+
much greater than ``adapation_time``. (default: 100)
|
|
227
|
+
stop_adaptation (int, optional): If ``stop_adaptation > 0``, the adapating will stop after
|
|
228
|
+
``stop_adaption`` steps. The number of steps is counted as the number times adaptation
|
|
229
|
+
has happened which is generally once per sampler iteration. For example,
|
|
230
|
+
if you only want to adapt temperatures during burn-in, you set ``stop_adaption = burn``.
|
|
231
|
+
This can become complicated when using the repeating proposal options, so the
|
|
232
|
+
user must be careful and verify constant temperatures in the backend.
|
|
233
|
+
(default: -1)
|
|
234
|
+
permute (bool, optional): If ``True``, permute the walkers in each temperature during
|
|
235
|
+
swaps. (default: ``True``)
|
|
236
|
+
skip_swap_supp_names (list, optional): List of strings that indicate supplemental keys that are not to be swapped.
|
|
237
|
+
(default: ``[]``)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
def __init__(
|
|
243
|
+
self,
|
|
244
|
+
effective_ndim,
|
|
245
|
+
nwalkers,
|
|
246
|
+
ntemps=1,
|
|
247
|
+
betas=None,
|
|
248
|
+
Tmax=None,
|
|
249
|
+
adaptive=True,
|
|
250
|
+
adaptation_lag=10000,
|
|
251
|
+
adaptation_time=100,
|
|
252
|
+
stop_adaptation=-1,
|
|
253
|
+
permute=True,
|
|
254
|
+
skip_swap_supp_names=[],
|
|
255
|
+
):
|
|
256
|
+
|
|
257
|
+
if betas is None:
|
|
258
|
+
if ntemps == 1:
|
|
259
|
+
betas = np.array([1.0])
|
|
260
|
+
else:
|
|
261
|
+
# A compromise for building a temperature ladder for the case of rj.
|
|
262
|
+
# We start by assuming that the dimensionality will be defined by the number of
|
|
263
|
+
# components. We take that maximum divided by two, and multiply it with the higher
|
|
264
|
+
# dimensional component.
|
|
265
|
+
betas = make_ladder(effective_ndim, ntemps=ntemps, Tmax=Tmax)
|
|
266
|
+
|
|
267
|
+
# store information
|
|
268
|
+
self.nwalkers = nwalkers
|
|
269
|
+
self.betas = betas
|
|
270
|
+
self.ntemps = ntemps = len(betas)
|
|
271
|
+
self.permute = permute
|
|
272
|
+
self.skip_swap_supp_names = skip_swap_supp_names
|
|
273
|
+
|
|
274
|
+
# number of times adapted
|
|
275
|
+
self.time = 0
|
|
276
|
+
|
|
277
|
+
# store adapting inf
|
|
278
|
+
self.adaptive = adaptive
|
|
279
|
+
self.adaptation_time, self.adaptation_lag = adaptation_time, adaptation_lag
|
|
280
|
+
self.stop_adaptation = stop_adaptation
|
|
281
|
+
|
|
282
|
+
self.swaps_proposed = np.full(self.ntemps - 1, self.nwalkers)
|
|
283
|
+
|
|
284
|
+
def compute_log_posterior_tempered(self, logl, logp, betas=None):
|
|
285
|
+
"""Compute the log of the tempered posterior
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
logl (np.ndarray): Log of the Likelihood. Can be 1D or 2D array. If 2D,
|
|
289
|
+
must have shape ``(ntemps, nwalkers)``. If 1D, ``betas`` must be provided
|
|
290
|
+
with the same shape.
|
|
291
|
+
logp (np.ndarray): Log of the Prior. Can be 1D or 2D array. If 2D,
|
|
292
|
+
must have shape ``(ntemps, nwalkers)``. If 1D, ``betas`` must be provided
|
|
293
|
+
with the same shape.
|
|
294
|
+
betas (np.ndarray[ntemps]): If provided, inverse temperatures as 1D array.
|
|
295
|
+
If not provided, it will use ``self.betas``. (default: ``None``)
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
np.ndarray: Log of the temperated posterior.
|
|
299
|
+
|
|
300
|
+
Raises:
|
|
301
|
+
AssertionError: Inputs are incorrectly shaped.
|
|
302
|
+
|
|
303
|
+
"""
|
|
304
|
+
assert logl.shape == logp.shape
|
|
305
|
+
tempered_logl = self.tempered_likelihood(logl, betas=betas)
|
|
306
|
+
return tempered_logl + logp
|
|
307
|
+
|
|
308
|
+
def tempered_likelihood(self, logl, betas=None):
|
|
309
|
+
"""Compute the log of the tempered Likelihood
|
|
310
|
+
|
|
311
|
+
From `ptemcee`: "This is usually a mundane multiplication, except for the special case where
|
|
312
|
+
beta == 0 *and* we're outside the likelihood support.
|
|
313
|
+
Here, we find a singularity that demands more careful attention; we allow the
|
|
314
|
+
likelihood to dominate the temperature, since wandering outside the
|
|
315
|
+
likelihood support causes a discontinuity."
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
logl (np.ndarray): Log of the Likelihood. Can be 1D or 2D array. If 2D,
|
|
319
|
+
must have shape ``(ntemps, nwalkers)``. If 1D, ``betas`` must be provided
|
|
320
|
+
with the same shape.
|
|
321
|
+
betas (np.ndarray[ntemps]): If provided, inverse temperatures as 1D array.
|
|
322
|
+
If not provided, it will use ``self.betas``. (default: ``None``)
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
np.ndarray: Log of the temperated Likelihood.
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
ValueError: betas not provided if needed.
|
|
329
|
+
|
|
330
|
+
"""
|
|
331
|
+
# perform calculation on 1D likelihoods.
|
|
332
|
+
if logl.ndim == 1:
|
|
333
|
+
if betas is None:
|
|
334
|
+
raise ValueError(
|
|
335
|
+
"If inputing a 1D logl array, need to provide 1D betas array of the same length."
|
|
336
|
+
)
|
|
337
|
+
loglT = logl * betas
|
|
338
|
+
|
|
339
|
+
else:
|
|
340
|
+
if betas is None:
|
|
341
|
+
betas = self.betas
|
|
342
|
+
|
|
343
|
+
with np.errstate(invalid="ignore"):
|
|
344
|
+
loglT = logl * betas[:, None]
|
|
345
|
+
|
|
346
|
+
# anywhere the likelihood is nan, turn into -infinity
|
|
347
|
+
loglT[np.isnan(loglT)] = -np.inf
|
|
348
|
+
|
|
349
|
+
return loglT
|
|
350
|
+
|
|
351
|
+
def do_swaps_indexing(
|
|
352
|
+
self,
|
|
353
|
+
i,
|
|
354
|
+
iperm_sel,
|
|
355
|
+
i1perm_sel,
|
|
356
|
+
dbeta,
|
|
357
|
+
x,
|
|
358
|
+
logP,
|
|
359
|
+
logl,
|
|
360
|
+
logp,
|
|
361
|
+
inds=None,
|
|
362
|
+
blobs=None,
|
|
363
|
+
supps=None,
|
|
364
|
+
branch_supps=None,
|
|
365
|
+
):
|
|
366
|
+
|
|
367
|
+
# for x and inds, just do full copy
|
|
368
|
+
x_temp = {name: np.copy(x[name]) for name in x}
|
|
369
|
+
if inds is not None:
|
|
370
|
+
inds_temp = {name: np.copy(inds[name]) for name in inds}
|
|
371
|
+
if branch_supps is not None:
|
|
372
|
+
branch_supps_temp = {
|
|
373
|
+
name: deepcopy(branch_supps[name]) for name in branch_supps
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
logl_temp = np.copy(logl[i, iperm_sel])
|
|
377
|
+
logp_temp = np.copy(logp[i, iperm_sel])
|
|
378
|
+
logP_temp = np.copy(logP[i, iperm_sel])
|
|
379
|
+
if blobs is not None:
|
|
380
|
+
blobs_temp = np.copy(blobs[i, iperm_sel])
|
|
381
|
+
if supps is not None:
|
|
382
|
+
supps_temp = deepcopy(supps[i, iperm_sel])
|
|
383
|
+
|
|
384
|
+
# swap from i1 to i
|
|
385
|
+
for name in x:
|
|
386
|
+
# coords first
|
|
387
|
+
x[name][i, iperm_sel, :, :] = x[name][i - 1, i1perm_sel, :, :]
|
|
388
|
+
|
|
389
|
+
# then inds
|
|
390
|
+
if inds is not None:
|
|
391
|
+
inds[name][i, iperm_sel, :] = inds[name][i - 1, i1perm_sel, :]
|
|
392
|
+
|
|
393
|
+
# do something special for branch_supps in case in contains a large amount of data
|
|
394
|
+
# that is heavy to copy
|
|
395
|
+
if branch_supps[name] is not None:
|
|
396
|
+
tmp = branch_supps[name][i - 1, i1perm_sel, :]
|
|
397
|
+
|
|
398
|
+
for key in self.skip_swap_supp_names:
|
|
399
|
+
tmp.pop(key)
|
|
400
|
+
|
|
401
|
+
branch_supps[name][i, iperm_sel, :] = tmp
|
|
402
|
+
"""# where the inds are alive in the current permutation
|
|
403
|
+
# need inds_temp because that is the original
|
|
404
|
+
inds_i = np.where(inds_temp[name][i][iperm_sel])
|
|
405
|
+
|
|
406
|
+
# gives the associated walker for each spot in the permuted array
|
|
407
|
+
walker_inds_i = iperm_sel[inds_i[0]]
|
|
408
|
+
|
|
409
|
+
# represents which permuted leaves are alive
|
|
410
|
+
leaf_inds_i = inds_i[1]
|
|
411
|
+
|
|
412
|
+
# all of these are at the same temperature
|
|
413
|
+
temp_inds_i = np.full_like(leaf_inds_i, i)
|
|
414
|
+
|
|
415
|
+
# repeat all for the i1 permutated temperature
|
|
416
|
+
# need inds_temp because that is the original
|
|
417
|
+
inds_i1 = np.where(inds_temp[name][i - 1][i1perm_sel])
|
|
418
|
+
walker_inds_i1 = i1perm_sel[inds_i1[0]]
|
|
419
|
+
leaf_inds_i1 = inds_i1[1]
|
|
420
|
+
temp_inds_i1 = np.full_like(leaf_inds_i1, i - 1)
|
|
421
|
+
|
|
422
|
+
# go through the values within each branch supplemental holder
|
|
423
|
+
# do direct movement of things that need to change
|
|
424
|
+
# rather than copying the whole thing
|
|
425
|
+
for name2 in branch_supps[name].holder:
|
|
426
|
+
# store temperarily
|
|
427
|
+
bring_back_branch_supps = (
|
|
428
|
+
branch_supps[name]
|
|
429
|
+
.holder[name2][(temp_inds_i, walker_inds_i, leaf_inds_i)]
|
|
430
|
+
.copy()
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# make switch from i1 to i
|
|
434
|
+
branch_supps[name].holder[name2][
|
|
435
|
+
(temp_inds_i, walker_inds_i, leaf_inds_i)
|
|
436
|
+
] = branch_supps[name].holder[name2][
|
|
437
|
+
(temp_inds_i1, walker_inds_i1, leaf_inds_i1)
|
|
438
|
+
]
|
|
439
|
+
|
|
440
|
+
# make switch from i to i1
|
|
441
|
+
branch_supps[name].holder[name2][
|
|
442
|
+
(temp_inds_i1, walker_inds_i1, leaf_inds_i1)
|
|
443
|
+
] = bring_back_branch_supps"""
|
|
444
|
+
|
|
445
|
+
# switch everythin else from i1 to i
|
|
446
|
+
logl[i, iperm_sel] = logl[i - 1, i1perm_sel]
|
|
447
|
+
logp[i, iperm_sel] = logp[i - 1, i1perm_sel]
|
|
448
|
+
logP[i, iperm_sel] = logP[i - 1, i1perm_sel] - dbeta * logl[i - 1, i1perm_sel]
|
|
449
|
+
if blobs is not None:
|
|
450
|
+
blobs[i, iperm_sel] = blobs[i - 1, i1perm_sel]
|
|
451
|
+
if supps is not None:
|
|
452
|
+
tmp_supps = supps[i - 1, i1perm_sel]
|
|
453
|
+
for key in self.skip_swap_supp_names:
|
|
454
|
+
tmp_supps.pop(key)
|
|
455
|
+
supps[i, iperm_sel] = tmp_supps
|
|
456
|
+
|
|
457
|
+
# switch x from i to i1
|
|
458
|
+
for name in x:
|
|
459
|
+
x[name][i - 1, i1perm_sel, :, :] = x_temp[name][i, iperm_sel, :, :]
|
|
460
|
+
if inds is not None:
|
|
461
|
+
inds[name][i - 1, i1perm_sel, :] = inds_temp[name][i, iperm_sel, :]
|
|
462
|
+
if branch_supps[name] is not None:
|
|
463
|
+
tmp = branch_supps_temp[name][i, iperm_sel, :]
|
|
464
|
+
|
|
465
|
+
for key in self.skip_swap_supp_names:
|
|
466
|
+
tmp.pop(key)
|
|
467
|
+
branch_supps[name][i - 1, i1perm_sel, :] = tmp
|
|
468
|
+
|
|
469
|
+
# switch the rest from i to i1
|
|
470
|
+
logl[i - 1, i1perm_sel] = logl_temp
|
|
471
|
+
logp[i - 1, i1perm_sel] = logp_temp
|
|
472
|
+
logP[i - 1, i1perm_sel] = logP_temp + dbeta * logl_temp
|
|
473
|
+
|
|
474
|
+
if blobs is not None:
|
|
475
|
+
blobs[i - 1, i1perm_sel] = blobs_temp
|
|
476
|
+
if supps is not None:
|
|
477
|
+
tmp_supps = supps_temp
|
|
478
|
+
for key in self.skip_swap_supp_names:
|
|
479
|
+
tmp_supps.pop(key)
|
|
480
|
+
supps[i - 1, i1perm_sel] = tmp_supps
|
|
481
|
+
|
|
482
|
+
return (x, logP, logl, logp, inds, blobs, supps, branch_supps)
|
|
483
|
+
|
|
484
|
+
def temperature_swaps(
|
|
485
|
+
self, x, logP, logl, logp, inds=None, blobs=None, supps=None, branch_supps=None
|
|
486
|
+
):
|
|
487
|
+
"""Perform parallel-tempering temperature swaps
|
|
488
|
+
|
|
489
|
+
This function performs the swapping between neighboring temperatures. It cascades from
|
|
490
|
+
high temperature down to low temperature.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
x (dict): Dictionary with keys as branch names and values as coordinate arrays.
|
|
494
|
+
logP (np.ndarray[ntemps, nwalkers]): Log of the posterior probability.
|
|
495
|
+
logl (np.ndarray[ntemps, nwalkers]): Log of the Likelihood.
|
|
496
|
+
logp (np.ndarray[ntemps, nwalkers]): Log of the prior probability.
|
|
497
|
+
inds (dict, optional): Dictionary with keys as branch names and values as the index arrays
|
|
498
|
+
indicating which leaves are used. (default: ``None``)
|
|
499
|
+
blobs (object, optional): Blobs associated with each walker. (default: ``None``)
|
|
500
|
+
supps (object, optional): :class:`eryn.state.BranchSupplemental` object. (default: ``None``)
|
|
501
|
+
branch_supps (dict, optional): Dictionary with keys as branch names and values as
|
|
502
|
+
:class:`eryn.state.BranchSupplemental` objects for each branch (can be ``None`` for some branches). (default: ``None``)
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
tuple: All of the information that was input now swapped (output in the same order as input).
|
|
506
|
+
|
|
507
|
+
"""
|
|
508
|
+
|
|
509
|
+
ntemps, nwalkers = self.ntemps, self.nwalkers
|
|
510
|
+
|
|
511
|
+
# prepare information on how many swaps are accepted this time
|
|
512
|
+
self.swaps_accepted = np.empty(ntemps - 1)
|
|
513
|
+
|
|
514
|
+
# iterate from highest to lowest temperatures
|
|
515
|
+
for i in range(ntemps - 1, 0, -1):
|
|
516
|
+
|
|
517
|
+
# get both temperature rungs
|
|
518
|
+
bi = self.betas[i]
|
|
519
|
+
bi1 = self.betas[i - 1]
|
|
520
|
+
|
|
521
|
+
# difference in inverse temps
|
|
522
|
+
dbeta = bi1 - bi
|
|
523
|
+
|
|
524
|
+
# permute the indices for the walkers in each temperature to randomize swap positions
|
|
525
|
+
if self.permute:
|
|
526
|
+
iperm = np.random.permutation(nwalkers)
|
|
527
|
+
i1perm = np.random.permutation(nwalkers)
|
|
528
|
+
|
|
529
|
+
# do not permute if desired
|
|
530
|
+
else:
|
|
531
|
+
iperm = np.arange(nwalkers)
|
|
532
|
+
i1perm = np.arange(nwalkers)
|
|
533
|
+
|
|
534
|
+
# random draw that produces log of the acceptance fraction
|
|
535
|
+
raccept = np.log(np.random.uniform(size=nwalkers))
|
|
536
|
+
|
|
537
|
+
# log of the detailed balance fraction
|
|
538
|
+
paccept = dbeta * (logl[i, iperm] - logl[i - 1, i1perm])
|
|
539
|
+
|
|
540
|
+
# How many swaps were accepted
|
|
541
|
+
sel = paccept > raccept
|
|
542
|
+
self.swaps_accepted[i - 1] = np.sum(sel)
|
|
543
|
+
|
|
544
|
+
(x, logP, logl, logp, inds, blobs, supps, branch_supps) = (
|
|
545
|
+
self.do_swaps_indexing(
|
|
546
|
+
i,
|
|
547
|
+
iperm[sel],
|
|
548
|
+
i1perm[sel],
|
|
549
|
+
dbeta,
|
|
550
|
+
x,
|
|
551
|
+
logP,
|
|
552
|
+
logl,
|
|
553
|
+
logp,
|
|
554
|
+
inds=inds,
|
|
555
|
+
blobs=blobs,
|
|
556
|
+
supps=supps,
|
|
557
|
+
branch_supps=branch_supps,
|
|
558
|
+
)
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
return (x, logP, logl, logp, inds, blobs, supps, branch_supps)
|
|
562
|
+
|
|
563
|
+
def _get_ladder_adjustment(self, time, betas0, ratios):
|
|
564
|
+
"""
|
|
565
|
+
Execute temperature adjustment according to dynamics outlined in
|
|
566
|
+
`arXiv:1501.05823 <http://arxiv.org/abs/1501.05823>`_.
|
|
567
|
+
"""
|
|
568
|
+
betas = betas0.copy()
|
|
569
|
+
|
|
570
|
+
# Modulate temperature adjustments with a hyperbolic decay.
|
|
571
|
+
decay = self.adaptation_lag / (time + self.adaptation_lag)
|
|
572
|
+
kappa = decay / self.adaptation_time
|
|
573
|
+
|
|
574
|
+
# Construct temperature adjustments.
|
|
575
|
+
dSs = kappa * (ratios[:-1] - ratios[1:])
|
|
576
|
+
|
|
577
|
+
# Compute new ladder (hottest and coldest chains don't move).
|
|
578
|
+
deltaTs = np.diff(1 / betas[:-1])
|
|
579
|
+
deltaTs *= np.exp(dSs)
|
|
580
|
+
betas[1:-1] = 1 / (np.cumsum(deltaTs) + 1 / betas[0])
|
|
581
|
+
|
|
582
|
+
# Don't mutate the ladder here; let the client code do that.
|
|
583
|
+
return betas - betas0
|
|
584
|
+
|
|
585
|
+
def adapt_temps(self):
|
|
586
|
+
# determine ratios of swaps accepted to swaps proposed (the ladder is fixed)
|
|
587
|
+
ratios = self.swaps_accepted / self.swaps_proposed
|
|
588
|
+
|
|
589
|
+
# adapt if desired
|
|
590
|
+
if self.adaptive and self.ntemps > 1:
|
|
591
|
+
if self.stop_adaptation < 0 or self.time < self.stop_adaptation:
|
|
592
|
+
dbetas = self._get_ladder_adjustment(self.time, self.betas, ratios)
|
|
593
|
+
self.betas += dbetas
|
|
594
|
+
|
|
595
|
+
# only increase time if it is adaptive.
|
|
596
|
+
self.time += 1
|
|
597
|
+
|
|
598
|
+
def temper_comps(self, state, adapt=True):
|
|
599
|
+
"""Perfrom temperature-related operations on a state.
|
|
600
|
+
|
|
601
|
+
This includes making swaps and then adapting the temperatures for the next round.
|
|
602
|
+
|
|
603
|
+
Args:
|
|
604
|
+
state (object): Filled ``State`` object.
|
|
605
|
+
adapt (bool, optional): If True, swaps are to be performed, but no
|
|
606
|
+
adaptation is made. In this case, ``self.time`` does not increase by 1.
|
|
607
|
+
(default: ``True``)
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
:class:`eryn.state.State`: State object after swaps.
|
|
611
|
+
|
|
612
|
+
"""
|
|
613
|
+
# get initial values
|
|
614
|
+
logl = state.log_like
|
|
615
|
+
logp = state.log_prior
|
|
616
|
+
|
|
617
|
+
# do posterior just for the hell of it
|
|
618
|
+
logP = self.compute_log_posterior_tempered(logl, logp)
|
|
619
|
+
|
|
620
|
+
# make swaps
|
|
621
|
+
x, logP, logl, logp, inds, blobs, supps, branch_supps = self.temperature_swaps(
|
|
622
|
+
state.branches_coords,
|
|
623
|
+
logP.copy(),
|
|
624
|
+
logl.copy(),
|
|
625
|
+
logp.copy(),
|
|
626
|
+
inds=state.branches_inds,
|
|
627
|
+
blobs=state.blobs,
|
|
628
|
+
supps=state.supplemental,
|
|
629
|
+
branch_supps=state.branches_supplemental,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
if adapt and self.adaptive and self.ntemps > 1:
|
|
633
|
+
self.adapt_temps()
|
|
634
|
+
|
|
635
|
+
# create a new state out of the swapped information
|
|
636
|
+
# TODO: make this more memory efficient?
|
|
637
|
+
new_state = State(
|
|
638
|
+
x,
|
|
639
|
+
log_like=logl,
|
|
640
|
+
log_prior=logp,
|
|
641
|
+
blobs=blobs,
|
|
642
|
+
inds=inds,
|
|
643
|
+
betas=self.betas,
|
|
644
|
+
supplemental=supps,
|
|
645
|
+
branch_supplemental=branch_supps,
|
|
646
|
+
random_state=state.random_state,
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
return new_state
|
eryn/pbar.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
__all__ = ["get_progress_bar"]
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import tqdm
|
|
11
|
+
except ImportError:
|
|
12
|
+
tqdm = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _NoOpPBar(object):
|
|
16
|
+
"""This class implements the progress bar interface but does nothing"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def __enter__(self, *args, **kwargs):
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def __exit__(self, *args, **kwargs):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def update(self, count):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_progress_bar(display, total):
|
|
32
|
+
"""Get a progress bar interface with given properties
|
|
33
|
+
|
|
34
|
+
If the tqdm library is not installed, this will always return a "progress
|
|
35
|
+
bar" that does nothing.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
display (bool or str): Should the bar actually show the progress? Or a
|
|
39
|
+
string to indicate which tqdm bar to use.
|
|
40
|
+
total (int): The total size of the progress bar.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
if display:
|
|
44
|
+
if tqdm is None:
|
|
45
|
+
logger.warning(
|
|
46
|
+
"You must install the tqdm library to use progress "
|
|
47
|
+
"indicators with emcee"
|
|
48
|
+
)
|
|
49
|
+
return _NoOpPBar()
|
|
50
|
+
else:
|
|
51
|
+
if display is True:
|
|
52
|
+
return tqdm.tqdm(total=total)
|
|
53
|
+
else:
|
|
54
|
+
return getattr(tqdm, "tqdm_" + display)(total=total)
|
|
55
|
+
|
|
56
|
+
return _NoOpPBar()
|