Calculation of detectable Gamma ray burst (GRB) event rate associated with Binary Neutron Star (BNS) mergers.
This is without considering a particular detectors’ sensitivity, but rather a general calculation of the rate of detectable GRBs based of viewing angle and luminosity distance. I have considered the inclination angle gravitational waves same as the viewing angle wrt GRB jet axis.
For BNS source redshift and mass distribution, refer here.
For lens and image properties, refer here.
Equations (GRB)
Ref: Granot, J. & Kumar, P. (2003). “Distribution of gamma-ray burst ejecta energy with Lorentz factor.” The Astrophysical Journal, 591(2), 1086-1096. DOI: 10.1086/375575
Disclamer: ChatGPT was used, partially, to get the following expression.
The observed energy of a gamma-ray burst (GRB) as a function of both the angular distance from the jet axis, \(\theta\), and the luminosity distance, \(D_L\), is given by:
Here, \(E_{\text{obs}}(\theta)\) is the observed energy, \(\epsilon_0\) is the energy per unit solid angle at the jet axis, \(D_L\) is the luminosity distance, \(\theta\) is the veiwing angle wrt the jet axis, \(\theta_c\) is the core angle of the jet, and \(k\) is the power-law index that determines how quickly the energy decreases with angle.
I cosidered the core angle of the jet: \(\theta_c = 5\) deg. (this might be too much)
So, probability of detection is given by:
Coefficients (a and c) are solved by considering conditions 2 and 3 below.,
If angle <= 5 deg, distance <= distance_cut, then \(P_{det}\)(bool) = 1.
Luminosity(core)/Luminosity(30deg) >= 100; from GW170817.
pdet <1, If angle < 30 deg (at distance=40) or distance < 40 Mpc (at angle<30). This is by considering the GRB from GW170817 is barely detectable, with viewing angle=30 deg and distance=40 Mpc.
I have also tried to consider the angular dependence with dipole radiation formula. But, the enenrgy doesn’t drop off quick enough in the off-axis viewing angle. So, I have considered the above equation.
[52]:
from ler.rates import LeR
import numpy as np
Probability of detection of GRBs
Finding the coefficients
[53]:
import numpy as np
from scipy.optimize import fsolve
# to find coefficient c
# consider: Luminosity(core)/Luminosity(30deg) >= 100; from GW170817
def equation(c):
return (1 / (1 + (5 / 5)**c))/(1 / (1 + (30 / 5)**c)) - 100
c_guess = 3
# Solve the equation
c_solution = fsolve(equation, c_guess)
print(f"c = {c_solution[0]}")
# to find coefficient a
# consider: pdet = 1, If angle = 30 deg (at distance=40). This is by considering the GRB from GW170817 is barely detectable, with viewing angle=30 deg and distance=40 Mpc.
def equation(a):
c = 2.9542496722537264
angle = 30
distance = 40
return (1 / (1 + (angle / 5)**c))* (40/distance)**2 * a/(4*np.pi) - 1
a_guess = 3
# Solve the equation
a_solution = fsolve(equation, a_guess)
print(f"a = {a_solution[0]}")
c = 2.9542496722537264
a = 2513.274122871834
Function to calculate the probability of detection
[54]:
# Find distance_cut
# convert redshift to luminoisty distance
import astropy.units as u
from astropy.cosmology import LambdaCDM
cosmo = LambdaCDM(H0=70, Om0=0.3, Ode0=0.7)
z = 5
d_L = cosmo.luminosity_distance(z)
print(f"d_L = {d_L}")
d_L = 46652.22297277721 Mpc
[59]:
# distance_cut: z ~ 5
def pdet_grb(angle, distance, distance_cut=46652, duty_cycle=0.5, mask_duty_cycle=True, bool=True):
"""
Function to calculate the probability of detection for a given angle and distance for GRB. Coefficients are based on 2 and 3 the following conditions,
0. GRB jet, core angle <= 5 deg
1. If angle <= 5 deg, distance <= distance_cut, then pdet(bool) = 1
2. Luminosity(core)/Luminosity(30deg) >= 100; from GW170817
3. pdet <1, If angle < 30 deg (at distance=40) or distance < 40 Mpc (at angle<30). This is by considering the GRB from GW170817 is barely detectable, with viewing angle=30 deg and distance=40 Mpc.
Parameters
----------
angle : numpy.ndarray
Angle between the GRB jet and viewing angle in (rad)
distance : numpy.ndarray
Distance between the GRB source and the satellite in (Mpc)
distance_cut : float
Core angular size of the GRB jet in (rad)
default is 25422.742 Mpc
duty_cycle : float
Duty cycle of detector(s)
default is 0.5
bool : bool
if True, return absolute value of pdet
if False, return the pdet value as boolean (with duty cycle applied)
"""
# coefficients, obtained with scipy.optimize fsolve by considering the condition 2,3 listed in docstring
c = 2.9542496722537264
a = 2513.274122871834
# convert angle to degree
angle = np.degrees(angle)
# make sure that the input data is a numpy array
angle, distance = np.array([angle]).reshape(-1), np.array([distance]).reshape(-1)
# angle should be less than 90 or equal to 90
if len(angle[angle > 90]) > 0:
angle[angle > 90] = angle[angle > 90]%90
if bool:
# calculate the probability of detection, absolute value
pdet = abs((1 / (1 + (angle / 5)**c))* (40/distance)**2 * a/(4*np.pi))
# find idx of angle <= 5 and distance <= distance_cut
idx = (angle <= 5) & (distance <= distance_cut)
# apply the condition, condition 1 from docstring
pdet[idx] = 1
if mask_duty_cycle:
# apply the duty cycle
# sample random numbers from 0 to 1 and check if it is less than the duty cycle
num_ = np.random.rand(len(angle))
mask_duty_cycle = num_ > duty_cycle
pdet[mask_duty_cycle] = 0
# return the pdet as boolean
return (pdet>=1).astype(int)
else:
# return the probability of detection (absolute value)
return abs((1 / (1 + (angle / 5)**c))* (40/distance)**2 * a/(4*np.pi))
test
[56]:
angle = np.array([1, 1, 30, 90])
angle = np.radians(angle)
distance = np.array([46652, 46653, 40, 40])
print(pdet_grb(angle, distance, mask_duty_cycle=False, bool=True))
[1 0 1 0]
Pdet condition checks
[57]:
print("condition 1, pdet(angle=core_angle,distance=distance_cut): ", pdet_grb(angle=np.radians(5), distance=46652, mask_duty_cycle=False, bool=True))
print("condition 2, absolute value pdet(angle=5,distance=40)/pdet(angle=30,distance=40): ", pdet_grb(angle=np.radians(5), distance=40, mask_duty_cycle=False, bool=False)/pdet_grb(angle=np.radians(30), distance=40, mask_duty_cycle=False, bool=False))
print("condition 3")
print(" i) Detectable, pdet(angle=30,distance=40): ", pdet_grb(angle=np.radians(30), distance=40, mask_duty_cycle=False, bool=True))
print(" ii) Not-Detectable, pdet(angle=31,distance=40): ", pdet_grb(angle=np.radians(31), distance=40, mask_duty_cycle=False, bool=True))
print(" iii) Not-Detectable, pdet(angle=30,distance=50): ", pdet_grb(angle=np.radians(30), distance=50, mask_duty_cycle=False, bool=True))
condition 1, pdet(angle=core_angle,distance=distance_cut): [1]
condition 2, absolute value pdet(angle=5,distance=40)/pdet(angle=30,distance=40): [100.]
condition 3
i) Detectable, pdet(angle=30,distance=40): [1]
ii) Not-Detectable, pdet(angle=31,distance=40): [0]
iii) Not-Detectable, pdet(angle=30,distance=50): [0]
let’s write out the function so that it can be used in with LeR
consider 50% duty cycle
[58]:
from gwsnr.utils import save_json_dict
# let's write out the function so that it can be used in with LeR
def pdet_calculator(gw_param_dict, duty_cycle=0.5, mask_duty_cycle=True, bool=True, output_jsonfile=False):
"""
Function to calculate the probability of detection for a given angle and distance for GRB. This is based on the following condition
1. GRB jet, core angle <= 5 deg
2. If angle <= 5 deg, distance <= distance_cut, then pdet = 1
3. Luminosity(core)/Luminosity(30deg) > 100, from GW170817
Parameters
----------
gw_param_dict : dict
dictionary containing the parameters for the GW event
"""
# get the angle and distance from the dictionary
angle = gw_param_dict['theta_jn']
distance = gw_param_dict['luminosity_distance']
# calculate the probability of detection
pdet = pdet_grb(angle, distance, duty_cycle=duty_cycle, mask_duty_cycle=mask_duty_cycle, bool=bool)
pdet_net_dict = dict(pdet_net=pdet)
# Save as JSON file, if output_jsonfile is provided
if output_jsonfile:
output_filename = (
output_jsonfile if isinstance(output_jsonfile, str) else "pdet.json"
)
save_json_dict(pdet_net_dict, output_filename)
# return the pdet
return pdet_net_dict
[38]:
# test
gw_param_dict = {'theta_jn': np.radians(np.array([1, 30, 90])), 'luminosity_distance': np.array([2500, 40, 40])}
print(pdet_calculator(gw_param_dict, mask_duty_cycle=False, bool=True))
{'pdet_net': array([1, 1, 0])}
Rate calculation of GRB with LeR
initialize LeR with pdet calculator
[39]:
ler = LeR(verbose=False, pdet_finder=pdet_calculator, event_type='BNS')
Unlensed events
[43]:
ler.batch_size = 100000
unlensed_param = ler.unlensed_cbc_statistics(size=500000, output_jsonfile='unlensed_param_grb.json', resume=False);
unlensed params will be store in ./ler_data/unlensed_param_grb.json
chosen batch size = 100000 with total size = 500000
There will be 5 batche(s)
Batch no. 1
sampling gw source params...
calculating pdet...
Batch no. 2
sampling gw source params...
calculating pdet...
Batch no. 3
sampling gw source params...
calculating pdet...
Batch no. 4
sampling gw source params...
calculating pdet...
Batch no. 5
sampling gw source params...
calculating pdet...
saving all unlensed_params in ./ler_data/unlensed_param_grb.json...
[44]:
ler.batch_size = 100000
lensed_param = ler.lensed_cbc_statistics(size=500000, output_jsonfile='lensed_param_grb.json', resume=False);
lensed params will be store in ./ler_data/lensed_param_grb.json
chosen batch size = 100000 with total size = 500000
There will be 5 batche(s)
Batch no. 1
sampling lensed params...
solving lens equations...
100%|█████████████████████████████████████████████████████| 100000/100000 [00:25<00:00, 3996.84it/s]
Invalid sample found. Resampling 4 lensed events...
solving lens equations...
100%|█████████████████████████████████████████████████████████████████| 4/4 [00:02<00:00, 1.38it/s]
calculating pdet...
Batch no. 2
sampling lensed params...
solving lens equations...
100%|█████████████████████████████████████████████████████| 100000/100000 [00:24<00:00, 4073.71it/s]
Invalid sample found. Resampling 3 lensed events...
solving lens equations...
100%|█████████████████████████████████████████████████████████████████| 3/3 [00:02<00:00, 1.09it/s]
calculating pdet...
Batch no. 3
sampling lensed params...
solving lens equations...
100%|█████████████████████████████████████████████████████| 100000/100000 [00:24<00:00, 4064.70it/s]
Invalid sample found. Resampling 2 lensed events...
solving lens equations...
100%|█████████████████████████████████████████████████████████████████| 2/2 [00:02<00:00, 1.35s/it]
calculating pdet...
Batch no. 4
sampling lensed params...
solving lens equations...
100%|█████████████████████████████████████████████████████| 100000/100000 [00:24<00:00, 4016.73it/s]
calculating pdet...
Batch no. 5
sampling lensed params...
solving lens equations...
100%|█████████████████████████████████████████████████████| 100000/100000 [00:25<00:00, 3942.88it/s]
Invalid sample found. Resampling 2 lensed events...
solving lens equations...
100%|█████████████████████████████████████████████████████████████████| 2/2 [00:02<00:00, 1.35s/it]
calculating pdet...
saving all lensed_params in ./ler_data/lensed_param_grb.json...
[45]:
rate_ratio, unlensed_param_detectable, lensed_param_detectable = ler.rate_comparision_with_rate_calculation(
unlensed_param='unlensed_param_grb.json',
lensed_param='lensed_param_grb.json',
)
getting unlensed_params from json file ./ler_data/unlensed_param_grb.json...
given detectability_condition == 'pdet'
total unlensed rate (yr^-1) (with step function): 2109.796632137801
number of simulated unlensed detectable events: 10190
number of all simulated unlensed events: 500000
storing detectable unlensed params in ./ler_data/unlensed_param_detectable.json
getting lensed_params from json file ./ler_data/lensed_param_grb.json...
given detectability_condition == 'pdet'
total lensed rate (yr^-1) (with pdet function): 2.117598781203888
number of simulated lensed detectable events: 4261
number of simulated all lensed events: 500000
storing detectable lensed params in ./ler_data/lensed_param_detectable.json
unlensed_rate (per year): 2109.796632137801
lensed_rate (per year): 2.117598781203888
ratio: 996.3155678330854
we have 2110 GRBs (detectable, un-lensed) per year
we have 2.11 GRBs (detectable, lensed) per year
ratio of lensed to un-lensed is 1:1000
Rate calculation of GW with LeR
using internal snr_calculator function, with gwsnr package.
Sensitivity: O4 design sensitivity, [L1,H1,V1]
for detection criteria of un-lensed BNS GW refer here, and for lensed BNS GW refer here.
[48]:
ler = LeR(verbose=False, event_type='BNS')
[49]:
ler.batch_size = 100000
unlensed_param = ler.unlensed_cbc_statistics(size=500000, output_jsonfile='unlensed_param_bns_gw.json', resume=False);
unlensed params will be store in ./ler_data/unlensed_param_bns_gw.json
chosen batch size = 100000 with total size = 500000
There will be 5 batche(s)
Batch no. 1
sampling gw source params...
calculating snrs...
Batch no. 2
sampling gw source params...
calculating snrs...
Batch no. 3
sampling gw source params...
calculating snrs...
Batch no. 4
sampling gw source params...
calculating snrs...
Batch no. 5
sampling gw source params...
calculating snrs...
saving all unlensed_params in ./ler_data/unlensed_param_bns_gw.json...
[50]:
ler.batch_size = 100000
lensed_param = ler.lensed_cbc_statistics(size=500000, output_jsonfile='lensed_param_bns_gw.json', resume=False);
lensed params will be store in ./ler_data/lensed_param_bns_gw.json
chosen batch size = 100000 with total size = 500000
There will be 5 batche(s)
Batch no. 1
sampling lensed params...
solving lens equations...
100%|█████████████████████████████████████████████████████| 100000/100000 [00:25<00:00, 3861.70it/s]
calculating snrs...
Batch no. 2
sampling lensed params...
solving lens equations...
100%|█████████████████████████████████████████████████████| 100000/100000 [00:24<00:00, 4054.86it/s]
Invalid sample found. Resampling 2 lensed events...
solving lens equations...
100%|█████████████████████████████████████████████████████████████████| 2/2 [00:02<00:00, 1.37s/it]
calculating snrs...
Batch no. 3
sampling lensed params...
solving lens equations...
100%|█████████████████████████████████████████████████████| 100000/100000 [00:24<00:00, 4052.37it/s]
Invalid sample found. Resampling 2 lensed events...
solving lens equations...
100%|█████████████████████████████████████████████████████████████████| 2/2 [00:02<00:00, 1.45s/it]
calculating snrs...
Batch no. 4
sampling lensed params...
solving lens equations...
100%|█████████████████████████████████████████████████████| 100000/100000 [00:24<00:00, 4021.48it/s]
Invalid sample found. Resampling 1 lensed events...
solving lens equations...
100%|█████████████████████████████████████████████████████████████████| 1/1 [00:03<00:00, 3.19s/it]
calculating snrs...
Batch no. 5
sampling lensed params...
solving lens equations...
100%|█████████████████████████████████████████████████████| 100000/100000 [00:24<00:00, 4062.46it/s]
Invalid sample found. Resampling 2 lensed events...
solving lens equations...
100%|█████████████████████████████████████████████████████████████████| 2/2 [00:03<00:00, 1.52s/it]
calculating snrs...
saving all lensed_params in ./ler_data/lensed_param_bns_gw.json...
[51]:
rate_ratio, unlensed_param_detectable, lensed_param_detectable = ler.rate_comparision_with_rate_calculation(
unlensed_param='unlensed_param_bns_gw.json',
lensed_param='lensed_param_bns_gw.json',
)
getting unlensed_params from json file ./ler_data/unlensed_param_bns_gw.json...
given detectability_condition == 'step_function'
total unlensed rate (yr^-1) (with step function): 2.898641104016606
number of simulated unlensed detectable events: 14
number of all simulated unlensed events: 500000
storing detectable unlensed params in ./ler_data/unlensed_param_detectable.json
getting lensed_params from json file ./ler_data/lensed_param_bns_gw.json...
given detectability_condition == 'step_function'
total lensed rate (yr^-1) (with step function): 0.008448528345568198
number of simulated lensed detectable events: 17
number of simulated all lensed events: 500000
storing detectable lensed params in ./ler_data/lensed_param_detectable.json
unlensed_rate (per year): 2.898641104016606
lensed_rate (per year): 0.008448528345568198
ratio: 343.094203564711
we have 2.9 GWs (detectable, un-lensed) per year
we have 0.0084 GWs (detectable, lensed) per year
ratio of lensed to un-lensed is 1:343
Final results
Table for GRB and GW rates
Event |
Unlensed |
Lensed |
ratio |
---|---|---|---|
GRB |
2110 |
2.11 |
1:1000 |
GW |
2.9 |
0.0084 |
1:343 |