diff --git a/pyfstat/mcmc_based_searches.py b/pyfstat/mcmc_based_searches.py index e2836b1a7e8fff8cedef802336371d2c2a7f0c44..42c2edf4bafa13a6d5e5fc515acb59e71dae6dbf 100644 --- a/pyfstat/mcmc_based_searches.py +++ b/pyfstat/mcmc_based_searches.py @@ -17,7 +17,7 @@ import dill as pickle import pyfstat.core as core from pyfstat.core import tqdm, args, earth_ephem, sun_ephem, read_par -from pyfstat.optimal_setup_functions import get_V_estimate, get_optimal_setup +from pyfstat.optimal_setup_functions import get_Nstar_estimate, get_optimal_setup import pyfstat.helper_functions as helper_functions @@ -1831,54 +1831,29 @@ class MCMCFollowUpSearch(MCMCSemiCoherentSearch): if prior[key]['type'] == 'unif': return .5*(prior[key]['upper'] + prior[key]['lower']) - def init_V_estimate_parameters(self): - if 'Alpha' in self.theta_keys: - DeltaAlpha = self.get_width_from_prior(self.theta_prior, 'Alpha') - DeltaDelta = self.get_width_from_prior(self.theta_prior, 'Delta') - DeltaMid = self.get_mid_from_prior(self.theta_prior, 'Delta') - DeltaOmega = np.sin(np.pi/2 - DeltaMid) * DeltaDelta * DeltaAlpha - logging.info('Search over Alpha and Delta') - else: - logging.info('No sky search requested') - DeltaOmega = 0 - if 'F0' in self.theta_keys: - DeltaF0 = self.get_width_from_prior(self.theta_prior, 'F0') - else: - raise ValueError("You aren't searching over F0?") - DeltaFs = [DeltaF0] - if 'F1' in self.theta_keys: - DeltaF1 = self.get_width_from_prior(self.theta_prior, 'F1') - DeltaFs.append(DeltaF1) - if 'F2' in self.theta_keys: - DeltaF2 = self.get_width_from_prior(self.theta_prior, 'F2') - DeltaFs.append(DeltaF2) - logging.info('Searching over Frequency and {} spin-down components' - .format(len(DeltaFs)-1)) - - if type(self.theta_prior['F0']) == dict: - fiducial_freq = self.get_mid_from_prior(self.theta_prior, 'F0') - else: - fiducial_freq = self.theta_prior['F0'] - - return fiducial_freq, DeltaOmega, DeltaFs - def read_setup_input_file(self, run_setup_input_file): with open(run_setup_input_file, 'r+') as f: d = pickle.load(f) return d def write_setup_input_file(self, run_setup_input_file, R, Nsegs0, - nsegs_vals, V_vals, DeltaOmega, DeltaFs): - d = dict(R=R, Nsegs0=Nsegs0, nsegs_vals=nsegs_vals, V_vals=V_vals, - DeltaOmega=DeltaOmega, DeltaFs=DeltaFs) + nsegs_vals, Nstar_vals, theta_prior): + d = dict(R=R, Nsegs0=Nsegs0, nsegs_vals=nsegs_vals, + theta_prior=theta_prior, Nstar_vals=Nstar_vals) with open(run_setup_input_file, 'w+') as f: pickle.dump(d, f) def check_old_run_setup(self, old_setup, **kwargs): try: truths = [val == old_setup[key] for key, val in kwargs.iteritems()] - return all(truths) - except KeyError: + if all(truths): + return True + else: + logging.info( + 'Old setup does not match one of R, Nsegs0 or prior') + except KeyError as e: + logging.info( + 'Error found when comparing with old setup: {}'.format(e)) return False def init_run_setup(self, run_setup=None, R=10, Nsegs0=None, log_table=True, @@ -1888,7 +1863,6 @@ class MCMCFollowUpSearch(MCMCSemiCoherentSearch): raise ValueError( 'You must either specify the run_setup, or Nsegs0 from which ' 'the optimal run_setup given R can be estimated') - fiducial_freq, DeltaOmega, DeltaFs = self.init_V_estimate_parameters() if run_setup is None: logging.info('No run_setup provided') @@ -1901,29 +1875,26 @@ class MCMCFollowUpSearch(MCMCSemiCoherentSearch): old_setup = self.read_setup_input_file(run_setup_input_file) if self.check_old_run_setup(old_setup, R=R, Nsegs0=Nsegs0, - DeltaOmega=DeltaOmega, - DeltaFs=DeltaFs): + theta_prior=self.theta_prior): logging.info('Using old setup with R={}, Nsegs0={}'.format( R, Nsegs0)) nsegs_vals = old_setup['nsegs_vals'] - V_vals = old_setup['V_vals'] + Nstar_vals = old_setup['Nstar_vals'] generate_setup = False else: - logging.info( - 'Old setup does not match requested R, Nsegs0') generate_setup = True else: generate_setup = True if generate_setup: - nsegs_vals, V_vals = get_optimal_setup( + nsegs_vals, Nstar_vals = get_optimal_setup( R, Nsegs0, self.tref, self.minStartTime, - self.maxStartTime, self.theta_prior, fiducial_freq, + self.maxStartTime, self.theta_prior, self.search.detector_names, self.earth_ephem, self.sun_ephem) self.write_setup_input_file(run_setup_input_file, R, Nsegs0, - nsegs_vals, V_vals, DeltaOmega, - DeltaFs) + nsegs_vals, Nstar_vals, + self.theta_prior) run_setup = [((self.nsteps[0], 0), nsegs, False) for nsegs in nsegs_vals[:-1]] @@ -1932,7 +1903,7 @@ class MCMCFollowUpSearch(MCMCSemiCoherentSearch): else: logging.info('Calculating the number of templates for this setup') - V_vals = [] + Nstar_vals = [] for i, rs in enumerate(run_setup): rs = list(rs) if len(rs) == 2: @@ -1942,25 +1913,24 @@ class MCMCFollowUpSearch(MCMCSemiCoherentSearch): run_setup[i] = rs if args.no_template_counting: - V_vals.append([1, 1, 1]) + Nstar_vals.append([1, 1, 1]) else: - V = get_V_estimate( + Nstar = get_Nstar_estimate( rs[1], self.tref, self.minStartTime, self.maxStartTime, - self.theta_prior, fiducial_freq, - self.search.detector_names, self.earth_ephem, - self.sun_ephem) - V_vals.append(V) + self.theta_prior, self.search.detector_names, + self.earth_ephem, self.sun_ephem) + Nstar_vals.append(Nstar) if log_table: logging.info('Using run-setup as follows:') logging.info( - 'Stage | nburn | nprod | nsegs | Tcoh d | resetp0 | V') + 'Stage | nburn | nprod | nsegs | Tcoh d | resetp0 | Nstar') for i, rs in enumerate(run_setup): Tcoh = (self.maxStartTime - self.minStartTime) / rs[1] / 86400 - if V_vals[i] is None: + if Nstar_vals[i] is None: vtext = 'N/A' else: - vtext = '{:1.0e}'.format(V_vals[i]) + vtext = '{:0.3e}'.format(int(Nstar_vals[i])) logging.info('{} | {} | {} | {} | {} | {} | {}'.format( str(i).ljust(5), str(rs[0][0]).ljust(5), str(rs[0][1]).ljust(5), str(rs[1]).ljust(5), @@ -1971,24 +1941,22 @@ class MCMCFollowUpSearch(MCMCSemiCoherentSearch): filename = '{}/{}_run_setup.tex'.format(self.outdir, self.label) with open(filename, 'w+') as f: f.write(r'\begin{tabular}{c|cccc}' + '\n') - f.write(r'Stage & $\Nseg$ & $\Tcoh^{\rm days}$ &' - r'$\Nsteps$ & $\V$ \\ \hline' + f.write(r'Stage & $N_\mathrm{seg}$ &' + r'$T_\mathrm{coh}^{\rm days}$ &' + r'$N_\mathrm{burn}$ & $N_\mathrm{prod}$ &' + r'$N^*$ \\ \hline' '\n') for i, rs in enumerate(run_setup): Tcoh = float( self.maxStartTime - self.minStartTime)/rs[1]/86400 - line = r'{} & {} & {} & {} & {} \\' + '\n' - if V_vals[i] is None: - V = 'N/A' - else: - V = V_vals[i] - if rs[0][-1] == 0: - nsteps = rs[0][0] + line = r'{} & {} & {} & {} & {} & {} \\' + '\n' + if Nstar_vals[i] is None: + Nstar = 'N/A' else: - nsteps = '{},{}'.format(*rs[0]) + Nstar = Nstar_vals[i] line = line.format(i, rs[1], '{:1.1f}'.format(Tcoh), - nsteps, - helper_functions.texify_float(V)) + rs[0], rs[1], + helper_functions.texify_float(Nstar)) f.write(line) f.write(r'\end{tabular}' + '\n') diff --git a/pyfstat/optimal_setup_functions.py b/pyfstat/optimal_setup_functions.py index 25017ce1b42ae3afd79b8c29ae69caa5b2e42f04..811dece452d85f8355f2d871580a7798a2591bd1 100644 --- a/pyfstat/optimal_setup_functions.py +++ b/pyfstat/optimal_setup_functions.py @@ -1,7 +1,6 @@ """ -Provides functions to aid in calculating the optimal setup based on the metric -volume estimates. +Provides functions to aid in calculating the optimal setup for zoom follow up """ from __future__ import division, absolute_import, print_function @@ -15,41 +14,64 @@ import pyfstat.helper_functions as helper_functions def get_optimal_setup( - R, Nsegs0, tref, minStartTime, maxStartTime, prior, fiducial_freq, + R, Nsegs0, tref, minStartTime, maxStartTime, prior, detector_names, earth_ephem, sun_ephem): + """ Using an optimisation step, calculate the optimal setup ladder + + Parameters + ---------- + R : float + Nsegs0 : int + The number of segments for the initial step of the ladder + minStartTime, maxStartTime : int + GPS times of the start and end time of the search + prior : dict + Prior dictionary, each item must either be a fixed scalar value, or + a uniform prior. + detector_names : list of str + earth_ephem, sun_ephem : str + + Returns + ------- + nsegs, Nstar : list + Ladder of segment numbers and the corresponding Nstar + + """ + logging.info('Calculating optimal setup for R={}, Nsegs0={}'.format( R, Nsegs0)) - V_0 = get_V_estimate( - Nsegs0, tref, minStartTime, maxStartTime, prior, fiducial_freq, + Nstar_0 = get_Nstar_estimate( + Nsegs0, tref, minStartTime, maxStartTime, prior, detector_names, earth_ephem, sun_ephem) - logging.info('Stage {}, nsegs={}, V={}'.format(0, Nsegs0, V_0)) + logging.info( + 'Stage {}, nsegs={}, Nstar={}'.format(0, Nsegs0, int(Nstar_0))) nsegs_vals = [Nsegs0] - V_vals = [V_0] + Nstar_vals = [Nstar_0] i = 0 nsegs_i = Nsegs0 while nsegs_i > 1: - nsegs_i, V_i = get_nsegs_ip1( - nsegs_i, R, tref, minStartTime, maxStartTime, prior, fiducial_freq, + nsegs_i, Nstar_i = _get_nsegs_ip1( + nsegs_i, R, tref, minStartTime, maxStartTime, prior, detector_names, earth_ephem, sun_ephem) nsegs_vals.append(nsegs_i) - V_vals.append(V_i) + Nstar_vals.append(Nstar_i) i += 1 logging.info( - 'Stage {}, nsegs={}, V={}'.format(i, nsegs_i, V_i)) + 'Stage {}, nsegs={}, Nstar={}'.format(i, nsegs_i, int(Nstar_i))) - return nsegs_vals, V_vals + return nsegs_vals, Nstar_vals -def get_nsegs_ip1( - nsegs_i, R, tref, minStartTime, maxStartTime, prior, fiducial_freq, - detector_names, earth_ephem, sun_ephem): +def _get_nsegs_ip1(nsegs_i, R, tref, minStartTime, maxStartTime, prior, + detector_names, earth_ephem, sun_ephem): + """ Calculate Nsegs_{i+1} given Nsegs_{i} """ log10R = np.log10(R) - log10Vi = np.log10(get_V_estimate( - nsegs_i, tref, minStartTime, maxStartTime, prior, fiducial_freq, + log10Nstari = np.log10(get_Nstar_estimate( + nsegs_i, tref, minStartTime, maxStartTime, prior, detector_names, earth_ephem, sun_ephem)) def f(nsegs_ip1): @@ -60,28 +82,46 @@ def get_nsegs_ip1( nsegs_ip1 = int(nsegs_ip1[0]) if nsegs_ip1 == 0: nsegs_ip1 = 1 - Vip1 = get_V_estimate( - nsegs_ip1, tref, minStartTime, maxStartTime, prior, fiducial_freq, + Nstarip1 = get_Nstar_estimate( + nsegs_ip1, tref, minStartTime, maxStartTime, prior, detector_names, earth_ephem, sun_ephem) - if Vip1 is None: + if Nstarip1 is None: return 1e6 else: - log10Vip1 = np.log10(Vip1) - return np.abs(log10Vi + log10R - log10Vip1) + log10Nstarip1 = np.log10(Nstarip1) + return np.abs(log10Nstari + log10R - log10Nstarip1) res = scipy.optimize.minimize(f, .5*nsegs_i, method='Powell', tol=0.1, options={'maxiter': 10}) nsegs_ip1 = int(res.x) if nsegs_ip1 == 0: nsegs_ip1 = 1 if res.success: - return nsegs_ip1, get_V_estimate( - nsegs_ip1, tref, minStartTime, maxStartTime, prior, fiducial_freq, + return nsegs_ip1, get_Nstar_estimate( + nsegs_ip1, tref, minStartTime, maxStartTime, prior, detector_names, earth_ephem, sun_ephem) else: raise ValueError('Optimisation unsuccesful') -def get_parallelepiped(prior): +def _extract_data_from_prior(prior): + """ Calculate the input data from the prior + + Parameters + ---------- + prior: dict + + Returns + ------- + p : ndarray + Matrix with columns being the edges of the uniform bounding box + spindowns : int + The number of spindowns + sky : bool + If true, search includes the sky position + fiducial_freq : float + Fidicual frequency + + """ keys = ['Alpha', 'Delta', 'F0', 'F1', 'F2'] spindown_keys = keys[3:] sky_keys = keys[:2] @@ -110,33 +150,43 @@ def get_parallelepiped(prior): p.append(basex) spindowns = np.sum([np.sum(lims_keys == k) for k in spindown_keys]) sky = any([key in lims_keys for key in sky_keys]) - return np.array(p).T, spindowns , sky + if type(prior['F0']) == dict: + fiducial_freq = prior['F0']['upper'] + else: + fiducial_freq = prior['F0'] + + return np.array(p).T, spindowns, sky, fiducial_freq -def get_V_estimate( - nsegs, tref, minStartTime, maxStartTime, prior, fiducial_freq, +def get_Nstar_estimate( + nsegs, tref, minStartTime, maxStartTime, prior, detector_names, earth_ephem, sun_ephem): - """ Returns V estimated from the super-sky metric + """ Returns N* estimated from the super-sky metric Parameters ---------- - nsegs: int + nsegs : int Number of semi-coherent segments - tref: int + tref : int Reference time in GPS seconds - minStartTime, maxStartTime: int + minStartTime, maxStartTime : int Minimum and maximum SFT timestamps - prior: dict + prior : dict The prior dictionary - fiducial_freq: float - Fidicual frequency - detector_names: array + detector_names : array Array of detectors to average over - earth_ephem, sun_ephem: st + earth_ephem, sun_ephem : str Paths to the ephemeris files + Returns + ------- + Nstar: int + The estimated approximate number of templates to cover the prior + parameter space at a mismatch of unity, assuming the normalised + thickness is unity. + """ - in_phys, spindowns, sky = get_parallelepiped(prior) + in_phys, spindowns, sky, fiducial_freq = _extract_data_from_prior(prior) out_rssky = np.zeros(in_phys.shape) in_phys = helper_functions.convert_array_to_gsl_matrix(in_phys) @@ -160,8 +210,9 @@ def get_V_estimate( ephemeris = lalpulsar.InitBarycenter(earth_ephem, sun_ephem) try: SSkyMetric = lalpulsar.ComputeSuperskyMetrics( - spindowns, ref_time, segments, fiducial_freq, detectors, - detector_weights, detector_motion, ephemeris) + lalpulsar.SUPERSKY_METRIC_TYPE, spindowns, ref_time, segments, + fiducial_freq, detectors, detector_weights, detector_motion, + ephemeris) except RuntimeError as e: logging.debug('Encountered run-time error {}'.format(e)) return None, None, None @@ -181,6 +232,6 @@ def get_V_estimate( dV = np.abs(np.linalg.det(parallelepiped)) - V = sqrtdetG * dV + Nstar = sqrtdetG * dV - return V + return Nstar