Repository: joshspeagle/dynesty
Issue: Merging results - `samples_n AttributeError`

Hi Josh.

The new work looks really great! I was playing with the `merge_runs` feature and have come across a potential bug.

I ran several jobs (using the standard `NestedSampler`, i.e. not-dynamic) and dumped the `Result` object to a pickle. Then loaded in the dumped objects and put them in a list. When I fed this into `utils.merge_runs` I received an `AttributeError` for `samples_n`  sourced from [this line](https://github.com/joshspeagle/dynesty/blob/master/dynesty/utils.py#L912). 

I hacked around it by using the way it is calculated [earlier on in the code](https://github.com/joshspeagle/dynesty/blob/master/dynesty/utils.py#L406). Is this a reasonable fix? 



Changed files:
dynesty/utils.py

diff --git a/dynesty/utils.py b/dynesty/utils.py
index 1670c22..7e8ed22 100644
--- a/dynesty/utils.py
+++ b/dynesty/utils.py
@@ -21,7 +21,7 @@ from .results import *
 __all__ = ["unitcheck", "resample_equal", "mean_and_cov", "quantile",
            "jitter_run", "resample_run", "simulate_run", "reweight_run",
            "unravel_run", "merge_runs", "kl_divergence", "kld_error",
-           "_merge_two"]
+           "_merge_two", "_get_nsamps_samples_n"]
 
 SQRTEPS = math.sqrt(float(np.finfo(np.float64).eps))
 
@@ -194,6 +194,43 @@ def quantile(x, q, weights=None):
         return quantiles
 
 
+def _get_nsamps_samples_n(res):
+    """ Helper function for calculating the number of samples
+
+    Parameters
+    ----------
+    res : :class:`~dynesty.results.Results` instance
+        The :class:`~dynesty.results.Results` instance taken from a previous
+        nested sampling run.
+
+    Returns
+    -------
+    nsamps: int
+        The total number of samples
+    samples_n: array
+        Number of live points at a given iteration
+
+    """
+    try:
+        # Check if the number of live points explicitly changes.
+        samples_n = res.samples_n
+        nsamps = len(samples_n)
+    except:
+        # If the number of live points is constant, compute `samples_n`.
+        niter = res.niter
+        nlive = res.nlive
+        nsamps = len(res.logvol)
+        if nsamps == niter:
+            samples_n = np.ones(niter, dtype='int') * nlive
+        elif nsamps == (niter + nlive):
+            samples_n = np.append(np.ones(niter, dtype='int') * nlive,
+                                  np.arange(1, nlive + 1)[::-1])
+        else:
+            raise ValueError("Final number of samples differs from number of "
+                             "iterations and number of live points.")
+    return nsamps, samples_n
+
+
 def jitter_run(res, rstate=None, approx=False):
     """
     Probes **statistical uncertainties** on a nested sampling run by
@@ -226,24 +263,8 @@ def jitter_run(res, rstate=None, approx=False):
         rstate = np.random
 
     # Initialize evolution of live points over the course of the run.
+    nsamps, samples_n = _get_nsamps_samples_n(res)
     logl = res.logl
-    try:
-        # Check if the number of live points explicitly changes.
-        samples_n = res.samples_n
-        nsamps = len(samples_n)
-    except:
-        # If the number of live points is constant, compute `samples_n`.
-        niter = res.niter
-        nlive = res.nlive
-        nsamps = len(res.logvol)
-        if nsamps == niter:
-            samples_n = np.ones(niter, dtype='int') * nlive
-        elif nsamps == (niter + nlive):
-            samples_n = np.append(np.ones(niter, dtype='int') * nlive,
-                                  np.arange(1, nlive + 1)[::-1])
-        else:
-            raise ValueError("Final number of samples differs from number of "
-                             "iterations and number of live points.")
 
     # Simulate the prior volume shrinkage associated with our set of "dead"
     # points. At each iteration, if the number of live points is constant or
@@ -909,9 +930,9 @@ def merge_runs(res_list, print_progress=True):
         if print_progress:
             sys.stderr.write('\\rMerge: {0}/{1}     '.format(counter, ntot))
 
-    samples_n = res.samples_n
-    niter = res.niter
+    nsamps, samples_n = _get_nsamps_samples_n(res)
     nlive = max(samples_n)
+    niter = res.niter
     standard_run = False
 
     # Check if we have a constant number of live points.