gstlal 1.13.0
Loading...
Searching...
No Matches
__init__.py
1# Copyright (C) 2020 Patrick Godwin (patrick.godwin@ligo.org)
2#
3# This program is free software; you can redistribute it and/or modify it
4# under the terms of the GNU General Public License as published by the
5# Free Software Foundation; either version 2 of the License, or (at your
6# option) any later version.
7#
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11# Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17
18import itertools
19import getpass
20import os
21
22import yaml
23
24from lal import LIGOTimeGPS
25from ligo.lw import utils as ligolw_utils
26from ligo.lw.utils import segments as ligolw_segments
27from ligo.segments import segment, segmentlist, segmentlistdict
28
29from gstlal import segments
30from gstlal.dags import profiles
31
32
33class Config:
34 """
35 Hold configuration used for analyzes.
36 """
37 def __init__(self, **kwargs):
38 # normalize options
39 kwargs = replace_keys(kwargs)
40
41 # basic analysis options
42 self.tag = kwargs.get("tag", "test")
43 self.rootdir = os.getcwd()
44
45 # instrument options
46 if isinstance(kwargs["instruments"], list):
47 self.ifos = kwargs["instruments"]
48 else:
49 self.ifos = self.to_ifo_list(kwargs["instruments"])
50 self.min_ifos = kwargs.get("min_instruments", 1)
51 self.all_ifos = frozenset(self.ifos)
52
53 # get instrument combinations
54 self.ifo_combos = []
55 for n_ifos in range(self.min_ifos, len(self.ifos) + 1):
56 for combo in itertools.combinations(self.ifos, n_ifos):
57 self.ifo_combos.append(frozenset(combo))
58
59 # source options
60 self.source = dotdict(replace_keys(kwargs["source"]))
61
62 # time options
63 if not "start" in kwargs:
64 self.span = segment(0, 0)
65 else:
66 self.start = LIGOTimeGPS(kwargs["start"])
67 if "stop" in kwargs:
68 self.stop = LIGOTimeGPS(kwargs["stop"])
69 self.duration = self.stop - self.start
70 else:
71 self.duration = kwargs["duration"]
72 self.stop = self.start + self.duration
73 self.span = segment(self.start, self.stop)
74
75 # section-specific options
76 if "psd" in kwargs:
77 self.psd = dotdict(replace_keys(kwargs["psd"]))
78 if "data" in kwargs:
79 self.data = dotdict(replace_keys(kwargs["data"]))
80 if "frames" in kwargs:
81 self.frames = dotdict(replace_keys(kwargs["frames"]))
82 if "segments" in kwargs:
83 self.segments = dotdict(replace_keys(kwargs["segments"]))
84 if "injections" in kwargs:
85 self.injections = dotdict(replace_keys(kwargs["injections"]))
86
87 # condor options
88 self.condor = dotdict(replace_keys(kwargs["condor"]))
89
90 # file transfer installed by default
91 if self.condor.transfer_files is None:
92 self.condor.transfer_files = True
93
94 self.condor.submit = self.create_condor_submit_options(
95 self.condor,
96 x509_proxy=self.source.x509_proxy,
97 )
98
99 # validate config
100 self.validate()
101
102 def create_time_bins(
103 self,
104 start_pad = 512,
105 overlap = 512,
106 min_instruments = 1,
107 one_ifo_only = False,
108 one_ifo_length = (3600 * 8)
109 ):
110 self.time_boundaries = segments.split_segments_by_lock(self.ifos, self.segments, self.span)
111 self.time_bins = segmentlistdict()
112 if not one_ifo_only:
113 for span in self.time_boundaries:
114 analysis_segs = segments.analysis_segments(
115 self.ifos,
116 self.segments,
117 span,
118 start_pad=start_pad,
119 overlap=overlap,
120 min_instruments=min_instruments,
121 one_ifo_length=one_ifo_length,
122 )
123 self.time_bins.extend(analysis_segs)
124 else:
125 for span in self.time_boundaries:
126 time_bin = segmentlistdict()
127 for ifo, segs in self.segments.items():
128 ifo_key = frozenset([ifo])
129 segs = segs & segmentlist([span])
130 time_bin[ifo_key] = segments.split_segments(segs, one_ifo_length, start_pad)
131 self.time_bins.extend(time_bin)
132
133 def create_condor_submit_options(self, condor_config, x509_proxy=False):
134 if "accounting_group_user" in condor_config:
135 accounting_group_user = condor_config["accounting_group_user"]
136 else:
137 accounting_group_user = getpass.getuser()
138
139 submit_opts = {
140 "want_graceful_removal": "True",
141 "kill_sig": "15",
142 "accounting_group": condor_config["accounting_group"],
143 "accounting_group_user": accounting_group_user,
144 }
145 requirements = []
146 environment = {}
147
148 # load site profile
149 profile = profiles.load_profile(condor_config["profile"])
150 assert profile["scheduler"] == "condor", "only scheduler=condor is allowed currently"
151
152 # profile options
153 if "directives" in profile:
154 submit_opts.update(profile["directives"])
155 if "requirements" in profile:
156 requirements.extend(profile["requirements"])
157 if "environment" in profile:
158 environment.update(profile["environment"])
159
160 # singularity options
161 if "singularity_image" in condor_config:
162 singularity_image = condor_config["singularity_image"]
163 submit_opts['+SingularityImage'] = f'"{singularity_image}"'
164 submit_opts['transfer_executable'] = False
165
166 # proxy options
167 if x509_proxy:
168 submit_opts['x509userproxy'] = x509_proxy
169 submit_opts['use_x509userproxy'] = True
170
171 # file transfer options
172 if not self.condor.transfer_files:
173 # set the job's working directory to be the current working directory
174 submit_opts['initialdir'] = self.rootdir
175
176 # config options
177 if "directives" in condor_config:
178 submit_opts.update(condor_config["directives"])
179 if "requirements" in condor_config:
180 requirements.extend(condor_config["requirements"])
181 if "environment" in condor_config:
182 environment.update(condor_config["environment"])
183
184 # condor requirements
185 submit_opts['requirements'] = " && ".join(requirements)
186
187 # condor environment
188 env_opts = [f"{key}={val}" for (key,val) in environment.items()]
189 if "environment" in submit_opts:
190 env_opts.append(submit_opts["environment"].strip('"'))
191 submit_opts["environment"] = f'"{" ".join(env_opts)}"'
192
193 return submit_opts
194
195 def validate(self):
196 """
197 Validate configuration settings.
198 """
199 pass
200
201 def setup(self):
202 """
203 Load segments and create time bins.
204 """
205 if "frame_segments_file" in self.source:
206 xmldoc = ligolw_utils.load_filename(
207 self.source.frame_segments_file,
208 contenthandler=ligolw_segments.LIGOLWContentHandler
209 )
210 self.segments = ligolw_segments.segmenttable_get_by_name(xmldoc, "datasegments").coalesce()
211 else:
212 self.segments = segmentlistdict((ifo, segmentlist([self.span])) for ifo in self.ifos)
213
214 if self.span != segment(0, 0):
215 self.create_time_bins(start_pad=512, min_instruments=self.min_ifos)
216
217 @staticmethod
218 def to_ifo_list(ifos):
219 """
220 Given a string of IFO pairs (e.g. H1L1), return a list of IFOs.
221 """
222 return [ifos[2*n:2*n+2] for n in range(len(ifos) // 2)]
223
224 @classmethod
225 def load(cls, path):
226 """
227 Load configuration from a file given a file path.
228 """
229 with open(path, "r") as f:
230 return cls(**yaml.safe_load(f))
231
232
233class dotdict(dict):
234 """
235 A dictionary supporting dot notation.
236
237 Implementation from https://gist.github.com/miku/dc6d06ed894bc23dfd5a364b7def5ed8.
238
239 """
240 __getattr__ = dict.get
241 __setattr__ = dict.__setitem__
242 __delattr__ = dict.__delitem__
243
244 def __init__(self, *args, **kwargs):
245 super().__init__(*args, **kwargs)
246 for k, v in self.items():
247 if isinstance(v, dict):
248 self[k] = dotdict(v)
249
250
251def replace_keys(dict_, reverse=False):
252 out = dict(dict_)
253 for k, v in out.items():
254 if isinstance(v, dict):
255 out[k] = replace_keys(v)
256 return {k.replace("_", "-") if reverse else k.replace("-", "_") : v for k, v in out.items()}
create_condor_submit_options(self, condor_config, x509_proxy=False)
Definition __init__.py:133