diff --git a/CHANGELOG.md b/CHANGELOG.md
index cdd2320735c7b47ca6c7f080a80a5b4a18f2d8eb..f34d8285fb71c74ad06d1b1e5c2e46915aefbcf7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,14 +3,31 @@
 ## Unreleased
 
 ### Added
--
+- 
 
 ### Changed
+- Make calibration work, maybe with interp1d
 -
 
 ### Removed
 -
 
+## [0.4.4] 2019-04-03
+
+### Added
+- Infrastucture for custom jump proposals (cpnest-only)
+- Evidence uncertainty estimate to cpnest
+
+### Changed
+- Bug fix to close figures after creation
+- Improved the frequency-mask to entirely remove values outside the mask rather
+  than simply set them to zero
+- Fix problem with Prior prob and ln_prob if passing multiple samples
+- Improved cpnest prior sampling
+
+### Removed
+-
+
 ## [0.4.3] 2019-03-21
 
 ### Added
diff --git a/bilby/core/prior.py b/bilby/core/prior.py
index aa3693e1642726dc7ca1c3610ae50bb1a497ce05..3108124e57e3737b43c4786cc5a482dd83ba7d74 100644
--- a/bilby/core/prior.py
+++ b/bilby/core/prior.py
@@ -268,12 +268,20 @@ class PriorDict(OrderedDict):
         """
         prob = np.product([self[key].prob(sample[key])
                            for key in sample], **kwargs)
-        if prob == 0:
-            return 0
-        elif self.evaluate_constraints(sample):
+
+        if np.all(prob == 0.):
             return prob
         else:
-            return 0
+            if isinstance(prob, float):
+                if self.evaluate_constraints(sample):
+                    return prob
+                else:
+                    return 0.
+            else:
+                constrained_prob = np.zeros_like(prob)
+                keep = np.array(self.evaluate_constraints(sample), dtype=bool)
+                constrained_prob[keep] = prob[keep]
+                return constrained_prob
 
     def ln_prob(self, sample, axis=None):
         """
@@ -293,12 +301,20 @@ class PriorDict(OrderedDict):
         """
         ln_prob = np.sum([self[key].ln_prob(sample[key])
                           for key in sample], axis=axis)
-        if np.isinf(ln_prob):
-            return ln_prob
-        elif self.evaluate_constraints(sample):
+
+        if np.all(np.isinf(ln_prob)):
             return ln_prob
         else:
-            return -np.inf
+            if isinstance(ln_prob, float):
+                if self.evaluate_constraints(sample):
+                    return ln_prob
+                else:
+                    return -np.inf
+            else:
+                constrained_ln_prob = -np.inf * np.ones_like(ln_prob)
+                keep = np.array(self.evaluate_constraints(sample), dtype=bool)
+                constrained_ln_prob[keep] = ln_prob[keep]
+                return constrained_ln_prob
 
     def rescale(self, keys, theta):
         """Rescale samples from unit cube to prior
@@ -392,7 +408,7 @@ class Prior(object):
     _default_latex_labels = dict()
 
     def __init__(self, name=None, latex_label=None, unit=None, minimum=-np.inf,
-                 maximum=np.inf):
+                 maximum=np.inf, periodic_boundary=False):
         """ Implements a Prior object
 
         Parameters
@@ -407,13 +423,15 @@ class Prior(object):
             Minimum of the domain, default=-np.inf
         maximum: float, optional
             Maximum of the domain, default=np.inf
-
+        periodic_boundary: bool, optional
+            Whether or not the boundary condition is periodic. Not available in all samplers.
         """
         self.name = name
         self.latex_label = latex_label
         self.unit = unit
         self.minimum = minimum
         self.maximum = maximum
+        self.periodic_boundary = periodic_boundary
 
     def __call__(self):
         """Overrides the __call__ special method. Calls the sample method.
@@ -617,6 +635,16 @@ class Prior(object):
     def maximum(self, maximum):
         self._maximum = maximum
 
+    @property
+    def periodic_boundary(self):
+        return self._periodic_boundary
+
+    @periodic_boundary.setter
+    def periodic_boundary(self, periodic_boundary):
+        if type(periodic_boundary) is not bool:
+            raise ValueError('{} is not a valid setting for prior boundaries'.format(periodic_boundary))
+        self._periodic_boundary = periodic_boundary
+
     @property
     def __default_latex_label(self):
         if self.name in self._default_latex_labels.keys():
@@ -694,7 +722,7 @@ class DeltaFunction(Prior):
 class PowerLaw(Prior):
 
     def __init__(self, alpha, minimum, maximum, name=None, latex_label=None,
-                 unit=None):
+                 unit=None, periodic_boundary=False):
         """Power law with bounds and alpha, spectral index
 
         Parameters
@@ -711,9 +739,12 @@ class PowerLaw(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         Prior.__init__(self, name=name, latex_label=latex_label,
-                       minimum=minimum, maximum=maximum, unit=unit)
+                       minimum=minimum, maximum=maximum, unit=unit,
+                       periodic_boundary=periodic_boundary)
         self.alpha = alpha
 
     def rescale(self, val):
@@ -774,13 +805,14 @@ class PowerLaw(Prior):
             normalising = (1 + self.alpha) / (self.maximum ** (1 + self.alpha) -
                                               self.minimum ** (1 + self.alpha))
 
-        return (self.alpha * np.nan_to_num(np.log(val)) + np.log(normalising)) + np.log(1. * self.is_in_prior_range(val))
+        return (self.alpha * np.nan_to_num(np.log(val)) + np.log(normalising)) + np.log(
+            1. * self.is_in_prior_range(val))
 
 
 class Uniform(Prior):
 
     def __init__(self, minimum, maximum, name=None, latex_label=None,
-                 unit=None):
+                 unit=None, periodic_boundary=False):
         """Uniform prior with bounds
 
         Parameters
@@ -795,9 +827,12 @@ class Uniform(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         Prior.__init__(self, name=name, latex_label=latex_label,
-                       minimum=minimum, maximum=maximum, unit=unit)
+                       minimum=minimum, maximum=maximum, unit=unit,
+                       periodic_boundary=periodic_boundary)
 
     def rescale(self, val):
         Prior.test_valid_for_rescaling(val)
@@ -835,7 +870,7 @@ class Uniform(Prior):
 class LogUniform(PowerLaw):
 
     def __init__(self, minimum, maximum, name=None, latex_label=None,
-                 unit=None):
+                 unit=None, periodic_boundary=False):
         """Log-Uniform prior with bounds
 
         Parameters
@@ -850,9 +885,11 @@ class LogUniform(PowerLaw):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         PowerLaw.__init__(self, name=name, latex_label=latex_label, unit=unit,
-                          minimum=minimum, maximum=maximum, alpha=-1)
+                          minimum=minimum, maximum=maximum, alpha=-1, periodic_boundary=periodic_boundary)
         if self.minimum <= 0:
             logger.warning('You specified a uniform-in-log prior with minimum={}'.format(self.minimum))
 
@@ -860,7 +897,7 @@ class LogUniform(PowerLaw):
 class SymmetricLogUniform(Prior):
 
     def __init__(self, minimum, maximum, name=None, latex_label=None,
-                 unit=None):
+                 unit=None, periodic_boundary=False):
         """Symmetric Log-Uniform distribtions with bounds
 
         This is identical to a Log-Uniform distribition, but mirrored about
@@ -880,9 +917,12 @@ class SymmetricLogUniform(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         Prior.__init__(self, name=name, latex_label=latex_label,
-                       minimum=minimum, maximum=maximum, unit=unit)
+                       minimum=minimum, maximum=maximum, unit=unit,
+                       periodic_boundary=periodic_boundary)
 
     def rescale(self, val):
         """
@@ -940,7 +980,7 @@ class SymmetricLogUniform(Prior):
 class Cosine(Prior):
 
     def __init__(self, name=None, latex_label=None, unit=None,
-                 minimum=-np.pi / 2, maximum=np.pi / 2):
+                 minimum=-np.pi / 2, maximum=np.pi / 2, periodic_boundary=False):
         """Cosine prior with bounds
 
         Parameters
@@ -955,9 +995,11 @@ class Cosine(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         Prior.__init__(self, name=name, latex_label=latex_label, unit=unit,
-                       minimum=minimum, maximum=maximum)
+                       minimum=minimum, maximum=maximum, periodic_boundary=periodic_boundary)
 
     def rescale(self, val):
         """
@@ -986,7 +1028,7 @@ class Cosine(Prior):
 class Sine(Prior):
 
     def __init__(self, name=None, latex_label=None, unit=None, minimum=0,
-                 maximum=np.pi):
+                 maximum=np.pi, periodic_boundary=False):
         """Sine prior with bounds
 
         Parameters
@@ -1001,9 +1043,11 @@ class Sine(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         Prior.__init__(self, name=name, latex_label=latex_label, unit=unit,
-                       minimum=minimum, maximum=maximum)
+                       minimum=minimum, maximum=maximum, periodic_boundary=periodic_boundary)
 
     def rescale(self, val):
         """
@@ -1031,7 +1075,7 @@ class Sine(Prior):
 
 class Gaussian(Prior):
 
-    def __init__(self, mu, sigma, name=None, latex_label=None, unit=None):
+    def __init__(self, mu, sigma, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """Gaussian prior with mean mu and width sigma
 
         Parameters
@@ -1046,8 +1090,10 @@ class Gaussian(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
-        Prior.__init__(self, name=name, latex_label=latex_label, unit=unit)
+        Prior.__init__(self, name=name, latex_label=latex_label, unit=unit, periodic_boundary=periodic_boundary)
         self.mu = mu
         self.sigma = sigma
 
@@ -1079,7 +1125,7 @@ class Gaussian(Prior):
 
 class Normal(Gaussian):
 
-    def __init__(self, mu, sigma, name=None, latex_label=None, unit=None):
+    def __init__(self, mu, sigma, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """A synonym for the Gaussian distribution.
 
         Parameters
@@ -1094,15 +1140,17 @@ class Normal(Gaussian):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
-        Gaussian.__init__(self, mu=mu, sigma=sigma, name=name,
-                          latex_label=latex_label, unit=unit)
+        Gaussian.__init__(self, mu=mu, sigma=sigma, name=name, latex_label=latex_label,
+                          unit=unit, periodic_boundary=periodic_boundary)
 
 
 class TruncatedGaussian(Prior):
 
     def __init__(self, mu, sigma, minimum, maximum, name=None,
-                 latex_label=None, unit=None):
+                 latex_label=None, unit=None, periodic_boundary=False):
         """Truncated Gaussian prior with mean mu and width sigma
 
         https://en.wikipedia.org/wiki/Truncated_normal_distribution
@@ -1123,9 +1171,11 @@ class TruncatedGaussian(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         Prior.__init__(self, name=name, latex_label=latex_label, unit=unit,
-                       minimum=minimum, maximum=maximum)
+                       minimum=minimum, maximum=maximum, periodic_boundary=periodic_boundary)
         self.mu = mu
         self.sigma = sigma
 
@@ -1161,14 +1211,14 @@ class TruncatedGaussian(Prior):
         -------
         float: Prior probability of val
         """
-        return np.exp(-(self.mu - val) ** 2 / (2 * self.sigma ** 2)) / (
-            2 * np.pi) ** 0.5 / self.sigma / self.normalisation * self.is_in_prior_range(val)
+        return np.exp(-(self.mu - val) ** 2 / (2 * self.sigma ** 2)) / \
+            (2 * np.pi) ** 0.5 / self.sigma / self.normalisation * self.is_in_prior_range(val)
 
 
 class TruncatedNormal(TruncatedGaussian):
 
     def __init__(self, mu, sigma, minimum, maximum, name=None,
-                 latex_label=None, unit=None):
+                 latex_label=None, unit=None, periodic_boundary=False):
         """A synonym for the TruncatedGaussian distribution.
 
         Parameters
@@ -1187,14 +1237,16 @@ class TruncatedNormal(TruncatedGaussian):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         TruncatedGaussian.__init__(self, mu=mu, sigma=sigma, minimum=minimum,
-                                   maximum=maximum, name=name,
-                                   latex_label=latex_label, unit=unit)
+                                   maximum=maximum, name=name, latex_label=latex_label,
+                                   unit=unit, periodic_boundary=periodic_boundary)
 
 
 class HalfGaussian(TruncatedGaussian):
-    def __init__(self, sigma, name=None, latex_label=None, unit=None):
+    def __init__(self, sigma, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """A Gaussian with its mode at zero, and truncated to only be positive.
 
         Parameters
@@ -1207,14 +1259,16 @@ class HalfGaussian(TruncatedGaussian):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         TruncatedGaussian.__init__(self, 0., sigma, minimum=0., maximum=np.inf,
                                    name=name, latex_label=latex_label,
-                                   unit=unit)
+                                   unit=unit, periodic_boundary=periodic_boundary)
 
 
 class HalfNormal(HalfGaussian):
-    def __init__(self, sigma, name=None, latex_label=None, unit=None):
+    def __init__(self, sigma, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """A synonym for the HalfGaussian distribution.
 
         Parameters
@@ -1227,14 +1281,16 @@ class HalfNormal(HalfGaussian):
             See superclass
         unit: str
             See superclass
-
+        periodic_boundary: bool
+            See superclass
         """
         HalfGaussian.__init__(self, sigma=sigma, name=name,
-                              latex_label=latex_label, unit=unit)
+                              latex_label=latex_label, unit=unit,
+                              periodic_boundary=periodic_boundary)
 
 
 class LogNormal(Prior):
-    def __init__(self, mu, sigma, name=None, latex_label=None, unit=None):
+    def __init__(self, mu, sigma, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """Log-normal prior with mean mu and width sigma
 
         https://en.wikipedia.org/wiki/Log-normal_distribution
@@ -1251,10 +1307,11 @@ class LogNormal(Prior):
             See superclass
         unit: str
             See superclass
-
+        periodic_boundary: bool
+            See superclass
         """
         Prior.__init__(self, name=name, minimum=0., latex_label=latex_label,
-                       unit=unit)
+                       unit=unit, periodic_boundary=periodic_boundary)
 
         if sigma <= 0.:
             raise ValueError("For the LogGaussian prior the standard deviation must be positive")
@@ -1290,7 +1347,7 @@ class LogNormal(Prior):
 
 
 class LogGaussian(LogNormal):
-    def __init__(self, mu, sigma, name=None, latex_label=None, unit=None):
+    def __init__(self, mu, sigma, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """Synonym of LogNormal prior
 
         https://en.wikipedia.org/wiki/Log-normal_distribution
@@ -1307,14 +1364,15 @@ class LogGaussian(LogNormal):
             See superclass
         unit: str
             See superclass
-
+        periodic_boundary: bool
+            See superclass
         """
         LogNormal.__init__(self, mu=mu, sigma=sigma, name=name,
-                           latex_label=latex_label, unit=unit)
+                           latex_label=latex_label, unit=unit, periodic_boundary=periodic_boundary)
 
 
 class Exponential(Prior):
-    def __init__(self, mu, name=None, latex_label=None, unit=None):
+    def __init__(self, mu, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """Exponential prior with mean mu
 
         Parameters
@@ -1327,10 +1385,11 @@ class Exponential(Prior):
             See superclass
         unit: str
             See superclass
-
+        periodic_boundary: bool
+            See superclass
         """
         Prior.__init__(self, name=name, minimum=0., latex_label=latex_label,
-                       unit=unit)
+                       unit=unit, periodic_boundary=periodic_boundary)
         self.mu = mu
 
     def rescale(self, val):
@@ -1362,7 +1421,7 @@ class Exponential(Prior):
 
 class StudentT(Prior):
     def __init__(self, df, mu=0., scale=1., name=None, latex_label=None,
-                 unit=None):
+                 unit=None, periodic_boundary=False):
         """Student's t-distribution prior with number of degrees of freedom df,
         mean mu and scale
 
@@ -1382,8 +1441,10 @@ class StudentT(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
-        Prior.__init__(self, name=name, latex_label=latex_label, unit=unit)
+        Prior.__init__(self, name=name, latex_label=latex_label, unit=unit, periodic_boundary=periodic_boundary)
 
         if df <= 0. or scale <= 0.:
             raise ValueError("For the StudentT prior the number of degrees of freedom and scale must be positive")
@@ -1422,7 +1483,7 @@ class StudentT(Prior):
 
 class Beta(Prior):
     def __init__(self, alpha, beta, minimum=0, maximum=1, name=None,
-                 latex_label=None, unit=None):
+                 latex_label=None, unit=None, periodic_boundary=False):
         """Beta distribution
 
         https://en.wikipedia.org/wiki/Beta_distribution
@@ -1446,7 +1507,8 @@ class Beta(Prior):
             See superclass
         unit: str
             See superclass
-
+        periodic_boundary: bool
+            See superclass
         """
         if alpha <= 0. or beta <= 0.:
             raise ValueError("alpha and beta must both be positive values")
@@ -1456,7 +1518,7 @@ class Beta(Prior):
         self._minimum = minimum
         self._maximum = maximum
         Prior.__init__(self, minimum=minimum, maximum=maximum, name=name,
-                       latex_label=latex_label, unit=unit)
+                       latex_label=latex_label, unit=unit, periodic_boundary=periodic_boundary)
         self._set_dist()
 
     def rescale(self, val):
@@ -1550,7 +1612,7 @@ class Beta(Prior):
 
 
 class Logistic(Prior):
-    def __init__(self, mu, scale, name=None, latex_label=None, unit=None):
+    def __init__(self, mu, scale, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """Logistic distribution
 
         https://en.wikipedia.org/wiki/Logistic_distribution
@@ -1567,8 +1629,10 @@ class Logistic(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
-        Prior.__init__(self, name=name, latex_label=latex_label, unit=unit)
+        Prior.__init__(self, name=name, latex_label=latex_label, unit=unit, periodic_boundary=periodic_boundary)
 
         if scale <= 0.:
             raise ValueError("For the Logistic prior the scale must be positive")
@@ -1605,7 +1669,7 @@ class Logistic(Prior):
 
 
 class Cauchy(Prior):
-    def __init__(self, alpha, beta, name=None, latex_label=None, unit=None):
+    def __init__(self, alpha, beta, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """Cauchy distribution
 
         https://en.wikipedia.org/wiki/Cauchy_distribution
@@ -1622,8 +1686,10 @@ class Cauchy(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
-        Prior.__init__(self, name=name, latex_label=latex_label, unit=unit)
+        Prior.__init__(self, name=name, latex_label=latex_label, unit=unit, periodic_boundary=periodic_boundary)
 
         if beta <= 0.:
             raise ValueError("For the Cauchy prior the scale must be positive")
@@ -1660,7 +1726,7 @@ class Cauchy(Prior):
 
 
 class Lorentzian(Cauchy):
-    def __init__(self, alpha, beta, name=None, latex_label=None, unit=None):
+    def __init__(self, alpha, beta, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """Synonym for the Cauchy distribution
 
         https://en.wikipedia.org/wiki/Cauchy_distribution
@@ -1677,13 +1743,15 @@ class Lorentzian(Cauchy):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         Cauchy.__init__(self, alpha=alpha, beta=beta, name=name,
-                        latex_label=latex_label, unit=unit)
+                        latex_label=latex_label, unit=unit, periodic_boundary=periodic_boundary)
 
 
 class Gamma(Prior):
-    def __init__(self, k, theta=1., name=None, latex_label=None, unit=None):
+    def __init__(self, k, theta=1., name=None, latex_label=None, unit=None, periodic_boundary=False):
         """Gamma distribution
 
         https://en.wikipedia.org/wiki/Gamma_distribution
@@ -1700,9 +1768,11 @@ class Gamma(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
         Prior.__init__(self, name=name, minimum=0., latex_label=latex_label,
-                       unit=unit)
+                       unit=unit, periodic_boundary=periodic_boundary)
 
         if k <= 0 or theta <= 0:
             raise ValueError("For the Gamma prior the shape and scale must be positive")
@@ -1740,7 +1810,7 @@ class Gamma(Prior):
 
 
 class ChiSquared(Gamma):
-    def __init__(self, nu, name=None, latex_label=None, unit=None):
+    def __init__(self, nu, name=None, latex_label=None, unit=None, periodic_boundary=False):
         """Chi-squared distribution
 
         https://en.wikipedia.org/wiki/Chi-squared_distribution
@@ -1755,13 +1825,15 @@ class ChiSquared(Gamma):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
         """
 
         if nu <= 0 or not isinstance(nu, int):
             raise ValueError("For the ChiSquared prior the number of degrees of freedom must be a positive integer")
 
         Gamma.__init__(self, name=name, k=nu / 2., theta=2.,
-                       latex_label=latex_label, unit=unit)
+                       latex_label=latex_label, unit=unit, periodic_boundary=periodic_boundary)
 
     @property
     def nu(self):
@@ -1775,7 +1847,7 @@ class ChiSquared(Gamma):
 class Interped(Prior):
 
     def __init__(self, xx, yy, minimum=np.nan, maximum=np.nan, name=None,
-                 latex_label=None, unit=None):
+                 latex_label=None, unit=None, periodic_boundary=False):
         """Creates an interpolated prior function from arrays of xx and yy=p(xx)
 
         Parameters
@@ -1794,6 +1866,8 @@ class Interped(Prior):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
 
         Attributes
         -------
@@ -1812,7 +1886,8 @@ class Interped(Prior):
         self.__all_interpolated = interp1d(x=xx, y=yy, bounds_error=False, fill_value=0)
         Prior.__init__(self, name=name, latex_label=latex_label, unit=unit,
                        minimum=np.nanmax(np.array((min(xx), minimum))),
-                       maximum=np.nanmin(np.array((max(xx), maximum))))
+                       maximum=np.nanmin(np.array((max(xx), maximum))),
+                       periodic_boundary=periodic_boundary)
         self._update_instance()
 
     def __eq__(self, other):
@@ -1905,7 +1980,7 @@ class Interped(Prior):
 class FromFile(Interped):
 
     def __init__(self, file_name, minimum=None, maximum=None, name=None,
-                 latex_label=None, unit=None):
+                 latex_label=None, unit=None, periodic_boundary=False):
         """Creates an interpolated prior function from arrays of xx and yy=p(xx) extracted from a file
 
         Parameters
@@ -1922,6 +1997,8 @@ class FromFile(Interped):
             See superclass
         unit: str
             See superclass
+        periodic_boundary: bool
+            See superclass
 
         Attributes
         -------
@@ -1933,8 +2010,8 @@ class FromFile(Interped):
             self.id = file_name
             xx, yy = np.genfromtxt(self.id).T
             Interped.__init__(self, xx=xx, yy=yy, minimum=minimum,
-                              maximum=maximum, name=name,
-                              latex_label=latex_label, unit=unit)
+                              maximum=maximum, name=name, latex_label=latex_label,
+                              unit=unit, periodic_boundary=periodic_boundary)
         except IOError:
             logger.warning("Can't load {}.".format(self.id))
             logger.warning("Format should be:")
diff --git a/bilby/core/result.py b/bilby/core/result.py
index 82fb07792c4ee844a2e6ec5cb67ddbc6c271e4be..a4630475fad32a85d70dd3d0becc946d948c8e87 100644
--- a/bilby/core/result.py
+++ b/bilby/core/result.py
@@ -412,12 +412,17 @@ class Result(object):
             default=False
         outdir: str, optional
             Path to the outdir. Default is the one stored in the result object.
-        extension: str, optional {json, hdf5}
-            Determines the method to use to store the data
+        extension: str, optional {json, hdf5, True}
+            Determines the method to use to store the data (if True defaults
+            to json)
         gzip: bool, optional
             If true, and outputing to a json file, this will gzip the resulting
             file and add '.gz' to the file extension.
         """
+
+        if extension is True:
+            extension = "json"
+
         outdir = self._safe_outdir_creation(outdir, self.save_to_file)
         file_name = result_file_name(outdir, self.label, extension, gzip)
 
@@ -978,6 +983,17 @@ class Result(object):
         fig.savefig(filename, dpi=dpi)
         plt.close(fig)
 
+    @staticmethod
+    def _add_prior_fixed_values_to_posterior(posterior, priors):
+        if priors is None:
+            return posterior
+        for key in priors:
+            if isinstance(priors[key], DeltaFunction):
+                posterior[key] = priors[key].peak
+            elif isinstance(priors[key], float):
+                posterior[key] = priors[key]
+        return posterior
+
     def samples_to_posterior(self, likelihood=None, priors=None,
                              conversion_function=None):
         """
@@ -1000,11 +1016,8 @@ class Result(object):
         except ValueError:
             data_frame = pd.DataFrame(
                 self.samples, columns=self.search_parameter_keys)
-            for key in priors:
-                if isinstance(priors[key], DeltaFunction):
-                    data_frame[key] = priors[key].peak
-                elif isinstance(priors[key], float):
-                    data_frame[key] = priors[key]
+            data_frame = self._add_prior_fixed_values_to_posterior(
+                data_frame, priors)
             data_frame['log_likelihood'] = getattr(
                 self, 'log_likelihood_evaluations', np.nan)
             if self.log_prior_evaluations is None:
diff --git a/bilby/core/sampler/__init__.py b/bilby/core/sampler/__init__.py
index c8a5d2307e808ee6dab2d1ff4529edc7b7a04576..7feed4e8049f570e7c03c541f57e3c3cc4adfd4b 100644
--- a/bilby/core/sampler/__init__.py
+++ b/bilby/core/sampler/__init__.py
@@ -17,6 +17,7 @@ from .ptmcmc import PTMCMCSampler
 from .pymc3 import Pymc3
 from .pymultinest import Pymultinest
 from .fake_sampler import FakeSampler
+from . import proposal
 
 IMPLEMENTED_SAMPLERS = {
     'cpnest': Cpnest, 'dynesty': Dynesty, 'emcee': Emcee, 'nestle': Nestle,
@@ -89,7 +90,7 @@ def run_sampler(likelihood, priors=None, label='label', outdir='outdir',
         If true, save the priors and results to disk.
         If hdf5, save as an hdf5 file instead of json.
     gzip: bool
-        If true, and save is true, gzip the saved results file. 
+        If true, and save is true, gzip the saved results file.
     result_class: bilby.core.result.Result, or child of
         The result class to use. By default, `bilby.core.result.Result` is used,
         but objects which inherit from this class can be given providing
@@ -167,6 +168,10 @@ def run_sampler(likelihood, priors=None, label='label', outdir='outdir',
     else:
         result = sampler.run_sampler()
 
+    # Initial save of the sampler in case of failure in post-processing
+    if save:
+        result.save_to_file(extension=save, gzip=gzip)
+
     end_time = datetime.datetime.now()
     result.sampling_time = (end_time - start_time).total_seconds()
     logger.info('Sampling time: {}'.format(end_time - start_time))
@@ -188,12 +193,11 @@ def run_sampler(likelihood, priors=None, label='label', outdir='outdir',
 
     result.samples_to_posterior(likelihood=likelihood, priors=result.priors,
                                 conversion_function=conversion_function)
-    if save == 'hdf5':
-        result.save_to_file(extension='hdf5')
-        logger.info("Results saved to {}/".format(outdir))
-    elif save:
-        result.save_to_file(gzip=gzip)
-        logger.info("Results saved to {}/".format(outdir))
+
+    if save:
+        # The overwrite here ensures we overwrite the initially stored data
+        result.save_to_file(overwrite=True, extension=save, gzip=gzip)
+
     if plot:
         result.plot_corner()
     logger.info("Summary of results:\n{}".format(result))
diff --git a/bilby/core/sampler/cpnest.py b/bilby/core/sampler/cpnest.py
index 0f1cd8780827756b25e4bbce9bbea064f9c2301d..6acd33689f2c6dbcf6204387f52d2737bd6ef9b7 100644
--- a/bilby/core/sampler/cpnest.py
+++ b/bilby/core/sampler/cpnest.py
@@ -1,9 +1,12 @@
 from __future__ import absolute_import
 
+import copy
+
 import numpy as np
 from pandas import DataFrame
 
 from .base_sampler import NestedSampler
+from .proposal import Sample, JumpProposalCycle
 from ..utils import logger, check_directory_exists_and_if_not_mkdir
 
 
@@ -37,7 +40,7 @@ class Cpnest(NestedSampler):
     """
     default_kwargs = dict(verbose=1, nthreads=1, nlive=500, maxmcmc=1000,
                           seed=None, poolsize=100, nhamiltonian=0, resume=True,
-                          output=None)
+                          output=None, proposals=None)
 
     def _translate_kwargs(self, kwargs):
         if 'nlive' not in kwargs:
@@ -49,14 +52,17 @@ class Cpnest(NestedSampler):
 
     def run_sampler(self):
         from cpnest import model as cpmodel, CPNest
+        from cpnest.parameter import LivePoint
 
         class Model(cpmodel.Model):
             """ A wrapper class to pass our log_likelihood into cpnest """
 
-            def __init__(self, names, bounds):
+            def __init__(self, names, priors):
                 self.names = names
-                self.bounds = bounds
-                self._check_bounds()
+                self.priors = priors
+                self.bounds = [
+                    [self.priors[key].minimum, self.priors[key].maximum]
+                    for key in self.names]
 
             @staticmethod
             def log_likelihood(x, **kwargs):
@@ -68,16 +74,26 @@ class Cpnest(NestedSampler):
                 theta = [x[n] for n in self.search_parameter_keys]
                 return self.log_prior(theta)
 
-            def _check_bounds(self):
-                for bound in self.bounds:
-                    if not all(np.isfinite(bound)):
-                        raise ValueError(
-                            'CPNest requires priors to have finite bounds.')
-
-        bounds = [[self.priors[key].minimum, self.priors[key].maximum]
-                  for key in self.search_parameter_keys]
-        model = Model(self.search_parameter_keys, bounds)
-        out = CPNest(model, **self.kwargs)
+            def new_point(self):
+                """Draw a point from the prior"""
+                point = LivePoint(
+                    self.names, [self.priors[name].sample()
+                                 for name in self.names])
+                return point
+
+        self._resolve_proposal_functions()
+        model = Model(self.search_parameter_keys, self.priors)
+        try:
+            out = CPNest(model, **self.kwargs)
+        except TypeError as e:
+            if 'proposals' in self.kwargs.keys():
+                logger.warning('YOU ARE TRYING TO USE PROPOSALS IN A VERSION OF CPNEST THAT DOES'
+                               'NOT ACCEPT CUSTOM PROPOSALS. SAMPLING WILL COMMENCE WITH THE DEFAULT'
+                               'PROPOSALS.')
+                del self.kwargs['proposals']
+                out = CPNest(model, **self.kwargs)
+            else:
+                raise TypeError(e)
         out.run()
 
         if self.plot:
@@ -87,7 +103,7 @@ class Cpnest(NestedSampler):
         self.result.posterior.rename(columns=dict(
             logL='log_likelihood', logPrior='log_prior'), inplace=True)
         self.result.log_evidence = out.NS.state.logZ
-        self.result.log_evidence_err = np.nan
+        self.result.log_evidence_err = np.sqrt(out.NS.state.info / out.NS.state.nlive)
         return self.result
 
     def _verify_kwargs_against_default_kwargs(self):
@@ -101,3 +117,69 @@ class Cpnest(NestedSampler):
             self.kwargs['output'] = '{}/'.format(self.kwargs['output'])
         check_directory_exists_and_if_not_mkdir(self.kwargs['output'])
         NestedSampler._verify_kwargs_against_default_kwargs(self)
+
+    def _resolve_proposal_functions(self):
+        from cpnest.proposal import ProposalCycle
+        if 'proposals' in self.kwargs:
+            if self.kwargs['proposals'] is None:
+                return
+            if type(self.kwargs['proposals']) == JumpProposalCycle:
+                self.kwargs['proposals'] = dict(mhs=self.kwargs['proposals'], hmc=self.kwargs['proposals'])
+            for key, proposal in self.kwargs['proposals'].items():
+                if isinstance(proposal, JumpProposalCycle):
+                    self.kwargs['proposals'][key] = cpnest_proposal_cycle_factory(proposal)
+                elif isinstance(proposal, ProposalCycle):
+                    pass
+                else:
+                    raise TypeError("Unknown proposal type")
+
+
+def cpnest_proposal_factory(jump_proposal):
+    import cpnest.proposal
+
+    class CPNestEnsembleProposal(cpnest.proposal.EnsembleProposal):
+
+        def __init__(self, jp):
+            self.jump_proposal = jp
+            self.ensemble = None
+
+        def __call__(self, sample, **kwargs):
+            return self.get_sample(sample, **kwargs)
+
+        def get_sample(self, cpnest_sample, **kwargs):
+            sample = Sample.from_cpnest_live_point(cpnest_sample)
+            self.ensemble = kwargs.get('coordinates', self.ensemble)
+            sample = self.jump_proposal(sample=sample, sampler_name='cpnest', **kwargs)
+            self.log_J = self.jump_proposal.log_j
+            return self._update_cpnest_sample(cpnest_sample, sample)
+
+        @staticmethod
+        def _update_cpnest_sample(cpnest_sample, sample):
+            cpnest_sample.names = list(sample.keys())
+            for i, value in enumerate(sample.values()):
+                cpnest_sample.values[i] = value
+            return cpnest_sample
+
+    return CPNestEnsembleProposal(jump_proposal)
+
+
+def cpnest_proposal_cycle_factory(jump_proposals):
+    import cpnest.proposal
+
+    class CPNestProposalCycle(cpnest.proposal.ProposalCycle):
+        def __init__(self):
+            self.jump_proposals = copy.deepcopy(jump_proposals)
+            for i, prop in enumerate(self.jump_proposals.proposal_functions):
+                self.jump_proposals.proposal_functions[i] = cpnest_proposal_factory(prop)
+            self.jump_proposals.update_cycle()
+            super(CPNestProposalCycle, self).__init__(proposals=self.jump_proposals.proposal_functions,
+                                                      weights=self.jump_proposals.weights,
+                                                      cyclelength=self.jump_proposals.cycle_length)
+
+        def get_sample(self, old, **kwargs):
+            return self.jump_proposals(sample=old, coordinates=self.ensemble, **kwargs)
+
+        def set_ensemble(self, ensemble):
+            self.ensemble = ensemble
+
+    return CPNestProposalCycle
diff --git a/bilby/core/sampler/dynesty.py b/bilby/core/sampler/dynesty.py
index a51ce406d75e2061af47270484a0f68a2d872199..e2787895f0cf220896b5e6c7aa232f046b33058e 100644
--- a/bilby/core/sampler/dynesty.py
+++ b/bilby/core/sampler/dynesty.py
@@ -189,7 +189,10 @@ class Dynesty(NestedSampler):
         if self.kwargs["verbose"]:
             print("")
 
-        # self.result.sampler_output = out
+        dynesty_result = "{}/{}_dynesty.pickle".format(self.outdir, self.label)
+        with open(dynesty_result, 'wb') as file:
+            pickle.dump(out, file)
+
         weights = np.exp(out['logwt'] - out['logz'][-1])
         nested_samples = DataFrame(
             out.samples, columns=self.search_parameter_keys)
@@ -307,8 +310,8 @@ class Dynesty(NestedSampler):
             return False
 
     def write_current_state_and_exit(self, signum=None, frame=None):
+        logger.warning("Run terminated with signal {}".format(signum))
         self.write_current_state()
-        logger.warning("Run terminated")
         sys.exit()
 
     def write_current_state(self):
@@ -328,6 +331,7 @@ class Dynesty(NestedSampler):
             NestedSampler to write to disk.
         """
         check_directory_exists_and_if_not_mkdir(self.outdir)
+        logger.info("Writing checkpoint file {}".format(self.resume_file))
 
         current_state = dict(
             unit_cube_samples=self.sampler.saved_u,
diff --git a/bilby/core/sampler/emcee.py b/bilby/core/sampler/emcee.py
index 3117a6c7a2cdd11266bce26fda6e9b06feb9a36d..6b4a0e732aa3274dbe5fee8ce4f211138964f1d5 100644
--- a/bilby/core/sampler/emcee.py
+++ b/bilby/core/sampler/emcee.py
@@ -1,10 +1,15 @@
 from __future__ import absolute_import, print_function
 
+from collections import namedtuple
 import os
+import signal
+from shutil import copyfile
+import sys
 
 import numpy as np
 from pandas import DataFrame
 from distutils.version import LooseVersion
+import dill as pickle
 
 from ..utils import (
     logger, get_progress_bar, check_directory_exists_and_if_not_mkdir)
@@ -41,11 +46,11 @@ class Emcee(MCMCSampler):
 
     """
 
-    default_kwargs = dict(nwalkers=500, a=2, args=[], kwargs={},
-                          postargs=None, pool=None, live_dangerously=False,
-                          runtime_sortingfn=None, lnprob0=None, rstate0=None,
-                          blobs0=None, iterations=100, thin=1, storechain=True,
-                          mh_proposal=None)
+    default_kwargs = dict(
+        nwalkers=500, a=2, args=[], kwargs={}, postargs=None, pool=None,
+        live_dangerously=False, runtime_sortingfn=None, lnprob0=None,
+        rstate0=None, blobs0=None, iterations=100, thin=1, storechain=True,
+        mh_proposal=None)
 
     def __init__(self, likelihood, priors, outdir='outdir', label='label',
                  use_ratio=False, plot=False, skip_import_verification=False,
@@ -65,7 +70,9 @@ class Emcee(MCMCSampler):
         self.nburn = nburn
         self.burn_in_fraction = burn_in_fraction
         self.burn_in_act = burn_in_act
-        self._old_chain = None
+
+        signal.signal(signal.SIGTERM, self.checkpoint_and_exit)
+        signal.signal(signal.SIGINT, self.checkpoint_and_exit)
 
     def _translate_kwargs(self, kwargs):
         if 'nwalkers' not in kwargs:
@@ -136,6 +143,14 @@ class Emcee(MCMCSampler):
 
         return init_kwargs
 
+    def lnpostfn(self, theta):
+        log_prior = self.log_prior(theta)
+        if np.isinf(log_prior):
+            return -np.inf, [np.nan, np.nan]
+        else:
+            log_likelihood = self.log_likelihood(theta)
+            return log_likelihood + log_prior, [log_likelihood, log_prior]
+
     @property
     def nburn(self):
         if type(self.__nburn) in [float, int]:
@@ -166,71 +181,141 @@ class Emcee(MCMCSampler):
     def nsteps(self, nsteps):
         self.kwargs['iterations'] = nsteps
 
-    def __getstate__(self):
-        # In order to be picklable with dill, we need to discard the pool
-        # object before trying.
-        d = self.__dict__
-        d["_Sampler__kwargs"]["pool"] = None
-        return d
+    @property
+    def stored_chain(self):
+        """ Read the stored zero-temperature chain data in from disk """
+        return np.genfromtxt(self.checkpoint_info.chain_file, names=True)
 
-    def run_sampler(self):
-        import emcee
-        tqdm = get_progress_bar()
-        sampler = emcee.EnsembleSampler(**self.sampler_init_kwargs)
-        out_dir = os.path.join(self.outdir, 'emcee_{}'.format(self.label))
-        out_file = os.path.join(out_dir, 'chain.dat')
+    @property
+    def stored_samples(self):
+        """ Returns the samples stored on disk """
+        return self.stored_chain[self.search_parameter_keys]
 
-        if self.resume:
-            self.load_old_chain(out_file)
-        else:
-            self._set_pos0()
+    @property
+    def stored_loglike(self):
+        """ Returns the log-likelihood stored on disk """
+        return self.stored_chain['log_l']
 
+    @property
+    def stored_logprior(self):
+        """ Returns the log-prior stored on disk """
+        return self.stored_chain['log_p']
+
+    def _init_chain_file(self):
+        with open(self.checkpoint_info.chain_file, "w+") as ff:
+            ff.write('walker\t{}\tlog_l\tlog_p\n'.format(
+                '\t'.join(self.search_parameter_keys)))
+
+    @property
+    def checkpoint_info(self):
+        """ Defines various things related to checkpointing and storing data
+
+        Returns
+        -------
+        checkpoint_info: named_tuple
+            An object with attributes `sampler_file`, `chain_file`, and
+            `chain_template`. The first two give paths to where the sampler and
+            chain data is stored, the last a formatted-str-template with which
+            to write the chain data to disk
+
+        """
+        out_dir = os.path.join(
+            self.outdir, '{}_{}'.format(self.__class__.__name__.lower(),
+                                        self.label))
         check_directory_exists_and_if_not_mkdir(out_dir)
-        if not os.path.isfile(out_file):
-            with open(out_file, "w") as ff:
-                ff.write('walker\t{}\tlog_l'.format(
-                    '\t'.join(self.search_parameter_keys)))
-        template =\
+
+        chain_file = os.path.join(out_dir, 'chain.dat')
+        sampler_file = os.path.join(out_dir, 'sampler.pickle')
+        chain_template =\
             '{:d}' + '\t{:.9e}' * (len(self.search_parameter_keys) + 2) + '\n'
 
-        for sample in tqdm(sampler.sample(**self.sampler_function_kwargs),
-                           total=self.nsteps):
-            if self.prerelease:
-                points = np.hstack([sample.coords, sample.blobs])
-            else:
-                points = np.hstack([sample[0], np.array(sample[3])])
-            with open(out_file, "a") as ff:
-                for ii, point in enumerate(points):
-                    ff.write(template.format(ii, *point))
+        CheckpointInfo = namedtuple(
+            'CheckpointInfo', ['sampler_file', 'chain_file', 'chain_template'])
 
-        self.result.sampler_output = np.nan
-        blobs_flat = np.array(sampler.blobs).reshape((-1, 2))
-        log_likelihoods, log_priors = blobs_flat.T
-        if self._old_chain is not None:
-            chain = np.vstack([self._old_chain[:, :-2],
-                               sampler.chain.reshape((-1, self.ndim))])
-            log_ls = np.hstack([self._old_chain[:, -2], log_likelihoods])
-            log_ps = np.hstack([self._old_chain[:, -1], log_priors])
-            self.nsteps = chain.shape[0] // self.nwalkers
+        checkpoint_info = CheckpointInfo(
+            sampler_file=sampler_file, chain_file=chain_file,
+            chain_template=chain_template)
+
+        return checkpoint_info
+
+    @property
+    def sampler_chain(self):
+        nsteps = self._previous_iterations
+        return self.sampler.chain[:, :nsteps, :]
+
+    def checkpoint(self):
+        """ Writes a pickle file of the sampler to disk using dill """
+        logger.info("Checkpointing sampler to file {}"
+                    .format(self.checkpoint_info.sampler_file))
+        with open(self.checkpoint_info.sampler_file, 'wb') as f:
+            # Overwrites the stored sampler chain with one that is truncated
+            # to only the completed steps
+            self.sampler._chain = self.sampler_chain
+            pickle.dump(self._sampler, f)
+
+    def checkpoint_and_exit(self, signum, frame):
+        logger.info("Recieved signal {}".format(signum))
+        self.checkpoint()
+        sys.exit()
+
+    def _initialise_sampler(self):
+        import emcee
+        self._sampler = emcee.EnsembleSampler(**self.sampler_init_kwargs)
+        self._init_chain_file()
+
+    @property
+    def sampler(self):
+        """ Returns the ptemcee sampler object
+
+        If, alrady initialized, returns the stored _sampler value. Otherwise,
+        first checks if there is a pickle file from which to load. If there is
+        not, then initialize the sampler and set the initial random draw
+
+        """
+        if hasattr(self, '_sampler'):
+            pass
+        elif self.resume and os.path.isfile(self.checkpoint_info.sampler_file):
+            logger.info("Resuming run from checkpoint file {}"
+                        .format(self.checkpoint_info.sampler_file))
+            with open(self.checkpoint_info.sampler_file, 'rb') as f:
+                self._sampler = pickle.load(f)
+            self._set_pos0_for_resume()
         else:
-            chain = sampler.chain.reshape((-1, self.ndim))
-            log_ls = log_likelihoods
-            log_ps = log_priors
-        self.calculate_autocorrelation(chain)
-        self.print_nburn_logging_info()
-        self.result.nburn = self.nburn
-        n_samples = self.nwalkers * self.nburn
-        if self.result.nburn > self.nsteps:
-            raise SamplerError(
-                "The run has finished, but the chain is not burned in: "
-                "`nburn < nsteps`. Try increasing the number of steps.")
-        self.result.samples = chain[n_samples:, :]
-        self.result.log_likelihood_evaluations = log_ls[n_samples:]
-        self.result.log_prior_evaluations = log_ps[n_samples:]
-        self.result.walkers = sampler.chain
-        self.result.log_evidence = np.nan
-        self.result.log_evidence_err = np.nan
-        return self.result
+            self._initialise_sampler()
+            self._set_pos0()
+        return self._sampler
+
+    def write_chains_to_file(self, sample):
+        chain_file = self.checkpoint_info.chain_file
+        temp_chain_file = chain_file + '.temp'
+        if os.path.isfile(chain_file):
+            copyfile(chain_file, temp_chain_file)
+
+        if self.prerelease:
+            points = np.hstack([sample.coords, sample.blobs])
+        else:
+            points = np.hstack([sample[0], np.array(sample[3])])
+        with open(temp_chain_file, "a") as ff:
+            for ii, point in enumerate(points):
+                ff.write(self.checkpoint_info.chain_template.format(ii, *point))
+        os.rename(temp_chain_file, chain_file)
+
+    @property
+    def _previous_iterations(self):
+        """ Returns the number of iterations that the sampler has saved
+
+        This is used when loading in a sampler from a pickle file to figure out
+        how much of the run has already been completed
+        """
+        return len(self.sampler.blobs)
+
+    def _draw_pos0_from_prior(self):
+        return np.array(
+            [self.get_random_draw_from_prior() for _ in range(self.nwalkers)])
+
+    @property
+    def _pos0_shape(self):
+        return (self.nwalkers, self.ndim)
 
     def _set_pos0(self):
         if self.pos0 is not None:
@@ -238,9 +323,9 @@ class Emcee(MCMCSampler):
             if isinstance(self.pos0, DataFrame):
                 self.pos0 = self.pos0[self.search_parameter_keys].values
             elif type(self.pos0) in (list, np.ndarray):
-                self.pos0 = np.squeeze(self.kwargs['pos0'])
+                self.pos0 = np.squeeze(self.pos0)
 
-            if self.pos0.shape != (self.nwalkers, self.ndim):
+            if self.pos0.shape != self._pos0_shape:
                 raise ValueError(
                     'Input pos0 should be of shape ndim, nwalkers')
             logger.debug("Checking input pos0")
@@ -248,27 +333,44 @@ class Emcee(MCMCSampler):
                 self.check_draw(draw)
         else:
             logger.debug("Generating initial walker positions from prior")
-            self.pos0 = [self.get_random_draw_from_prior()
-                         for _ in range(self.nwalkers)]
-
-    def load_old_chain(self, file_name=None):
-        if file_name is None:
-            out_dir = os.path.join(self.outdir, 'emcee_{}'.format(self.label))
-            file_name = os.path.join(out_dir, 'chain.dat')
-        if os.path.isfile(file_name):
-            old_chain = np.genfromtxt(file_name, skip_header=1)
-            self.pos0 = [np.squeeze(old_chain[-(self.nwalkers - ii), 1:-2])
-                         for ii in range(self.nwalkers)]
-            self._old_chain = old_chain[:-self.nwalkers + 1, 1:]
-            logger.info('Resuming from {}'.format(os.path.abspath(file_name)))
-        else:
-            logger.warning('Failed to resume. {} not found.'.format(file_name))
-            self._set_pos0()
+            self.pos0 = self._draw_pos0_from_prior()
 
-    def lnpostfn(self, theta):
-        log_prior = self.log_prior(theta)
-        if np.isinf(log_prior):
-            return -np.inf, [np.nan, np.nan]
-        else:
-            log_likelihood = self.log_likelihood(theta)
-            return log_likelihood + log_prior, [log_likelihood, log_prior]
+    def _set_pos0_for_resume(self):
+        self.pos0 = self.sampler.chain[:, -1, :]
+
+    def run_sampler(self):
+        tqdm = get_progress_bar()
+        sampler_function_kwargs = self.sampler_function_kwargs
+        iterations = sampler_function_kwargs.pop('iterations')
+        iterations -= self._previous_iterations
+
+        sampler_function_kwargs['p0'] = self.pos0
+
+        # main iteration loop
+        for sample in tqdm(
+                self.sampler.sample(iterations=iterations, **sampler_function_kwargs),
+                total=iterations):
+            self.write_chains_to_file(sample)
+        self.checkpoint()
+
+        self.result.sampler_output = np.nan
+        blobs_flat = np.array(self.sampler.blobs).reshape((-1, 2))
+        log_likelihoods, log_priors = blobs_flat.T
+        chain = self.sampler.chain.reshape((-1, self.ndim))
+        log_ls = log_likelihoods
+        log_ps = log_priors
+        self.calculate_autocorrelation(chain)
+        self.print_nburn_logging_info()
+        self.result.nburn = self.nburn
+        n_samples = self.nwalkers * self.nburn
+        if self.result.nburn > self.nsteps:
+            raise SamplerError(
+                "The run has finished, but the chain is not burned in: "
+                "`nburn < nsteps`. Try increasing the number of steps.")
+        self.result.samples = chain[n_samples:, :]
+        self.result.log_likelihood_evaluations = log_ls[n_samples:]
+        self.result.log_prior_evaluations = log_ps[n_samples:]
+        self.result.walkers = self.sampler.chain
+        self.result.log_evidence = np.nan
+        self.result.log_evidence_err = np.nan
+        return self.result
diff --git a/bilby/core/sampler/proposal.py b/bilby/core/sampler/proposal.py
new file mode 100644
index 0000000000000000000000000000000000000000..a928292b32b3293d593b0080e49f316c849c3a20
--- /dev/null
+++ b/bilby/core/sampler/proposal.py
@@ -0,0 +1,339 @@
+from collections import OrderedDict
+from inspect import isclass
+
+import numpy as np
+import random
+
+from bilby.core.prior import Uniform
+
+
+class Sample(OrderedDict):
+
+    def __init__(self, dictionary=None):
+        if dictionary is None:
+            dictionary = dict()
+        super(Sample, self).__init__(dictionary)
+
+    def __add__(self, other):
+        return Sample({key: self[key] + other[key] for key in self.keys()})
+
+    def __sub__(self, other):
+        return Sample({key: self[key] - other[key] for key in self.keys()})
+
+    def __mul__(self, other):
+        return Sample({key: self[key] * other for key in self.keys()})
+
+    @classmethod
+    def from_cpnest_live_point(cls, cpnest_live_point):
+        res = cls(dict())
+        for i, key in enumerate(cpnest_live_point.names):
+            res[key] = cpnest_live_point.values[i]
+        return res
+
+    @classmethod
+    def from_external_type(cls, external_sample, sampler_name):
+        if sampler_name == 'cpnest':
+            return cls.from_cpnest_live_point(external_sample)
+        return external_sample
+
+
+class JumpProposal(object):
+
+    def __init__(self, priors=None):
+        """ A generic class for jump proposals
+
+        Parameters
+        ----------
+        priors: bilby.core.prior.PriorDict
+            Dictionary of priors used in this sampling run
+
+        Attributes
+        ----------
+        log_j: float
+            Log Jacobian of the proposal. Characterises whether or not detailed balance
+            is preserved. If not, log_j needs to be adjusted accordingly.
+        """
+        self.priors = priors
+        self.log_j = 0.0
+
+    def __call__(self, sample, **kwargs):
+        """ A generic wrapper for the jump proposal function
+
+        Parameters
+        ----------
+        args: Arguments that are going to be passed into the proposal function
+        kwargs: Keyword arguments that are going to be passed into the proposal function
+
+        Returns
+        -------
+        dict: A dictionary with the new samples. Boundary conditions are applied.
+
+        """
+        return self._apply_boundaries(sample)
+
+    def _move_reflecting_keys(self, sample):
+        keys = [key for key in sample.keys() if not self.priors[key].periodic_boundary]
+        for key in keys:
+            if sample[key] > self.priors[key].maximum or sample[key] < self.priors[key].minimum:
+                r = self.priors[key].maximum - self.priors[key].minimum
+                delta = (sample[key] - self.priors[key].minimum) % (2 * r)
+                if delta > r:
+                    sample[key] = 2 * self.priors[key].maximum - self.priors[key].minimum - delta
+                elif delta < r:
+                    sample[key] = self.priors[key].minimum + delta
+        return sample
+
+    def _move_periodic_keys(self, sample):
+        keys = [key for key in sample.keys() if self.priors[key].periodic_boundary]
+        for key in keys:
+            if sample[key] > self.priors[key].maximum or sample[key] < self.priors[key].minimum:
+                sample[key] = (self.priors[key].minimum +
+                               ((sample[key] - self.priors[key].minimum) %
+                                (self.priors[key].maximum - self.priors[key].minimum)))
+        return sample
+
+    def _apply_boundaries(self, sample):
+        sample = self._move_periodic_keys(sample)
+        sample = self._move_reflecting_keys(sample)
+        return sample
+
+
+class JumpProposalCycle(object):
+
+    def __init__(self, proposal_functions, weights, cycle_length=100):
+        """ A generic wrapper class for proposal cycles
+
+        Parameters
+        ----------
+        proposal_functions: list
+            A list of callable proposal functions/objects
+        weights: list
+            A list of integer weights for the respective proposal functions
+        cycle_length: int, optional
+            Length of the proposal cycle
+        """
+        self.proposal_functions = proposal_functions
+        self.weights = weights
+        self.cycle_length = cycle_length
+        self._index = 0
+        self._cycle = np.array([])
+        self.update_cycle()
+
+    def __call__(self, **kwargs):
+        proposal = self._cycle[self.index]
+        self._index = (self.index + 1) % self.cycle_length
+        return proposal(**kwargs)
+
+    def __len__(self):
+        return len(self.proposal_functions)
+
+    def update_cycle(self):
+        self._cycle = np.random.choice(self.proposal_functions, size=self.cycle_length,
+                                       p=self.weights, replace=True)
+
+    @property
+    def proposal_functions(self):
+        return self._proposal_functions
+
+    @proposal_functions.setter
+    def proposal_functions(self, proposal_functions):
+        for i, proposal in enumerate(proposal_functions):
+            if isclass(proposal):
+                proposal_functions[i] = proposal()
+        self._proposal_functions = proposal_functions
+
+    @property
+    def index(self):
+        return self._index
+
+    @property
+    def weights(self):
+        """
+
+        Returns
+        -------
+        Normalised proposal weights
+
+        """
+        return np.array(self._weights) / np.sum(np.array(self._weights))
+
+    @weights.setter
+    def weights(self, weights):
+        assert len(weights) == len(self.proposal_functions)
+        self._weights = weights
+
+    @property
+    def unnormalised_weights(self):
+        return self._weights
+
+
+class NormJump(JumpProposal):
+    def __init__(self, step_size, priors=None):
+        """
+        A normal distributed step centered around the old sample
+
+        Parameters
+        ----------
+        step_size: float
+            The scalable step size
+        priors:
+            See superclass
+        """
+        super(NormJump, self).__init__(priors)
+        self.step_size = step_size
+
+    def __call__(self, sample, **kwargs):
+        for key in sample.keys():
+            sample[key] = np.random.normal(sample[key], self.step_size)
+        return super(NormJump, self).__call__(sample)
+
+
+class EnsembleWalk(JumpProposal):
+
+    def __init__(self, random_number_generator=random.random, n_points=3, priors=None,
+                 **random_number_generator_args):
+        """
+        An ensemble walk
+        Parameters
+        ----------
+        random_number_generator: func, optional
+            A random number generator. Default is random.random
+        n_points: int, optional
+            Number of points in the ensemble to average over. Default is 3.
+        priors:
+            See superclass
+        random_number_generator_args:
+            Additional keyword arguments for the random number generator
+        """
+        super(EnsembleWalk, self).__init__(priors)
+        self.random_number_generator = random_number_generator
+        self.n_points = n_points
+        self.random_number_generator_args = random_number_generator_args
+
+    def __call__(self, sample, **kwargs):
+        subset = random.sample(kwargs['coordinates'], self.n_points)
+        for i in range(len(subset)):
+            subset[i] = Sample.from_external_type(subset[i], kwargs.get('sampler_name', None))
+        center_of_mass = self.get_center_of_mass(subset)
+        for x in subset:
+            sample += (x - center_of_mass) * self.random_number_generator(**self.random_number_generator_args)
+        return super(EnsembleWalk, self).__call__(sample)
+
+    @staticmethod
+    def get_center_of_mass(subset):
+        return {key: np.mean([c[key] for c in subset]) for key in subset[0].keys()}
+
+
+class EnsembleStretch(JumpProposal):
+
+    def __init__(self, scale=2.0, priors=None):
+        """
+        Stretch move. Calculates the log Jacobian which can be used in cpnest to bias future moves.
+
+        Parameters
+        ----------
+        scale: float, optional
+            Stretching scale. Default is 2.0.
+        """
+        super(EnsembleStretch, self).__init__(priors)
+        self.scale = scale
+
+    def __call__(self, sample, **kwargs):
+        second_sample = random.choice(kwargs['coordinates'])
+        second_sample = Sample.from_external_type(second_sample, kwargs.get('sampler_name', None))
+        step = random.uniform(-1, 1) * np.log(self.scale)
+        sample = second_sample + (sample - second_sample) * np.exp(step)
+        self.log_j = len(sample) * step
+        return super(EnsembleStretch, self).__call__(sample)
+
+
+class DifferentialEvolution(JumpProposal):
+
+    def __init__(self, sigma=1e-4, mu=1.0, priors=None):
+        """
+        Differential evolution step. Takes two elements from the existing coordinates and differentially evolves the
+        old sample based on them using some Gaussian randomisation in the step.
+
+        Parameters
+        ----------
+        sigma: float, optional
+            Random spread in the evolution step. Default is 1e-4
+        mu: float, optional
+            Scale of the randomization. Default is 1.0
+        """
+        super(DifferentialEvolution, self).__init__(priors)
+        self.sigma = sigma
+        self.mu = mu
+
+    def __call__(self, sample, **kwargs):
+        a, b = random.sample(kwargs['coordinates'], 2)
+        sample = sample + (b - a) * random.gauss(self.mu, self.sigma)
+        return super(DifferentialEvolution, self).__call__(sample)
+
+
+class EnsembleEigenVector(JumpProposal):
+
+    def __init__(self, priors=None):
+        """
+        Ensemble step based on the ensemble eigenvectors.
+
+        Parameters
+        ----------
+        priors:
+            See superclass
+        """
+        super(EnsembleEigenVector, self).__init__(priors)
+        self.eigen_values = None
+        self.eigen_vectors = None
+        self.covariance = None
+
+    def update_eigenvectors(self, coordinates):
+        if coordinates is None:
+            return
+        elif len(coordinates[0]) == 1:
+            self._set_1_d_eigenvectors(coordinates)
+        else:
+            self._set_n_d_eigenvectors(coordinates)
+
+    def _set_1_d_eigenvectors(self, coordinates):
+        n_samples = len(coordinates)
+        key = list(coordinates[0].keys())[0]
+        variance = np.var([coordinates[j][key] for j in range(n_samples)])
+        self.eigen_values = np.atleast_1d(variance)
+        self.covariance = self.eigen_values
+        self.eigen_vectors = np.eye(1)
+
+    def _set_n_d_eigenvectors(self, coordinates):
+        n_samples = len(coordinates)
+        dim = len(coordinates[0])
+        cov_array = np.zeros((dim, n_samples))
+        for i, key in enumerate(coordinates[0].keys()):
+            for j in range(n_samples):
+                cov_array[i, j] = coordinates[j][key]
+        self.covariance = np.cov(cov_array)
+        self.eigen_values, self.eigen_vectors = np.linalg.eigh(self.covariance)
+
+    def __call__(self, sample, **kwargs):
+        self.update_eigenvectors(kwargs['coordinates'])
+        i = random.randrange(len(sample))
+        jump_size = np.sqrt(np.fabs(self.eigen_values[i])) * random.gauss(0, 1)
+        for j, key in enumerate(sample.keys()):
+            sample[key] += jump_size * self.eigen_vectors[j, i]
+        return super(EnsembleEigenVector, self).__call__(sample)
+
+
+class DrawFlatPrior(JumpProposal):
+    """
+    Draws a proposal from the flattened prior distribution.
+    """
+
+    def __call__(self, sample, **kwargs):
+        sample = _draw_from_flat_priors(sample, self.priors)
+        return super(DrawFlatPrior, self).__call__(sample)
+
+
+def _draw_from_flat_priors(sample, priors):
+    for key in sample.keys():
+        flat_prior = Uniform(priors[key].minimum, priors[key].maximum, priors[key].name)
+        sample[key] = flat_prior.sample()
+    return sample
diff --git a/bilby/core/sampler/ptemcee.py b/bilby/core/sampler/ptemcee.py
index 8ea4165cd4a2f600700f18c81d46bfe762a07d91..31c5609a6a72ac338cf36981108bd37d4f73154b 100644
--- a/bilby/core/sampler/ptemcee.py
+++ b/bilby/core/sampler/ptemcee.py
@@ -1,8 +1,11 @@
 from __future__ import absolute_import, division, print_function
 
+import os
+from shutil import copyfile
+
 import numpy as np
 
-from ..utils import get_progress_bar
+from ..utils import logger, get_progress_bar
 from . import Emcee
 from .base_sampler import SamplerError
 
@@ -27,23 +30,22 @@ class Ptemcee(Emcee):
         The number of temperatures used by ptemcee
 
     """
-    default_kwargs = dict(ntemps=2, nwalkers=500,
-                          Tmax=None, betas=None,
-                          threads=1, pool=None, a=2.0,
-                          loglargs=[], logpargs=[],
-                          loglkwargs={}, logpkwargs={},
-                          adaptation_lag=10000, adaptation_time=100,
-                          random=None, iterations=100, thin=1,
-                          storechain=True, adapt=True,
-                          swap_ratios=False,
-                          )
-
-    def __init__(self, likelihood, priors, outdir='outdir', label='label', use_ratio=False, plot=False,
-                 skip_import_verification=False, nburn=None, burn_in_fraction=0.25,
-                 burn_in_act=3, **kwargs):
-        Emcee.__init__(self, likelihood=likelihood, priors=priors, outdir=outdir, label=label,
-                       use_ratio=use_ratio, plot=plot, skip_import_verification=skip_import_verification,
-                       nburn=nburn, burn_in_fraction=burn_in_fraction, burn_in_act=burn_in_act, **kwargs)
+    default_kwargs = dict(
+        ntemps=2, nwalkers=500, Tmax=None, betas=None, threads=1, pool=None,
+        a=2.0, loglargs=[], logpargs=[], loglkwargs={}, logpkwargs={},
+        adaptation_lag=10000, adaptation_time=100, random=None, iterations=100,
+        thin=1, storechain=True, adapt=True, swap_ratios=False)
+
+    def __init__(self, likelihood, priors, outdir='outdir', label='label',
+                 use_ratio=False, plot=False, skip_import_verification=False,
+                 nburn=None, burn_in_fraction=0.25, burn_in_act=3, resume=True,
+                 **kwargs):
+        Emcee.__init__(
+            self, likelihood=likelihood, priors=priors, outdir=outdir,
+            label=label, use_ratio=use_ratio, plot=plot,
+            skip_import_verification=skip_import_verification,
+            nburn=nburn, burn_in_fraction=burn_in_fraction,
+            burn_in_act=burn_in_act, resume=resume, **kwargs)
 
     @property
     def sampler_function_kwargs(self):
@@ -56,42 +58,97 @@ class Ptemcee(Emcee):
                 for key, value in self.kwargs.items()
                 if key not in self.sampler_function_kwargs}
 
-    def run_sampler(self):
+    @property
+    def ntemps(self):
+        return self.kwargs['ntemps']
+
+    @property
+    def sampler_chain(self):
+        nsteps = self._previous_iterations
+        return self.sampler.chain[:, :, :nsteps, :]
+
+    def _initialise_sampler(self):
         import ptemcee
+        self._sampler = ptemcee.Sampler(
+            dim=self.ndim, logl=self.log_likelihood, logp=self.log_prior,
+            **self.sampler_init_kwargs)
+        self._init_chain_file()
+
+    def print_tswap_acceptance_fraction(self):
+        logger.info("Sampler per-chain tswap acceptance fraction = {}".format(
+            self.sampler.tswap_acceptance_fraction))
+
+    def write_chains_to_file(self, pos, loglike, logpost):
+        chain_file = self.checkpoint_info.chain_file
+        temp_chain_file = chain_file + '.temp'
+        if os.path.isfile(chain_file):
+            copyfile(chain_file, temp_chain_file)
+
+        with open(temp_chain_file, "a") as ff:
+            loglike = np.squeeze(loglike[0, :])
+            logprior = np.squeeze(logpost[0, :]) - loglike
+            for ii, (point, logl, logp) in enumerate(zip(pos[0, :, :], loglike, logprior)):
+                line = np.concatenate((point, [logl, logp]))
+                ff.write(self.checkpoint_info.chain_template.format(ii, *line))
+        os.rename(temp_chain_file, chain_file)
+
+    @property
+    def _previous_iterations(self):
+        """ Returns the number of iterations that the sampler has saved
+
+        This is used when loading in a sampler from a pickle file to figure out
+        how much of the run has already been completed
+        """
+        return self.sampler.time
+
+    def _draw_pos0_from_prior(self):
+        # for ptemcee, the pos0 has the shape ntemps, nwalkers, ndim
+        return [[self.get_random_draw_from_prior()
+                 for _ in range(self.nwalkers)]
+                for _ in range(self.kwargs['ntemps'])]
+
+    @property
+    def _pos0_shape(self):
+        return (self.ntemps, self.nwalkers, self.ndim)
+
+    def _set_pos0_for_resume(self):
+        self.pos0 = None
+
+    def run_sampler(self):
         tqdm = get_progress_bar()
-        sampler = ptemcee.Sampler(dim=self.ndim, logl=self.log_likelihood,
-                                  logp=self.log_prior, **self.sampler_init_kwargs)
-        self.pos0 = [[self.get_random_draw_from_prior()
-                      for _ in range(self.nwalkers)]
-                     for _ in range(self.kwargs['ntemps'])]
-
-        log_likelihood_evaluations = []
-        log_prior_evaluations = []
+        sampler_function_kwargs = self.sampler_function_kwargs
+        iterations = sampler_function_kwargs.pop('iterations')
+        iterations -= self._previous_iterations
+
+        # main iteration loop
         for pos, logpost, loglike in tqdm(
-                sampler.sample(self.pos0, **self.sampler_function_kwargs),
-                total=self.nsteps):
-            log_likelihood_evaluations.append(loglike)
-            log_prior_evaluations.append(logpost - loglike)
-            pass
+                self.sampler.sample(self.pos0, iterations=iterations,
+                                    **sampler_function_kwargs),
+                total=iterations):
+            self.write_chains_to_file(pos, loglike, logpost)
+        self.checkpoint()
 
-        self.calculate_autocorrelation(sampler.chain.reshape((-1, self.ndim)))
+        self.calculate_autocorrelation(self.sampler.chain.reshape((-1, self.ndim)))
         self.result.sampler_output = np.nan
         self.print_nburn_logging_info()
+        self.print_tswap_acceptance_fraction()
+
         self.result.nburn = self.nburn
         if self.result.nburn > self.nsteps:
             raise SamplerError(
                 "The run has finished, but the chain is not burned in: "
                 "`nburn < nsteps`. Try increasing the number of steps.")
-        self.result.samples = sampler.chain[0, :, self.nburn:, :].reshape(
+
+        self.result.samples = self.sampler.chain[0, :, self.nburn:, :].reshape(
             (-1, self.ndim))
-        self.result.log_likelihood_evaluations = np.array(
-            log_likelihood_evaluations)[self.nburn:, 0, :].reshape((-1))
-        self.result.log_prior_evaluations = np.array(
-            log_prior_evaluations)[self.nburn:, 0, :].reshape((-1))
-        self.result.betas = sampler.betas
+        self.result.walkers = self.sampler.chain[0, :, :, :]
+
+        n_samples = self.nwalkers * self.nburn
+        self.result.log_likelihood_evaluations = self.stored_loglike[n_samples:]
+        self.result.log_prior_evaluations = self.stored_logprior[n_samples:]
+        self.result.betas = self.sampler.betas
         self.result.log_evidence, self.result.log_evidence_err =\
-            sampler.log_evidence_estimate(
-                sampler.loglikelihood, self.nburn / self.nsteps)
-        self.result.walkers = sampler.chain[0, :, :, :]
+            self.sampler.log_evidence_estimate(
+                self.sampler.loglikelihood, self.nburn / self.nsteps)
 
         return self.result
diff --git a/bilby/core/series.py b/bilby/core/series.py
index 980c6c11afe529e4fc1cfbef3b4f26e69a512a6c..0c9b2efaa2e830c8096315aae89a0bc42229c1f0 100644
--- a/bilby/core/series.py
+++ b/bilby/core/series.py
@@ -35,7 +35,7 @@ class CoupledTimeAndFrequencySeries(object):
         -------
         array_like: The frequency array
         """
-        if self._frequency_array_updated is False:
+        if not self._frequency_array_updated:
             if self.sampling_frequency and self.duration:
                 self._frequency_array = utils.create_frequency_series(
                     sampling_frequency=self.sampling_frequency,
@@ -45,6 +45,7 @@ class CoupledTimeAndFrequencySeries(object):
                                  'legitimate sampling_frequency ({}) or duration ({})'
                                  .format(self.sampling_frequency, self.duration))
 
+            self._frequency_array_updated = True
         return self._frequency_array
 
     @frequency_array.setter
@@ -63,7 +64,7 @@ class CoupledTimeAndFrequencySeries(object):
         array_like: The time array
         """
 
-        if self._time_array_updated is False:
+        if not self._time_array_updated:
             if self.sampling_frequency and self.duration:
                 self._time_array = utils.create_time_series(
                     sampling_frequency=self.sampling_frequency,
diff --git a/bilby/gw/calibration.py b/bilby/gw/calibration.py
index d95a12f002a391188ec5efcc3a9dbc44594b0e39..cf4fc7d9392e35f369edb31d2379b5430b65a058 100644
--- a/bilby/gw/calibration.py
+++ b/bilby/gw/calibration.py
@@ -2,7 +2,7 @@
 """
 
 import numpy as np
-from scipy.interpolate import UnivariateSpline
+from scipy.interpolate import interp1d
 
 
 class Recalibrate(object):
@@ -83,11 +83,12 @@ class CubicSpline(Recalibrate):
         self.n_points = n_points
         self.minimum_frequency = minimum_frequency
         self.maximum_frequency = maximum_frequency
-        self.__spline_points = np.logspace(np.log10(minimum_frequency), np.log10(maximum_frequency), n_points)
+        self._log_spline_points = np.linspace(
+            np.log10(minimum_frequency), np.log10(maximum_frequency), n_points)
 
     @property
-    def spline_points(self):
-        return self.__spline_points
+    def log_spline_points(self):
+        return self._log_spline_points
 
     def __repr__(self):
         return self.__class__.__name__ + '(prefix=\'{}\', minimum_frequency={}, maximum_frequency={}, n_points={})'\
@@ -112,13 +113,17 @@ class CubicSpline(Recalibrate):
             The factor to multiply the strain by.
         """
         self.set_calibration_parameters(**params)
-        amplitude_parameters = [self.params['amplitude_{}'.format(ii)] for ii in range(self.n_points)]
-        amplitude_spline = UnivariateSpline(self.spline_points, amplitude_parameters)
-        delta_amplitude = amplitude_spline(frequency_array)
-
-        phase_parameters = [self.params['phase_{}'.format(ii)] for ii in range(self.n_points)]
-        phase_spline = UnivariateSpline(self.spline_points, phase_parameters)
-        delta_phase = phase_spline(frequency_array)
+        amplitude_parameters = [self.params['amplitude_{}'.format(ii)]
+                                for ii in range(self.n_points)]
+        delta_amplitude = interp1d(
+            self.log_spline_points, amplitude_parameters, kind='cubic',
+            bounds_error=False, fill_value=0)(np.log10(frequency_array))
+
+        phase_parameters = [
+            self.params['phase_{}'.format(ii)] for ii in range(self.n_points)]
+        delta_phase = interp1d(
+            self.log_spline_points, phase_parameters, kind='cubic',
+            bounds_error=False, fill_value=0)(np.log10(frequency_array))
 
         calibration_factor = (1 + delta_amplitude) * (2 + 1j * delta_phase) / (2 - 1j * delta_phase)
 
diff --git a/bilby/gw/likelihood.py b/bilby/gw/likelihood.py
index 7e913a50364a14d4ce3eda51c5fbe247600be43f..bbcb75ab770a891644436721e7320f12f3351c4a 100644
--- a/bilby/gw/likelihood.py
+++ b/bilby/gw/likelihood.py
@@ -70,6 +70,12 @@ class GravitationalWaveTransient(likelihood.Likelihood):
 
     """
 
+    _CalculatedSNRs = namedtuple('CalculatedSNRs',
+                                 ['d_inner_h',
+                                  'optimal_snr_squared',
+                                  'complex_matched_filter_snr',
+                                  'd_inner_h_squared_tc_array'])
+
     def __init__(self, interferometers, waveform_generator,
                  time_marginalization=False, distance_marginalization=False,
                  phase_marginalization=False, priors=None,
@@ -160,8 +166,6 @@ class GravitationalWaveTransient(likelihood.Likelihood):
         d_inner_h = interferometer.inner_product(signal=signal)
         optimal_snr_squared = interferometer.optimal_snr_squared(signal=signal)
         complex_matched_filter_snr = d_inner_h / (optimal_snr_squared**0.5)
-        CalculatedSNRs = namedtuple(
-            'CalculatedSNRs', ['d_inner_h', 'optimal_snr_squared', 'complex_matched_filter_snr', 'd_inner_h_squared_tc_array'])
 
         if self.time_marginalization:
             d_inner_h_squared_tc_array =\
@@ -172,7 +176,7 @@ class GravitationalWaveTransient(likelihood.Likelihood):
         else:
             d_inner_h_squared_tc_array = None
 
-        return CalculatedSNRs(
+        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_squared_tc_array=d_inner_h_squared_tc_array)
@@ -334,7 +338,7 @@ class GravitationalWaveTransient(likelihood.Likelihood):
             dmax = self._distance_array[-1]
             n = len(self._distance_array)
             self._lookup_table_filename = (
-                '.distance_marginalization_lookup_dmin{}_dmax{}_n{}.npz'
+                '.distance_marginalization_lookup.npz'
                 .format(dmin, dmax, n))
         return self._lookup_table_filename
 
@@ -609,12 +613,9 @@ class ROQGravitationalWaveTransient(GravitationalWaveTransient):
         h_plus_quadratic = f_plus * signal['quadratic']['plus']
         h_cross_quadratic = f_cross * signal['quadratic']['cross']
 
-        CalculatedSNRs = namedtuple(
-            'CalculatedSNRs', ['d_inner_h', 'optimal_snr_squared', 'complex_matched_filter_snr', 'd_inner_h_squared_tc_array'])
-
         indices, in_bounds = self._closest_time_indices(ifo_time, self.time_samples)
         if not in_bounds:
-            return CalculatedSNRs(
+            return self._CalculatedSNRs(
                 d_inner_h=np.nan_to_num(-np.inf), optimal_snr_squared=0,
                 complex_matched_filter_snr=np.nan_to_num(-np.inf),
                 d_inner_h_squared_tc_array=None)
@@ -634,7 +635,7 @@ class ROQGravitationalWaveTransient(GravitationalWaveTransient):
         complex_matched_filter_snr = d_inner_h / (optimal_snr_squared**0.5)
         d_inner_h_squared_tc_array = None
 
-        return CalculatedSNRs(
+        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_squared_tc_array=d_inner_h_squared_tc_array)
diff --git a/bilby/gw/prior.py b/bilby/gw/prior.py
index 58790e41cff09904f4accfe656f3a01f7ead8e70..c751303d2c9270c8a48697db62236f70e95f3eb8 100644
--- a/bilby/gw/prior.py
+++ b/bilby/gw/prior.py
@@ -31,7 +31,7 @@ class Cosmological(Interped):
                 name='comoving_distance', latex_label='$d_C$', unit=units.Mpc))
 
     def __init__(self, minimum, maximum, cosmology=None, name=None,
-                 latex_label=None, unit=None):
+                 latex_label=None, unit=None, periodic_boundary=False):
         self.cosmology = get_cosmology(cosmology)
         if name not in self._default_args_dict:
             raise ValueError(
@@ -59,7 +59,7 @@ class Cosmological(Interped):
         else:
             raise ValueError('Name {} not recognized.'.format(name))
         Interped.__init__(self, xx=xx, yy=yy, minimum=minimum, maximum=maximum,
-                          **label_args)
+                          periodic_boundary=periodic_boundary, **label_args)
 
     @property
     def minimum(self):
@@ -162,7 +162,7 @@ class UniformComovingVolume(Cosmological):
 class AlignedSpin(Interped):
 
     def __init__(self, a_prior=Uniform(0, 1), z_prior=Uniform(-1, 1),
-                 name=None, latex_label=None, unit=None):
+                 name=None, latex_label=None, unit=None, periodic_boundary=False):
         """
         Prior distribution for the aligned (z) component of the spin.
 
@@ -193,7 +193,8 @@ class AlignedSpin(Interped):
         yy = [np.trapz(np.nan_to_num(a_prior.prob(aas) / aas *
                                      z_prior.prob(x / aas)), aas) for x in xx]
         Interped.__init__(self, xx=xx, yy=yy, name=name,
-                          latex_label=latex_label, unit=unit)
+                          latex_label=latex_label, unit=unit,
+                          periodic_boundary=periodic_boundary)
 
 
 class BBHPriorDict(PriorDict):
@@ -509,13 +510,15 @@ class CalibrationPriorDict(PriorDict):
             latex_label = "$A^{}_{}$".format(label, ii)
             prior[name] = Gaussian(mu=amplitude_mean_nodes[ii],
                                    sigma=amplitude_sigma_nodes[ii],
-                                   name=name, latex_label=latex_label)
+                                   name=name, latex_label=latex_label,
+                                   periodic_boundary=False)
         for ii in range(n_nodes):
             name = "recalib_{}_phase_{}".format(label, ii)
             latex_label = "$\\phi^{}_{}$".format(label, ii)
             prior[name] = Gaussian(mu=phase_mean_nodes[ii],
                                    sigma=phase_sigma_nodes[ii],
-                                   name=name, latex_label=latex_label)
+                                   name=name, latex_label=latex_label,
+                                   periodic_boundary=False)
         for ii in range(n_nodes):
             name = "recalib_{}_frequency_{}".format(label, ii)
             latex_label = "$f^{}_{}$".format(label, ii)
@@ -568,13 +571,15 @@ class CalibrationPriorDict(PriorDict):
             latex_label = "$A^{}_{}$".format(label, ii)
             prior[name] = Gaussian(mu=amplitude_mean_nodes[ii],
                                    sigma=amplitude_sigma_nodes[ii],
-                                   name=name, latex_label=latex_label)
+                                   name=name, latex_label=latex_label,
+                                   periodic_boundary=False)
         for ii in range(n_nodes):
             name = "recalib_{}_phase_{}".format(label, ii)
             latex_label = "$\\phi^{}_{}$".format(label, ii)
             prior[name] = Gaussian(mu=phase_mean_nodes[ii],
                                    sigma=phase_sigma_nodes[ii],
-                                   name=name, latex_label=latex_label)
+                                   name=name, latex_label=latex_label,
+                                   periodic_boundary=False)
         for ii in range(n_nodes):
             name = "recalib_{}_frequency_{}".format(label, ii)
             latex_label = "$f^{}_{}$".format(label, ii)
diff --git a/bilby/gw/prior_files/GW150914.prior b/bilby/gw/prior_files/GW150914.prior
index 410fc927682662918f3d229640b564b672e4a86e..9641b77d906a058481bc7fb76512b8f9738540e8 100644
--- a/bilby/gw/prior_files/GW150914.prior
+++ b/bilby/gw/prior_files/GW150914.prior
@@ -1,20 +1,20 @@
 # These are the default priors for analysing GW150914.
-mass_1 = Uniform(name='mass_1', minimum=30, maximum=50, unit='$M_{\\odot}$')
-mass_2 = Uniform(name='mass_2', minimum=20, maximum=40, unit='$M_{\\odot}$')
-mass_ratio =  Constraint(name='mass_ratio', minimum=0.125, maximum=1)
-a_1 =  Uniform(name='a_1', minimum=0, maximum=0.8)
-a_2 =  Uniform(name='a_2', minimum=0, maximum=0.8)
-tilt_1 =  Sine(name='tilt_1')
-tilt_2 =  Sine(name='tilt_2')
-phi_12 =  Uniform(name='phi_12', minimum=0, maximum=2 * np.pi)
-phi_jl =  Uniform(name='phi_jl', minimum=0, maximum=2 * np.pi)
-luminosity_distance =  bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=1e2, maximum=1e3, unit='Mpc')
-dec =  Cosine(name='dec')
-ra =  Uniform(name='ra', minimum=0, maximum=2 * np.pi)
-theta_jn =  Sine(name='theta_jn')
-psi =  Uniform(name='psi', minimum=0, maximum=np.pi)
-phase =  Uniform(name='phase', minimum=0, maximum=2 * np.pi)
-geocent_time = Uniform(1126259462.322, 1126259462.522, name='geocent_time', unit='$s$')
+mass_1 = Uniform(name='mass_1', minimum=30, maximum=50, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_2 = Uniform(name='mass_2', minimum=20, maximum=40, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_ratio = Constraint(name='mass_ratio', minimum=0.125, maximum=1)
+a_1 = Uniform(name='a_1', minimum=0, maximum=0.8, periodic_boundary=False)
+a_2 = Uniform(name='a_2', minimum=0, maximum=0.8, periodic_boundary=False)
+tilt_1 = Sine(name='tilt_1', periodic_boundary=False)
+tilt_2 = Sine(name='tilt_2', periodic_boundary=False)
+phi_12 = Uniform(name='phi_12', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+phi_jl = Uniform(name='phi_jl', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+luminosity_distance = bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=1e2, maximum=1e3, unit='Mpc', periodic_boundary=False)
+dec = Cosine(name='dec', periodic_boundary=False)
+ra = Uniform(name='ra', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+theta_jn = Sine(name='theta_jn', periodic_boundary=False)
+psi = Uniform(name='psi', minimum=0, maximum=np.pi, periodic_boundary=True)
+phase = Uniform(name='phase', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+geocent_time = Uniform(1126259462.322, 1126259462.522, name='geocent_time', unit='$s$', periodic_boundary=False)
 # These are the calibration parameters as described in
 # https://journals.aps.org/prx/abstract/10.1103/PhysRevX.6.041015
 # recalib_H1_frequency_0 = 20
diff --git a/bilby/gw/prior_files/aligned_spin_binary_black_holes.prior b/bilby/gw/prior_files/aligned_spin_binary_black_holes.prior
index ca49a506b37a1fd7d8e855029932e8c437335a4c..8be6dcd1eeea32d50eecd9598cd98c5be30b7b71 100644
--- a/bilby/gw/prior_files/aligned_spin_binary_black_holes.prior
+++ b/bilby/gw/prior_files/aligned_spin_binary_black_holes.prior
@@ -2,19 +2,19 @@
 # Note that you may wish to use more specific mass and distance parameters.
 # These commands are all known to bilby.gw.prior.
 # Lines beginning "#" are ignored.
-mass_1 = Uniform(name='mass_1', minimum=5, maximum=100, unit='$M_{\\odot}$')
-mass_2 = Uniform(name='mass_2', minimum=5, maximum=100, unit='$M_{\\odot}$')
-mass_ratio =  Constraint(name='mass_ratio', minimum=0.125, maximum=1)
-# chirp_mass = Uniform(name='chirp_mass', minimum=25, maximum=100, unit='$M_{\\odot}$')
-# total_mass =  Uniform(name='total_mass', minimum=10, maximum=200, unit='$M_{\\odot}$')
-# mass_ratio =  Uniform(name='mass_ratio', minimum=0.125, maximum=1)
-# symmetric_mass_ratio =  Uniform(name='symmetric_mass_ratio', minimum=8 / 81, maximum=0.25)
-chi_1 =  bilby.gw.prior.AlignedSpin(name='chi_1', a_prior=Uniform(minimum=0, maximum=0.8))
-chi_2 =  bilby.gw.prior.AlignedSpin(name='chi_2', a_prior=Uniform(minimum=0, maximum=0.8))
-luminosity_distance =  bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=1e2, maximum=5e3, unit='Mpc')
-dec =  Cosine(name='dec')
-ra =  Uniform(name='ra', minimum=0, maximum=2 * np.pi)
-theta_jn =  Sine(name='theta_jn')
-# cos_theta_jn =  Uniform(name='cos_theta_jn', minimum=-1, maximum=1)
-psi =  Uniform(name='psi', minimum=0, maximum=np.pi)
-phase =  Uniform(name='phase', minimum=0, maximum=2 * np.pi)
+mass_1 = Uniform(name='mass_1', minimum=5, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_2 = Uniform(name='mass_2', minimum=5, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_ratio = Constraint(name='mass_ratio', minimum=0.125, maximum=1)
+# chirp_mass = Uniform(name='chirp_mass', minimum=25, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False)
+# total_mass = Uniform(name='total_mass', minimum=10, maximum=200, unit='$M_{\\odot}$', periodic_boundary=False)
+# mass_ratio = Uniform(name='mass_ratio', minimum=0.125, maximum=1, periodic_boundary=False)
+# symmetric_mass_ratio =  Uniform(name='symmetric_mass_ratio', minimum=8 / 81, maximum=0.25, periodic_boundary=False)
+chi_1 = bilby.gw.prior.AlignedSpin(name='chi_1', a_prior=Uniform(minimum=0, maximum=0.8), periodic_boundary=False)
+chi_2 = bilby.gw.prior.AlignedSpin(name='chi_2', a_prior=Uniform(minimum=0, maximum=0.8), periodic_boundary=False)
+luminosity_distance = bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=1e2, maximum=5e3, unit='Mpc', periodic_boundary=False)
+dec = Cosine(name='dec', periodic_boundary=False)
+ra = Uniform(name='ra', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+theta_jn = Sine(name='theta_jn', periodic_boundary=False)
+# cos_theta_jn = Uniform(name='cos_theta_jn', minimum=-1, maximum=1, periodic_boundary=False)
+psi = Uniform(name='psi', minimum=0, maximum=np.pi, periodic_boundary=True)
+phase = Uniform(name='phase', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
diff --git a/bilby/gw/prior_files/binary_black_holes.prior b/bilby/gw/prior_files/binary_black_holes.prior
index e79bd5baa754eb09aeb2a9de41b47adf82b9452c..6df97a1dd817948fa86d88410445bf89f11bb4fa 100644
--- a/bilby/gw/prior_files/binary_black_holes.prior
+++ b/bilby/gw/prior_files/binary_black_holes.prior
@@ -2,25 +2,25 @@
 # Note that you may wish to use more specific mass and distance parameters.
 # These commands are all known to bilby.gw.prior.
 # Lines beginning "#" are ignored.
-mass_1 = Uniform(name='mass_1', minimum=5, maximum=100, unit='$M_{\\odot}$')
-mass_2 = Uniform(name='mass_2', minimum=5, maximum=100, unit='$M_{\\odot}$')
-mass_ratio =  Constraint(name='mass_ratio', minimum=0.125, maximum=1)
-# chirp_mass = Uniform(name='chirp_mass', minimum=25, maximum=100, unit='$M_{\\odot}$')
-# total_mass =  Uniform(name='total_mass', minimum=10, maximum=200, unit='$M_{\\odot}$')
-# mass_ratio =  Uniform(name='mass_ratio', minimum=0.125, maximum=1)
-# symmetric_mass_ratio =  Uniform(name='symmetric_mass_ratio', minimum=8 / 81, maximum=0.25)
-a_1 =  Uniform(name='a_1', minimum=0, maximum=0.8)
-a_2 =  Uniform(name='a_2', minimum=0, maximum=0.8)
-tilt_1 =  Sine(name='tilt_1')
-tilt_2 =  Sine(name='tilt_2')
-# cos_tilt_1 =  Uniform(name='cos_tilt_1', minimum=-1, maximum=1)
-# cos_tilt_2 =  Uniform(name='cos_tilt_2', minimum=-1, maximum=1)
-phi_12 =  Uniform(name='phi_12', minimum=0, maximum=2 * np.pi)
-phi_jl =  Uniform(name='phi_jl', minimum=0, maximum=2 * np.pi)
-luminosity_distance =  bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=1e2, maximum=5e3, unit='Mpc')
-dec =  Cosine(name='dec')
-ra =  Uniform(name='ra', minimum=0, maximum=2 * np.pi)
-theta_jn =  Sine(name='theta_jn')
-# cos_theta_jn =  Uniform(name='cos_theta_jn', minimum=-1, maximum=1)
-psi =  Uniform(name='psi', minimum=0, maximum=np.pi)
-phase =  Uniform(name='phase', minimum=0, maximum=2 * np.pi)
+mass_1 = Uniform(name='mass_1', minimum=5, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_2 = Uniform(name='mass_2', minimum=5, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_ratio = Constraint(name='mass_ratio', minimum=0.125, maximum=1)
+# chirp_mass = Uniform(name='chirp_mass', minimum=25, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False)
+# total_mass = Uniform(name='total_mass', minimum=10, maximum=200, unit='$M_{\\odot}$', periodic_boundary=False)
+# mass_ratio = Uniform(name='mass_ratio', minimum=0.125, maximum=1, periodic_boundary=False)
+# symmetric_mass_ratio = Uniform(name='symmetric_mass_ratio', minimum=8 / 81, maximum=0.25, periodic_boundary=False)
+a_1 = Uniform(name='a_1', minimum=0, maximum=0.8, periodic_boundary=False)
+a_2 = Uniform(name='a_2', minimum=0, maximum=0.8, periodic_boundary=False)
+tilt_1 = Sine(name='tilt_1', periodic_boundary=False)
+tilt_2 = Sine(name='tilt_2', periodic_boundary=False)
+# cos_tilt_1 = Uniform(name='cos_tilt_1', minimum=-1, maximum=1, periodic_boundary=False)
+# cos_tilt_2 = Uniform(name='cos_tilt_2', minimum=-1, maximum=1, periodic_boundary=False)
+phi_12 = Uniform(name='phi_12', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+phi_jl = Uniform(name='phi_jl', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+luminosity_distance = bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=1e2, maximum=5e3, unit='Mpc', periodic_boundary=False)
+dec = Cosine(name='dec', periodic_boundary=False)
+ra = Uniform(name='ra', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+theta_jn = Sine(name='theta_jn', periodic_boundary=False)
+# cos_theta_jn = Uniform(name='cos_theta_jn', minimum=-1, maximum=1, periodic_boundary=False)
+psi = Uniform(name='psi', minimum=0, maximum=np.pi, periodic_boundary=True)
+phase = Uniform(name='phase', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
diff --git a/bilby/gw/prior_files/binary_neutron_stars.prior b/bilby/gw/prior_files/binary_neutron_stars.prior
index eff1a2f2a4923b520cf3e242ee1bc1545cb6c64f..09acbdd14d8df03ea842872efab4de803c14e611 100644
--- a/bilby/gw/prior_files/binary_neutron_stars.prior
+++ b/bilby/gw/prior_files/binary_neutron_stars.prior
@@ -2,23 +2,23 @@
 # Note that you may wish to use more specific mass and distance parameters.
 # These commands are all known to bilby.gw.prior.
 # Lines beginning "#" are ignored.
-mass_1 = Uniform(name='mass_1', minimum=1, maximum=2, unit='$M_{\\odot}$')
-mass_2 = Uniform(name='mass_2', minimum=1, maximum=2, unit='$M_{\\odot}$')
-mass_ratio =  Constraint(name='mass_ratio', minimum=0.125, maximum=1)
-# chirp_mass = Uniform(name='chirp_mass', minimum=0.87, maximum=1.74, unit='$M_{\\odot}$')
-# total_mass =  Uniform(name='total_mass', minimum=2, maximum=4, unit='$M_{\\odot}$')
-# mass_ratio =  Uniform(name='mass_ratio', minimum=0.5, maximum=1)
-# symmetric_mass_ratio =  Uniform(name='symmetric_mass_ratio', minimum=0.22, maximum=0.25)
-chi_1 =  bilby.gw.prior.AlignedSpin(a_prior=Uniform(0, 0.05), z_prior=Uniform(-1, 1), name='chi_1', latex_label='$\\chi_1$')
-chi_2 =  bilby.gw.prior.AlignedSpin(a_prior=Uniform(0, 0.05), z_prior=Uniform(-1, 1), name='chi_2', latex_label='$\\chi_2$')
-luminosity_distance =  bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=10, maximum=500, unit='Mpc')
-dec =  Cosine(name='dec')
-ra =  Uniform(name='ra', minimum=0, maximum=2 * np.pi)
-theta_jn =  Sine(name='theta_jn')
-# cos_theta_jn =  Uniform(name='cos_theta_jn', minimum=-1, maximum=1)
-psi =  Uniform(name='psi', minimum=0, maximum=np.pi)
-phase =  Uniform(name='phase', minimum=0, maximum=2 * np.pi)
-lambda_1 = Uniform(name='lambda_1', minimum=0, maximum=3000 )
-lambda_2 = Uniform(name='lambda_2', minimum=0, maximum=3000 )
-# lambda_tilde = Uniform(name='lambda_tilde', minimum=0, maximum=5000)
-# delta_lambda = Uniform(name='delta_lambda', minimum=-5000, maximum=5000)
+mass_1 = Uniform(name='mass_1', minimum=1, maximum=2, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_2 = Uniform(name='mass_2', minimum=1, maximum=2, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_ratio = Constraint(name='mass_ratio', minimum=0.125, maximum=1)
+# chirp_mass = Uniform(name='chirp_mass', minimum=0.87, maximum=1.74, unit='$M_{\\odot}$', periodic_boundary=False)
+# total_mass = Uniform(name='total_mass', minimum=2, maximum=4, unit='$M_{\\odot}$', periodic_boundary=False)
+# mass_ratio = Uniform(name='mass_ratio', minimum=0.5, maximum=1, periodic_boundary=False)
+# symmetric_mass_ratio = Uniform(name='symmetric_mass_ratio', minimum=0.22, maximum=0.25, periodic_boundary=False)
+chi_1 = bilby.gw.prior.AlignedSpin(a_prior=Uniform(0, 0.05), z_prior=Uniform(-1, 1), name='chi_1', latex_label='$\\chi_1$', periodic_boundary=False)
+chi_2 = bilby.gw.prior.AlignedSpin(a_prior=Uniform(0, 0.05), z_prior=Uniform(-1, 1), name='chi_2', latex_label='$\\chi_2$', periodic_boundary=False)
+luminosity_distance = bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=10, maximum=500, unit='Mpc', periodic_boundary=False)
+dec = Cosine(name='dec', periodic_boundary=False)
+ra = Uniform(name='ra', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+theta_jn = Sine(name='theta_jn', periodic_boundary=False)
+# cos_theta_jn = Uniform(name='cos_theta_jn', minimum=-1, maximum=1, periodic_boundary=False)
+psi = Uniform(name='psi', minimum=0, maximum=np.pi, periodic_boundary=True)
+phase = Uniform(name='phase', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+lambda_1 = Uniform(name='lambda_1', minimum=0, maximum=3000, periodic_boundary=False)
+lambda_2 = Uniform(name='lambda_2', minimum=0, maximum=3000, periodic_boundary=False)
+# lambda_tilde = Uniform(name='lambda_tilde', minimum=0, maximum=5000, periodic_boundary=False)
+# delta_lambda = Uniform(name='delta_lambda', minimum=-5000, maximum=5000, periodic_boundary=False)
diff --git a/bilby/gw/sampler/__init__.py b/bilby/gw/sampler/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..25ad2ca38576b83835854b025b36f3a73b6a1cb8
--- /dev/null
+++ b/bilby/gw/sampler/__init__.py
@@ -0,0 +1,2 @@
+from __future__ import absolute_import
+from . import proposal
diff --git a/bilby/gw/sampler/proposal.py b/bilby/gw/sampler/proposal.py
new file mode 100644
index 0000000000000000000000000000000000000000..328fd98a89f051b634f99602a5cfba50b7a68dc6
--- /dev/null
+++ b/bilby/gw/sampler/proposal.py
@@ -0,0 +1,49 @@
+import random
+
+import numpy as np
+
+from bilby.core.sampler.proposal import JumpProposal
+
+
+class SkyLocationWanderJump(JumpProposal):
+    """
+    Jump proposal for wandering over the sky location. Does a Gaussian step in
+    RA and DEC depending on the temperature.
+    """
+
+    def __call__(self, sample, **kwargs):
+        temperature = 1 / kwargs.get('inverse_temperature', 1.0)
+        sigma = np.sqrt(temperature) / 2 / np.pi
+        sample['ra'] += random.gauss(0, sigma)
+        sample['dec'] += random.gauss(0, sigma)
+        return super(SkyLocationWanderJump, self).__call__(sample)
+
+
+class CorrelatedPolarisationPhaseJump(JumpProposal):
+    """
+    Correlated polarisation/phase jump proposal. Jumps between degenerate phi/psi regions.
+    """
+
+    def __call__(self, sample, **kwargs):
+        alpha = sample['psi'] + sample['phase']
+        beta = sample['psi'] - sample['phase']
+
+        draw = random.random()
+        if draw < 0.5:
+            alpha = 3.0 * np.pi * random.random()
+        else:
+            beta = 3.0 * np.pi * random.random() - 2 * np.pi
+        sample['psi'] = (alpha + beta) * 0.5
+        sample['phase'] = (alpha - beta) * 0.5
+        return super(CorrelatedPolarisationPhaseJump, self).__call__(sample)
+
+
+class PolarisationPhaseJump(JumpProposal):
+    """
+    Correlated polarisation/phase jump proposal. Jumps between degenerate phi/psi regions.
+    """
+
+    def __call__(self, sample, **kwargs):
+        sample['phase'] += np.pi
+        sample['psi'] += np.pi / 2
+        return super(PolarisationPhaseJump, self).__call__(sample)
diff --git a/bilby/gw/utils.py b/bilby/gw/utils.py
index b0959727f202d07e6e7e69b31030df485ee481d9..3bb38eb7a896dd6cac4ca92aa310f7f61b11a570 100644
--- a/bilby/gw/utils.py
+++ b/bilby/gw/utils.py
@@ -443,7 +443,7 @@ def read_frame_file(file_name, start_time, end_time, channel=None, buffer_time=0
         return None
 
 
-def get_gracedb(gracedb, outdir, duration, calibration, detectors, query_types=None):
+def get_gracedb(gracedb, outdir, duration, calibration, detectors, query_types=None, server=None):
     candidate = gracedb_to_json(gracedb, outdir)
     trigger_time = candidate['gpstime']
     gps_start_time = trigger_time - duration
@@ -453,7 +453,7 @@ def get_gracedb(gracedb, outdir, duration, calibration, detectors, query_types=N
     for i, det in enumerate(detectors):
         output_cache_file = gw_data_find(
             det, gps_start_time, duration, calibration,
-            outdir=outdir, query_type=query_types[i])
+            outdir=outdir, query_type=query_types[i], server=server)
         cache_files.append(output_cache_file)
     return candidate, cache_files
 
@@ -476,7 +476,7 @@ def gracedb_to_json(gracedb, outdir=None):
     logger.info('Initialise client and attempt to download')
     try:
         client = GraceDb()
-    except FileNotFoundError:
+    except IOError:
         raise ValueError(
             'Failed to authenticate with gracedb: check your X509 '
             'certificate is accessible and valid')
@@ -499,7 +499,7 @@ def gracedb_to_json(gracedb, outdir=None):
 
 
 def gw_data_find(observatory, gps_start_time, duration, calibration,
-                 outdir='.', query_type=None):
+                 outdir='.', query_type=None, server=None):
     """ Builds a gw_data_find call and process output
 
     Parameters
@@ -542,6 +542,13 @@ def gw_data_find(observatory, gps_start_time, duration, calibration,
     output_cache_file = os.path.join(outdir, cache_file)
 
     gps_end_time = gps_start_time + duration
+    if server is None:
+        server = os.environ.get("LIGO_DATAFIND_SERVER")
+        if server is None:
+            logger.warning('No data_find server found, defaulting to CIT server. '
+                           'To run on other clusters, pass the output of '
+                           '`echo $LIGO_DATAFIND_SERVER`')
+            server = 'ldr.ldas.cit:80'
 
     cl_list = ['gw_data_find']
     cl_list.append('--observatory {}'.format(observatory_code))
@@ -549,6 +556,7 @@ def gw_data_find(observatory, gps_start_time, duration, calibration,
     cl_list.append('--gps-end-time {}'.format(int(np.ceil(gps_end_time))))
     cl_list.append('--type {}'.format(query_type))
     cl_list.append('--output {}'.format(output_cache_file))
+    cl_list.append('--server {}'.format(server))
     cl_list.append('--url-type file')
     cl_list.append('--lal-cache')
     cl = ' '.join(cl_list)
diff --git a/bilby/gw/waveform_generator.py b/bilby/gw/waveform_generator.py
index 00e24bde621c5b989ba3daca27d4f4ebf4ed2267..98d06914695af20473745e88d3916a0367832e2b 100644
--- a/bilby/gw/waveform_generator.py
+++ b/bilby/gw/waveform_generator.py
@@ -62,6 +62,7 @@ class WaveformGenerator(object):
             self.waveform_arguments = dict()
         if isinstance(parameters, dict):
             self.parameters = parameters
+        self._cache = dict(parameters=None, waveform=None)
 
     def __repr__(self):
         if self.frequency_domain_source_model is not None:
@@ -147,6 +148,8 @@ class WaveformGenerator(object):
                           transformed_model_data_points, parameters):
         if parameters is not None:
             self.parameters = parameters
+        if self.parameters == self._cache['parameters']:
+            return self._cache['waveform']
         if model is not None:
             model_strain = self._strain_from_model(model_data_points, model)
         elif transformed_model is not None:
@@ -154,6 +157,8 @@ class WaveformGenerator(object):
                                                                transformation_function)
         else:
             raise RuntimeError("No source model given")
+        self._cache['waveform'] = model_strain
+        self._cache['parameters'] = self.parameters.copy()
         return model_strain
 
     def _strain_from_model(self, model_data_points, model):
diff --git a/docs/conversion.txt b/docs/conversion.txt
index f130df063d75bbe1dd32888f737c874c48a209a0..d15671aa98f13e7eb2f54fb054ada4383d6d2281 100644
--- a/docs/conversion.txt
+++ b/docs/conversion.txt
@@ -7,5 +7,11 @@ E.g., sampling in chirp mass and mass ratio can be much more efficient than samp
 
 We have many functions to do this.
 
+These are used in multiple places:
+- `PriorDict`s have a `conversion_function`, for the GW PriorDicts, these are from this module.
+- `WaveformGenerator`s can use a `parameter_conversion`, again these are from this module.
+- A `conversion_function` can be passed to `run_sampler`, this is done as a post-processing step.
+For CBCs either `generate_all_bbh_parameters` or `generate_all_bns_parameters` can be used.
+
 .. automodule:: bilby.gw.conversion
     :members:
\ No newline at end of file
diff --git a/docs/prior.txt b/docs/prior.txt
index 867a843516e4c4db7dff685cf8953e431f1f46e6..fe655557ff3ea611c5d4b7c7bfbc8b93f099e2fa 100644
--- a/docs/prior.txt
+++ b/docs/prior.txt
@@ -83,8 +83,9 @@ note that this list is incomplete.
    :members:
    :special-members:
 
+---------------------------
 Multivariate Gaussian prior
-===========================
+---------------------------
 
 We provide a prior class for correlated parameters in the form of a
 `multivariate Gaussian distribution <https://en.wikipedia.org/wiki/Multivariate_normal_distribution>`_.
@@ -130,11 +131,85 @@ The detailed API information for the distribution and prior classes are below:
 .. autoclass:: bilby.core.prior.MultivariateGaussian
    :members:
 
+-----------------------
 Defining your own prior
-=======================
+-----------------------
 
 You can define your own by subclassing the :code:`bilby.prior.Prior` class.
 
 .. autoclass:: bilby.core.prior.Prior
    :members:
    :special-members:
+
+-----------------
+Prior Constraints
+-----------------
+
+Added v. 0.4.3
+
+This allows cuts to be specified in the prior space.
+
+You can provide the `PriorDict` a `conversion_function` and a set of `Constraint` priors to remove parts of the prior space.
+
+*Note*: after doing this the prior probability will not be normalised.
+
+Simple example
+==============
+
+Sample from uniform distributions in two parameters x and y with the condition x >= y.
+
+First thing: define a function which generates z=x-y from x and y.
+
+.. code:: python
+
+    def convert_x_y_to_z(parameters):
+        """
+        Function to convert between sampled parameters and constraint parameter.
+
+        Parameters
+        ----------
+        parameters: dict
+            Dictionary containing sampled parameter values, 'x', 'y'.
+
+        Returns
+        -------
+        dict: Dictionary with constraint parameter 'z' added.
+        """
+        parameters['z'] = parameters['x'] - parameters['y']
+        return parameters
+
+Create our prior:
+
+.. code:: python
+
+    from bilby.core.prior import PriorDict, Uniform, Constraint
+
+    priors = PriorDict(conversion_function=convert_x_y_to_z)
+    priors['x'] = Uniform(minimum=0, maximum=10)
+    priors['y'] = Uniform(minimum=0, maximum=10)
+    priors['z'] = Constraint(minimum=0, maximum=10)
+
+Sample from this distribution and plot the samples.
+
+.. code:: python
+
+    import matplotlib.pyplot as plt
+
+    samples = priors.sample(1000000)
+    plt.hist2d(samples['x'], samples['y'], bins=100, cmap='Blues')
+    plt.xlabel('$x$')
+    plt.ylabel('$y$')
+    plt.tight_layout()
+    plt.show()
+    plt.close()
+
+------
+
+Gravitational wave priors
+=========================
+
+We provide default conversion functions for the BBH and BNS PriorDicts.
+
+For BBHs this generates all the BBH mass parameters so constraints can be placed on any mass parameters.
+
+For BNSs it also generates the tidal deformability parameters.
diff --git a/examples/injection_examples/custom_proposal_example.py b/examples/injection_examples/custom_proposal_example.py
new file mode 100644
index 0000000000000000000000000000000000000000..8480dd361a141b7407d7ab618028ad542d737cd1
--- /dev/null
+++ b/examples/injection_examples/custom_proposal_example.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+"""
+Tutorial for running cpnest with custom jump proposals.
+"""
+from __future__ import division, print_function
+
+import numpy as np
+import bilby.gw.sampler.proposal
+from bilby.core.sampler import proposal
+
+
+# The set up here is the same as in fast_tutorial.py. Look there for descriptive explanations.
+
+duration = 4.
+sampling_frequency = 2048.
+
+outdir = 'outdir'
+label = 'custom_jump_proposals'
+bilby.core.utils.setup_logger(outdir=outdir, label=label)
+
+np.random.seed(88170235)
+
+injection_parameters = dict(
+    mass_1=36., mass_2=29., a_1=0.4, a_2=0.3, tilt_1=0.5, tilt_2=1.0,
+    phi_12=1.7, phi_jl=0.3, luminosity_distance=2000., theta_jn=0.4, psi=2.659,
+    phase=1.3, geocent_time=1126259642.413, ra=1.375, dec=-1.2108)
+waveform_arguments = dict(waveform_approximant='IMRPhenomPv2',
+                          reference_frequency=50., minimum_frequency=20.)
+waveform_generator = bilby.gw.WaveformGenerator(
+    duration=duration, sampling_frequency=sampling_frequency,
+    frequency_domain_source_model=bilby.gw.source.lal_binary_black_hole,
+    waveform_arguments=waveform_arguments)
+ifos = bilby.gw.detector.InterferometerList(['H1', 'L1'])
+ifos.set_strain_data_from_power_spectral_densities(
+    sampling_frequency=sampling_frequency, duration=duration,
+    start_time=injection_parameters['geocent_time'] - 3)
+ifos.inject_signal(waveform_generator=waveform_generator,
+                   parameters=injection_parameters)
+priors = bilby.gw.prior.BBHPriorDict()
+priors['geocent_time'] = bilby.core.prior.Uniform(
+    minimum=injection_parameters['geocent_time'] - 1,
+    maximum=injection_parameters['geocent_time'] + 1,
+    name='geocent_time', latex_label='$t_c$', unit='$s$')
+for key in ['a_1', 'a_2', 'tilt_1', 'tilt_2', 'phi_12', 'phi_jl', 'geocent_time']:
+    priors[key] = injection_parameters[key]
+likelihood = bilby.gw.GravitationalWaveTransient(
+    interferometers=ifos, waveform_generator=waveform_generator)
+
+# Definition of the custom jump proposals. Define a JumpProposalCycle. The first argument is a list
+# of all allowed jump proposals. The second argument is a list of weights for the respective jump
+# proposal.
+
+jump_proposals = proposal.JumpProposalCycle(
+    [proposal.EnsembleWalk(priors=priors), proposal.EnsembleStretch(priors=priors),
+     proposal.DifferentialEvolution(priors=priors), proposal.EnsembleEigenVector(priors=priors),
+     bilby.gw.sampler.proposal.SkyLocationWanderJump(priors=priors), bilby.gw.sampler.proposal.CorrelatedPolarisationPhaseJump(priors=priors),
+     bilby.gw.sampler.proposal.PolarisationPhaseJump(priors=priors), proposal.DrawFlatPrior(priors=priors)],
+    weights=[2, 2, 5, 1, 1, 1, 1, 1])
+
+# Run cpnest with the proposals kwarg specified.
+# Make sure to have a version of cpnest installed that supports custom proposals.
+result = bilby.run_sampler(
+    likelihood=likelihood, priors=priors, sampler='cpnest', npoints=1000,
+    injection_parameters=injection_parameters, outdir=outdir, label=label,
+    proposals=jump_proposals)
+
+# Make a corner plot.
+result.plot_corner()
diff --git a/requirements.txt b/requirements.txt
index bb184b6a62790c07f5b232056a2d89b0291f398e..de58a6b16f36ea4614961dcdb0b54cce46167f8e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,3 +6,4 @@ matplotlib>=2.0
 scipy>=0.16
 pandas
 mock
+dill
diff --git a/setup.py b/setup.py
index 81575551bd6cfc9ae8a6682328739361c9141d8d..43e8190855ca0dbde139eb17cdd1f49817461822 100644
--- a/setup.py
+++ b/setup.py
@@ -57,7 +57,7 @@ def readfile(filename):
     return filecontents
 
 
-VERSION = '0.4.3'
+VERSION = '0.4.4'
 version_file = write_version_file(VERSION)
 long_description = get_long_description()
 
@@ -79,6 +79,7 @@ setup(name='bilby',
           'future',
           'dynesty',
           'corner',
+          'dill',
           'numpy>=1.9',
           'matplotlib>=2.0',
           'pandas',
diff --git a/test/gw_prior_test.py b/test/gw_prior_test.py
index 893d4f3de0ed3391df17b17603e9588d71d2f80e..485341dbfa4fc82895ca48b9afd7d438236cf8b1 100644
--- a/test/gw_prior_test.py
+++ b/test/gw_prior_test.py
@@ -36,8 +36,10 @@ class TestBBHPriorDict(unittest.TestCase):
                       for key in default.keys()])
         names = all([self.bbh_prior_dict[key].name == default[key].name
                      for key in default.keys()])
+        boundaries = all([self.bbh_prior_dict[key].periodic_boundary is default[key].periodic_boundary
+                          for key in default.keys()])
 
-        self.assertTrue(all([minima, maxima, names]))
+        self.assertTrue(all([minima, maxima, names, boundaries]))
 
     def test_create_from_dict(self):
         new_dict = bilby.gw.prior.BBHPriorDict(dictionary=self.prior_dict)
@@ -135,8 +137,10 @@ class TestBNSPriorDict(unittest.TestCase):
                       for key in default.keys()])
         names = all([self.bns_prior_dict[key].name == default[key].name
                      for key in default.keys()])
+        boundaries = all([self.bns_prior_dict[key].periodic_boundary == default[key].periodic_boundary
+                          for key in default.keys()])
 
-        self.assertTrue(all([minima, maxima, names]))
+        self.assertTrue(all([minima, maxima, names, boundaries]))
 
     def test_create_from_dict(self):
         new_dict = bilby.gw.prior.BNSPriorDict(dictionary=self.prior_dict)
diff --git a/test/prior_files/binary_black_holes.prior b/test/prior_files/binary_black_holes.prior
index e79bd5baa754eb09aeb2a9de41b47adf82b9452c..b9abc7a45e36e7f42e9a768128d9e5723643b82d 100644
--- a/test/prior_files/binary_black_holes.prior
+++ b/test/prior_files/binary_black_holes.prior
@@ -2,25 +2,25 @@
 # Note that you may wish to use more specific mass and distance parameters.
 # These commands are all known to bilby.gw.prior.
 # Lines beginning "#" are ignored.
-mass_1 = Uniform(name='mass_1', minimum=5, maximum=100, unit='$M_{\\odot}$')
-mass_2 = Uniform(name='mass_2', minimum=5, maximum=100, unit='$M_{\\odot}$')
-mass_ratio =  Constraint(name='mass_ratio', minimum=0.125, maximum=1)
-# chirp_mass = Uniform(name='chirp_mass', minimum=25, maximum=100, unit='$M_{\\odot}$')
-# total_mass =  Uniform(name='total_mass', minimum=10, maximum=200, unit='$M_{\\odot}$')
-# mass_ratio =  Uniform(name='mass_ratio', minimum=0.125, maximum=1)
-# symmetric_mass_ratio =  Uniform(name='symmetric_mass_ratio', minimum=8 / 81, maximum=0.25)
-a_1 =  Uniform(name='a_1', minimum=0, maximum=0.8)
-a_2 =  Uniform(name='a_2', minimum=0, maximum=0.8)
-tilt_1 =  Sine(name='tilt_1')
-tilt_2 =  Sine(name='tilt_2')
-# cos_tilt_1 =  Uniform(name='cos_tilt_1', minimum=-1, maximum=1)
-# cos_tilt_2 =  Uniform(name='cos_tilt_2', minimum=-1, maximum=1)
-phi_12 =  Uniform(name='phi_12', minimum=0, maximum=2 * np.pi)
-phi_jl =  Uniform(name='phi_jl', minimum=0, maximum=2 * np.pi)
-luminosity_distance =  bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=1e2, maximum=5e3, unit='Mpc')
-dec =  Cosine(name='dec')
-ra =  Uniform(name='ra', minimum=0, maximum=2 * np.pi)
-theta_jn =  Sine(name='theta_jn')
-# cos_theta_jn =  Uniform(name='cos_theta_jn', minimum=-1, maximum=1)
-psi =  Uniform(name='psi', minimum=0, maximum=np.pi)
-phase =  Uniform(name='phase', minimum=0, maximum=2 * np.pi)
+mass_1 = Uniform(name='mass_1', minimum=5, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_2 = Uniform(name='mass_2', minimum=5, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_ratio = Constraint(name='mass_ratio', minimum=0.125, maximum=1)
+# chirp_mass = Uniform(name='chirp_mass', minimum=25, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False)
+# total_mass = Uniform(name='total_mass', minimum=10, maximum=200, unit='$M_{\\odot}$', periodic_boundary=False)
+# mass_ratio = Uniform(name='mass_ratio', minimum=0.125, maximum=1, periodic_boundary=False)
+# symmetric_mass_ratio =  Uniform(name='symmetric_mass_ratio', minimum=8 / 81, maximum=0.25, periodic_boundary=False)
+a_1 = Uniform(name='a_1', minimum=0, maximum=0.8, periodic_boundary=False)
+a_2 = Uniform(name='a_2', minimum=0, maximum=0.8, periodic_boundary=False)
+tilt_1 = Sine(name='tilt_1', periodic_boundary=False)
+tilt_2 = Sine(name='tilt_2', periodic_boundary=False)
+# cos_tilt_1 = Uniform(name='cos_tilt_1', minimum=-1, maximum=1, periodic_boundary=False)
+# cos_tilt_2 = Uniform(name='cos_tilt_2', minimum=-1, maximum=1, periodic_boundary=False)
+phi_12 = Uniform(name='phi_12', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+phi_jl = Uniform(name='phi_jl', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+luminosity_distance = bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=1e2, maximum=5e3, unit='Mpc', periodic_boundary=False)
+dec = Cosine(name='dec', periodic_boundary=False)
+ra = Uniform(name='ra', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+theta_jn = Sine(name='theta_jn', periodic_boundary=False)
+# cos_theta_jn = Uniform(name='cos_theta_jn', minimum=-1, maximum=1, periodic_boundary=False)
+psi = Uniform(name='psi', minimum=0, maximum=np.pi, periodic_boundary=True)
+phase = Uniform(name='phase', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
diff --git a/test/prior_files/binary_neutron_stars.prior b/test/prior_files/binary_neutron_stars.prior
index eff1a2f2a4923b520cf3e242ee1bc1545cb6c64f..09acbdd14d8df03ea842872efab4de803c14e611 100644
--- a/test/prior_files/binary_neutron_stars.prior
+++ b/test/prior_files/binary_neutron_stars.prior
@@ -2,23 +2,23 @@
 # Note that you may wish to use more specific mass and distance parameters.
 # These commands are all known to bilby.gw.prior.
 # Lines beginning "#" are ignored.
-mass_1 = Uniform(name='mass_1', minimum=1, maximum=2, unit='$M_{\\odot}$')
-mass_2 = Uniform(name='mass_2', minimum=1, maximum=2, unit='$M_{\\odot}$')
-mass_ratio =  Constraint(name='mass_ratio', minimum=0.125, maximum=1)
-# chirp_mass = Uniform(name='chirp_mass', minimum=0.87, maximum=1.74, unit='$M_{\\odot}$')
-# total_mass =  Uniform(name='total_mass', minimum=2, maximum=4, unit='$M_{\\odot}$')
-# mass_ratio =  Uniform(name='mass_ratio', minimum=0.5, maximum=1)
-# symmetric_mass_ratio =  Uniform(name='symmetric_mass_ratio', minimum=0.22, maximum=0.25)
-chi_1 =  bilby.gw.prior.AlignedSpin(a_prior=Uniform(0, 0.05), z_prior=Uniform(-1, 1), name='chi_1', latex_label='$\\chi_1$')
-chi_2 =  bilby.gw.prior.AlignedSpin(a_prior=Uniform(0, 0.05), z_prior=Uniform(-1, 1), name='chi_2', latex_label='$\\chi_2$')
-luminosity_distance =  bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=10, maximum=500, unit='Mpc')
-dec =  Cosine(name='dec')
-ra =  Uniform(name='ra', minimum=0, maximum=2 * np.pi)
-theta_jn =  Sine(name='theta_jn')
-# cos_theta_jn =  Uniform(name='cos_theta_jn', minimum=-1, maximum=1)
-psi =  Uniform(name='psi', minimum=0, maximum=np.pi)
-phase =  Uniform(name='phase', minimum=0, maximum=2 * np.pi)
-lambda_1 = Uniform(name='lambda_1', minimum=0, maximum=3000 )
-lambda_2 = Uniform(name='lambda_2', minimum=0, maximum=3000 )
-# lambda_tilde = Uniform(name='lambda_tilde', minimum=0, maximum=5000)
-# delta_lambda = Uniform(name='delta_lambda', minimum=-5000, maximum=5000)
+mass_1 = Uniform(name='mass_1', minimum=1, maximum=2, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_2 = Uniform(name='mass_2', minimum=1, maximum=2, unit='$M_{\\odot}$', periodic_boundary=False)
+mass_ratio = Constraint(name='mass_ratio', minimum=0.125, maximum=1)
+# chirp_mass = Uniform(name='chirp_mass', minimum=0.87, maximum=1.74, unit='$M_{\\odot}$', periodic_boundary=False)
+# total_mass = Uniform(name='total_mass', minimum=2, maximum=4, unit='$M_{\\odot}$', periodic_boundary=False)
+# mass_ratio = Uniform(name='mass_ratio', minimum=0.5, maximum=1, periodic_boundary=False)
+# symmetric_mass_ratio = Uniform(name='symmetric_mass_ratio', minimum=0.22, maximum=0.25, periodic_boundary=False)
+chi_1 = bilby.gw.prior.AlignedSpin(a_prior=Uniform(0, 0.05), z_prior=Uniform(-1, 1), name='chi_1', latex_label='$\\chi_1$', periodic_boundary=False)
+chi_2 = bilby.gw.prior.AlignedSpin(a_prior=Uniform(0, 0.05), z_prior=Uniform(-1, 1), name='chi_2', latex_label='$\\chi_2$', periodic_boundary=False)
+luminosity_distance = bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=10, maximum=500, unit='Mpc', periodic_boundary=False)
+dec = Cosine(name='dec', periodic_boundary=False)
+ra = Uniform(name='ra', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+theta_jn = Sine(name='theta_jn', periodic_boundary=False)
+# cos_theta_jn = Uniform(name='cos_theta_jn', minimum=-1, maximum=1, periodic_boundary=False)
+psi = Uniform(name='psi', minimum=0, maximum=np.pi, periodic_boundary=True)
+phase = Uniform(name='phase', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+lambda_1 = Uniform(name='lambda_1', minimum=0, maximum=3000, periodic_boundary=False)
+lambda_2 = Uniform(name='lambda_2', minimum=0, maximum=3000, periodic_boundary=False)
+# lambda_tilde = Uniform(name='lambda_tilde', minimum=0, maximum=5000, periodic_boundary=False)
+# delta_lambda = Uniform(name='delta_lambda', minimum=-5000, maximum=5000, periodic_boundary=False)
diff --git a/test/prior_test.py b/test/prior_test.py
index ce3ab12041c2343744e44e1a5b6f1b7eafc10411..4734d80e2845e51d1b4877dda62f75579e915031 100644
--- a/test/prior_test.py
+++ b/test/prior_test.py
@@ -35,8 +35,10 @@ class TestPriorInstantiationWithoutOptionalPriors(unittest.TestCase):
         self.assertIsNone(self.prior.rescale(1))
 
     def test_base_repr(self):
-        self.prior = bilby.core.prior.Prior(name='test_name', latex_label='test_label', minimum=0, maximum=1)
-        expected_string = "Prior(name='test_name', latex_label='test_label', unit=None, minimum=0, maximum=1)"
+        self.prior = bilby.core.prior.Prior(name='test_name', latex_label='test_label', minimum=0, maximum=1,
+                                            periodic_boundary=False)
+        expected_string = "Prior(name='test_name', latex_label='test_label', unit=None, minimum=0, maximum=1, " \
+                          "periodic_boundary=False)"
         self.assertEqual(expected_string, self.prior.__repr__())
 
     def test_base_prob(self):
@@ -60,6 +62,9 @@ class TestPriorInstantiationWithoutOptionalPriors(unittest.TestCase):
         self.assertFalse(self.prior.is_in_prior_range(val_below))
         self.assertFalse(self.prior.is_in_prior_range(val_above))
 
+    def test_periodic_boundary_is_false(self):
+        self.assertFalse(self.prior.periodic_boundary)
+
 
 class TestPriorName(unittest.TestCase):
 
@@ -105,7 +110,7 @@ class TestPriorIsFixed(unittest.TestCase):
         pass
 
     def tearDown(self):
-        pass
+        del self.prior
 
     def test_is_fixed_parent_class(self):
         self.prior = bilby.core.prior.Prior()
@@ -120,6 +125,23 @@ class TestPriorIsFixed(unittest.TestCase):
         self.assertFalse(self.prior.is_fixed)
 
 
+class TestPriorBoundary(unittest.TestCase):
+
+    def setUp(self):
+        self.prior = bilby.core.prior.Prior(periodic_boundary=False)
+
+    def tearDown(self):
+        del self.prior
+
+    def test_set_boundary_valid(self):
+        self.prior.periodic_boundary = True
+        self.assertTrue(self.prior.periodic_boundary)
+
+    def test_set_boundary_invalid(self):
+        with self.assertRaises(ValueError):
+            self.prior.periodic_boundary = 'else'
+
+
 class TestPriorClasses(unittest.TestCase):
 
     def setUp(self):
@@ -163,7 +185,6 @@ class TestPriorClasses(unittest.TestCase):
             bilby.core.prior.Lorentzian(name='test', unit='unit', alpha=0, beta=1),
             bilby.core.prior.Gamma(name='test', unit='unit', k=1, theta=1),
             bilby.core.prior.ChiSquared(name='test', unit='unit', nu=2),
-            bilby.core.prior.FermiDirac(name='test', unit='unit', sigma=1., r=10.),
             bilby.gw.prior.AlignedSpin(name='test', unit='unit'),
             bilby.core.prior.MultivariateGaussian(mvg=mvg, name='testa', unit='unit'),
             bilby.core.prior.MultivariateGaussian(mvg=mvg, name='testb', unit='unit'),
@@ -489,8 +510,9 @@ class TestPriorClasses(unittest.TestCase):
 class TestPriorDict(unittest.TestCase):
 
     def setUp(self):
-        self.first_prior = bilby.core.prior.Uniform(name='a', minimum=0, maximum=1, unit='kg')
-        self.second_prior = bilby.core.prior.PowerLaw(name='b', alpha=3, minimum=1, maximum=2, unit='m/s')
+        self.first_prior = bilby.core.prior.Uniform(name='a', minimum=0, maximum=1, unit='kg', periodic_boundary=False)
+        self.second_prior = bilby.core.prior.PowerLaw(name='b', alpha=3, minimum=1, maximum=2, unit='m/s',
+                                                      periodic_boundary=False)
         self.third_prior = bilby.core.prior.DeltaFunction(name='c', peak=42, unit='m')
         self.priors = dict(mass=self.first_prior,
                            speed=self.second_prior,
@@ -530,43 +552,46 @@ class TestPriorDict(unittest.TestCase):
     def test_read_from_file(self):
         expected = dict(
             mass_1=bilby.core.prior.Uniform(
-                name='mass_1', minimum=5, maximum=100, unit='$M_{\\odot}$'),
+                name='mass_1', minimum=5, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False),
             mass_2=bilby.core.prior.Uniform(
-                name='mass_2', minimum=5, maximum=100, unit='$M_{\\odot}$'),
-            mass_ratio=bilby.core.prior.Constraint(
-                name='mass_ratio', minimum=0.125, maximum=1.0),
-            a_1=bilby.core.prior.Uniform(name='a_1', minimum=0, maximum=0.8),
-            a_2=bilby.core.prior.Uniform(name='a_2', minimum=0, maximum=0.8),
-            tilt_1=bilby.core.prior.Sine(name='tilt_1'),
-            tilt_2=bilby.core.prior.Sine(name='tilt_2'),
+                name='mass_2', minimum=5, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False),
+            mass_ratio=bilby.core.prior.Constraint(name='mass_ratio', minimum=0.125, maximum=1, latex_label='$q$',
+                                                   unit=None),
+            a_1=bilby.core.prior.Uniform(name='a_1', minimum=0, maximum=0.8, periodic_boundary=False),
+            a_2=bilby.core.prior.Uniform(name='a_2', minimum=0, maximum=0.8, periodic_boundary=False),
+            tilt_1=bilby.core.prior.Sine(name='tilt_1', periodic_boundary=False),
+            tilt_2=bilby.core.prior.Sine(name='tilt_2', periodic_boundary=False),
             phi_12=bilby.core.prior.Uniform(
-                name='phi_12', minimum=0, maximum=2 * np.pi),
+                name='phi_12', minimum=0, maximum=2 * np.pi, periodic_boundary=True),
             phi_jl=bilby.core.prior.Uniform(
-                name='phi_jl', minimum=0, maximum=2 * np.pi),
+                name='phi_jl', minimum=0, maximum=2 * np.pi, periodic_boundary=True),
             luminosity_distance=bilby.gw.prior.UniformComovingVolume(
                 name='luminosity_distance', minimum=1e2,
-                maximum=5e3, unit='Mpc'),
-            dec=bilby.core.prior.Cosine(name='dec'),
+                maximum=5e3, unit='Mpc', periodic_boundary=False),
+            dec=bilby.core.prior.Cosine(name='dec', periodic_boundary=False),
             ra=bilby.core.prior.Uniform(
-                name='ra', minimum=0, maximum=2 * np.pi),
-            theta_jn=bilby.core.prior.Sine(name='theta_jn'),
-            psi=bilby.core.prior.Uniform(name='psi', minimum=0, maximum=np.pi),
+                name='ra', minimum=0, maximum=2 * np.pi, periodic_boundary=True),
+            theta_jn=bilby.core.prior.Sine(name='theta_jn', periodic_boundary=False),
+            psi=bilby.core.prior.Uniform(name='psi', minimum=0, maximum=np.pi, periodic_boundary=True),
             phase=bilby.core.prior.Uniform(
-                name='phase', minimum=0, maximum=2 * np.pi)
+                name='phase', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
             )
         self.assertDictEqual(expected, self.prior_set_from_file)
 
     def test_to_file(self):
         expected = ["length = DeltaFunction(peak=42, name='c', latex_label='c', unit='m')\n",
-                    "speed = PowerLaw(alpha=3, minimum=1, maximum=2, name='b', latex_label='b', unit='m/s')\n",
-                    "mass = Uniform(minimum=0, maximum=1, name='a', latex_label='a', unit='kg')\n"]
+                    "speed = PowerLaw(alpha=3, minimum=1, maximum=2, name='b', latex_label='b', "
+                    "unit='m/s', periodic_boundary=False)\n",
+                    "mass = Uniform(minimum=0, maximum=1, name='a', latex_label='a', "
+                    "unit='kg', periodic_boundary=False)\n"]
         self.prior_set_from_dict.to_file(outdir='prior_files', label='to_file_test')
         with open('prior_files/to_file_test.prior') as f:
             for i, line in enumerate(f.readlines()):
                 self.assertTrue(line in expected)
 
     def test_from_dict_with_string(self):
-        string_prior = "bilby.core.prior.PowerLaw(name='b', alpha=3, minimum=1, maximum=2, unit='m/s')"
+        string_prior = "bilby.core.prior.PowerLaw(name='b', alpha=3, minimum=1, maximum=2, unit='m/s', " \
+                       "periodic_boundary=False)"
         self.priors['speed'] = string_prior
         from_dict = bilby.core.prior.PriorDict(dictionary=self.priors)
         self.assertDictEqual(self.prior_set_from_dict, from_dict)
@@ -576,8 +601,10 @@ class TestPriorDict(unittest.TestCase):
         self.prior_set_from_dict['e'] = 7.3
         self.prior_set_from_dict['f'] = 'unconvertable'
         self.prior_set_from_dict.convert_floats_to_delta_functions()
-        expected = dict(mass=bilby.core.prior.Uniform(name='a', minimum=0, maximum=1, unit='kg'),
-                        speed=bilby.core.prior.PowerLaw(name='b', alpha=3, minimum=1, maximum=2, unit='m/s'),
+        expected = dict(mass=bilby.core.prior.Uniform(name='a', minimum=0, maximum=1, unit='kg',
+                                                      periodic_boundary=False),
+                        speed=bilby.core.prior.PowerLaw(name='b', alpha=3, minimum=1, maximum=2, unit='m/s',
+                                                        periodic_boundary=False),
                         length=bilby.core.prior.DeltaFunction(name='c', peak=42, unit='m'),
                         d=bilby.core.prior.DeltaFunction(peak=5),
                         e=bilby.core.prior.DeltaFunction(peak=7.3),
@@ -586,33 +613,37 @@ class TestPriorDict(unittest.TestCase):
 
     def test_prior_set_from_dict_but_using_a_string(self):
         prior_set = bilby.core.prior.PriorDict(dictionary=self.default_prior_file)
-        expected = dict(
-            mass_1=bilby.core.prior.Uniform(
-                name='mass_1', minimum=5, maximum=100, unit='$M_{\\odot}$'),
-            mass_2=bilby.core.prior.Uniform(
-                name='mass_2', minimum=5, maximum=100, unit='$M_{\\odot}$'),
-            mass_ratio=bilby.core.prior.Constraint(
-                name='mass_ratio', minimum=0.125, maximum=1.0),
-            a_1=bilby.core.prior.Uniform(name='a_1', minimum=0, maximum=0.8),
-            a_2=bilby.core.prior.Uniform(name='a_2', minimum=0, maximum=0.8),
-            tilt_1=bilby.core.prior.Sine(name='tilt_1'),
-            tilt_2=bilby.core.prior.Sine(name='tilt_2'),
-            phi_12=bilby.core.prior.Uniform(
-                name='phi_12', minimum=0, maximum=2 * np.pi),
-            phi_jl=bilby.core.prior.Uniform(
-                name='phi_jl', minimum=0, maximum=2 * np.pi),
-            luminosity_distance=bilby.gw.prior.UniformComovingVolume(
-                name='luminosity_distance', minimum=1e2,
-                maximum=5e3, unit='Mpc'),
-            dec=bilby.core.prior.Cosine(name='dec'),
-            ra=bilby.core.prior.Uniform(
-                name='ra', minimum=0, maximum=2 * np.pi),
-            theta_jn=bilby.core.prior.Sine(name='theta_jn'),
-            psi=bilby.core.prior.Uniform(name='psi', minimum=0, maximum=np.pi),
-            phase=bilby.core.prior.Uniform(
-                name='phase', minimum=0, maximum=2 * np.pi)
+        expected = bilby.core.prior.PriorDict(
+            dict(
+                mass_1=bilby.core.prior.Uniform(
+                    name='mass_1', minimum=5, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False),
+                mass_2=bilby.core.prior.Uniform(
+                    name='mass_2', minimum=5, maximum=100, unit='$M_{\\odot}$', periodic_boundary=False),
+                mass_ratio=bilby.core.prior.Constraint(name='mass_ratio', minimum=0.125, maximum=1, latex_label='$q$',
+                                                       unit=None),
+                a_1=bilby.core.prior.Uniform(name='a_1', minimum=0, maximum=0.8, periodic_boundary=False),
+                a_2=bilby.core.prior.Uniform(name='a_2', minimum=0, maximum=0.8, periodic_boundary=False),
+                tilt_1=bilby.core.prior.Sine(name='tilt_1', periodic_boundary=False),
+                tilt_2=bilby.core.prior.Sine(name='tilt_2', periodic_boundary=False),
+                phi_12=bilby.core.prior.Uniform(
+                    name='phi_12', minimum=0, maximum=2 * np.pi, periodic_boundary=True),
+                phi_jl=bilby.core.prior.Uniform(
+                    name='phi_jl', minimum=0, maximum=2 * np.pi, periodic_boundary=True),
+                luminosity_distance=bilby.gw.prior.UniformComovingVolume(
+                    name='luminosity_distance', minimum=1e2,
+                    maximum=5e3, unit='Mpc', periodic_boundary=False),
+                dec=bilby.core.prior.Cosine(name='dec', periodic_boundary=False),
+                ra=bilby.core.prior.Uniform(
+                    name='ra', minimum=0, maximum=2 * np.pi, periodic_boundary=True),
+                theta_jn=bilby.core.prior.Sine(name='theta_jn', periodic_boundary=False),
+                psi=bilby.core.prior.Uniform(name='psi', minimum=0, maximum=np.pi, periodic_boundary=True),
+                phase=bilby.core.prior.Uniform(
+                    name='phase', minimum=0, maximum=2 * np.pi, periodic_boundary=True)
+            )
         )
-        self.assertDictEqual(expected, prior_set)
+        all_keys = set(prior_set.keys()).union(set(expected.keys()))
+        for key in all_keys:
+            self.assertEqual(expected[key], prior_set[key])
 
     def test_dict_argument_is_not_string_or_dict(self):
         with self.assertRaises(ValueError):
diff --git a/test/proposal_test.py b/test/proposal_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc86444c455d019b8f44886d0ba6cd9c7be839e7
--- /dev/null
+++ b/test/proposal_test.py
@@ -0,0 +1,433 @@
+import unittest
+import mock
+import random
+
+import numpy as np
+
+import bilby.gw.sampler.proposal
+from bilby.core import prior
+from bilby.core.sampler import proposal
+
+
+class TestSample(unittest.TestCase):
+
+    def setUp(self):
+        self.sample = proposal.Sample(dict(a=1, c=2))
+
+    def tearDown(self):
+        del self.sample
+
+    def test_add_sample(self):
+        other = proposal.Sample(dict(a=2, c=5))
+        expected = proposal.Sample(dict(a=3, c=7))
+        self.assertDictEqual(expected, self.sample + other)
+
+    def test_subtract_sample(self):
+        other = proposal.Sample(dict(a=2, c=5))
+        expected = proposal.Sample(dict(a=-1, c=-3))
+        self.assertDictEqual(expected, self.sample - other)
+
+    def test_multiply_sample(self):
+        other = 2
+        expected = proposal.Sample(dict(a=2, c=4))
+        self.assertDictEqual(expected, self.sample * other)
+
+
+class TestJumpProposal(unittest.TestCase):
+
+    def setUp(self):
+        self.priors = prior.PriorDict(dict(reflecting=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=False),
+                                           periodic=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=True),
+                                           default=prior.Uniform(minimum=-0.5, maximum=1)))
+        self.sample_above = dict(reflecting=1.1, periodic=1.1, default=1.1)
+        self.sample_below = dict(reflecting=-0.6, periodic=-0.6, default=-0.6)
+        self.sample_way_above_case1 = dict(reflecting=272, periodic=272, default=272)
+        self.sample_way_above_case2 = dict(reflecting=270.1, periodic=270.1, default=270.1)
+        self.sample_way_below_case1 = dict(reflecting=-274, periodic=-274.1, default=-274)
+        self.sample_way_below_case2 = dict(reflecting=-273.1, periodic=-273.1, default=-273.1)
+        self.jump_proposal = proposal.JumpProposal(priors=self.priors)
+
+    def tearDown(self):
+        del self.priors
+        del self.sample_above
+        del self.sample_below
+        del self.sample_way_above_case1
+        del self.sample_way_above_case2
+        del self.sample_way_below_case1
+        del self.sample_way_below_case2
+        del self.jump_proposal
+
+    def test_set_get_log_j(self):
+        self.jump_proposal.log_j = 2.3
+        self.assertEqual(2.3, self.jump_proposal.log_j)
+
+    def test_boundary_above_reflecting(self):
+        new_sample = self.jump_proposal(self.sample_above)
+        self.assertAlmostEqual(0.9, new_sample['reflecting'])
+
+    def test_boundary_above_periodic(self):
+        new_sample = self.jump_proposal(self.sample_above)
+        self.assertAlmostEqual(-0.4, new_sample['periodic'])
+
+    def test_boundary_above_default(self):
+        new_sample = self.jump_proposal(self.sample_above)
+        self.assertAlmostEqual(0.9, new_sample['default'])
+
+    def test_boundary_below_reflecting(self):
+        new_sample = self.jump_proposal(self.sample_below)
+        self.assertAlmostEqual(-0.4, new_sample['reflecting'])
+
+    def test_boundary_below_periodic(self):
+        new_sample = self.jump_proposal(self.sample_below)
+        self.assertAlmostEqual(0.9, new_sample['periodic'])
+
+    def test_boundary_below_default(self):
+        new_sample = self.jump_proposal(self.sample_below)
+        self.assertAlmostEqual(-0.4, new_sample['default'])
+
+    def test_boundary_way_below_reflecting_case1(self):
+        new_sample = self.jump_proposal(self.sample_way_below_case1)
+        self.assertAlmostEqual(0.0, new_sample['reflecting'])
+
+    def test_boundary_way_below_reflecting_case2(self):
+        new_sample = self.jump_proposal(self.sample_way_below_case2)
+        self.assertAlmostEqual(-0.1, new_sample['reflecting'])
+
+    def test_boundary_way_below_periodic(self):
+        new_sample = self.jump_proposal(self.sample_way_below_case2)
+        self.assertAlmostEqual(-0.1, new_sample['periodic'])
+
+    def test_boundary_way_above_reflecting_case1(self):
+        new_sample = self.jump_proposal(self.sample_way_above_case1)
+        self.assertAlmostEqual(0.0, new_sample['reflecting'])
+
+    def test_boundary_way_above_reflecting_case2(self):
+        new_sample = self.jump_proposal(self.sample_way_above_case2)
+        self.assertAlmostEqual(0.1, new_sample['reflecting'])
+
+    def test_boundary_way_above_periodic(self):
+        new_sample = self.jump_proposal(self.sample_way_above_case2)
+        self.assertAlmostEqual(0.1, new_sample['periodic'])
+
+    def test_priors(self):
+        self.assertEqual(self.priors, self.jump_proposal.priors)
+
+
+class TestNormJump(unittest.TestCase):
+
+    def setUp(self):
+        self.priors = prior.PriorDict(dict(reflecting=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=True),
+                                           periodic=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=False),
+                                           default=prior.Uniform(minimum=-0.5, maximum=1)))
+        self.jump_proposal = proposal.NormJump(step_size=3.0, priors=self.priors)
+
+    def tearDown(self):
+        del self.priors
+        del self.jump_proposal
+
+    def test_step_size_init(self):
+        self.assertEqual(3.0, self.jump_proposal.step_size)
+
+    def test_set_step_size(self):
+        self.jump_proposal.step_size = 1.0
+        self.assertEqual(1.0, self.jump_proposal.step_size)
+
+    def test_jump_proposal_call(self):
+        with mock.patch("numpy.random.normal") as m:
+            m.return_value = 0.5
+            sample = proposal.Sample(dict(reflecting=0.0, periodic=0.0, default=0.0))
+            new_sample = self.jump_proposal(sample)
+            expected = proposal.Sample(dict(reflecting=0.5, periodic=0.5, default=0.5))
+            self.assertDictEqual(expected, new_sample)
+
+
+class TestEnsembleWalk(unittest.TestCase):
+
+    def setUp(self):
+        self.priors = prior.PriorDict(dict(reflecting=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=False),
+                                           periodic=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=True),
+                                           default=prior.Uniform(minimum=-0.5, maximum=1)))
+        self.jump_proposal = proposal.EnsembleWalk(random_number_generator=random.random,
+                                                   n_points=4, priors=self.priors)
+
+    def tearDown(self):
+        del self.priors
+        del self.jump_proposal
+
+    def test_n_points_init(self):
+        self.assertEqual(4, self.jump_proposal.n_points)
+
+    def test_set_n_points(self):
+        self.jump_proposal.n_points = 3
+        self.assertEqual(3, self.jump_proposal.n_points)
+
+    def test_random_number_generator_init(self):
+        self.assertEqual(random.random, self.jump_proposal.random_number_generator)
+
+    def test_get_center_of_mass(self):
+        samples = [proposal.Sample(dict(reflecting=0.1*i, periodic=0.1*i, default=0.1*i)) for i in range(3)]
+        expected = proposal.Sample(dict(reflecting=0.1, periodic=0.1, default=0.1))
+        actual = self.jump_proposal.get_center_of_mass(samples)
+        for key in samples[0].keys():
+            self.assertAlmostEqual(expected[key], actual[key])
+
+    def test_jump_proposal_call(self):
+        with mock.patch('random.sample') as m:
+            self.jump_proposal.random_number_generator = lambda: 2
+            m.return_value = [proposal.Sample(dict(periodic=0.3, reflecting=0.3, default=0.3)),
+                              proposal.Sample(dict(periodic=0.1, reflecting=0.1, default=0.1))]
+            sample = proposal.Sample(dict(periodic=0.1, reflecting=0.1, default=0.1))
+            new_sample = self.jump_proposal(sample, coordinates=None)
+            expected = proposal.Sample(dict(periodic=0.1, reflecting=0.1, default=0.1))
+            for key, value in new_sample.items():
+                self.assertAlmostEqual(expected[key], value)
+
+
+class TestEnsembleEnsembleStretch(unittest.TestCase):
+
+    def setUp(self):
+        self.priors = prior.PriorDict(dict(reflecting=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=False),
+                                           periodic=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=True),
+                                           default=prior.Uniform(minimum=-0.5, maximum=1)))
+        self.jump_proposal = proposal.EnsembleStretch(scale=3.0, priors=self.priors)
+
+    def tearDown(self):
+        del self.priors
+        del self.jump_proposal
+
+    def test_scale_init(self):
+        self.assertEqual(3.0, self.jump_proposal.scale)
+
+    def test_set_get_scale(self):
+        self.jump_proposal.scale = 5.0
+        self.assertEqual(5.0, self.jump_proposal.scale)
+
+    def test_jump_proposal_call(self):
+        with mock.patch('random.choice') as m:
+            with mock.patch('random.uniform') as n:
+                second_sample = proposal.Sample(dict(periodic=0.3, reflecting=0.3, default=0.3))
+                random_number = 0.5
+                m.return_value = second_sample
+                n.return_value = random_number
+                sample = proposal.Sample(dict(periodic=0.1, reflecting=0.1, default=0.1))
+                new_sample = self.jump_proposal(sample, coordinates=None)
+                coords = 0.3 - 0.2 * np.exp(random_number * np.log(self.jump_proposal.scale))
+                expected = proposal.Sample(dict(periodic=coords, reflecting=coords, default=coords))
+                for key, value in new_sample.items():
+                    self.assertAlmostEqual(expected[key], value)
+
+    def test_log_j_after_call(self):
+        with mock.patch('random.uniform') as m1:
+            with mock.patch('numpy.log') as m2:
+                with mock.patch('numpy.exp') as m3:
+                    m1.return_value = 1
+                    m2.return_value = 1
+                    m3.return_value = 1
+                    coordinates = [proposal.Sample(dict(periodic=0.3, reflecting=0.3, default=0.3)),
+                                   proposal.Sample(dict(periodic=0.3, reflecting=0.3, default=0.3))]
+                    sample = proposal.Sample(dict(periodic=0.2, reflecting=0.2, default=0.2))
+                    self.jump_proposal(sample=sample,
+                                       coordinates=coordinates)
+                    self.assertEqual(3, self.jump_proposal.log_j)
+
+
+class TestDifferentialEvolution(unittest.TestCase):
+
+    def setUp(self):
+        self.priors = prior.PriorDict(dict(reflecting=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=False),
+                                           periodic=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=True),
+                                           default=prior.Uniform(minimum=-0.5, maximum=1)))
+        self.jump_proposal = proposal.DifferentialEvolution(sigma=1e-3, mu=0.5, priors=self.priors)
+
+    def tearDown(self):
+        del self.priors
+        del self.jump_proposal
+
+    def test_mu_init(self):
+        self.assertEqual(0.5, self.jump_proposal.mu)
+
+    def test_set_get_mu(self):
+        self.jump_proposal.mu = 1
+        self.assertEqual(1, self.jump_proposal.mu)
+
+    def test_set_get_sigma(self):
+        self.jump_proposal.sigma = 2
+        self.assertEqual(2, self.jump_proposal.sigma)
+
+    def test_jump_proposal_call(self):
+        with mock.patch('random.sample') as m:
+            with mock.patch('random.gauss') as n:
+                m.return_value = proposal.Sample(dict(periodic=0.2, reflecting=0.2, default=0.2)),\
+                                 proposal.Sample(dict(periodic=0.3, reflecting=0.3, default=0.3))
+                n.return_value = 1
+                sample = proposal.Sample(dict(periodic=0.1, reflecting=0.1, default=0.1))
+                expected = proposal.Sample(dict(periodic=0.2, reflecting=0.2, default=0.2))
+                new_sample = self.jump_proposal(sample, coordinates=None)
+                for key, value in new_sample.items():
+                    self.assertAlmostEqual(expected[key], value)
+
+
+class TestEnsembleEigenVector(unittest.TestCase):
+
+    def setUp(self):
+        self.priors = prior.PriorDict(dict(reflecting=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=False),
+                                           periodic=prior.Uniform(minimum=-0.5, maximum=1, periodic_boundary=True),
+                                           default=prior.Uniform(minimum=-0.5, maximum=1)))
+        self.jump_proposal = proposal.EnsembleEigenVector(priors=self.priors)
+
+    def tearDown(self):
+        del self.priors
+        del self.jump_proposal
+
+    def test_init_eigen_values(self):
+        self.assertIsNone(self.jump_proposal.eigen_values)
+
+    def test_init_eigen_vectors(self):
+        self.assertIsNone(self.jump_proposal.eigen_vectors)
+
+    def test_init_covariance(self):
+        self.assertIsNone(self.jump_proposal.covariance)
+
+    def test_jump_proposal_update_eigenvectors_none(self):
+        self.assertIsNone(self.jump_proposal.update_eigenvectors(coordinates=None))
+
+    def test_jump_proposal_update_eigenvectors_1_d(self):
+        coordinates = [proposal.Sample(dict(periodic=0.3)), proposal.Sample(dict(periodic=0.1))]
+        with mock.patch('numpy.var') as m:
+            m.return_value = 1
+            self.jump_proposal.update_eigenvectors(coordinates)
+            self.assertTrue(np.equal(np.array([1]), self.jump_proposal.eigen_values))
+            self.assertTrue(np.equal(np.array([1]), self.jump_proposal.covariance))
+            self.assertTrue(np.equal(np.array([[1.]]), self.jump_proposal.eigen_vectors))
+
+    def test_jump_proposal_update_eigenvectors_n_d(self):
+        coordinates = [proposal.Sample(dict(periodic=0.3, reflecting=0.3, default=0.3)),
+                       proposal.Sample(dict(periodic=0.1, reflecting=0.1, default=0.1))]
+        with mock.patch('numpy.cov') as m:
+            with mock.patch('numpy.linalg.eigh') as n:
+                m.side_effect = lambda x: x
+                n.return_value = 1, 2
+                self.jump_proposal.update_eigenvectors(coordinates)
+                self.assertTrue(np.array_equal(np.array([[0.3, 0.1], [0.3, 0.1], [0.3, 0.1]]), self.jump_proposal.covariance))
+                self.assertEqual(1, self.jump_proposal.eigen_values)
+                self.assertEqual(2, self.jump_proposal.eigen_vectors)
+
+    def test_jump_proposal_call(self):
+        self.jump_proposal.update_eigenvectors = lambda x: None
+        self.jump_proposal.eigen_values = np.array([1, np.nan, np.nan])
+        self.jump_proposal.eigen_vectors = np.array([[0.1, np.nan, np.nan],
+                                                    [0.4, np.nan, np.nan],
+                                                    [0.7, np.nan, np.nan]])
+        with mock.patch('random.randrange') as m:
+            with mock.patch('random.gauss') as n:
+                m.return_value = 0
+                n.return_value = 1
+                expected = proposal.Sample()
+                expected['periodic'] = 0.2
+                expected['reflecting'] = 0.5
+                expected['default'] = 0.8
+                sample = proposal.Sample()
+                sample['periodic'] = 0.1
+                sample['reflecting'] = 0.1
+                sample['default'] = 0.1
+                new_sample = self.jump_proposal(sample, coordinates=None)
+                for key, value in new_sample.items():
+                    self.assertAlmostEqual(expected[key], value)
+
+
+class TestSkyLocationWanderJump(unittest.TestCase):
+
+    def setUp(self):
+        self.priors = prior.PriorDict(dict(ra=prior.Uniform(minimum=0.0, maximum=2*np.pi, periodic_boundary=True),
+                                           dec=prior.Uniform(minimum=0.0, maximum=np.pi, periodic_boundary=False)))
+        self.jump_proposal = bilby.gw.sampler.proposal.SkyLocationWanderJump(priors=self.priors)
+
+    def tearDown(self):
+        del self.priors
+        del self.jump_proposal
+
+    def test_jump_proposal_call_without_inverse_temperature(self):
+        with mock.patch('random.gauss') as m:
+            m.return_value = 1
+            sample = proposal.Sample(dict(ra=0.2, dec=-0.5))
+            expected = proposal.Sample(dict(ra=1.2, dec=0.5))
+            new_sample = self.jump_proposal(sample)
+            for key, value in new_sample.items():
+                self.assertAlmostEqual(expected[key], value)
+            m.assert_called_with(0, 1.0 / 2 / np.pi)
+
+    def test_jump_proposal_call_with_inverse_temperature(self):
+        with mock.patch('random.gauss') as m:
+            m.return_value = 1
+            sample = proposal.Sample(dict(ra=0.2, dec=-0.5))
+            expected = proposal.Sample(dict(ra=1.2, dec=0.5))
+            new_sample = self.jump_proposal(sample, inverse_temperature=2.0)
+            for key, value in new_sample.items():
+                self.assertAlmostEqual(expected[key], value)
+            m.assert_called_with(0, np.sqrt(1 / 2.0) / 2 / np.pi)
+
+
+class TestCorrelatedPolarisationPhaseJump(unittest.TestCase):
+
+    def setUp(self):
+        self.priors = prior.PriorDict(dict(phase=prior.Uniform(minimum=0.0, maximum=2*np.pi),
+                                           psi=prior.Uniform(minimum=0.0, maximum=np.pi)))
+        self.jump_proposal = bilby.gw.sampler.proposal.CorrelatedPolarisationPhaseJump(priors=self.priors)
+
+    def tearDown(self):
+        del self.priors
+        del self.jump_proposal
+
+    def test_jump_proposal_call_case_1(self):
+        with mock.patch('random.random') as m:
+            m.return_value = 0.3
+            sample = proposal.Sample(dict(phase=0.2, psi=0.5))
+            alpha = 3.0 * np.pi * 0.3
+            beta = 0.3
+            expected = proposal.Sample(dict(phase=0.5*(alpha-beta), psi=0.5*(alpha+beta)))
+            self.assertEqual(expected, self.jump_proposal(sample, coordinates=None))
+
+    def test_jump_proposal_call_case_2(self):
+        with mock.patch('random.random') as m:
+            m.return_value = 0.7
+            sample = proposal.Sample(dict(phase=0.2, psi=0.5))
+            alpha = 0.7
+            beta = 3.0 * np.pi * 0.7 - 2 * np.pi
+            expected = proposal.Sample(dict(phase=0.5*(alpha-beta), psi=0.5*(alpha+beta)))
+            self.assertEqual(expected, self.jump_proposal(sample))
+
+
+class TestPolarisationPhaseJump(unittest.TestCase):
+
+    def setUp(self):
+        self.priors = prior.PriorDict(dict(phase=prior.Uniform(minimum=0.0, maximum=2*np.pi),
+                                           psi=prior.Uniform(minimum=0.0, maximum=np.pi)))
+        self.jump_proposal = bilby.gw.sampler.proposal.PolarisationPhaseJump(priors=self.priors)
+
+    def tearDown(self):
+        del self.priors
+        del self.jump_proposal
+
+    def test_jump_proposal_call(self):
+        sample = proposal.Sample(dict(phase=0.2, psi=0.5))
+        expected = proposal.Sample(dict(phase=0.2+np.pi, psi=0.5+np.pi/2))
+        self.assertEqual(expected, self.jump_proposal(sample))
+
+
+class TestDrawFlatPrior(unittest.TestCase):
+
+    def setUp(self):
+        self.priors = prior.PriorDict(dict(phase=prior.Uniform(minimum=0.0, maximum=2*np.pi),
+                                           psi=prior.Cosine(minimum=0.0, maximum=np.pi)))
+        self.jump_proposal = proposal.DrawFlatPrior(priors=self.priors)
+
+    def tearDown(self):
+        del self.priors
+        del self.jump_proposal
+
+    def test_jump_proposal_call(self):
+        with mock.patch('bilby.core.prior.Uniform.sample') as m:
+            m.return_value = 0.3
+            sample = proposal.Sample(dict(phase=0.2, psi=0.5))
+            expected = proposal.Sample(dict(phase=0.3, psi=0.3))
+            self.assertEqual(expected, self.jump_proposal(sample))
diff --git a/test/result_test.py b/test/result_test.py
index 7648b9708948d9c293e4ae0a3df268d46b74d37b..a18a44ac6bb088253bfe014f99b01de87f33bddc 100644
--- a/test/result_test.py
+++ b/test/result_test.py
@@ -266,6 +266,17 @@ class TestResult(unittest.TestCase):
         df = pd.read_csv(filename)
         self.assertTrue(np.allclose(self.result.posterior.values, df.values))
 
+    def test_samples_to_posterior_simple(self):
+        self.result.posterior = None
+        x = [1, 2, 3]
+        y = [4, 6, 8]
+        self.result.samples = np.array([x, y]).T
+        self.result.samples_to_posterior()
+        self.assertTrue(all(self.result.posterior['x'] == x))
+        self.assertTrue(all(self.result.posterior['y'] == y))
+        self.assertTrue(np.all(
+            None == self.result.posterior.log_likelihood.values))
+
     def test_samples_to_posterior(self):
         self.result.posterior = None
         x = [1, 2, 3]
diff --git a/test/sampler_test.py b/test/sampler_test.py
index f3063b4628e9d76b3808a557258ea8a3321cc7eb..6aa1d138b6df1275e81ca477f9290c973071f8ed 100644
--- a/test/sampler_test.py
+++ b/test/sampler_test.py
@@ -113,13 +113,13 @@ class TestCPNest(unittest.TestCase):
     def test_default_kwargs(self):
         expected = dict(verbose=1, nthreads=1, nlive=500, maxmcmc=1000,
                         seed=None, poolsize=100, nhamiltonian=0, resume=True,
-                        output='outdir/cpnest_label/')
+                        output='outdir/cpnest_label/', proposals=None)
         self.assertDictEqual(expected, self.sampler.kwargs)
 
     def test_translate_kwargs(self):
         expected = dict(verbose=1, nthreads=1, nlive=250, maxmcmc=1000,
                         seed=None, poolsize=100, nhamiltonian=0, resume=True,
-                        output='outdir/cpnest_label/')
+                        output='outdir/cpnest_label/', proposals=None)
         for equiv in bilby.core.sampler.base_sampler.NestedSampler.npoints_equiv_kwargs:
             new_kwargs = self.sampler.kwargs.copy()
             del new_kwargs['nlive']
diff --git a/test/waveform_generator_test.py b/test/waveform_generator_test.py
index 8eba859ac03f0a1d73ade2a74f0785aff1162953..15aa65e467f2ecccb379effab951417904bed6b7 100644
--- a/test/waveform_generator_test.py
+++ b/test/waveform_generator_test.py
@@ -263,6 +263,19 @@ class TestFrequencyDomainStrainMethod(unittest.TestCase):
         self.assertListEqual(sorted(self.waveform_generator.parameters.keys()),
                              sorted(['amplitude', 'mu', 'sigma', 'ra', 'dec', 'geocent_time', 'psi']))
 
+    def test_caching_with_parameters(self):
+        original_waveform = self.waveform_generator.frequency_domain_strain(
+            parameters=self.simulation_parameters)
+        new_waveform = self.waveform_generator.frequency_domain_strain(
+            parameters=self.simulation_parameters)
+        self.assertDictEqual(original_waveform, new_waveform)
+
+    def test_caching_without_parameters(self):
+        original_waveform = self.waveform_generator.frequency_domain_strain(
+            parameters=self.simulation_parameters)
+        new_waveform = self.waveform_generator.frequency_domain_strain()
+        self.assertDictEqual(original_waveform, new_waveform)
+
 
 class TestTimeDomainStrainMethod(unittest.TestCase):
 
@@ -354,6 +367,20 @@ class TestTimeDomainStrainMethod(unittest.TestCase):
         self.assertListEqual(sorted(self.waveform_generator.parameters.keys()),
                              sorted(['amplitude', 'mu', 'sigma', 'ra', 'dec', 'geocent_time', 'psi']))
 
+    def test_caching_with_parameters(self):
+        original_waveform = self.waveform_generator.time_domain_strain(
+            parameters=self.simulation_parameters)
+        new_waveform = self.waveform_generator.time_domain_strain(
+            parameters=self.simulation_parameters)
+        self.assertDictEqual(original_waveform, new_waveform)
+
+    def test_caching_without_parameters(self):
+        original_waveform = self.waveform_generator.time_domain_strain(
+            parameters=self.simulation_parameters)
+        new_waveform = self.waveform_generator.time_domain_strain()
+        self.assertDictEqual(original_waveform, new_waveform)
+
+
 
 if __name__ == '__main__':
     unittest.main()