Hide keyboard shortcuts

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 

2 

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""" 

10 

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) 

32 

33DEFAULT_FNAME = { 

34 'json': IceCubeNeutrinoList.FILENAME, 

35 'txt': IceCubeNeutrinoListTxt.FILENAME, 

36} 

37 

38 

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 

85 

86 

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 

98 

99 

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) 

137 

138 

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 

154 

155 

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) 

162 

163 

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.") 

173 

174 

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}") 

209 

210 

211if __name__ == "__main__": 

212 main()