Source code for desilike.samplers.pocomc
"""Module implementing the pocoMC samplers."""
import numpy as np
try:
import pocomc
POCOMC_INSTALLED = True
except ModuleNotFoundError:
POCOMC_INSTALLED = False
from .base import _update_parameters, PopulationSampler
class Prior(object):
"""Prior distribution for ``pocoMC``."""
def __init__(self, params, random_state=None):
self.dists = [param.prior for param in params]
self.random_state = random_state
def logpdf(self, x):
"""Logarithm of the prior distribution."""
logp = np.zeros(len(x))
for i, dist in enumerate(self.dists):
logp += dist(x[:, i])
return logp
def rvs(self, size=1):
"""Sample from the pior."""
samples = []
for dist in self.dists:
samples.append(dist.sample(
size=size, random_state=self.random_state))
return np.transpose(samples)
@property
def bounds(self):
"""Bounds of the prior distribution."""
bounds = []
for dist in self.dists:
bounds.append(dist.limits)
return np.array(bounds).astype(float)
@property
def dim(self):
"""Dimensionality of the prior."""
return len(self.dists)
[docs]
class PocoMCSampler(PopulationSampler):
"""Class for the ``pocoMC`` preconditioned Monte Carlo sampling.
.. rubric:: References
- `pocoMC repo <https://github.com/minaskar/pocomc>`_
- `pocoMC docs <https://pocomc.readthedocs.io>`_
- `pocoMC paper A <https://doi.org/10.21105/joss.04634>`_
- `pocoMC paper B <https://doi.org/10.1093/mnras/stac2272>`_
"""
def __init__(self, likelihood, rng=None, directory=None, **kwargs):
"""Initialize the ``PocoMC`` sampler.
Parameters
----------
likelihood : BaseLikelihood
Likelihood to sample.
rng : numpy.random.Generator, int or None, optional
Random number generator. Default is ``None``.
directory : str, Path, optional
Save samples to this location. Default is ``None``.
**kwargs
Extra keyword arguments passed to pocoMC during initialization.
"""
if not POCOMC_INSTALLED:
raise ImportError("The 'pocomc' package is required but not "
"installed.")
super().__init__(likelihood, rng=rng, directory=directory)
if self.mpicomm.rank == 0:
prior = Prior(self.likelihood.varied_params)
kwargs = _update_parameters(
kwargs, 'pocoMC', prior=prior,
likelihood=self._compute_likelihood, n_dim=self.n_dim,
pool=self.pool, output_dir=self.directory,
random_state=self.rng.integers(2**32 - 1))
self.sampler = pocomc.Sampler(**kwargs)
# Try to read existing sampler state, if available.
if self.directory is not None:
filepath_max = None
state_max = -1
for filepath in self.directory.glob('pmc_*.state'):
state = str(filepath.stem).split('_')[1]
if state == 'final':
filepath_max = filepath
break
state = int(state)
if state > state_max:
state_max = state
filepath_max = filepath
if filepath_max is not None:
# PocoMC tries to read the pickled likelihood. However,
# that's not stored on disk.
log_likelihood = self.sampler.log_likelihood
self.sampler.load_state(filepath_max)
self.sampler.log_likelihood = log_likelihood
def _run(self, **kwargs):
"""Run the ``pocoMC`` sampler.
Parameters
----------
**kwargs
Extra keyword arguments passed to ``pocoMC``'s ``run`` method.
Returns
-------
samples : numpy.ndarray of shape (n_samples, n_dim)
Samples of varied parameters.
derived : numpy.ndarray
Samples of derived parameters.
extras : dict
Extra parameters such as weights.
"""
kwargs = _update_parameters(
kwargs, 'pocoMC', resume_state_path=None,
save_every=1 if self.directory is not None else None)
self.sampler.run(**kwargs)
samples, weights, logl, logp, blobs = self.sampler.posterior(
return_blobs=True)
extras = dict(log_weight=np.log(weights), log_posterior=logp,
log_likelihood=logl)
return samples, blobs, extras