From bc43fd9c364db4db35585bf85b0ace8921af0677 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Tue, 9 Jun 2020 12:15:40 +0100 Subject: [PATCH 01/13] Add insertion index logging --- cpnest/NestedSampling.py | 17 ++++++++++++++++- cpnest/cpnest.py | 4 +++- cpnest/plot.py | 24 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/cpnest/NestedSampling.py b/cpnest/NestedSampling.py index fd3dc546..da659ecd 100644 --- a/cpnest/NestedSampling.py +++ b/cpnest/NestedSampling.py @@ -4,6 +4,7 @@ import pickle import time import logging +import bisect import numpy as np from numpy import logaddexp from numpy import inf @@ -182,6 +183,8 @@ def __init__(self, self.logLmax = self.manager.logLmax self.iteration = 0 self.nested_samples = [] + self.log_likelihoods = [] + self.insertion_indices = [] self.logZ = None self.state = _NSintegralState(self.Nlive) sys.stdout.flush() @@ -252,7 +255,7 @@ def consume_sample(self): self.state.increment(self.params[k].logL) self.nested_samples.append(self.params[k]) logLtmp.append(self.params[k].logL) - + # Make sure we are mixing the chains for i in np.random.permutation(range(len(self.worst))): self.manager.consumer_pipes[self.worst[i]].send(self.params[self.worst[i]]) self.condition = logaddexp(self.state.logZ,self.logLmax.value - self.iteration/(float(self.Nlive))) - self.state.logZ @@ -267,6 +270,7 @@ def consume_sample(self): if proposed.logL > self.logLmin.value: # replace worst point with new one self.params[k] = proposed + self.add_insertion_index(proposed) self.queue_counter = (self.queue_counter + 1) % len(self.manager.consumer_pipes) self.accepted += 1 break @@ -291,6 +295,16 @@ def get_worst_n_live_points(self, n): self.logLmin.value = np.float128(self.params[n-1].logL) return np.float128(self.logLmin.value) + def add_insertion_index(self, point): + """ + Gets the insertion index for a proposed point given + the current set of live points + """ + current_logL = sorted(self.log_likelihoods[-self.Nlive:]) + index = bisect.bisect(current_logL, point.logL) + self.insertion_indices.append(index) + self.log_likelihoods.append(point.logL) + def reset(self): """ Initialise the pool of `cpnest.parameter.LivePoint` by @@ -310,6 +324,7 @@ def reset(self): self.logger.warn("Likelihood function returned NaN for params "+str(self.params)) self.logger.warn("You may want to check your likelihood function") if self.params[i].logP!=-np.inf and self.params[i].logL!=-np.inf: + self.log_likelihoods.append(self.params[i].logL) i+=1 pbar.update() break diff --git a/cpnest/cpnest.py b/cpnest/cpnest.py index 7b29c17d..13a168c5 100644 --- a/cpnest/cpnest.py +++ b/cpnest/cpnest.py @@ -73,7 +73,7 @@ class CPNest(object): **deprecated** checkpoint the sampler every n_periodic_checkpoint iterations Default: None (disabled) - + periodic_checkpoint_interval: `float` checkpoing the sampler every periodic_checkpoint_interval seconds Default: None (disabled) @@ -337,6 +337,8 @@ def plot(self, corner = True): if self.verbose >= 3: plotting_priors = np.squeeze(pri.view((pri.dtype[0], len(pri.dtype.names)))) plotting_mcmc = np.squeeze(mc.view((mc.dtype[0], len(mc.dtype.names)))) + plot.plot_indices(self.NS.insertion_indices, self.nlive, + filename=os.path.join(self.output, 'insertion_indices.pdf')) else: plotting_priors = None plotting_mcmc = None diff --git a/cpnest/plot.py b/cpnest/plot.py index d4a370d9..9e8204fc 100644 --- a/cpnest/plot.py +++ b/cpnest/plot.py @@ -71,6 +71,30 @@ def plot_hist(x, name=None, prior_samples=None, mcmc_samples=None, filename=None plt.savefig(filename, bbox_inches='tight') plt.close() + +def plot_indices(indices, nlive, name=None, filename=None): + """ + Histogram indices for index insertion tests + """ + import random + fig = plt.figure() + ax = fig.add_subplot(111) + ax.hist(indices, density = True, color='tab:blue', linewidth = 1.25, + histtype='step', bins=len(indices)//50, label = 'produced') + u = [random.uniform(0, nlive) for _ in range(len(indices))] + ax.hist(u, density = True, color='tab:orange', linewidth = 1.25, + histtype='step', bins=len(u)//50, label = 'expected') + ax.legend(loc='upper left') + ax.set_ylabel('insertion indices') + if name is not None: + ax.set_xlabel(name) + if filename is None: + filename=name+'_hist.pdf' + if filename is not None: + plt.savefig(filename, bbox_inches='tight') + plt.close() + + def plot_corner(xs, ps=None, ms=None, filename=None, **kwargs): """ Produce a corner plot From 875fc672b82de784bc69f1b3063c3cceb12c743c Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Tue, 9 Jun 2020 13:52:56 +0100 Subject: [PATCH 02/13] Add chi-square test --- cpnest/cpnest.py | 20 ++++++++++++++++++-- cpnest/plot.py | 14 +++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/cpnest/cpnest.py b/cpnest/cpnest.py index 13a168c5..b8f4fbeb 100644 --- a/cpnest/cpnest.py +++ b/cpnest/cpnest.py @@ -222,6 +222,8 @@ def run(self): sys.exit(130) self.posterior_samples = self.get_posterior_samples(filename=None) + chisq, p = self.check_insertion_indices() + self.logger.warning('Insertion indices checks: chi-square={0:.4}, p-value={1:.2}'.format(chisq, p)) if self.verbose>1: self.plot(corner = False) #TODO: Clean up the resume pickles @@ -313,6 +315,19 @@ def get_posterior_samples(self, filename='posterior.dat'): newline='\n',delimiter=' ') return posterior_samples + + def check_insertion_indices(self): + """ + Compute the p-statistic between the distribution of + insertion indices and the uniform distibution U(0, nlive-1) + and produce diagnostic plots + """ + from scipy.stats import chisquare + f, bins = np.histogram(self.NS.insertion_indices, bins=np.arange(self.nlive), + density=True) + chisq, p = chisquare(f) + return chisq, p + def plot(self, corner = True): """ Make diagnostic plots of the posterior and nested samples @@ -337,8 +352,6 @@ def plot(self, corner = True): if self.verbose >= 3: plotting_priors = np.squeeze(pri.view((pri.dtype[0], len(pri.dtype.names)))) plotting_mcmc = np.squeeze(mc.view((mc.dtype[0], len(mc.dtype.names)))) - plot.plot_indices(self.NS.insertion_indices, self.nlive, - filename=os.path.join(self.output, 'insertion_indices.pdf')) else: plotting_priors = None plotting_mcmc = None @@ -349,6 +362,9 @@ def plot(self, corner = True): labels=pos.dtype.names, filename=os.path.join(self.output,'corner.pdf')) + plot.plot_indices(self.NS.insertion_indices, nlive=self.nlive, + filename=os.path.join(self.output, 'insertion_indices.pdf')) + def worker_sampler(self, producer_pipe, logLmin): cProfile.runctx('self.sampler.produce_sample(producer_pipe, logLmin)', globals(), locals(), 'prof_sampler.prof') diff --git a/cpnest/plot.py b/cpnest/plot.py index 9e8204fc..96c5d3e6 100644 --- a/cpnest/plot.py +++ b/cpnest/plot.py @@ -72,7 +72,7 @@ def plot_hist(x, name=None, prior_samples=None, mcmc_samples=None, filename=None plt.close() -def plot_indices(indices, nlive, name=None, filename=None): +def plot_indices(indices, nlive=None, u=None, name=None, filename=None): """ Histogram indices for index insertion tests """ @@ -81,11 +81,15 @@ def plot_indices(indices, nlive, name=None, filename=None): ax = fig.add_subplot(111) ax.hist(indices, density = True, color='tab:blue', linewidth = 1.25, histtype='step', bins=len(indices)//50, label = 'produced') - u = [random.uniform(0, nlive) for _ in range(len(indices))] - ax.hist(u, density = True, color='tab:orange', linewidth = 1.25, - histtype='step', bins=len(u)//50, label = 'expected') + if u is not None: + ax.hist(u, density = True, color='tab:orange', linewidth = 1.25, + histtype='step', bins=len(u)//50, label = 'expected') + if nlive is not None: + ax.axhline(1 / nlive, color='black', linewidth=1.25, linestyle=':', + label='pmf') + ax.legend(loc='upper left') - ax.set_ylabel('insertion indices') + ax.set_xlabel('insertion indices') if name is not None: ax.set_xlabel(name) if filename is None: From bf3492231d4eb92f5bd3d4535761be08d09e95e1 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Tue, 9 Jun 2020 16:07:47 +0100 Subject: [PATCH 03/13] Add 2 sampler KS test --- cpnest/cpnest.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/cpnest/cpnest.py b/cpnest/cpnest.py index b8f4fbeb..fc237d72 100644 --- a/cpnest/cpnest.py +++ b/cpnest/cpnest.py @@ -222,8 +222,7 @@ def run(self): sys.exit(130) self.posterior_samples = self.get_posterior_samples(filename=None) - chisq, p = self.check_insertion_indices() - self.logger.warning('Insertion indices checks: chi-square={0:.4}, p-value={1:.2}'.format(chisq, p)) + self.check_insertion_indices() if self.verbose>1: self.plot(corner = False) #TODO: Clean up the resume pickles @@ -322,11 +321,18 @@ def check_insertion_indices(self): insertion indices and the uniform distibution U(0, nlive-1) and produce diagnostic plots """ - from scipy.stats import chisquare + from scipy.stats import chisquare, ks_2samp + from .plot import plot_indices f, bins = np.histogram(self.NS.insertion_indices, bins=np.arange(self.nlive), - density=True) - chisq, p = chisquare(f) - return chisq, p + density=False) + chisq, chi_p = chisquare(f) + u = np.random.randint(0, self.nlive, len(self.NS.insertion_indices)) + self.logger.warning('Insertion indices checks: chi-square={0:.4}, p-value={1:.2}'.format(chisq, chi_p)) + D, ks_p = ks_2samp(self.NS.insertion_indices, u) + if self.verbose > 1: + plot_indices(self.NS.insertion_indices, nlive=self.nlive, u=u, + filename=os.path.join(self.output, 'insertion_indices.pdf')) + self.logger.warning('Insertion indices checks: ks-statistic={0:.4}, p-value={1:.2}'.format(D, ks_p)) def plot(self, corner = True): """ @@ -362,8 +368,6 @@ def plot(self, corner = True): labels=pos.dtype.names, filename=os.path.join(self.output,'corner.pdf')) - plot.plot_indices(self.NS.insertion_indices, nlive=self.nlive, - filename=os.path.join(self.output, 'insertion_indices.pdf')) def worker_sampler(self, producer_pipe, logLmin): cProfile.runctx('self.sampler.produce_sample(producer_pipe, logLmin)', globals(), locals(), 'prof_sampler.prof') From 12921f5630720ba7d353591d06dbf19a0c2f6782 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Wed, 10 Jun 2020 09:38:08 +0100 Subject: [PATCH 04/13] Fix likelihoods used for insertion indices --- cpnest/NestedSampling.py | 5 +---- cpnest/cpnest.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cpnest/NestedSampling.py b/cpnest/NestedSampling.py index da659ecd..192f0a20 100644 --- a/cpnest/NestedSampling.py +++ b/cpnest/NestedSampling.py @@ -183,7 +183,6 @@ def __init__(self, self.logLmax = self.manager.logLmax self.iteration = 0 self.nested_samples = [] - self.log_likelihoods = [] self.insertion_indices = [] self.logZ = None self.state = _NSintegralState(self.Nlive) @@ -300,10 +299,9 @@ def add_insertion_index(self, point): Gets the insertion index for a proposed point given the current set of live points """ - current_logL = sorted(self.log_likelihoods[-self.Nlive:]) + current_logL = sorted([p.logL for p in self.params]) index = bisect.bisect(current_logL, point.logL) self.insertion_indices.append(index) - self.log_likelihoods.append(point.logL) def reset(self): """ @@ -324,7 +322,6 @@ def reset(self): self.logger.warn("Likelihood function returned NaN for params "+str(self.params)) self.logger.warn("You may want to check your likelihood function") if self.params[i].logP!=-np.inf and self.params[i].logL!=-np.inf: - self.log_likelihoods.append(self.params[i].logL) i+=1 pbar.update() break diff --git a/cpnest/cpnest.py b/cpnest/cpnest.py index fc237d72..ce52661a 100644 --- a/cpnest/cpnest.py +++ b/cpnest/cpnest.py @@ -222,7 +222,7 @@ def run(self): sys.exit(130) self.posterior_samples = self.get_posterior_samples(filename=None) - self.check_insertion_indices() + self.check_insertion_indices(filename='insertion_indices.dat') if self.verbose>1: self.plot(corner = False) #TODO: Clean up the resume pickles @@ -315,7 +315,7 @@ def get_posterior_samples(self, filename='posterior.dat'): return posterior_samples - def check_insertion_indices(self): + def check_insertion_indices(self, filename=None): """ Compute the p-statistic between the distribution of insertion indices and the uniform distibution U(0, nlive-1) @@ -334,6 +334,12 @@ def check_insertion_indices(self): filename=os.path.join(self.output, 'insertion_indices.pdf')) self.logger.warning('Insertion indices checks: ks-statistic={0:.4}, p-value={1:.2}'.format(D, ks_p)) + if filename is not None: + np.savetxt(os.path.join( + self.NS.output_folder, filename), + self.NS.insertion_indices, + newline='\n',delimiter=' ') + def plot(self, corner = True): """ Make diagnostic plots of the posterior and nested samples From ca4c06e9d166f7b31ccd4b1c2de36a4b7873f837 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 11 Jun 2020 12:46:23 +0100 Subject: [PATCH 05/13] Add rolling p-value --- cpnest/NestedSampling.py | 38 ++++++++++++++++++++++++++++++++++++++ cpnest/cpnest.py | 31 ++++--------------------------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/cpnest/NestedSampling.py b/cpnest/NestedSampling.py index 192f0a20..3eed8624 100644 --- a/cpnest/NestedSampling.py +++ b/cpnest/NestedSampling.py @@ -8,6 +8,7 @@ import numpy as np from numpy import logaddexp from numpy import inf +from scipy.stats import ksone from math import isnan from . import nest2pos from .nest2pos import logsubexp @@ -184,6 +185,7 @@ def __init__(self, self.iteration = 0 self.nested_samples = [] self.insertion_indices = [] + self.rolling_p = [] self.logZ = None self.state = _NSintegralState(self.Nlive) sys.stdout.flush() @@ -303,6 +305,38 @@ def add_insertion_index(self, point): index = bisect.bisect(current_logL, point.logL) self.insertion_indices.append(index) + def check_insertion_indices(self, rolling=True, filename=None): + """ + Checking the distibution of the insertion indices either during + the nested sampling run (rolling=True) or for the whole run + (rolling=False). + """ + if rolling: + indices = self.insertion_indices[-self.Nlive:] + else: + indices = self.insertion_indices + + analytic_cdf = np.arange(self.Nlive + 1) / self.Nlive + counts, _ = np.histogram(indices, bins=np.arange(self.Nlive + 1)) + cdf = np.cumsum(counts) / len(indices) + gaps = np.column_stack([cdf - analytic_cdf[:self.Nlive], + analytic_cdf[1:] - cdf]) + D = np.max(gaps) + p = ksone.sf(D, self.Nlive) + + if rolling: + self.logger.warning('Rolling KS test: D={0:.3}, p-value={1:.3}'.format(D, p)) + self.rolling_p.append(p) + else: + self.logger.warning('Final KS test: D={0:.3}, p-value={1:.3}'.format(D, p)) + + if filename is not None: + np.savetxt(os.path.join( + self.output_folder, filename), + self.insertion_indices, + newline='\n',delimiter=' ') + + def reset(self): """ Initialise the pool of `cpnest.parameter.LivePoint` by @@ -354,6 +388,10 @@ def nested_sampling_loop(self): if time.time() - self.last_checkpoint_time > self.manager.periodic_checkpoint_interval: self.checkpoint() self.last_checkpoint_time = time.time() + + if (self.iteration % self.Nlive) < self.manager.nthreads: + self.check_insertion_indices() + except CheckPoint: self.checkpoint() # Run each pipe to get it to checkpoint diff --git a/cpnest/cpnest.py b/cpnest/cpnest.py index ce52661a..500a77d7 100644 --- a/cpnest/cpnest.py +++ b/cpnest/cpnest.py @@ -222,7 +222,7 @@ def run(self): sys.exit(130) self.posterior_samples = self.get_posterior_samples(filename=None) - self.check_insertion_indices(filename='insertion_indices.dat') + self.NS.check_insertion_indices(rolling=False, filename='insertion_indices.dat') if self.verbose>1: self.plot(corner = False) #TODO: Clean up the resume pickles @@ -314,32 +314,6 @@ def get_posterior_samples(self, filename='posterior.dat'): newline='\n',delimiter=' ') return posterior_samples - - def check_insertion_indices(self, filename=None): - """ - Compute the p-statistic between the distribution of - insertion indices and the uniform distibution U(0, nlive-1) - and produce diagnostic plots - """ - from scipy.stats import chisquare, ks_2samp - from .plot import plot_indices - f, bins = np.histogram(self.NS.insertion_indices, bins=np.arange(self.nlive), - density=False) - chisq, chi_p = chisquare(f) - u = np.random.randint(0, self.nlive, len(self.NS.insertion_indices)) - self.logger.warning('Insertion indices checks: chi-square={0:.4}, p-value={1:.2}'.format(chisq, chi_p)) - D, ks_p = ks_2samp(self.NS.insertion_indices, u) - if self.verbose > 1: - plot_indices(self.NS.insertion_indices, nlive=self.nlive, u=u, - filename=os.path.join(self.output, 'insertion_indices.pdf')) - self.logger.warning('Insertion indices checks: ks-statistic={0:.4}, p-value={1:.2}'.format(D, ks_p)) - - if filename is not None: - np.savetxt(os.path.join( - self.NS.output_folder, filename), - self.NS.insertion_indices, - newline='\n',delimiter=' ') - def plot(self, corner = True): """ Make diagnostic plots of the posterior and nested samples @@ -373,6 +347,9 @@ def plot(self, corner = True): ms=plotting_mcmc, labels=pos.dtype.names, filename=os.path.join(self.output,'corner.pdf')) + if self.verbose >= 3: + plot.plot_indices(self.NS.insertion_indices, nlive=self.nlive, + filename=os.path.join(self.output, 'insertion_indices.pdf')) def worker_sampler(self, producer_pipe, logLmin): From 6d2489a9bd6ed1b0b7a27b3f141e572c3b79b79a Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Fri, 19 Jun 2020 11:42:50 +0100 Subject: [PATCH 06/13] Change to using ordered live points for insertion indices --- cpnest/NestedSampling.py | 112 +++++++++++++++++++++++++-------------- cpnest/plot.py | 6 +-- 2 files changed, 75 insertions(+), 43 deletions(-) diff --git a/cpnest/NestedSampling.py b/cpnest/NestedSampling.py index 3eed8624..2579116d 100644 --- a/cpnest/NestedSampling.py +++ b/cpnest/NestedSampling.py @@ -8,7 +8,7 @@ import numpy as np from numpy import logaddexp from numpy import inf -from scipy.stats import ksone +from scipy.stats import kstest from math import isnan from . import nest2pos from .nest2pos import logsubexp @@ -18,6 +18,50 @@ from tqdm import tqdm +class KeyOrderedList(list): + + def __init__(self, iterable, key=lambda x: x): + iterable = sorted(iterable, key=key) + super(KeyOrderedList, self).__init__(iterable) + + self._key = key + self._keys = [self._key(v) for v in iterable] + + def search(self, item): + """ + Find the location of a new entry + """ + return bisect.bisect(self._keys, self._key(item)) + + def add(self, item): + """ + Update the ordered list with a single item + """ + index = self.search(item) + self.insert(index, item) + self._keys.insert(index, self._key(item)) + return index + + +class OrderedLivePoints(KeyOrderedList): + + def __init__(self, live_points): + super(OrderedLivePoints, self).__init__(live_points, key=lambda x: x.logL) + + def insert_live_point(self, live_point): + """ + Insert a live point + """ + return self.add(live_point) + + def remove_n_worst_points(self, n): + """ + Remvoe the n worst live points + """ + del self[:n] + del self._keys[:n] + + class _NSintegralState(object): """ Stores the state of the nested sampling integrator @@ -250,9 +294,11 @@ def consume_sample(self): and updates the evidence logZ """ # Increment the state of the evidence integration - logLmin = self.get_worst_n_live_points(len(self.manager.consumer_pipes)) + n = len(self.manager.consumer_pipes) + self.logLmin.value = np.float128(self.params[n-1].logL) logLtmp = [] - for k in self.worst: + worst = list(range(n)) + for k in worst: self.state.increment(self.params[k].logL) self.nested_samples.append(self.params[k]) logLtmp.append(self.params[k].logL) @@ -262,16 +308,22 @@ def consume_sample(self): self.condition = logaddexp(self.state.logZ,self.logLmax.value - self.iteration/(float(self.Nlive))) - self.state.logZ # Replace the points we just consumed with the next acceptable ones - for k in self.worst: + # Reversed since the for the first point the current number of + # live points is N - n_worst - 1 + for k in reversed(worst): self.iteration += 1 loops = 0 while(True): loops += 1 acceptance, sub_acceptance, self.jumps, proposed = self.manager.consumer_pipes[self.queue_counter].recv() if proposed.logL > self.logLmin.value: - # replace worst point with new one - self.params[k] = proposed - self.add_insertion_index(proposed) + # Insert the new live point into the ordered list and + # return the index at which is was inserted, this will + # include the n worst points, so this subtracted + index = self.params.insert_live_point(proposed) + # the index is then coverted to a value between [0, 1] + # accounting for the variable number of live points + self.insertion_indices.append((index - n) / (self.Nlive - k)) self.queue_counter = (self.queue_counter + 1) % len(self.manager.consumer_pipes) self.accepted += 1 break @@ -283,27 +335,11 @@ def consume_sample(self): if self.verbose: self.logger.info("{0:d}: n:{1:4d} NS_acc:{2:.3f} S{3:d}_acc:{4:.3f} sub_acc:{5:.3f} H: {6:.2f} logL {7:.5f} --> {8:.5f} dZ: {9:.3f} logZ: {10:.3f} logLmax: {11:.2f}"\ .format(self.iteration, self.jumps*loops, self.acceptance, k, acceptance, sub_acceptance, self.state.info,\ - logLtmp[k], self.params[k].logL, self.condition, self.state.logZ, self.logLmax.value)) - #sys.stderr.flush() - - def get_worst_n_live_points(self, n): - """ - selects the lowest likelihood N live points - for evolution - """ - self.params.sort(key=attrgetter('logL')) - self.worst = np.arange(n) - self.logLmin.value = np.float128(self.params[n-1].logL) - return np.float128(self.logLmin.value) + logLtmp[k], proposed.logL, self.condition, self.state.logZ, self.logLmax.value)) - def add_insertion_index(self, point): - """ - Gets the insertion index for a proposed point given - the current set of live points - """ - current_logL = sorted([p.logL for p in self.params]) - index = bisect.bisect(current_logL, point.logL) - self.insertion_indices.append(index) + # points not removed earlier because they are used to resend to + # samplers if rejected + self.params.remove_n_worst_points(n) def check_insertion_indices(self, rolling=True, filename=None): """ @@ -316,14 +352,7 @@ def check_insertion_indices(self, rolling=True, filename=None): else: indices = self.insertion_indices - analytic_cdf = np.arange(self.Nlive + 1) / self.Nlive - counts, _ = np.histogram(indices, bins=np.arange(self.Nlive + 1)) - cdf = np.cumsum(counts) / len(indices) - gaps = np.column_stack([cdf - analytic_cdf[:self.Nlive], - analytic_cdf[1:] - cdf]) - D = np.max(gaps) - p = ksone.sf(D, self.Nlive) - + D, p = kstest(indices, 'uniform', args=(0, 1)) if rolling: self.logger.warning('Rolling KS test: D={0:.3}, p-value={1:.3}'.format(D, p)) self.rolling_p.append(p) @@ -345,20 +374,22 @@ def reset(self): # send all live points to the samplers for start i = 0 nthreads=self.manager.nthreads + params = [None] * self.Nlive with tqdm(total=self.Nlive, disable= not self.verbose, desc='CPNEST: populate samplers', position=nthreads) as pbar: while i < self.Nlive: for j in range(nthreads): self.manager.consumer_pipes[j].send(self.model.new_point()) for j in range(nthreads): while i < self.Nlive: - acceptance,sub_acceptance,self.jumps,self.params[i] = self.manager.consumer_pipes[self.queue_counter].recv() + acceptance,sub_acceptance,self.jumps,params[i] = self.manager.consumer_pipes[self.queue_counter].recv() self.queue_counter = (self.queue_counter + 1) % len(self.manager.consumer_pipes) - if np.isnan(self.params[i].logL): - self.logger.warn("Likelihood function returned NaN for params "+str(self.params)) + if np.isnan(params[i].logL): + self.logger.warn("Likelihood function returned NaN for params "+str(params)) self.logger.warn("You may want to check your likelihood function") - if self.params[i].logP!=-np.inf and self.params[i].logL!=-np.inf: + if params[i].logP!=-np.inf and params[i].logL!=-np.inf: i+=1 pbar.update() break + self.params = OrderedLivePoints(params) if self.verbose: sys.stderr.write("\n") sys.stderr.flush() @@ -372,7 +403,8 @@ def nested_sampling_loop(self): self.reset() if self.prior_sampling: for i in range(self.Nlive): - self.nested_samples.append(self.params[i]) + #self.nested_samples.append(self.params[i]) + self.nested_samples = self.params.live_points self.write_chain_to_file() self.write_evidence_to_file() self.logLmin.value = np.inf diff --git a/cpnest/plot.py b/cpnest/plot.py index 96c5d3e6..6968fa18 100644 --- a/cpnest/plot.py +++ b/cpnest/plot.py @@ -85,11 +85,11 @@ def plot_indices(indices, nlive=None, u=None, name=None, filename=None): ax.hist(u, density = True, color='tab:orange', linewidth = 1.25, histtype='step', bins=len(u)//50, label = 'expected') if nlive is not None: - ax.axhline(1 / nlive, color='black', linewidth=1.25, linestyle=':', - label='pmf') + ax.axhline(1, color='black', linewidth=1.25, linestyle=':', + label='pdf') ax.legend(loc='upper left') - ax.set_xlabel('insertion indices') + ax.set_xlabel('insertion indices [0, 1]') if name is not None: ax.set_xlabel(name) if filename is None: From ab96f51f1d0f8b9e8685856b18d1cd4ebd12b524 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Fri, 19 Jun 2020 11:47:26 +0100 Subject: [PATCH 07/13] Fix permutation of worst points --- cpnest/NestedSampling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpnest/NestedSampling.py b/cpnest/NestedSampling.py index 2579116d..064ca538 100644 --- a/cpnest/NestedSampling.py +++ b/cpnest/NestedSampling.py @@ -304,7 +304,7 @@ def consume_sample(self): logLtmp.append(self.params[k].logL) # Make sure we are mixing the chains - for i in np.random.permutation(range(len(self.worst))): self.manager.consumer_pipes[self.worst[i]].send(self.params[self.worst[i]]) + for i in np.random.permutation(range(n)): self.manager.consumer_pipes[worst[i]].send(self.params[worst[i]]) self.condition = logaddexp(self.state.logZ,self.logLmax.value - self.iteration/(float(self.Nlive))) - self.state.logZ # Replace the points we just consumed with the next acceptable ones From 42349a948b64fdc9134d9bfdee0ce6681a065b35 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 21 Jan 2021 14:51:27 +0000 Subject: [PATCH 08/13] Make final insertion indices consistent with verbosity --- cpnest/cpnest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpnest/cpnest.py b/cpnest/cpnest.py index e8dcbd7e..87f5d6c4 100644 --- a/cpnest/cpnest.py +++ b/cpnest/cpnest.py @@ -288,6 +288,8 @@ def run(self): sys.exit(130) if self.verbose >= 2: + self.NS.check_insertion_indices(rolling=False, + filename='insertion_indices.dat') self.logger.critical( "Saving nested samples in {0}".format(self.output) ) @@ -297,12 +299,12 @@ def run(self): ) self.posterior_samples = self.get_posterior_samples() else: + self.NS.check_insertion_indices(rolling=False, + filename=None) self.nested_samples = self.get_nested_samples(filename=None) self.posterior_samples = self.get_posterior_samples( filename=None ) - self.NS.check_insertion_indices(rolling=False, - filename='insertion_indices.dat') if self.verbose>=3 or self.NS.prior_sampling: self.prior_samples = self.get_prior_samples(filename=None) From e00fc0df24a66de4f71ab71c7dcc468f1463ea66 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 21 Jan 2021 15:10:23 +0000 Subject: [PATCH 09/13] Simplify plotting --- cpnest/cpnest.py | 3 +-- cpnest/plot.py | 34 ++++++++++++++++++---------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/cpnest/cpnest.py b/cpnest/cpnest.py index 87f5d6c4..29fa9d55 100644 --- a/cpnest/cpnest.py +++ b/cpnest/cpnest.py @@ -528,8 +528,7 @@ def plot(self, corner = True): ms=plotting_mcmc, labels=pos.dtype.names, filename=os.path.join(self.output,'corner.pdf')) - plot.plot_indices(self.NS.insertion_indices, nlive=self.nlive, - filename=os.path.join(self.output, 'insertion_indices.pdf')) + plot.plot_indices(self.NS.insertion_indices, filename=os.path.join(self.output, 'insertion_indices.pdf')) def worker_sampler(self, producer_pipe, logLmin): cProfile.runctx('self.sampler.produce_sample(producer_pipe, logLmin)', globals(), locals(), 'prof_sampler.prof') diff --git a/cpnest/plot.py b/cpnest/plot.py index 6968fa18..680029a2 100644 --- a/cpnest/plot.py +++ b/cpnest/plot.py @@ -72,28 +72,30 @@ def plot_hist(x, name=None, prior_samples=None, mcmc_samples=None, filename=None plt.close() -def plot_indices(indices, nlive=None, u=None, name=None, filename=None): +def plot_indices(indices, filename=None): """ - Histogram indices for index insertion tests + Histogram indices for insertion indices tests. + + Parameters + ---------- + indices : list + List of insertion indices + filename : str, optional + Filename used to saved resulting figure. If not specified figure + is not saved. """ - import random fig = plt.figure() ax = fig.add_subplot(111) + ax.hist(indices, density = True, color='tab:blue', linewidth = 1.25, - histtype='step', bins=len(indices)//50, label = 'produced') - if u is not None: - ax.hist(u, density = True, color='tab:orange', linewidth = 1.25, - histtype='step', bins=len(u)//50, label = 'expected') - if nlive is not None: - ax.axhline(1, color='black', linewidth=1.25, linestyle=':', - label='pdf') + histtype='step', bins=min(len(indices)//100, 30)) + # Theoretical distribution + ax.axhline(1, color='black', linewidth=1.25, linestyle=':', + label='pdf') + + ax.legend() + ax.set_xlabel('Insertion indices [0, 1]') - ax.legend(loc='upper left') - ax.set_xlabel('insertion indices [0, 1]') - if name is not None: - ax.set_xlabel(name) - if filename is None: - filename=name+'_hist.pdf' if filename is not None: plt.savefig(filename, bbox_inches='tight') plt.close() From b852a04bce0d55609ea73e0d412dc7851659eaf9 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 11 Feb 2021 14:50:20 +0000 Subject: [PATCH 10/13] Add doc-strings to new classes --- cpnest/NestedSampling.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/cpnest/NestedSampling.py b/cpnest/NestedSampling.py index 07b5b735..e461152e 100644 --- a/cpnest/NestedSampling.py +++ b/cpnest/NestedSampling.py @@ -22,7 +22,17 @@ class KeyOrderedList(list): - + """ + List object that is ordered according to a key + + Parameters + ---------- + iterable : array_like + Initial input used to intialise the list + key : function, optional + Key to use to sort the list, by defaul it is sorted by its + values. + """ def __init__(self, iterable, key=lambda x: x): iterable = sorted(iterable, key=key) super(KeyOrderedList, self).__init__(iterable) @@ -38,7 +48,7 @@ def search(self, item): def add(self, item): """ - Update the ordered list with a single item + Update the ordered list with a single item and return the index """ index = self.search(item) self.insert(index, item) @@ -47,13 +57,23 @@ def add(self, item): class OrderedLivePoints(KeyOrderedList): + """ + Object tha contains live points ordered by increasing log-likelihood. Requires + the log-likelihood to be pre-computed. + + Assumes the log-likelihood is accesible as an attribute of each live point. + Parameters + ---------- + live_points : array_like + Initial live points + """ def __init__(self, live_points): super(OrderedLivePoints, self).__init__(live_points, key=lambda x: x.logL) def insert_live_point(self, live_point): """ - Insert a live point + Insert a live point and return the index of the new point """ return self.add(live_point) @@ -313,7 +333,7 @@ def consume_sample(self): # Replace the points we just consumed with the next acceptable ones # Reversed since the for the first point the current number of - # live points is N - n_worst -1 (minus 1 because of couting from zero) + # live points is N - n_worst -1 (minus 1 because of counting from zero) for k in reversed(self.worst): self.iteration += 1 loops = 0 @@ -323,7 +343,7 @@ def consume_sample(self): if proposed.logL > self.logLmin.value: # Insert the new live point into the ordered list and # return the index at which is was inserted, this will - # include the n worst points, so this subtracted + # include the n worst points, so this subtracted next index = self.params.insert_live_point(proposed) # the index is then coverted to a value between [0, 1] # accounting for the variable number of live points From fc934790f0e2cde4861f637fb116ffb227dd0b9e Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 11 Feb 2021 14:58:21 +0000 Subject: [PATCH 11/13] Fix nested samples when using prior sampling --- cpnest/NestedSampling.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cpnest/NestedSampling.py b/cpnest/NestedSampling.py index e461152e..e0872a1a 100644 --- a/cpnest/NestedSampling.py +++ b/cpnest/NestedSampling.py @@ -435,9 +435,7 @@ def nested_sampling_loop(self): if not self.initialised: self.reset() if self.prior_sampling: - for i in range(self.Nlive): - #self.nested_samples.append(self.params[i]) - self.nested_samples = self.params.live_points + self.nested_samples = self.params self.write_chain_to_file() self.write_evidence_to_file() self.logLmin.value = np.inf From dd3ee632fffb28f98ca1237bef9ea5bf41e09ab3 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 11 Feb 2021 14:59:50 +0000 Subject: [PATCH 12/13] Prevent checking insertion indices if list is empty --- cpnest/NestedSampling.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpnest/NestedSampling.py b/cpnest/NestedSampling.py index e0872a1a..d2d07d66 100644 --- a/cpnest/NestedSampling.py +++ b/cpnest/NestedSampling.py @@ -380,6 +380,8 @@ def check_insertion_indices(self, rolling=True, filename=None): the nested sampling run (rolling=True) or for the whole run (rolling=False). """ + if not self.insertion_indices: + return if rolling: indices = self.insertion_indices[-self.Nlive:] else: From 40e0713f6ea5fef7d7e2e27aba067d33fc44e8cc Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 11 Feb 2021 15:13:48 +0000 Subject: [PATCH 13/13] Add max. bins to the indices plot --- cpnest/plot.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cpnest/plot.py b/cpnest/plot.py index 680029a2..2b5ac7c6 100644 --- a/cpnest/plot.py +++ b/cpnest/plot.py @@ -72,7 +72,7 @@ def plot_hist(x, name=None, prior_samples=None, mcmc_samples=None, filename=None plt.close() -def plot_indices(indices, filename=None): +def plot_indices(indices, filename=None, max_bins=30): """ Histogram indices for insertion indices tests. @@ -83,15 +83,16 @@ def plot_indices(indices, filename=None): filename : str, optional Filename used to saved resulting figure. If not specified figure is not saved. + max_bins : int, optional + Maximum number of bins in the histogram. """ fig = plt.figure() ax = fig.add_subplot(111) - ax.hist(indices, density = True, color='tab:blue', linewidth = 1.25, - histtype='step', bins=min(len(indices)//100, 30)) + ax.hist(indices, density=True, color='tab:blue', linewidth=1.25, + histtype='step', bins=min(len(indices) // 100, max_bins)) # Theoretical distribution - ax.axhline(1, color='black', linewidth=1.25, linestyle=':', - label='pdf') + ax.axhline(1, color='black', linewidth=1.25, linestyle=':', label='pdf') ax.legend() ax.set_xlabel('Insertion indices [0, 1]')