From 7043077cba71e76b89152e350d6ded41c411309a Mon Sep 17 00:00:00 2001
From: Colm Talbot <colm.talbot@ligo.org>
Date: Sun, 22 Mar 2020 15:29:28 -0500
Subject: [PATCH] Consistent plot formatting

---
 .gitlab-ci.yml         |  11 ++++
 bilby/core/result.py   |  25 +++++---
 bilby/core/utils.py    |  39 +++++++++++++
 bilby/gw/result.py     |  89 ++++++++++-------------------
 setup.cfg              |   1 +
 test/gw_plot_test.py   | 127 +++++++++++++++++++++++++++++++++++++++++
 test/gw_result_test.py |  90 ++++++++++++++++++++++++-----
 7 files changed, 301 insertions(+), 81 deletions(-)
 create mode 100644 test/gw_plot_test.py

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f426e45d1..319010cdb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -107,6 +107,17 @@ scheduled-python-3.7:
     - pytest test/gw_example_test.py
     - pytest test/sample_from_the_prior_test.py
 
+plotting:
+  stage: test
+  image: bilbydev/bilby-test-suite-python37
+  only:
+    - schedules
+  script:
+    - python -m pip install .
+    - python -m pip install ligo.skymap
+
+    - pytest test/gw_plot_test.py
+
 pages:
   stage: deploy
   dependencies:
diff --git a/bilby/core/result.py b/bilby/core/result.py
index 19b372096..ab5acbd15 100644
--- a/bilby/core/result.py
+++ b/bilby/core/result.py
@@ -17,8 +17,11 @@ import scipy.stats
 from scipy.special import logsumexp
 
 from . import utils
-from .utils import (logger, infer_parameters_from_function,
-                    check_directory_exists_and_if_not_mkdir,)
+from .utils import (
+    logger, infer_parameters_from_function,
+    check_directory_exists_and_if_not_mkdir,
+    latex_plot_format, safe_save_figure,
+)
 from .utils import BilbyJsonEncoder, decode_bilby_json
 from .prior import Prior, PriorDict, DeltaFunction
 
@@ -655,6 +658,7 @@ class Result(object):
             fmt(summary.median), fmt(summary.minus), fmt(summary.plus))
         return summary
 
+    @latex_plot_format
     def plot_single_density(self, key, prior=None, cumulative=False,
                             title=None, truth=None, save=True,
                             file_base_name=None, bins=50, label_fontsize=16,
@@ -734,7 +738,7 @@ class Result(object):
                 file_name = file_base_name + key + '_cdf'
             else:
                 file_name = file_base_name + key + '_pdf'
-            fig.savefig(file_name, dpi=dpi)
+            safe_save_figure(fig=fig, filename=file_name, dpi=dpi)
             plt.close(fig)
         else:
             return fig
@@ -819,6 +823,7 @@ class Result(object):
                     bins=bins, label_fontsize=label_fontsize, dpi=dpi,
                     title_fontsize=title_fontsize, quantiles=quantiles)
 
+    @latex_plot_format
     def plot_corner(self, parameters=None, priors=None, titles=True, save=True,
                     filename=None, dpi=300, **kwargs):
         """ Plot a corner-plot
@@ -976,11 +981,12 @@ class Result(object):
                 outdir = self._safe_outdir_creation(kwargs.get('outdir'), self.plot_corner)
                 filename = '{}/{}_corner.png'.format(outdir, self.label)
             logger.debug('Saving corner plot to {}'.format(filename))
-            fig.savefig(filename, dpi=dpi)
+            safe_save_figure(fig=fig, filename=filename, dpi=dpi)
             plt.close(fig)
 
         return fig
 
+    @latex_plot_format
     def plot_walkers(self, **kwargs):
         """ Method to plot the trace of the walkers in an ensemble MCMC plot """
         if hasattr(self, 'walkers') is False:
@@ -1008,9 +1014,10 @@ class Result(object):
         outdir = self._safe_outdir_creation(kwargs.get('outdir'), self.plot_walkers)
         filename = '{}/{}_walkers.png'.format(outdir, self.label)
         logger.debug('Saving walkers plot to {}'.format('filename'))
-        fig.savefig(filename)
+        safe_save_figure(fig=fig, filename=filename)
         plt.close(fig)
 
+    @latex_plot_format
     def plot_with_data(self, model, x, y, ndraws=1000, npoints=1000,
                        xlabel=None, ylabel=None, data_label='data',
                        data_fmt='o', draws_label=None, filename=None,
@@ -1082,7 +1089,7 @@ class Result(object):
         if filename is None:
             outdir = self._safe_outdir_creation(outdir, self.plot_with_data)
             filename = '{}/{}_plot_with_data'.format(outdir, self.label)
-        fig.savefig(filename, dpi=dpi)
+        safe_save_figure(fig=fig, filename=filename, dpi=dpi)
         plt.close(fig)
 
     @staticmethod
@@ -1459,6 +1466,7 @@ class ResultList(list):
             raise ResultListError("Inconsistent samplers between results")
 
 
+@latex_plot_format
 def plot_multiple(results, filename=None, labels=None, colours=None,
                   save=True, evidences=False, **kwargs):
     """ Generate a corner plot overlaying two sets of results
@@ -1538,10 +1546,11 @@ def plot_multiple(results, filename=None, labels=None, colours=None,
         filename = default_filename
 
     if save:
-        fig.savefig(filename)
+        safe_save_figure(fig=fig, filename=filename)
     return fig
 
 
+@latex_plot_format
 def make_pp_plot(results, filename=None, save=True, confidence_interval=[0.68, 0.95, 0.997],
                  lines=None, legend_fontsize='x-small', keys=None, title=True,
                  confidence_interval_alpha=0.1,
@@ -1651,7 +1660,7 @@ def make_pp_plot(results, filename=None, save=True, confidence_interval=[0.68, 0
     if save:
         if filename is None:
             filename = 'outdir/pp.png'
-        fig.savefig(filename, dpi=500)
+        safe_save_figure(fig=fig, filename=filename, dpi=500)
 
     return fig, pvals
 
diff --git a/bilby/core/utils.py b/bilby/core/utils.py
index 0cc56f204..9dcbc5b9f 100644
--- a/bilby/core/utils.py
+++ b/bilby/core/utils.py
@@ -1,11 +1,13 @@
 from __future__ import division
 
+from distutils.spawn import find_executable
 import logging
 import os
 from math import fmod
 import argparse
 import traceback
 import inspect
+import functools
 import types
 import subprocess
 import multiprocessing
@@ -1099,6 +1101,43 @@ def reflect(u):
     return u
 
 
+def latex_plot_format(func):
+    """
+    Wrap a plotting function to set rcParams so that text renders nicely with
+    latex and Computer Modern Roman font.
+    """
+    @functools.wraps(func)
+    def wrapper_decorator(*args, **kwargs):
+        from matplotlib import rcParams
+        _old_tex = rcParams["text.usetex"]
+        _old_serif = rcParams["font.serif"]
+        _old_family = rcParams["font.family"]
+        if find_executable("latex"):
+            rcParams["text.usetex"] = True
+        else:
+            rcParams["text.usetex"] = False
+        rcParams["font.serif"] = "Computer Modern Roman"
+        rcParams["font.family"] = "serif"
+        value = func(*args, **kwargs)
+        rcParams["text.usetex"] = _old_tex
+        rcParams["font.serif"] = _old_serif
+        rcParams["font.family"] = _old_family
+        return value
+    return wrapper_decorator
+
+
+def safe_save_figure(fig, filename, **kwargs):
+    from matplotlib import rcParams
+    try:
+        fig.savefig(fname=filename, **kwargs)
+    except RuntimeError:
+        logger.debug(
+            "Failed to save plot with tex labels turning off tex."
+        )
+        rcParams["text.usetex"] = False
+        fig.savefig(fname=filename, **kwargs)
+
+
 class IllegalDurationAndSamplingFrequencyException(Exception):
     pass
 
diff --git a/bilby/gw/result.py b/bilby/gw/result.py
index 66c655fd4..3f68eb971 100644
--- a/bilby/gw/result.py
+++ b/bilby/gw/result.py
@@ -9,7 +9,10 @@ from matplotlib import rcParams
 import numpy as np
 
 from ..core.result import Result as CoreResult
-from ..core.utils import infft, logger, check_directory_exists_and_if_not_mkdir
+from ..core.utils import (
+    infft, logger, check_directory_exists_and_if_not_mkdir,
+    latex_plot_format, safe_save_figure
+)
 from .utils import plot_spline_pos, spline_angle_xform, asd_from_freq_series
 from .detector import get_empty_interferometer, Interferometer
 
@@ -133,6 +136,7 @@ class CompactBinaryCoalescenceResult(CoreResult):
             logger.info("No injection for detector {}".format(detector))
             return None
 
+    @latex_plot_format
     def plot_calibration_posterior(self, level=.9, format="png"):
         """ Plots the calibration amplitude and phase uncertainty.
         Adapted from the LALInference version in bayespputils
@@ -148,12 +152,6 @@ class CompactBinaryCoalescenceResult(CoreResult):
         """
         if format not in ["png", "pdf"]:
             raise ValueError("Format should be one of png or pdf")
-        _old_tex = rcParams["text.usetex"]
-        _old_serif = rcParams["font.serif"]
-        _old_family = rcParams["font.family"]
-        rcParams["text.usetex"] = True
-        rcParams["font.serif"] = "Computer Modern Roman"
-        rcParams["font.family"] = "Serif"
 
         fig, [ax1, ax2] = plt.subplots(2, 1, figsize=(15, 15), dpi=500)
         posterior = self.posterior
@@ -214,17 +212,11 @@ class CompactBinaryCoalescenceResult(CoreResult):
 
         filename = os.path.join(outdir, self.label + '_calibration.' + format)
         fig.tight_layout()
-        try:
-            plt.savefig(filename, format=format, dpi=600, bbox_inches='tight')
-        except RuntimeError:
-            logger.debug(
-                "Failed to save waveform with tex labels turning off tex."
-            )
-            rcParams["text.usetex"] = False
-            plt.savefig(filename, format=format, dpi=600, bbox_inches='tight')
-        rcParams["text.usetex"] = _old_tex
-        rcParams["font.serif"] = _old_serif
-        rcParams["font.family"] = _old_family
+        safe_save_figure(
+            fig=fig, filename=filename,
+            format=format, dpi=600, bbox_inches='tight'
+        )
+        logger.debug("Calibration figure saved to {}".format(filename))
         plt.close()
 
     def plot_waveform_posterior(
@@ -268,6 +260,7 @@ class CompactBinaryCoalescenceResult(CoreResult):
                 save=True, format=format, start_time=start_time,
                 end_time=end_time)
 
+    @latex_plot_format
     def plot_interferometer_waveform_posterior(
             self, interferometer, level=0.9, n_samples=None, save=True,
             format='png', start_time=None, end_time=None):
@@ -328,13 +321,6 @@ class CompactBinaryCoalescenceResult(CoreResult):
                     "HTML plotting requested, but plotly cannot be imported, "
                     "falling back to png format for waveform plot.")
                 format = "png"
-        else:
-            _old_tex = rcParams["text.usetex"]
-            _old_serif = rcParams["font.serif"]
-            _old_family = rcParams["font.family"]
-            rcParams["text.usetex"] = True
-            rcParams["font.serif"] = "Computer Modern Roman"
-            rcParams["font.family"] = "Serif"
 
         if isinstance(interferometer, str):
             interferometer = get_empty_interferometer(interferometer)
@@ -383,7 +369,6 @@ class CompactBinaryCoalescenceResult(CoreResult):
             len(frequency_idxs))
         )
         plot_times = interferometer.time_array[time_idxs]
-        # if format == "html":
         plot_times -= interferometer.strain_data.start_time
         start_time -= interferometer.strain_data.start_time
         end_time -= interferometer.strain_data.start_time
@@ -663,7 +648,7 @@ class CompactBinaryCoalescenceResult(CoreResult):
             fig.update_xaxes(title_text=f_domain_x_label, type="log", row=1)
             fig.update_yaxes(title_text=f_domain_y_label, type="log", row=1)
             fig.update_xaxes(title_text=t_domain_x_label, type="linear", row=2)
-            fig.update_yaxes(title_text=t_domain_x_label, type="linear", row=2)
+            fig.update_yaxes(title_text=t_domain_y_label, type="linear", row=2)
         else:
             axs[0].set_xlim(interferometer.minimum_frequency,
                             interferometer.maximum_frequency)
@@ -684,19 +669,12 @@ class CompactBinaryCoalescenceResult(CoreResult):
                 plot(fig, filename=filename, include_mathjax='cdn', auto_open=False)
             else:
                 plt.tight_layout()
-                try:
-                    plt.savefig(filename, format=format, dpi=600)
-                except RuntimeError:
-                    logger.debug(
-                        "Failed to save waveform with tex labels turning off tex."
-                    )
-                    rcParams["text.usetex"] = False
-                    plt.savefig(filename, format=format, dpi=600)
+                safe_save_figure(
+                    fig=fig, filename=filename,
+                    format=format, dpi=600
+                )
                 plt.close()
-                rcParams["text.usetex"] = _old_tex
-                rcParams["font.serif"] = _old_serif
-                rcParams["font.family"] = _old_family
-            logger.debug("Figure saved to {}".format(filename))
+            logger.debug("Waveform figure saved to {}".format(filename))
         else:
             return fig
 
@@ -714,14 +692,14 @@ class CompactBinaryCoalescenceResult(CoreResult):
         Parameters
         ----------
         maxpts: int
-            Number of samples to use, if None all samples are used
+            Maximum number of samples to use, if None all samples are used
         trials: int
             Number of trials at each clustering number
         jobs: int
             Number of multiple threads
         enable_multiresolution: bool
             Generate a multiresolution HEALPix map (default: True)
-        objid: st
+        objid: str
             Event ID to store in FITS header
         instruments: str
             Name of detectors
@@ -766,16 +744,16 @@ class CompactBinaryCoalescenceResult(CoreResult):
         if load_pickle is False:
             try:
                 pts = data[['ra', 'dec', 'luminosity_distance']].values
-                cls = kde.Clustered2Plus1DSkyKDE
+                confidence_levels = kde.Clustered2Plus1DSkyKDE
                 distance = True
             except KeyError:
                 logger.warning("The results file does not contain luminosity_distance")
                 pts = data[['ra', 'dec']].values
-                cls = kde.Clustered2DSkyKDE
+                confidence_levels = kde.Clustered2DSkyKDE
                 distance = False
 
             logger.info('Initialising skymap class')
-            skypost = cls(pts, trials=trials, multiprocess=jobs)
+            skypost = confidence_levels(pts, trials=trials, jobs=jobs)
             logger.info('Pickling skymap to {}'.format(default_obj_filename))
             with open(default_obj_filename, 'wb') as out:
                 pickle.dump(skypost, out)
@@ -788,7 +766,8 @@ class CompactBinaryCoalescenceResult(CoreResult):
             logger.info('Reading from pickle {}'.format(obj_filename))
             with open(obj_filename, 'rb') as file:
                 skypost = pickle.load(file)
-            skypost.multiprocess = jobs
+            skypost.jobs = jobs
+            distance = isinstance(skypost, kde.Clustered2Plus1DSkyKDE)
 
         logger.info('Making skymap')
         hpmap = skypost.as_healpix()
@@ -844,12 +823,12 @@ class CompactBinaryCoalescenceResult(CoreResult):
             cb.set_label(r'prob. per deg$^2$')
 
         if contour is not None:
-            cls = 100 * postprocess.find_greedy_credible_levels(skymap)
-            cs = ax.contour_hpx(
-                (cls, 'ICRS'), nested=metadata['nest'],
+            confidence_levels = 100 * postprocess.find_greedy_credible_levels(skymap)
+            contours = ax.contour_hpx(
+                (confidence_levels, 'ICRS'), nested=metadata['nest'],
                 colors='k', linewidths=0.5, levels=contour)
             fmt = r'%g\%%' if rcParams['text.usetex'] else '%g%%'
-            plt.clabel(cs, fmt=fmt, fontsize=6, inline=True)
+            plt.clabel(contours, fmt=fmt, fontsize=6, inline=True)
 
         # Add continents.
         if geo:
@@ -875,7 +854,7 @@ class CompactBinaryCoalescenceResult(CoreResult):
                 text.append('event ID: {}'.format(objid))
             if contour:
                 pp = np.round(contour).astype(int)
-                ii = np.round(np.searchsorted(np.sort(cls), contour) *
+                ii = np.round(np.searchsorted(np.sort(confidence_levels), contour) *
                               deg2perpix).astype(int)
                 for i, p in zip(ii, pp):
                     text.append(
@@ -884,15 +863,7 @@ class CompactBinaryCoalescenceResult(CoreResult):
 
         filename = os.path.join(self.outdir, "{}_skymap.png".format(self.label))
         logger.info("Generating 2D projected skymap to {}".format(filename))
-        plt.savefig(filename, dpi=500)
-
-
-class CompactBinaryCoalesenceResult(CompactBinaryCoalescenceResult):
-
-    def __init__(self, **kwargs):
-        logger.warning('CompactBinaryCoalesenceResult is deprecated use '
-                       'CompactBinaryCoalescenceResult')
-        super(CompactBinaryCoalesenceResult, self).__init__(**kwargs)
+        safe_save_figure(fig=plt.gcf(), filename=filename, dpi=dpi)
 
 
 CBCResult = CompactBinaryCoalescenceResult
diff --git a/setup.cfg b/setup.cfg
index fad8e426f..4a3da3b80 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -9,6 +9,7 @@ addopts =
     --ignore test/gw_example_test.py
     --ignore test/example_test.py
     --ignore test/sample_from_the_prior_test.py
+    --ignore test/gw_plot_test.py
     --ignore test/sampler_test.py
 
 [metadata]
diff --git a/test/gw_plot_test.py b/test/gw_plot_test.py
new file mode 100644
index 000000000..b440f2eb8
--- /dev/null
+++ b/test/gw_plot_test.py
@@ -0,0 +1,127 @@
+import os
+import shutil
+import unittest
+
+import pandas as pd
+
+import bilby
+
+
+class TestCBCResult(unittest.TestCase):
+
+    def setUp(self):
+        bilby.utils.command_line_args.bilby_test_mode = False
+        priors = bilby.gw.prior.BBHPriorDict()
+        priors['geocent_time'] = 2
+        injection_parameters = priors.sample()
+        self.meta_data = dict(
+            likelihood=dict(
+                phase_marginalization=True,
+                distance_marginalization=False,
+                time_marginalization=True,
+                frequency_domain_source_model=bilby.gw.source.lal_binary_black_hole,
+                waveform_arguments=dict(
+                    reference_frequency=20.0,
+                    waveform_approximant='IMRPhenomPv2'),
+                interferometers=dict(
+                    H1=dict(optimal_SNR=1, parameters=injection_parameters),
+                    L1=dict(optimal_SNR=1, parameters=injection_parameters)),
+                sampling_frequency=4096,
+                duration=4,
+                start_time=0,
+                waveform_generator_class=bilby.gw.waveform_generator.WaveformGenerator,
+                parameter_conversion=bilby.gw.conversion.convert_to_lal_binary_black_hole_parameters,
+            )
+        )
+        self.result = bilby.gw.result.CBCResult(
+            label='label', outdir='outdir', sampler='nestle',
+            search_parameter_keys=list(priors.keys()), fixed_parameter_keys=list(),
+            priors=priors, sampler_kwargs=dict(test='test', func=lambda x: x),
+            injection_parameters=injection_parameters,
+            meta_data=self.meta_data,
+            posterior=pd.DataFrame(priors.sample(100))
+        )
+        if not os.path.isdir(self.result.outdir):
+            os.mkdir(self.result.outdir)
+        pass
+
+    def tearDown(self):
+        bilby.utils.command_line_args.bilby_test_mode = True
+        try:
+            shutil.rmtree(self.result.outdir)
+        except OSError:
+            pass
+        del self.result
+        pass
+
+    def test_calibration_plot(self):
+        calibration_prior = bilby.gw.prior.CalibrationPriorDict.constant_uncertainty_spline(
+            amplitude_sigma=0.1,
+            phase_sigma=0.1,
+            minimum_frequency=20,
+            maximum_frequency=2048,
+            label="recalib_H1_",
+            n_nodes=5,
+        )
+        calibration_filename = f"{self.result.outdir}/{self.result.label}_calibration.png"
+        for key in calibration_prior:
+            self.result.posterior[key] = calibration_prior[key].sample(100)
+        self.result.plot_calibration_posterior()
+        self.assertTrue(os.path.exists(calibration_filename))
+
+    def test_calibration_plot_returns_none_with_no_calibration_parameters(self):
+        self.assertIsNone(self.result.plot_calibration_posterior())
+        calibration_filename = f"{self.result.outdir}/{self.result.label}_calibration.png"
+        self.assertFalse(os.path.exists(calibration_filename))
+
+    def test_calibration_pdf_plot(self):
+        calibration_prior = bilby.gw.prior.CalibrationPriorDict.constant_uncertainty_spline(
+            amplitude_sigma=0.1,
+            phase_sigma=0.1,
+            minimum_frequency=20,
+            maximum_frequency=2048,
+            label="recalib_H1_",
+            n_nodes=5,
+        )
+        calibration_filename = f"{self.result.outdir}/{self.result.label}_calibration.pdf"
+        for key in calibration_prior:
+            self.result.posterior[key] = calibration_prior[key].sample(100)
+        self.result.plot_calibration_posterior(format="pdf")
+        self.assertTrue(os.path.exists(calibration_filename))
+
+    def test_calibration_invalid_format_raises_error(self):
+        with self.assertRaises(ValueError):
+            self.result.plot_calibration_posterior(format="bilby")
+
+    def test_waveform_plotting_png(self):
+        self.result.plot_waveform_posterior(n_samples=200)
+        for ifo in self.result.interferometers:
+            self.assertTrue(os.path.exists(
+                f"{self.result.outdir}/{self.result.label}_{ifo}_waveform.png")
+            )
+
+    def test_plot_skymap_meta_data(self):
+        from ligo.skymap import io
+        expected_keys = {
+            "HISTORY", "build_date", "creator", "distmean", "diststd",
+            "gps_creation_time", "gps_time", "nest", "objid", "origin",
+            "vcs_revision", "vcs_version", "instruments"
+        }
+        self.result.plot_skymap(
+            maxpts=50, geo=False, objid="test", instruments="H1L1"
+        )
+        fits_filename = f"{self.result.outdir}/{self.result.label}_skymap.fits"
+        skymap_filename = f"{self.result.outdir}/{self.result.label}_skymap.png"
+        pickle_filename = f"{self.result.outdir}/{self.result.label}_skypost.obj"
+        hpmap, meta = io.read_sky_map(fits_filename)
+        self.assertEqual(expected_keys, set(meta.keys()))
+        self.assertTrue(os.path.exists(skymap_filename))
+        self.assertTrue(os.path.exists(pickle_filename))
+        self.result.plot_skymap(
+            maxpts=50, geo=False, objid="test", instruments="H1L1",
+            load_pickle=True, colorbar=True
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/gw_result_test.py b/test/gw_result_test.py
index 82c0c7963..4c6c64fac 100644
--- a/test/gw_result_test.py
+++ b/test/gw_result_test.py
@@ -1,36 +1,48 @@
-from __future__ import absolute_import, division
+import os
+import shutil
+import unittest
+
+import pandas as pd
 
 import bilby
-import unittest
-import shutil
 
 
 class TestCBCResult(unittest.TestCase):
 
     def setUp(self):
         bilby.utils.command_line_args.bilby_test_mode = False
-        priors = bilby.prior.PriorDict(dict(
-            x=bilby.prior.Uniform(0, 1, 'x', latex_label='$x$', unit='s'),
-            y=bilby.prior.Uniform(0, 1, 'y', latex_label='$y$', unit='m'),
-            c=1,
-            d=2))
+        priors = bilby.gw.prior.BBHPriorDict()
+        priors['geocent_time'] = 2
+        injection_parameters = priors.sample()
         self.meta_data = dict(
             likelihood=dict(
-                phase_marginalization=True, distance_marginalization=False,
+                phase_marginalization=True,
+                distance_marginalization=False,
                 time_marginalization=True,
                 frequency_domain_source_model=bilby.gw.source.lal_binary_black_hole,
                 waveform_arguments=dict(
                     reference_frequency=20.0,
                     waveform_approximant='IMRPhenomPv2'),
                 interferometers=dict(
-                    H1=dict(optimal_SNR=1, parameters=dict(x=0.1, y=0.3)),
-                    L1=dict(optimal_SNR=1, parameters=dict(x=0.1, y=0.3)))))
+                    H1=dict(optimal_SNR=1, parameters=injection_parameters),
+                    L1=dict(optimal_SNR=1, parameters=injection_parameters)),
+                sampling_frequency=4096,
+                duration=4,
+                start_time=0,
+                waveform_generator_class=bilby.gw.waveform_generator.WaveformGenerator,
+                parameter_conversion=bilby.gw.conversion.convert_to_lal_binary_black_hole_parameters,
+            )
+        )
         self.result = bilby.gw.result.CBCResult(
             label='label', outdir='outdir', sampler='nestle',
-            search_parameter_keys=['x', 'y'], fixed_parameter_keys=['c', 'd'],
+            search_parameter_keys=list(priors.keys()), fixed_parameter_keys=list(),
             priors=priors, sampler_kwargs=dict(test='test', func=lambda x: x),
-            injection_parameters=dict(x=0.5, y=0.5),
-            meta_data=self.meta_data)
+            injection_parameters=injection_parameters,
+            meta_data=self.meta_data,
+            posterior=pd.DataFrame(priors.sample(100))
+        )
+        if not os.path.isdir(self.result.outdir):
+            os.mkdir(self.result.outdir)
         pass
 
     def tearDown(self):
@@ -82,6 +94,36 @@ class TestCBCResult(unittest.TestCase):
         with self.assertRaises(AttributeError):
             self.result.reference_frequency
 
+    def test_sampling_frequency(self):
+        self.assertEqual(
+            self.result.sampling_frequency,
+            self.meta_data['likelihood']['sampling_frequency'])
+
+    def test_sampling_frequency_unset(self):
+        self.result.meta_data['likelihood'].pop('sampling_frequency')
+        with self.assertRaises(AttributeError):
+            self.result.sampling_frequency
+
+    def test_duration(self):
+        self.assertEqual(
+            self.result.duration,
+            self.meta_data['likelihood']['duration'])
+
+    def test_duration_unset(self):
+        self.result.meta_data['likelihood'].pop('duration')
+        with self.assertRaises(AttributeError):
+            self.result.duration
+
+    def test_start_time(self):
+        self.assertEqual(
+            self.result.start_time,
+            self.meta_data['likelihood']['start_time'])
+
+    def test_start_time_unset(self):
+        self.result.meta_data['likelihood'].pop('start_time')
+        with self.assertRaises(AttributeError):
+            self.result.start_time
+
     def test_waveform_approximant(self):
         self.assertEqual(
             self.result.waveform_approximant,
@@ -107,6 +149,26 @@ class TestCBCResult(unittest.TestCase):
         with self.assertRaises(AttributeError):
             self.result.frequency_domain_source_model
 
+    def test_parameter_conversion(self):
+        self.assertEqual(
+            self.result.parameter_conversion,
+            self.meta_data['likelihood']['parameter_conversion'])
+
+    def test_parameter_conversion_unset(self):
+        self.result.meta_data['likelihood'].pop('parameter_conversion')
+        with self.assertRaises(AttributeError):
+            self.result.parameter_conversion
+
+    def test_waveform_generator_class(self):
+        self.assertEqual(
+            self.result.waveform_generator_class,
+            self.meta_data['likelihood']['waveform_generator_class'])
+
+    def test_waveform_generator_class_unset(self):
+        self.result.meta_data['likelihood'].pop('waveform_generator_class')
+        with self.assertRaises(AttributeError):
+            self.result.waveform_generator_class
+
     def test_interferometer_names(self):
         self.assertEqual(
             self.result.interferometers,
-- 
GitLab