Horizon Distance Calculation with Mass-Dependent SNR Thresholds

Find the threshold for BNS systems

  • choose ‘mass1_source’ \(\in [1,3]\)

  • we will use pre-selected injection catalogue data.

  • I will take threshold for individual detector same as that of detector network, for simplicity.

  • default sensitivity is consider with O4 psds.

[7]:
## original catalogue data: https://zenodo.org/records/16740117/files/samples-rpo4a_v2_20250503133839UTC-1366933504-23846400.hdf?download=1

## Uncomment the following lines to download the original catalogue data for testing purpose
# injection_data_bns.json is the reduced data extracted from the above hdf file for testing purpose
# it contains L1 and H1 injection data
! rm injection_data_bns.json # remove if exists
! wget https://raw.githubusercontent.com/hemantaph/gwsnr/refs/heads/main/tests/unit/injection_data_bns.json
--2026-07-08 23:23:52--  https://raw.githubusercontent.com/hemantaph/gwsnr/refs/heads/main/tests/unit/injection_data_bns.json
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.108.133, 185.199.111.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6444606 (6.1M) [text/plain]
Saving to: ‘injection_data_bns.json’

injection_data_bns. 100%[===================>]   6.15M  8.12MB/s    in 0.8s

2026-07-08 23:23:54 (8.12 MB/s) - ‘injection_data_bns.json’ saved [6444606/6444606]

[16]:
import h5py
import numpy as np
np.random.seed(42)
import matplotlib.pyplot as plt
from gwsnr.threshold import SNRThresholdFinder

from gwsnr.utils import get_param_from_json
params = get_param_from_json('injection_data_bns.json')

gstlal_far = params['gstlal_far']
observed_snr_net = params['observed_snr_net']
z = params['z']
mass1_source = params['mass1_source']

test = SNRThresholdFinder(
    catalog_file = None,
    # below are all default values. You can omit them if you want.
    npool=4,
    original_detection_statistic = dict(
                key_name='gstlal_far',
                threshold=1,  # 1 per year
                parameter=gstlal_far,
            ),
    projected_detection_statistic = dict(
                key_name='observed_snr_net',
                threshold=None, # to be determined
                threshold_search_bounds=(8, 14),
                parameter=observed_snr_net,
            ),
    parameters_to_fit = dict(
        key_name = 'z',
        parameter=z,
    ),
    sample_size=20000,
    selection_range = dict(
        key_name = 'mass1_source',
        range = (1, 3), # in solar masses
        parameter = mass1_source,
    ),
)

best_thr, _, _, _, _ = test.find_threshold(iteration=10, print_output=True, no_multiprocessing=True)
100%|███████████████████████████████████████████████████████████████| 10/10 [00:14<00:00,  1.43s/it]
Best SNR threshold: 11.62

[17]:
snr_th, snr_th_net = best_thr, best_thr
print(f"Best threshold for SNR: {snr_th:.2f}, Best threshold for SNR_net: {snr_th_net:.2f}")
Best threshold for SNR: 11.62, Best threshold for SNR_net: 11.62
[18]:
from gwsnr import GWSNR

mtot_min = (1+1)*(1+0) # (mass_1_min + mass_2_min)*(1 + z_min)
mtot_max = (2.3+2.3)*(1+5) # (mass_1_max + mass_2_max)*(1 + z_max)

gwsnr = GWSNR(
    npool=6,
    ifos=['H1', 'L1'],
    snr_method="interpolation_no_spins",
    mtot_min=mtot_min,
    mtot_max=mtot_max,
)

Initializing GWSNR class...

Interpolator will be loaded for H1 detector from ./interpolator_json/H1/partialSNR_dict_3.json
Interpolator will be loaded for L1 detector from ./interpolator_json/L1/partialSNR_dict_4.json

Chosen GWSNR initialization parameters:

npool:  6
snr type:  interpolation_no_spins
waveform approximant:  IMRPhenomD
sampling frequency:  2048.0
minimum frequency (fmin):  20.0
reference frequency (f_ref):  20.0
mtot=mass1+mass2
min(mtot):  2
max(mtot) (with the given fmin=20.0): 27.599999999999998
detectors:  ['H1', 'L1']
psds:  [[array([  10.21659,   10.23975,   10.26296, ..., 4972.81   ,
       4984.081  , 4995.378  ]), array([4.43925574e-41, 4.22777986e-41, 4.02102594e-41, ...,
       6.51153524e-46, 6.43165104e-46, 6.55252996e-46]), <scipy.interpolate._interpolate.interp1d object at 0x30cf05bc0>], [array([  10.21659,   10.23975,   10.26296, ..., 4972.81   ,
       4984.081  , 4995.378  ]), array([4.43925574e-41, 4.22777986e-41, 4.02102594e-41, ...,
       6.51153524e-46, 6.43165104e-46, 6.55252996e-46]), <scipy.interpolate._interpolate.interp1d object at 0x30fd9fb50>]]


Horizon Distance - Analytic

[19]:
hd_dict_ =  gwsnr.horizon_distance_analytical(mass_1=1.4, mass_2=1.4, snr_th=snr_th)
hd_dict_ # in Mpc
[19]:
{'horizon_distance_H1': array([286.83332778]),
 'horizon_distance_L1': array([286.83332778])}

Horizon Distance - Numerical

[20]:
hd_dict_, optimal_orientation =  gwsnr.horizon_distance_numerical(mass_1=1.4, mass_2=1.4, snr_th=snr_th)

hd_dict_ # in Mpc
[20]:
{'horizon_distance_H1': 286.8333285966073,
 'horizon_distance_L1': 286.8333285966073,
 'horizon_distance_net': 457.9805338138831}
[21]:
# optimal orientation
optimal_orientation
[21]:
{'optimal_orientation_H1': {'ra': 2.251317689687112,
  'dec': -0.8112775059498398,
  'theta_jn': 3.141592653589793},
 'optimal_orientation_L1': {'ra': 5.893058202133175,
  'dec': 0.5327335489575311,
  'theta_jn': 0.0},
 'optimal_orientation_net': {'ra': 2.5299508320516666,
  'dec': -0.6873371132879433,
  'theta_jn': 3.435784008143372e-05}}
[ ]:

[ ]: