gwsnr 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gwsnr-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.1
2
+ Name: gwsnr
3
+ Version: 0.1.0
4
+ Summary: Fast SNR interpolator
5
+ Home-page: https://github.com/hemantaph/gwsnr
6
+ Author: Hemantakumar, Otto
7
+ Author-email: hemantaphurailatpam@gmail.com
8
+ License: MIT
gwsnr-0.1.0/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # Quintet
2
+
3
+ QUINTET - QUick sIgNal To noisE raTio
4
+
5
+ is a fast package for computing signal-to-noise ratios for any spinless, non-HOM binary black hole system.
6
+
7
+ # Installation
8
+
9
+ * git clone https://git.ligo.org/otto.hannuksela/quintet.git
10
+ * cd quintet .
11
+ * from datetime import datetime
12
+
13
+ # How to use (python ide)
14
+
15
+ * from quintet import Quintet as quin
16
+ * quin_ = quin()
17
+
18
+ ## for further details on how to use, refer to quintet/test.ipynb file
@@ -0,0 +1,2 @@
1
+ from .gwsnr import GWSNR
2
+
@@ -0,0 +1,1024 @@
1
+ # for delta_f =1/duration, duration = 16s
2
+ # f_min =20Hz
3
+ # duration=16.0, sampling_frequency=4096,
4
+ # note: setting mtot_min and mtot_max is important.
5
+ # mtot_min=219. is accordance to minimum_frequency = 20
6
+ # __init__ paramters are important don't to change for a particular analysis
7
+ # they are detector and waveform dependent parameters
8
+ # at f_min==10Hz: mtot_max=439.6
9
+ import numpy as np
10
+ import bilby
11
+ from pycbc.detector import Detector
12
+ from scipy.stats import norm
13
+ from scipy.interpolate import interp1d
14
+ from gwpy.timeseries import TimeSeries
15
+ from scipy.optimize import fsolve
16
+ from multiprocessing import Pool
17
+ import pycbc.psd
18
+ from tqdm import tqdm
19
+ import warnings
20
+ import json
21
+ import os
22
+ import pickle
23
+
24
+ C = 299792458.
25
+ G = 6.67408*1e-11
26
+ Mo = 1.989*1e30
27
+ Gamma = 0.5772156649015329
28
+ Pi = np.pi
29
+ MTSUN_SI = 4.925491025543576e-06
30
+
31
+ '''
32
+ ------------------------------------------------
33
+ class containing following methods
34
+ 1. to calculate fast SNR
35
+ 2. interpolation of with cubic spline
36
+ with bilby SNR
37
+ 3. Pdet: probability of detection
38
+ ------------------------------------------------
39
+ '''
40
+ class GWSNR():
41
+ ####################################################
42
+ # #
43
+ # Class initialization #
44
+ # #
45
+ ####################################################
46
+ def __init__(self, npool=int(4), mtot_min=2., mtot_max=439.6, nsamples_mtot=100, nsamples_mass_ratio=50, \
47
+ sampling_frequency=4096.,\
48
+ waveform_approximant = 'TaylorF2', minimum_frequency = 20., \
49
+ snr_type = 'interpolation', waveform_inspiral_must_be_above_fmin=False, psds=False, psd_file=False):
50
+
51
+ '''
52
+ Initialized parameters and functions
53
+ snr_half_scaled() : function for finding (f/PSD) integration in the limit [f_min,f_max]
54
+ list_of_detectors : list of detector initials, e.g. L1 for Livingston
55
+ f_min : minimum frequency for the detector
56
+ -----------------
57
+ input parameters
58
+ -----------------
59
+ mtot_min : minimum value of Mtotal=mass_1+mass_2, use in interpolation
60
+ mtot_max : maximum value of Mtotal=mass_1+mass_2, use in interpolation
61
+ nsamples : number of points you want to use for SNR interpolation (here it is half SNR not complete)
62
+ list_of_detectors : detector list. It can be single or multiple.
63
+ duration : duration of the data in time domain.
64
+ sampling_frequency : sampling frequency of the data. e.g. 4096Hz,2048Hz,1024Hz
65
+ waveform_arguments : contains which waveform model to use for interpolation. Extra paramters like reference_frequency\
66
+ minimum_frequency are also included. minimum_frequency will also relate to the mtot_max set inside\
67
+ the code. High mass blackholes tends to merge at lower frequency < f_min, and can have SNR=0
68
+ snr_type : method for SNR calculation. Values: 'interpolation', 'inner_product'
69
+
70
+ psds : psd dict.
71
+ example_1=> when values are psd name from pycbc analytical psds,
72
+ psds={'L1':'aLIGOaLIGODesignSensitivityT1800044','H1':'aLIGOaLIGODesignSensitivityT1800044','V1':'AdvVirgo'}
73
+ to check available psd name run $ import pycbc.psd ; $ pycbc.psd.get_lalsim_psd_list()
74
+ example_2=> when values are psd txt file in bilby or custom created,
75
+ psds={'L1':'aLIGO_O4_high_asd.txt','H1':'aLIGO_O4_high_asd.txt'}
76
+ custom created txt file has two columns. 1st column: frequency array, 2nd column: strain
77
+ psd_file : if set True, the given value of psds param should be of psds instead of asd. If asd, set psd_file=False.
78
+
79
+ '''
80
+ self.npool = npool
81
+ self.mtot_min = mtot_min
82
+ self.mtot_max = mtot_max
83
+ self.nsamples = nsamples_mtot
84
+ ratio = np.geomspace(0.1,1,nsamples_mass_ratio)
85
+ self.ratio = ratio
86
+
87
+ self.sampling_frequency = sampling_frequency
88
+ self.waveform_approximant = waveform_approximant
89
+ self.f_min = minimum_frequency
90
+ self.waveform_type = self.waveform_classifier(waveform_approximant)
91
+ self.snr_type = snr_type
92
+ self.waveform_inspiral_must_be_above_fmin = waveform_inspiral_must_be_above_fmin
93
+ self.psd_file = psd_file
94
+ # pre-initialized half scaled snr with search sort
95
+ # self.halfSNR values are initialized
96
+ #print('waveform_type=',self.waveform_type)
97
+
98
+
99
+ if psds==False:
100
+ print("psds not given. Choosing bilby's default psds")
101
+ self.psds_default = True
102
+ psds = dict()
103
+ psds['L1'] = 'aLIGO_O4_high_asd.txt'
104
+ psds['H1'] = 'aLIGO_O4_high_asd.txt'
105
+ psds['V1'] = 'AdV_asd.txt'
106
+ self.psds = psds
107
+ self.list_of_detectors = list(psds.keys())
108
+ else:
109
+ self.psds_default = False
110
+ self.list_of_detectors = list(psds.keys())
111
+ print("given psds: ",psds)
112
+ self.psds = psds
113
+
114
+
115
+ if snr_type == 'interpolation':
116
+
117
+ # creating interpolator_pickle directory to store scipy inter
118
+ path = './interpolator_pickle'
119
+ if not os.path.exists(path):
120
+ os.makedirs(path)
121
+ dict_list = []
122
+ with open(path+'/param_dict_list.pickle', 'wb') as handle:
123
+ pickle.dump(dict_list, handle, protocol=pickle.HIGHEST_PROTOCOL)
124
+
125
+ # check existing interpolators
126
+ param_dict_stored = pickle.load(open("./interpolator_pickle/param_dict_list.pickle", "rb"))
127
+ param_dict_given = {'mtot_min':mtot_min, 'mtot_max':mtot_max, 'nsamples_mtot':nsamples_mtot,\
128
+ 'nsamples_mass_ratio':nsamples_mass_ratio, \
129
+ 'sampling_frequency':sampling_frequency, \
130
+ 'waveform_approximant':waveform_approximant,\
131
+ 'minimum_frequency':minimum_frequency, \
132
+ 'waveform_inspiral_must_be_above_fmin':waveform_inspiral_must_be_above_fmin,\
133
+ 'psds':psds ,'detector_list': self.list_of_detectors}
134
+
135
+ # checking for existing gwsnr interpolator or generate it
136
+ len_ = len(param_dict_stored)
137
+ if param_dict_given in param_dict_stored:
138
+ # try and except is added so that user can regenerate a new interpolator pickle file just by
139
+ # deleting the right file and reruing gwsnr with that params again
140
+ # also, if the user delete the file by mistake, it will generate in the next run
141
+ try:
142
+ print("getting stored interpolator...")
143
+ idx = param_dict_stored.index(param_dict_given)
144
+ path_interpolator_old = path+'/halfSNR_dict_'+str(idx)+'.pickle'
145
+ self.halfSNR = pickle.load(open(path_interpolator_old, "rb"))
146
+
147
+ print("In case if you need regeneration of interpolator of the given gwsnr param, please delete this file, {}".format(path_interpolator_old))
148
+ except:
149
+ print("interpolator not found, generating new interpolator")
150
+ self.__init_halfScaled() # you can also reinitialized this
151
+ with open(path_interpolator_old, 'wb') as handle:
152
+ pickle.dump(self.halfSNR, handle, protocol=pickle.HIGHEST_PROTOCOL)
153
+ print("interpolator stored as {}.".format(path_interpolator_old))
154
+ print("In case if you need regeneration of interpolator of the given gwsnr param, please delete this file, {}".format(path_interpolator_old))
155
+
156
+ # if interpolators are not found
157
+ else:
158
+ path_interpolator = path+'/halfSNR_dict_'+str(len_)+'.pickle'
159
+ print("generating new interpolator for the given new gwsnr params")
160
+ self.__init_halfScaled() # you can also reinitialized this
161
+ # self.snr_correction_func() # extra correction needed for the snr
162
+ with open(path_interpolator, 'wb') as handle:
163
+ pickle.dump(self.halfSNR, handle, protocol=pickle.HIGHEST_PROTOCOL)
164
+
165
+ param_dict_stored.append(param_dict_given)
166
+ with open("./interpolator_pickle/param_dict_list.pickle", 'wb') as handle:
167
+ pickle.dump(param_dict_stored, handle, protocol=pickle.HIGHEST_PROTOCOL)
168
+ print("interpolator stored as {}.".format(path_interpolator))
169
+ print("In case if you need regeneration of interpolator of the given gwsnr param, please delete this file, {}".format(path_interpolator))
170
+
171
+
172
+
173
+ ####################################################
174
+ # #
175
+ # waveform classifier #
176
+ # #
177
+ ####################################################
178
+ def waveform_classifier(self, waveform_approximant):
179
+ waveform_dict = {'Inspiral': ['TaylorF2','TaylorF2Ecc'], 'IMR': ['IMRPhenomD','IMRPhenomXPHM'], 'Ringdown': [],}
180
+ if waveform_approximant in waveform_dict['Inspiral']:
181
+ print('Given: Inspiral waveform')
182
+ return('inspiral')
183
+ elif waveform_approximant in waveform_dict['IMR']:
184
+ print('Given: IMR waveform')
185
+ return('IMR')
186
+ else:
187
+ print('waveform type not recognised. It will be considered as IMRPhenom waveform')
188
+
189
+ ####################################################
190
+ # #
191
+ # Main SNR finder function #
192
+ # #
193
+ ####################################################
194
+ def snr(self, mass_1=10., mass_2=10., luminosity_distance=100., iota=0., \
195
+ psi=0., phase=0., geocent_time=1246527224.169434, ra=0., dec=0., GWparam_dict=False, verbose=True, jsonFile=False):
196
+ '''
197
+ -----------------
198
+ Input parameters (GW parameters)
199
+ -----------------
200
+ mass_1 : Heavier compact object of the binary, unit: Mo (solar mass)
201
+ (flaot array or just float)
202
+ mass_2 : Lighter compact object of the binary, unit: Mo (solar mass)
203
+ (flaot array or just float)
204
+ luminosity_distance : Distance between detector and binary, unit: Mpc
205
+ iota : Inclination angle of binary orbital plane wrt to the line of sight. unit: rad
206
+ psi : Polarization angle. unit: rad
207
+ phase : Phase of GW at the the time of coalesence, unit: rad
208
+ geocent_time : GPS time of colescence of that GW, unit: sec
209
+ ra : Right ascention of source position, unit: rad
210
+ dec : Declination of source position, unit: rad
211
+ -----------------
212
+ Return values
213
+ -----------------
214
+ snr_dict : dictionary containing net optimal snr and optimal snr of individual detectors
215
+ example of opt_snr_unscaled return values for len(mass_1)=3
216
+ {'opt_snr_net': array([156.53268655, 243.00092419, 292.10396943]),
217
+ 'L1': array([132.08275995, 205.04492349, 246.47822334]),
218
+ 'H1': array([ 84.00372897, 130.40716432, 156.75845871])}
219
+
220
+ '''
221
+ if GWparam_dict!=False:
222
+ mass_1 = GWparam_dict['mass_1']
223
+ mass_2 = GWparam_dict['mass_2']
224
+ luminosity_distance = GWparam_dict['luminosity_distance']
225
+ iota = GWparam_dict['iota']
226
+ psi = GWparam_dict['psi']
227
+ phase = GWparam_dict['phase']
228
+ geocent_time = GWparam_dict['geocent_time']
229
+ ra = GWparam_dict['ra']
230
+ dec = GWparam_dict['dec']
231
+
232
+ if self.snr_type == 'interpolation':
233
+ snr_dict = self.snr_with_interpolation(mass_1, mass_2, luminosity_distance=luminosity_distance, iota=iota, \
234
+ psi=psi, phase=phase, geocent_time=geocent_time, ra=ra, dec=dec, jsonFile=jsonFile)
235
+ elif self.snr_type == 'inner_product':
236
+ print('solving SNR with inner product')
237
+ snr_dict = self.compute_bilby_snr_(mass_1, mass_2, luminosity_distance=luminosity_distance, theta_jn=iota, \
238
+ psi=psi, phase=phase, geocent_time=geocent_time, ra=ra, dec=dec, verbose=verbose, jsonFile=jsonFile)
239
+ else:
240
+ print('SNR function type not recognised, using inner_product method')
241
+ snr_dict = self.compute_bilby_snr_(mass_1, mass_2, luminosity_distance=luminosity_distance, theta_jn=iota, \
242
+ psi=psi, phase=phase, geocent_time=geocent_time, ra=ra, dec=dec, jsonFile=jsonFile)
243
+ return(snr_dict)
244
+
245
+ ####################################################
246
+ # #
247
+ # fast snr with cubic spline interpolation #
248
+ # #
249
+ ####################################################
250
+ def snr_with_interpolation(self, mass_1, mass_2, luminosity_distance=100., iota=0., \
251
+ psi=0., phase=0., geocent_time=1246527224.169434, ra=0., dec=0., jsonFile=False):
252
+ '''
253
+ -----------------
254
+ Input parameters (GW parameters)
255
+ -----------------
256
+ mass_1 : Heavier compact object of the binary, unit: Mo (solar mass)
257
+ (flaot array or just float)
258
+ mass_2 : Lighter compact object of the binary, unit: Mo (solar mass)
259
+ (flaot array or just float)
260
+ luminosity_distance : Distance between detector and binary, unit: Mpc
261
+ iota : Inclination angle of binary orbital plane wrt to the line of sight. unit: rad
262
+ psi : Polarization angle. unit: rad
263
+ phase : Phase of GW at the the time of coalesence, unit: rad
264
+ geocent_time : GPS time of colescence of that GW, unit: sec
265
+ ra : Right ascention of source position, unit: rad
266
+ dec : Declination of source position, unit: rad
267
+ -----------------
268
+ Return values
269
+ -----------------
270
+ opt_snr : dictionary containing net optimal snr and optimal snr of individual detectors
271
+ example of opt_snr_unscaled return values for len(mass_1)=3
272
+ {'opt_snr_net': array([156.53268655, 243.00092419, 292.10396943]),
273
+ 'L1': array([132.08275995, 205.04492349, 246.47822334]),
274
+ 'H1': array([ 84.00372897, 130.40716432, 156.75845871])}
275
+
276
+ '''
277
+ mass_1, mass_2 = np.array([mass_1]).reshape(-1), np.array([mass_2]).reshape(-1)
278
+ size = len(mass_1)
279
+ luminosity_distance, theta_jn, psi, phase, geocent_time, ra, dec = \
280
+ np.array([luminosity_distance]).reshape(-1)*np.ones(size), \
281
+ np.array([iota]).reshape(-1)*np.ones(size), \
282
+ np.array([psi]).reshape(-1)*np.ones(size), \
283
+ np.array([phase]).reshape(-1)*np.ones(size), \
284
+ np.array([geocent_time]).reshape(-1)*np.ones(size), \
285
+ np.array([ra]).reshape(-1)*np.ones(size), \
286
+ np.array([dec]).reshape(-1)*np.ones(size)
287
+
288
+ Mc = ( (mass_1*mass_2)**(3/5) )/( (mass_1+mass_2)**(1/5) )
289
+ mtot = mass_1+mass_2
290
+ luminosity_distance = luminosity_distance
291
+
292
+ '''
293
+ # dealing with mtot array
294
+ # mtot > mtot_max will be have snr = 0.
295
+ snr_half_scaled = np.zeros(size) # for mtot > mtot_max, set zero value will not change later
296
+ idx2 = np.array(np.where(self.mtot_max>=mtot)).reshape(-1).tolist() # record index with mtot values less than mtot_max
297
+ # getting simple snr_half_scaled values for interpolation
298
+ halfSNR_interpolator = self.halfSNR
299
+ '''
300
+ snr_half_scaled = np.zeros(size)
301
+ approx_duration = self.findchirp_chirptime(mass_1, mass_2, self.f_min)
302
+ # select only those that have inspiral part above f_min
303
+ if self.waveform_inspiral_must_be_above_fmin==True:
304
+ idx2 = approx_duration>0.
305
+ else:
306
+ idx2 = np.full(size,True)
307
+
308
+ idx2 = idx2&(mtot>=self.mtot_min)&(mtot<=self.mtot_max)
309
+
310
+ # getting simple snr_half_scaled values for interpolation
311
+ halfSNR_interpolator = self.halfSNR
312
+
313
+ A1 = Mc**(5./6.)
314
+ ci_2 = np.cos(iota)**2
315
+ ci_param = ((1+np.cos(iota)**2)/2)**2
316
+ detectors = self.list_of_detectors
317
+
318
+ opt_snr = {'opt_snr_net': 0}
319
+
320
+ idx_ratio = np.searchsorted(self.ratio, mass_2/mass_1)
321
+ idx_tracker = np.arange(size)
322
+ idx_tracker = idx_tracker[idx2]
323
+ #self.idx_ratio = idx_ratio
324
+ # loop wrt detectors
325
+ i = 0
326
+ for det in detectors:
327
+ # calculation of snr_half_scaled for particular detector at the required mtot
328
+ for j in idx_tracker:
329
+ snr_half_scaled[j] = halfSNR_interpolator[idx_ratio[j],i](mtot[j]) # i is iterator wrt detectors
330
+
331
+ Fp, Fc = Detector(det).antenna_pattern(ra, dec, psi, geocent_time)
332
+ Deff1 = luminosity_distance/np.sqrt( Fp**2*ci_param + Fc**2*ci_2 )
333
+
334
+ opt_snr[det] = (A1/Deff1)*snr_half_scaled
335
+ opt_snr['opt_snr_net'] += opt_snr[det]**2
336
+ i+=1
337
+
338
+ opt_snr['opt_snr_net'] = np.sqrt(opt_snr['opt_snr_net'])
339
+ self.stored_snrs = opt_snr # this stored snrs can be use for Pdet calculation
340
+
341
+ # saving as json file
342
+ if jsonFile:
343
+ parameters_dict = {'mass_1':mass_1, 'mass_2':mass_2, 'luminosity_distance':luminosity_distance, 'theta_jn':theta_jn, 'psi':psi, 'phase':phase, 'ra':ra, 'dec':dec, 'geocent_time':geocent_time,}
344
+ parameters_dict.update(opt_snr)
345
+ file_name = './bilby_GWparams_interpolatedSNRs.json'
346
+ json_dump = json.dumps(parameters_dict, cls=NumpyEncoder)
347
+ with open(file_name, "w") as write_file:
348
+ json.dump(json.loads(json_dump), write_file, indent=4)
349
+
350
+ # how to load data form .json file
351
+ # f = open ('data.json', "r")
352
+ # data = json.loads(f.read())
353
+
354
+ return( opt_snr )
355
+
356
+ ####################################################
357
+ # #
358
+ # half_snr vs mtot table for interpolation #
359
+ # #
360
+ ####################################################
361
+ def snr_correction_func(self):
362
+ '''
363
+ '''
364
+ mtot_min = self.mtot_min
365
+ mtot_max = self.mtot_max
366
+ nsamples = self.nsamples
367
+ detectors = self.list_of_detectors
368
+ f_min = self.f_min
369
+
370
+ # geocent_time cannot be array here
371
+ # this geocent_time is only to get halfScaledSNR
372
+ geocent_time_ = 1246527224.169434 # random time from O3
373
+
374
+ iota_, ra_, dec_, psi_, phase_ = 0.,0.,0.,0.,0.
375
+ luminosity_distance_ = 100.
376
+
377
+ ratio = self.ratio
378
+ correction_ = np.zeros((len(ratio),len(detectors)),dtype=object)
379
+ i = 0
380
+ for q in tqdm(ratio, desc="interpolation for extra snr correction", total=len(ratio), ncols= 100):
381
+
382
+ mass_ratio = q
383
+ if self.waveform_inspiral_must_be_above_fmin==True:
384
+ func = lambda x: self.findchirp_chirptime(x/(1+mass_ratio),x/(1+mass_ratio)*mass_ratio, f_min)
385
+ mtot_max = fsolve(func, 150)[0] # to make sure that chirptime is not negative, TaylorF2 might need this
386
+
387
+ mtot_table = np.sort(np.random.uniform(mtot_min, mtot_max, nsamples-2)).tolist()
388
+ mtot_table = np.array([mtot_min]+mtot_table+[mtot_max])
389
+ mass_1_ = np.round(mtot_table/(1+q),5)
390
+ mass_1_[0] = mass_1_[0]+0.00001
391
+ mass_1_[-1] = mass_1_[-1]-0.00001
392
+ mass_2_ = np.round(mass_1_*q,5)
393
+ ######## calling bilby_snr ########
394
+ bilby_snr = self.compute_bilby_snr_(mass_1=mass_1_, mass_2=mass_2_, luminosity_distance=luminosity_distance_, \
395
+ theta_jn=iota_, psi=psi_, ra=ra_, dec=dec_,verbose=False, jsonFile=False)
396
+
397
+ interpolation_snr = self.snr_with_interpolation(mass_1=mass_1_, mass_2=mass_2_, luminosity_distance=luminosity_distance_, \
398
+ iota=iota_, psi=psi_, ra=ra_, dec=dec_, jsonFile=False)
399
+
400
+ ######## filling in interpolation table for different detectors ########
401
+ j = 0
402
+ for det in detectors:
403
+ correction_[i,j] = interp1d( mtot_table, abs(interpolation_snr[det]-bilby_snr[det]), kind = 'cubic')
404
+ j+=1
405
+ i+=1
406
+ self.snr_correction = correction_
407
+
408
+ return None
409
+
410
+
411
+ ####################################################
412
+ # #
413
+ # half_snr vs mtot table for interpolation #
414
+ # #
415
+ ####################################################
416
+ def __init_halfScaled(self):
417
+ '''
418
+ Function for finding (f/PSD) integration in the limit [f_min,f_max]
419
+ f_min is already initialized
420
+ f_max is taken as 'last stable orbit frequency' is a function of mtot
421
+ __init_halfScaled(self) will initialize the interpolator (scipy cubic spline) as self.halfSNR
422
+ -----------------
423
+ Input parameters
424
+ -----------------
425
+ None
426
+ -----------------
427
+ Return values
428
+ -----------------
429
+ snrHalf_det : cubic spline interpolator for halfScaledSNR --> (f/PSD) integration in the limit [f_min,f_max]
430
+ If there is 3 detectors, it will return 3 types of scipy cubic spline objects
431
+ '''
432
+ mtot_min = self.mtot_min
433
+ mtot_max = self.mtot_max
434
+ nsamples = self.nsamples
435
+ detectors = self.list_of_detectors
436
+
437
+ try:
438
+ if mtot_min<1.:
439
+ raise ValueError
440
+ except ValueError:
441
+ print('Error: mass too low')
442
+
443
+ C = 299792458.
444
+ G = 6.67408*1e-11
445
+ Mo = 1.989*1e30
446
+ f_min = self.f_min
447
+ '''
448
+ # mtot_max_propose from f_min
449
+ mtot_max_propose = (C**3)/( G*Mo*f_min*np.pi*6**(3/2) )
450
+
451
+ if mtot_max_propose<mtot_max:
452
+ warnings.warn\
453
+ (f'\n Mtot_max={mtot_max} given here is smaller than Mtot_max set by \
454
+ f_min={f_min}, \n new Mtot_max={mtot_max_propose}. \n If you want higher Mtot_max, set f_min lower \
455
+ (e.g. f_min=10Hz, but not lesser than 10Hz)')
456
+ mtot_max = mtot_max_propose
457
+ self.mtot_max = mtot_max
458
+
459
+ #mtot_table = np.sort(mtot_min+mtot_max-np.geomspace(mtot_min, mtot_max, nsamples))
460
+ #mtot_table = np.geomspace(mtot_min, mtot_max, nsamples)
461
+ mtot_table = np.linspace(mtot_min, mtot_max, nsamples)
462
+ '''
463
+
464
+ # geocent_time cannot be array here
465
+ # this geocent_time is only to get halfScaledSNR
466
+ geocent_time_ = 1246527224.169434 # random time from O3
467
+
468
+ iota_, ra_, dec_, psi_, phase_ = 0.,0.,0.,0.,0.
469
+ luminosity_distance_ = 100.
470
+
471
+ ratio = self.ratio
472
+ snrHalf_ = np.zeros((len(ratio),len(detectors)),dtype=object)
473
+ i = 0
474
+ for q in tqdm(ratio, desc="interpolation for each mass_ratios", total=len(ratio), ncols= 100):
475
+
476
+ mass_ratio = q
477
+ if self.waveform_inspiral_must_be_above_fmin==True:
478
+ func = lambda x: self.findchirp_chirptime(x/(1+mass_ratio),x/(1+mass_ratio)*mass_ratio, f_min)
479
+ mtot_max = fsolve(func, 150)[0] # to make sure that chirptime is not negative, TaylorF2 might need this
480
+
481
+ #mtot_table = np.linspace(mtot_min, mtot_max, nsamples)
482
+ mtot_table = np.sort(mtot_min+mtot_max-np.geomspace(mtot_min, mtot_max, nsamples))
483
+ #mtot_table = np.sort(mtot_min+mtot_max-np.geomspace(mtot_min, mtot_max, nsamples))
484
+ mass_1_ = mtot_table/(1+q)
485
+ mass_2_ = mass_1_*q
486
+ mchirp = ( (mass_1_*mass_2_)**(3/5) )/( (mtot_table)**(1/5) )
487
+ ######## calling bilby_snr ########
488
+ opt_snr_unscaled = self.compute_bilby_snr_(mass_1=mass_1_, mass_2=mass_2_, luminosity_distance=luminosity_distance_, \
489
+ theta_jn=iota_, psi=psi_, ra=ra_, dec=dec_,verbose=False, jsonFile=False)
490
+ '''
491
+ example of opt_snr_unscaled return values
492
+ {'opt_snr_net': array([156.53268655, 243.00092419, 292.10396943]),
493
+ 'L1': array([132.08275995, 205.04492349, 246.47822334]),
494
+ 'H1': array([ 84.00372897, 130.40716432, 156.75845871])}
495
+ '''
496
+
497
+ A2 = mchirp**(5./6.)
498
+ ######## filling in interpolation table for different detectors ########
499
+ j = 0
500
+ for det in detectors:
501
+ Fp, Fc = Detector(det).antenna_pattern(ra_, dec_, psi_, geocent_time_)
502
+ Deff2 = luminosity_distance_/np.sqrt(Fp**2*((1+np.cos(iota_)**2)/2)**2+Fc**2*np.cos(iota_)**2 )
503
+
504
+ snrHalf_[i,j] = interp1d( mtot_table, (Deff2/A2)*opt_snr_unscaled[det], kind = 'cubic')
505
+ j+=1
506
+ i+=1
507
+
508
+ # 2D array size: n_detectors X nsamples np.concatenate((a, b), axis=0)
509
+ # snrHalf_det['mtot'] = mtot_table
510
+ #print(snrHalf_det)
511
+ self.halfSNR = snrHalf_
512
+
513
+ # save halfSNR interpolation values
514
+
515
+
516
+ return(snrHalf_)
517
+
518
+ ####################################################
519
+ # #
520
+ # bilby snr #
521
+ # #
522
+ ####################################################
523
+ def compute_bilby_snr_(self, mass_1, mass_2, luminosity_distance=100., theta_jn=0., \
524
+ psi=0., phase=0., geocent_time=np.array([]), ra=0., dec=0., psds=False, psd_file=True, \
525
+ psd_with_time=False, verbose=True, jsonFile=False ):
526
+ '''
527
+ SNR calculated using bilby python package
528
+ Use for interpolation purpose
529
+ -----------------
530
+ Input parameters (GW parameters)
531
+ -----------------
532
+ mass_1 : Heavier compact object of the binary, unit: Mo (solar mass)
533
+ (flaot array or just float)
534
+ mass_2 : Lighter compact object of the binary, unit: Mo (solar mass)
535
+ (flaot array or just float)
536
+ luminosity_distance : Distance between detector and binary, unit: Mpc
537
+ theta_jn : Inclination angle of binary orbital plane wrt to the line of sight. unit: rad
538
+ psi : Polarization angle. unit: rad
539
+ phase : Phase of GW at the the time of coalesence, unit: rad
540
+ geocent_time : GPS time of colescence of that GW, unit: sec
541
+ ra : Right ascention of source position, unit: rad
542
+ dec : Declination of source position, unit: rad
543
+ psds : psd dict. if set False will get the values set at class initialization
544
+ example_1=> when values are psd name from pycbc analytical psds,
545
+ psds={'L1':'aLIGOaLIGODesignSensitivityT1800044','H1':'aLIGOaLIGODesignSensitivityT1800044'}
546
+ example_2=> when values are psd txt file in bilby or custom created,
547
+ psds={'L1':'aLIGO_O4_high_asd.txt','H1':'aLIGO_O4_high_asd.txt'}
548
+ custom created txt file has two columns. 1st column: frequency array, 2nd column: strain
549
+ psd_file : if set True, the given value of psds param should be of psds instead of asd. If asd, set psd_file=False.
550
+ psd_with_time : gps end time end strain data for which psd will be found. (this param will be given highest priority)
551
+ example=> psd_with_time=1246527224.169434
552
+
553
+ -----------------
554
+ Return values
555
+ -----------------
556
+ opt_snr : dictionary containing net optimal snr and optimal snr of individual detectors
557
+ example of opt_snr_unscaled return values for len(mass_1)=3
558
+ {'opt_snr_net': array([156.53268655, 243.00092419, 292.10396943]),
559
+ 'L1': array([132.08275995, 205.04492349, 246.47822334]),
560
+ 'H1': array([ 84.00372897, 130.40716432, 156.75845871])}
561
+ '''
562
+ npool = self.npool
563
+ geocent_time_ = 1246527224.169434 # random time from O3
564
+ sampling_frequency = self.sampling_frequency
565
+ if psds==False:
566
+ detectors = self.list_of_detectors
567
+ else:
568
+ # if psds are given
569
+ detectors = list(psds.keys())
570
+ approximant = self.waveform_approximant
571
+ f_min = self.f_min
572
+
573
+ ################
574
+ # psd handling #
575
+ ################
576
+ # if psds information is not manually given, we will use the one provided in bilby for O3 sensitivity
577
+ psds_arrays = dict()
578
+ psds_ = dict()
579
+ #######################################
580
+ # more realistic psds
581
+ # psd calculation from gps time point
582
+ # add exception handling for unrecognised time
583
+ if psd_with_time!=False:
584
+ print('wait for sometime while psd data is being fetch...')
585
+ # Use gwpy to fetch the open data
586
+ duration = 4.
587
+ roll_off = 0.2
588
+ psd_duration = duration * 32. # uint (seconds)
589
+ psd_start_time = psd_with_time - psd_duration
590
+ for ifo in detectors:
591
+ psd_data = TimeSeries.fetch_open_data(
592
+ ifo, psd_start_time, psd_start_time + psd_duration, sample_rate=sampling_frequency, cache=True)
593
+
594
+ psd_alpha = 2 * roll_off / duration
595
+ det_psd = psd_data.psd(fftlength=duration, overlap=0.5, window=("tukey", psd_alpha), method="median")
596
+
597
+ psds_arrays[ifo] = bilby.gw.detector.PowerSpectralDensity(frequency_array=det_psd.frequencies.value, \
598
+ psd_array=det_psd.value)
599
+
600
+ elif psds==False and self.psds_default==True:
601
+ psds = self.psds
602
+ for det in detectors:
603
+ try:
604
+ psds_[det] = psds[det]
605
+ except KeyError:
606
+ print('psd for {} detector not provided. The parameter psds dict should be contain, chosen detector names as keys \
607
+ and corresponding psds txt file name as their values'.format(det))
608
+
609
+ # psd or asd txt file has two columns. 1st column: frequency array, 2nd column: strain
610
+ for key in psds_:
611
+ psds_arrays[key] = bilby.gw.detector.PowerSpectralDensity(asd_file = psds_[key])
612
+
613
+
614
+ else:
615
+ if psds==False:
616
+ psds = self.psds
617
+ check_dtype = list(psds.values())[0]
618
+ ######################
619
+ # if psds dict is provided with txt file name corresponding to name of detectors as keys,
620
+ # psd or asd txt file has two columns. 1st column: frequency array, 2nd column: strain
621
+ if type(check_dtype)==str and check_dtype[-3:]=='txt':
622
+ for det in detectors:
623
+ try:
624
+ psds_[det] = psds[det]
625
+ except KeyError:
626
+ print('psd for {} detector not provided. The parameter psds dict should be contain, chosen detector names as keys \
627
+ and corresponding psds txt file name as their values'.format(det))
628
+
629
+ # pushing the chosen psds to bilby's PowerSpectralDensity object
630
+ psd_file = self.psd_file
631
+ if psd_file:
632
+ if verbose==True:
633
+ print('the noise curve provided is psd type and not asd. If not, please set the psd_file=False')
634
+ for key in psds_:
635
+ psds_arrays[key] = bilby.gw.detector.PowerSpectralDensity(psd_file = psds[key])
636
+ else:
637
+ if verbose==True:
638
+ print('the noise curve provided is asd type and not psd. If not, please set the psd_file=True')
639
+ for key in psds_:
640
+ psds_arrays[key] = bilby.gw.detector.PowerSpectralDensity(asd_file = psds[key])
641
+ ######################
642
+ # if psds dict is provided with txt file name corresponding to name of detectors as keys,
643
+ # we will use the one provided in bilby for O3 sensitivity
644
+ # this txt file should contain frequency and psd information
645
+ elif type(check_dtype)==str:
646
+ delta_f = 1.0 / 16.
647
+ flen = int(self.sampling_frequency / delta_f)
648
+ low_frequency_cutoff = self.f_min
649
+
650
+ for det in detectors:
651
+ try:
652
+ psds_[det] = pycbc.psd.from_string(psds[det], flen, delta_f, low_frequency_cutoff)
653
+ except KeyError:
654
+ print('psd for {} detector not provided or psd name provided is not recognised by pycbc'.format(det))
655
+
656
+ # pushing the chosen psds to bilby's PowerSpectralDensity object
657
+ if psd_file:
658
+ if verbose==True:
659
+ print('the noise curve provided is psd type and not asd. If not, please set the psd_file=False')
660
+ for key in psds_:
661
+ psds_arrays[key] = bilby.gw.detector.PowerSpectralDensity(frequency_array=psds_[det].sample_frequencies, \
662
+ psd_array=psds_[det].data)
663
+ else:
664
+ if verbose==True:
665
+ print('the noise curve provided is asd type and not psd. If not, please set the psd_file=True')
666
+ for key in psds_:
667
+ psds_arrays[key] = bilby.gw.detector.PowerSpectralDensity(frequency_array=psds_[det].sample_frequencies, \
668
+ asd_array=psds_[det].data)
669
+
670
+ ######################
671
+ else:
672
+ raise Exception("the psds format is not recognised. The parameter psds dict should contain chosen detector names as keys \
673
+ and corresponding psds txt file name (or name from pycbc psd)as their values'")
674
+
675
+ #######################################
676
+
677
+ # check whether there is input for geocent_time
678
+ if not np.array(geocent_time).tolist():
679
+ geocent_time = geocent_time_
680
+
681
+ # reshape(-1) is so that either a float value is given or the input is an numpy array
682
+ # np.ones is multipled to make sure everything is of same length
683
+ mass_1, mass_2 = np.array([mass_1]).reshape(-1), np.array([mass_2]).reshape(-1)
684
+ num = len(mass_1)
685
+ luminosity_distance, theta_jn, psi, phase, ra, dec, geocent_time = \
686
+ np.array([luminosity_distance]).reshape(-1)*np.ones(num), \
687
+ np.array([theta_jn]).reshape(-1)*np.ones(num), \
688
+ np.array([psi]).reshape(-1)*np.ones(num), \
689
+ np.array([phase]).reshape(-1)*np.ones(num), \
690
+ np.array([ra]).reshape(-1)*np.ones(num), \
691
+ np.array([dec]).reshape(-1)*np.ones(num), \
692
+ np.array([geocent_time]).reshape(-1)*np.ones(num)
693
+
694
+ iter_ = []
695
+ SNRs_list = []
696
+ SNRs_dict = {}
697
+ # time duration calculation for each of the mass combination
698
+ safety = 1.2
699
+ approx_duration = safety*self.findchirp_chirptime(mass_1, mass_2, f_min)
700
+ duration = np.ceil(approx_duration + 4.)
701
+
702
+ if self.waveform_inspiral_must_be_above_fmin==True:
703
+ # select only those that have inspiral part above f_min
704
+ idx = approx_duration>0.
705
+
706
+ # setting up parameters for feeding the inner product calculator (multiprocessing)
707
+ size1 = len(mass_1)
708
+ size2 = len(mass_1[idx]) # chossing only those that have inspiral part above f_min
709
+ iterations = np.arange(size1) # to keep track of index
710
+ iterations = iterations[idx] # to keep track of index
711
+
712
+ dectectorList = np.array(detectors)*np.ones((size2,len(detectors)),dtype=object)
713
+ psds_arrays_list = np.array([np.full(size2, psds_arrays, dtype=object)]).T
714
+
715
+ input_arguments = np.array([mass_1[idx], mass_2[idx], luminosity_distance[idx], theta_jn[idx], psi[idx], phase[idx], \
716
+ ra[idx], dec[idx], geocent_time[idx], \
717
+ np.full(size2, approximant), np.full(size2, f_min), \
718
+ duration[idx], np.full(size2, sampling_frequency), iterations], dtype=object).T
719
+ else:
720
+ # setting up parameters for feeding the inner product calculator (multiprocessing)
721
+ size1 = len(mass_1)
722
+ iterations = np.arange(size1) # to keep track of index
723
+
724
+ dectectorList = np.array(detectors)*np.ones((size1,len(detectors)),dtype=object)
725
+ psds_arrays_list = np.array([np.full(size1, psds_arrays, dtype=object)]).T
726
+
727
+ input_arguments = np.array([mass_1, mass_2, luminosity_distance, theta_jn, psi, phase, \
728
+ ra, dec, geocent_time, \
729
+ np.full(size1, approximant), np.full(size1, f_min), \
730
+ duration, np.full(size1, sampling_frequency), iterations], dtype=object).T
731
+
732
+ input_arguments = np.concatenate((input_arguments,psds_arrays_list,dectectorList),axis=1)
733
+
734
+ #######################################
735
+ # if inspiral only waveform
736
+ if self.waveform_type=='Inspiral':
737
+ with Pool(processes=npool) as pool:
738
+ # call the same function with different data in parallel
739
+ # imap->retain order in the list, while map->doesn't
740
+ for result in tqdm(pool.imap(self.snr_with_fmax_cutoff,input_arguments),total=len(input_arguments), \
741
+ ncols= 100, disable=not verbose):
742
+ iter_.append(result[1])
743
+ SNRs_list.append(result[0])
744
+ else:
745
+ with Pool(processes=npool) as pool:
746
+ # call the same function with different data in parallel
747
+ # imap->retain order in the list, while map->doesn't
748
+ for result in tqdm(pool.imap(self.noise_weighted_inner_prod,input_arguments),total=len(input_arguments), \
749
+ ncols= 100, disable=not verbose):
750
+ iter_.append(result[1])
751
+ SNRs_list.append(result[0])
752
+ #######################################
753
+
754
+ # to fill in the snr values at the right index
755
+ SNRs_list = np.array(SNRs_list)
756
+ i = 0
757
+ for det in detectors:
758
+ snrs_ = np.zeros(size1)
759
+ snrs_[iter_] = SNRs_list[:,i]
760
+ SNRs_dict[det] = snrs_
761
+ i = i+1
762
+
763
+ snrs_ = np.zeros(size1)
764
+ snrs_[iter_] = SNRs_list[:,i]
765
+ SNRs_dict['opt_snr_net'] = snrs_
766
+ self.stored_snrs = SNRs_dict # this stored snrs can be use for Pdet calculation
767
+
768
+ # saving as json file
769
+ if jsonFile:
770
+ parameters_dict = {'mass_1':mass_1, 'mass_2':mass_2, 'luminosity_distance':luminosity_distance, 'theta_jn':theta_jn, 'psi':psi, 'phase':phase, 'ra':ra, 'dec':dec, 'geocent_time':geocent_time,}
771
+ parameters_dict.update(SNRs_dict)
772
+ file_name = './bilby_GWparams_innerproductSNRs.json'
773
+ json_dump = json.dumps(parameters_dict, cls=NumpyEncoder)
774
+ with open(file_name, "w") as write_file:
775
+ json.dump(json.loads(json_dump), write_file, indent=4)
776
+
777
+ # how to load data form .json file
778
+ # f = open ('data.json', "r")
779
+ # data = json.loads(f.read())
780
+
781
+
782
+ return(SNRs_dict)
783
+
784
+ ####################################################
785
+ # #
786
+ # SNR with f_max cutoff (Multiprocessing) #
787
+ # (needed for inspiral only waveforms) #
788
+ # #
789
+ ####################################################
790
+ def snr_with_fmax_cutoff(self, params):
791
+ '''
792
+ Probaility of detection of GW for the given sensitivity of the detectors
793
+ -----------------
794
+ Input parameters
795
+ -----------------
796
+ params : np.array([mass_1[idx], mass_2[idx], luminosity_distance[idx], theta_jn[idx], psi[idx], phase[idx], \
797
+ ra[idx], dec[idx], GPStimeValue[idx], \
798
+ np.full(size, approximant), np.full(size, f_min), \
799
+ duration[idx], np.full(size, sampling_frequency), iterations], dtype=object).T
800
+ np.concatenate((input_arguments,psds_arrays_list,dectectorList),axis=1)
801
+
802
+ -----------------
803
+ Return values
804
+ -----------------
805
+ SNRs_list : contains opt_snr for each detector and net_opt_snr
806
+ (list of float)
807
+ params[13] : index tracker
808
+ '''
809
+ bilby.core.utils.logger.disabled = True
810
+ np.random.seed(88170235)
811
+ parameters = {'mass_1':params[0], 'mass_2':params[1], 'eccentricity':0.0, 'a_1':0., 'a_2':0., 'tilt_1':0., \
812
+ 'tilt_2':0., 'phi_12':0., 'phi_jl':0., 'luminosity_distance':params[2], 'theta_jn':params[3], \
813
+ 'psi':params[4], 'phase':params[5], 'geocent_time':params[8], 'ra':params[6], \
814
+ 'dec':params[7],}
815
+
816
+
817
+ f_min = params[10]
818
+ f_max = (C**3)/( G*(params[0]+params[1])*Mo*np.pi*6**(3/2) ) # last stable orbit frequency
819
+ waveform_arguments = dict(waveform_approximant = params[9], \
820
+ reference_frequency = 30., minimum_frequency = params[10])
821
+
822
+ waveform_generator = bilby.gw.WaveformGenerator(duration = params[11],
823
+ sampling_frequency = params[12],
824
+ frequency_domain_source_model = bilby.gw.source.lal_binary_black_hole,
825
+ waveform_arguments = waveform_arguments)
826
+ polas = waveform_generator.frequency_domain_strain(parameters = parameters)
827
+
828
+ # f_max for for cutoff
829
+ f_array = waveform_generator.frequency_array
830
+ idx = (f_array>=f_min)&(f_array<=f_max)
831
+ h_plus = polas['plus'][idx]
832
+ h_cross = polas['cross'][idx]
833
+
834
+ SNRs_list = []
835
+ NetSNR = 0.
836
+ list_of_detectors = params[15:].tolist()
837
+ psds_arrays = params[14]
838
+ for ifo in list_of_detectors:
839
+ # need to compute the inner product for
840
+ p_array = psds_arrays[ifo].get_power_spectral_density_array(f_array)[idx]
841
+ idx2 = (p_array!=0.) & (p_array!=np.inf)
842
+ hp_inner_hp = bilby.gw.utils.noise_weighted_inner_product(h_plus[idx2],
843
+ h_plus[idx2],
844
+ p_array[idx2],
845
+ waveform_generator.duration)
846
+ hc_inner_hc = bilby.gw.utils.noise_weighted_inner_product(h_cross[idx2],
847
+ h_cross[idx2],
848
+ p_array[idx2],
849
+ waveform_generator.duration)
850
+ # make an ifo object to get the antenna pattern
851
+ Fp, Fc = Detector(ifo).antenna_pattern(parameters['ra'],parameters['dec'],parameters['psi'],parameters['geocent_time'])
852
+
853
+ snrs_sq = abs((Fp**2)*hp_inner_hp + (Fc**2)*hc_inner_hc)
854
+
855
+ SNRs_list.append(np.sqrt(snrs_sq))
856
+ NetSNR += snrs_sq
857
+
858
+ SNRs_list.append(np.sqrt(NetSNR))
859
+
860
+ return(SNRs_list,params[13])
861
+
862
+
863
+ ####################################################
864
+ # #
865
+ # Noise weigthed inner product (Multiprocessing) #
866
+ # #
867
+ ####################################################
868
+ def noise_weighted_inner_prod(self, params):
869
+ '''
870
+ Probaility of detection of GW for the given sensitivity of the detectors
871
+ -----------------
872
+ Input parameters
873
+ -----------------
874
+ params : np.array([mass_1[idx], mass_2[idx], luminosity_distance[idx], theta_jn[idx], psi[idx], phase[idx], \
875
+ ra[idx], dec[idx], GPStimeValue[idx], \
876
+ np.full(size, approximant), np.full(size, f_min), \
877
+ duration[idx], np.full(size, sampling_frequency), iterations], dtype=object).T
878
+ np.concatenate((input_arguments,psds_arrays_list,dectectorList),axis=1)
879
+
880
+ -----------------
881
+ Return values
882
+ -----------------
883
+ SNRs_list : contains opt_snr for each detector and net_opt_snr
884
+ (list of float)
885
+ params[13] : index tracker
886
+ '''
887
+ bilby.core.utils.logger.disabled = True
888
+ np.random.seed(88170235)
889
+ parameters = {'mass_1':params[0], 'mass_2':params[1], 'eccentricity':0.0, 'a_1':0., 'a_2':0., 'tilt_1':0., \
890
+ 'tilt_2':0., 'phi_12':0., 'phi_jl':0., 'luminosity_distance':params[2], 'theta_jn':params[3], \
891
+ 'psi':params[4], 'phase':params[5], 'geocent_time':params[8], 'ra':params[6], \
892
+ 'dec':params[7],}
893
+
894
+
895
+ waveform_arguments = dict(waveform_approximant = params[9], \
896
+ reference_frequency = 30., minimum_frequency = params[10])
897
+
898
+ waveform_generator = bilby.gw.WaveformGenerator(duration = params[11],
899
+ sampling_frequency = params[12],
900
+ frequency_domain_source_model = bilby.gw.source.lal_binary_black_hole,
901
+ waveform_arguments = waveform_arguments)
902
+ polas = waveform_generator.frequency_domain_strain(parameters = parameters)
903
+
904
+ SNRs_list = []
905
+ NetSNR = 0.
906
+ list_of_detectors = params[15:].tolist()
907
+ psds_arrays = params[14]
908
+ for ifo in list_of_detectors:
909
+ # need to compute the inner product for
910
+ p_array = psds_arrays[ifo].get_power_spectral_density_array(waveform_generator.frequency_array)
911
+ idx2 = (p_array!=0.) & (p_array!=np.inf)
912
+ hp_inner_hp = bilby.gw.utils.noise_weighted_inner_product(polas['plus'][idx2],
913
+ polas['plus'][idx2],
914
+ p_array[idx2],
915
+ waveform_generator.duration)
916
+ hc_inner_hc = bilby.gw.utils.noise_weighted_inner_product(polas['cross'][idx2],
917
+ polas['cross'][idx2],
918
+ p_array[idx2],
919
+ waveform_generator.duration)
920
+ # make an ifo object to get the antenna pattern
921
+ Fp, Fc = Detector(ifo).antenna_pattern(parameters['ra'],parameters['dec'],parameters['psi'],parameters['geocent_time'])
922
+
923
+ snrs_sq = abs((Fp**2)*hp_inner_hp + (Fc**2)*hc_inner_hc)
924
+
925
+ SNRs_list.append(np.sqrt(snrs_sq))
926
+ NetSNR += snrs_sq
927
+
928
+ SNRs_list.append(np.sqrt(NetSNR))
929
+
930
+ return(SNRs_list,params[13])
931
+
932
+ ####################################################
933
+ # #
934
+ # Probaility of detection #
935
+ # #
936
+ ####################################################
937
+ def pdet(self, snrs=False, rho_th=8., rhoNet_th=8.):
938
+ '''
939
+ Probaility of detection of GW for the given sensitivity of the detectors
940
+ -----------------
941
+ Input parameters
942
+ -----------------
943
+ snrs : Signal-to-noise ratio for all the chosen detectors and GW parameters
944
+ (numpy array of float)
945
+
946
+ -----------------
947
+ Return values
948
+ -----------------
949
+ dict_pdet : dictionary of {'pdet_net':pdet_net, 'pdet_L1':pdet_L1, 'pdet_H1':pdet_H1, 'pdet_V1':pdet_V1}
950
+ '''
951
+ if snrs==False:
952
+ snrs = self.stored_snrs
953
+
954
+ detectors = self.list_of_detectors
955
+ pdet_dict = {}
956
+ for det in detectors:
957
+ pdet_dict['pdet_'+det] = 1 - norm.cdf(rho_th - snrs[det])
958
+
959
+ pdet_dict['pdet_net'] = 1 - norm.cdf(rhoNet_th - snrs['opt_snr_net'])
960
+
961
+ return( pdet_dict )
962
+
963
+ ####################################################
964
+ # #
965
+ # Chirp time #
966
+ # #
967
+ ####################################################
968
+ def findchirp_chirptime(self, m1, m2, fmin=20.):
969
+ '''
970
+ Time taken from f_min to f_lso (last stable orbit). 3.5PN in fourier phase considered.
971
+ -----------------
972
+ Input parameters
973
+ -----------------
974
+ m1 : component mass of BBH, m1>m2, unit(Mo)
975
+ m2 : component mass of BBH, m1>m2, unit(Mo)
976
+ fmin : minimum frequency cut-off for the analysis, unit(s)
977
+ -----------------
978
+ Return values
979
+ -----------------
980
+ chirp_time : Time taken from f_min to f_lso (frequency at last stable orbit), unit(s)
981
+ '''
982
+ # variables used to compute chirp time
983
+ m = m1 + m2
984
+ eta = m1 * m2 / m / m
985
+ c0T = c2T = c3T = c4T = c5T = c6T = c6LogT = c7T = 0.
986
+
987
+
988
+ c7T = Pi * (14809.0 * eta * eta / 378.0 - 75703.0 * eta / 756.0 - 15419335.0 / 127008.0)
989
+
990
+ c6T = Gamma * 6848.0 / 105.0 - 10052469856691.0 / 23471078400.0 +\
991
+ Pi * Pi * 128.0 / 3.0 + \
992
+ eta * (3147553127.0 / 3048192.0 - Pi * Pi * 451.0 / 12.0) -\
993
+ eta * eta * 15211.0 / 1728.0 + eta * eta * eta * 25565.0 / 1296.0 +\
994
+ eta * eta * eta * 25565.0 / 1296.0 + np.log(4.0) * 6848.0 / 105.0
995
+ c6LogT = 6848.0 / 105.0
996
+
997
+ c5T = 13.0 * Pi * eta / 3.0 - 7729.0 * Pi / 252.0
998
+
999
+ c4T = 3058673.0 / 508032.0 + eta * (5429.0 / 504.0 + eta * 617.0 / 72.0)
1000
+ c3T = -32.0 * Pi / 5.0
1001
+ c2T = 743.0 / 252.0 + eta * 11.0 / 3.0
1002
+ c0T = 5.0 * m * MTSUN_SI / (256.0 * eta)
1003
+
1004
+ # This is the PN parameter v evaluated at the lower freq. cutoff
1005
+ xT = pow (Pi * m * MTSUN_SI * fmin, 1.0 / 3.0)
1006
+ x2T = xT * xT
1007
+ x3T = xT * x2T
1008
+ x4T = x2T * x2T
1009
+ x5T = x2T * x3T
1010
+ x6T = x3T * x3T
1011
+ x7T = x3T * x4T
1012
+ x8T = x4T * x4T
1013
+
1014
+ # Computes the chirp time as tC = t(v_low)
1015
+ # tC = t(v_low) - t(v_upper) would be more
1016
+ # correct, but the difference is negligble.
1017
+ return c0T * (1 + c2T * x2T + c3T * x3T + c4T * x4T + c5T * x5T + (c6T + c6LogT * np.log(xT)) * x6T + c7T * x7T) / x8T
1018
+
1019
+ # Store as JSON a numpy.ndarray or any nested-list composition.
1020
+ class NumpyEncoder(json.JSONEncoder):
1021
+ def default(self, obj):
1022
+ if isinstance(obj, np.ndarray):
1023
+ return obj.tolist()
1024
+ return json.JSONEncoder.default(self, obj)
File without changes
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.1
2
+ Name: gwsnr
3
+ Version: 0.1.0
4
+ Summary: Fast SNR interpolator
5
+ Home-page: https://github.com/hemantaph/gwsnr
6
+ Author: Hemantakumar, Otto
7
+ Author-email: hemantaphurailatpam@gmail.com
8
+ License: MIT
@@ -0,0 +1,10 @@
1
+ README.md
2
+ setup.py
3
+ gwsnr/__init__.py
4
+ gwsnr/gwsnr.py
5
+ gwsnr/pdet.py
6
+ gwsnr.egg-info/PKG-INFO
7
+ gwsnr.egg-info/SOURCES.txt
8
+ gwsnr.egg-info/dependency_links.txt
9
+ gwsnr.egg-info/requires.txt
10
+ gwsnr.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ setuptools>=61.1.0
2
+ bilby>=1.0.2
3
+ pycbc>=2.0.4
4
+ scipy>=1.9.0
5
+ tqdm>=4.64.0
6
+ gwpy>=2.1.5
@@ -0,0 +1 @@
1
+ gwsnr
gwsnr-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
gwsnr-0.1.0/setup.py ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python
2
+ from setuptools import setup, find_packages
3
+ setup(name='gwsnr',
4
+ version='0.1.0',
5
+ description='Fast SNR interpolator',
6
+ author='Hemantakumar, Otto',
7
+ license="MIT",
8
+ author_email='hemantaphurailatpam@gmail.com',
9
+ url='https://github.com/hemantaph/gwsnr',
10
+ packages=find_packages(),
11
+ install_requires=[
12
+ "setuptools>=61.1.0",
13
+ "bilby>=1.0.2",
14
+ "pycbc>=2.0.4",
15
+ "scipy>=1.9.0",
16
+ "tqdm>=4.64.0",
17
+ "gwpy>=2.1.5",
18
+ ]
19
+ )