33DAG construction tools.
49from ligo
import segments
51from lal.utils
import CacheEntry
53from gstlal
import pipeline
55__author__ =
"Kipp Cannon <kipp.cannon@ligo.org>, Chad Hanna <chad.hanna@ligo.org>"
57__version__ =
"$Revision$"
70 which = subprocess.Popen([
'which',prog], stdout=subprocess.PIPE)
71 out = which.stdout.read().strip().decode(
'utf-8')
73 raise ValueError(
"could not find %s in your path, have you built the proper software and sourced the proper environment scripts?" % prog)
77def condor_scratch_space():
79 A way to standardize the condor scratch space even if it changes
80 >>> condor_scratch_space()
83 return "_CONDOR_SCRATCH_DIR"
88 The stupid pet tricks to find log space on the LDG.
89 Defaults to checking TMPDIR first.
91 host = socket.getfqdn()
93 return os.environ[
'TMPDIR']
95 print(
"\n\n!!!! $TMPDIR NOT SET !!!!\n\n\tPLEASE email your admin to tell them to set $TMPDIR to be the place where a users temporary files should be\n")
97 if 'cit' in host
or 'caltech.edu' in host:
98 tmp =
'/usr1/' + os.environ[
'USER']
99 print(f
"falling back to {tmp}")
101 if 'phys.uwm.edu' in host:
102 tmp =
'/localscratch/' + os.environ[
'USER']
103 print(f
"falling back to {tmp}")
105 if 'aei.uni-hannover.de' in host:
106 tmp =
'/local/user/' + os.environ[
'USER']
107 print(f
"falling back to {tmp}")
109 if 'phy.syr.edu' in host:
110 tmp =
'/usr1/' + os.environ[
'USER']
111 print(f
"falling back to {tmp}")
114 raise KeyError(
"$TMPDIR is not set and I don't recognize this environment")
128 A thin subclass of pipeline.CondorDAG.
130 Extra features include an add_node() method and a cache writing method.
131 Also includes some standard setup, e.g., log file paths etc.
133 def __init__(self, name, logpath = log_path()):
134 self.
basename = name.replace(
".dag",
"")
135 tempfile.tempdir = logpath
136 tempfile.template = self.
basename +
'.dag.log.'
137 logfile = tempfile.mktemp()
138 fh = open( logfile,
"w" )
140 pipeline.CondorDAG.__init__(self,logfile)
146 node.set_retry(retry)
147 node.add_macro(
"macronodename", node.get_name())
148 pipeline.CondorDAG.add_node(self, node)
150 def write_cache(self):
160 A job class that subclasses pipeline.CondorDAGJob and adds some extra
161 boiler plate items for gstlal jobs which tends to do the "right" thing
162 when given just an executable name.
164 def __init__(self, executable, tag_base = None, universe = "vanilla", condor_commands = {}):
176 self.
set_stdout_file(
'logs/$(macronodename)-$(cluster)-$(process).out')
177 self.
set_stderr_file(
'logs/$(macronodename)-$(cluster)-$(process).err')
185 for cmd, val
in condor_commands.items():
191 A node class that subclasses pipeline.CondorDAGNode that automates
192 adding the node to the dag, makes sensible names and allows a list of parent
193 nodes to be provided.
195 It tends to do the "right" thing when given a job, a dag, parent nodes, dictionary
196 options relevant to the job, a dictionary of options related to input files and a
197 dictionary of options related to output files.
199 NOTE and important and subtle behavior - You can specify an option with
200 an empty argument by setting it to "". However options set to None are simply
203 def __init__(self, job, dag, parent_nodes, opts = {}, input_files = {}, output_files = {}, input_cache_files = {}, output_cache_files = {}, input_cache_file_name = None):
204 pipeline.CondorDAGNode.__init__(self, job)
205 for p
in parent_nodes:
207 self.
set_name(
"%s_%04X" % (job.tag_base, job.number))
219 for opt, val
in list(opts.items()) + list(output_files.items()) + list(input_files.items()):
222 if isinstance(val, str)
or not isinstance(val, collections.Iterable):
232 self.
add_var_opt(opt, pipeline_dot_py_append_opts_hack(opt, val))
237 cache_dir = os.path.join(job.tag_base,
'cache')
239 for opt, val
in input_cache_files.items():
240 if not os.path.isdir(cache_dir):
242 cache_entries = [CacheEntry.from_T050017(
"file://localhost%s" % os.path.abspath(filename))
for filename
in val]
243 if input_cache_file_name
is None:
244 cache_file_name = group_T050017_filename_from_T050017_files(cache_entries,
'.cache', path = cache_dir)
246 cache_file_name = os.path.join(cache_dir, input_cache_file_name)
247 open(cache_file_name,
"w").write(
"\n".join(map(str, cache_entries)))
250 self.
cache_inputs.setdefault(opt, []).append(cache_file_name)
252 for opt, val
in output_cache_files.items():
253 if not os.path.isdir(cache_dir):
255 cache_entries = [CacheEntry.from_T050017(
"file://localhost%s" % os.path.abspath(filename))
for filename
in val]
256 cache_file_name = group_T050017_filename_from_T050017_files(cache_entries,
'.cache', path = cache_dir)
257 open(cache_file_name,
"w").write(
"\n".join(map(str, cache_entries)))
260 self.
cache_outputs.setdefault(opt, []).append(cache_file_name)
263def condor_command_dict_from_opts(opts, defaultdict = None):
265 A function to turn a list of options into a dictionary of condor commands, e.g.,
267 >>> condor_command_dict_from_opts(["+Online_CBC_SVD=True", "TARGET.Online_CBC_SVD =?= True"])
268 {'+Online_CBC_SVD': 'True', 'TARGET.Online_CBC_SVD ': '?= True'}
269 >>> condor_command_dict_from_opts(["+Online_CBC_SVD=True", "TARGET.Online_CBC_SVD =?= True"], {"somecommand":"somevalue"})
270 {'somecommand': 'somevalue', '+Online_CBC_SVD': 'True', 'TARGET.Online_CBC_SVD ': '?= True'}
271 >>> condor_command_dict_from_opts(["+Online_CBC_SVD=True", "TARGET.Online_CBC_SVD =?= True"], {"+Online_CBC_SVD":"False"})
272 {'+Online_CBC_SVD': 'True', 'TARGET.Online_CBC_SVD ': '?= True'}
275 if defaultdict
is None:
278 osplit = o.split(
"=")
280 v =
"=".join(osplit[1:])
281 defaultdict.update([(k, v)])
285def pipeline_dot_py_append_opts_hack(opt, vals):
287 A way to work around the dictionary nature of pipeline.py which can
288 only record options once.
290 >>> pipeline_dot_py_append_opts_hack("my-favorite-option", [1,2,3])
291 '1 --my-favorite-option 2 --my-favorite-option 3'
295 out +=
" --%s %s" % (opt, str(v))
299def format_ifo_args(ifos, args):
301 Given a set of instruments and arguments keyed by instruments, this
302 creates a list of strings in the form {ifo}={arg}. This is suitable
303 for command line options like --channel-name which expects this
306 if isinstance(ifos, str):
308 return [f
"{ifo}={args[ifo]}" for ifo
in ifos]
320def breakupseg(seg, maxextent, overlap):
322 raise ValueError(
"maxextent must be positive, not %s" % repr(maxextent))
325 if abs(seg) < maxextent:
326 return segments.segmentlist([seg])
329 maxextent = max(int(abs(seg) / (int(abs(seg)) // int(maxextent) + 1)), overlap)
330 maxextent = int(math.ceil(abs(seg) / math.ceil(abs(seg) / maxextent)))
333 seglist = segments.segmentlist()
337 if (seg[0] + maxextent + overlap) < end:
338 seglist.append(segments.segment(seg[0], seg[0] + maxextent + overlap))
339 seg = segments.segment(seglist[-1][1] - overlap, seg[1])
341 seglist.append(segments.segment(seg[0], end))
347def breakupsegs(seglist, maxextent, overlap):
348 newseglist = segments.segmentlist()
349 for bigseg
in seglist:
350 newseglist.extend(breakupseg(bigseg, maxextent, overlap))
354def breakupseglists(seglists, maxextent, overlap):
355 for instrument, seglist
in seglists.iteritems():
356 newseglist = segments.segmentlist()
357 for bigseg
in seglist:
358 newseglist.extend(breakupseg(bigseg, maxextent, overlap))
359 seglists[instrument] = newseglist
362def partition_by_time(span, segdict, ifos, min_ifos=1, max_livetime=14440, start_pad=512):
364 Splits a time span roughly equally based on livetime.
367 segdict_by_combo = segments.segmentlistdict()
368 ifo_combos = flatten(itertools.combinations(ifos, n)
for n
in range(min_ifos, len(ifos)))
369 ifo_combos = [frozenset(ifo_combo)
for ifo_combo
in ifo_combos]
370 for ifo_combo
in ifo_combos:
371 segdict_by_combo[ifo_combo] = segdict.intersection(ifo_combo)
372 all_segs = segdict_by_combo.union(ifo_combos) & segments.segmentlist([span])
375 num_bins = int(numpy.ceil(float(abs(all_segs) / max_livetime)))
376 time_bins = [segments.segmentlist()
for i
in range(num_bins)]
380 small_bin, remainder = divmod(float(abs(all_segs)), num_bins)
381 big_bin = small_bin + remainder
382 bin_livetime = [big_bin
if n == 0
else small_bin
for n
in range(num_bins)]
388 current_livetime = abs(time_bins[bin_])
389 if current_livetime + abs(segments.segmentlist([seg])) <= bin_livetime[bin_]:
390 time_bins[bin_] |= segments.segmentlist([seg])
394 diff_livetime = bin_livetime[bin_] - current_livetime
395 needed_seg = segments.segmentlist([segments.segment(seg[0], seg[0] + diff_livetime)])
396 time_bins[bin_] |= needed_seg
399 remainder = segments.segmentlist([segments.segment(seg[0] + diff_livetime, seg[1])])
400 while abs(remainder) > bin_livetime[bin_]:
401 remainder_start = remainder[0][0]
402 remainder_mid = remainder[0][0] + bin_livetime[bin_]
403 time_bins[bin_+1] |= segments.segmentlist([segments.segment(remainder_start, remainder_mid)])
404 remainder = segments.segmentlist([segments.segment(remainder_mid, seg[1])])
408 if bin_ < num_bins - 1:
410 time_bins[bin_] |= remainder
413 half_pad = start_pad / 2
414 return [segs.extent().protract(half_pad).shift(-half_pad)
if i != 0
else segs.extent()
for i, segs
in enumerate(time_bins)]
426def cache_to_instruments(cache):
428 Given a cache, returns back a string containing all the IFOs that are
429 contained in each of its cache entries, sorted by IFO name.
431 observatories = set()
432 for cache_entry
in cache:
433 observatories.update(groups(cache_entry.observatory, 2))
434 return ''.join(sorted(list(observatories)))
437def group_T050017_filename_from_T050017_files(cache_entries, extension, path = None):
439 A function to return the name of a file created from multiple files following
440 the T050017 convention. In addition to the T050017 requirements, this assumes
441 that numbers relevant to organization schemes will be the first entry in the
442 description, e.g. 0_DIST_STATS, and that all files in a given cache file are
443 from the same group of ifos and either contain data from the same segment or
444 from the same background bin. Note, that each file doesn't have to be from
445 the same IFO, for example the template bank cache could contain template bank
446 files from H1 and template bank files from L1.
449 observatories = cache_to_instruments(cache_entries)
450 split_description = cache_entries[0].description.split(
'_')
451 min_bin = [x
for x
in split_description[:2]
if x.isdigit()]
452 max_bin = [x
for x
in cache_entries[-1].description.split(
'_')[:2]
if x.isdigit()]
453 seg = segments.segmentlist(cache_entry.segment
for cache_entry
in cache_entries).extent()
457 max_bin = max_bin[-1]
458 if min_bin
and (min_bin == max_bin
or not max_bin):
467 return T050017_filename(observatories, cache_entries[0].description, seg, extension, path = path)
468 elif min_bin
and max_bin
and min_bin != max_bin:
469 if split_description[1].isdigit():
470 description_base = split_description[2:]
472 description_base = split_description[1:]
474 return T050017_filename(observatories,
'_'.join([min_bin, max_bin] + description_base), seg, extension, path = path)
476 print(
"ERROR: first and last file of cache file do not match known pattern, cannot name group file under T050017 convention. \nFile 1: %s\nFile 2: %s" % (cache_entries[0].path, cache_entries[-1].path), file=sys.stderr)
491 Given a list, returns back sublists with a maximum size n.
493 for i
in range(0, len(l), n):
499 Flatten a list by one level of nesting.
501 return list(itertools.chain.from_iterable(lst))
A job class that subclasses pipeline.CondorDAGJob and adds some extra boiler plate items for gstlal j...
A node class that subclasses pipeline.CondorDAGNode that automates adding the node to the dag,...
A thin subclass of pipeline.CondorDAG.
add_node(self, node, retry=3)
add_var_opt(self, opt, value, short=False)
add_var_arg(self, arg, quote=False)
set_stderr_file(self, path)
set_stdout_file(self, path)
add_condor_cmd(self, cmd, value)