diff --git a/bilby/gw/likelihood/__init__.py b/bilby/gw/likelihood/__init__.py
index 88fb527ad579513e39c4cda69e0d20a9c1d38e84..2f7eb2aafc02b6919b6c80cf50623b11ded46ba8 100644
--- a/bilby/gw/likelihood/__init__.py
+++ b/bilby/gw/likelihood/__init__.py
@@ -2,6 +2,7 @@ from .base import GravitationalWaveTransient
 from .basic import BasicGravitationalWaveTransient
 from .roq import BilbyROQParamsRangeError, ROQGravitationalWaveTransient
 from .multiband import MBGravitationalWaveTransient
+from .relbin import RelativeBinningGravitationalWaveTransient
 
 from ..source import lal_binary_black_hole
 from ..waveform_generator import WaveformGenerator
diff --git a/bilby/gw/likelihood/relbin.py b/bilby/gw/likelihood/relbin.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c0892f0cff1a8e1fce926a8b355f3245eb698e5
--- /dev/null
+++ b/bilby/gw/likelihood/relbin.py
@@ -0,0 +1,626 @@
+import numpy as np
+from .base import GravitationalWaveTransient
+from ...core.utils import logger, create_time_series
+from ...core.prior.base import Constraint
+from ...core.prior import Interped, DeltaFunction
+from ..utils import noise_weighted_inner_product, ln_i0
+from scipy.optimize import differential_evolution
+import copy
+
+
+class RelativeBinningGravitationalWaveTransient(GravitationalWaveTransient):
+    """A gravitational-wave transient likelihood object which uses the relative
+    binning procedure to calculate a fast likelihood. See IAS paper:
+
+
+    Parameters
+    ----------
+    interferometers: list, bilby.gw.detector.InterferometerList
+        A list of `bilby.detector.Interferometer` instances - contains the
+        detector data and power spectral densities
+    waveform_generator: `bilby.waveform_generator.WaveformGenerator`
+        An object which computes the frequency-domain strain of the signal,
+        given some set of parameters
+    fiducial_parameters: dict, optional
+        A starting guess for initial parameters of the event for finding the
+        maximum likelihood (fiducial) waveform.
+    parameter_bounds: dict, optional
+        Dictionary of bounds (lists) for the initial parameters when finding
+        the initial maximum likelihood (fiducial) waveform.
+    distance_marginalization: bool, optional
+        If true, marginalize over distance in the likelihood.
+        This uses a look up table calculated at run time.
+        The distance prior is set to be a delta function at the minimum
+        distance allowed in the prior being marginalised over.
+    time_marginalization: bool, optional
+        If true, marginalize over time in the likelihood.
+        This uses a FFT to calculate the likelihood over a regularly spaced
+        grid.
+        In order to cover the whole space the prior is set to be uniform over
+        the spacing of the array of times.
+        If using time marginalisation and jitter_time is True a "jitter"
+        parameter is added to the prior which modifies the position of the
+        grid of times.
+    phase_marginalization: bool, optional
+        If true, marginalize over phase in the likelihood.
+        This is done analytically using a Bessel function.
+        The phase prior is set to be a delta function at phase=0.
+    priors: dict, optional
+        If given, used in the distance and phase marginalization.
+    distance_marginalization_lookup_table: (dict, str), optional
+        If a dict, dictionary containing the lookup_table, distance_array,
+        (distance) prior_array, and reference_distance used to construct
+        the table.
+        If a string the name of a file containing these quantities.
+        The lookup table is stored after construction in either the
+        provided string or a default location:
+        '.distance_marginalization_lookup_dmin{}_dmax{}_n{}.npz'
+    jitter_time: bool, optional
+        Whether to introduce a `time_jitter` parameter. This avoids either
+        missing the likelihood peak, or introducing biases in the
+        reconstructed time posterior due to an insufficient sampling frequency.
+        Default is False, however using this parameter is strongly encouraged.
+    reference_frame: (str, bilby.gw.detector.InterferometerList, list), optional
+        Definition of the reference frame for the sky location.
+        - "sky": sample in RA/dec, this is the default
+        - e.g., "H1L1", ["H1", "L1"], InterferometerList(["H1", "L1"]):
+          sample in azimuth and zenith, `azimuth` and `zenith` defined in the
+          frame where the z-axis is aligned the the vector connecting H1
+          and L1.
+    time_reference: str, optional
+        Name of the reference for the sampled time parameter.
+        - "geocent"/"geocenter": sample in the time at the Earth's center,
+          this is the default
+        - e.g., "H1": sample in the time of arrival at H1
+    chi: float, optional
+        Tunable parameter which limits the perturbation of alpha when setting
+        up the bin range. See https://arxiv.org/abs/1806.08792.
+    epsilon: float, optional
+        Tunable parameter which limits the differential phase change in each
+        bin when setting up the bin range. See https://arxiv.org/abs/1806.08792.
+
+    Returns
+    -------
+    Likelihood: `bilby.core.likelihood.Likelihood`
+        A likelihood object, able to compute the likelihood of the data given
+        some model parameters.
+    """
+
+    def __init__(self, interferometers,
+                 waveform_generator,
+                 fiducial_parameters={}, parameter_bounds=None,
+                 maximization_kwargs=dict(),
+                 update_fiducial_parameters=False,
+                 distance_marginalization=False,
+                 time_marginalization=False,
+                 phase_marginalization=False,
+                 priors=None,
+                 distance_marginalization_lookup_table=None,
+                 jitter_time=True,
+                 reference_frame="sky",
+                 time_reference="geocenter",
+                 chi=1,
+                 epsilon=0.5):
+
+        super(RelativeBinningGravitationalWaveTransient, self).__init__(
+            interferometers=interferometers,
+            waveform_generator=waveform_generator,
+            distance_marginalization=distance_marginalization,
+            phase_marginalization=phase_marginalization,
+            time_marginalization=time_marginalization,
+            priors=priors,
+            distance_marginalization_lookup_table=distance_marginalization_lookup_table,
+            jitter_time=jitter_time,
+            reference_frame=reference_frame,
+            time_reference=time_reference)
+
+        self.fiducial_parameters = fiducial_parameters
+        self.chi = chi
+        self.epsilon = epsilon
+        self.gamma = np.array([-5 / 3, -2 / 3, 1, 5 / 3, 7 / 3])
+        self.fiducial_waveform_obtained = False
+        self.check_if_bins_are_setup = False
+        self.fiducial_polarizations = None
+        self.per_detector_fiducial_waveforms = {}
+        self.bin_freqs = dict()
+        self.bin_inds = dict()
+        self.set_fiducial_waveforms(self.fiducial_parameters)
+        logger.info("Initial fiducial waveforms set up")
+        self.setup_bins()
+        self.compute_summary_data()
+        logger.info("Summary Data Obtained")
+
+        if update_fiducial_parameters:
+            # write a check to make sure prior is not None
+            logger.info("Using scipy optimization to find maximum likelihood parameters.")
+            self.parameters_to_be_updated = [key for key in self.priors if not isinstance(
+                self.priors[key], (DeltaFunction, Constraint))]
+            logger.info("Parameters over which likelihood is maximized: {}".format(self.parameters_to_be_updated))
+            if parameter_bounds is None:
+                logger.info("No parameter bounds were given. Using priors instead.")
+                self.parameter_bounds = self.get_bounds_from_priors(self.priors)
+            else:
+                self.parameter_bounds = self.get_parameter_list_from_dictionary(parameter_bounds)
+            self.fiducial_parameters = self.find_maximum_likelihood_parameters(
+                self.parameter_bounds, maximization_kwargs=maximization_kwargs)
+
+    def __repr__(self):
+        return self.__class__.__name__ + '(interferometers={},\n\twaveform_generator={},\n\fiducial_parameters={},' \
+            .format(self.interferometers, self.waveform_generator, self.fiducial_parameters)
+
+    def setup_bins(self):
+        frequency_array = self.waveform_generator.frequency_array
+        gamma = self.gamma
+        for interferometer in self.interferometers:
+            frequency_array_useful = frequency_array[np.intersect1d(
+                np.where(frequency_array >= interferometer.minimum_frequency),
+                np.where(frequency_array <= interferometer.maximum_frequency))]
+
+            d_alpha = self.chi * 2 * np.pi / np.abs(
+                (interferometer.minimum_frequency ** gamma) * np.heaviside(
+                    -gamma, 1) - (interferometer.maximum_frequency ** gamma) * np.heaviside(
+                    gamma, 1))
+            d_phi = np.sum(np.array([np.sign(gamma[i]) * d_alpha[i] * (
+                frequency_array_useful ** gamma[i]) for i in range(len(gamma))]), axis=0)
+            d_phi_from_start = d_phi - d_phi[0]
+            self.number_of_bins = int(d_phi_from_start[-1] // self.epsilon)
+            self.bin_freqs[interferometer.name] = np.zeros(self.number_of_bins + 1)
+            self.bin_inds[interferometer.name] = np.zeros(self.number_of_bins + 1, dtype=np.int)
+
+            for i in range(self.number_of_bins + 1):
+                bin_index = np.where(d_phi_from_start >= ((i / self.number_of_bins) * d_phi_from_start[-1]))[0][0]
+                bin_freq = frequency_array_useful[bin_index]
+                self.bin_freqs[interferometer.name][i] = bin_freq
+                self.bin_inds[interferometer.name][i] = np.where(frequency_array >= bin_freq)[0][0]
+
+            logger.info("Set up {} bins for {} between {} Hz and {} Hz".format(
+                self.number_of_bins, interferometer.name, interferometer.minimum_frequency,
+                interferometer.maximum_frequency))
+            self.waveform_generator.waveform_arguments["frequency_bin_edges"] = self.bin_freqs[interferometer.name]
+        return
+
+    def set_fiducial_waveforms(self, parameters):
+        parameters["fiducial"] = 1
+        self.fiducial_polarizations = self.waveform_generator.frequency_domain_strain(
+            parameters)
+
+        maximum_nonzero_index = np.where(self.fiducial_polarizations["plus"] != 0j)[0][-1]
+        logger.info("Maximum Nonzero Index is {}".format(maximum_nonzero_index))
+        maximum_nonzero_frequency = self.waveform_generator.frequency_array[maximum_nonzero_index]
+        logger.info("Maximum Nonzero Frequency is {}".format(maximum_nonzero_frequency))
+
+        if self.fiducial_polarizations is None:
+            return np.nan_to_num(-np.inf)
+
+        for interferometer in self.interferometers:
+            logger.info("Maximum Frequency is {}".format(interferometer.maximum_frequency))
+            if interferometer.maximum_frequency > maximum_nonzero_frequency:
+                interferometer.maximum_frequency = maximum_nonzero_frequency
+
+            self.per_detector_fiducial_waveforms[interferometer.name] = (
+                interferometer.get_detector_response(
+                    self.fiducial_polarizations, parameters))
+        parameters["fiducial"] = 0
+        return
+
+    def log_likelihood(self):
+        return self.log_likelihood_ratio() + self.noise_log_likelihood()
+
+    def log_likelihood_ratio(self):
+        self.parameters.update(self.get_sky_frame_parameters())
+
+        d_inner_h = 0.
+        optimal_snr_squared = 0.
+        complex_matched_filter_snr = 0.
+        waveform_ratio = self.compute_waveform_ratio(self.parameters)
+
+        if self.time_marginalization:
+            if self.jitter_time:
+                self.parameters['geocent_time'] += self.parameters['time_jitter']
+            d_inner_h_tc_array = np.zeros(
+                self.interferometers.frequency_array[0:-1].shape,
+                dtype=np.complex128)
+
+        for interferometer in self.interferometers:
+            per_detector_snr = self.calculate_snrs_relative_binning(
+                waveform_ratio[interferometer.name], interferometer)
+            d_inner_h += per_detector_snr.d_inner_h
+            optimal_snr_squared += np.real(per_detector_snr.optimal_snr_squared)
+            complex_matched_filter_snr += per_detector_snr.complex_matched_filter_snr
+
+            if self.time_marginalization:
+                d_inner_h_tc_array += per_detector_snr.d_inner_h_squared_tc_array
+
+        if self.time_marginalization:
+            log_l = self.time_marginalized_likelihood(
+                d_inner_h_tc_array=d_inner_h_tc_array,
+                h_inner_h=optimal_snr_squared)
+            if self.jitter_time:
+                self.parameters['geocent_time'] -= self.parameters['time_jitter']
+
+        elif self.distance_marginalization:
+            log_l = self.distance_marginalized_likelihood(
+                d_inner_h=d_inner_h, h_inner_h=optimal_snr_squared)
+
+        elif self.phase_marginalization:
+            log_l = self.phase_marginalized_likelihood(
+                d_inner_h=d_inner_h, h_inner_h=optimal_snr_squared)
+
+        else:
+            log_l = np.real(d_inner_h) - optimal_snr_squared / 2
+
+        return float(log_l.real)
+
+    def find_maximum_likelihood_parameters(self, parameter_bounds,
+                                           iterations=1, maximization_kwargs=dict()):
+
+        for i in range(iterations):
+            logger.info("Optimizing fiducial parameters. Iteration : {}".format(i + 1))
+            output = differential_evolution(self.lnlike_scipy_maximize,
+                                            bounds=parameter_bounds, **maximization_kwargs)
+            updated_parameters_list = output['x']
+            updated_parameters = self.get_parameter_dictionary_from_list(updated_parameters_list)
+            self.set_fiducial_waveforms(updated_parameters)
+            self.compute_summary_data()
+
+        logger.info("Fiducial waveforms updated")
+        logger.info("Summary Data updated")
+        return updated_parameters
+
+    def lnlike_scipy_maximize(self, parameter_list):
+        self.parameters = self.get_parameter_dictionary_from_list(parameter_list)
+        return -self.log_likelihood_ratio()
+
+    def get_parameter_dictionary_from_list(self, parameter_list):
+        parameter_dictionary = dict(zip(self.parameters_to_be_updated, parameter_list))
+        excluded_parameter_keys = set(self.fiducial_parameters) - set(self.parameters_to_be_updated)
+        for key in excluded_parameter_keys:
+            parameter_dictionary[key] = self.fiducial_parameters[key]
+        return parameter_dictionary
+
+    def get_parameter_list_from_dictionary(self, parameter_dict):
+        return [parameter_dict[k] for k in self.parameters_to_be_updated]
+
+    def get_bounds_from_priors(self, priors):
+        bounds = []
+        for key in self.parameters_to_be_updated:
+            bounds.append([priors[key].minimum, priors[key].maximum])
+        return bounds
+
+    def compute_summary_data(self):
+        summary_data = dict()
+
+        for interferometer in self.interferometers:
+            mask = interferometer.frequency_mask
+            masked_frequency_array = interferometer.frequency_array[mask]
+            masked_bin_inds = []
+            for edge in self.bin_freqs[interferometer.name]:
+                index = np.where(masked_frequency_array == edge)[0][0]
+                masked_bin_inds.append(index)
+            masked_strain = interferometer.frequency_domain_strain[mask]
+            masked_h0 = self.per_detector_fiducial_waveforms[interferometer.name][mask]
+            masked_psd = interferometer.power_spectral_density_array[mask]
+            a0, b0, a1, b1 = np.zeros((4, self.number_of_bins), dtype=np.complex)
+
+            for i in range(self.number_of_bins):
+
+                central_frequency_i = 0.5 * \
+                    (masked_frequency_array[masked_bin_inds[i]] + masked_frequency_array[masked_bin_inds[i + 1]])
+                masked_strain_i = masked_strain[masked_bin_inds[i]:masked_bin_inds[i + 1]]
+                masked_h0_i = masked_h0[masked_bin_inds[i]:masked_bin_inds[i + 1]]
+                masked_psd_i = masked_psd[masked_bin_inds[i]:masked_bin_inds[i + 1]]
+                masked_frequency_i = masked_frequency_array[masked_bin_inds[i]:masked_bin_inds[i + 1]]
+
+                a0[i] = noise_weighted_inner_product(
+                    masked_h0_i,
+                    masked_strain_i,
+                    masked_psd_i,
+                    self.waveform_generator.duration)
+
+                b0[i] = noise_weighted_inner_product(
+                    masked_h0_i,
+                    masked_h0_i,
+                    masked_psd_i,
+                    self.waveform_generator.duration)
+
+                a1[i] = noise_weighted_inner_product(
+                    masked_h0_i,
+                    masked_strain_i * (masked_frequency_i - central_frequency_i),
+                    masked_psd_i,
+                    self.waveform_generator.duration)
+
+                b1[i] = noise_weighted_inner_product(
+                    masked_h0_i,
+                    masked_h0_i * (masked_frequency_i - central_frequency_i),
+                    masked_psd_i,
+                    self.waveform_generator.duration)
+
+            summary_data[interferometer.name] = dict(a0=a0, a1=a1, b0=b0, b1=b1)
+
+        self.summary_data = summary_data
+
+    def compute_waveform_ratio(self, parameters):
+        waveform_ratio = dict()
+        self.waveform_generator.parameters = parameters
+        new_polarizations = self.waveform_generator.frequency_domain_strain(parameters)
+        for interferometer in self.interferometers:
+            h = interferometer.get_detector_response_relative_binning(
+                new_polarizations, parameters, self.bin_freqs[interferometer.name])
+            h0 = self.per_detector_fiducial_waveforms[interferometer.name][self.bin_inds[interferometer.name]]
+            waveform_ratio_per_detector = h / h0
+
+            r0 = (waveform_ratio_per_detector[1:] + waveform_ratio_per_detector[:-1]) / 2
+            r1 = (waveform_ratio_per_detector[1:] - waveform_ratio_per_detector[:-1]) / (
+                self.bin_freqs[interferometer.name][1:] - self.bin_freqs[interferometer.name][:-1])
+
+            waveform_ratio[interferometer.name] = [r0, r1]
+
+        return waveform_ratio
+
+    def calculate_snrs_relative_binning(self, waveform_ratio_per_detector, interferometer):
+        summary_data_per_interferometer = self.summary_data[interferometer.name]
+        a0 = summary_data_per_interferometer["a0"]
+        a1 = summary_data_per_interferometer["a1"]
+        b0 = summary_data_per_interferometer["b0"]
+        b1 = summary_data_per_interferometer["b1"]
+
+        r0, r1 = waveform_ratio_per_detector
+
+        d_inner_h = np.sum(a0 * np.conjugate(r0) + a1 * np.conjugate(r1))
+        h_inner_h = np.sum(b0 * np.abs(r0) ** 2 + 2 * b1 * np.real(
+            r0 * np.conjugate(r1)))
+        optimal_snr_squared = h_inner_h
+        complex_matched_filter_snr = d_inner_h / (optimal_snr_squared ** 0.5)
+
+        if self.time_marginalization:
+            f = interferometer.frequency_array
+            duplicated_r0, duplicated_r1, duplicated_fm = np.zeros((3, f.shape[0]), dtype=np.complex)
+
+            for i in range(self.number_of_bins):
+                ind = self.bin_inds[interferometer.name]
+                fm = 0.5 * (self.bin_freqs[interferometer.name][1:] + self.bin_freqs[interferometer.name][:-1])
+                duplicated_fm[ind[i]:ind[i + 1]] = fm[i]
+                duplicated_r0[ind[i]:ind[i + 1]] = r0[i]
+                duplicated_r1[ind[i]:ind[i + 1]] = r1[i]
+
+            duplicated_r = duplicated_r0 + duplicated_r1 * (f - duplicated_fm)
+
+            d_inner_h_squared_tc_array = 4 / self.waveform_generator.duration * np.fft.fft(
+                self.per_detector_fiducial_waveforms[interferometer.name][0:-1] *
+                interferometer.frequency_domain_strain.conjugate()[0:-1] *
+                duplicated_r[0:-1] / interferometer.power_spectral_density_array[0:-1])
+
+        else:
+            d_inner_h_squared_tc_array = None
+
+        return self._CalculatedSNRs(
+            d_inner_h=d_inner_h, optimal_snr_squared=optimal_snr_squared,
+            complex_matched_filter_snr=complex_matched_filter_snr,
+            d_inner_h_array=None, optimal_snr_squared_array=None,
+            d_inner_h_squared_tc_array=d_inner_h_squared_tc_array)
+
+    def generate_posterior_sample_from_marginalized_likelihood(self):
+        """
+        Reconstruct the distance posterior from a run which used a likelihood
+        which explicitly marginalised over time/distance/phase.
+
+        See Eq. (C29-C32) of https://arxiv.org/abs/1809.02293
+
+        Return
+        ------
+        sample: dict
+            Returns the parameters with new samples.
+
+        Notes
+        -----
+        This involves a deepcopy of the signal to avoid issues with waveform
+        caching, as the signal is overwritten in place.
+        """
+        if any([self.phase_marginalization, self.distance_marginalization,
+                self.time_marginalization]):
+            waveform_ratio = copy.deepcopy(
+                self.compute_waveform_ratio(self.parameters))
+        else:
+            return self.parameters
+        if self.time_marginalization:
+            new_time = self.generate_time_sample_from_marginalized_likelihood(
+                waveform_ratio=waveform_ratio)
+            self.parameters['geocent_time'] = new_time
+        if self.distance_marginalization:
+            new_distance = self.generate_distance_sample_from_marginalized_likelihood(
+                waveform_ratio=waveform_ratio)
+            self.parameters['luminosity_distance'] = new_distance
+        if self.phase_marginalization:
+            new_phase = self.generate_phase_sample_from_marginalized_likelihood(
+                waveform_ratio=waveform_ratio)
+            self.parameters['phase'] = new_phase
+        return self.parameters.copy()
+
+    def generate_time_sample_from_marginalized_likelihood(
+            self, waveform_ratio=None):
+        """
+        Generate a single sample from the posterior distribution for coalescence
+        time when using a likelihood which explicitly marginalises over time.
+
+        In order to resolve the posterior we artifically upsample to 16kHz.
+
+        See Eq. (C29-C32) of https://arxiv.org/abs/1809.02293
+
+        Parameters
+        ----------
+        waveform_ratio: dict, optional
+            contains waveform ratios for all interferometers.
+
+        Returns
+        -------
+        new_time: float
+            Sample from the time posterior.
+        """
+        self.parameters.update(self.get_sky_frame_parameters())
+        if self.jitter_time:
+            self.parameters['geocent_time'] += self.parameters['time_jitter']
+        if waveform_ratio is None:
+            waveform_ratio = \
+                self.compute_waveform_ratio(self.parameters)
+
+        times = create_time_series(
+            sampling_frequency=16384,
+            starting_time=self.parameters['geocent_time'] - self.waveform_generator.start_time,
+            duration=self.waveform_generator.duration)
+        times = times % self.waveform_generator.duration
+        times += self.waveform_generator.start_time
+
+        prior = self.priors["geocent_time"]
+        in_prior = (times >= prior.minimum) & (times < prior.maximum)
+        times = times[in_prior]
+
+        n_time_steps = int(self.waveform_generator.duration * 16384)
+        d_inner_h = np.zeros(len(times), dtype=np.complex)
+        psd = np.ones(n_time_steps)
+        signal_long = np.zeros(n_time_steps, dtype=np.complex)
+        data = np.zeros(n_time_steps, dtype=np.complex)
+        h_inner_h = np.zeros(1)
+        for ifo in self.interferometers:
+            f = ifo.frequency_array
+            ifo_length = len(f)
+            mask = ifo.frequency_mask
+            r0, r1 = waveform_ratio[ifo.name]
+            duplicated_r0, duplicated_r1, duplicated_fm = np.zeros((3, ifo_length), dtype=np.complex)
+            for i in range(self.number_of_bins):
+                ind = self.bin_inds[ifo.name]
+                fm = 0.5 * (self.bin_freqs[ifo.name][1:] + self.bin_freqs[ifo.name][:-1])
+                duplicated_fm[ind[i]:ind[i + 1]] = fm[i]
+                duplicated_r0[ind[i]:ind[i + 1]] = r0[i]
+                duplicated_r1[ind[i]:ind[i + 1]] = r1[i]
+            duplicated_r = duplicated_r0 + duplicated_r1 * (f - duplicated_fm)
+            signal = duplicated_r * self.per_detector_fiducial_waveforms[ifo.name]
+            signal_long[:ifo_length] = signal
+            data[:ifo_length] = np.conj(ifo.frequency_domain_strain)
+            psd[:ifo_length][mask] = ifo.power_spectral_density_array[mask]
+            d_inner_h += np.fft.fft(signal_long * data / psd)[in_prior]
+            h_inner_h += ifo.optimal_snr_squared(signal=signal).real
+
+        if self.distance_marginalization:
+            time_log_like = self.distance_marginalized_likelihood(
+                d_inner_h, h_inner_h)
+        elif self.phase_marginalization:
+            time_log_like = (ln_i0(abs(d_inner_h)) -
+                             h_inner_h.real / 2)
+        else:
+            time_log_like = (d_inner_h.real - h_inner_h.real / 2)
+
+        time_prior_array = self.priors['geocent_time'].prob(times)
+        time_post = (
+            np.exp(time_log_like - max(time_log_like)) * time_prior_array)
+
+        keep = (time_post > max(time_post) / 1000)
+        if sum(keep) < 3:
+            keep[1:-1] = keep[1:-1] | keep[2:] | keep[:-2]
+        time_post = time_post[keep]
+        times = times[keep]
+
+        new_time = Interped(times, time_post).sample()
+        return new_time
+
+    def generate_distance_sample_from_marginalized_likelihood(
+            self, waveform_ratio=None):
+        """
+        Generate a single sample from the posterior distribution for luminosity
+        distance when using a likelihood which explicitly marginalises over
+        distance.
+
+        See Eq. (C29-C32) of https://arxiv.org/abs/1809.02293
+
+        Parameters
+        ----------
+        signal_polarizations: dict, optional
+            Polarizations modes of the template.
+            Note: These are rescaled in place after the distance sample is
+                  generated to allow further parameter reconstruction to occur.
+
+        Returns
+        -------
+        new_distance: float
+            Sample from the distance posterior.
+        """
+        self.parameters.update(self.get_sky_frame_parameters())
+        if waveform_ratio is None:
+            waveform_ratio = \
+                self.compute_waveform_ratio(self.parameters)
+        d_inner_h, h_inner_h = self._calculate_inner_products(waveform_ratio)
+
+        d_inner_h_dist = (
+            d_inner_h * self.parameters['luminosity_distance'] /
+            self._distance_array)
+
+        h_inner_h_dist = (
+            h_inner_h * self.parameters['luminosity_distance']**2 /
+            self._distance_array**2)
+
+        if self.phase_marginalization:
+            distance_log_like = (
+                ln_i0(abs(d_inner_h_dist)) -
+                h_inner_h_dist.real / 2)
+        else:
+            distance_log_like = (d_inner_h_dist.real - h_inner_h_dist.real / 2)
+
+        distance_post = (np.exp(distance_log_like - max(distance_log_like)) *
+                         self.distance_prior_array)
+
+        new_distance = Interped(
+            self._distance_array, distance_post).sample()
+
+        self._rescale_signal(waveform_ratio, new_distance)
+        return new_distance
+
+    def _rescale_signal(self, waveform_ratio, new_distance):
+        for ifo in self.interferometers:
+            for i in range(2):
+                waveform_ratio[ifo.name][i] *= self._ref_dist / new_distance
+
+    def _calculate_inner_products(self, waveform_ratio):
+        d_inner_h = 0
+        h_inner_h = 0
+        for interferometer in self.interferometers:
+            waveform_ratio_per_detector = waveform_ratio[interferometer.name]
+            per_detector_snr = self.calculate_snrs_relative_binning(
+                waveform_ratio_per_detector, interferometer)
+
+            d_inner_h += per_detector_snr.d_inner_h
+            h_inner_h += per_detector_snr.optimal_snr_squared
+        return d_inner_h, h_inner_h
+
+    def generate_phase_sample_from_marginalized_likelihood(
+            self, waveform_ratio=None):
+        """
+        Generate a single sample from the posterior distribution for phase when
+        using a likelihood which explicitly marginalises over phase.
+
+        See Eq. (C29-C32) of https://arxiv.org/abs/1809.02293
+
+        Parameters
+        ----------
+        signal_polarizations: dict, optional
+            Polarizations modes of the template.
+
+        Returns
+        -------
+        new_phase: float
+            Sample from the phase posterior.
+
+        Notes
+        -----
+        This is only valid when assumes that mu(phi) \propto exp(-2i phi).
+        """
+        self.parameters.update(self.get_sky_frame_parameters())
+        if waveform_ratio is None:
+            waveform_ratio = \
+                self.compute_waveform_ratio(self.parameters)
+        d_inner_h, h_inner_h = self._calculate_inner_products(waveform_ratio)
+
+        phases = np.linspace(0, 2 * np.pi, 101)
+        phasor = np.exp(-2j * phases)
+        phase_log_post = d_inner_h * phasor - h_inner_h / 2
+        phase_post = np.exp(phase_log_post.real - max(phase_log_post.real))
+        new_phase = Interped(phases, phase_post).sample()
+        return new_phase