Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# (c) Stefan Countryman 2016-2018
3"""
4Define a FileHandler that makes draft human-readable GCN Circulars for events.
5"""
7import logging
8from textwrap import fill
9from llama.filehandler import FileHandler, GenerateOnceMixin
10from llama.files.skymap_info import SkymapInfo
11from llama.files.team_receipts import TeamEmailReceipt
12from llama.files.i3 import (
13 IceCubeNeutrinoList,
14 IceCubeNeutrinoListCoincTxt,
15)
16from llama.files.i3.json import RctGdbIceCubeNeutrinoList
17from llama.files.coinc_o2 import (
18 RctGdbCoincSkymapO2Large,
19 CoincAnalysisInitialIcecubeJson,
20)
21from llama.files.lvc_skymap import LvcSkymapFits
22LOGGER = logging.getLogger(__name__)
25@TeamEmailReceipt.upload_this(subject=lambda s: s.UPLOAD(s).subject)
26@FileHandler.set_class_attributes
27class I3LvcGcnDraft(GenerateOnceMixin, FileHandler):
28 """
29 A draft version of the email sent to lvccirc@caeplla2.gsfc.nasa.gov for
30 distribution to the LVC GCN Circular list. This message is meant to be sent
31 to a LLAMA member for verification and modifications before being
32 manually submitted to GCN.
33 """
35 FILENAME = "gwhen_coinc_icecube_gcn_circular_draft.txt"
36 DEPENDENCIES = (
37 SkymapInfo,
38 IceCubeNeutrinoList,
39 IceCubeNeutrinoListCoincTxt,
40 RctGdbCoincSkymapO2Large,
41 RctGdbIceCubeNeutrinoList,
42 CoincAnalysisInitialIcecubeJson,
43 LvcSkymapFits,
44 )
46 @property
47 def temporally_coincident_neutrinos(self):
48 """Return the list of temporally-coincident neutrinos fetched from
49 IceCube."""
50 return IceCubeNeutrinoList(self).read_json()
52 @property
53 def reconstructed_neutrinos(self):
54 """Return the list of reconstructed neutrino directions generated by
55 the MATLAB analysis pipeline."""
56 return CoincAnalysisInitialIcecubeJson(self).read_json()
58 @property
59 def subject(self):
60 """Get a highly-descriptive subject line for the Circular email."""
61 graceid = SkymapInfo(self).graceid
62 if CoincAnalysisInitialIcecubeJson(self).best_neutrino is None:
63 fmt = "LIGO/Virgo {}: IceCube neutrino observations"
64 else:
65 fmt = ("LIGO/Virgo {}: FOUND COINCIDENT "
66 "IceCube neutrino observation")
67 return fmt.format(graceid)
69 @property
70 def authors(self):
71 """Return a list of authors for this Circular."""
72 return fill("""I. Bartos, S. Countryman (Columbia), C. Finley (U
73 Stockholm), E. Blaufuss (U Maryland), R. Corley, Z. Marka,
74 S. Marka (Columbia) on behalf of the IceCube
75 Collaboration.""")
77 @property
78 def introduction(self):
79 """An introductory paragraph for this Circular."""
80 num_temp_coinc = len(self.temporally_coincident_neutrinos)
81 finish = CoincAnalysisInitialIcecubeJson(self).modtime()
82 finish_time_str = finish.strftime('%c')
83 intro = ("In an analysis completed at {}, we searched IceCube online "
84 "track-like neutrino candidates (GFU) detected in a "
85 "[-500,500] second interval about the LIGO-Virgo trigger "
86 "{} found by the {} analysis "
87 "pipeline.").format(finish_time_str,
88 SkymapInfo(self).graceid,
89 SkymapInfo(self).pipeline)
90 if num_temp_coinc == 0:
91 graceid = SkymapInfo(self).graceid
92 intro += (" There were NO ONLINE TRACK-LIKE NEUTRINO CANDIDATES "
93 "DETECTED by IceCube within the 500 "
94 "second window surrounding {}.").format(graceid)
95 else:
96 intro += (" We compared the candidate source directions of {} "
97 "temporally-coincident neutrino candidates with the "
98 "below parameters to the {} "
99 "skymap:").format(num_temp_coinc,
100 SkymapInfo(self).skymap_filename)
101 return intro
103 @property
104 def neutrino_table(self):
105 """Return the human-readable neutrino table that will go in this
106 Circular."""
107 with IceCubeNeutrinoListCoincTxt(self).open() as infile:
108 neutrino_table = infile.read()
109 return neutrino_table
111 @property
112 def table_legend(self):
113 """A legend explaining the contents of the neutrino table."""
114 return ("(dt--time from GW in [seconds]; "
115 "RA/Dec--sky location in [degrees]; "
116 "E--reconstructed secondary muon energy in [TeV]; "
117 "Sigma--uncertainty of direction reconstruction in [degrees])")
119 @property
120 def conclusion(self):
121 """A conclusion paragraph for this Circular."""
122 # pylint: disable=invalid-name
123 n = CoincAnalysisInitialIcecubeJson(self).best_neutrino
124 if n is None:
125 fmt = fill("""The analysis found NO COINCIDENT ONLINE TRACK-LIKE
126 NEUTRINO CANDIDATES detected by IceCube within the 500
127 second window surrounding {} within the {} skymap.""")
128 else:
129 fmt = fill("""The analysis FOUND A COINCIDENT ONLINE TRACK-LIKE
130 NEUTRINO CANDIDATE detected by IceCube within the 500
131 second window surrounding {} within the {} skymap. The
132 coordinates of the reconstructed neutrino source are
133 below:""")
134 return fmt.format(SkymapInfo(self).graceid,
135 SkymapInfo(self).skymap_filename)
137 @property
138 def reconstructed_neutrino_table(self):
139 """A table showing the value of the reconstructed neutrino."""
140 # pylint: disable=invalid-name
141 n = CoincAnalysisInitialIcecubeJson(self).best_neutrino
142 if n is None:
143 return ''
144 column_titles = ['dt[s]', 'RA[deg]', 'Dec[deg]', 'E[TeV]',
145 'Sigma[deg]']
146 title_fmt = '#' + (' ' * 5) + ('{: >12}' * len(column_titles))
147 title = title_fmt.format(*column_titles)
148 separator = '-' * (6 + 12*len(column_titles))
149 n_fmt = '1. {:11.2f} {:11.1f} {:11.1f} {:11.2f} {:11.1f}'
150 n_row = n_fmt.format(float(n['dt']), n['RA'], n['DEC'],
151 n['energy_gev'] / 1e3, n['sigma_deg'])
152 return '{}\n{}\n{}'.format(title, separator, n_row)
154 @property
155 def uploaded_file_links(self):
156 """Don't mention GraceDB uploaded files if we haven't uploaded there
157 for whatever reason."""
158 fhclass = RctGdbCoincSkymapO2Large
159 skymap_fh = fhclass(self)
160 skymap_dict = skymap_fh.upload_dict
161 neutrino_fh = RctGdbIceCubeNeutrinoList(self)
162 neutrino_dict = neutrino_fh.upload_dict
164 if neutrino_dict is not None and skymap_dict is not None:
165 fmt = ("A coincident neutrino-GW skymap has been posted to "
166 "GraceDB (<{}>). A JSON-formatted list of the above "
167 "neutrinos can be downloaded from GraceDB at: <{}>\n\n")
168 # we want the human-readable API, which has the same URLs as the
169 # REST API, but with 'api' replaced by 'apiweb' in the URL.
170 neutrino_url = neutrino_dict['file'].replace('/api/', '/apiweb/')
171 skymap_url = skymap_dict['file'].replace('/api/', '/apiweb/')
172 return fmt.format(skymap_url, neutrino_url)
173 return ''
175 @property
176 def postscript(self):
177 """A postscript with some extra info about the LLAMA project."""
178 return fill(
179 """In addition, we are performing coincident searches with other
180 IceCube data streams, including the high-energy starting events
181 (HESE) and Supernova triggers. HESE events have typical energies >
182 60 TeV and start inside the detector volume, leading to a
183 relatively pure event sample with a high fraction of astrophysical
184 neutrinos. The SN trigger system is sensitive to sudden increases
185 in photomultiplier counts across the detector, which could indicate
186 a burst of MeV neutrinos. We will submit separate GCN circulars if
187 coincident HESE or SN triggers are found."""
188 )
190 @property
191 def description(self):
192 """A description of IceCube."""
193 return fill(
194 """The IceCube Neutrino Observatory is a cubic-kilometer neutrino
195 detector operating at the geographic South Pole, Antarctica. For a
196 description of the IceCube realtime alert system, please refer to
197 <http://adsabs.harvard.edu/cgi-bin/bib_query?arXiv:1610.01814>; for
198 more information on joint neutrino and gravitational wave searches,
199 please refer to
200 <http://adsabs.harvard.edu/cgi-bin/bib_query?arXiv:1602.05411>."""
201 )
203 def _generate(self): # pylint: disable=arguments-differ
204 """There are three possible structures for this GCN circular based on
205 whether we have:
207 1. No temporally coincident neutrinos
208 2. 1 or more temporally coincident neutrinos with no spatial
209 coincidence
210 3. 1 or more temporally and spatially coincident neutrinos
212 The included paragraphs and their ordering vary somewhat based on the
213 result."""
214 with open(self.fullpath, 'w') as f: # pylint: disable=invalid-name
215 # always write authors first
216 f.write('{}\n\n'.format(self.authors))
217 # in case of no temporal coincidence
218 if not self.temporally_coincident_neutrinos:
219 # If there are no IceCube neutrinos, it is not worth having a
220 # separate conclusion. The lack of neutrinos can just be stated
221 # in the introduction section.
222 f.write('{}\n\n'.format(self.introduction))
223 # in case of temporal coincidence, no spatial coincidence
224 elif (CoincAnalysisInitialIcecubeJson(self).best_neutrino
225 is None):
226 f.write('{}\n\n'.format(self.introduction))
227 f.write('{}\n\n'.format(self.neutrino_table))
228 f.write('{}\n\n'.format(self.table_legend))
229 f.write('{}\n\n'.format(self.conclusion))
230 # in case of full temporal and spatial coincidence
231 else:
232 f.write('{}\n\n'.format(self.conclusion))
233 f.write('{}\n\n'.format(self.reconstructed_neutrino_table))
234 f.write('{}\n\n'.format(self.introduction))
235 f.write('{}\n\n'.format(self.neutrino_table))
236 f.write('{}\n\n'.format(self.table_legend))
237 # always add available uploaded file links, postscript, and
238 # description
239 f.write(self.uploaded_file_links)
240 f.write('{}\n\n'.format(self.postscript))
241 f.write('{}\n\n'.format(self.description))