32DAG construction tools.
47from ligo
import segments
49from lal.utils
import CacheEntry
51from gstlal
import pipeline
53__author__ =
"Kipp Cannon <kipp.cannon@ligo.org>, Chad Hanna <chad.hanna@ligo.org>"
55__version__ =
"$Revision$"
59 "all functionality within this module has been replaced by gstlal.dags and "
60 "gstlal.dags.util, and will be removed from gstlal in the future",
75 which = subprocess.Popen([
'which',prog], stdout=subprocess.PIPE)
76 out = which.stdout.read().strip().decode(
'utf-8')
78 raise ValueError(
"could not find %s in your path, have you built the proper software and sourced the proper environment scripts?" % prog)
82def condor_scratch_space():
84 A way to standardize the condor scratch space even if it changes
85 >>> condor_scratch_space()
88 return "_CONDOR_SCRATCH_DIR"
93 The stupid pet tricks to find log space on the LDG.
94 Defaults to checking TMPDIR first.
96 host = socket.getfqdn()
98 return os.environ[
'TMPDIR']
100 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")
102 if 'cit' in host
or 'caltech.edu' in host:
103 tmp =
'/usr1/' + os.environ[
'USER']
104 print(f
"falling back to {tmp}")
106 if 'phys.uwm.edu' in host:
107 tmp =
'/localscratch/' + os.environ[
'USER']
108 print(f
"falling back to {tmp}")
110 if 'aei.uni-hannover.de' in host:
111 tmp =
'/local/user/' + os.environ[
'USER']
112 print(f
"falling back to {tmp}")
114 if 'phy.syr.edu' in host:
115 tmp =
'/usr1/' + os.environ[
'USER']
116 print(f
"falling back to {tmp}")
119 raise KeyError(
"$TMPDIR is not set and I don't recognize this environment")
133 A thin subclass of pipeline.CondorDAG.
135 Extra features include an add_node() method and a cache writing method.
136 Also includes some standard setup, e.g., log file paths etc.
138 def __init__(self, name, logpath = log_path()):
139 self.
basename = name.replace(
".dag",
"")
140 tempfile.tempdir = logpath
141 tempfile.template = self.
basename +
'.dag.log.'
142 logfile = tempfile.mktemp()
143 fh = open( logfile,
"w" )
145 pipeline.CondorDAG.__init__(self,logfile)
151 node.set_retry(retry)
152 node.add_macro(
"macronodename", node.get_name())
153 pipeline.CondorDAG.add_node(self, node)
155 def write_cache(self):
165 A job class that subclasses pipeline.CondorDAGJob and adds some extra
166 boiler plate items for gstlal jobs which tends to do the "right" thing
167 when given just an executable name.
169 def __init__(self, executable, tag_base = None, universe = "vanilla", condor_commands = {}):
181 self.
set_stdout_file(
'logs/$(macronodename)-$(cluster)-$(process).out')
182 self.
set_stderr_file(
'logs/$(macronodename)-$(cluster)-$(process).err')
190 for cmd, val
in condor_commands.items():
196 A node class that subclasses pipeline.CondorDAGNode that automates
197 adding the node to the dag, makes sensible names and allows a list of parent
198 nodes to be provided.
200 It tends to do the "right" thing when given a job, a dag, parent nodes, dictionary
201 options relevant to the job, a dictionary of options related to input files and a
202 dictionary of options related to output files.
204 NOTE and important and subtle behavior - You can specify an option with
205 an empty argument by setting it to "". However options set to None are simply
208 def __init__(self, job, dag, parent_nodes, opts = {}, input_files = {}, output_files = {}, input_cache_files = {}, output_cache_files = {}, input_cache_file_name = None):
209 pipeline.CondorDAGNode.__init__(self, job)
210 for p
in parent_nodes:
212 self.
set_name(
"%s_%04X" % (job.tag_base, job.number))
224 for opt, val
in list(opts.items()) + list(output_files.items()) + list(input_files.items()):
227 if isinstance(val, str)
or not isinstance(val, collections.Iterable):
237 self.
add_var_opt(opt, pipeline_dot_py_append_opts_hack(opt, val))
242 cache_dir = os.path.join(job.tag_base,
'cache')
244 for opt, val
in input_cache_files.items():
245 if not os.path.isdir(cache_dir):
247 cache_entries = [CacheEntry.from_T050017(
"file://localhost%s" % os.path.abspath(filename))
for filename
in val]
248 if input_cache_file_name
is None:
249 cache_file_name = group_T050017_filename_from_T050017_files(cache_entries,
'.cache', path = cache_dir)
251 cache_file_name = os.path.join(cache_dir, input_cache_file_name)
252 open(cache_file_name,
"w").write(
"\n".join(map(str, cache_entries)))
255 self.
cache_inputs.setdefault(opt, []).append(cache_file_name)
257 for opt, val
in output_cache_files.items():
258 if not os.path.isdir(cache_dir):
260 cache_entries = [CacheEntry.from_T050017(
"file://localhost%s" % os.path.abspath(filename))
for filename
in val]
261 cache_file_name = group_T050017_filename_from_T050017_files(cache_entries,
'.cache', path = cache_dir)
262 open(cache_file_name,
"w").write(
"\n".join(map(str, cache_entries)))
265 self.
cache_outputs.setdefault(opt, []).append(cache_file_name)
268def condor_command_dict_from_opts(opts, defaultdict = None):
270 A function to turn a list of options into a dictionary of condor commands, e.g.,
272 >>> condor_command_dict_from_opts(["+Online_CBC_SVD=True", "TARGET.Online_CBC_SVD =?= True"])
273 {'+Online_CBC_SVD': 'True', 'TARGET.Online_CBC_SVD ': '?= True'}
274 >>> condor_command_dict_from_opts(["+Online_CBC_SVD=True", "TARGET.Online_CBC_SVD =?= True"], {"somecommand":"somevalue"})
275 {'somecommand': 'somevalue', '+Online_CBC_SVD': 'True', 'TARGET.Online_CBC_SVD ': '?= True'}
276 >>> condor_command_dict_from_opts(["+Online_CBC_SVD=True", "TARGET.Online_CBC_SVD =?= True"], {"+Online_CBC_SVD":"False"})
277 {'+Online_CBC_SVD': 'True', 'TARGET.Online_CBC_SVD ': '?= True'}
280 if defaultdict
is None:
283 osplit = o.split(
"=")
285 v =
"=".join(osplit[1:])
286 defaultdict.update([(k, v)])
290def pipeline_dot_py_append_opts_hack(opt, vals):
292 A way to work around the dictionary nature of pipeline.py which can
293 only record options once.
295 >>> pipeline_dot_py_append_opts_hack("my-favorite-option", [1,2,3])
296 '1 --my-favorite-option 2 --my-favorite-option 3'
300 out +=
" --%s %s" % (opt, str(v))
313def breakupseg(seg, maxextent, overlap):
315 raise ValueError(
"maxextent must be positive, not %s" % repr(maxextent))
318 if abs(seg) < maxextent:
319 return segments.segmentlist([seg])
322 maxextent = max(int(abs(seg) / (int(abs(seg)) // int(maxextent) + 1)), overlap)
323 maxextent = int(math.ceil(abs(seg) / math.ceil(abs(seg) / maxextent)))
326 seglist = segments.segmentlist()
330 if (seg[0] + maxextent + overlap) < end:
331 seglist.append(segments.segment(seg[0], seg[0] + maxextent + overlap))
332 seg = segments.segment(seglist[-1][1] - overlap, seg[1])
334 seglist.append(segments.segment(seg[0], end))
340def breakupsegs(seglist, maxextent, overlap):
341 newseglist = segments.segmentlist()
342 for bigseg
in seglist:
343 newseglist.extend(breakupseg(bigseg, maxextent, overlap))
347def breakupseglists(seglists, maxextent, overlap):
348 for instrument, seglist
in seglists.iteritems():
349 newseglist = segments.segmentlist()
350 for bigseg
in seglist:
351 newseglist.extend(breakupseg(bigseg, maxextent, overlap))
352 seglists[instrument] = newseglist
364def cache_to_instruments(cache):
366 Given a cache, returns back a string containing all the IFOs that are
367 contained in each of its cache entries, sorted by IFO name.
369 observatories = set()
370 for cache_entry
in cache:
371 observatories.update(groups(cache_entry.observatory, 2))
372 return ''.join(sorted(list(observatories)))
375def T050017_filename(instruments, description, seg, extension, path = None):
377 A function to generate a T050017 filename.
379 if not isinstance(instruments, str):
380 instruments =
"".join(sorted(instruments))
382 start = int(math.floor(start))
384 duration = int(math.ceil(end)) - start
386 except OverflowError:
387 duration = 2000000000
388 extension = extension.strip(
'.')
390 return '%s/%s-%s-%d-%d.%s' % (path, instruments, description, start, duration, extension)
392 return '%s-%s-%d-%d.%s' % (instruments, description, start, duration, extension)
395def group_T050017_filename_from_T050017_files(cache_entries, extension, path = None):
397 A function to return the name of a file created from multiple files following
398 the T050017 convention. In addition to the T050017 requirements, this assumes
399 that numbers relevant to organization schemes will be the first entry in the
400 description, e.g. 0_DIST_STATS, and that all files in a given cache file are
401 from the same group of ifos and either contain data from the same segment or
402 from the same background bin. Note, that each file doesn't have to be from
403 the same IFO, for example the template bank cache could contain template bank
404 files from H1 and template bank files from L1.
407 observatories = cache_to_instruments(cache_entries)
408 split_description = cache_entries[0].description.split(
'_')
409 min_bin = [x
for x
in split_description[:2]
if x.isdigit()]
410 max_bin = [x
for x
in cache_entries[-1].description.split(
'_')[:2]
if x.isdigit()]
411 seg = segments.segmentlist(cache_entry.segment
for cache_entry
in cache_entries).extent()
415 max_bin = max_bin[-1]
416 if min_bin
and (min_bin == max_bin
or not max_bin):
425 return T050017_filename(observatories, cache_entries[0].description, seg, extension, path = path)
426 elif min_bin
and max_bin
and min_bin != max_bin:
427 if split_description[1].isdigit():
428 description_base = split_description[2:]
430 description_base = split_description[1:]
432 return T050017_filename(observatories,
'_'.join([min_bin, max_bin] + description_base), seg, extension, path = path)
434 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)
449 Given a list, returns back sublists with a maximum size n.
451 for i
in range(0, len(l), n):
457 Flatten a list by one level of nesting.
459 return list(itertools.chain.from_iterable(lst))
462if __name__ ==
"__main__":
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)