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.
@@ -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()