13 Miscellaneous utility functions for skim experts.
19 from pathlib
import Path
24 def get_file_metadata(filename):
26 Retrieve the metadata for a file using ``b2file-metadata-show``.
29 metadata (str): File to get number of events from.
32 dict: Metadata of file in dict format.
34 if not Path(filename).exists():
35 raise FileNotFoundError(f
"Could not find file {filename}")
37 proc = subprocess.run(
38 [
"b2file-metadata-show",
"--json", str(filename)],
39 stdout=subprocess.PIPE,
42 metadata = json.loads(proc.stdout.decode(
"utf-8"))
46 def get_eventN(filename):
48 Retrieve the number of events in a file using ``b2file-metadata-show``.
51 filename (str): File to get number of events from.
54 int: Number of events in the file.
56 return int(get_file_metadata(filename)[
"nEvents"])
59 def resolve_skim_modules(SkimsOrModules, *, LocalModule=None):
61 Produce an ordered list of skims, by expanding any Python skim module names into a
62 list of skims in that module. Also produce a dict of skims grouped by Python module.
65 RuntimeError: Raised if a skim is listed twice.
66 ValueError: Raised if ``LocalModule`` is passed and skims are normally expected
67 from more than one module.
71 for name
in SkimsOrModules:
72 if name
in Registry.names:
74 elif name
in Registry.modules:
75 skims.extend(Registry.get_skims_in_module(name))
77 duplicates = set([skim
for skim
in skims
if skims.count(skim) > 1])
80 f
"Skim{'s'*(len(duplicates)>1)} requested more than once: {', '.join(duplicates)}"
83 modules = sorted({Registry.get_skim_module(skim)
for skim
in skims})
87 f
"Local module {LocalModule} specified, but the combined skim expects "
88 "skims from more than one module. No steering file written."
90 modules = {LocalModule.rstrip(
".py"): sorted(skims)}
94 [skim
for skim
in skims
if Registry.get_skim_module(skim) == module]
104 return hash(tuple(self))
107 def _sphinxify_decay(decay_string):
108 """Format the given decay string by using LaTeX commands instead of plain-text.
109 Output is formatted for use with Sphinx (ReStructured Text).
111 This is a utility function for autogenerating skim documentation.
114 decay_string (str): A decay descriptor.
117 sphinxed_string (str): LaTeX version of the decay descriptor.
120 decay_string = re.sub(
"^(B.):generic",
"\\1_{\\\\text{had}}", decay_string)
121 decay_string = decay_string.replace(
":generic",
"")
122 decay_string = decay_string.replace(
":semileptonic",
"_{\\text{SL}}")
123 decay_string = decay_string.replace(
":FSP",
"_{FSP}")
124 decay_string = decay_string.replace(
":V0",
"_{V0}")
125 decay_string = re.sub(
"_[0-9]+",
"", decay_string)
131 (
"gamma",
"\\gamma"),
133 (
"anti-p-",
"\\bar{p}"),
148 (
"J/psi",
"J/\\psi"),
149 (
"anti-Lambda_c-",
"\\Lambda^{-}_{c}"),
150 (
"anti-Sigma+",
"\\overline{\\Sigma}^{+}"),
151 (
"anti-Lambda0",
"\\overline{\\Lambda}^{0}"),
152 (
"anti-D0*",
"\\overline{D}^{0*}"),
153 (
"anti-D*0",
"\\overline{D}^{0*}"),
154 (
"anti-D0",
"\\overline{D}^0"),
155 (
"anti-B0",
"\\overline{B}^0"),
156 (
"Sigma+",
"\\Sigma^{+}"),
157 (
"Lambda_c+",
"\\Lambda^{+}_{c}"),
158 (
"Lambda0",
"\\Lambda^{0}"),
167 (
"D_s*+",
"D^{+*}_s"),
168 (
"D_s*-",
"D^{-*}_s"),
175 tex_string = decay_string
176 for (key, value)
in substitutes:
177 tex_string = tex_string.replace(key, value)
178 return f
":math:`{tex_string}`"
181 def fancy_skim_header(SkimClass):
182 """Decorator to generate a fancy header to skim documentation and prepend it to the
183 docstring. Add this just above the definition of a skim.
185 Also ensures the documentation of the template functions like `BaseSkim.build_lists`
186 is not repeated in every skim documentation.
188 .. code-block:: python
191 class MySkimName(BaseSkim):
192 # docstring here describing your skim, and explaining cuts.
194 SkimName = SkimClass.__name__
195 SkimCode = Registry.encode_skim_name(SkimName)
196 authors = SkimClass.__authors__
or [
"(no authors listed)"]
197 description = SkimClass.__description__
or "(no description)"
198 contact = SkimClass.__contact__
or "(no contact listed)"
199 category = SkimClass.__category__
or "(no category listed)"
201 if isinstance(authors, str):
204 r",\s+and\s+|\s+and\s+|,\s+&\s+|\s+&\s+|,\s+|\s*\n\s*", authors
207 authors = [re.sub(
r"^\s+|\s+$",
"", author)
for author
in authors]
209 if isinstance(category, list):
210 category =
", ".join(category)
213 match = re.match(
"([^<>()`]+) [<(]([^<>()`]+@[^<>()`]+)[>)]", contact)
215 name, email = match[1], match[2]
216 contact = f
"`{name} <mailto:{email}>`_"
220 * **Skim description**: {description}
221 * **Skim name**: {SkimName}
222 * **Skim LFN code**: {SkimCode}
223 * **Category**: {category}
224 * **Author{"s"*(len(authors) > 1)}**: {", ".join(authors)}
225 * **Contact**: {contact}
228 if SkimClass.ApplyHLTHadronCut:
229 HLTLine =
"*This skim includes a selection on the HLT flag* ``hlt_hadron``."
230 header = f
"{header.rstrip()}\n\n {HLTLine}\n"
232 if SkimClass.__doc__:
233 SkimClass.__doc__ = header +
"\n\n" + SkimClass.__doc__.lstrip(
"\n")
236 SkimClass.__doc__ = header
239 SkimClass.load_standard_lists.__doc__ = SkimClass.load_standard_lists.__doc__
or ""
240 SkimClass.build_lists.__doc__ = SkimClass.build_lists.__doc__
or ""
241 SkimClass.validation_histograms.__doc__ = (
242 SkimClass.validation_histograms.__doc__
or ""
244 SkimClass.additional_setup.__doc__ = SkimClass.additional_setup.__doc__
or ""
249 def dry_run_steering_file(SteeringFile):
251 Check if the steering file at the given path can be run with the "--dry-run" option.
253 proc = subprocess.run(
254 [
"basf2",
"--dry-run",
"-i",
"i.root",
"-o",
"o.root", str(SteeringFile)],
255 stderr=subprocess.PIPE,
256 stdout=subprocess.PIPE,
259 if proc.returncode != 0:
260 stdout = proc.stdout.decode(
"utf-8")
261 stderr = proc.stderr.decode(
"utf-8")
264 f
"An error occured while dry-running steering file {SteeringFile}\n"
265 f
"Script output:\n{stdout}\n{stderr}"