Loading [MathJax]/extensions/TeX/AMSsymbols.js
LALApps 10.1.0.1-5e288d3
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
lalapps_string_plot_binj.py
Go to the documentation of this file.
1##python
2#
3# Copyright (C) 2009 Kipp Cannon
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the
7# Free Software Foundation; either version 2 of the License, or (at your
8# option) any later version.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13# Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19
20#
21# =============================================================================
22#
23# Preamble
24#
25# =============================================================================
26#
27
28
29from __future__ import print_function
30
31
32import math
33from optparse import OptionParser
34import sqlite3
35import sys
36
37
38from igwn_ligolw import dbtables
39from igwn_ligolw import utils as ligolw_utils
40from lal import rate
41from lal.utils import CacheEntry
42from lalburst import git_version
43from lalburst import SimBurstUtils
44from lalburst import SnglBurstUtils
45import igwn_segments as segments
46
47
48__author__ = "Kipp Cannon <kipp.cannon@ligo.org>"
49__version__ = "git id %s" % git_version.id
50__date__ = git_version.date
51
52
53#
54# =============================================================================
55#
56# Command Line
57#
58# =============================================================================
59#
60
61
63 parser = OptionParser(
64 version = "Name: %%prog\n%s" % git_version.verbose_msg
65 )
66 parser.add_option("-b", "--base", metavar = "base", default = "string_plotbinj_", help = "Set the prefix for output filenames (default = \"string_plotbinj_\").")
67 parser.add_option("-f", "--format", metavar = "format", action = "append", default = [], help = "Set the output image format (default = \"png\"). Option can be given multiple times to generate plots in multiple formats.")
68 parser.add_option("--amplitude-func", metavar = "det|wave", default = "det", help = "Select the amplitude to show on the plots. \"det\" = the amplitude expected in the detector, \"wave\" = the amplitude of the wave (default = \"det\").")
69 parser.add_option("--input-cache", metavar = "filename", action = "append", default = [], help = "Get list of files from this LAL cache.")
70 parser.add_option("-l", "--live-time-program", metavar = "program", default = "StringSearch", help = "Set the name of the program, as it appears in the process table, whose search summary entries define the search live time (default = \"StringSearch\").")
71 parser.add_option("--plot", metavar = "number", action = "append", default = None, help = "Generate the given plot number (default = make all plots).")
72 parser.add_option("-t", "--tmp-space", metavar = "path", help = "Path to a directory suitable for use as a work area while manipulating the database file. The database file will be worked on in this directory, and then moved to the final location when complete. This option is intended to improve performance when running in a networked environment, where there might be a local disk with higher bandwidth than is available to the filesystem on which the final output will reside.")
73 parser.add_option("-v", "--verbose", action = "store_true", help = "Be verbose.")
74 options, filenames = parser.parse_args()
75
76 if options.amplitude_func == "wave":
77 options.amplitude_func = lambda sim, instrument, offsetvector: sim.amplitude
78 options.amplitude_lbl = r"$A^{\mathrm{wave}}$"
79 elif options.amplitude_func == "det":
80 options.amplitude_func = SimBurstUtils.string_amplitude_in_instrument
81 options.amplitude_lbl = r"$A^{\mathrm{det}}$"
82 else:
83 raise ValueError("unrecognized --amplitude-func %s" % options.amplitude_func)
84
85 if not options.format:
86 options.format = ["png"]
87
88 if options.plot is not None:
89 # use sorted(set(...)) to ensure the indexes are ordered
90 # and unique
91 options.plot = sorted(set(map(int, options.plot)))
92
93 filenames = filenames or []
94 for cache in options.input_cache:
95 if options.verbose:
96 print("reading '%s' ..." % cache, file=sys.stderr)
97 filenames += [CacheEntry(line).path for line in file(cache)]
98 if not filenames:
99 raise ValueError("Nothing to do!")
100
101 return options, filenames
102
103
104#
105# =============================================================================
106#
107# Trigger Count Histogram
108#
109# =============================================================================
110#
111
112
114 def __init__(self):
115 self.found = 0
116 self.bins = []
117
118 def add_contents(self, contents):
119 for nevents, in contents.connection.cursor().execute("""
120SELECT
121 nevents
122FROM
123 coinc_event
124WHERE
125 coinc_def_id == ?
126 """, (contents.sb_definer_id,)):
127 self.found += 1
128 while nevents + 1 >= len(self.bins):
129 self.bins.append(0)
130 self.bins[nevents] += 1
131
132 def finish(self):
133 fig, axes = SnglBurstUtils.make_burst_plot("Number of Triggers Coincident with Injection", "Count")
134 axes.semilogy()
135 axes.plot(range(len(self.bins)), self.bins, "ko-")
136 axes.set_title("Triggers per Found Injection\n(%d Found Injections)" % self.found)
137
138 return fig,
139
140
141#
142# =============================================================================
143#
144# Recovered vs. Injected h_rss
145#
146# =============================================================================
147#
148
149
151 class Data(object):
152 def __init__(self):
153 self.found = 0
154 self.x = []
155 self.y = []
156 self.c = []
157
158 def __init__(self, amplitude_func, amplitude_lbl):
159 self.amplitude_func = amplitude_func
160 self.amplitude_lbl = amplitude_lbl
161 self.data = {}
162
163 def add_contents(self, contents):
164 offsetvectors = contents.time_slide_table.as_dict()
165 for values in contents.connection.cursor().execute("""
166SELECT
167 sim_burst.*,
168 sngl_burst.ifo,
169 sngl_burst.amplitude
170FROM
171 sim_burst
172 JOIN sim_burst_map AS map ON (
173 map.simulation_id == sim_burst.simulation_id
174 )
175 JOIN sngl_burst ON (
176 sngl_burst.event_id == map.event_id
177 )
178 """):
179 sim = contents.sim_burst_table.row_from_cols(values)
180 instrument, amplitude_rec = values[-2:]
181 try:
182 data = self.data[instrument]
183 except KeyError:
184 data = self.data[instrument] = self.Data()
185 data.found += 1
186 data.x.append(abs(self.amplitude_func(sim, instrument, offsetvectors[sim.time_slide_id])))
187 data.y.append(abs(amplitude_rec))
188 data.c.append(math.log(sim.frequency))
189
190 def finish(self):
191 for instrument, data in sorted(self.data.items()):
192 fig, axes = SnglBurstUtils.make_burst_plot("Injected %s" % self.amplitude_lbl, r"Recovered $A$")
193 axes.loglog()
194 fig.set_size_inches(8, 8)
195 axes.scatter(data.x, data.y, c = data.c, s = 16)
196 #xmin, xmax = axes.get_xlim()
197 #ymin, ymax = axes.get_ylim()
198 xmin, xmax = min(data.x), max(data.x)
199 ymin, ymax = min(data.y), max(data.y)
200 xmin = ymin = min(xmin, ymin)
201 xmax = ymax = max(xmax, ymax)
202 axes.plot([xmin, xmax], [ymin, ymax], "k-")
203 axes.set_xlim([xmin, xmax])
204 axes.set_ylim([ymin, ymax])
205 axes.set_title(r"Recovered $A$ vs.\ Injected %s in %s (%d Recovered Injections)" % (self.amplitude_lbl, instrument, data.found))
206 yield fig
207
208
209#
210# =============================================================================
211#
212# Recovered Time Offset
213#
214# =============================================================================
215#
216
217
219 class Data(object):
220 def __init__(self, binning):
221 self.found = 0
222 self.offsets = rate.BinnedDensity(binning)
223
224 def __init__(self, interval, width):
225 # 21 bins per filter width
226 bins = int(float(abs(interval)) / width) * 21
227 self.binning = rate.NDBins((rate.LinearBins(interval[0], interval[1], bins),))
228 self.data = {}
229
230 def add_contents(self, contents):
231 offsetvectors = contents.time_slide_table.as_dict()
232 for values in contents.connection.cursor().execute("""
233SELECT
234 sim_burst.*,
235 sngl_burst.ifo,
236 sngl_burst.snr,
237 sngl_burst.peak_time, sngl_burst.peak_time_ns
238FROM
239 sim_burst
240 JOIN sim_burst_map AS map ON (
241 map.simulation_id == sim_burst.simulation_id
242 )
243 JOIN sngl_burst ON (
244 sngl_burst.event_id == map.event_id
245 )
246 """):
247 sim = contents.sim_burst_table.row_from_cols(values)
248 instrument = values[-4]
249 burst_snr = values[-3]
250 burst_peak = dbtables.lsctables.LIGOTimeGPS(*values[-2:])
251 sim_peak = sim.time_at_instrument(instrument, offsetvectors[sim.time_slide_id])
252
253 try:
254 data = self.data[instrument]
255 except KeyError:
256 data = self.data[instrument] = self.Data(self.binning)
257
258 data.found += 1
259 try:
260 data.offsets.count[float(burst_peak - sim_peak),] += 1.0
261 except IndexError:
262 # outside plot range
263 pass
264
265 def finish(self):
266 for instrument, data in sorted(self.data.items()):
267 fig, axes = SnglBurstUtils.make_burst_plot(r"$t_{\mathrm{recovered}} - t_{\mathrm{injected}}$ (s)", "Triggers per Unit Offset")
268 axes.semilogy()
269 axes.set_title("Trigger Peak Time - Injection Peak Time in %s\n(%d Found Injections)" % (instrument, data.found))
270 # 21 bins per filter width
271 rate.filter_array(data.offsets.array, rate.gaussian_window(21))
272 axes.plot(data.offsets.centres()[0], data.offsets.at_centres(), "k")
273 #axes.legend(["%s residuals" % instrument, "SNR-weighted mean of residuals in all instruments"], loc = "lower right")
274 yield fig
275
276
277#
278# =============================================================================
279#
280# Recovered vs. Injected Frequency Plots
281#
282# =============================================================================
283#
284
285
286class RecoveredFreq(object):
287 class DataA(object):
288 def __init__(self):
289 self.found = 0
290 self.x = []
291 self.y = []
292
293 class DataB(object):
294 def __init__(self):
295 self.found = 0
296 self.x = []
297 self.y = []
298 self.c = []
299
300 def __init__(self):
301 self.dataA = {}
302 self.dataB = {}
303
304 def add_contents(self, contents):
305 simulation_ids = {}
306 for simulation_id, sim_frequency, instrument, burst_frequency, burst_snr in contents.connection.cursor().execute("""
307SELECT
308 sim_burst.simulation_id,
309 sim_burst.frequency,
310 sngl_burst.ifo,
311 -- central_freq = (f_cut + f_bankstart) / 2
312 -- bandwidth = f_cut - f_bankstart
313 -- therefore f_cut = ...
314 sngl_burst.central_freq + sngl_burst.bandwidth / 2,
315 sngl_burst.snr
316FROM
317 sim_burst
318 JOIN sim_burst_map AS map ON (
319 map.simulation_id == sim_burst.simulation_id
320 )
321 JOIN sngl_burst ON (
322 sngl_burst.event_id == map.event_id
323 )
324 """):
325 simulation_ids.setdefault(instrument, set()).add(simulation_id)
326 try:
327 dataA = self.dataA[instrument]
328 dataB = self.dataB[instrument]
329 except KeyError:
330 dataA = self.dataA[instrument] = self.DataA()
331 dataB = self.dataB[instrument] = self.DataB()
332
333 dataA.x.append(burst_snr)
334 dataA.y.append((burst_frequency - sim_frequency) / sim_frequency)
335
336 dataB.x.append(sim_frequency)
337 dataB.y.append(burst_frequency)
338 dataB.c.append(math.log(burst_snr))
339
340 # count number of distinct injections that had matching
341 # burst triggers
342 for instrument, simulation_ids in simulation_ids.items():
343 self.dataA[instrument].found += len(simulation_ids)
344 self.dataB[instrument].found += len(simulation_ids)
345
346 def finish(self):
347 for instrument, data in sorted(self.dataA.items()):
348 fig, axes = SnglBurstUtils.make_burst_plot(r"SNR in %s" % instrument, r"$(f_{\mathrm{recovered}} - f_{\mathrm{injected}})/ f_{\mathrm{injected}}$")
349
350 axes.set_title("Cut-off Frequency Residual vs.\\ SNR in %s\n(%d Found Injections)" % (instrument, data.found))
351 axes.loglog()
352 axes.plot(data.x, data.y, "k+")
353
354 yield fig
355
356 for instrument, data in sorted(self.dataB.items()):
357 fig, axes = SnglBurstUtils.make_burst_plot(r"$f_{\mathrm{injected}}$ (Hz)", r"$f_{\mathrm{recovered}}$ (Hz)")
358 axes.loglog()
359 fig.set_size_inches(8, 8)
360 axes.scatter(data.x, data.y, c = data.c, s = 16)
361 xmin, xmax = min(data.x), max(data.x)
362 ymin, ymax = min(data.y), max(data.y)
363 xmin = ymin = min(xmin, ymin)
364 xmax = ymax = max(xmax, ymax)
365 axes.plot([xmin, xmax], [ymin, ymax], "k-")
366 axes.set_xlim([xmin, xmax])
367 axes.set_ylim([ymin, ymax])
368 axes.set_title(r"Recovered Cut-off Frequency vs.\ Injected Cut-off Frequency in %s\\(%d Injections; red = high recovered SNR, blue = low recovered SNR)" % (instrument, data.found))
369
370 yield fig
371
372
373#
374# =============================================================================
375#
376# \chi^{2} vs. \rho Scatter Plot
377#
378# =============================================================================
379#
380
381
382class Chi2VsRho(object):
383 class Data(object):
384 def __init__(self):
385 self.injections_x = []
386 self.injections_y = []
389
390 def __init__(self):
391 self.data = {}
392
393 def add_contents(self, contents):
394 for instrument, chi2, chi2dof, snr, is_injection in contents.connection.cursor().execute("""
395SELECT
396 sngl_burst.ifo,
397 sngl_burst.chisq,
398 sngl_burst.chisq_dof,
399 sngl_burst.snr,
400 sngl_burst.event_id IN (
401 SELECT
402 coinc_event_map.event_id
403 FROM
404 coinc_event_map
405 JOIN coinc_event ON (
406 coinc_event.coinc_event_id == coinc_event_map.coinc_event_id
407 )
408 WHERE
409 coinc_event_map.table_name == 'sngl_burst'
410 AND coinc_event.coinc_def_id == ?
411 )
412FROM
413 sngl_burst
414 """, (contents.sb_definer_id,)):
415 if is_injection:
416 try:
417 self.data[instrument].injections_x.append(snr)
418 self.data[instrument].injections_y.append(chi2 / chi2dof)
419 except KeyError:
420 self.data[instrument] = self.Data()
421 self.data[instrument].injections_x.append(snr)
422 self.data[instrument].injections_y.append(chi2 / chi2dof)
423 else:
424 try:
425 self.data[instrument].noninjections_x.append(snr)
426 self.data[instrument].noninjections_y.append(chi2 / chi2dof)
427 except KeyError:
428 self.data[instrument] = self.Data()
429 self.data[instrument].noninjections_x.append(snr)
430 self.data[instrument].noninjections_y.append(chi2 / chi2dof)
431
432 def finish(self):
433 for instrument, data in sorted(self.data.items()):
434 fig, axes = SnglBurstUtils.make_burst_plot(r"SNR", r"$\chi^{2} / \mathrm{DOF}$")
435
436 axes.set_title("$\\chi^{2} / \\mathrm{DOF}$ vs.\\ SNR in %s" % instrument)
437 axes.loglog()
438 axes.plot(data.injections_x, data.injections_y, "r+")
439 axes.plot(data.noninjections_x, data.noninjections_y, "k+")
440
441 yield fig
442
443
444#
445# =============================================================================
446#
447# Main
448#
449# =============================================================================
450#
451
452
453#
454# Parse command line
455#
456
457
458options, filenames = parse_command_line()
459
460
461#
462# Initialize plots
463#
464
465
466plots = [
467 (0, True, TriggerCountHistogram()),
468 (1, True, RecoveredVsInjectedAmplitude(options.amplitude_func, options.amplitude_lbl)),
469 (2, True, RecoveredTimeOffset(segments.segment(-0.01, +0.01), 0.00005)),
470 (3, True, RecoveredFreq()),
471 (4, False, Chi2VsRho())
472]
473
474if options.plot is not None:
475 plots = [plots[i] for i in options.plot]
476
477
478#
479# Process files
480#
481
482
483for n, filename in enumerate(ligolw_utils.sort_files_by_size(filenames, options.verbose, reverse = True)):
484 if options.verbose:
485 print("%d/%d: %s" % (n + 1, len(filenames), filename), file=sys.stderr)
486 working_filename = dbtables.get_connection_filename(filename, tmp_path = options.tmp_space, verbose = options.verbose)
487 database = SnglBurstUtils.CoincDatabase(sqlite3.connect(str(working_filename)), options.live_time_program, search = "StringCusp")
488 if options.verbose:
489 SnglBurstUtils.summarize_coinc_database(database)
490 is_injection_db = "sim_burst" in dbtables.get_table_names(database.connection)
491 if is_injection_db:
492 database.connection.cursor().execute("""
493CREATE TEMPORARY TABLE
494 sim_burst_map
495AS
496 SELECT
497 a.event_id AS simulation_id,
498 a.coinc_event_id AS coinc_event_id,
499 b.event_id AS event_id
500 FROM
501 coinc_event_map AS a
502 JOIN coinc_event ON (
503 coinc_event.coinc_event_id == a.coinc_event_id
504 )
505 JOIN coinc_event_map AS b ON (
506 b.table_name == 'sngl_burst'
507 AND b.coinc_event_id == a.coinc_event_id
508 )
509 WHERE
510 a.table_name == 'sim_burst'
511 AND coinc_event.coinc_def_id == ?
512 """, (database.sb_definer_id,))
513 for n, requires_injection_db, plot in plots:
514 if requires_injection_db and not is_injection_db:
515 continue
516 if options.verbose:
517 print("adding to plot group %d ..." % n, file=sys.stderr)
518 plot.add_contents(database)
519 database.connection.close()
520 dbtables.discard_connection_filename(filename, working_filename, verbose = options.verbose)
521
522
523#
524# finish and write plots, deleting them as we go to save memory
525#
526
527
528format = "%%s%%0%dd%%s.%%s" % (int(math.log10(len(plots) or 1)) + 1)
529while len(plots):
530 n, requires_injection_db, plot = plots.pop(0)
531 if options.verbose:
532 print("finishing plot group %d ..." % n, file=sys.stderr)
533 try:
534 figs = plot.finish()
535 except ValueError as e:
536 print("can't finish plot group %d: %s" % (n, str(e)), file=sys.stderr)
537 else:
538 for f, fig in enumerate(figs):
539 for extension in options.format:
540 filename = format % (options.base, n, chr(97 + f), extension)
541 if options.verbose:
542 print("writing %s ..." % filename, file=sys.stderr)
543 fig.savefig(filename)