diff --git a/gstlal-inspiral/python/streamthinca.py b/gstlal-inspiral/python/streamthinca.py index f09cfbb09493a646f2ee9cad0a171ec3135640e9..842f5a0634c849fcb61809f8a1c559ee89bb6c45 100644 --- a/gstlal-inspiral/python/streamthinca.py +++ b/gstlal-inspiral/python/streamthinca.py @@ -234,6 +234,28 @@ def lower_bound_in_seglist(seglist, x): return i - 1 if i else 0 +class backgroundcollector(object): + def __init__(self): + self.zerolag_singles = set() + self.timeshifted_coincs = set() + + def push(self, event_ids, offset_vector): + if any(offset_vector.values()): + if len(event_ids) > 1: + self.timeshifted_coincs.update(event_ids) + elif len(event_ids) == 1: + self.zerolag_singles.update(event_ids) + + def pull(self, two_or_more_instruments, flushed_events): + index = dict((id(event), event) for event in flushed_events) + flushed_ids = set(index) + background_ds = self.timeshifted_coincs & flushed_ids + self.timeshifted_coincs -= flushed_ids + background_ds |= set(event_id for event_id in self.zerolag_singles & flushed_ids if index[event_id].end in two_or_more_instruments) + self.zerolag_singles -= flushed_ids + return map(index.__getitem__, background_ds) + + class StreamThinca(object): def __init__(self, xmldoc, process_id, delta_t, min_instruments = 2, sngls_snr_threshold = None): self.ln_lr_from_triggers = None @@ -254,6 +276,7 @@ class StreamThinca(object): self.delta_t, min_instruments = self.min_instruments ) + self.backgroundcollector = backgroundcollector() def push(self, instrument, events, t_complete): @@ -286,18 +309,6 @@ class StreamThinca(object): age = float(self.time_slide_graph.age) snr_segments = segments.segmentlistdict((instrument, ratebinlist[lower_bound_in_seglist(ratebinlist, age):].segmentlist()) for instrument, ratebinlist in rankingstat.denominator.triggerrates.items()) - # times when at least 2 instruments were generating SNR. - # used to sieve triggers for inclusion in the denominator. - - two_or_more_instruments = segmentsUtils.vote(snr_segments.values(), 2) - # FIXME: this is needed to work around rounding problems - # in safety checks below, trying to compare GPS trigger - # times to float segment boundaries (the boundaries don't - # have enough precision to know if triggers near the edge - # are in or out). it would be better not to have to screw - # around like this. - two_or_more_instruments.protract(1e-3) # 1 ms - # # iterate over coincidences # @@ -307,14 +318,13 @@ class StreamThinca(object): flushed = [] flushed_unused = [] self.last_coincs.clear() - for node, events in self.time_slide_graph.pull(self.delta_t, newly_reported = newly_reported, flushed = flushed, flushed_unused = flushed_unused, coinc_sieve = coinc_sieve, flush = flush): - # construct row objects for coinc tables. latency - # goes in minimum_duration column. NOTE: latency - # is nonsense unless running live. - # FIXME: add a proper column for latency + for node, events in self.time_slide_graph.pull(self.delta_t, newly_reported = newly_reported, flushed = flushed, flushed_unused = flushed_unused, coinc_sieve = coinc_sieve, event_collector = self.backgroundcollector, flush = flush): + # construct row objects for coinc tables. coinc, coincmaps, coinc_inspiral = self.coinc_tables.coinc_rows(self.process_id, node.time_slide_id, events, seglists = snr_segments) - coinc_inspiral.minimum_duration = gps_time_now - float(coinc_inspiral.end) + + # assign ranking statistic, FAP and FAR + if self.ln_lr_from_triggers is not None: coinc.likelihood = self.ln_lr_from_triggers(events, node.offset_vector) if fapfar is not None: @@ -323,25 +333,9 @@ class StreamThinca(object): coinc_inspiral.combined_far = fapfar.far_from_rank(coinc.likelihood) coinc_inspiral.false_alarm_rate = fapfar.fap_from_rank(coinc.likelihood) - # cluster candidates - # FIXME: implement clustering - - # ---> - #if self.stream_thinca.last_coincs: - # self.stream_thinca.last_coincs.coinc_event_index = dict([max(self.stream_thinca.last_coincs.coinc_event_index.items(), key = lambda (coinc_event_id, coinc_event): coinc_event.likelihood)]) - # <--- - # some tasks for zero-lag candidates if node.is_zero_lag: - # single-detector candidates collected from - # zero-lag time slides during times when - # two or more instruments were producing - # SNR are added to the noise model - - if len(events) == 1 and events[0].end in two_or_more_instruments: - rankingstat.denominator.increment(events[0]) - # populate ranking statistic's zero-lag # PDFs with triggers from all zero-lag # candidates @@ -349,21 +343,41 @@ class StreamThinca(object): for event in events: rankingstat.zerolag.increment(event) - # Add events to the observed likelihood - # histogram post "clustering" + # add events to the zero-lag ranking + # statistic histogram if zerolag_rankingstatpdf is not None and coinc.likelihood is not None: zerolag_rankingstatpdf.zero_lag_lr_lnpdf.count[coinc.likelihood,] += 1 + # latency goes in minimum_duration column. NOTE: + # latency is nonsense unless running live. FIXME: + # add a proper column for latency + + coinc_inspiral.minimum_duration = gps_time_now - float(coinc_inspiral.end) + # finally, append coinc to tables + self.coinc_tables.append_coinc(coinc, coincmaps, coinc_inspiral) self.last_coincs.add(events, coinc, coincmaps, coinc_inspiral) - # add singles that were not used for any candidates to the - # noise model + # add selected singles to the noise model + + if flushed: + # times when at least 2 instruments were generating + # SNR. used to select zero-lag singles for + # inclusion in the denominator. + + two_or_more_instruments = segmentsUtils.vote(snr_segments.values(), 2) + # FIXME: this is needed to work around rounding + # problems in safety checks below, trying to + # compare GPS trigger times to float segment + # boundaries (the boundaries don't have enough + # precision to know if triggers near the edge are + # in or out). it would be better not to have to + # screw around like this. + two_or_more_instruments.protract(1e-3) # 1 ms - for event in flushed_unused: - if event.end in two_or_more_instruments: + for event in self.backgroundcollector.pull(two_or_more_instruments, flushed): rankingstat.denominator.increment(event) # add any triggers that have been used in coincidences for