diff --git a/gstlal-inspiral/bin/gstlal_inspiral b/gstlal-inspiral/bin/gstlal_inspiral index 29ef3ad39bfee2884f87620fbc5d37d4b990ee92..7c70c1b8f169ebb96ad6befedbb61255d92ad7b8 100755 --- a/gstlal-inspiral/bin/gstlal_inspiral +++ b/gstlal-inspiral/bin/gstlal_inspiral @@ -684,7 +684,7 @@ for output_file_number, (svd_bank, output_filename, likelihood_namedtuple, zero_ if coinc_params_distributions is None: raise ValueError("\"%s\" does not contain parameter distribution data" % likelihood_namedtuple[0] if likelihood_namedtuple[0] is not None else likelihood_namedtuple[1]) else: - coinc_params_distributions, seglists = far.ThincaCoincParamsDistributions(), segments.segmentlistdict((instrument, segments.segmentlist()) for instrument in detectors.channel_dict) + coinc_params_distributions, seglists = far.ThincaCoincParamsDistributions(instruments = set(detectors.channel_dict)), segments.segmentlistdict((instrument, segments.segmentlist()) for instrument in detectors.channel_dict) if zero_lag_ranking_stats_file is None: zero_lag_ranking_stats = None @@ -692,6 +692,8 @@ for output_file_number, (svd_bank, output_filename, likelihood_namedtuple, zero_ _, zero_lag_ranking_stats, _ = far.parse_likelihood_control_doc(ligolw_utils.load_filename(zero_lag_ranking_stats_file, verbose = options.verbose, contenthandler = far.ThincaCoincParamsDistributions.LIGOLWContentHandler)) if zero_lag_ranking_stats is None: raise ValueError("\"%s\" does not contain ranking statistic PDF data" % zero_lag_ranking_stat_file) + if zero_lag_ranking_stats.instruments != set(detectors.channel_dict): + raise ValueError("\"%s\" is for the instruments %s, we need %s" % (", ".join(sorted(zero_lag_ranking_stats.instruments)), ", ".join(sorted(detectors.channel_dict)))) # @@ -705,7 +707,6 @@ for output_file_number, (svd_bank, output_filename, likelihood_namedtuple, zero_ filename = output_filename or "%s-%s_LLOID-%d-%d.xml.gz" % (lsctables.ifos_from_instrument_set(detectors.channel_dict.keys()).replace(",", ""), options.job_tag, int(detectors.seg[0]), int(abs(detectors.seg))), process_params = process_params, pipeline = pipeline, - instruments = set(detectors.channel_dict), seg = detectors.seg or segments.segment(LIGOTimeGPS(0), LIGOTimeGPS(2000000000)), # online data doesn't have a segment so make it all possible time coincidence_threshold = options.coincidence_threshold, coinc_params_distributions = coinc_params_distributions, diff --git a/gstlal-inspiral/bin/gstlal_inspiral_calc_likelihood b/gstlal-inspiral/bin/gstlal_inspiral_calc_likelihood index 264ad8c70954f0be2a874fcc20ce7021a01e0b4f..a2ce0db137abf16fef852b5b087b158feacb98bb 100755 --- a/gstlal-inspiral/bin/gstlal_inspiral_calc_likelihood +++ b/gstlal-inspiral/bin/gstlal_inspiral_calc_likelihood @@ -171,7 +171,7 @@ ln_likelihood_ratio_func = snglcoinc.LnLikelihoodRatio(coincparamsdistributions) if options.write_rates is not None: - ranking_data = far.RankingData(None, seglists.keys()) + ranking_data = far.RankingData(None, instruments = seglists.keys()) else: ranking_data = None diff --git a/gstlal-inspiral/bin/gstlal_inspiral_calc_rank_pdfs b/gstlal-inspiral/bin/gstlal_inspiral_calc_rank_pdfs index a4224e1a71aa904f349b7b0efb470429ac133b3a..225afa2dc68ff45042d061c69bc8675118457e94 100755 --- a/gstlal-inspiral/bin/gstlal_inspiral_calc_rank_pdfs +++ b/gstlal-inspiral/bin/gstlal_inspiral_calc_rank_pdfs @@ -140,7 +140,7 @@ coincparamsdistributions.finish(verbose = options.verbose) # -ranking_data = far.RankingData(coincparamsdistributions, instruments = seglists.keys(), sampler_coinc_params_distributions = sampler_coincparamsdistributions, nsamples = options.ranking_stat_samples, verbose = options.verbose) +ranking_data = far.RankingData(coincparamsdistributions, sampler_coinc_params_distributions = sampler_coincparamsdistributions, nsamples = options.ranking_stat_samples, verbose = options.verbose) # diff --git a/gstlal-inspiral/bin/gstlal_inspiral_create_prior_diststats b/gstlal-inspiral/bin/gstlal_inspiral_create_prior_diststats index c22f76fbc817a5033a144a6d58637824c2e539b9..5be7c635588d7bcbe95b4a92834b7644f5bc574a 100755 --- a/gstlal-inspiral/bin/gstlal_inspiral_create_prior_diststats +++ b/gstlal-inspiral/bin/gstlal_inspiral_create_prior_diststats @@ -117,7 +117,7 @@ process = ligolw_process.register_to_xmldoc(xmldoc, u"gstlal_inspiral_create_pri # -coincparamsdistributions = far.ThincaCoincParamsDistributions() +coincparamsdistributions = far.ThincaCoincParamsDistributions(instruments = options.instrument) if options.background_prior > 0: coincparamsdistributions.add_background_prior(n = dict((ifo, options.background_prior) for ifo in options.instrument), verbose = options.verbose) diff --git a/gstlal-inspiral/bin/gstlal_ll_inspiral_create_prior_diststats b/gstlal-inspiral/bin/gstlal_ll_inspiral_create_prior_diststats index c7b292cdebab5f7040236f75e5ccd990615c84c2..d2e523c27e0d240f8e772b1d189a7c641a2958e4 100755 --- a/gstlal-inspiral/bin/gstlal_ll_inspiral_create_prior_diststats +++ b/gstlal-inspiral/bin/gstlal_ll_inspiral_create_prior_diststats @@ -133,7 +133,7 @@ n_zerolag = dict(((ifo, float(abs(seg)) * options.num_templates / 50.) for ifo, # Initialize an empty ThincaCoincParamsDistributions class # -diststats = far.ThincaCoincParamsDistributions() +diststats = far.ThincaCoincParamsDistributions(instruments = set(segs)) # # Add background, zero_lag and injection prior distributions in the SNR and chi-squared plane @@ -179,7 +179,7 @@ process = ligolw_process.register_to_xmldoc(xmldoc, u"gstlal_ll_inspiral_create_ # Instantiate a new RankingData class from our distribution stats # -ranking_data = far.RankingData(diststats, instruments = options.instruments, process_id = process.process_id, nsamples = 100000, verbose = options.verbose) +ranking_data = far.RankingData(diststats, process_id = process.process_id, nsamples = 100000, verbose = options.verbose) # # Simulate a measured coincident trigger likelihood histogram by just using the background one with a lower normalized count diff --git a/gstlal-inspiral/python/far.py b/gstlal-inspiral/python/far.py index b49295843ae294d8a6fdae7009b1776f53be9a16..e30f2b908ab9b335e73365d15080bc26c4c62c88 100644 --- a/gstlal-inspiral/python/far.py +++ b/gstlal-inspiral/python/far.py @@ -119,47 +119,62 @@ class ThincaCoincParamsDistributions(snglcoinc.CoincParamsDistributions): # accidental_weight * (measured denominator) numerator_accidental_weight = 0. - # binnings (filter funcs look-up initialized in .__init__() - snr_chi_binning = rate.NDBins((rate.ATanLogarithmicBins(3.6, 70., 600), rate.ATanLogarithmicBins(.001, 0.5, 300))) + # binnings (initialized in .__init__() binnings = { - "instruments": rate.NDBins((snglcoinc.InstrumentBins(),)), - "H1_snr_chi": snr_chi_binning, - "H2_snr_chi": snr_chi_binning, - "H1H2_snr_chi": snr_chi_binning, - "L1_snr_chi": snr_chi_binning, - "V1_snr_chi": snr_chi_binning, - "E1_snr_chi": snr_chi_binning, - "E2_snr_chi": snr_chi_binning, - "E3_snr_chi": snr_chi_binning, - "E0_snr_chi": snr_chi_binning } - del snr_chi_binning - - def __init__(self, *args, **kwargs): - super(ThincaCoincParamsDistributions, self).__init__(*args, **kwargs) - self.horizon_history = horizonhistory.HorizonHistories() - self.pdf_from_rates_func = { - "instruments": self.pdf_from_rates_instruments, - "H1_snr_chi": self.pdf_from_rates_snrchi2, - "H2_snr_chi": self.pdf_from_rates_snrchi2, - "H1H2_snr_chi": self.pdf_from_rates_snrchi2, - "L1_snr_chi": self.pdf_from_rates_snrchi2, - "V1_snr_chi": self.pdf_from_rates_snrchi2, - "E1_snr_chi": self.pdf_from_rates_snrchi2, - "E2_snr_chi": self.pdf_from_rates_snrchi2, - "E3_snr_chi": self.pdf_from_rates_snrchi2, - "E0_snr_chi": self.pdf_from_rates_snrchi2, - } + + def __init__(self, instruments = frozenset(("H1", "L1", "V1")), min_instruments = 2, **kwargs): + # + # check input + # + + if min_instruments < 1: + raise ValueError("min_instruments=%d must be >= 1" % min_instruments) + if min_instruments > len(instruments): + raise ValueError("not enough instruments (%s) to satisfy min_instruments=%d" % (", ".join(sorted(instruments)), min_instruments)) + + # in the parent class this is a class attribute, but we use + # it as an instance attribute here + self.binnings = dict.fromkeys(("%s_snr_chi" % instrument for instrument in instruments), rate.NDBins((rate.ATanLogarithmicBins(3.6, 70., 600), rate.ATanLogarithmicBins(.001, 0.5, 300)))) + self.binnings.update({ + "instruments": rate.NDBins((snglcoinc.InstrumentBins(instruments),)) + }) + + self.pdf_from_rates_func = dict.fromkeys(("%s_snr_chi" % instrument for instrument in instruments), self.pdf_from_rates_snrchi2) + self.pdf_from_rates_func.update({ + "instruments": self.pdf_from_rates_instruments + }) + + # this can't be done until the binnings attribute has been + # populated + super(ThincaCoincParamsDistributions, self).__init__(**kwargs) + + # record of horizon distances for all instruments in the + # network + self.horizon_history = horizonhistory.HorizonHistories((instrument, horizonhistory.NearestLeafTree()) for instrument in instruments) + + # the minimum number of instruments required to form a + # candidate + self.min_instruments = min_instruments + assert min_instruments == 2 # FIXME: generalize # set to True to include zero-lag histograms in background model self.zero_lag_in_background = False + @property + def instruments(self): + return frozenset(self.horizon_history) + def __iadd__(self, other): # NOTE: because we use custom PDF constructions, the stock # .__iadd__() method for this class will not result in # valid PDFs. the rates arrays *are* handled correctly by # the .__iadd__() method, by fiat, so just remember to # invoke .finish() to get the PDFs in shape afterwards + if self.instruments != other.instruments: + raise ValueError("incompatible instrument sets") + if self.min_instruments != other.min_instruments: + raise ValueError("incompatible minimum number of instruments") super(ThincaCoincParamsDistributions, self).__iadd__(other) self.horizon_history += other.horizon_history return self @@ -173,14 +188,10 @@ class ThincaCoincParamsDistributions(snglcoinc.CoincParamsDistributions): # # - # 2D (snr, \chi^2) values. don't allow both H1 and H2 to - # both contribute parameters to the same coinc. if both - # have participated favour H1 + # 2D (snr, \chi^2) values. # params = CoincParams(("%s_snr_chi" % event.ifo, (event.snr, event.chisq / event.snr**2)) for event in events) - if "H2_snr_chi" in params and "H1_snr_chi" in params: - del params["H2_snr_chi"] # # instrument combination @@ -246,6 +257,8 @@ class ThincaCoincParamsDistributions(snglcoinc.CoincParamsDistributions): print >>sys.stderr, "synthesizing signal-like (SNR, \\chi^2) distributions ..." if df <= 0.: raise ValueError("require df >= 0: %s" % repr(df)) + if set(n) != self.instruments: + raise ValueError("n must provide a count for exactly each of %s" % ", ".join(sorted(self.instruments))) pfs = numpy.logspace(numpy.log10(prefactors_range[0]), numpy.log10(prefactors_range[1]), 100) for instrument, number_of_events in n.items(): binarr = rates_dict["%s_snr_chi" % instrument] @@ -296,6 +309,8 @@ class ThincaCoincParamsDistributions(snglcoinc.CoincParamsDistributions): # extrapolated background. # + if set(n) != self.instruments: + raise ValueError("n must provide a count for exactly each of %s" % ", ".join(sorted(self.instruments))) if verbose: print >>sys.stderr, "adding tilt to (SNR, \\chi^2) background PDFs ..." for instrument, number_of_events in n.items(): @@ -353,6 +368,7 @@ class ThincaCoincParamsDistributions(snglcoinc.CoincParamsDistributions): zero_lag_coinc_counts = dict((instruments, self.zero_lag_rates["instruments"][instruments,]) for instruments in self.zero_lag_rates["instruments"].bins[0].centres()), segs = segs, delta_t = 0.005, + min_instruments = self.min_instruments, verbose = verbose ).items(): self.background_rates["instruments"][instruments,] = count @@ -369,7 +385,7 @@ class ThincaCoincParamsDistributions(snglcoinc.CoincParamsDistributions): # instrument combinations. because the horizon distance is # 0'ed when an instrument is off, this marginalization over # horizon distance histories also accounts for duty cycles - P = inspiral_extrinsics.P_instruments_given_signal(self.horizon_history) + P = inspiral_extrinsics.P_instruments_given_signal(self.horizon_history, min_instruments = self.min_instruments) # populate binning from probabilities for instruments, p in P.items(): @@ -490,13 +506,18 @@ class ThincaCoincParamsDistributions(snglcoinc.CoincParamsDistributions): @classmethod def from_xml(cls, xml, name): - self = super(ThincaCoincParamsDistributions, cls).from_xml(xml, name) - xml = self.get_xml_root(xml, name) + xml = cls.get_xml_root(xml, name) + self = super(ThincaCoincParamsDistributions, cls).from_xml(xml, name, + instruments = lsctables.instrument_set_from_ifos(ligolw_param.get_pyvalue(xml, u"instruments")), + min_instruments = ligolw_param.get_pyvalue(xml, u"min_instruments"), + ) self.horizon_history = horizonhistory.HorizonHistories.from_xml(xml, name) return self def to_xml(self, name): xml = super(ThincaCoincParamsDistributions, self).to_xml(name) + xml.appendChild(ligolw_param.from_pyvalue(u"instruments", lsctables.ifos_from_instrument_set(self.instruments))) + xml.appendChild(ligolw_param.from_pyvalue(u"min_instruments", self.min_instruments)) xml.appendChild(self.horizon_history.to_xml(name)) return xml @@ -665,7 +686,7 @@ class ThincaCoincParamsDistributions(snglcoinc.CoincParamsDistributions): continue params[key] = snr, chi2_over_snr2() instruments.append(instrument) - if coinc_only and len(instruments) < 2: + if coinc_only and len(instruments) < self.min_instruments: continue params["instruments"] = (frozenset(instruments),) params.horizons = horizon_distance @@ -736,7 +757,7 @@ class RankingData(object): "ln_likelihood_ratio": rate.gaussian_window(8.) } - def __init__(self, coinc_params_distributions, instruments, sampler_coinc_params_distributions = None, process_id = None, nsamples = 1000000, min_instruments = 2, verbose = False): + def __init__(self, coinc_params_distributions, instruments = None, sampler_coinc_params_distributions = None, process_id = None, nsamples = 1000000, verbose = False): self.background_likelihood_rates = {} self.background_likelihood_pdfs = {} self.signal_likelihood_rates = {} @@ -745,16 +766,6 @@ class RankingData(object): self.zero_lag_likelihood_pdfs = {} self.process_id = process_id - # - # initialize binnings - # - - instruments = tuple(instruments) - for key in [frozenset(ifos) for n in range(min_instruments, len(instruments) + 1) for ifos in iterutils.choices(instruments, n)]: - self.background_likelihood_rates[key] = rate.BinnedArray(self.binnings["ln_likelihood_ratio"]) - self.signal_likelihood_rates[key] = rate.BinnedArray(self.binnings["ln_likelihood_ratio"]) - self.zero_lag_likelihood_rates[key] = rate.BinnedArray(self.binnings["ln_likelihood_ratio"]) - # # bailout out used by .from_xml() class method to get an # uninitialized instance @@ -764,13 +775,22 @@ class RankingData(object): return # - # now check input + # initialize binnings # - if min_instruments < 1: - raise ValueError("min_instruments=%d must be >= 1" % min_instruments) - if min_instruments > len(instruments): - raise ValueError("not enough instruments to satisfy min_instruments") + if coinc_params_distributions is not None: + if instruments is not None and set(instruments) != coinc_params_distributions.instruments: + raise ValueError("instrument set does not match coinc_params_distributions (hint: don't supply instruments when initializing from ThincaCoincParamsDistributions)") + instruments = tuple(coinc_params_distributions.instruments) + elif instruments is None: + raise ValueError("must supply an instrument set when not initializing from a ThincaCoincParamsDistributions object") + else: + instruments = tuple(instruments) + + for key in [frozenset(ifos) for n in range(coinc_params_distributions.min_instruments, len(instruments) + 1) for ifos in iterutils.choices(instruments, n)]: + self.background_likelihood_rates[key] = rate.BinnedArray(self.binnings["ln_likelihood_ratio"]) + self.signal_likelihood_rates[key] = rate.BinnedArray(self.binnings["ln_likelihood_ratio"]) + self.zero_lag_likelihood_rates[key] = rate.BinnedArray(self.binnings["ln_likelihood_ratio"]) # # run importance-weighted random sampling to populate diff --git a/gstlal-inspiral/python/inspiral.py b/gstlal-inspiral/python/inspiral.py index 367834cc6e72f245e1c4427c8676edd5d21c1753..e4fd6bcfbb1612ab0455ab4acb0df590524edc60 100644 --- a/gstlal-inspiral/python/inspiral.py +++ b/gstlal-inspiral/python/inspiral.py @@ -516,7 +516,7 @@ class CoincsDocument(object): class Data(object): - def __init__(self, filename, process_params, pipeline, instruments, seg, coincidence_threshold, coinc_params_distributions, zero_lag_ranking_stats = None, marginalized_likelihood_file = None, likelihood_files_namedtuple = None, injection_filename = None, time_slide_file = None, comment = None, tmp_path = None, likelihood_snapshot_interval = None, thinca_interval = 50.0, sngls_snr_threshold = None, gracedb_far_threshold = None, gracedb_group = "Test", gracedb_search = "LowMass", gracedb_pipeline = "gstlal", gracedb_service_url = "https://gracedb.ligo.org/api/", replace_file = True, upload_auxiliary_data_to_gracedb = True, verbose = False): + def __init__(self, filename, process_params, pipeline, seg, coincidence_threshold, coinc_params_distributions, zero_lag_ranking_stats = None, marginalized_likelihood_file = None, likelihood_files_namedtuple = None, injection_filename = None, time_slide_file = None, comment = None, tmp_path = None, likelihood_snapshot_interval = None, thinca_interval = 50.0, sngls_snr_threshold = None, gracedb_far_threshold = None, gracedb_group = "Test", gracedb_search = "LowMass", gracedb_pipeline = "gstlal", gracedb_service_url = "https://gracedb.ligo.org/api/", replace_file = True, upload_auxiliary_data_to_gracedb = True, verbose = False): # # initialize # @@ -558,7 +558,7 @@ class Data(object): # initialize document to hold coincs and segments # - self.coincs_document = CoincsDocument(filename, process_params, comment, instruments, seg, injection_filename = injection_filename, time_slide_file = time_slide_file, tmp_path = tmp_path, replace_file = replace_file, verbose = verbose) + self.coincs_document = CoincsDocument(filename, process_params, comment, coinc_params_distributions.instruments, seg, injection_filename = injection_filename, time_slide_file = time_slide_file, tmp_path = tmp_path, replace_file = replace_file, verbose = verbose) # # attach a StreamThinca instance to ourselves diff --git a/gstlal-inspiral/python/lloidparts.py b/gstlal-inspiral/python/lloidparts.py index 702c122053c62b7860285b0eb8ea4f3cef861f63..7867c173085b7ab365c4f870146e652d00d1188e 100644 --- a/gstlal-inspiral/python/lloidparts.py +++ b/gstlal-inspiral/python/lloidparts.py @@ -94,7 +94,6 @@ from gstlal import pipeio from gstlal import pipeparts from gstlal import reference_psd from gstlal import simplehandler -from gstlal.stats import horizonhistory import lal from lal import LIGOTimeGPS @@ -340,15 +339,12 @@ class Handler(simplehandler.Handler): """ timestamp can be a float or a slice with float boundaries. """ - # retrieve the horizon history for this instrument or - # initialize it if it doesn't exist yet - try: - horizon_history = self.dataclass.coinc_params_distributions.horizon_history[instrument] - except KeyError: - horizon_history = self.dataclass.coinc_params_distributions.horizon_history[instrument] = horizonhistory.NearestLeafTree() - # NOTE: timestamps are floats here (or float slices). - # whitener should be reporting PSDs with integer - # timestamps, and, anyway, we don't need nanosecond + # retrieve the horizon history for this instrument + horizon_history = self.dataclass.coinc_params_distributions.horizon_history[instrument] + # NOTE: timestamps are floats here (or float slices), not + # LIGOTimeGPS. whitener should be reporting PSDs with + # integer timestamps so the timestamps are not naturally + # high precision, and, anyway, we don't need nanosecond # precision for the horizon distance history. horizon_history[timestamp] = horizon_distance @@ -468,7 +464,7 @@ class Handler(simplehandler.Handler): # set the horizon distance history to 0 at # on-to-off transitions of whitened h(t) if segtype == "whitehtsegments": - if instrument in self.dataclass.coinc_params_distributions.horizon_history and self.dataclass.coinc_params_distributions.horizon_history[instrument]: + if self.dataclass.coinc_params_distributions.horizon_history[instrument]: self._record_horizon_distance(instrument, slice(float(timestamp), None), 0.) else: self._record_horizon_distance(instrument, float(timestamp), 0.)