Skip to content

Commit b8763da

Browse files
committed
update bounding to make interface more generic
1 parent a6dc087 commit b8763da

2 files changed

Lines changed: 44 additions & 55 deletions

File tree

py/dynesty/bounding.py

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"""
2323

2424
import warnings
25+
from collections.abc import Iterable
2526
import numpy as np
2627
from numpy import linalg
2728
from numpy import cov as mle_cov
@@ -43,15 +44,12 @@
4344

4445
class Bound:
4546
"""
46-
Parameters
47-
----------
48-
ndim : int
49-
The number of dimensions of the unit cube.
50-
47+
This is is master class for the all the bounds listing
48+
the key methods required from the bound
5149
"""
5250

5351
def __init__(self):
54-
pass
52+
self.logvol = 0
5553

5654
def contains(self, x):
5755
"""Checks if unit cube contains the point `x`."""
@@ -84,8 +82,14 @@ def samples(self, nsamples, rstate=None):
8482
def get_random_axes(self, rstate):
8583
pass
8684

85+
def scale_to_logvol(self, rstate):
86+
pass
87+
88+
def update(self, points, rstate=None, bootstrap=0, pool=None):
89+
pass
8790

88-
class UnitCube:
91+
92+
class UnitCube(Bound):
8993
"""
9094
An N-dimensional unit cube.
9195
@@ -139,8 +143,11 @@ def update(self, points, rstate=None, bootstrap=0, pool=None):
139143
def get_random_axes(self, rstate):
140144
return np.eye(self.n)
141145

146+
def scale_to_logvol(self, logvol):
147+
pass
142148

143-
class Ellipsoid:
149+
150+
class Ellipsoid(Bound):
144151
"""
145152
An N-dimensional ellipsoid defined by::
146153
@@ -195,7 +202,6 @@ def __init__(self, ctr, cov, am=None, axes=None):
195202

196203
# Amount by which volume was increased after initialization (i.e.
197204
# cumulative factor from `scale_to_vol`).
198-
self.expand = 1.
199205
self.funit = 1
200206

201207
def scale_to_logvol(self, logvol):
@@ -342,7 +348,6 @@ def update(self,
342348
self.logvol = ell.logvol
343349
self.axlens = ell.axlens
344350
self.axes = ell.axes
345-
self.expand = ell.expand
346351

347352
# Use bootstrapping to determine the volume expansion factor.
348353
if bootstrap > 0:
@@ -376,7 +381,7 @@ def get_random_axes(self, rstate):
376381
return self.axes
377382

378383

379-
class MultiEllipsoid:
384+
class MultiEllipsoid(Bound):
380385
"""
381386
A collection of M N-dimensional ellipsoids.
382387
@@ -418,9 +423,7 @@ def __init__(self, ells=None, ctrs=None, covs=None):
418423
self.__update_arrays()
419424

420425
# Compute quantities.
421-
self.expands = np.ones(self.nells)
422-
self.logvol_tot = logsumexp(self.logvols)
423-
self.expand_tot = 1.
426+
self.logvol = logsumexp(self.logvol_ells) # ignores overlap
424427
self.funit = 1
425428

426429
def __update_arrays(self):
@@ -430,23 +433,26 @@ def __update_arrays(self):
430433
self.ctrs = np.array([ell.ctr for ell in self.ells])
431434
self.covs = np.array([ell.cov for ell in self.ells])
432435
self.ams = np.array([ell.am for ell in self.ells])
433-
self.logvols = np.array([ell.logvol for ell in self.ells])
436+
self.logvol_ells = np.array([ell.logvol for ell in self.ells])
434437

435-
def scale_to_logvol(self, logvols):
438+
def scale_to_logvol(self, logvol):
436439
"""Scale ellipoids to a corresponding set of
437-
target volumes.
440+
target volumes or to a new total volume
441+
If the argument is scalar this is assumed to
442+
be new logvol, if it's iterable it's assumed to be
443+
array of log volumes
438444
"""
445+
if isinstance(logvol, Iterable):
446+
logvol_ells_new = logvol
447+
else:
448+
scale = logvol - self.logvol
449+
logvol_ells_new = self.logvol_ells + scale
439450
for i in range(self.nells):
440-
self.ells[i].scale_to_logvol(logvols[i])
451+
self.ells[i].scale_to_logvol(logvol_ells_new[i])
441452

442453
# IMPORTANT We must also update arrays ams, covs
443454
self.__update_arrays()
444-
445-
self.expands = np.array(
446-
[self.ells[i].expand for i in range(self.nells)])
447-
logvol_tot = logsumexp(logvols)
448-
self.expand_tot *= np.exp(logvol_tot - self.logvol_tot)
449-
self.logvol_tot = logvol_tot
455+
self.logvol = logsumexp(self.logvol_ells)
450456

451457
def major_axis_endpoints(self):
452458
"""Return the endpoints of the major axis of each ellipsoid."""
@@ -503,7 +509,7 @@ def sample(self, rstate=None, return_q=False):
503509
else:
504510
return x, idx
505511

506-
probs = np.exp(self.logvols - self.logvol_tot)
512+
probs = np.exp(self.logvol_ells - self.logvol)
507513
while True:
508514
# Select an ellipsoid at random proportional to its volume.
509515
idx = rand_choice(probs, rstate)
@@ -572,7 +578,7 @@ def monte_carlo_logvol(self,
572578
self.sample(rstate=rstate, return_q=True) for i in range(ndraws)
573579
]
574580
qsum = sum((1. / q for (x, idx, q) in samples))
575-
logvol = np.log(qsum / ndraws) + self.logvol_tot
581+
logvol = np.log(qsum / ndraws) + self.logvol
576582

577583
if return_overlap:
578584
# Estimate the fractional amount of overlap with the
@@ -637,17 +643,10 @@ def update(self,
637643
if not all(self.contains(p) for p in points):
638644
# refuse to update
639645
raise RuntimeError('Rejecting invalid MultiEllipsoid region')
640-
self.logvol_tot = logsumexp(self.logvols)
641-
642-
# Compute expansion factor.
643-
expands = np.array([ell.expand for ell in self.ells])
644-
logvols_orig = self.logvols - np.log(expands)
645-
logvol_tot_orig = logsumexp(logvols_orig)
646-
self.expand_tot = np.exp(self.logvol_tot - logvol_tot_orig)
646+
self.logvol = logsumexp(self.logvol_ells)
647647

648648
# Use bootstrapping to determine the volume expansion factor.
649649
if bootstrap > 0:
650-
651650
# If provided, compute bootstraps in parallel using a pool.
652651
if pool is None:
653652
M = map
@@ -675,24 +674,24 @@ def update(self,
675674
' or alternatively disable bootstrap (bootstrap=0)')
676675
# If our ellipsoids are overly constrained, expand them.
677676
if expand > 1.:
678-
lvs = self.logvols + ndim * np.log(expand)
677+
lvs = self.logvol_ells + ndim * np.log(expand)
679678
self.scale_to_logvol(lvs)
680679

681680
# Estimate the volume and fractional overlap with the unit cube
682681
# using Monte Carlo integration.
683682
if mc_integrate:
684-
self.logvol_tot, self.funit = self.monte_carlo_logvol(
683+
self.logvol, self.funit = self.monte_carlo_logvol(
685684
rstate=rstate, return_overlap=True)
686685

687686
def get_random_axes(self, rstate):
688-
probs = np.exp(self.logvols - self.logvol_tot)
687+
probs = np.exp(self.logvol_ells - self.logvol)
689688
ell_idx = rand_choice(probs, rstate)
690689
# Choose axes.
691690
ax = self.ells[ell_idx].axes
692691
return ax
693692

694693

695-
class RadFriends:
694+
class RadFriends(Bound):
696695
"""
697696
A collection of N-balls of identical size centered on each live point.
698697
Importantly this class requires that the centers of the balls are set
@@ -721,7 +720,6 @@ def __init__(self, ndim, cov=None):
721720
detsign, detln = linalg.slogdet(self.am)
722721
assert detsign > 0
723722
self.logvol = logvol_prefactor(self.n) - 0.5 * detln
724-
self.expand = 1.
725723
self.funit = 1
726724

727725
def scale_to_logvol(self, logvol):
@@ -913,7 +911,6 @@ def update(self,
913911
detsign, detln = linalg.slogdet(self.am)
914912
assert detsign > 0
915913
self.logvol = (logvol_prefactor(self.n) - 0.5 * detln)
916-
self.expand = 1.
917914

918915
# Estimate the volume and fractional overlap with the unit cube
919916
# using Monte Carlo integration.
@@ -960,7 +957,7 @@ def get_random_axes(self, rstate):
960957
return self.axes
961958

962959

963-
class SupFriends:
960+
class SupFriends(Bound):
964961
"""
965962
A collection of N-cubes of identical size centered on each live point.
966963
Importantly this class requires that the centers of the cubes are set
@@ -989,7 +986,6 @@ def __init__(self, ndim, cov=None):
989986
detsign, detln = linalg.slogdet(self.am)
990987
assert detsign > 0
991988
self.logvol = self.n * np.log(2.) - 0.5 * detln
992-
self.expand = 1.
993989
self.funit = 1
994990

995991
def scale_to_logvol(self, logvol):
@@ -1182,7 +1178,6 @@ def update(self,
11821178
detsign, detln = linalg.slogdet(self.am)
11831179
assert detsign > 0
11841180
self.logvol = (self.n * np.log(2.) - 0.5 * detln)
1185-
self.expand = 1.
11861181

11871182
# Estimate the volume and fractional overlap with the unit cube
11881183
# using Monte Carlo integration.

py/dynesty/sampler.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -310,19 +310,13 @@ def update_bound(self, subset=slice(None)):
310310
else:
311311
pool = None
312312

313-
if self.bounding in ['single', 'multi', 'balls', 'cubes']:
314-
# Update the ellipsoid.
315-
self.bound.update(self.live_u[subset, :self.ncdim],
316-
rstate=self.rstate,
317-
bootstrap=self.bootstrap,
318-
pool=pool)
313+
self.bound.update(self.live_u[subset, :self.ncdim],
314+
rstate=self.rstate,
315+
bootstrap=self.bootstrap,
316+
pool=pool)
319317
if self.enlarge != 1.:
320-
if self.bounding in ['single', 'balls', 'cubes']:
321-
self.bound.scale_to_logvol(self.bound.logvol +
322-
np.log(self.enlarge))
323-
if self.bounding == 'multi':
324-
self.bound.scale_to_logvol(self.bound.logvols +
325-
np.log(self.enlarge))
318+
self.bound.scale_to_logvol(self.bound.logvol +
319+
np.log(self.enlarge))
326320

327321
return copy.deepcopy(self.bound)
328322

0 commit comments

Comments
 (0)