Loading [MathJax]/jax/input/TeX/config.js
LALBurst 2.0.7.1-b246709
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Macros Modules Pages
burca_tailor.py
Go to the documentation of this file.
1# Copyright (C) 2007--2021 Kipp Cannon
2#
3# This program is free software; you can redistribute it and/or modify it
4# under the terms of the GNU General Public License as published by the
5# Free Software Foundation; either version 2 of the License, or (at your
6# option) any later version.
7#
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11# Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17
18#
19# =============================================================================
20#
21# Preamble
22#
23# =============================================================================
24#
25
26
27import copy
28import itertools
29import math
30import sys
31
32
33import lal
34from lal import rate
35
36
37from igwn_ligolw import ligolw
38from igwn_ligolw import lsctables
39from igwn_ligolw import utils as ligolw_utils
40from igwn_ligolw.utils import process as ligolw_process
41from igwn_ligolw.utils import search_summary as ligolw_search_summary
42from . import snglcoinc
43from .SimBurstUtils import MW_CENTER_J2000_RA_RAD, MW_CENTER_J2000_DEC_RAD
44
45
46__author__ = "Kipp Cannon <kipp.cannon@ligo.org>"
47from .git_version import date as __date__
48from .git_version import version as __version__
49
50
51#
52# =============================================================================
53#
54# Excess Power Specific Parameter Distributions
55#
56# =============================================================================
57#
58
59
61 def __init__(self, instruments):
62 self.densities = {}
63 for pair in intertools.combinations(sorted(instruments), 2):
64 # FIXME: hard-coded for directional search
65 #dt = 0.02 + snglcoinc.light_travel_time(*pair)
66 dt = 0.02
67 self.densities["%s_%s_dt" % pair] = rate.BinnedLnDPF(rate.NDBins((rate.ATanBins(-dt, +dt, 12001), rate.LinearBins(0.0, 2 * math.pi, 61))))
68 self.densities["%s_%s_dband" % pair] = rate.BinnedLnDPF(rate.NDBins((rate.LinearBins(-2.0, +2.0, 12001), rate.LinearBins(0.0, 2 * math.pi, 61))))
69 self.densities["%s_%s_ddur" % pair] = rate.BinnedLnDPF(rate.NDBins((rate.LinearBins(-2.0, +2.0, 12001), rate.LinearBins(0.0, 2 * math.pi, 61))))
70 self.densities["%s_%s_df" % pair] = rate.BinnedLnDPF(rate.NDBins((rate.LinearBins(-2.0, +2.0, 12001), rate.LinearBins(0.0, 2 * math.pi, 61))))
71 self.densities["%s_%s_dh" % pair] = rate.BinnedLnDPF(rate.NDBins((rate.LinearBins(-2.0, +2.0, 12001), rate.LinearBins(0.0, 2 * math.pi, 61))))
72
73 def __call__(self, **params):
74 try:
75 interps = self.interps
76 except AttributeError:
77 self.mkinterps()
78 interps = self.interps
79 return sum(interps[param](value) for param, value in params.items())
80
81 def __iadd__(self, other):
82 if type(self) != type(other) or set(self.densities) != set(other.densities):
83 raise TypeError("cannot add %s and %s" % (type(self), type(other)))
84 for key, pdf in self.densities.items():
85 pdf += other.densities[key]
86 del self.interps
87 return self
88
89 def increment(self, params, weight = 1.0):
90 for param, value in params.items():
91 self.densities[param].count[value] += weight
92
93 def copy(self):
94 new = type(self)([])
95 for key, pdf in self.densities.items():
96 new.densities[key] = pdf.copy()
97 return new
98
99 def mkinterps(self):
100 self.interps = dict((key, pdf.mkinterp()) for key, pdf in self.densities.items())
101
102 def finish(self):
103 for key, pdf in self.densities.items():
104 rate.filter_array(pdf.array, rate.gaussian_window(11, 5))
105 pdf.normalize()
106 self.mkinterps()
107
108 def to_xml(self, name):
109 xml = super(LnLRDensity, self).to_xml(name)
110 instruments = set(key.split("_", 2)[0] for key in self.densities if key.endswith("_dt"))
111 instruments |= set(key.split("_", 2)[1] for key in self.densities if key.endswith("_dt"))
112 xml.appendChild(ligolw.Param.from_pyvalue("instruments", lsctables.instrumentsproperty.set(instruments)))
113 for key, pdf in self.densities.items():
114 xml.appendChild(pdf.to_xml(key))
115 return xml
116
117 @classmethod
118 def from_xml(cls, xml, name):
119 xml = cls.get_xml_root(xml, name)
120 self = cls(lsctables.instrumentsproperty.get(ligolw.Param.get_param(xml, "instruments").value))
121 for key in self.densities:
122 self.densities[key] = rate.BinnedLnPDF.from_xml(xml, key)
123 return self
124
125
127 ligo_lw_name_suffix = "excesspower_coincparamsdistributions"
128
129 def __init__(self, instruments):
130 self.numerator = LnLRDensity(instruments)
131 self.denominator = LnLRDensity(instruments)
132 self.candidates = LnLRDensity(instruments)
133
134 def __iadd__(self, other):
135 if type(self) != type(other):
136 raise TypeError(other)
137 self.numerator += other.numerator
138 self.denominator += other.denominator
139 self.candidates += other.candidates
140 return self
141
142 def copy(self):
143 new = type(self)([])
144 new.numerator = self.numerator.copy()
145 new.denominator = self.denominator.copy()
146 new.candidates = self.candidates.copy()
147 return new
148
149 def finish(self):
150 self.numerator.finish()
151 self.denominator.finish()
152 self.candidates.finish()
153
154 @classmethod
155 def get_xml_root(cls, xml, name):
156 name = "%s:%s" % (name, cls.ligo_lw_name_suffix)
157 xml = [elem for elem in xml.getElementsByTagName(ligolw.LIGO_LW.tagName) if elem.hasAttribute("Name") and elem.Name == name]
158 if len(xml) != 1:
159 raise ValueError("XML tree must contain exactly one %s element named %s" % (ligolw.LIGO_LW.tagName, name))
160 return xml[0]
161
162 @classmethod
163 def from_xml(cls, xml, name):
164 xml = cls.get_xml_root(xml, name)
165 self = cls([])
166 self.numerator = LnLRDensity.from_xml(xml, "numerator")
167 self.denominator = LnLRDensity.from_xml(xml, "denominator")
168 self.candidates = LnLRDensity.from_xml(xml, "candidates")
169
170 def to_xml(self, name):
171 xml = ligolw.LIGO_LW({"Name": "%s:%s" % (name, self.ligo_lw_name_suffix)})
172 xml.appendChild(self.numerator.to_xml("numerator"))
173 xml.appendChild(self.denominator.to_xml("denominator"))
174 xml.appendChild(self.candidates.to_xml("candidates"))
175 return xml
176
177 @classmethod
178 def from_filenames(cls, filenames, name, verbose = False):
179 """
180 Convenience function to deserialize
181 CoincParamsDistributions objects from a collection of XML
182 files and return their sum. The return value is a
183 two-element tuple. The first element is the deserialized
184 and summed CoincParamsDistributions object, the second is a
185 segmentlistdict indicating the interval of time spanned by
186 the out segments in the search_summary rows matching the
187 process IDs that were attached to the
188 CoincParamsDistributions objects in the XML.
189 """
190 self = None
191 for n, filename in enumerate(filenames, 1):
192 if verbose:
193 print("%d/%d:" % (n, len(filenames)), end=' ', file=sys.stderr)
194 xmldoc = ligolw_utils.load_filename(filename, verbose = verbose)
195 if self is None:
196 self = cls.from_xml(xmldoc, name)
197 seglists = lsctables.SearchSummaryTable.get_table(xmldoc).get_out_segmentlistdict(set([self.process_id])).coalesce()
198 else:
199 other = cls.from_xml(xmldoc, name)
200 self += other
201 seglists |= lsctables.SearchSummaryTable.get_table(xmldoc).get_out_segmentlistdict(set([other.process_id])).coalesce()
202 del other
203 xmldoc.unlink()
204 return self, seglists
205
206
207#
208# All sky version
209#
210
211
213 def ln_lr_from_triggers(self, events, offsetvector):
214 #
215 # check for coincs that have been vetoed entirely
216 #
217
218 if len(events) < 2:
219 return None
220
221 params = {}
222
223 # the "time" is the ms_snr squared weighted average of the
224 # peak times neglecting light-travel times. because
225 # LIGOTimeGPS objects have overflow problems in this sort
226 # of a calculation, the first event's peak time is used as
227 # an epoch and the calculations are done w.r.t. that time.
228
229 # FIXME: this time is available as the peak_time in the
230 # multi_burst table, and it should be retrieved from that
231 # table instead of being recomputed
232 events = tuple(events)
233 t = events[0].peak
234 t += sum(float(event.peak - t) * event.ms_snr**2.0 for event in events) / sum(event.ms_snr**2.0 for event in events)
235 gmst = lal.GreenwichMeanSiderealTime(t) % (2 * math.pi)
236
237 for event1, event2 in itertools.combinations(sorted(events, key = lambda x: x.ifo), 2):
238 if event1.ifo == event2.ifo:
239 # a coincidence is parameterized only by
240 # inter-instrument deltas
241 continue
242
243 prefix = "%s_%s_" % (event1.ifo, event2.ifo)
244
245 # in each of the following, if the list of events contains
246 # more than one event from a given instrument, the smallest
247 # deltas are recorded
248
249 dt = float(event1.peak + offsetvector[event1.ifo] - event2.peak - offsetvector[event2.ifo])
250 name = "%sdt" % prefix
251 if name not in params or abs(params[name][0]) > abs(dt):
252 #params[name] = (dt,)
253 params[name] = (dt, gmst)
254
255 df = (event1.peak_frequency - event2.peak_frequency) / ((event1.peak_frequency + event2.peak_frequency) / 2)
256 name = "%sdf" % prefix
257 if name not in params or abs(params[name][0]) > abs(df):
258 #params[name] = (df,)
259 params[name] = (df, gmst)
260
261 dh = (event1.ms_hrss - event2.ms_hrss) / ((event1.ms_hrss + event2.ms_hrss) / 2)
262 name = "%sdh" % prefix
263 if name not in params or abs(params[name][0]) > abs(dh):
264 #params[name] = (dh,)
265 params[name] = (dh, gmst)
266
267 dband = (event1.ms_bandwidth - event2.ms_bandwidth) / ((event1.ms_bandwidth + event2.ms_bandwidth) / 2)
268 name = "%sdband" % prefix
269 if name not in params or abs(params[name][0]) > abs(dband):
270 #params[name] = (dband,)
271 params[name] = (dband, gmst)
272
273 ddur = (event1.ms_duration - event2.ms_duration) / ((event1.ms_duration + event2.ms_duration) / 2)
274 name = "%sddur" % prefix
275 if name not in params or abs(params[name][0]) > abs(ddur):
276 #params[name] = (ddur,)
277 params[name] = (ddur, gmst)
278
279 return self(**params)
280
281
282#
283# Galactic core coinc params
284#
285
286
287def delay_and_amplitude_correct(event, ra, dec):
288 # retrieve station metadata
289
290 detector = lal.cached_detector_by_prefix[event.ifo]
291
292 # delay-correct the event to the geocentre
293
294 delay = lal.TimeDelayFromEarthCenter(detector.location, ra, dec, event.peak)
295 event.peak -= delay
296 event.period = event.period.shift(-delay)
297 try:
298 event.ms_peak -= delay
299 except AttributeError:
300 pass
301 try:
302 event.ms_period = event.ms_period.shift(-delay)
303 except AttributeError:
304 pass
305
306 # amplitude-correct the event using the polarization-averaged
307 # antenna response
308
309 fp, fc = lal.ComputeDetAMResponse(detector.response, ra, dec, 0, lal.GreenwichMeanSiderealTime(event.peak))
310 mean_response = math.sqrt(fp**2 + fc**2)
311 event.amplitude /= mean_response
312 event.ms_hrss /= mean_response
313
314 # done
315
316 return event
317
318
320 def ln_lr_from_triggers(self, events, offsetvector):
321 ra, dec = MW_CENTER_J2000_RA_RAD, MW_CENTER_J2000_DEC_RAD
322 return EPAllSkyCoincParamsDistributions.coinc_params([delay_and_amplitude_correct(copy.copy(event), ra, dec) for event in events], offsetvector)
323
324
325#
326# =============================================================================
327#
328# I/O
329#
330# =============================================================================
331#
332
333
334process_program_name = "lalburst_power_meas_likelihood"
335
336
337def gen_likelihood_control(coinc_params_distributions, seglists, name = process_program_name, comment = ""):
338 xmldoc = ligolw.Document()
339 node = xmldoc.appendChild(ligolw.LIGO_LW())
340
341 process = ligolw_process.register_to_xmldoc(xmldoc, program = process_program_name, paramdict = {}, version = __version__, cvs_repository = "lscsoft", cvs_entry_time = __date__, comment = comment)
342 coinc_params_distributions.process_id = process.process_id
343 ligolw_search_summary.append_search_summary(xmldoc, process, ifos = seglists.keys(), inseg = seglists.extent_all(), outseg = seglists.extent_all())
344
345 node.appendChild(coinc_params_distributions.to_xml(name))
346
347 process.set_end_time_now()
348
349 return xmldoc
def from_filenames(cls, filenames, name, verbose=False)
Convenience function to deserialize CoincParamsDistributions objects from a collection of XML files a...
def ln_lr_from_triggers(self, events, offsetvector)
def __init__(self, instruments)
Definition: burca_tailor.py:61
def increment(self, params, weight=1.0)
Increment the counts defining this density at the given parameters.
Definition: burca_tailor.py:89
def to_xml(self, name)
Serialize to an XML fragment and return the root element of the resulting XML tree.
def copy(self)
Return a duplicate copy of this object.
Definition: burca_tailor.py:93
def __iadd__(self, other)
Marginalize the two densities.
Definition: burca_tailor.py:81
def __call__(self, **params)
Evaluate.
Definition: burca_tailor.py:73
def finish(self)
Ensure all internal densities are normalized, and initialize interpolator objects as needed for smoot...
def from_xml(cls, xml, name)
In the XML document tree rooted at xml, search for the serialized LnLRDensity object named name,...
Base class for parameter distribution densities for use in log likelihood ratio ranking statistics.
Definition: snglcoinc.py:2320
def get_xml_root(cls, xml, name)
Sub-classes can use this in their overrides of the .from_xml() method to find the root element of the...
Definition: snglcoinc.py:2391
Mixin to assist in implementing a log likelihood ratio ranking statistic class.
Definition: snglcoinc.py:2485
def delay_and_amplitude_correct(event, ra, dec)
def gen_likelihood_control(coinc_params_distributions, seglists, name=process_program_name, comment="")