3 # Copyright (C) 2012-2014 Stephen Privitera, Kipp Cannon, Chad Hanna, Drew Keppel
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.
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.
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.
20 # A program to plot the sensitivity of a gstlal inspiral search, e.g., VT
28 from pysqlite2
import dbapi2 as sqlite3
32 from matplotlib
import pyplot
37 from optparse
import OptionParser
39 from gstlal
import inspiral_pipe
41 from glue
import segments
42 from glue.ligolw
import ligolw
43 from glue.ligolw
import lsctables
44 from glue.ligolw
import dbtables
45 from glue.ligolw
import utils as ligolw_utils
46 from glue.ligolw
import array as ligolw_array
47 from glue
import segmentsUtils
48 from glue.lal
import CacheEntry
49 from glue
import iterutils
53 from pylal
import rate
54 from pylal
import imr_utils
55 from pylal
import git_version
57 __author__ =
"Stephen Privitera <sprivite@caltech.edu>, Chad Hanna <channa@perimeterinstitute.ca>, Kipp Cannon <kipp.cannon@ligo.org>"
61 pyplot.rc(
'font',**{
'family':
'serif',
'serif':[
'Computer Modern Roman']})
62 matplotlib.rcParams.update({
"text.usetex": True})
65 def chirp_mass(m1,m2):
70 return mu**(3./5) *mtotal**(2./5)
72 class upper_limit(
object):
74 The upper_limit class organizes the calculation of the sensitive search volume
75 for a search described by the input database.
77 def __init__(self, opts):
78 ## Instance variables ######################################
81 self.segments = segments.segmentlistdict()
82 self.zero_lag_segments = segments.segmentlistdict()
83 self.start_time = None
85 ############################################################
87 # read the zero lag databases first
88 for f in
self.opts.zero_lag_database:
90 print >> sys.stdout,
"\nGathering search data from zero-lag database: %s...." % (f,)
91 working_filename = dbtables.get_connection_filename(f, tmp_path=opts.tmp_space, verbose = opts.verbose)
92 connection = sqlite3.connect(working_filename)
94 # find out which instruments were on and when
95 self.get_on_instruments(connection)
96 self.get_segments(connection) #
get single ifo segments with vetoes applied
98 # done with zl database
100 dbtables.discard_connection_filename(f, working_filename, verbose =
self.opts.verbose)
103 self.get_zero_lag_segments() #make coincident ifo segments from single ifo segments
104 self.get_livetime() #
get the livetime
for the chosen
set of instruments
106 # open up injection databases
107 self.found_injections_fars = {}
108 self.found_injections_snrs = {}
109 self.total_injections = {}
110 for f in
self.opts.injection_database:
112 print
"Reading results of injection analysis from %s ..."%f
113 working_filename = dbtables.get_connection_filename(f, tmp_path=opts.tmp_space, verbose = opts.verbose)
114 connection = sqlite3.connect(working_filename)
115 for inst in
self.instruments:
116 self.found_injections_fars.setdefault(inst,[])
117 self.found_injections_snrs.setdefault(inst,[])
118 self.total_injections.setdefault(inst,[])
120 # get sim-far relationships
121 found, total, missed = imr_utils.get_min_far_inspiral_injections(connection, segments =
self.zero_lag_segments[inst], table_name=
"coinc_inspiral:table")
122 self.found_injections_fars[inst] += found
123 self.total_injections[inst] += total
125 # get sim-snr relationships
126 found, total, missed = imr_utils.get_max_snr_inspiral_injections(connection, segments =
self.zero_lag_segments[inst], table_name=
"coinc_inspiral:table")
127 self.found_injections_snrs[inst] += found
129 dbtables.discard_connection_filename(f, working_filename, verbose =
self.opts.verbose)
131 # remove injections that violate user-imposed constraints and
132 # symmetrize between m1-m2
133 for instr in
self.instruments:
134 self.total_injections[instr] = imr_utils.symmetrize_sims(
self.total_injections[instr],
"mass1",
"mass2")
135 self.found_injections_fars[instr] =
self.filter_injections(
self.found_injections_fars[instr])
136 self.found_injections_snrs[instr] =
self.filter_injections(
self.found_injections_snrs[instr])
139 def get_on_instruments(
self,connection):
141 Retrieve the sets of instruments which were on during the search.
143 for inst in connection.cursor().execute(
"""SELECT DISTINCT(ifos) FROM search_summary"""):
144 inst = frozenset(lsctables.instrument_set_from_ifos(inst[0]))
145 if not inst in self.instruments:
146 self.instruments.append(inst)
148 return self.instruments
151 def get_segments(self,connection):
153 Retrieve raw single IFO segments from the database and
157 # get start and end times of the analysis, used for output
158 # naming. these may extend beyond the actual analyzed
159 # segments because of vetoes.
161 start_time = int( connection.cursor().execute(
'SELECT MIN(start_time) FROM segment').fetchone()[0] )
162 end_time = int( connection.cursor().execute(
'SELECT MAX(end_time) FROM segment').fetchone()[0] )
163 if not self.start_time or start_time < self.start_time:
164 self.start_time = start_time
165 if not self.end_time or end_time < self.end_time:
166 self.end_time = end_time
169 # get single ifos from ifo sets
171 for inst in self.instruments:
174 # retrieve single-ifo segments
175 segs = imr_utils.get_segments( connection, dbtables.get_xml(connection),
"%s:table" % opts.coinc_table_name, opts.live_time_program, opts.veto_segments_name, opts.data_segments_name)
178 self.segments.setdefault(ifo,segments.segmentlist())
179 self.segments[ifo] |= segs[ifo]
184 def get_zero_lag_segments(self):
186 Compute multi-ifo (coincident) segment list from single ifo segments.
189 print >>sys.stdout,
"\nForming coincident segments from single IFO segments..."
191 for inst in self.instruments[:]:
192 # intersect single ifo segments
193 self.zero_lag_segments.setdefault(inst,segments.segmentlist())
194 self.segments.union(set(self.segments.keys()) - inst)
195 self.zero_lag_segments[inst] = self.segments.intersection(inst) - self.segments.union(set(self.segments.keys()) - inst)
197 print >>sys.stdout,
"\t%s were on for %d seconds (after vetoes, including playground)" % (
','.join(sorted(list(inst))),abs(self.zero_lag_segments[inst]))
199 # subtract playground segments
200 if not self.opts.include_play:
201 self.zero_lag_segments[inst] -= segmentsUtils.S2playground(self.zero_lag_segments[inst].extent())
203 print >>sys.stdout,
"\t%s were on for %d seconds (after vetoes, excluding playground)" % (
','.join(sorted(list(inst))),abs(self.zero_lag_segments[inst]))
205 # remove instrument sets that were never on
206 if abs(self.zero_lag_segments[inst]) == 0:
207 print >>sys.stderr,
"No livetime for in %s observation time. Skipping..."%(
"".join(sorted(list(inst))))
208 self.instruments.remove(inst)
210 return self.zero_lag_segments
213 def get_livetime(self):
214 '''Compute the instrument livetimes from the search segments.'''
216 for inst in
self.zero_lag_segments.keys():
217 self.livetime[inst] = float(abs(
self.zero_lag_segments[inst]))/lal.YRJUL_SI
222 def set_bins(
self, bin_type, inst):
223 if bin_type ==
"Total_Mass":
224 self.bins = imr_utils.guess_distance_total_mass_bins_from_sims(self.total_injections[inst], nbins = opts.mass_bins, distbins = opts.dist_bins)
225 self.sim_to_bins = imr_utils.sim_to_distance_total_mass_bins_function
226 if bin_type ==
"Chirp_Mass":
227 self.bins = imr_utils.guess_distance_chirp_mass_bins_from_sims(self.total_injections[inst], mbins = opts.mass_bins, distbins = opts.dist_bins)
228 self.sim_to_bins = imr_utils.sim_to_distance_chirp_mass_bins_function
229 if bin_type ==
"Mass1_Mass2":
230 self.bins = imr_utils.guess_distance_mass1_mass2_bins_from_sims(self.total_injections[inst], mass1bins = opts.mass_bins, mass2bins = opts.mass_bins, distbins = opts.dist_bins)
231 self.sim_to_bins = imr_utils.sim_to_distance_mass1_mass2_bins_function
232 if bin_type ==
"Aligned_Spin":
233 self.bins = imr_utils.guess_distance_effective_spin_parameter_bins_from_sims(self.total_injections[inst], chibins = opts.spin_bins, distbins = opts.dist_bins)
234 self.sim_to_bins = imr_utils.sim_to_distance_effective_spin_parameter_bins_function
235 if bin_type ==
"Mass_Ratio":
236 self.bins = imr_utils.guess_distance_mass_ratio_bins_from_sims(self.total_injections[inst], qbins = opts.mass_bins, distbins = opts.dist_bins)
237 self.sim_to_bins = imr_utils.sim_to_distance_mass_ratio_bins_function
239 return self.bins, self.sim_to_bins
242 def filter_injections(self, sims):
244 Remove injections from those found in the database based on
245 user-imposed restrictions.
247 for inst in self.instruments:
249 for far, sim in sims:
250 # exclude simulations from certain waveform families
251 if sim.waveform in self.opts.exclude_sim_type:
254 if not self.opts.min_mtotal < sim.mass1 + sim.mass2 < self.opts.max_mtotal:
257 if self.opts.max_mass_ratio and not (1.0/self.opts.max_mass_ratio <= sim.mass1/sim.mass2 <= self.opts.max_mass_ratio):
260 # symmetrize in m1-m2
261 if sim.mass1 > sim.mass2:
267 # passed all constraint tests, we can use it
268 newinjs.append((far,sim))
273 def parse_command_line():
278 The program gstlal_inspiral_plot_sensitivity computes the sensitive volume of a CBC search from input databases containing triggers from simulation experiments. These triggers need to be ranked by false alarm rate, the detection statistic used in S6 searches. Then injections which register a trigger louder than the loudest event, by false alarm rate, are considered found. All others are considered missed. The efficiency of detecting an event depends on the source parameters, such as its component masses, distance, spin, inclination, sky position, etc. However, lalapps_cbc_svim only considers the dependency of the efficiency on distance and mass, marginalizing over the other parameters. Injections are binned in distance and mass and the estimated efficiency is integrated over distance to convert the efficiency into a physical volume.
281 parser = OptionParser(version = git_version.verbose_msg, usage = description)
283 # FAR range and resolution
284 parser.add_option(
"--xaxis-points", default = 50, type =
"int", help =
"Specify the number of FARs/SNRs for which to compute the search volume.")
285 parser.add_option(
"--min-far", default = 1.0e-6/lal.YRJUL_SI, type =
"float", help =
"Specify the minimum FAR (Hz)") # one per million years is probably detection worthy
286 parser.add_option(
"--max-far", default = 12.0/lal.YRJUL_SI, type =
"float", help =
"Specify the maximum FAR (Hz)") # one per month is possibly EM-followup worthy
287 parser.add_option(
"--min-snr", default = 7, type =
"float", help =
"Specify the minimum SNR for volume calculation.")
288 parser.add_option(
"--max-snr", default = 15, type =
"float", help =
"Specify the maximum SNR for volume calculation.")
291 parser.add_option(
"--include-play", default = False, action =
"store_true", help =
"Include playground data in computing the livetime and volume.")
292 parser.add_option(
"--zero-lag-database", default = [], action =
"append", help =
"Name of database containing the zero lag segments and triggers.")
293 parser.add_option(
"--injection-database", default = [], action =
"append", help =
"Name of database containing injection parameters and triggers.")
294 parser.add_option(
"--live-time-program", default =
"gstlal_inspiral", metavar =
"name", help =
"Set the name of the program whose rings will be extracted from the search_summary table. Default = \"gstlal_inspiral\".")
295 parser.add_option(
"--veto-segments-name", default =
"vetoes", help =
"Set the name of the veto segments to use from the XML document.")
296 parser.add_option(
"--data-segments-name", default =
"whitehtsegments", help =
"Set the name of the data segments. Default = \"whitehtsegments\".")
297 parser.add_option(
"--coinc-table-name", default =
"coinc_inspiral", metavar =
"name", help =
"Set the name of the table containing coincident triggers. Default = \"coinc_inspiral\".")
299 # Output data options
300 parser.add_option(
"--user-tag", default =
"SEARCH", metavar =
"name", help =
"Add a descriptive tag to the names of output files.")
301 parser.add_option(
"--output-dir", default =
"./", metavar =
"name", help =
"Select a directory to place output files.")
302 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.")
303 parser.add_option(
"--output-path", default =
"./", action =
"store", help=
"Choose directory to save output files.")
304 parser.add_option(
"--output-cache", default = None, help =
"Name of output cache file. If not specified, then no cache file will be written.")
305 parser.add_option(
"--verbose", action =
"store_true", help =
"Be verbose.")
310 parser.add_option(
"--mass-bins", default = 4, metavar =
"integer", type =
"int", help =
"Number of mass bins (per dimension), if mass bin boundaries are not explicitly set.")
311 parser.add_option(
"--spin-bins", default = 4, metavar =
"integer", type =
"int", help =
"Number of spin bins (per dimension), if spin bin boundaries are not explicitly set.")
312 parser.add_option(
"--dist-bins", default = 15, metavar =
"integer", type =
"int", help =
"Space distance bins evenly and specify the number of distance bins to use.")
315 parser.add_option(
"--min-mtotal", metavar =
"m", type =
'float', default = 2.0, help =
"Specify the minimum total mass to consider among the injections found in the DB. This filters all injections outside this total mass range.")
316 parser.add_option(
"--max-mtotal", metavar =
"m", type =
'float', default = 100.0, help =
"Specify the maximum total mass to consider among the injections found in the DB. This filters all injections outside this total mass range.")
317 parser.add_option(
"--min-mass", metavar =
"MM", type =
'float', default = 1.0, help =
"Specify the minimum component mass to consider among the injections found in the DB. This filters all injectionswith any component lighter than MM.")
318 parser.add_option(
"--max-mass", metavar =
"MM", type =
'float', default = 99.0, help =
"Specify the maximum mass to consider among the injections found in the DB. This filters all injections with any component heavier than MM.")
319 parser.add_option(
"--max-mass-ratio", metavar =
"q", type =
'float', default = None, help =
"Specify the maximum allowed mass ratio. Should be >= 1.")
320 parser.add_option(
"--exclude-sim-type", default = [], action =
"append", metavar =
"SIM", help =
"When computing the search volume, exclude injections made using the SIM waveform family. Example: SpinTaylorthreePointFivePN. Use this option multiple times to exclude more than one injection type.")
323 # Bin injections in mass by m1-m2
324 parser.add_option(
"--bin-by-mass1-mass2", default = False, action =
"store_true", help =
"Bin injections by component mass in two dimensions when estimating the search efficiency.")
326 # Bin injections by total mass
327 parser.add_option(
"--bin-by-total-mass", default = False, action =
"store_true", help =
"Bin injections by total mass when estimating the search efficiency.")
329 # Bin injections by chirp mass
330 parser.add_option(
"--bin-by-chirp-mass", default = False, action =
"store_true", help =
"Bin injections by chirp mass when estimating the search efficiency.")
333 parser.add_option(
"--bin-by-aligned-spin", default = False, action =
"store_true", help =
"Bin injections by aligned spin parameter when estimating the search efficiency.")
336 parser.add_option(
"--bin-by-mass-ratio", default = False, action =
"store_true", help =
"Bin injections by mass ratio when estimating the search efficiency.")
338 opts, filenames = parser.parse_args()
339 opts.injection_database.extend(filenames)
342 if opts.bin_by_total_mass:
343 opts.bin_types.append(
"Total_Mass")
344 if opts.bin_by_chirp_mass:
345 opts.bin_types.append(
"Chirp_Mass")
346 if opts.bin_by_mass1_mass2:
347 opts.bin_types.append(
"Mass1_Mass2")
348 if opts.bin_by_aligned_spin:
349 opts.bin_types.append(
"Aligned_Spin")
350 if opts.bin_by_mass_ratio:
351 opts.bin_types.append(
"Mass_Ratio")
353 if opts.max_mass_ratio and (opts.max_mass_ratio < 1):
354 raise ValueError,
"The maximum mass ratio must be >=1!"
356 return opts, filenames
359 ############################ MAIN PROGRAM #####################################
360 ###############################################################################
361 ###############################################################################
363 #create an empty cache
which will store the output files/metadata
367 def write_cache(cache_list, fileout):
369 if opts.output_cache is not None:
370 fd = open( fileout,
"w" )
372 fd.write( str(l) +
"\n")
381 # read in command line opts
382 opts, database = parse_command_line()
384 # read in data from input database, store in upper limit object
385 if opts.
verbose: print
"\n\nSetting up the search volume calculations...\n"
386 UL = upper_limit(opts)
388 # get the range of xaxis values to use
389 fars = numpy.logspace(numpy.log10(opts.min_far), numpy.log10(opts.max_far), opts.xaxis_points)
390 snrs = numpy.logspace(numpy.log10(opts.min_snr), numpy.log10(opts.max_snr), opts.xaxis_points)
394 # loop over the requested instruments and mass bin types, compute the
395 # search volume as a function of FAR and SNR threshold
397 for bin_type in opts.bin_types:
399 for instr in UL.instruments:
401 # check for empty injection sets
402 if not UL.total_injections[instr]:
403 print >> sys.stderr,
"No injections performed in %s time. Skipping..." %
"".join(sorted(list(instr)))
407 print
"\n\nComputing sensitive volume for %s observation time binning by %s...\n" % (
"".join(sorted(list(instr))),bin_type)
409 bins, s2b = UL.set_bins(bin_type,instr)
412 # prepare output XML file. record mass bins, fars, snrs and
415 xmldoc = ligolw.Document()
416 child = xmldoc.appendChild(ligolw.LIGO_LW())
418 # write out mass bins
419 for j in range(len(bins[1:])):
420 xml = ligolw.LIGO_LW({u
"Name": u
"mass%d_bins:%s" % (j+1,bin_type)})
421 edges = tuple( numpy.concatenate((l,u[-1:])) for l,u in zip(bins.lower(), bins.upper()) )
422 xml.appendChild(ligolw_array.from_array(u"array", edges[j+1]))
423 child.appendChild(xml)
425 # write out fars/snrs/livetime
426 xml = ligolw.LIGO_LW({u
"Name": u
"far_bins:%s" % (bin_type,)})
427 xml.appendChild(ligolw_array.from_array(u
"array", fars))
428 child.appendChild(xml)
430 xml = ligolw.LIGO_LW({u
"Name": u
"snr_bins:%s" % (bin_type,)})
431 xml.appendChild(ligolw_array.from_array(u
"array", snrs))
432 child.appendChild(xml)
434 xml = ligolw.LIGO_LW({u
"Name": u
"livetime:%s" % (bin_type,)})
435 xml.appendChild(ligolw_array.from_array(u
"array", numpy.array([UL.livetime[instr]])))
436 child.appendChild(xml)
439 # compute volume by far and snr for all mass bins
441 vols_far, errs_far, vols_snr, errs_snr = [], [], [], []
442 for k, far, snr in zip(range(opts.xaxis_points), fars, snrs):
445 # get found/missed injections
447 found_by_far = [s for f, s in UL.found_injections_fars[instr] if f < far]
448 found_by_snr = [s for rho, s in UL.found_injections_snrs[instr] if rho > snr]
451 # compute volume vs far
453 vol, err = imr_utils.compute_search_volume_in_bins(found_by_far, UL.total_injections[instr], bins, s2b)
454 vol.array *= UL.livetime[instr] #preferred unit is Mpc^3*yr
455 err.array *= UL.livetime[instr] #preferred unit is Mpc^3*yr
460 # write volume and volume errors array to xml
462 xml = ligolw.LIGO_LW({u
"Name": u
"volume_by_far_%d:%s" % (k, bin_type,)})
463 xml.appendChild(ligolw_array.from_array(u"array", vol.array))
464 child.appendChild(xml)
466 xml = ligolw.LIGO_LW({u
"Name": u
"volume_error_by_far_%d:%s" % (k, bin_type,)})
467 xml.appendChild(ligolw_array.from_array(u"array", err.array))
468 child.appendChild(xml)
471 # compute volume vs snr
473 vol, err = imr_utils.compute_search_volume_in_bins(found_by_snr, UL.total_injections[instr], bins, s2b)
474 vol.array *= UL.livetime[instr] #preferred unit is Mpc^3*yr
475 err.array *= UL.livetime[instr] #preferred unit is Mpc^3*yr
480 # write volume and volume errors array to xml
482 xml = ligolw.LIGO_LW({u
"Name": u
"volume_by_snr_%d:%s" % (k, bin_type,)})
483 xml.appendChild(ligolw_array.from_array(u
"array", vol.array))
484 child.appendChild(xml)
486 xml = ligolw.LIGO_LW({u
"Name": u
"volume_error_by_snr_%d:%s" % (k, bin_type,)})
487 xml.appendChild(ligolw_array.from_array(u
"array", err.array))
488 child.appendChild(xml)
491 # finish xml document
493 output_filename =
"%s-SEARCH_VOLUME_BINNED_BY_%s_%s-%d-%d.xml" % (
"".join(sorted(list(instr))), bin_type.upper(), opts.user_tag, UL.start_time, UL.end_time-UL.start_time)
494 ligolw_utils.write_filename(xmldoc, output_filename)
495 cache_list.append( CacheEntry(
"".join(sorted(list(instr))), bin_type,segments.segment(UL.start_time, UL.end_time),
"file://localhost%s/%s" % (os.getcwd(), output_filename)) )
498 #
set up figures
for plots FIXME: since we are now writing the
499 # data to disk, it may make more sense to carry out the
500 # plotting in a separate code
502 fig_far_vol, fig_far_range, fig_snr_vol, fig_snr_range = pyplot.figure(), pyplot.figure(), pyplot.figure(), pyplot.figure()
503 figs = [fig_far_vol, fig_far_range, fig_snr_vol, fig_snr_range]
505 # plot the volume/range versus far/snr for each bin
506 mbins = rate.NDBins(bins[1:])
508 for mlo, mmid, mhi in zip(iterutils.MultiIter(*mbins.lower()),
509 iterutils.MultiIter(*mbins.centres()),
510 iterutils.MultiIter(*mbins.upper())):
513 if bin_type ==
"Aligned_Spin":
514 label =
"$\chi \in [%.2f, %.2f]$" % (mlo[0], mhi[0])
515 if bin_type ==
"Mass_Ratio":
516 label =
"$m_1/m_2 \in [%.2f, %.2f]$" % (mlo[0], mhi[0])
517 if bin_type ==
"Total_Mass":
518 label =
"$M_{total} \in [%.2f, %.2f] M_\odot$" % (mlo[0], mhi[0])
519 if bin_type ==
"Chirp_Mass":
520 label =
"$M_{chirp} \in [%.2f, %.2f] M_\odot$" % (mlo[0], mhi[0])
521 if bin_type ==
"Mass1_Mass2":
522 if mmid[0] > mmid[1]: # symmetrized sims have m1 < m2
524 label =
"$m_1 \in [%.2f, %.2f], m_2 \in [%.2f, %.2f] M_\odot$" % (mlo[0], mhi[0], mlo[1], mhi[1])
528 fig_far_vol.gca().errorbar( fars, [v[mmid] for v in vols_far], yerr = [e[mmid] for e in errs_far], label=label )
529 fig_snr_vol.gca().errorbar( snrs, [v[mmid] for v in vols_snr], yerr = [e[mmid] for e in errs_snr], label=label )
532 dist = [ rate.BinnedArray(mbins,array = (v.array/UL.livetime[instr]/(4*numpy.pi/3))**(1./3) ) for v in vols_far ]
533 derr = [ rate.BinnedArray(mbins,array = (d.array/3)*(e.array/v.array) ) for e,v,d in zip(errs_far, vols_far, dist) ]
534 fig_far_range.gca().errorbar( fars, [d[mmid] for d in dist], yerr = [e[mmid] for e in derr], label=label )
536 dist = [ rate.BinnedArray(mbins,array = (v.array/UL.livetime[instr]/(4*numpy.pi/3))**(1./3) ) for v in vols_snr ]
537 derr = [ rate.BinnedArray(mbins,array = (d.array/3)*(e.array/v.array) ) for e,v,d in zip(errs_snr, vols_snr, dist) ]
538 fig_snr_range.gca().errorbar( snrs, [d[mmid] for d in dist], yerr = [e[mmid] for e in derr], label=label )
541 ax = fig_far_vol.gca()
542 ax.set_xlabel(
"Combined FAR (Hz)")
543 ax.set_ylabel(
"VT (Mpc$^3$ yr)")
545 ax.set_xlim([min(fars), max(fars)])
549 ax = fig_snr_vol.gca()
550 ax.set_xlabel(
"Network SNR")
551 ax.set_ylabel(
"VT (Mpc$^3$ yr)")
552 ax.set_xlim([min(snrs), max(snrs)])
555 ax = fig_far_range.gca()
556 ax.set_xlabel(
"Combined FAR (Hz)")
557 ax.set_ylabel(
"Range (Mpc)")
559 ax.set_xlim([min(fars), max(fars)])
563 ax = fig_snr_range.gca()
564 ax.set_xlabel(
"Network SNR")
565 ax.set_ylabel(
"Range (Mpc)")
566 ax.set_xlim([min(snrs), max(snrs)])
569 # common to all figures
573 ax.legend(loc=
"upper right")
574 ax.set_title(
"Search Sensitivity in %s Time" %
"".join(sorted(list(instr))))
576 # save and close figures
577 tag = inspiral_pipe.
T050017_filename(instr,
"GSTLAL_INSPIRAL_PLOT_SENSITIVITY_%s_VOLUME_VS_FAR_BINNED_BY_%s" % (opts.user_tag, bin_type.upper()), UL.start_time, UL.end_time,
".png", path = opts.output_dir)
578 fig_far_vol.savefig(tag)
580 tag = inspiral_pipe.
T050017_filename(instr,
"GSTLAL_INSPIRAL_PLOT_SENSITIVITY_%s_VOLUME_VS_SNR_BINNED_BY_%s" % (opts.user_tag, bin_type.upper()), UL.start_time, UL.end_time,
".png", path = opts.output_dir)
581 fig_snr_vol.savefig(tag)
583 tag = inspiral_pipe.
T050017_filename(instr,
"GSTLAL_INSPIRAL_PLOT_SENSITIVITY_%s_RANGE_VS_FAR_BINNED_BY_%s" % (opts.user_tag, bin_type.upper()), UL.start_time, UL.end_time,
".png", path = opts.output_dir)
584 fig_far_range.savefig(tag)
586 tag = inspiral_pipe.
T050017_filename(instr,
"GSTLAL_INSPIRAL_PLOT_SENSITIVITY_%s_RANGE_VS_SNR_BINNED_BY_%s" % (opts.user_tag, bin_type.upper()), UL.start_time, UL.end_time,
".png", path = opts.output_dir)
587 fig_snr_range.savefig(tag)
592 # write a cache file describing the files generated during by this program
593 if opts.output_cache:
594 write_cache(cache_list, opts.output_cache)