Skip to content

Commit

Permalink
Merge e64c8e3 into c14b212
Browse files Browse the repository at this point in the history
  • Loading branch information
OriolAbril committed Mar 20, 2020
2 parents c14b212 + e64c8e3 commit 84b3fc6
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 16 deletions.
21 changes: 17 additions & 4 deletions src/emcee/ensemble.py
Expand Up @@ -7,7 +7,7 @@
from .moves import StretchMove
from .pbar import get_progress_bar
from .state import State
from .utils import deprecated, deprecation_warning
from .utils import deprecated, deprecation_warning, _check_random_state

__all__ = ["EnsembleSampler", "walkers_independent"]

Expand Down Expand Up @@ -58,6 +58,15 @@ class EnsembleSampler(object):
to accept a list of position vectors instead of just one. Note
that ``pool`` will be ignored if this is ``True``.
(default: ``False``)
seed (Union[int, np.random.RandomState, np.random.Generator, None]): If
`seed` is not specified the `np.RandomState` singleton is used.
If `seed` is an int, a new `np.random.RandomState` instance is used,
seeded with seed.
If `seed` is already a `np.random.RandomState` or a
`np.random.Generator` instance, then that `RandomState` or
`Generator` instance is used, omitting the stored state if
re-using a backend.
Specify `seed` for reproducable minimizations.
"""

Expand All @@ -73,6 +82,7 @@ def __init__(
backend=None,
vectorize=False,
blobs_dtype=None,
seed=None,
# Deprecated...
a=None,
postargs=None,
Expand Down Expand Up @@ -121,6 +131,7 @@ def __init__(
self.backend = Backend() if backend is None else backend

# Deal with re-used backends
state = None
if not self.backend.initialized:
self._previous_state = None
self.reset()
Expand All @@ -147,8 +158,7 @@ def __init__(

# This is a random number generator that we can easily set the state
# of without affecting the numpy-wide generator
self._random = np.random.mtrand.RandomState()
self._random.set_state(state)
self._random = _check_random_state(seed, state)

# Do a little bit of _magic_ to make the likelihood call with
# ``args`` and ``kwargs`` pickleable.
Expand All @@ -164,7 +174,10 @@ def random_state(self):
so silently.
"""
return self._random.get_state()
try:
return self._random.get_state()
except AttributeError:
return self._random.bit_generator.state

@random_state.setter # NOQA
def random_state(self, state):
Expand Down
5 changes: 3 additions & 2 deletions src/emcee/moves/de.py
Expand Up @@ -3,6 +3,7 @@
import numpy as np

from .red_blue import RedBlueMove
from ..utils import rng_integers

__all__ = ["DEMove"]

Expand Down Expand Up @@ -42,9 +43,9 @@ def get_proposal(self, s, c, random):
Nc = list(map(len, c))
ndim = s.shape[1]
q = np.empty((Ns, ndim), dtype=np.float64)
f = self.sigma * random.randn(Ns)
f = self.sigma * random.standard_normal(Ns)
for i in range(Ns):
w = np.array([c[j][random.randint(Nc[j])] for j in range(2)])
w = np.array([c[j][rng_integers(random, Nc[j])] for j in range(2)])
random.shuffle(w)
g = np.diff(w, axis=0) * self.g0 + f[i]
q[i] = s[i] + g
Expand Down
3 changes: 2 additions & 1 deletion src/emcee/moves/de_snooker.py
Expand Up @@ -3,6 +3,7 @@
import numpy as np

from .red_blue import RedBlueMove
from ..utils import rng_integers

__all__ = ["DESnookerMove"]

Expand Down Expand Up @@ -35,7 +36,7 @@ def get_proposal(self, s, c, random):
q = np.empty((Ns, ndim), dtype=np.float64)
metropolis = np.empty(Ns, dtype=np.float64)
for i in range(Ns):
w = np.array([c[j][random.randint(Nc[j])] for j in range(3)])
w = np.array([c[j][rng_integers(random, Nc[j])] for j in range(3)])
random.shuffle(w)
z, z1, z2 = w
delta = s[i] - z
Expand Down
7 changes: 4 additions & 3 deletions src/emcee/moves/gaussian.py
Expand Up @@ -3,6 +3,7 @@
import numpy as np

from .mh import MHMove
from ..utils import rng_integers

__all__ = ["GaussianMove"]

Expand Down Expand Up @@ -88,13 +89,13 @@ def get_factor(self, rng):
return np.exp(rng.uniform(-self._log_factor, self._log_factor))

def get_updated_vector(self, rng, x0):
return x0 + self.get_factor(rng) * self.scale * rng.randn(*(x0.shape))
return x0 + self.get_factor(rng) * self.scale * rng.standard_normal((x0.shape))

def __call__(self, x0, rng):
nw, nd = x0.shape
xnew = self.get_updated_vector(rng, x0)
if self.mode == "random":
m = (range(nw), rng.randint(x0.shape[-1], size=nw))
m = (range(nw), rng_integers(rng, x0.shape[-1], size=nw))
elif self.mode == "sequential":
m = (range(nw), self.index % nd + np.zeros(nw, dtype=int))
self.index = (self.index + 1) % nd
Expand All @@ -107,7 +108,7 @@ def __call__(self, x0, rng):

class _diagonal_proposal(_isotropic_proposal):
def get_updated_vector(self, rng, x0):
return x0 + self.get_factor(rng) * self.scale * rng.randn(*(x0.shape))
return x0 + self.get_factor(rng) * self.scale * rng.standard_normal((x0.shape))


class _proposal(_isotropic_proposal):
Expand Down
2 changes: 1 addition & 1 deletion src/emcee/moves/mh.py
Expand Up @@ -56,7 +56,7 @@ def propose(self, model, state):

# Loop over the walkers and update them accordingly.
lnpdiff = new_log_probs - state.log_prob + factors
accepted = np.log(model.random.rand(nwalkers)) < lnpdiff
accepted = np.log(model.random.uniform(size=nwalkers)) < lnpdiff

# Update the parameters
new_state = State(q, log_prob=new_log_probs, blobs=new_blobs)
Expand Down
2 changes: 1 addition & 1 deletion src/emcee/moves/red_blue.py
Expand Up @@ -97,7 +97,7 @@ def propose(self, model, state):
zip(all_inds[S1], factors, new_log_probs)
):
lnpdiff = f + nlp - state.log_prob[j]
if lnpdiff > np.log(model.random.rand()):
if lnpdiff > np.log(model.random.uniform()):
accepted[j] = True

new_state = State(q, log_prob=new_log_probs, blobs=new_blobs)
Expand Down
5 changes: 3 additions & 2 deletions src/emcee/moves/stretch.py
Expand Up @@ -3,6 +3,7 @@
import numpy as np

from .red_blue import RedBlueMove
from ..utils import rng_integers

__all__ = ["StretchMove"]

Expand All @@ -27,7 +28,7 @@ def get_proposal(self, s, c, random):
c = np.concatenate(c, axis=0)
Ns, Nc = len(s), len(c)
ndim = s.shape[1]
zz = ((self.a - 1.0) * random.rand(Ns) + 1) ** 2.0 / self.a
zz = random.uniform(low=1, high=self.a, size=Ns) ** 2.0 / self.a
factors = (ndim - 1.0) * np.log(zz)
rint = random.randint(Nc, size=(Ns,))
rint = rng_integers(random, Nc, size=(Ns,))
return c[rint] - (c[rint] - s) * zz[:, None], factors
2 changes: 1 addition & 1 deletion src/emcee/tests/integration/test_longdouble.py
Expand Up @@ -11,7 +11,7 @@ def log_prob(x, ivar):

ndim, nwalkers = 5, 20
ivar = 1. / np.random.rand(ndim).astype(np.longdouble)
p0 = np.random.randn(nwalkers, ndim).astype(np.longdouble)
p0 = np.random.standard_normal((nwalkers, ndim)).astype(np.longdouble)

sampler = emcee.EnsembleSampler(nwalkers, ndim, log_prob, args=[ivar])
sampler.run_mcmc(p0, 100)
Expand Down
58 changes: 57 additions & 1 deletion src/emcee/tests/unit/test_sampler.py
Expand Up @@ -2,9 +2,11 @@

import pickle
from itertools import product
from copy import deepcopy

import numpy as np
import pytest
import packaging

from emcee import EnsembleSampler, backends, moves, walkers_independent

Expand Down Expand Up @@ -42,6 +44,7 @@ def test_shapes(backend, moves, nwalkers=32, ndim=3, nsteps=10, seed=1234):

# Run the sampler.
sampler.run_mcmc(coords, nsteps)

chain = sampler.get_chain()
assert len(chain) == nsteps, "wrong number of steps"

Expand Down Expand Up @@ -137,7 +140,10 @@ def run_sampler(
):
np.random.seed(seed)
coords = np.random.randn(nwalkers, ndim)
sampler = EnsembleSampler(nwalkers, ndim, normal_log_prob, backend=backend)
np.random.seed(None)
sampler = EnsembleSampler(
nwalkers, ndim, normal_log_prob, backend=backend, seed=seed
)
sampler.run_mcmc(
coords,
nsteps,
Expand Down Expand Up @@ -319,3 +325,53 @@ def test_walkers_independent_randn_offset_longdouble(nwalkers, ndim, offset):
np.random.randn(nwalkers, ndim)
+ np.ones((nwalkers, ndim), dtype=np.longdouble) * offset
)


def test_sampler_seed():
nwalkers = 32
ndim = 3
nsteps = 25
np.random.seed(456)
coords = np.random.randn(nwalkers, ndim)
sampler1 = EnsembleSampler(nwalkers, ndim, normal_log_prob, seed=1234)
sampler2 = EnsembleSampler(nwalkers, ndim, normal_log_prob, seed=2)
sampler3 = EnsembleSampler(nwalkers, ndim, normal_log_prob, seed=1234)
sampler4 = EnsembleSampler(
nwalkers, ndim, normal_log_prob, seed=deepcopy(sampler1._random)
)
for sampler in (sampler1, sampler2, sampler3, sampler4):
sampler.run_mcmc(coords, nsteps)
for k in ["get_chain", "get_log_prob"]:
attr1 = getattr(sampler1, k)()
attr2 = getattr(sampler2, k)()
attr3 = getattr(sampler3, k)()
attr4 = getattr(sampler4, k)()
assert not np.allclose(attr1, attr2), "inconsistent {0}".format(k)
np.testing.assert_allclose(attr1, attr3, err_msg="inconsistent {0}".format(k))
np.testing.assert_allclose(attr1, attr4, err_msg="inconsistent {0}".format(k))


def test_sampler_bad_seed():
nwalkers = 32
ndim = 3
with pytest.raises(TypeError, match="seed must be"):
EnsembleSampler(nwalkers, ndim, normal_log_prob, seed="bad_seed")

@pytest.mark.skipif(
packaging.version.parse(np.__version__) < packaging.version.parse("1.17.0"),
reason="requires numpy 1.17.0 or higher",
)
def test_sampler_generator():
nwalkers = 32
ndim = 3
nsteps = 5
np.random.seed(456)
coords = np.random.randn(nwalkers, ndim)
seed1 = np.random.default_rng(1)
sampler1 = EnsembleSampler(nwalkers, ndim, normal_log_prob, seed=seed1)
sampler1.run_mcmc(coords, nsteps)
seed2 = np.random.default_rng(1)
sampler2 = EnsembleSampler(nwalkers, ndim, normal_log_prob, seed=seed2)
sampler2.run_mcmc(coords, nsteps)
np.testing.assert_allclose(sampler1.get_chain(), sampler2.get_chain())
np.testing.assert_allclose(sampler1.get_log_prob(), sampler2.get_log_prob())
44 changes: 44 additions & 0 deletions src/emcee/utils.py
Expand Up @@ -60,3 +60,47 @@ def sample_ellipsoid(p0, covmat, size=1):
return np.random.multivariate_normal(
np.atleast_1d(p0), np.atleast_2d(covmat), size=size
)

try:
Generator = np.random.Generator
except AttributeError:
Generator = None

def rng_integers(gen, low, **kwargs):
"""
Generate integers from a Generator or RandomState.
:param gen: Generator or RandomState object
:param low: Passed as first argument to gen.integers() or to
gen.randint() depending of its type.
:param kwargs: Passed to gen.integers() or to gen.randint()
depending on its type.
"""
if isinstance(gen, Generator):
return gen.integers(low, **kwargs)
return gen.randint(low, **kwargs)

def _check_random_state(seed, state):
"""Check seed argument and set RandomState.
Based on scikit-learn utils/validation.py.
"""
if isinstance(seed, (int, np.integer)) or seed is None:
random = np.random.mtrand.RandomState()
random.set_state(state)
if seed is not None:
random.seed(seed)
return random
if isinstance(seed, np.random.RandomState):
return seed
try:
# Generator is only available in numpy >= 1.17
if isinstance(seed, np.random.Generator):
return seed
except AttributeError:
pass
raise TypeError(
"seed must be an int, np.random.RandomState, np.random.Generator or "
"None of type {}".format(type(seed))
)

0 comments on commit 84b3fc6

Please sign in to comment.