#!/usr/bin/python3 # -*- coding: utf-8 -*- # Copyright (C) Joshua Smith (2016-) # # This file is part of the hveto python package. # # hveto is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # hveto is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with hveto. If not, see . """Run the HierarchichalVeto (hveto) algorithm over some data """ import time jobstart = time.time() # noqa import os import sys import json import random import warnings import datetime import configparser import multiprocessing from pathlib import Path from socket import getfqdn from getpass import getuser import numpy from matplotlib import use use('agg') # noqa from gwpy.io.cache import read_cache from gwpy.segments import (Segment, SegmentList, DataQualityFlag, DataQualityDict) from gwdetchar import cli from gwdetchar.io.html import (FancyPlot, cis_link) from gwdetchar.omega import batch from gwdetchar.plot import texify from hveto import (__version__, config, core, plot, html, utils) from hveto.plot import (HEADER_CAPTION, ROUND_CAPTION, get_column_label) from hveto.segments import (write_ascii as write_ascii_segments, read_veto_definer_file) from hveto.triggers import (get_triggers, find_auxiliary_channels) IFO = os.getenv('IFO') __author__ = 'Duncan Macleod ' __credits__ = ('Joshua Smith , ' 'Alex Urban ') logger = cli.logger(name=os.path.basename(__file__)) # -- parse command line ------------------------------------------------------- def abs_path(p): return os.path.abspath(os.path.expanduser(p)) parser = cli.create_parser(description=__doc__, version=__version__) cli.add_gps_start_stop_arguments(parser) cli.add_ifo_option(parser, required=IFO is None, ifo=IFO) parser.add_argument('-f', '--config-file', action='append', default=[], type=abs_path, help='path to hveto configuration file, can be given ' 'multiple times (files read in order)') cli.add_nproc_option(parser, default=1) parser.add_argument('-p', '--primary-cache', default=None, type=abs_path, help='path for cache containing primary channel files') parser.add_argument('-a', '--auxiliary-cache', default=None, type=abs_path, help='path for cache containing auxiliary channel files, ' 'files contained must be T050017-compliant with the ' 'channel name as the leading name parts, e.g. ' '\'L1-GDS_CALIB_STRAIN_--.' '\' for L1:GDS-CALIB_STRAIN triggers') parser.add_argument('-S', '--analysis-segments', action='append', default=[], type=abs_path, help='path to file containing segments for ' 'the analysis flag (name in data file ' 'must match analysis-flag in config file)') parser.add_argument('-w', '--omega-scans', type=int, metavar='NSCAN', help='generate a workflow of omega scans for each round, ' 'this will launch automatically to condor, ' 'requires the gwdetchar package') pout = parser.add_argument_group('Output options') pout.add_argument('-o', '--output-directory', default=os.curdir, help='path of output directory, default: %(default)s') args = parser.parse_args() ifo = args.ifo start = int(args.gpsstart) end = int(args.gpsend) duration = end - start logger.info("-- Welcome to Hveto --") logger.info("GPS start time: %d" % start) logger.info("GPS end time: %d" % end) logger.info("Interferometer: %s" % ifo) # -- initialisation ----------------------------------------------------------- # read configuration cp = config.HvetoConfigParser(ifo=ifo) cp.read(args.config_file) logger.info("Parsed configuration file(s)") # format output directory outdir = abs_path(args.output_directory) if not os.path.isdir(outdir): os.makedirs(outdir) os.chdir(outdir) logger.info("Working directory: %s" % outdir) segdir = 'segments' plotdir = 'plots' trigdir = 'triggers' omegadir = 'scans' for d in [segdir, plotdir, trigdir, omegadir]: if not os.path.isdir(d): os.makedirs(d) # prepare html variables htmlv = { 'title': '%s Hveto | %d-%d' % (ifo, start, end), 'config': None, 'context': ifo.lower(), } # get segments aflag = cp.get('segments', 'analysis-flag') url = cp.get('segments', 'url') padding = tuple(cp.getfloats('segments', 'padding')) if args.analysis_segments: segs_ = DataQualityDict.read(args.analysis_segments, gpstype=float) analysis = segs_[aflag] span = SegmentList([Segment(start, end)]) analysis.active &= span analysis.known &= span analysis.coalesce() logger.debug("Segments read from disk") else: analysis = DataQualityFlag.query(aflag, start, end, url=url) logger.debug("Segments recovered from %s" % url) if padding != (0, 0): mindur = padding[0] - padding[1] analysis.active = type(analysis.active)([s for s in analysis.active if abs(s) >= mindur]) analysis.pad(*padding, inplace=True) logger.debug("Padding %s applied" % str(padding)) livetime = int(abs(analysis.active)) livetimepc = livetime / duration * 100. logger.info("Retrieved %d segments for %s with %ss (%.2f%%) livetime" % (len(analysis.active), aflag, livetime, livetimepc)) # apply vetoes from veto-definer file try: vetofile = cp.get('segments', 'veto-definer-file') except configparser.NoOptionError: vetofile = None else: try: categories = cp.getfloats('segments', 'veto-definer-categories') except configparser.NoOptionError: categories = None # read file vdf = read_veto_definer_file(vetofile, start=start, end=end, ifo=ifo) logger.debug("Read veto-definer file from %s" % vetofile) # get vetoes from segdb vdf.populate(source=url, segments=analysis.active, on_error='warn') # coalesce flags from chosen categories vetoes = DataQualityFlag('%s:VDF-VETOES:1' % ifo) nflags = 0 for flag in vdf: if not categories or vdf[flag].category in categories: vetoes += vdf[flag] nflags += 1 try: deadtime = int(abs(vetoes.active)) / int(abs(vetoes.known)) * 100 except ZeroDivisionError: deadtime = 0 logger.debug("Coalesced %ss (%.2f%%) of deadtime from %d veto flags" % (abs(vetoes.active), deadtime, nflags)) # apply to analysis segments analysis -= vetoes logger.debug("Applied vetoes from veto-definer file") livetime = int(abs(analysis.active)) livetimepc = livetime / duration * 100. logger.info("%ss (%.2f%%) livetime remaining after vetoes" % (livetime, livetimepc)) snrs = cp.getfloats('hveto', 'snr-thresholds') minsnr = min(snrs) windows = cp.getfloats('hveto', 'time-windows') # record all segments segments = DataQualityDict() segments[analysis.name] = analysis # -- load channels ------------------------------------------------------------ # get primary channel name pchannel = cp.get('primary', 'channel') # read auxiliary cache if args.auxiliary_cache is not None: acache = read_cache(args.auxiliary_cache) else: acache = None # load auxiliary channels auxetg = cp.get('auxiliary', 'trigger-generator') auxfreq = cp.getfloats('auxiliary', 'frequency-range') try: auxchannels = cp.get('auxiliary', 'channels').strip('\n').split('\n') except config.configparser.NoOptionError: auxchannels = find_auxiliary_channels(auxetg, (start, end), ifo=ifo, cache=acache) cp.set('auxiliary', 'channels', '\n'.join(auxchannels)) logger.debug("Auto-discovered %d auxiliary channels" % len(auxchannels)) else: auxchannels = sorted(set(auxchannels)) logger.debug("Read list of %d auxiliary channels" % len(auxchannels)) # load unsafe channels list _unsafe = cp.get('safety', 'unsafe-channels') if os.path.isfile(_unsafe): # from file unsafe = set() with open(_unsafe, 'rb') as f: for c in f.read().rstrip('\n').split('\n'): if c.startswith('%(IFO)s'): unsafe.add(c.replace('%(IFO)s', ifo)) elif not c.startswith('%s:' % ifo): unsafe.add('%s:%s' % (ifo, c)) else: unsafe.add(c) else: # or from line-seprated list unsafe = set(_unsafe.strip('\n').split('\n')) unsafe.add(pchannel) cp.set('safety', 'unsafe-channels', '\n'.join(sorted(unsafe))) logger.debug("Read list of %d unsafe channels" % len(unsafe)) # remove unsafe channels nunsafe = 0 for i in range(len(auxchannels) - 1, -1, -1): if auxchannels[i] in unsafe: logger.warning("Auxiliary channel %r identified as unsafe and has " "been removed" % auxchannels[i]) auxchannels.pop(i) nunsafe += 1 logger.debug("%d auxiliary channels identified as unsafe" % nunsafe) naux = len(auxchannels) logger.info("Identified %d auxiliary channels to process" % naux) # record INI file in output HTML directory inifile = '%s-HVETO_CONFIGURATION-%d-%d.ini' % (ifo, start, duration) if os.path.isfile(inifile) and any( os.path.samefile(inifile, x) for x in args.config_file): logger.debug("Cannot write INI file to %s, file was given as input") else: with open(inifile, 'w') as f: cp.write(f) logger.info("Configuration recorded as %s" % inifile) htmlv['config'] = inifile # -- load primary triggers ---------------------------------------------------- # read primary cache if args.primary_cache is not None: pcache = read_cache(args.primary_cache) else: pcache = None # load primary triggers petg = cp.get('primary', 'trigger-generator') psnr = cp.getfloat('primary', 'snr-threshold') pfreq = cp.getfloats('primary', 'frequency-range') preadkw = cp.getparams('primary', 'read-') #if pcache is not None: # auto-detect the file format # original line, but not None, we don't have auto-detection if pcache is None: # auto-detect the file format logger.debug('Unsetting the primary trigger file format') preadkw['format'] = None preadkw['path'] = 'triggers' # original line, YMKIM, 2020.05.06 ptrigfindkw = cp.getparams('primary', 'trigfind-') primary = get_triggers(pchannel, petg, analysis.active, snr=psnr, frange=pfreq, cache=pcache, nproc=args.nproc, trigfind_kwargs=ptrigfindkw, **preadkw) fcol, scol = primary.dtype.names[1:3] if len(primary): logger.info("Read %d events for %s" % (len(primary), pchannel)) else: message = "No events found for %r in %d seconds of livetime" % ( pchannel, livetime) logger.critical(message) # cluster primary triggers clusterkwargs = cp.getparams('primary', 'cluster-') if clusterkwargs: primary = primary.cluster(**clusterkwargs) logger.info("%d primary events remain after clustering over %s" % (len(primary), clusterkwargs['rank'])) # -- bail out early ----------------------------------------------------------- # the bail out is done here so that we can at least generate the eventual # configuration file, mainly for HTML purposes # no segments if livetime == 0: message = ("No active segments found for analysis flag %r in interval " "[%d, %d)" % (aflag, start, end)) logger.critical(message) htmlv['context'] = 'info' index = html.write_null_page(ifo, start, end, message, **htmlv) logger.info("HTML report written to %s" % index) sys.exit(0) # no primary triggers if len(primary) == 0: htmlv['context'] = 'danger' index = html.write_null_page(ifo, start, end, message, **htmlv) logger.info("HTML report written to %s" % index) sys.exit(0) # otherwise write all primary triggers to ASCII trigfile = os.path.join( trigdir, '%s-HVETO_RAW_TRIGS_ROUND_0-%d-%d.txt' % (ifo, start, duration)) primary.write(trigfile, format='ascii', overwrite=True) # -- load auxiliary triggers -------------------------------------------------- logger.info("Reading triggers for aux channels...") counter = multiprocessing.Value('i', 0) areadkw = cp.getparams('auxiliary', 'read-') #if acache is not None: # auto-detect the file format # original line, but not None, we don't have auto-detection if acache is None: # auto-detect the file format logger.debug('Unsetting the auxiliary trigger file format') areadkw['format'] = None areadkw['path'] = 'triggers' atrigfindkw = cp.getparams('auxiliary', 'trigfind-') def _get_aux_triggers(channel): if acache is None: auxcache = None else: ifo, name = channel.split(':') match = "{}-{}".format(ifo, name.replace('-', '_')) auxcache = [e for e in acache if Path(e).name.startswith(match)] # get triggers try: trigs = get_triggers(channel, auxetg, analysis.active, snr=minsnr, frange=auxfreq, cache=auxcache, nproc=1, trigfind_kwargs=atrigfindkw, **areadkw) # catch error and continue except ValueError as e: warnings.warn('%s: %s' % (type(e).__name__, str(e))) out = None else: out = (channel, trigs) # log result of load with counter.get_lock(): counter.value += 1 tag = '[%d/%d]' % (counter.value, naux) if out is None: # something went wrong logger.critical(" %s Failed to read events for %s" % (tag, channel)) elif len(trigs): # no triggers logger.debug(" %s Read %d events for %s" % (tag, len(trigs), channel)) else: # everything is fine logger.warning(" %s No events found for %s" % (tag, channel)) return out # map with multiprocessing if args.nproc > 1: pool = multiprocessing.Pool(processes=args.nproc) results = pool.map(_get_aux_triggers, auxchannels) pool.close() # map without multiprocessing else: results = map(_get_aux_triggers, auxchannels) logger.info("All aux events loaded") auxiliary = dict(x for x in results if x is not None) auxchannels = sorted(auxiliary.keys()) chanfile = '%s-HVETO_CHANNEL_LIST-%d-%d.txt' % (ifo, start, duration) with open(chanfile, 'w') as f: for chan in auxchannels: print(chan, file=f) logger.info("Recorded list of valid auxiliary channels in %s" % chanfile) # -- execute hveto analysis --------------------------------------------------- minsig = cp.getfloat('hveto', 'minimum-significance') significance = 1e9 pevents = [primary] pvetoed = [] auxfcol, auxscol = auxiliary[auxchannels[0]].dtype.names[1:3] slabel = get_column_label(scol) flabel = get_column_label(fcol) auxslabel = get_column_label(auxscol) auxflabel = get_column_label(auxfcol) rounds = [] round = core.HvetoRound(1, pchannel, rank=scol) round.segments = analysis.active while True: logger.info("-- Processing round %d --" % round.n) # write segments for this round segfile = os.path.join( segdir, '%s-HVETO_ANALYSIS_SEGS_ROUND_%d-%d-%d.txt' % (ifo, round.n, start, duration)) write_ascii_segments(segfile, round.segments) # calculate significances for this round if args.nproc > 1: # multiprocessing def _find_max_significance(channels): aux = dict((c, auxiliary[c]) for c in channels) return core.find_max_significance(primary, aux, pchannel, snrs, windows, round.livetime) # separate channel list into chunks and process each chunk pool = multiprocessing.Pool( processes=min(args.nproc, len(auxiliary.keys()))) chunks = utils.channel_groups(list(auxiliary.keys()), args.nproc) results = pool.map(_find_max_significance, chunks) pool.close() winners, sigsets = zip(*results) # find winner of chunk winners winner = sorted(winners, key=lambda w: w.significance)[-1] # flatten sets of significances into one list newsignificances = sigsets[0] for subdict in sigsets[1:]: newsignificances.update(subdict) else: # single process winner, newsignificances = core.find_max_significance( primary, auxiliary, pchannel, snrs, windows, round.livetime) logger.info("Round %d winner: %s" % (round.n, winner.name)) # plot significance drop here for the last round # only now do we actually have the new data to calculate significance # drop if round.n > 1: svg = (pngname % 'SIG_DROP').replace('.png', '.svg') # noqa: F821 plot.significance_drop( svg, oldsignificances, newsignificances, # noqa: F821 title=' | '.join([title, subtitle]), # noqa: F821 bbox_inches='tight') logger.debug("Figure written to %s" % svg) svg = FancyPlot(svg, caption=ROUND_CAPTION['SIG_DROP']) rounds[-1].plots.append(svg) oldsignificances = newsignificances # break out of the loop if the significance is below the stopping point if winner.significance < minsig: logger.info("Maximum signifiance below stopping point") logger.debug(" (%.2f < %.2f)" % (winner.significance, minsig)) logger.info("-- Rounds complete! --") break # work out the vetoes for this round allaux = auxiliary[winner.name][ auxiliary[winner.name][auxscol] >= winner.snr] winner.events = allaux coincs = allaux[core.find_coincidences(allaux['time'], primary['time'], dt=winner.window)] round.vetoes = winner.get_segments(allaux['time']) flag = DataQualityFlag( '%s:HVT-ROUND_%d:1' % (ifo, round.n), active=round.vetoes, known=round.segments, description="winner=%s, window=%s, snr=%s" % ( winner.name, winner.window, winner.snr)) segments[flag.name] = flag logger.debug("Generated veto segments for round %d" % round.n) # link events before veto for plotting before = primary beforeaux = auxiliary[winner.name] # apply vetoes to primary primary, vetoed = core.veto(primary, round.vetoes) pevents.append(primary) pvetoed.append(vetoed) logger.debug("Applied vetoes to primary") # record results round.winner = winner round.efficiency = (len(vetoed), len(primary) + len(vetoed)) round.use_percentage = (len(coincs), len(winner.events)) if round.n > 1: round.cum_efficiency = ( len(vetoed) + rounds[-1].cum_efficiency[0], rounds[0].efficiency[1]) round.cum_deadtime = ( round.deadtime[0] + rounds[-1].cum_deadtime[0], livetime) else: round.cum_efficiency = round.efficiency round.cum_deadtime = round.deadtime # apply vetoes to auxiliary if args.nproc > 1: # multiprocess def _veto(channels): return core.veto_all(dict((c, auxiliary[c]) for c in channels), round.vetoes) # separate channel list into chunks and process each chunk pool = multiprocessing.Pool( processes=min(args.nproc, len(auxiliary.keys()))) chunks = utils.channel_groups(list(auxiliary.keys()), args.nproc) results = pool.map(_veto, chunks) pool.close() auxiliary = results[0] for subdict in results[1:]: auxiliary.update(subdict) else: # single process auxiliary = core.veto_all(auxiliary, round.vetoes) logger.debug("Applied vetoes to auxiliary channels") # log results logger.info("""Results for round %d:\n\n winner : %s significance : %s mu : %s snr : %s dt : %s use_percentage : %s efficiency : %s deadtime : %s cum. efficiency : %s cum. deadtime : %s\n\n""" % ( round.n, round.winner.name, round.winner.significance, round.winner.mu, round.winner.snr, round.winner.window, round.use_percentage, round.efficiency, round.deadtime, round.cum_efficiency, round.cum_deadtime)) # write segments segfile = os.path.join(segdir, '%s-HVETO_VETO_SEGS_ROUND_%d-%d-%d.txt' % ( ifo, round.n, start, duration)) write_ascii_segments(segfile, round.vetoes) logger.debug("Round %d vetoes written to %s" % (round.n, segfile)) round.files['VETO_SEGS'] = (segfile,) # write triggers trigfile = os.path.join(trigdir, '%s-HVETO_%%s_TRIGS_ROUND_%d-%d-%d.txt' % (ifo, round.n, start, duration)) for tag, arr in zip( ['WINNER', 'VETOED', 'RAW'], [winner.events, vetoed, primary]): f = trigfile % tag arr.write(f, format='ascii', overwrite=True) logger.debug("Round %d %s events written to %s" % (round.n, tag.lower(), f)) round.files[tag] = f # record times to omega scan if args.omega_scans: N = len(vetoed) ind = random.sample(range(0, N), min(args.omega_scans, N)) round.scans = vetoed[ind] logger.debug("Collected %d events to omega scan:\n\n%s\n\n" % (len(round.scans), round.scans)) # -- make some plots -- pngname = os.path.join(plotdir, '%s-HVETO_%%s_ROUND_%d-%d-%d.png' % ( ifo, round.n, start, duration)) wname = texify(round.winner.name) beforel = 'Before\n[%d]' % len(before) afterl = 'After\n[%d]' % len(primary) vetoedl = 'Vetoed\n(primary)\n[%d]' % len(vetoed) beforeauxl = 'All\n[%d]' % len(beforeaux) usedl = 'Used\n(aux)\n[%d]' % len(winner.events) coincl = 'Coinc.\n[%d]' % len(coincs) title = '%s Hveto round %d' % (ifo, round.n) ptitle = '%s: primary impact' % title atitle = '%s: auxiliary use' % title subtitle = 'winner: %s [%d-%d]' % (wname, start, end) # before/after histogram png = pngname % 'HISTOGRAM' plot.before_after_histogram( png, before[scol], primary[scol], label1=beforel, label2=afterl, xlabel=slabel, title=ptitle, subtitle=subtitle) logger.debug("Figure written to %s" % png) png = FancyPlot(png, caption=ROUND_CAPTION['HISTOGRAM']) round.plots.append(png) # snr versus time png = pngname % 'SNR_TIME' plot.veto_scatter( png, before, vetoed, x='time', y=scol, label1=beforel, label2=vetoedl, epoch=start, xlim=[start, end], ylabel=slabel, title=ptitle, subtitle=subtitle, legend_title="Primary:") logger.debug("Figure written to %s" % png) png = FancyPlot(png, caption=ROUND_CAPTION['SNR_TIME']) round.plots.append(png) # snr versus frequency png = pngname % 'SNR_%s' % fcol.upper() plot.veto_scatter( png, before, vetoed, x=fcol, y=scol, label1=beforel, label2=vetoedl, xlabel=flabel, ylabel=slabel, xlim=pfreq, title=ptitle, subtitle=subtitle, legend_title="Primary:") logger.debug("Figure written to %s" % png) png = FancyPlot(png, caption=ROUND_CAPTION['SNR']) round.plots.append(png) # frequency versus time coloured by SNR png = pngname % '%s_TIME' % fcol.upper() plot.veto_scatter( png, before, vetoed, x='time', y=fcol, color=scol, label1=None, label2=None, ylabel=flabel, clabel=slabel, clim=[3, 100], cmap='YlGnBu', epoch=start, xlim=[start, end], ylim=pfreq, title=ptitle, subtitle=subtitle) logger.debug("Figure written to %s" % png) png = FancyPlot(png, caption=ROUND_CAPTION['TIME']) round.plots.append(png) # aux used versus frequency png = pngname % 'USED_SNR_TIME' plot.veto_scatter( png, winner.events, vetoed, x='time', y=[auxscol, scol], label1=usedl, label2=vetoedl, ylabel=slabel, epoch=start, xlim=[start, end], title=atitle, subtitle=subtitle) logger.debug("Figure written to %s" % png) png = FancyPlot(png, caption=ROUND_CAPTION['USED_SNR_TIME']) round.plots.append(png) # snr versus time png = pngname % 'AUX_SNR_TIME' plot.veto_scatter( png, beforeaux, (winner.events, coincs), x='time', y=auxscol, label1=beforeauxl, label2=(usedl, coincl), epoch=start, xlim=[start, end], ylabel=auxslabel, title=atitle, subtitle=subtitle) logger.debug("Figure written to %s" % png) png = FancyPlot(png, caption=ROUND_CAPTION['AUX_SNR_TIME']) round.plots.append(png) # snr versus frequency png = pngname % 'AUX_SNR_FREQUENCY' plot.veto_scatter( png, beforeaux, (winner.events, coincs), x=auxfcol, y=auxscol, label1=beforeauxl, label2=(usedl, coincl), xlabel=auxflabel, ylabel=auxslabel, title=atitle, subtitle=subtitle, legend_title="Aux:") logger.debug("Figure written to %s" % png) png = FancyPlot(png, caption=ROUND_CAPTION['AUX_SNR_FREQUENCY']) round.plots.append(png) # frequency versus time coloured by SNR png = pngname % 'AUX_FREQUENCY_TIME' plot.veto_scatter( png, beforeaux, (winner.events, coincs), x='time', y=auxfcol, color=auxscol, label1=None, label2=[None, None], ylabel=auxflabel, clabel=auxslabel, clim=[3, 100], cmap='YlGnBu', epoch=start, xlim=[start, end], title=atitle, subtitle=subtitle) logger.debug("Figure written to %s" % png) png = FancyPlot(png, caption=ROUND_CAPTION['AUX_FREQUENCY_TIME']) round.plots.append(png) # move to the next round rounds.append(round) round = core.HvetoRound(round.n + 1, pchannel, rank=scol, segments=round.segments-round.vetoes) # write file with all segments segfile = os.path.join( segdir, '%s-HVETO_SEGMENTS-%d-%d.h5' % (ifo, start, duration)) segments.write(segfile, overwrite=True) logger.debug("Segment summary written to %s" % segfile) logger.debug("Making summary figures...") # -- exit early if no rounds above threshold if not rounds: message = ("No rounds completed above threshold. Analysis stopped with " "%s achieving significance of %.2f" % (winner.name, winner.significance)) logger.critical(message) message = message.replace( winner.name, cis_link(winner.name, class_='alert-link')) message += '
[Twin: %ss, SNR: %s]' % ( winner.window, winner.snr) htmlv['context'] = 'warning' index = html.write_null_page(ifo, start, end, message, **htmlv) logger.info("HTML report written to %s" % index) sys.exit(0) # -- plot all rounds impact pngname = os.path.join(plotdir, '%s-HVETO_%%s_ALL_ROUNDS-%d-%d.png' % ( ifo, start, duration)) plots = [] title = '%s Hveto all rounds' % args.ifo subtitle = '%d rounds | %d-%d' % (len(rounds), start, end) # before/after histogram png = pngname % 'HISTOGRAM' beforel = 'Before analysis [%d events]' % len(pevents[0]) afterl = 'After %d rounds [%d]' % (len(pevents) - 1, len(pevents[-1])) plot.before_after_histogram( png, pevents[0][scol], pevents[-1][scol], label1=beforel, label2=afterl, xlabel=slabel, title=title, subtitle=subtitle) png = FancyPlot(png, caption=HEADER_CAPTION['HISTOGRAM']) plots.append(png) logger.debug("Figure written to %s" % png) # efficiency/deadtime curve png = pngname % 'ROC' plot.hveto_roc(png, rounds, title=title, subtitle=subtitle) png = FancyPlot(png, caption=HEADER_CAPTION['ROC']) plots.append(png) logger.debug("Figure written to %s" % png) # frequency versus time png = pngname % '%s_TIME' % fcol.upper() labels = [str(r.n) for r in rounds] legtitle = 'Vetoed at\nround' plot.veto_scatter( png, pevents[0], pvetoed, label1='', label2=labels, title=title, subtitle=subtitle, ylabel=flabel, x='time', y=fcol, epoch=start, xlim=[start, end], legend_title=legtitle) png = FancyPlot(png, caption=HEADER_CAPTION['TIME']) plots.append(png) logger.debug("Figure written to %s" % png) # snr versus time png = pngname % 'SNR_TIME' plot.veto_scatter( png, pevents[0], pvetoed, label1='', label2=labels, title=title, subtitle=subtitle, ylabel=slabel, x='time', y=scol, epoch=start, xlim=[start, end], legend_title=legtitle) png = FancyPlot(png, caption=HEADER_CAPTION['SNR_TIME']) plots.append(png) logger.debug("Figure written to %s" % png) # -- write summary states to ASCII table and JSON json_ = { 'user': getuser(), 'host': getfqdn(), 'date': str(datetime.datetime.now()), 'configuration': inifile, 'ifo': ifo, 'gpsstart': start, 'gpsend': end, 'call': ' '.join(sys.argv), 'rounds': [], } with open('summary-stats.txt', 'w') as f: # print header print('#N winner window SNR significance nveto use-percentage efficiency ' 'deadtime cumulative-efficiency cumulative-deadtime', file=f) for r in rounds: # extract relevant statistics results = [ ('round', r.n), ('name', r.winner.name), ('window', r.winner.window), ('snr', r.winner.snr), ('significance', r.winner.significance), ('nveto', r.efficiency[0]), ('use-percentage', r.use_percentage[0] / r.use_percentage[1] * 100.), ('efficiency', r.efficiency[0] / r.efficiency[1] * 100.), ('deadtime', r.deadtime[0] / r.deadtime[1] * 100.), ('cumulative-efficiency', r.cum_efficiency[0] / r.cum_efficiency[1] * 100.), ('cumulative-deadtime', r.cum_deadtime[0] / r.cum_deadtime[1] * 100.), ] # write to ASCII print(' '.join(map(str, list(zip(*results))[1])), file=f) # write to JSON results.append(('files', r.files)) json_['rounds'].append(dict(results)) logger.debug("Summary table written to %s" % f.name) with open('summary-stats.json', 'w') as f: json.dump(json_, f, sort_keys=True) logger.debug("Summary JSON written to %s" % f.name) # -- generate workflow for omega scans if args.omega_scans: omegatimes = list(map(str, sorted(numpy.unique( [t['time'] for r in rounds for t in r.scans])))) logger.debug("Collected %d times to omega scan" % len(omegatimes)) newtimes = [t for t in omegatimes if not os.path.exists(os.path.join(omegadir, str(t)))] logger.debug("%d scans already complete or in progress, %d remaining" % (len(omegatimes) - len(newtimes), len(newtimes))) if len(newtimes) > 0: logger.info('Creating workflow for omega scans') flags = batch.get_command_line_flags( ifo=ifo, ignore_state_flags=True) condorcmds = batch.get_condor_arguments( timeout=4, gps=start) dagman = batch.generate_dag( newtimes, flags=flags, submit=True, outdir=omegadir, condor_commands=condorcmds) logger.info('Launched {} omega scans to condor'.format(len(newtimes))) else: logger.debug('Skipping omega scans') # -- write HTML and finish index = html.write_hveto_page( ifo, start, end, rounds, plots, winners=[r.winner.name for r in rounds], **htmlv) logger.debug("HTML written to %s" % index) logger.debug("Analysis completed in %d seconds" % (time.time() - jobstart)) logger.info("-- Hveto complete --")