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 +8 -0
- gwsnr-0.1.0/README.md +18 -0
- gwsnr-0.1.0/gwsnr/__init__.py +2 -0
- gwsnr-0.1.0/gwsnr/gwsnr.py +1024 -0
- gwsnr-0.1.0/gwsnr/pdet.py +0 -0
- gwsnr-0.1.0/gwsnr.egg-info/PKG-INFO +8 -0
- gwsnr-0.1.0/gwsnr.egg-info/SOURCES.txt +10 -0
- gwsnr-0.1.0/gwsnr.egg-info/dependency_links.txt +1 -0
- gwsnr-0.1.0/gwsnr.egg-info/requires.txt +6 -0
- gwsnr-0.1.0/gwsnr.egg-info/top_level.txt +1 -0
- gwsnr-0.1.0/setup.cfg +4 -0
- gwsnr-0.1.0/setup.py +19 -0
gwsnr-0.1.0/PKG-INFO
ADDED
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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gwsnr
|
gwsnr-0.1.0/setup.cfg
ADDED
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
|
+
)
|