LALInspiral 5.0.3.1-eeff03c
inspinjfind.py
Go to the documentation of this file.
1# Copyright (C) 2006--2011,2013,2014,2016,2017 Kipp Cannon
2#
3# This program is free software; you can redistribute it and/or modify it
4# under the terms of the GNU General Public License as published by the
5# Free Software Foundation; either version 2 of the License, or (at your
6# option) any later version.
7#
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11# Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17
18#
19# =============================================================================
20#
21# Preamble
22#
23# =============================================================================
24#
25
26
27"""
28Inspiral injection identification library. Contains code providing the
29capacity to search a list of sngl_inspiral candidates for events
30matching entries in a sim_inspiral list of software injections, recording the
31matches as inspiral <--> injection coincidences using the standard coincidence
32infrastructure. Also, any pre-recorded inspiral <--> inspiral coincidences are
33checked for cases where all the inspiral events in a coincidence match an
34injection, and these are recorded as coinc <--> injection coincidences,
35again using the standard coincidence infrastructure.
36"""
37
38
39import bisect
40import functools
41import sys
42import tqdm
43
44
45from lal import iterutils
46from igwn_ligolw import ligolw
47from igwn_ligolw import lsctables
48from igwn_ligolw.utils import coincs as ligolw_coincs
49from igwn_ligolw.utils import time_slide as ligolw_time_slide
50from . import thinca
51
52
53__author__ = "Kipp Cannon <kipp.cannon@ligo.org>"
54from .git_version import date as __date__
55from .git_version import version as __version__
56
57
58#
59# =============================================================================
60#
61# Content Handler
62#
63# =============================================================================
64#
65
66
67@functools.total_ordering
68class SnglInspiral(lsctables.SnglInspiral):
69 """
70 Version of lsctables.SnglInspiral who's .__cmp__() method compares
71 this object's .end value directly to the value of other. Allows a
72 list of instances of this class sorted by .end to be bisection
73 searched for a LIGOTimeGPS end time.
74 """
75 __slots__ = ()
76
77 def __lt__(self, other):
78 return self.end < other
79
80 def __eq__(self, other):
81 return self.end == other
82
83
84#
85# =============================================================================
86#
87# Document Interface
88#
89# =============================================================================
90#
91
92
93InspiralSICoincDef = lsctables.CoincDef(search = u"inspiral", search_coinc_type = 1, description = u"sim_inspiral<-->sngl_inspiral coincidences")
94InspiralSCNearCoincDef = lsctables.CoincDef(search = u"inspiral", search_coinc_type = 2, description = u"sim_inspiral<-->coinc_event coincidences (nearby)")
95InspiralSCExactCoincDef = lsctables.CoincDef(search = u"inspiral", search_coinc_type = 3, description = u"sim_inspiral<-->coinc_event coincidences (exact)")
96InspiralSTCoincDef = lsctables.CoincDef(search = u"inspiral", search_coinc_type = 4, description = u"sim_inspiral<-->sngl_inspiral template coincidences")
97
98
99class DocContents(object):
100 """
101 A wrapper interface to the XML document.
102 """
103 def __init__(self, xmldoc, bbdef, sbdef, scedef, scndef, process, end_time_bisect_window):
104 #
105 # store the process row
106 #
107
108 self.process = process
109
110 #
111 # locate the sngl_inspiral and sim_inspiral tables
112 #
113
114 self.snglinspiraltable = lsctables.SnglInspiralTable.get_table(xmldoc)
115 self.siminspiraltable = lsctables.SimInspiralTable.get_table(xmldoc)
116
117 #
118 # get the offset vectors from the document
119 #
120
121 self.offsetvectors = lsctables.TimeSlideTable.get_table(xmldoc).as_dict()
122
123 #
124 # construct the zero-lag time slide needed to cover the
125 # instruments listed in all the triggers, then determine
126 # its ID (or create it if needed)
127 #
128 # FIXME: in the future, the sim_inspiral table should
129 # indicate time slide at which the injection was done
130 #
131
132 self.tisi_id = ligolw_time_slide.get_time_slide_id(xmldoc, {}.fromkeys(self.snglinspiraltable.getColumnByName("ifo"), 0.0), create_new = process, superset_ok = True, nonunique_ok = True)
133
134 #
135 # get coinc_definer row for sim_inspiral <--> sngl_inspiral
136 # coincs; this creates a coinc_definer table if the
137 # document doesn't have one
138 #
139
140 self.sb_coinc_def_id = ligolw_coincs.get_coinc_def_id(xmldoc, sbdef.search, sbdef.search_coinc_type, create_new = True, description = sbdef.description)
141
142 #
143 # get coinc_def_id's for sngl_inspiral <--> sngl_inspiral, and
144 # both kinds of sim_inspiral <--> coinc_event coincs. set all
145 # to None if this document does not contain any sngl_inspiral
146 # <--> sngl_inspiral coincs.
147 #
148
149 try:
150 ii_coinc_def_id = ligolw_coincs.get_coinc_def_id(xmldoc, bbdef.search, bbdef.search_coinc_type, create_new = False)
151 except KeyError:
152 ii_coinc_def_id = None
155 else:
156 self.sce_coinc_def_id = ligolw_coincs.get_coinc_def_id(xmldoc, scedef.search, scedef.search_coinc_type, create_new = True, description = scedef.description)
157 self.scn_coinc_def_id = ligolw_coincs.get_coinc_def_id(xmldoc, scndef.search, scndef.search_coinc_type, create_new = True, description = scndef.description)
158
159 #
160 # get coinc table, create one if needed
161 #
162
163 try:
164 self.coinctable = lsctables.CoincTable.get_table(xmldoc)
165 except ValueError:
166 self.coinctable = lsctables.CoincTable.new()
167 xmldoc.childNodes[0].appendChild(self.coinctable)
168 self.coinctable.sync_next_id()
169
170 #
171 # get coinc_map table, create one if needed
172 #
173
174 try:
175 self.coincmaptable = lsctables.CoincMapTable.get_table(xmldoc)
176 except ValueError:
177 self.coincmaptable = lsctables.CoincMapTable.new()
178 xmldoc.childNodes[0].appendChild(self.coincmaptable)
179
180 #
181 # index the document
182 #
183 # FIXME: inspiral<-->inspiral coincs should be organized by time
184 # slide ID, but since injections are only done at zero lag
185 # for now this is ignored.
186 #
187
188 # index the sngl_inspiral table
189 index = dict((row.event_id, row) for row in self.snglinspiraltable)
190 # find IDs of inspiral<-->inspiral coincs
191 self.sngls = dict((row.coinc_event_id, []) for row in self.coinctable if row.coinc_def_id == ii_coinc_def_id)
192 # construct event list for each inspiral<-->inspiral coinc
193 for row in self.coincmaptable:
194 try:
195 self.sngls[row.coinc_event_id].append(index[row.event_id])
196 except KeyError:
197 pass
198 del index
199 # construct a sngl-->coincs look-up table
200 self.coincs = dict((event.event_id, set()) for events in self.sngls.values() for event in events)
201 for row in self.coincmaptable:
202 if row.event_id in self.coincs and row.coinc_event_id in self.sngls:
203 self.coincs[row.event_id].add(row.coinc_event_id)
204 # create a coinc_event_id to offset vector look-up table
205 self.coincoffsets = dict((row.coinc_event_id, self.offsetvectors[row.time_slide_id]) for row in self.coinctable if row.coinc_def_id == ii_coinc_def_id)
206
207 #
208 # sort sngl_inspiral table by end time
209 #
210
211 self.snglinspiraltable.sort(key = lambda row: row.end)
212
213 #
214 # set the window for inspirals_near_endtime(). this window
215 # is the amount of time such that if an injection's end
216 # time and a inspiral event's end time differ by more than
217 # this it is *impossible* for them to match one another.
218 #
219
220 self.end_time_bisect_window = lsctables.LIGOTimeGPS(end_time_bisect_window)
221
222
223 def inspirals_near_endtime(self, t):
224 """
225 Return a list of the inspiral events whose end times are
226 within self.end_time_bisect_window of t.
227 """
228 return self.snglinspiraltable[bisect.bisect_left(self.snglinspiraltable, t - self.end_time_bisect_window):bisect.bisect_right(self.snglinspiraltable, t + self.end_time_bisect_window)]
229
230 def coincs_near_endtime(self, t):
231 """
232 Return a list of the (coinc_event_id, event list) tuples in
233 which at least one inspiral event's end time is within
234 self.end_time_bisect_window of t.
235 """
236 # FIXME: this test does not consider the time slide
237 # offsets that should be applied to the coinc, but for now
238 # injections are done at zero lag so this isn't a problem
239 # yet
240 coinc_event_ids = set()
241 for event in self.inspirals_near_endtime(t):
242 try:
243 coinc_event_ids |= self.coincs[event.event_id]
244 except KeyError:
245 # this single isn't in any coincs
246 pass
247 return [(coinc_event_id, self.sngls[coinc_event_id]) for coinc_event_id in coinc_event_ids]
248
249 def sort_triggers_by_id(self):
250 """
251 Sort the sngl_inspiral table's rows by ID (tidy-up document
252 for output).
253 """
254 self.snglinspiraltable.sort(key = lambda row: row.event_id)
255
256 def new_coinc(self, coinc_def_id):
257 """
258 Construct a new coinc_event row attached to the given
259 process, and belonging to the set of coincidences defined
260 by the given coinc_def_id.
261 """
262 coinc = lsctables.Coinc()
263 coinc.process_id = self.process.process_id
264 coinc.coinc_def_id = coinc_def_id
265 coinc.coinc_event_id = self.coinctable.get_next_id()
266 coinc.time_slide_id = self.tisi_id
267 coinc.insts = None
268 coinc.nevents = 0
269 coinc.likelihood = None
270 self.coinctable.append(coinc)
271 return coinc
272
273
274#
275# =============================================================================
276#
277# Build sim_inspiral <--> sngl_inspiral Coincidences
278#
279# =============================================================================
280#
281
282
283def find_sngl_inspiral_matches(contents, sim, comparefunc):
284 """
285 Scan the inspiral table for triggers matching sim.
286 """
287 return [inspiral for inspiral in contents.inspirals_near_endtime(sim.time_geocent) if not comparefunc(sim, inspiral)]
288
289
290def add_sim_inspiral_coinc(contents, sim, inspirals):
291 """
292 Create a coinc_event in the coinc table, and add arcs in the
293 coinc_event_map table linking the sim_inspiral row and the list of
294 sngl_inspiral rows to the new coinc_event row.
295 """
296 coinc = contents.new_coinc(contents.sb_coinc_def_id)
297 coinc.insts = (event.ifo for event in inspirals)
298 coinc.nevents = len(inspirals)
299
300 contents.coincmaptable.append(lsctables.CoincMap(
301 coinc_event_id = coinc.coinc_event_id,
302 table_name = u"sim_inspiral",
303 event_id = sim.simulation_id
304 ))
305
306 for event in inspirals:
307 contents.coincmaptable.append(lsctables.CoincMap(
308 coinc_event_id = coinc.coinc_event_id,
309 table_name = u"sngl_inspiral",
310 event_id = event.event_id
311 ))
312
313
314#
315# =============================================================================
316#
317# Build sim_inspiral <--> coinc Coincidences
318#
319# =============================================================================
320#
321
322
323def find_exact_coinc_matches(coincs, sim, comparefunc):
324 """
325 Return a set of the coinc_event_ids of the inspiral<-->inspiral
326 coincs in which all inspiral events match sim.
327 """
328 # comparefunc is True --> inspiral does not match sim
329 # any(...) --> at least one inspiral does not match sim
330 # not any(...) --> all inspirals match sim
331 #
332 # FIXME: this test does not consider the time slide offsets that
333 # should be applied to the coinc, but for now injections are done
334 # at zero lag so this isn't a problem yet
335 return set(coinc_event_id for coinc_event_id, inspirals in coincs if not any(comparefunc(sim, inspiral) for inspiral in inspirals))
336
337
338def find_near_coinc_matches(coincs, sim, comparefunc):
339 """
340 Return a set of the coinc_event_ids of the inspiral<-->inspiral
341 coincs in which at least one inspiral event matches sim.
342 """
343 # comparefunc is True --> inspiral does not match sim
344 # all(...) --> no inspirals match sim
345 # not all(...) --> at least one inspiral matches sim
346 #
347 # FIXME: this test does not consider the time slide offsets that
348 # should be applied to the coinc, but for now injections are done
349 # at zero lag so this isn't a problem yet
350 return set(coinc_event_id for coinc_event_id, inspirals in coincs if not all(comparefunc(sim, inspiral) for inspiral in inspirals))
351
352
353def add_sim_coinc_coinc(contents, sim, coinc_event_ids, coinc_def_id):
354 """
355 Create a coinc_event in the coinc table, and add arcs in the
356 coinc_event_map table linking the sim_inspiral row and the list of
357 coinc_event rows to the new coinc_event row.
358 """
359 coinc = contents.new_coinc(coinc_def_id)
360 coinc.nevents = len(coinc_event_ids)
361
362 contents.coincmaptable.append(lsctables.CoincMap(
363 coinc_event_id = coinc.coinc_event_id,
364 table_name = u"sim_inspiral",
365 event_id = sim.simulation_id
366 ))
367
368 for coinc_event_id in coinc_event_ids:
369 contents.coincmaptable.append(lsctables.CoincMap(
370 coinc_event_id = coinc.coinc_event_id,
371 table_name = u"coinc_event",
372 event_id = coinc_event_id
373 ))
374
375
376#
377# =============================================================================
378#
379# Library API
380#
381# =============================================================================
382#
383
384
385def ligolw_inspinjfind(xmldoc, process, search, snglcomparefunc, nearcoinccomparefunc, end_time_bisect_window = 1.0, verbose = False):
386 #
387 # Analyze the document's contents.
388 #
389
390 if verbose:
391 print("indexing ...", file=sys.stderr)
392
393 bbdef = {"inspiral": thinca.InspiralCoincDef}[search]
394 sbdef = {"inspiral": InspiralSICoincDef}[search]
395 scedef = {"inspiral": InspiralSCExactCoincDef}[search]
396 scndef = {"inspiral": InspiralSCNearCoincDef}[search]
397
398 contents = DocContents(xmldoc = xmldoc, bbdef = bbdef, sbdef = sbdef, scedef = scedef, scndef = scndef, process = process, end_time_bisect_window = end_time_bisect_window)
399
400 #
401 # Find sim_inspiral <--> sngl_inspiral coincidences.
402 #
403
404 for sim in tqdm.tqdm(contents.siminspiraltable, desc = sbdef.description, disable = not verbose):
405 inspirals = find_sngl_inspiral_matches(contents, sim, snglcomparefunc)
406 if inspirals:
407 add_sim_inspiral_coinc(contents, sim, inspirals)
408
409 #
410 # Find sim_inspiral <--> coinc_event coincidences.
411 #
412
413 if contents.scn_coinc_def_id:
414 for sim in tqdm.tqdm(contents.siminspiraltable, desc = scndef.description, disable = not verbose):
415 coincs = contents.coincs_near_endtime(sim.time_geocent)
416 exact_coinc_event_ids = find_exact_coinc_matches(coincs, sim, snglcomparefunc)
417 near_coinc_event_ids = find_near_coinc_matches(coincs, sim, nearcoinccomparefunc)
418 assert exact_coinc_event_ids.issubset(near_coinc_event_ids)
419 if exact_coinc_event_ids:
420 add_sim_coinc_coinc(contents, sim, exact_coinc_event_ids, contents.sce_coinc_def_id)
421 if near_coinc_event_ids:
422 add_sim_coinc_coinc(contents, sim, near_coinc_event_ids, contents.scn_coinc_def_id)
423
424 #
425 # Restore the original event order.
426 #
427
428 if verbose:
429 print("finishing ...", file=sys.stderr)
430 contents.sort_triggers_by_id()
431
432 #
433 # Done.
434 #
435
436 return xmldoc
437
438
439#
440# =============================================================================
441#
442# Revert
443#
444# =============================================================================
445#
446
447
448def revert(xmldoc, program, verbose = False):
449 #
450 # remove entries from process metadata tables
451 #
452
453 if verbose:
454 print("removing process metadata ...", file=sys.stderr)
455 process_table = lsctables.ProcessTable.get_table(xmldoc)
456 # IDs of things to delete
457 process_ids = process_table.get_ids_by_program(program)
458 iterutils.inplace_filter((lambda row: row.process_id not in process_ids), process_table)
459 iterutils.inplace_filter((lambda row: row.process_id not in process_ids), lsctables.ProcessParamsTable.get_table(xmldoc))
460
461 #
462 # remove coinc_event and coinc_event_map entries
463 #
464
465 if verbose:
466 print("removing coincs ...", file=sys.stderr)
467 coinc_event_table = lsctables.CoincTable.get_table(xmldoc)
468 # IDs of things to delete
469 coinc_ids = frozenset(row.coinc_event_id for row in coinc_event_table if row.process_id in process_ids)
470 iterutils.inplace_filter((lambda row: row.coinc_event_id not in coinc_ids), coinc_event_table)
471 iterutils.inplace_filter((lambda row: row.coinc_event_id not in coinc_ids), lsctables.CoincMapTable.get_table(xmldoc))
472 # IDs of things to keep
473 time_slide_ids = frozenset(row.time_slide_id for row in coinc_event_table)
474 coinc_def_ids = frozenset(row.coinc_def_id for row in coinc_event_table)
475
476 #
477 # remove time_slide and coinc_definer entries
478 #
479
480 if verbose:
481 print("removing coinc metadata ...", file=sys.stderr)
482 # coinc types to delete
483 coinc_defs = frozenset((row.search, row.search_coinc_type) for row in (InspiralSICoincDef, InspiralSCNearCoincDef, InspiralSCExactCoincDef))
484 iterutils.inplace_filter((lambda row: row.process_id not in process_ids or row.time_slide_id in time_slide_ids), lsctables.TimeSlideTable.get_table(xmldoc))
485 iterutils.inplace_filter((lambda row: (row.search, row.search_coinc_type) not in coinc_defs or row.coinc_def_id in coinc_def_ids), lsctables.CoincDefTable.get_table(xmldoc))
A wrapper interface to the XML document.
Definition: inspinjfind.py:102
def coincs_near_endtime(self, t)
Return a list of the (coinc_event_id, event list) tuples in which at least one inspiral event's end t...
Definition: inspinjfind.py:235
def inspirals_near_endtime(self, t)
Return a list of the inspiral events whose end times are within self.end_time_bisect_window of t.
Definition: inspinjfind.py:227
def new_coinc(self, coinc_def_id)
Construct a new coinc_event row attached to the given process, and belonging to the set of coincidenc...
Definition: inspinjfind.py:261
def sort_triggers_by_id(self)
Sort the sngl_inspiral table's rows by ID (tidy-up document for output).
Definition: inspinjfind.py:253
def __init__(self, xmldoc, bbdef, sbdef, scedef, scndef, process, end_time_bisect_window)
Definition: inspinjfind.py:103
Version of lsctables.SnglInspiral who's .__cmp__() method compares this object's ....
Definition: inspinjfind.py:74
def ligolw_inspinjfind(xmldoc, process, search, snglcomparefunc, nearcoinccomparefunc, end_time_bisect_window=1.0, verbose=False)
Definition: inspinjfind.py:385
def find_near_coinc_matches(coincs, sim, comparefunc)
Return a set of the coinc_event_ids of the inspiral<-->inspiral coincs in which at least one inspiral...
Definition: inspinjfind.py:342
def add_sim_inspiral_coinc(contents, sim, inspirals)
Create a coinc_event in the coinc table, and add arcs in the coinc_event_map table linking the sim_in...
Definition: inspinjfind.py:295
def find_sngl_inspiral_matches(contents, sim, comparefunc)
Scan the inspiral table for triggers matching sim.
Definition: inspinjfind.py:286
def find_exact_coinc_matches(coincs, sim, comparefunc)
Return a set of the coinc_event_ids of the inspiral<-->inspiral coincs in which all inspiral events m...
Definition: inspinjfind.py:327
def add_sim_coinc_coinc(contents, sim, coinc_event_ids, coinc_def_id)
Create a coinc_event in the coinc table, and add arcs in the coinc_event_map table linking the sim_in...
Definition: inspinjfind.py:358
def revert(xmldoc, program, verbose=False)
Definition: inspinjfind.py:448