From 410eb05bff9c535a626f5738418e71e6b10d651c Mon Sep 17 00:00:00 2001
From: Michael Williams <michael.williams@ligo.org>
Date: Tue, 18 Jan 2022 15:07:18 +0000
Subject: [PATCH] Resolve "Conversion function not applied to posterior"

---
 bilby/core/sampler/cpnest.py         |   8 +-
 bilby/core/sampler/nessai.py         |   9 +-
 bilby/core/sampler/pymc3.py          |  22 ++---
 test/integration/sampler_run_test.py | 120 ++++++++++++++++++++-------
 4 files changed, 104 insertions(+), 55 deletions(-)

diff --git a/bilby/core/sampler/cpnest.py b/bilby/core/sampler/cpnest.py
index 7a089a4ca..e64365f2e 100644
--- a/bilby/core/sampler/cpnest.py
+++ b/bilby/core/sampler/cpnest.py
@@ -3,6 +3,7 @@ import array
 import copy
 
 import numpy as np
+from numpy.lib.recfunctions import structured_to_unstructured
 from pandas import DataFrame
 
 from .base_sampler import NestedSampler
@@ -121,11 +122,12 @@ class Cpnest(NestedSampler):
             out.plot()
 
         self.calc_likelihood_count()
-        self.result.posterior = DataFrame(out.posterior_samples)
+        self.result.samples = structured_to_unstructured(
+            out.posterior_samples[self.search_parameter_keys]
+        )
+        self.result.log_likelihood_evaluations = out.posterior_samples['logL']
         self.result.nested_samples = DataFrame(out.get_nested_samples(filename=''))
         self.result.nested_samples.rename(columns=dict(logL='log_likelihood'), inplace=True)
-        self.result.posterior.rename(columns=dict(logL='log_likelihood', logPrior='log_prior'),
-                                     inplace=True)
         _, log_weights = compute_weights(np.array(self.result.nested_samples.log_likelihood),
                                          np.array(out.NS.state.nlive))
         self.result.nested_samples['weights'] = np.exp(log_weights)
diff --git a/bilby/core/sampler/nessai.py b/bilby/core/sampler/nessai.py
index 5e3ef364b..d8bb578a4 100644
--- a/bilby/core/sampler/nessai.py
+++ b/bilby/core/sampler/nessai.py
@@ -70,7 +70,7 @@ class Nessai(NestedSampler):
     def run_sampler(self):
         from nessai.flowsampler import FlowSampler
         from nessai.model import Model as BaseModel
-        from nessai.livepoint import dict_to_live_points
+        from nessai.livepoint import dict_to_live_points, live_points_to_array
         from nessai.posterior import compute_weights
         from nessai.utils import setup_logger
 
@@ -136,12 +136,13 @@ class Nessai(NestedSampler):
         # Manually set likelihood evaluations because parallelisation breaks the counter
         self.result.num_likelihood_evaluations = out.ns.likelihood_evaluations[-1]
 
-        self.result.posterior = DataFrame(out.posterior_samples)
+        self.result.samples = live_points_to_array(
+            out.posterior_samples, self.search_parameter_keys
+        )
+        self.result.log_likelihood_evaluations = out.posterior_samples['logL']
         self.result.nested_samples = DataFrame(out.nested_samples)
         self.result.nested_samples.rename(
             columns=dict(logL='log_likelihood', logP='log_prior'), inplace=True)
-        self.result.posterior.rename(
-            columns=dict(logL='log_likelihood', logP='log_prior'), inplace=True)
         _, log_weights = compute_weights(np.array(self.result.nested_samples.log_likelihood),
                                          np.array(out.ns.state.nlive))
         self.result.nested_samples['weights'] = np.exp(log_weights)
diff --git a/bilby/core/sampler/pymc3.py b/bilby/core/sampler/pymc3.py
index c5c0a0d18..25522f82f 100644
--- a/bilby/core/sampler/pymc3.py
+++ b/bilby/core/sampler/pymc3.py
@@ -551,23 +551,13 @@ class Pymc3(MCMCSampler):
 
         with self.pymc3_model:
             # perform the sampling
-            trace = pymc3.sample(**self.kwargs)
+            trace = pymc3.sample(**self.kwargs, return_inferencedata=True)
 
-        nparams = len([key for key in self.priors.keys() if not isinstance(self.priors[key], DeltaFunction)])
-        nsamples = len(trace) * self.chains
-
-        self.result.samples = np.zeros((nsamples, nparams))
-        count = 0
-        for key in self.priors.keys():
-            if not isinstance(self.priors[key], DeltaFunction):  # ignore DeltaFunction variables
-                if not isinstance(self.priors[key], MultivariateGaussian):
-                    self.result.samples[:, count] = trace[key]
-                else:
-                    # get multivariate Gaussian samples
-                    priorset = self.multivariate_normal_sets[key]['set']
-                    index = self.multivariate_normal_sets[key]['index']
-                    self.result.samples[:, count] = trace[priorset][:, index]
-                count += 1
+        posterior = trace.posterior.to_dataframe().reset_index()
+        self.result.samples = posterior[self.search_parameter_keys]
+        self.result.log_likelihood_evaluations = np.sum(
+            trace.log_likelihood.likelihood.values, axis=-1
+        ).flatten()
         self.result.sampler_output = np.nan
         self.calculate_autocorrelation(self.result.samples)
         self.result.log_evidence = np.nan
diff --git a/test/integration/sampler_run_test.py b/test/integration/sampler_run_test.py
index c72ca91b7..e89336edc 100644
--- a/test/integration/sampler_run_test.py
+++ b/test/integration/sampler_run_test.py
@@ -25,8 +25,19 @@ class TestRunningSamplers(unittest.TestCase):
         self.priors = bilby.core.prior.PriorDict()
         self.priors["m"] = bilby.core.prior.Uniform(0, 5, boundary="periodic")
         self.priors["c"] = bilby.core.prior.Uniform(-2, 2, boundary="reflective")
+        self.kwargs = dict(
+            save=False,
+            conversion_function=self.conversion_function,
+        )
         bilby.core.utils.check_directory_exists_and_if_not_mkdir("outdir")
 
+    @staticmethod
+    def conversion_function(parameters, likelihood, prior):
+        converted = parameters.copy()
+        if 'derived' not in converted:
+            converted['derived'] = converted['m'] * converted['c']
+        return converted
+
     def tearDown(self):
         del self.likelihood
         del self.priors
@@ -34,17 +45,21 @@ class TestRunningSamplers(unittest.TestCase):
         shutil.rmtree("outdir")
 
     def test_run_cpnest(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("cpnest")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="cpnest",
             nlive=100,
-            save=False,
             resume=False,
+            **self.kwargs
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_dnest4(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("dnest4")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="dnest4",
@@ -54,20 +69,26 @@ class TestRunningSamplers(unittest.TestCase):
             num_per_step=10,
             thread_steps=1,
             num_particles=50,
-            save=False,
+            **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_dynesty(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("dynesty")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="dynesty",
             nlive=100,
-            save=False,
+            **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_dynamic_dynesty(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("dynesty")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="dynamic_dynesty",
@@ -77,62 +98,79 @@ class TestRunningSamplers(unittest.TestCase):
             maxbatch=0,
             maxcall=100,
             bound="single",
-            save=False,
+            **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_emcee(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("emcee")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="emcee",
             iterations=1000,
             nwalkers=10,
-            save=False,
+            **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_kombine(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("kombine")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="kombine",
             iterations=2000,
             nwalkers=20,
-            save=False,
             autoburnin=False,
+            **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_nestle(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("nestle")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="nestle",
             nlive=100,
-            save=False,
+            **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_nessai(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("nessai")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="nessai",
             nlive=100,
             poolsize=1000,
             max_iteration=1000,
-            save=False,
+            **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_pypolychord(self):
         pytest.importorskip("pypolychord")
-        _ = bilby.run_sampler(
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="pypolychord",
             nlive=100,
-            save=False,
+            **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_ptemcee(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("ptemcee")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="ptemcee",
@@ -141,60 +179,78 @@ class TestRunningSamplers(unittest.TestCase):
             burn_in_act=1,
             ntemps=1,
             frac_threshold=0.5,
-            save=False,
+            **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     @pytest.mark.skipif(sys.version_info[1] <= 6, reason="pymc3 is broken in py36")
     def test_run_pymc3(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("pymc3")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="pymc3",
             draws=50,
             tune=50,
             n_init=250,
-            save=False,
+            **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_pymultinest(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("pymultinest")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="pymultinest",
             nlive=100,
-            save=False,
+            **self.kwargs
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_PTMCMCSampler(self):
-        _ = bilby.run_sampler(
+        pytest.importorskip("PTMCMCSampler")
+        res = bilby.run_sampler(
             likelihood=self.likelihood,
             priors=self.priors,
             sampler="PTMCMCsampler",
             Niter=101,
             burn=2,
             isave=100,
-            save=False,
+            **self.kwargs
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_ultranest(self):
+        pytest.importorskip("ultranest")
         # run using NestedSampler (with nlive specified)
-        _ = bilby.run_sampler(
+        res = bilby.run_sampler(
             likelihood=self.likelihood, priors=self.priors,
-            sampler="ultranest", nlive=100, save=False,
+            sampler="ultranest", nlive=100, **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
         # run using ReactiveNestedSampler (with no nlive given)
-        _ = bilby.run_sampler(
+        res = bilby.run_sampler(
             likelihood=self.likelihood, priors=self.priors,
-            sampler='ultranest', save=False,
+            sampler='ultranest', **self.kwargs,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
     def test_run_bilby_mcmc(self):
-        _ = bilby.run_sampler(
+        res = bilby.run_sampler(
             likelihood=self.likelihood, priors=self.priors,
-            sampler="bilby_mcmc", nsamples=200, save=False,
+            sampler="bilby_mcmc", nsamples=200, **self.kwargs,
             printdt=1,
         )
+        assert 'derived' in res.posterior
+        assert res.log_likelihood_evaluations is not None
 
 
 if __name__ == "__main__":
-- 
GitLab