Skip to content

Commit

Permalink
Dynesty warnings added (#1324)
Browse files Browse the repository at this point in the history
* added warnings to dynesty and support for posterior

* adapted objective in test to likelihood

* added string constants

* added objective type to dynesty in notebook
  • Loading branch information
arrjon committed Mar 15, 2024
1 parent b1298ea commit 2558763
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 7 deletions.
2 changes: 1 addition & 1 deletion doc/example/sampler_study.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@
"metadata": {},
"outputs": [],
"source": [
"sampler = sample.DynestySampler()\n",
"sampler = sample.DynestySampler(objective_type=\"negloglike\")\n",
"result = sample.sample(\n",
" problem=problem,\n",
" n_samples=None,\n",
Expand Down
2 changes: 2 additions & 0 deletions pypesto/C.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ class EnsembleType(Enum):
RES = "res" # residual
SRES = "sres" # residual sensitivities
RDATAS = "rdatas" # returned simulated data sets
OBJECTIVE_NEGLOGPOST = "neglogpost" # objective is negative log-posterior
OBJECTIVE_NEGLOGLIKE = "negloglike" # objective is negative log-likelihood

TIME = "time" # time
N_FVAL = "n_fval" # number of function evaluations
Expand Down
44 changes: 41 additions & 3 deletions pypesto/sample/dynesty.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import numpy as np

from ..C import OBJECTIVE_NEGLOGLIKE, OBJECTIVE_NEGLOGPOST
from ..problem import Problem
from ..result import McmcPtResult
from .sampler import Sampler, SamplerImportError
Expand Down Expand Up @@ -62,6 +63,7 @@ def __init__(
sampler_args: dict = None,
run_args: dict = None,
dynamic: bool = True,
objective_type: str = OBJECTIVE_NEGLOGPOST,
):
"""
Initialize sampler.
Expand All @@ -76,6 +78,9 @@ def __init__(
method of the dynesty sampler.
dynamic:
Whether to use dynamic or static nested sampling.
objective_type:
The objective to optimize (as defined in pypesto.problem). Either "neglogpost" or
"negloglike". If "neglogpost", x_priors have to be defined in the problem.
"""
# check dependencies
import dynesty
Expand All @@ -94,6 +99,13 @@ def __init__(
run_args = {}
self.run_args: dict = run_args

if objective_type not in [OBJECTIVE_NEGLOGPOST, OBJECTIVE_NEGLOGLIKE]:
raise ValueError(
f"Objective has to be either '{OBJECTIVE_NEGLOGPOST}' or '{OBJECTIVE_NEGLOGLIKE}' "
f"as defined in pypesto.problem."
)
self.objective_type = objective_type

# set in initialize
self.problem: Union[Problem, None] = None
self.sampler: Union[
Expand All @@ -112,8 +124,6 @@ def prior_transform(self, prior_sample: np.ndarray) -> np.ndarray:
----------
prior_sample:
The prior sample, provided by dynesty.
problem:
The pyPESTO problem.
Returns
-------
Expand All @@ -130,7 +140,13 @@ def loglikelihood(self, x):
if any(x < self.problem.lb) or any(x > self.problem.ub):
return -np.inf
# invert sign
# TODO this is possibly the posterior if priors are defined
if self.objective_type == OBJECTIVE_NEGLOGPOST:
# problem.objective returns negative log-posterior
# compute log-likelihood by subtracting log-prior
return -1.0 * (
self.problem.objective(x) - self.problem.x_priors(x)
)
# problem.objective returns negative log-likelihood
return -1.0 * self.problem.objective(x)

def initialize(
Expand All @@ -149,6 +165,28 @@ def initialize(
if self.dynamic:
sampler_class = dynesty.DynamicNestedSampler

# check if objective fits to the pyPESTO problem
if self.objective_type == OBJECTIVE_NEGLOGPOST:
if self.problem.x_priors is None:
# objective is the negative log-posterior, but no priors are defined
# sampler needs the likelihood
raise ValueError(
f"x_priors have to be defined in the problem if objective is '{OBJECTIVE_NEGLOGPOST}'."
)
else:
# if objective is the negative log likelihood, we will ignore x_priors even if they are defined
if self.problem.x_priors is not None:
logger.warning(
f"Assuming '{OBJECTIVE_NEGLOGLIKE}' as objective. "
f"'x_priors' defined in the problem will be ignored."
)

# if priors are uniform, we can use the default prior transform (assuming that bounds are set correctly)
logger.warning(
"Assuming 'prior_transform' is correctly specified. If 'x_priors' is not uniform, 'prior_transform'"
" has to be adjusted accordingly."
)

# initialize sampler
self.sampler = sampler_class(
loglikelihood=self.loglikelihood,
Expand Down
47 changes: 44 additions & 3 deletions test/sample/test_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import pypesto.optimize as optimize
import pypesto.petab
import pypesto.sample as sample
from pypesto.C import OBJECTIVE_NEGLOGLIKE, OBJECTIVE_NEGLOGPOST
from pypesto.sample.pymc import PymcSampler


Expand Down Expand Up @@ -190,7 +191,7 @@ def sampler(request):
elif request.param == "Emcee":
return sample.EmceeSampler(nwalkers=10)
elif request.param == "Dynesty":
return sample.DynestySampler()
return sample.DynestySampler(objective_type="negloglike")


@pytest.fixture(params=["gaussian", "gaussian_mixture", "rosenbrock"])
Expand Down Expand Up @@ -722,13 +723,13 @@ def test_samples_cis():
assert (diff == 0).all()
# check if lower bound is smaller than upper bound
assert (lb < ub).all()
# check if dimmensions agree
# check if dimensions agree
assert lb.shape == ub.shape


def test_dynesty_mcmc_samples():
problem = gaussian_problem()
sampler = sample.DynestySampler()
sampler = sample.DynestySampler(objective_type=OBJECTIVE_NEGLOGLIKE)

result = sample.sample(
problem=problem,
Expand All @@ -746,6 +747,46 @@ def test_dynesty_mcmc_samples():
assert not (np.diff(mcmc_sample_result.trace_neglogpost) <= 0).all()


def test_dynesty_posterior():
# define negative log posterior
posterior_fun = pypesto.Objective(fun=negative_log_posterior)

# define negative log prior
prior_fun = pypesto.Objective(fun=negative_log_prior)

# define pypesto prior object
prior_object = pypesto.NegLogPriors(objectives=[prior_fun])

# define pypesto problem using prior object
test_problem = pypesto.Problem(
objective=posterior_fun,
x_priors_defs=prior_object,
lb=-10,
ub=10,
x_names=["x"],
)

# define sampler
sampler = sample.DynestySampler(
objective_type=OBJECTIVE_NEGLOGPOST
) # default

result = sample.sample(
problem=test_problem,
sampler=sampler,
n_samples=None,
filename=None,
)

original_sample_result = sampler.get_original_samples()
mcmc_sample_result = result.sample_result

# Nested sampling function values are monotonically increasing
assert (np.diff(original_sample_result.trace_neglogpost) <= 0).all()
# MCMC samples are not
assert not (np.diff(mcmc_sample_result.trace_neglogpost) <= 0).all()


def test_thermodynamic_integration():
# test thermodynamic integration
problem = gaussian_problem()
Expand Down

0 comments on commit 2558763

Please sign in to comment.