def __init__(self, test=False): """Initializing.""" self.test = test self.set_file_name() # Setup database self.db = False self.step = 0.1 self.rounding = 2 # For parallel processes self.temp_path = None if self.test: self.step = 0.1 if os.path.exists(self.file_name): os.remove(self.file_name) if os.path.exists(self.file_name) and self.test is False: self.db = True else: # Calculations take quite some time # Provide a way for people to quit try: self.create_table() except KeyboardInterrupt: pprint('Losing all progress in calculations') os.remove(self.file_name) if self.temp: os.remove(self.temp_path) sys.exit()
def __init__(self, H_0=67.74, W_m=0.3089, W_v=0.6911, test=False): """Initializing.""" self.H_0 = H_0 self.W_m = W_m self.W_v = W_v self.test = test self.set_file_name() # Setup database self.db = False self.step = 0.00001 self.z_max = 6.5 if self.test: self.step = 0.001 self.z_max = 6.5 if os.path.exists(self.file_name): os.remove(self.file_name) if os.path.exists(self.file_name) and self.test is False: self.db = True else: # Calculations take quite some time # Provide a way for people to quit try: self.create_table() except KeyboardInterrupt: pprint('Losing all progress in calculations') os.remove(self.file_name) sys.exit()
def check(self, path): """Perform checks on path.""" # Just convient to have files ending in a slash if path[-1] != '/': path += '/' if not os.path.exists(path): pprint(f"Creating directory {path}") os.makedirs(path) return path
def generate(self): """Generate all manner of intrinsic parameters.""" # Let user know what's happening pprint(f'Generating {self.name} population') self.gen_dist() self.gen_direction() self.gen_gal_coords() self.gen_dm() self.gen_w(self.n_gen) self.gen_lum(self.n_gen) self.gen_si(self.n_gen) pprint(f'Finished generating {self.name} population')
def match_surveys(self, interrupt=True): """Match up frbs with surveys.""" # Merge survey names surf = os.path.join(self.data_dir, 'paper_survey.csv') self._surveys = pd.read_csv(surf) cols = ['frb_name', 'pub_description'] self.df = pd.merge(self.df, self._surveys, on=cols, how='outer') # Clean up possible unnamed columns self.df = self.df.loc[:, ~self.df.columns.str.contains('unnamed')] # Check whether any FRBs have not yet been assigned no_surveys = self.df['survey'].isnull() if interrupt: if any(no_surveys): ns_df = self.df[no_surveys] pprint('It seems there are new FRBs!') pprint( " - Frbcat doesn't know which *survey* was running when the FRB was seen" ) pprint( " - To use these recent detections, please link the FRB to a survey by:" ) pprint(' - Adding these frbs to {}'.format(surf)) for i, r in ns_df[['pub_description', 'frb_name']].iterrows(): title, name = r if isinstance(title, str): title = title.replace('\n', '') print(f'"{title}","{name}",""')
def get_data(self): """Read in populations.""" # Read in files for f in self.files: # Check whether file exists if os.path.isfile(f): try: df = unpickle(f).frbs.to_df() except ValueError: continue if '.' in f: name = f.split('/')[-1].split('.')[0] else: name = f if df is not None: pass else: m = 'Skipping population {} - contains no sources'.format(f) pprint(m) continue # Downsample population size if it's too large if df.shape[0] > 10000: df = df.iloc[::1000] df['population'] = name df['color'] = self.colours[self.n_df] df['lum_bol'] = df['lum_bol'] / 1e30 # Sidestepping Bokeh issue self.dfs.append(df) self.n_df += 1 # Add on frbcat if self.frbcat: df = Frbcat().df # Filter by survey if wished if isinstance(self.frbcat, str): if df['survey'].str.match(self.frbcat).any(): df = df[df.survey == self.frbcat] elif df['telescope'].str.match(self.frbcat).any(): df = df[df.telescope == self.frbcat] else: m = 'Your chosen input for frbcat is not found.' raise ValueError(m) df['population'] = f'frbcat {self.frbcat}' df['color'] = self.colours[len(self.dfs)] self.dfs.append(df)
def get_data(self): """Read in populations.""" # Read in files for f in self.files: # Check whether file exists if os.path.isfile(f): try: df = unpickle(f).frbs.to_df() except ValueError: pprint(f'Unpacking {f} seemed to have failed.') continue if '.' in f: name = f.split('/')[-1].split('.')[0] if '_for_plotting' in name: name = name.split('_for_plotting')[0] else: name = f # If things haven't worked if df is None: m = 'Skipping population {} - contains no sources'.format(f) pprint(m) continue # Downsample population size if it's too large if df.shape[0] > 10000: pprint(f'Downsampling population {f} (else too big to plot)') df = df.sample(n=10000) df['population'] = name df['color'] = self.colours[self.n_df] df['lum_bol'] = df['lum_bol'] / 1e30 # Sidestepping Bokeh issue if df.empty: m = 'Skipping population {} - contains no sources'.format(f) pprint(m) continue else: self.dfs.append(df) self.n_df += 1 # Add on frbcat if self.frbcat: df = Frbcat().df # Filter by survey if wished if isinstance(self.frbcat, str): if df['survey'].str.match(self.frbcat).any(): df = df[df.survey == self.frbcat] elif df['telescope'].str.match(self.frbcat).any(): df = df[df.telescope == self.frbcat] else: m = 'Your chosen input for frbcat is not found.' raise ValueError(m) df['population'] = f'frbcat {self.frbcat}' df['color'] = self.colours[len(self.dfs)] self.dfs.append(df)
def to_df(self): """Gather source values into a pandas dataframe.""" values = self._values() if not values: return None if len(values) >= 200000: # Take quarter of the values pprint(f'Quartering {self.name} population') values = values[:len(values) // 4] index = values.rfind('\n') values = values[:index] data = StringIO(values) df = pd.read_csv(data) return df
def create_table(self): """Create a lookup table for dispersion measure.""" # Connect to database conn = sqlite3.connect(self.file_name) c = conn.cursor() # Set array of coordinates gls = np.arange(-180., 180. + self.step, self.step) gbs = np.arange(-90., 90. + self.step, self.step) dist = 0.1 # [Gpc] # Create database c.execute('create table dm ' + '(gl real, gb real, dm_mw real)') results = [] # Give an update on the progress pprint('Creating a DM lookup table (only needs to be done once)') for gl in gls: gl = round(gl, 1) for gb in gbs: gb = round(gb, 1) dm_mw = go.ne2001_dist_to_dm(dist, gl, gb) r = (gl, gb, dm_mw) results.append(r) sys.stdout.write('\r{}'.format(gl)) sys.stdout.flush() # Save results to database c.executemany('insert into dm values (?,?,?)', results) # Make for easier searching c.execute('create index ix on dm (gl, gb)') # Save conn.commit()
def match_surveys(self, interrupt=True): """Match up frbs with surveys.""" # Merge survey names surf = os.path.join(self.data_dir, 'paper_survey.csv') self._surveys = pd.read_csv(surf) cols = ['frb', 'pub_description'] self.df = pd.merge(self.df, self._surveys, on=cols, how='outer') # Clean up possible unnamed columns self.df = self.df.loc[:, ~self.df.columns.str.contains('unnamed')] # Check whether any FRBs have not yet been assigned no_surveys = self.df['survey'].isnull() if interrupt: if any(no_surveys): ns_df = self.df[no_surveys] pprint('Please add these frbs to {}'.format(surf)) for i, r in ns_df[['pub_description', 'frb']].iterrows(): title, frb = r print(f'"{title}",{frb},')
def intensity_profile(self, shape=1, dimensions=2, rep_loc='random'): """Calculate intensity profile. Args: shape (tuple): Usually the shape of frbs.s_peak dimensions (int): Use a 2D beampattern or a 1D one. rep_loc (str): 'same' or 'random'. Whether repeaters are observed in the same spot or a different spot Returns: array, array: intensity profile, offset from beam [arcmin] """ # Calculate Full Width Half Maximum from beamsize self.fwhm = 2 * math.sqrt( self.beam_size_fwhm / math.pi) * 60 # [arcmin] offset = self.fwhm / 2 # Radius = diameter/2. if rep_loc == 'same': r = r = np.random.random(shape[0]) else: r = np.random.random(shape) if dimensions == 2: # 2D offset *= np.sqrt(r) elif dimensions == 1: # 1D offset *= r # Allow for a perfect beam pattern in which all is detected if self.gain_pattern == 'perfect': int_pro = np.ones(shape) self.beam_size = self.beam_size_fwhm return int_pro, offset # Formula's based on 'Interferometry and Synthesis in Radio # Astronomy' by A. Richard Thompson, James. M. Moran and # George W. Swenson, JR. (Second edition), around p. 15 max_offset = self.max_offset(self.n_sidelobes) self.beam_size = math.pi * (self.fwhm / 2 * max_offset / 60)**2 # [sq degrees] if self.gain_pattern == 'gaussian': # Set the maximum offset equal to the null after a sidelobe # I realise this pattern isn't an airy, but you have to cut # somewhere offset *= max_offset alpha = 2 * math.sqrt(math.log(2)) int_pro = np.exp(-(alpha * offset / self.fwhm)**2) return int_pro, offset elif self.gain_pattern == 'airy': # Set the maximum offset equal to the null after a sidelobe offset *= max_offset c = 299792458 conv = math.pi / (60 * 180) # Conversion arcmins -> radians eff_diam = c / (self.central_freq * 1e6 * conv * self.fwhm) a = eff_diam / 2 # Effective radius of telescope lamda = c / (self.central_freq * 1e6) ka = (2 * math.pi * a / lamda) kasin = ka * np.sin(offset * conv) int_pro = 4 * (j1(kasin) / kasin)**2 return int_pro, offset elif self.gain_pattern in ['parkes', 'apertif']: place = paths.models() + f'/beams/{self.gain_pattern}.npy' beam_array = np.load(place) b_shape = beam_array.shape ran_x = np.random.randint(0, b_shape[0], shape) ran_y = np.random.randint(0, b_shape[1], shape) int_pro = beam_array[ran_x, ran_y] offset = np.sqrt((ran_x - b_shape[0] / 2)**2 + (ran_y - b_shape[1] / 2)**2) # Scaling factors to correct for pixel scale if self.gain_pattern == 'apertif': # 1 pixel = 0.94' offset *= 240 / 256 # [arcmin] self.beam_size = 25. if self.gain_pattern == 'parkes': # 1 pixel = 54" offset *= 0.9 # [arcmin] self.beam_size = 9. return int_pro, offset else: pprint(f'Gain pattern "{self.gain_pattern}" not recognised')
def plot(*pops, files=[], frbcat=True, show=True, no_browser=False, mute=True, port=5006, print_command=False): """ Plot populations with bokeh. Has to save populations before plotting. Args: *pops (Population, optional): Add the populations you would like to see plotted files (list, optional): List of population files to plot. frbcat (bool, optional): Whether to plot frbcat parameters. Defaults to True show (bool, optional): Whether to display the plot or not. Mainly used for debugging purposes. Defaults to True. port (int): The port on which to launch Bokeh print_command (bool): Whether to show the command do_plot is running """ if len(pops) > 0: # Save populations for pop in pops: if type(pop) == str: name = pop else: pop.name = pop.name.lower() + '_for_plotting' name = pop.name pop.save() # Save location file_name = name + '.p' out = os.path.join(paths.populations(), file_name) files.append(out) # Command for starting up server command = 'nice -n 19'.split(' ') if show: command.extend('bokeh serve --show'.split(' ')) else: command.append('python3') # Command for changing port if port != 5006: command.append(f'--port={port}') # Add script path script = 'plot.py' out = os.path.join(os.path.dirname(__file__), script) command.append(out) # For the arguments command.append('--args') # Add frbcat command.append('-frbcat') if frbcat is False: command.append('False') if frbcat is True: command.append('True') elif type(frbcat) == str and len(frbcat) > 0: command.append(f'{frbcat}') # Add in populations for f in files: command.append(f'"{f}"') # Let people know what's happening pprint('Plotting populations') if print_command: pprint(' '.join(command)) pprint('Press Ctrl+C to quit') # Add method to gracefully quit plotting try: with open(os.devnull, 'w') as f: if mute: out = f else: out = None subprocess.run(command, stderr=out, stdout=out) except KeyboardInterrupt: print(' ') sys.exit()
def create_table(self, parallel=True): """Create a lookup table for dispersion measure.""" # Connect to database conn = sqlite3.connect(self.file_name) c = conn.cursor() # Set array of coordinates gls = np.arange(-180., 180. + self.step, self.step).round(1) gbs = np.arange(-90., 90. + self.step, self.step).round(1) dist = 0.1 # [Gpc] gls = gls.astype(np.float32) gbs = gbs.astype(np.float32) # Create database c.execute('create table dm ' + '(gl real, gb real, dm_mw real)') # Give an update on the progress m = [ 'Creating a DM lookup table', ' - Only needs to happen once', ' - Unfortunately pretty slow', ' - Prepare to wait for ~1.5h (4 cores)', ' - Time given as [time_spent<time_left] in (hh:)mm:ss', 'Starting to calculate DM values' ] for n in m: pprint(n) n_opt = len(gls) * len(gbs) options = np.array(np.meshgrid(gls, gbs)).T.reshape(-1, 2) dm_mw = np.zeros(len(options)).astype(np.float32) def dm_tot(i, dm_mw): gl, gb = options[i] dm_mw[i] = go.ne2001_dist_to_dm(dist, gl, gb) if parallel: temp_path = os.path.join(paths.models(), 'universe/') + 'temp.mmap' self.temp_path = temp_path # Make a temp memmap to have a sharedable memory object temp = np.memmap(temp_path, dtype=dm_mw.dtype, shape=len(dm_mw), mode='w+') # Parallel process in order to populate array r = range(n_opt) j = min([4, os.cpu_count() - 1]) print(os.cpu_count()) Parallel(n_jobs=j)(delayed(dm_tot)(i, temp) for i in tqdm(r)) # Map results r = np.concatenate((options, temp[:, np.newaxis]), axis=1) results = map(tuple, r.tolist()) # Delete the temporary directory and contents try: os.remove(temp_path) except FileNotFoundError: print(f'Unable to remove {temp_path}') else: for i in tqdm(range(n_opt)): dm_tot(i, dm_mw) # Save results to database dm_mw = dm_mw.astype(np.float32) r = np.concatenate((options, dm_mw[:, np.newaxis]), axis=1) results = map(tuple, r.tolist()) pprint(' - Saving results') c.executemany('insert into dm values (?,?,?)', results) # Make for easier searching c.execute('create index ix on dm (gl, gb)') # Save conn.commit() pprint('Finished DM table')
def create_table(self): """Create a lookup table for distances.""" m = [ 'Creating a distance table', ' - Only needs to happen once', ' - May take up to 2m on a single core' ] for n in m: pprint(n) # Connect to database conn = sqlite3.connect(self.file_name) c = conn.cursor() H_0 = self.H_0 W_m = self.W_m W_v = self.W_v W_k = 1.0 - W_m - W_v # Omega curvature if W_k != 0.0: pprint('Careful - Your cosmological parameters do not sum to 1.0') zs = np.arange(0, self.z_max + self.step, self.step) # Create database t = 'real' par = f'(z {t}, dist {t}, vol {t}, dvol {t}, cdf_sfr {t}, cdf_smd {t})' s = f'create table distances {par}' c.execute(s) results = [] pprint(' - Calculating parameters at various redshifts') conv = go.Redshift(zs, H_0=H_0, W_m=W_m, W_v=W_v) dists = conv.dist_co() vols = conv.vol_co() # Get dV dvols = np.zeros_like(vols) dvols[1:] = np.diff(vols) pprint(' - Calculating Star Formation Rate') # Get pdf sfr pdf_sfr = sfr(zs) * dvols cdf_sfr = np.cumsum(pdf_sfr) # Unnormalized cdf_sfr /= cdf_sfr[-1] pprint(' - Calculating Stellar Mass Density') # Get pdf csmd pdf_smd = smd(zs, H_0=H_0, W_m=W_m, W_v=W_v) * dvols cdf_smd = np.cumsum(pdf_smd) # Unnormalized cdf_smd /= cdf_smd[-1] results = np.stack((zs, dists, vols, dvols, cdf_sfr, cdf_smd)).T pprint(' - Saving values to database') # Save results to database data = map(tuple, results.tolist()) c.executemany('insert into distances values (?,?,?,?,?,?)', data) # Make for easier searching # I don't really understand SQL index names... c.execute('create index ix on distances (z)') c.execute('create index ixx on distances (dist)') c.execute('create index ixxx on distances (vol)') c.execute('create index ixxxx on distances (dvol)') c.execute('create index ixxxxx on distances (cdf_sfr)') c.execute('create index ixxxxxx on distances (cdf_smd)') # Save conn.commit() pprint('Finished distance table')
def get(self, internet=True, save=True, local=False): """ Get frbcat from online or from a local file. Args: internet (bool): Whether to get a new version of frbcat local (bool): Whether to use a local version of frbcat """ # Check whether a copy of FRBCAT has already been downloaded # Ensures frbcat is only queried once a month path = self.data_dir + '/frbcat_' path += str(datetime.datetime.today()).split()[0][:-3] path += '-??.csv' exists = glob.glob(path) if internet and exists: internet = False local = True if internet: try: pprint('Attempting to retrieve FRBCAT from www.frbcat.org') # First get all FRB names from the main page pprint(' - Getting FRB names') url = 'http://frbcat.org/products/' main_df = self.url_to_df(url) # Then get any subsequent analyses (multiple entries per FRB) pprint(' - Getting subsequent analyses') url = 'http://frbcat.org/product/' frb_df = self.urls_to_df(main_df.frb_name, url) # Find all frb note properties pprint(' - Getting notes on FRBs') url = 'http://frbcat.org/frbnotes/' frbnotes_df = self.urls_to_df(set(frb_df.index), url) if frbnotes_df is not None: frbnotes_df = frbnotes_df.add_prefix('frb_notes_') # Find all notes on radio observation parameters pprint(' - Getting radio observation parameters') url = 'http://frbcat.org/ropnotes/' ropnotes_df = self.urls_to_df(set(frb_df.index), url) if ropnotes_df is not None: ropnotes_df = ropnotes_df.add_prefix('rop_notes_') # Find all radio measurement parameters pprint(' - Getting radio measurement parameters') url = 'http://frbcat.org/rmppubs/' rmppubs_df = self.urls_to_df(set(frb_df.index), url) rmppubs_df = rmppubs_df.add_prefix('rmp_pub_') # Have skipped # 'http://frbcat.org/rmpimages/<rmp_id>' (images) # 'http://frbcat.org/rmpnotes/<rmp_id>' (empty) # Merge all databases together try: df = pd.merge(frb_df, frbnotes_df, left_on='frb_id', right_on='frb_notes_frb_id', how='left') except ValueError: df = frb_df df = pd.merge(df, ropnotes_df, left_on='rop_id', right_on='rop_notes_rop_id', how='left') self.df = pd.merge(df, rmppubs_df, left_on='rmp_id', right_on='rmp_pub_rmp_id', how='left') pprint('Succeeded') if save: date = str(datetime.datetime.today()).split()[0] path = self.data_dir + f'/frbcat_{date}.csv' self.df.to_csv(path) local = False # Unless there's no internet except requests.ConnectionError: local = True if local or not internet: # Find latest version of frbcat f = max(glob.glob(self.data_dir + '/frbcat*.csv'), key=os.path.getctime) pprint(f"Using {f.split('/')[-1]}") self.df = pd.read_csv(f)
def __init__(self, cosmic_pop, survey, scat=False, scin=False, rate_limit=True): """ Run a survey to detect FRB sources. Args: cosmic_pop (Population): Population class of FRB sources to observe survey (Survey): Survey class with which to observe scat (bool, optional): Whether to include scattering in signal to noise calculations. scin (bool, optional): Whether to apply scintillation to observations. rate_limit (bool, optional): Whether to limit detections by 1/(1+z) due to limitation in observing time """ # Set up population Population.__init__(self) # Set attributes self.name = survey.name self.time = cosmic_pop.time self.vol_co_max = cosmic_pop.vol_co_max self.frbs = deepcopy(cosmic_pop.frbs) self.rate = Rates() pprint(f'Surveying {cosmic_pop.name} with {self.name}') frbs = self.frbs # Check whether source is in region region_mask = survey.in_region(frbs) frbs.apply(region_mask) self.rate.out = np.size(region_mask) - np.count_nonzero(region_mask) # Calculate dispersion measure across single channel frbs.t_dm = survey.dm_smear(frbs) # Set scattering timescale if scat: frbs.t_scat = survey.calc_scat(frbs.dm) # Calculate total temperature frbs.T_sky, frbs.T_sys = survey.calc_Ts(frbs) # Calculate effective pulse width frbs.w_eff = survey.calc_w_eff(frbs) # Calculate peak flux density f_min = cosmic_pop.f_min f_max = cosmic_pop.f_max frbs.s_peak = survey.calc_s_peak(frbs, f_low=f_min, f_high=f_max) # Account for beam offset int_pro, _ = survey.intensity_profile(n_gen=len(frbs.s_peak)) frbs.s_peak *= int_pro # Calculate fluence [Jy*ms] frbs.fluence = frbs.s_peak * frbs.w_eff # Caculate Signal to Noise Ratio frbs.snr = survey.calc_snr(frbs) # Add scintillation if scin: frbs.snr = survey.calc_scint(frbs) # Check whether frbs would be above detection threshold snr_mask = (frbs.snr >= survey.snr_limit) frbs.apply(snr_mask) self.rate.faint = np.size(snr_mask) - np.count_nonzero(snr_mask) if rate_limit is True: limit = 1 / (1 + frbs.z) rate_mask = np.random.random(len(frbs.z)) <= limit frbs.apply(rate_mask) self.rate.late = np.size(rate_mask) - np.count_nonzero(rate_mask) self.rate.det = len(frbs.snr) # Calculate scaling factors for rates area_sky = 4 * math.pi * (180 / math.pi)**2 # In sq. degrees f_area = (survey.beam_size * self.rate.tot()) inside = self.rate.det + self.rate.late + self.rate.faint f_area /= (inside * area_sky) f_time = 86400 / self.time # Saving scaling factors self.rate.days = self.time / 86400 # seconds -> days self.rate.f_area = f_area self.rate.f_time = f_time self.rate.name = self.name self.rate.vol = self.rate.tot() self.rate.vol /= self.vol_co_max * (365.25 * 86400 / self.time)
curdoc().title = 'frbpoppy' curdoc().add_root(L) # Parse system arguments # (I know ArgumentParser is nicer, but bokeh only works with argv) args = sys.argv # Whether to plot the frbcat population if '-frbcat' in args: frbcat = args[args.index('-frbcat') + 1] if frbcat == 'True': frbcat = True elif frbcat == 'False': frbcat = False else: frcat = True # Which files to plot files = [] for a in args: a = a.strip('"') if a.endswith('.p'): files.append(a) # Check whether populations have been given as input if len(files) == 0: pprint('Nothing to plot: plot arguments are empty') else: Plot(files=files, frbcat=frbcat)
def __init__(self, cosmic_pop, survey, scat=False, scin=False, rate_limit=True): """ Run a survey to detect FRB sources. Args: cosmic_pop (Population): Population class of FRB sources to observe survey (Survey): Survey class with which to observe scat (bool, optional): Whether to include scattering in signal to noise calculations. scin (bool, optional): Whether to apply scintillation to observations. rate_limit (bool, optional): Whether to limit detections by 1/(1+z) due to limitation in observing time """ pprint(f'Surveying {cosmic_pop.name} with {survey.name}') # Stops RuntimeWarnings about nan values np.warnings.filterwarnings('ignore') # Set up population Population.__init__(self) # Set attributes self.name = survey.name self.time = cosmic_pop.time self.vol_co_max = cosmic_pop.vol_co_max self.frbs = deepcopy(cosmic_pop.frbs) self.rate = Rates() frbs = self.frbs # Check whether source is in region region_mask = survey.in_region(frbs) frbs.apply(region_mask) self.rate.out = np.size(region_mask) - np.count_nonzero(region_mask) if survey.pointings: p_mask = survey.in_pointings(frbs) frbs.apply(p_mask) self.rate.out += np.size(p_mask) - np.count_nonzero(p_mask) # Calculate dispersion measure across single channel frbs.t_dm = survey.dm_smear(frbs) # Set scattering timescale if scat: frbs.t_scat = survey.calc_scat(frbs.dm) # Calculate total temperature frbs.T_sky, frbs.T_sys = survey.calc_Ts(frbs) # Calculate effective pulse width frbs.w_eff = survey.calc_w_eff(frbs) # Calculate peak flux density f_min = cosmic_pop.f_min f_max = cosmic_pop.f_max frbs.s_peak = survey.calc_s_peak(frbs, f_low=f_min, f_high=f_max) # Account for beam offset int_pro, offset = survey.intensity_profile(shape=frbs.s_peak.shape) if int_pro.ndim == frbs.s_peak.ndim: frbs.s_peak *= int_pro else: frbs.s_peak *= int_pro[:, None] frbs.offset = offset / 60. # In degrees # Calculate fluence [Jy*ms] frbs.fluence = survey.calc_fluence(frbs) # Caculate Signal to Noise Ratio frbs.snr = survey.calc_snr(frbs) # Add scintillation if scin: # Not sure whether this can cope with 2D arrays frbs.snr = survey.calc_scint(frbs) # Check whether frbs would be above detection threshold snr_mask = (frbs.snr >= survey.snr_limit) frbs.apply(snr_mask) self.rate.faint = np.size(snr_mask) - np.count_nonzero(snr_mask) if rate_limit is True: limit = 1 / (1 + frbs.z) rate_mask = np.random.random(len(frbs.z)) <= limit frbs.apply(rate_mask) self.rate.late = np.size(rate_mask) - np.count_nonzero(rate_mask) self.rate.det = len(frbs.snr) self.calc_rates(survey)
def __init__(self, n_gen, days=1, name='cosmic', H_0=67.74, W_m=0.3089, W_v=0.6911, dm_host_model='normal', dm_host_mu=100, dm_host_sigma=200, dm_igm_index=1000, dm_igm_sigma=None, dm_mw_model='ne2001', emission_range=[10e6, 10e9], lum_range=[1e40, 1e45], lum_index=0, n_model='sfr', alpha=-1.5, pulse_model='lognormal', pulse_range=[0.1, 10], pulse_mu=0.1, pulse_sigma=0.5, si_mu=-1.4, si_sigma=1., z_max=2.5): """Generate a popuation of FRBs. Args: n_gen (int): Number of FRB sources/sky/time to generate. days (float): Number of days over which FRBs are generated. name (str): Population name. H_0 (float): Hubble constant. W_m (float): Density parameter Ω_m. W_v (float): Cosmological constant Ω_Λ. dm_host_model (float): Dispersion measure host model. Options are 'normal' or 'lognormal'. dm_host_mu (float): Mean dispersion measure host [pc/cm^3]. dm_host_sigma (float): Deviation dispersion measure host [pc/cm^3]. dm_igm_index (float): Dispersion measure slope for IGM [pc/cm^3]. dm_igm_sigma (float): Scatter around dm_igm. Defaults 0.2*slope*z dm_mw_model (str): Dispersion measure model for the Milky Way. Options are 'ne2001' or 'zero'. emission_range (list): The frequency range [Hz] between which FRB sources should emit the given bolometric luminosity. lum_range (list): Bolometric luminosity (distance) range [erg/s]. lum_index (float): Power law index. n_model (str): Number density model. Either 'vol_co', 'sfr' or 'smd'. alpha (float): Desired logN/logS of perfectly detected population. pulse_model (str): Pulse width model, 'lognormal' or 'uniform'. pulse_range (list): Pulse width range [ms]. pulse_mu (float): Mean pulse width [ms]. pulse_sigma (float): Deviation pulse width [ms]. si_mu (float): Mean spectral index. si_sigma (float): Standard deviation spectral index. z_max (float): Maximum redshift. Returns: Population: Population of FRBs. """ # Set up population Population.__init__(self) self.alpha = alpha self.dm_host_model = dm_host_model self.dm_host_mu = dm_host_mu self.dm_host_sigma = dm_host_sigma self.dm_igm_index = dm_igm_index self.dm_igm_sigma = dm_igm_sigma self.dm_mw_model = dm_mw_model self.f_max = emission_range[1] self.f_min = emission_range[0] self.H_0 = H_0 self.lum_max = lum_range[1] self.lum_min = lum_range[0] self.lum_pow = lum_index self.name = name self.n_gen = n_gen self.n_model = n_model self.si_mu = si_mu self.si_sigma = si_sigma self.time = days * 86400 # Convert to seconds self.w_model = pulse_model self.w_max = pulse_range[1] self.w_min = pulse_range[0] self.w_mu = pulse_mu self.w_sigma = pulse_sigma self.W_m = W_m self.W_v = W_v self.z_max = z_max # Cosmology calculations r = go.Redshift(self.z_max, H_0=self.H_0, W_m=self.W_m, W_v=self.W_v) self.dist_co_max = r.dist_co() self.vol_co_max = r.vol_co() # Ensure precalculations are done if necessary pc.DistanceTable(H_0=self.H_0, W_m=self.W_m, W_v=self.W_v) # Set up number density n_den = NumberDensity(model=self.n_model, z_max=self.z_max, alpha=self.alpha, H_0=self.H_0, W_m=self.W_m, W_v=self.W_v).draw # Let user know what's happening pprint(f'Generating {self.name} population') frbs = self.frbs # Add random directional coordinates frbs.gl = np.random.random(n_gen) * 360.0 - 180 frbs.gb = np.degrees(np.arcsin(np.random.random(n_gen))) frbs.gb[::2] *= -1 # Convert frbs.ra, frbs.dec = go.lb_to_radec(frbs.gl, frbs.gb) # Draw from number density frbs.z, frbs.dist_co = n_den(n_gen) # Get the proper distance dist_pr = frbs.dist_co / (1 + frbs.z) # Convert into galactic coordinates frbs.gx, frbs.gy, frbs.gz = go.lb_to_xyz(frbs.gl, frbs.gb, dist_pr) # Dispersion measure of the Milky Way if self.dm_mw_model == 'ne2001': frbs.dm_mw = pc.NE2001Table().lookup(frbs.gl, frbs.gb) elif self.dm_mw_model == 'zero': frbs.dm_mw = np.zeros_like(frbs.z) # Dispersion measure of the intergalactic medium frbs.dm_igm = go.ioka_dm_igm(frbs.z, slope=self.dm_igm_index, sigma=self.dm_igm_sigma) # Dispersion measure of the host (Tendulkar) if self.dm_host_model == 'normal': frbs.dm_host = dis.trunc_norm(self.dm_host_mu, self.dm_host_sigma, n_gen).astype(np.float64) elif self.dm_host_model == 'lognormal': frbs.dm_host = np.random.lognormal(self.dm_host_mu, self.dm_host_sigma, n_gen).astype(np.float64) frbs.dm_host /= (1 + frbs.z) # Total dispersion measure frbs.dm = frbs.dm_mw + frbs.dm_igm + frbs.dm_host # Get a random intrinsic pulse width [ms] if self.w_model == 'lognormal': frbs.w_int = np.random.lognormal(self.w_mu, self.w_sigma, n_gen) if self.w_model == 'uniform': frbs.w_int = np.random.uniform(self.w_min, self.w_max, n_gen) # Calculate the pulse width upon arrival to Earth frbs.w_arr = frbs.w_int * (1 + frbs.z) # Add bolometric luminosity [erg/s] frbs.lum_bol = dis.powerlaw(self.lum_min, self.lum_max, self.lum_pow, n_gen) # Add spectral index frbs.si = np.random.normal(si_mu, si_sigma, n_gen) pprint('Finished')