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, 2019
3"""
4Command Line Interface for manually creating IceCube GFU neutrino lists (by
5pulling them from either archives or the GFU API) and returning the neutrino
6lists in the desired formats. You must specify either a time window with
7``--interval`` or a central trigger time with ``--time`` (the way you would do
8for a triggered event).
9"""
11import os
12import argparse
13import tempfile
14from llama.files.i3.json import (
15 DELTAT_MINUS,
16 DELTAT_PLUS,
17 IceCubeNeutrinoList,
18 get_archive_and_real_neutrinos,
19)
20from llama.files.i3.txt import (
21 IceCubeNeutrinoListTxt,
22 convert_json_neutrinos_to_txt,
23)
24from llama.utils import (
25 utc2mjd,
26 utc2gps,
27 gps2mjd,
28 mjd2utc,
29 mjd2gps,
30 COLOR as COL,
31)
33DEFAULT_FNAME = {
34 'json': IceCubeNeutrinoList.FILENAME,
35 'txt': IceCubeNeutrinoListTxt.FILENAME,
36}
39def get_parser():
40 """Get a CLI argument parser."""
41 parser = argparse.ArgumentParser(description=__doc__, epilog="""
42 You must specify at least 1 output format from above. Default names are
43 the names as used in the pipeline (augmentable with the ``--prefix``
44 argument). Existing files will not be overwritten.""")
45 arg = parser.add_argument
46 arg("-t", "--time", help="""
47 If specified, this is the central time of the search (for the common
48 use case where we are triggering on an external event happening at this
49 time and searching for neutrinos in the surrounding time window). If
50 this can be parsed as a float, it will be interpreted as a GPS
51 timestamp (unless ``--mjd`` is specified, in which case it will be
52 interpreted as an MJD date). Otherwise, an attempt will be made to
53 parse it as a UTC datetime string, e.g. "2019-05-01T12:34:56".""")
54 arg("-i", "--interval", nargs=2, metavar=("START", "STOP"), help=f"""
55 Provide an interval surrounding ``--time`` in which to search. If
56 ``--time`` is provided, find neutrinos in the interval (TIME+START,
57 TIME+STOP) and interpret START and STOP as seconds (or, if ``--mjd`` is
58 provided, as days). If ``--time`` is omitted, find neutrinos in the
59 interval (START, STOP), where both variables are interpreted as dates
60 in the same way as ``--time``; note that in this case you **must**
61 specify (START, STOP) since it cannot be inferred. Defaults to the same
62 values as used by ``IceCubeNeutrinoList``: ({-DELTAT_MINUS},
63 {DELTAT_PLUS})""")
64 arg("-u", "--unblinded", action='store_true', help="""
65 Whether to unblind the neutrinos. **If not specified, neutrinos will be
66 blinded.**""")
67 arg("--mjd", action='store_true', help="""
68 If specified, interpret numerical arguments to ``--time`` or
69 ``--interval`` as MJD dates rather than GPS times.""")
70 arg("-p", "--prefix", nargs="?", default=False, help="""
71 If specified, prepend the neutrino search interval to the default
72 filenames in a human-readable format. Optionally provide an explicit
73 prefix for these default filenames to override this default. If
74 specific filenames are given, this option is ignored.""")
75 formats = parser.add_argument_group('output formats (specify at least 1)')
76 formats.add_argument("--json", nargs="?", default=False, help=f"""
77 Save this as a JSON file (LLAMA's internal storage format used by the
78 pipeline). Optionally specify a file path (default:
79 {DEFAULT_FNAME['json']}) in the current directory).""")
80 formats.add_argument("--txt", nargs="?", default=False, help=f"""
81 Save this as a human-readable ASCII table. Optionally specify a
82 file path (default: {DEFAULT_FNAME['txt']}) in the current
83 directory).""")
84 return parser
87def gpstime(time, mjd):
88 """Get the central time specified by ``--time`` in GPS format if it is
89 specified; otherwise, return ``None``."""
90 if time is None:
91 return time
92 try:
93 return utc2gps(time)
94 except ValueError:
95 if mjd:
96 return mjd2gps(time)
97 return time
100def mjd_interval(time, interval, mjd):
101 """Get the ``(mjd_start, mjd_end)`` interval for the input arguments. See
102 ``get_parser().print_help()`` for instructions on how this is
103 accomplished."""
104 if time is not None:
105 if interval is None:
106 interval = [-DELTAT_MINUS, DELTAT_PLUS]
107 # interpret the interval in days if --mjd is specified
108 if mjd:
109 interval = [i/86400 for i in interval]
110 # these must be floats if time is specified
111 interval = [float(t) for t in interval]
112 # load the ``time`` as either GPS or MJD and convert at the end
113 try:
114 timefloat = float(time)
115 except ValueError:
116 if mjd:
117 timefloat = utc2mjd(time)
118 else:
119 timefloat = utc2gps(time)
120 window = [timefloat+interval[0], timefloat+interval[1]]
121 if not mjd:
122 window = [gps2mjd(t) for t in window] # must be MJD
123 else:
124 if interval is None:
125 raise ValueError("Must specify --interval if omitting --time.")
126 window = []
127 for t in interval:
128 try:
129 t = float(t)
130 if not mjd:
131 t = gps2mjd(t) # must be MJD
132 except ValueError:
133 t = utc2mjd(t) # must be MJD
134 window.append(t)
135 assert len(window) == 2
136 return tuple(window)
139def filename(prefix, arg, default, mjd_start, mjd_end):
140 """Get the filename of the output JSON file."""
141 if arg is False:
142 raise ValueError("``arg=False`` implies that the user did not provide "
143 "--arg at the command line.")
144 if arg is not None:
145 return arg
146 # if the user specified --arg with no filename, use default
147 arg = default
148 if prefix is False:
149 return arg
150 # if the user specified --prefix with no string, use default
151 if prefix is None:
152 prefix = f"{mjd2utc(mjd_start)}_to_{mjd2utc(mjd_end)}_"
153 return prefix + arg
156def die(message):
157 """Print helpstring plus a descriptive ``message`` of what went wrong in red,
158 then exit with status 1."""
159 get_parser().print_help()
160 print(f"\n{COL.RED}{message}{COL.CLEAR}")
161 exit(1)
164def validate(parser, args):
165 """Validate CLI arguments, printing help and exiting if the provided
166 arguments are not valid."""
167 if args.time is None and args.interval is None:
168 die("You have to specify --interval if omitting --time.")
169 # if you specify the output types without giving a filename, their values
170 # will be done; they will be ``False`` if not specified, so check for that.
171 if not any(a is not False for a in [args.json, args.txt]):
172 die("You didn't specify any output formats; giving up.")
175def main():
176 """
177 Pull the requested neutrinos from IceCube and the local archive and save
178 them to the specified paths (see ``get_parser`` for command line
179 interface).
180 """
181 parser = get_parser()
182 args = parser.parse_args()
183 validate(parser, args)
184 mjd_start, mjd_end = mjd_interval(args.time, args.interval, args.mjd)
185 blind = not args.unblinded
186 gps = gpstime(args.time, args.mjd)
187 # make all files in a temporary directory (the slowest one, the main JSON
188 # output, always needs to be made anyway) and copy back the ones that were
189 # explicitly requested.
190 with tempfile.TemporaryDirectory() as tmpdir:
191 rundir, eventid = os.path.split(tmpdir)
192 neutrinos = get_archive_and_real_neutrinos(mjd_start, mjd_end, blind)
193 filehandler = IceCubeNeutrinoList(eventid, rundir=rundir)
194 filehandler._write_json(neutrinos)
195 # give each file the same name as the CLI arg
196 json_path = os.path.join(tmpdir, 'json')
197 os.rename(filehandler.fullpath, json_path)
198 convert_json_neutrinos_to_txt(json_path, os.path.join(tmpdir, 'txt'),
199 gpstime=gps)
200 for ftype in ('json', 'txt'):
201 if getattr(args, ftype) is not False:
202 fname = filename(args.prefix, getattr(args, ftype),
203 DEFAULT_FNAME[ftype], mjd_start, mjd_end)
204 try:
205 os.link(os.path.join(tmpdir, ftype), fname)
206 except FileExistsError:
207 die("Destination file already exists, not overwriting: "
208 f"{fname}")
211if __name__ == "__main__":
212 main()