diff --git a/pesummary/core/file/formats/base_read.py b/pesummary/core/file/formats/base_read.py index 6e1de67a8ff985d76c64829a955b8e37ebe30ebc..b6bd00f4d2476a7467255dce4c25e9c7460e790a 100644 --- a/pesummary/core/file/formats/base_read.py +++ b/pesummary/core/file/formats/base_read.py @@ -131,6 +131,8 @@ class Read(object): self.extra_kwargs["sampler"]["nsamples"] = len(self.data["samples"]) if "prior" in self.data.keys(): self.priors = self.data["prior"] + if "analytic" in self.data.keys(): + self.analytic = self.data["analytic"] if "labels" in self.data.keys(): self.labels = self.data["labels"] if "config" in self.data.keys(): diff --git a/pesummary/core/file/formats/bilby.py b/pesummary/core/file/formats/bilby.py index fa95198e2d602d4d84b5f1abb7b51d51fe8d2721..5aedb199f7f8270f2566b9182ecc92f5b4f5011c 100644 --- a/pesummary/core/file/formats/bilby.py +++ b/pesummary/core/file/formats/bilby.py @@ -19,6 +19,7 @@ from pesummary.core.file.formats.base_read import Read from pesummary.core.plots.latex_labels import latex_labels from pesummary import conf from pesummary.utils.utils import logger +from bilby.core.result import Result def read_bilby( @@ -99,12 +100,15 @@ def read_bilby( if not disable_prior: prior_samples = Bilby.grab_priors(bilby_object, nsamples=len(samples)) data["prior"] = {"samples": prior_samples} + if len(prior_samples): + data["prior"]["analytic"] = prior_samples.analytic return data def write_bilby( parameters, samples, outdir="./", label=None, filename=None, overwrite=False, - extension="json", save=True, **kwargs + extension="json", save=True, analytic_priors=None, cls=Result, + meta_data=None, **kwargs ): """Write a set of samples to a bilby file @@ -128,15 +132,18 @@ def write_bilby( save: Bool, optional if True, save the bilby object to file """ - from bilby.core.result import Result from bilby.core.prior import Prior, PriorDict from pandas import DataFrame - priors = PriorDict() - priors.update({parameter: Prior() for parameter in parameters}) + if analytic_priors is not None: + priors = PriorDict._get_from_json_dict(analytic_priors) + search_parameters = priors.keys() + else: + priors = {param: Prior() for param in parameters} + search_parameters = parameters posterior_data_frame = DataFrame(samples, columns=parameters) - bilby_object = Result( - search_parameter_keys=parameters, samples=samples, priors=priors, + bilby_object = cls( + search_parameter_keys=search_parameters, samples=samples, priors=priors, posterior=posterior_data_frame, label="pesummary_%s" % label, ) if save: @@ -146,6 +153,53 @@ def write_bilby( return bilby_object +def prior_samples_from_file(path, nsamples=5000): + """Return a dict of prior samples from a `bilby` prior file + + Parameters + ---------- + path: str + path to a `bilby` prior file + nsamples: int, optional + number of samples to draw from a prior file. Default 5000 + """ + from bilby.core.prior import PriorDict + + _prior = PriorDict(filename=path) + samples = _prior.sample(size=nsamples) + return _bilby_prior_dict_to_pesummary_samples_dict(samples, prior=_prior) + + +def prior_samples_from_bilby_object(bilby_object, nsamples=5000): + """Return a dict of prior samples from a `bilby.core.result.Result` + object + + Parameters + ---------- + bilby_object: bilby.core.result.Result + a bilby.core.result.Result object you wish to draw prior samples from + nsamples: int, optional + number of samples to draw from a prior file. Default 5000 + """ + samples = bilby_object.priors.sample(size=nsamples) + return _bilby_prior_dict_to_pesummary_samples_dict( + samples, prior=bilby_object.priors + ) + + +def _bilby_prior_dict_to_pesummary_samples_dict(samples, prior=None): + """Return a pesummary.utils.samples_dict.SamplesDict object from a bilby + priors dict + """ + from pesummary.utils.samples_dict import SamplesDict + + _samples = SamplesDict(samples) + if prior is not None: + analytic = {key: str(item) for key, item in prior.items()} + setattr(_samples, "analytic", analytic) + return _samples + + class Bilby(Read): """PESummary wrapper of `bilby` (https://git.ligo.org/lscsoft/bilby). The path_to_results_file argument will be passed directly to @@ -201,16 +255,13 @@ class Bilby(Read): def grab_priors(bilby_object, nsamples=5000): """Draw samples from the prior functions stored in the bilby file """ - from pesummary.utils.samples_dict import Array - - f = bilby_object try: - samples = f.priors.sample(size=nsamples) - priors = {key: Array(samples[key]) for key in samples} + return prior_samples_from_bilby_object( + bilby_object, nsamples=nsamples + ) except Exception as e: logger.info("Failed to draw prior samples because {}".format(e)) - priors = {} - return priors + return {} @staticmethod def grab_extra_kwargs(bilby_object): diff --git a/pesummary/core/file/formats/default.py b/pesummary/core/file/formats/default.py index 9966e1faf08db400d13343e120f723750d14e92e..e6185f1ca9c32ef4dc4b2de304ed6d60f7d99225 100644 --- a/pesummary/core/file/formats/default.py +++ b/pesummary/core/file/formats/default.py @@ -58,7 +58,8 @@ class Default(Read): "txt": self._grab_data_from_dat_file, "hdf5": self._grab_data_from_hdf5_file, "h5": self._grab_data_from_hdf5_file, - "hdf": self._grab_data_from_hdf5_file} + "hdf": self._grab_data_from_hdf5_file, + "prior": self._grab_data_from_prior_file} self.load_function = func_map[self.extension] try: @@ -88,6 +89,21 @@ class Default(Read): "parameters": parameters, "samples": samples, "injection": injection } + @staticmethod + def _grab_data_from_prior_file(path, **kwargs): + """Grab the data stored in a .prior file + """ + from pesummary.core.file.formats.bilby import prior_samples_from_file + + samples = prior_samples_from_file(path, **kwargs) + parameters = samples.parameters + analytic = samples.analytic + injection = {i: float("nan") for i in parameters} + return { + "parameters": parameters, "samples": samples.samples.T.tolist(), + "injection": injection, "analytic": analytic + } + @staticmethod def _grab_data_from_json_file(path, path_to_samples=None, **kwargs): """Grab the data stored in a .json file diff --git a/pesummary/core/file/formats/pesummary.py b/pesummary/core/file/formats/pesummary.py index 0afb35c64774d43863116c4ac3390da8bce44de6..8a990431bdb145683e3182d33fecba0ea7b2c39f 100644 --- a/pesummary/core/file/formats/pesummary.py +++ b/pesummary/core/file/formats/pesummary.py @@ -15,6 +15,7 @@ from glob import glob import os +import copy import h5py import json @@ -513,8 +514,14 @@ class PESummary(Read): except (KeyError, TypeError): kwargs[prop] = None priors = getattr(self, "priors", {label: None}) + if "analytic" in priors.keys() and label in priors["analytic"].keys(): + kwargs.update({"analytic_priors": priors["analytic"][label]}) if not len(priors): priors = {} + elif all(label in value.keys() for value in priors.values()): + priors = {key: item[label] for key, item in priors.items()} + elif "samples" in priors.keys() and label in priors["samples"].keys(): + priors = {"samples": {label: priors["samples"][label]}} elif label not in priors.keys(): priors = {} else: diff --git a/pesummary/core/inputs.py b/pesummary/core/inputs.py index 2a26257db9cc63a72b5e67847f9b42b6b0d9c095..bbd76fafb201075d8e532c193551884d79e2e612 100644 --- a/pesummary/core/inputs.py +++ b/pesummary/core/inputs.py @@ -993,7 +993,7 @@ class _Input(object): prior_dict = {} if priors is not None: - prior_dict = {"samples": {}} + prior_dict = {"samples": {}, "analytic": {}} for i in priors: if not os.path.isfile(i): raise InputError("The file {} does not exist".format(i)) @@ -1006,17 +1006,27 @@ class _Input(object): data = Read(priors[0]) for i in self.labels: prior_dict["samples"][i] = data.samples_dict + try: + prior_dict["analytic"][i] = data.analytic + except AttributeError: + continue elif len(priors) != len(self.labels): raise InputError( "Please provide a prior file for each result file" ) else: for num, i in enumerate(priors): + if i.lower() == "none": + continue logger.info( "Assigning {} to {}".format(self.labels[num], i) ) data = Read(priors[num]) prior_dict["samples"][self.labels[num]] = data.samples_dict + try: + prior_dict["analytic"][self.labels[num]] = data.analytic + except AttributeError: + continue return prior_dict @property diff --git a/pesummary/gw/file/formats/base_read.py b/pesummary/gw/file/formats/base_read.py index e244e76cdeac2fbe8f802cd015254f77c1e8a794..967b97c0bf252bcf0258e393c9f34295f082e775 100644 --- a/pesummary/gw/file/formats/base_read.py +++ b/pesummary/gw/file/formats/base_read.py @@ -125,22 +125,26 @@ class GWRead(Read): key: val for key, val in i.items() } for i in self.injection_parameters ] - if "prior" in data.keys() and data["prior"]["samples"] != {}: - priors = data["prior"]["samples"] - default_parameters = list(priors.keys()) - default_samples = [ - [priors[parameter][i] for parameter in default_parameters] for i - in range(len(priors[default_parameters[0]])) - ] - parameters, samples = self.translate_parameters( - default_parameters, default_samples - ) - if not kwargs.get("disable_prior_conversion", False): - self.priors = {"samples": con._Conversion( - parameters, samples, extra_kwargs=self.extra_kwargs - )} - else: - self.priors = {"samples": SamplesDict(parameters, samples)} + if "prior" in data.keys(): + self.priors = data["prior"] + if data["prior"]["samples"] != {}: + priors = data["prior"]["samples"] + default_parameters = list(priors.keys()) + default_samples = [ + [priors[parameter][i] for parameter in default_parameters] + for i in range(len(priors[default_parameters[0]])) + ] + parameters, samples = self.translate_parameters( + default_parameters, default_samples + ) + if not kwargs.get("disable_prior_conversion", False): + self.priors["samples"] = con._Conversion( + parameters, samples, extra_kwargs=self.extra_kwargs + ) + else: + self.priors["samples"] = SamplesDict(parameters, samples) + if "analytic" in data.keys(): + self.analytic = data["analytic"] if "weights" in self.data.keys(): self.weights = self.data["weights"] else: diff --git a/pesummary/gw/file/formats/default.py b/pesummary/gw/file/formats/default.py index 5df0ed22f920dc165f33f915f1f869b62e81eca4..9f4e4e8415ea5ac44ce45e346782997ec7e52047 100644 --- a/pesummary/gw/file/formats/default.py +++ b/pesummary/gw/file/formats/default.py @@ -62,7 +62,8 @@ class Default(GWRead): "txt": self._grab_data_from_dat_file, "hdf5": self._grab_data_from_hdf5_file, "h5": self._grab_data_from_hdf5_file, - "hdf": self._grab_data_from_hdf5_file} + "hdf": self._grab_data_from_hdf5_file, + "prior": self._grab_data_from_prior_file} self.load_function = func_map[self.extension] try: @@ -127,6 +128,12 @@ class Default(GWRead): "injection": injection, "kwargs": extra_kwargs } + @staticmethod + def _grab_data_from_prior_file(path, **kwargs): + """Grab data stored in a .prior file + """ + return CoreDefault._grab_data_from_prior_file(path, **kwargs) + @staticmethod def _grab_data_from_hdf5_file(path, path_to_samples=None, **kwargs): """Grab the data stored in an hdf5 file diff --git a/pesummary/gw/file/formats/pesummary.py b/pesummary/gw/file/formats/pesummary.py index 010385224adfdca874e5b6ff34f011e2b6d578ac..12523921331e523a5a389caa361069d10769d1bc 100644 --- a/pesummary/gw/file/formats/pesummary.py +++ b/pesummary/gw/file/formats/pesummary.py @@ -314,32 +314,15 @@ class PESummary(GWRead, CorePESummary): ) } - def to_bilby(self): + def to_bilby(self, labels="all", **kwargs): """Convert a PESummary metafile to a bilby results object """ from bilby.gw.result import CompactBinaryCoalescenceResult - from bilby.core.prior import Prior, PriorDict - from pandas import DataFrame - - objects = dict() - for num, label in enumerate(self.labels): - priors = PriorDict() - logger.warn( - "No prior information is known so setting it to a default") - priors.update({parameter: Prior() for parameter in self.parameters[num]}) - posterior_data_frame = DataFrame( - self.samples[num], columns=self.parameters[num]) - meta_data = { - "likelihood": { - "waveform_arguments": { - "waveform_approximant": self.approximant[num]}, - "interferometers": self.detectors[num]}} - bilby_object = CompactBinaryCoalescenceResult( - search_parameter_keys=self.parameters[num], - posterior=posterior_data_frame, label="pesummary_%s" % label, - samples=self.samples[num], priors=priors, meta_data=meta_data) - objects[label] = bilby_object - return objects + + return CorePESummary.write( + self, labels=labels, package="core", file_format="bilby", + _return=True, cls=CompactBinaryCoalescenceResult, **kwargs + ) def to_lalinference(self, labels="all", **kwargs): """Convert the samples stored in a PESummary metafile to a .dat file diff --git a/pesummary/gw/inputs.py b/pesummary/gw/inputs.py index 141e45ab3d37ca78c91ed3e83b32eacdae3e6042..ad37afe053a76f8495def7a3f06be7a3af72c3ec 100644 --- a/pesummary/gw/inputs.py +++ b/pesummary/gw/inputs.py @@ -684,9 +684,9 @@ class _GWInput(_Input): prior_dict = {} if priors is not None: - prior_dict = {"samples": {}} + prior_dict = {"samples": {}, "analytic": {}} for i in priors: - if not os.path.isfile(i): + if i.lower() != "none" and not os.path.isfile(i): raise InputError("The file {} does not exist".format(i)) if len(priors) != len(self.labels) and len(priors) == 1: logger.warn( @@ -698,12 +698,18 @@ class _GWInput(_Input): data.generate_all_posterior_samples() for i in self.labels: prior_dict["samples"][i] = data.samples_dict + try: + prior_dict["analytic"][i] = data.analytic + except AttributeError: + continue elif len(priors) != len(self.labels): raise InputError( "Please provide a prior file for each result file" ) else: for num, i in enumerate(priors): + if i.lower() == "none": + continue logger.info( "Assigning {} to {}".format(self.labels[num], i) ) @@ -716,6 +722,10 @@ class _GWInput(_Input): data = GWRead(priors[num]) data.generate_all_posterior_samples(**grab_data_kwargs) prior_dict["samples"][self.labels[num]] = data.samples_dict + try: + prior_dict["analytic"][self.labels[num]] = data.analytic + except AttributeError: + continue return prior_dict diff --git a/tests/executable_test.py b/tests/executable_test.py index b4ad05c5fae8667f6f8a0508d87f0f413862e1e5..03d14bc15e11ecf53ea50173dceb9e27fdaced0b 100644 --- a/tests/executable_test.py +++ b/tests/executable_test.py @@ -63,11 +63,18 @@ class TestSummaryPages(Base): ) ) + def test_prior_input(self): """Check that `summarypages` works when a prior file is passed from the command line """ import importlib + import pkg_resources + + path = pkg_resources.resource_filename("bilby", "gw") + bilby_prior_file = os.path.join( + path, "prior_files", "GW150914.prior" + ) for package in ["core", "gw"]: gw = True if package == "gw" else False @@ -76,20 +83,29 @@ class TestSummaryPages(Base): ) make_result_file(gw=gw, extension="json") os.rename(".outdir/test.json", ".outdir/prior.json") - command_line = ( - "summarypages --webdir .outdir --samples .outdir/example.json " - "--prior_file .outdir/prior.json --labels test" - ) - command_line += " --gw" if gw else "" - self.launch(command_line) - f = module.read(".outdir/samples/posterior_samples.h5") - stored = f.priors["samples"]["test"] - f = module.read(".outdir/prior.json") - original = f.samples_dict - for param in original.keys(): - np.testing.assert_almost_equal( - original[param], stored[param] + for _file in [".outdir/prior.json", bilby_prior_file]: + command_line = ( + "summarypages --webdir .outdir --samples .outdir/example.json " + "--labels test --prior_file {}".format(_file) ) + command_line += " --gw" if gw else "" + self.launch(command_line) + f = module.read(".outdir/samples/posterior_samples.h5") + if _file != bilby_prior_file: + stored = f.priors["samples"]["test"] + f = module.read(_file) + original = f.samples_dict + for param in original.keys(): + np.testing.assert_almost_equal( + original[param], stored[param] + ) + else: + from bilby.core.prior import PriorDict + + analytic = f.priors["analytic"]["test"] + bilby_prior = PriorDict(filename=bilby_prior_file) + for param, value in bilby_prior.items(): + assert analytic[param] == str(value) def test_calibration_and_psd(self): """Test that the calibration and psd files are passed appropiately