Searching for coincidences#
The RAVEN framework is a data-analysis pipeline used to identify GW events that coincide with external observatories. RAVEN ingests candidate gravitational-wave triggers, extracts physical parameters (time, sky location, significance), and cross-references them with event notices from partners in the electromagnetic domain. By comparing the timing and localization of these external alerts against gravitational-wave candidates, RAVEN assigns coincidence scores, flags statistically significant matches, and delivers rapid follow-up summaries. This enables coordinated multi-messenger observations and accelerates the discovery of events like binary mergers or supernovae.
Untargeted search#
In this search RAVEN checks if there is any spatio-temporal coincidence between a confident external messenger (GBR, MeV or TeV, neutrino, FRB, X-ray transient) and any GW candidate with a given \(FAR<FAR_{max}\). The quantity \(FAR_{max}\) identifies the depth of the search. The joint FAR in this regime is computed as:
We need to know what is the typical rate of discovery of the external class of transients. The overlap integral is defined as:
Targeted search#
In this case the external observatory performs a search triggered by the GW candidate, down to a depth defined again by \(\rm{FAR}_{GW}^{max}\)
where the test statistic Z is defined as:
Note
In the case of a confident GW detection, the valid equation is the one of the untargeted search, swapping the GW and ext indexes.
Searching a GW event on GraceDB#
from ligo.raven import gracedb_events, search
from gracedb_sdk import Client
gracedb = Client('https://gracedb.ligo.org/api/')
from astropy.time import Time
utc_time = '2025-10-17 12:41:04'
gps_from_utc = Time(utc_time, format='iso', scale='utc').gps
gpstime = gps_from_utc
tl, th = -1e4, 1e4
group = 'CBC' # 'CBC', 'Burst', or 'Test'
pipelines = [] # 'Fermi', 'Swift', 'INTEGRAL', 'AGILE', 'SNEWS', or 'IceCube'
ext_searches = [] # 'GRB', 'SubGRB', 'SubGRBTargeted', 'HEN', or 'MDC'
se_searches = [] # 'AllSky', 'BBH', 'IMBH', or 'MDC'
results = search.query('Superevent', gpstime, tl, th, gracedb=gracedb,
group=group, pipelines=pipelines,
ext_searches=ext_searches, se_searches=se_searches)
for n in results:
print(n['superevent_id'], n['far'])
The search on External events can be done only using LVK credentials
gpstime = gps_from_utc
tl, th = -1e5, 1e5
group = 'CBC' # 'CBC', 'Burst', or 'Test'
pipelines = ['SVOM'] # 'Fermi', 'Swift', 'SVOM', 'SNEWS', or 'IceCube'
ext_searches = ['GRB'] # 'GRB', 'SubGRB', 'SubGRBTargeted', 'HEN', or 'MDC'
se_searches = [] # 'AllSky', 'BBH', 'IMBH', or 'MDC'
results = search.query('External', gpstime, tl, th, gracedb=gracedb,
pipelines=pipelines,
ext_searches=ext_searches, se_searches=se_searches)
for n in results:
print(n['graceid'], n['far'])
Joint FAR calculation#
We test here the calculation of joint FAR according to which search we are considering
Temporal FAR#
Imagine to have an EXT event and you only know its trigger time and FAR
se_far = 1e-6 # Hz
# this corresponds to
print("1 event every", format(1 / se_far / 3600 / 24, ".2f"), "days")
# indeed T = 1 / se_far = 1 / (1e-6) seconds = 1e6 seconds ~ 11.57 days
tl, th = -1, 10
Note
The default temporal windows used by RAVEN are these:
Event Type |
Time window (s) |
Notice Type Considered (see full list) |
|
|---|---|---|---|
CBC |
Burst |
||
GRB (Fermi, Swift, INTEGRAL, AGILE) |
[-1,5] |
[-60,600] |
FERMI_GBM_ALERT |
SubGRB (Fermi) |
[-1,11] |
[-1,11] |
FERMI_GBM_SUBTHRESH |
SubGRBTargeted (Fermi) |
[-1,11] |
[-1,11] |
via Kafka alert |
SubGRBTargeted (Swift) |
[-10,20] |
[-10,20] |
via Kafka alert |
Low-energy Neutrinos (SNEWS) |
[-10,10] |
[-10,10] |
SNEWS |
The generic command to compute joint FAR is
far_joint = search.coinc_far(se_far, tl, th,
joint_far_method = 'untargeted', # 'targeted' or 'untargeted'
ext_rate = 1 / 24 / 3600, # once per day, it is used only if joint_far_method is 'untargeted'
far_ext = 1e-5, # it is used only if joint_far_method is 'untargeted'
far_gw_thresh = 1e-4, far_ext_thresh = 1e-4, # by default far_gw_thresh is 2/day and far_ext_thresh depends on the ext instrument, only used if joint_far_method is 'targeted'
ext_search = None, # "GRB", "SubGRB", "SubGRBTargeted", "MDC", or "HEN" --> if you don't specify joint_far_method, you must specify ext_search
# {'GRB', 'SubGRB', 'HEN'} --> uses untargeted
# {'SubGRBTargeted'} --> uses targeted
ext_pipeline=None # --> if you don't specify far_ext_thresh, you must specify ext_pipeline. Now it supports 'Fermi', 'Swift'
)
Note
The following rates are used in the untargeted search:
the combined rate of independent GRB discovery by Swift, Fermi, and SVOM (avoiding double counts)
Fermi: 236/yr
Swift: 65/yr
SVOM ECLAIRs: ~25/yr
grb_gcn_rate = 325. / (365. * 24. * 60. * 60.)
Rate of subthreshold GRBs (rate of above threshold plus rate of subthreshold)
subgrb_gcn_rate = 65. / (365. * 24. * 60. * 60.)
Combined rate of all GOLD IceCube notice from Table 1 of https://arxiv.org/pdf/2304.01174
hen_gcn_rate = 13.91 / (365. * 24. * 60. * 60.)
Joint FAR from untargeted search#
far_joint = search.coinc_far(se_far, tl, th,
joint_far_method = 'untargeted', # 'targeted' or 'untargeted'
ext_rate = 1 / 24 / 3600, # once per day, it is used only if joint_far_method is 'untargeted'
ext_search = None, # "GRB", "SubGRB", "SubGRBTargeted", "MDC", or "HEN" --> if you don't specify joint_far_method, you must specify ext_search
# {'GRB', 'SubGRB', 'HEN'} --> uses untargeted
# {'SubGRBTargeted'} --> uses targeted
ext_pipeline=None # --> if you don't specify far_ext_thresh, you must specify ext_pipeline
)
(th - tl) * 1 / 24 / 3600 * se_far
print(f"This coincidence happens every {format(1 / far_joint['temporal_coinc_far'] / 365 /3600 / 24, '.2f')} yrs")
Joint FAR from targeted search (confident EXT detection & any GW candidate)#
far_joint = search.coinc_far(se_far, tl, th,
joint_far_method = 'targeted', # 'targeted' or 'untargeted'
far_ext = 1e-5, # it is used only if joint_far_method is 'untargeted'
far_gw_thresh = 1e-4, far_ext_thresh = 1e-4, # by default far_gw_thresh is 2/day and far_ext_thresh depends on the ext instrument, only used if joint_far_method is 'targeted'
ext_search = 'SubGRBTargeted', # "GRB", "SubGRB", "SubGRBTargeted", "MDC", or "HEN" --> if you don't specify joint_far_method, you must specify ext_search
# {'GRB', 'SubGRB', 'HEN'} --> uses untargeted
# {'SubGRBTargeted'} --> uses targeted
ext_pipeline=None # --> if you don't specify far_ext_thresh, you must specify ext_pipeline
)
Joint FAR from targeted search, but we are in the special case of a confident GW detection & any EXT candidate#
gw_rate = 1 / 3 / 24 / 3600 # one GW event every 3 days
# But if there is strong evidence from GW data that it is either a BNS or NSBH merger,
# the rate can should be much lower, since we detect just a handful of these SO FAR
ext_far = 1e-5 # Hz
far_joint = search.coinc_far(ext_far, tl, th,
joint_far_method = 'untargeted', # 'targeted' or 'untargeted'
ext_rate = gw_rate, # once per day, it is used only if joint_far_method is 'untargeted'
)
(th - tl) * 1 / 24 / 3600 * se_far
print(f"This coincidence happens every {format(1 / far_joint['temporal_coinc_far'] / 365 /3600 / 24, '.2f')} yrs")
Joint FAR from untargeted search, but we are in the special case of both GW and EXT are higly significant#
grb_rate = 325. / (365. * 24. * 60. * 60.) # Hz
gw_rate = 1 / 3 / 24 / 3600 # one GW event every 3 days
far_joint = gw_rate * grb_rate * (th - tl)
print(f"This coincidence happens every {format(1 / far_joint / 365 /3600 / 24, '.2f')} yrs")
Note
Remember that after computing joint FAR, RAVEN applies a trials factor \(N(N+1)\), where \(N\) is the number of independent searches. Currently \(N = 6\) for CBC and \(N = 2\) for Burst. Since search pipelines are not really independent, this trials factor is a conservative choice.
Once the trials factor is applied, RAVEN sends a public alert if FAR < 1 / month for CBC and FAR < 1 / yr for Burst.
How to give empirically “weight” to each \(\mathcal{H}^{XY}\) hypothesis#
To decide which FAR one should consider for a given couple of GW + EXT event, a possible proposal is to give a relative weight to each hypothesis, according to the properties of the events.
Note
This is a rude approximation because we know that we could detect high significant GRB with Swift-BAT targeted search, for instance if we were slewing or the GRB is outside the FOV and not visible to other telescopes
The weights could look like:
Warning
Remember that FAR cannot be summed to have a final joint FAR. But you can still decide which \(\mathcal{H}^{XY}\) is more appropriate according to observational evidences and decide accordingly how to compute your FAR