6 from variables
import variables
as _variablemanager
7 from variables
import std_vector
as _std_vector
8 from typing
import Iterable, Union, List, Tuple, Optional
11 def create_aliases(list_of_variables: Iterable[str], wrapper: str, prefix: str) -> List[str]:
13 The function creates aliases for variables from the variables list with given wrapper
14 and returns list of the aliases.
16 If the variables in the list have arguments (like ``useLabFrame(p)``) all
17 non-alphanumeric characters in the variable will be replaced by underscores
18 (for example ``useLabFrame_x``) for the alias name.
20 >>> list_of_variables = ['M','p','matchedMC(useLabFrame(px))']
21 >>> wrapper = 'daughter(1,{variable})'
23 >>> print(create_aliases(list_of_variables, wrapper, prefix))
24 ['pref_M', 'pref_p', 'pref_matchedMC_useLabFrame_px']
25 >>> from variables import variables
26 >>> variables.printAliases()
27 [INFO] =====================================
28 [INFO] The following aliases are registered:
29 [INFO] pref_M --> daughter(1,M)
30 [INFO] pref_matchedMC_useLabFrame_px --> daughter(1,matchedMC(useLabFrame(px)))
31 [INFO] pref_p --> daughter(1,p)
32 [INFO] =====================================
35 list_of_variables (list(str)): list of variable names
36 wrapper (str): metafunction taking variables from list_of_variables as a parameter \
37 (``<metafunction>(<some configs>, {variable} ,<some other configs>)``
38 prefix (str): alias prefix used for wrapped variables.
41 list(str): new variables list
43 replacement = re.compile(
'[^a-zA-Z0-9]+')
45 for var
in list_of_variables:
47 safe = replacement.sub(
"_", var).strip(
"_")
48 aliases.append(f
"{prefix}_{safe}")
49 _variablemanager.addAlias(aliases[-1], wrapper.format(variable=var))
54 def get_hierarchy_of_decay(decay_string: str) -> List[List[Tuple[int, str]]]:
56 This function returns paths of the particles selected in decay string. For
57 each selected particle return a list of (index, name) tuples which indicate
58 which daughter index to choose to arrive at the selected particle.
60 For example for the decay string ``B+ -> [ D+ -> pi0 ^K+ ] pi0`` the
61 resulting path for the K+ would be ``[(0, 'D'), (1, 'K')]``: The K is the
62 second daughter of the first daughter of the B+
64 >>> get_hierarchy_of_decay('B+ -> [ D+ -> ^K+ pi0 ] pi0')
65 [[(0, 'D'), (0, 'K')]]
67 Every selected particle has its own path so if multiple particles are
68 collected a list of paths is returned
70 >>> get_hierarchy_of_decay('B+ -> [ D+ -> ^K+ pi0 ] ^pi0')
71 [[(0, 'D'), (0, 'K')], [(1, 'pi0')]]
73 If the mother particle is selected an empty list will be returned as its path
75 >>> get_hierarchy_of_decay('^B+ -> ^pi+ pi-')
79 decay_string (str): Decay string with selected particles
82 list(list(tuple(int, str))): list of hierarchies of selected particles.
84 from ROOT
import Belle2
86 if not d.init(decay_string):
87 raise ValueError(
"Invalid decay string")
89 selected_particles = []
90 for path
in d.getHierarchyOfSelected():
91 selected_particles.append([tuple(e)
for e
in path[1:]])
92 return selected_particles
95 def create_daughter_aliases(
96 list_of_variables: Iterable[str],
97 indices: Union[int, Iterable[int]],
98 prefix=
"", include_indices=
True
100 """Create Aliases for all variables for a given daughter hierarchy
103 list_of_variables (list(str)): list of variables to create aliases for
104 indices (int or list(int)): index of the daughter, grand-daughter, grand-grand-daughter,
106 prefix (str): optional prefix to prepend to the aliases
107 include_indices(bool): if set to True (default) the aliases will contain
108 the daughter indices as dX_dY_dZ...
111 list(str): new variables list
113 * create aliases for the second daughter as "d1_E", "d1_M" (daughters start at 0)
115 >>> create_daughter_aliases(["E", "m"], 1)
117 >>> from variables import variables
118 >>> variables.printAliases()
119 [INFO] =========================
120 [INFO] Following aliases exists:
121 [INFO] 'd1_E' --> 'daughter(1,E)'
122 [INFO] 'd1_m' --> 'daughter(1,m)'
123 [INFO] =========================
126 * create aliases for the first grand daughter of the second daughter,
127 starting with "my" and without including the indices, resulting in "my_E", "my_m"
129 >>> create_daughter_aliases(["E", "m"], [1, 0], prefix="my", include_indices=False)
131 >>> from variables import variables
132 >>> variables.printAliases()
133 [INFO] =========================
134 [INFO] Following aliases exists:
135 [INFO] 'my_E' --> 'daughter(1,daughter(0,E))'
136 [INFO] 'my_m' --> 'daughter(1,daughter(0,m))'
137 [INFO] =========================
139 * create aliases for the second grand grand daughter of the third grand
140 daughter of the fifth daugther, starting with my and including the
141 indices, resulting in "my_d4_d2_d1_E", "my_d4_d2_d1_m"
143 >>> create_daughter_aliases(["E", "m"], [4, 2, 1], prefix="my")
144 ['my_d4_d2_d1_E', 'my_d4_d2_d1_m']
145 >>> from variables import variables
146 >>> variables.printAliases()
147 [INFO] =========================
148 [INFO] Following aliases exists:
149 [INFO] 'my_d4_d2_d1_E' --> 'daughter(4,daughter(2,daughter(1,E))'
150 [INFO] 'my_d4_d2_d1_m' --> 'daughter(4,daughter(2,daughter(1,m))'
151 [INFO] =========================
155 if not isinstance(indices, collections.Iterable):
159 prefix = functools.reduce(
lambda x, y: f
"{x}_d{y}", indices, prefix).lstrip(
"_")
161 template = functools.reduce(
lambda x, y: f
"daughter({y},{x})", reversed(indices),
"{variable}")
162 return create_aliases(list_of_variables, template, prefix)
167 Class to present selected particles from a DecayString as tree structure.
168 For each node of the tree we safe the name of the particle, whether it is
169 selected and a dictionary of all children (as mapping decayIndex -> Node)
173 """Just set default values"""
181 def get_prefixes(self, always_include_indices=False, use_relative_indices=False):
183 Recursively walk through the tree of selected particles and return a list
184 of prefixes for aliases and a tuple of decay indexes for that prefix.
186 For example for ``B0 -> [D0 -> ^pi+] ^pi0`` it might return
188 >>> DecayParticleNode.build('^B0 -> [D0 -> ^pi+] ^pi0').get_prefixes()
189 [ ("", None), ("D0_pi", (0, 0)), ("pi0", (1,)) ]
191 and to create aliases from these one would use the indices as arguments for
192 te b2:var:`daughter` meta variable.
194 This function will make sure that prefix names are unique: If there are
195 multiple siblings of one node with the same particle name they will be
196 distinguised by either suffixing them with the decay index (if
197 ``use_relative_indices=False``) or they will just be enumerated
198 starting at 0 otherwise.
201 always_include_indices (bool): If True always add the index of the
202 particle to the prefix, otherwise the index is only added if
203 more than one sibling of the same particle exist.
204 use_relative_indices (bool): If True the indices used will **not**
205 be the daughter indices in the full decay string but just the
206 relative indices: If multiple sibling particles with the same
207 name they will be just numbered starting at zero as they appear
210 return self.
__walk(always_include_indices, use_relative_indices,
"", tuple())
212 def __walk(self, always_include_indices, use_relative_indices, current_prefix, current_path):
213 """Recursively walk the tree and collect all prefixes
219 always_include_indices (bool): see `get_prefixes()`
220 use_relative_indices (bool): see `get_prefixes()`
221 current_prefix: the current prefix so far collected from any parent
223 current_path: the current path of indices so far collected from any
229 if not current_path
and self.
selected:
230 result.append((
"",
None))
234 names = collections.Counter(e.name
for e
in self.
children.values())
236 relative_indices = collections.defaultdict(int)
239 for index, c
in sorted(self.
children.items()):
241 full_path = current_path + (index,)
243 prefix = current_prefix + c.name
245 if always_include_indices
or names[c.name] > 1:
246 prefix +=
"_{}".format(relative_indices[c.name]
if use_relative_indices
else index)
248 relative_indices[c.name] += 1
252 result.append((prefix, full_path))
255 result += c.__walk(always_include_indices, use_relative_indices, prefix +
"_", full_path)
262 """Build a tree of selected particles from a `DecayString`
264 This will return a `DecayParticleNode` instance which is the top of a
265 tree of all the selected particles from the decat string.
268 decay_string (str): `DecayString` containing at least one selected particle
270 selected = get_hierarchy_of_decay(decay_string)
272 raise ValueError(
"No particle selected in decay string")
276 for path
in selected:
279 for index, name
in path:
281 if index
not in current.children:
282 current.children[index] = cls(name)
284 current = current.children[index]
287 current.selected =
True
293 def create_aliases_for_selected(
294 list_of_variables: List[str],
296 prefix: Optional[Union[str, List[str]]] =
None,
299 always_include_indices=
False,
300 use_relative_indices=
False
303 The function creates list of aliases for given variables so that they are calculated for
304 particles selected in decay string. That is for each particle selected in
305 the decay string an alias is created to calculate each variable in the
306 ``list_of_variables``.
308 If ``use_names=True`` (the default) then the names of the aliases are assigned as follows:
310 * If names are unambiguous, it's semi-laconic :doc:`DecayString` style: The
311 aliases will be prefixed with the names of all parent particle names
312 separated by underscore. For example given the decay string ``B0 -> [D0 -> ^pi+ K-] pi0``
313 the aliases for the ``pi+` will start with ``D0_pi_`` followed by the
316 >>> list_of_variables = ['M','p']
317 >>> decay_string = 'B0 -> [D0 -> ^pi+ K-] pi0'
318 >>> create_aliases_for_selected(list_of_variables, decay_string)
319 ['D0_pi_M', 'D0_pi_p']
320 >>> from variables import variables
321 >>> variables.printAliases()
322 [INFO] =========================
323 [INFO] Following aliases exists:
324 [INFO] 'D0_pi_M' --> 'daughter(0,daughter(0,M))'
325 [INFO] 'D0_pi_p' --> 'daughter(0,daughter(0,p))'
326 [INFO] =========================
329 * If names are ambiguous because there are multiple daughters with the same
330 name these particles will be followed by their daughter index. For example
331 given the decay string ``B0 -> [D0 -> ^pi+:1 ^pi-:2 ^pi0:3 ] ^pi0:4``
332 will create aliases with the following prefixes for particle with the
333 corresponding number as list name:
340 >>> list_of_variables = ['M','p']
341 >>> decay_string = 'B0 -> [D0 -> ^pi+ ^pi- ^pi0] ^pi0'
342 >>> create_aliases_for_selected(list_of_variables, decay_string)
343 ['D0_pi_0_M', 'D0_pi_0_p', 'D0_pi_1_M', 'D0_pi_1_p',
344 'D0_pi0_M', 'D0_pi0_p', 'pi0_M', 'pi0_p']
345 >>> from variables import variables
346 >>> variables.printAliases()
347 [INFO] =========================
348 [INFO] Following aliases exists:
349 [INFO] 'D0_pi0_M' --> 'daughter(0,daughter(2,M))'
350 [INFO] 'D0_pi0_p' --> 'daughter(0,daughter(2,p))'
351 [INFO] 'D0_pi_0_M' --> 'daughter(0,daughter(0,M))'
352 [INFO] 'D0_pi_0_p' --> 'daughter(0,daughter(0,p))'
353 [INFO] 'D0_pi_1_M' --> 'daughter(0,daughter(1,M))'
354 [INFO] 'D0_pi_1_p' --> 'daughter(0,daughter(1,p))'
355 [INFO] 'D0_pi_M' --> 'daughter(0,daughter(0,M))'
356 [INFO] 'D0_pi_p' --> 'daughter(0,daughter(0,p))'
357 [INFO] 'pi0_M' --> 'daughter(1,M)'
358 [INFO] 'pi0_p' --> 'daughter(1,p)'
359 [INFO] =========================
361 * The user can select to always include the index even for unambiguous
362 particles by passing ``always_include_indices=True``
364 * The user can choose two different numbering schemes: If
365 ``use_relative_indices=False`` the original decay string indices will be
366 used if a index is added to a particle name.
368 But if ``use_relative_indices=True`` the indices will just start at zero for each
369 particle which is part of the prefixes. For example for ``B0-> e+ ^e-``
371 >>> create_aliases_for_selected(['M'], 'B0-> mu+ e- ^e+ ^e-', use_relative_indices=False)
373 >>> create_aliases_for_selected(['M'], 'B0-> mu+ e- ^e+ ^e-', use_relative_indices=True)
376 If ``use_names=False`` the aliases will just start with the daughter indices
377 of all parent particles prefixed with a ``d`` and separated by underscore. So
378 for the previous example ``B0 -> [D0 -> ^pi+:1 ^pi-:2 ^pi0:3 ] ^pi0:4``
379 this would result in aliases starting with
386 In this case the ``always_include_indices`` and ``use_relative_indices``
387 arguments are ignored.
389 The naming can be modified by providing a custom prefix for each selected
390 particle. In this case the parameter ``prefix`` needs to be either a simple
391 string if only one particle is selected or a list of strings with one
392 prefix for each selected particle.
394 >>> list_of_variables = ['M','p']
395 >>> decay_string = 'B0 -> [D0 -> ^pi+ ^pi- pi0] pi0'
396 >>> create_aliases_for_selected(list_of_variables, decay_string, prefix=['pip', 'pim'])
397 ['pip_M', 'pip_p', 'pim_M', 'pim_p']
398 >>> from variables import variables
399 >>> variables.printAliases()
400 [INFO] =========================
401 [INFO] Following aliases exists:
402 [INFO] 'pim_M' --> 'daughter(0,daughter(1,M))'
403 [INFO] 'pim_p' --> 'daughter(0,daughter(1,p))'
404 [INFO] 'pip_M' --> 'daughter(0,daughter(0,M))'
405 [INFO] 'pip_p' --> 'daughter(0,daughter(0,p))'
406 [INFO] =========================
408 If the mother particle itself is selected the input list of variables will
409 also be added to the returned list of created aliases. If custom prefixes
410 are supplied then aliases will be created for the mother particle as well:
412 >>> create_aliases_for_selected(['M', 'p'], '^B0 -> pi+ ^pi-')
413 ['M', 'p', 'pi_M', 'pi_p']
414 >>> create_aliases_for_selected(['M', 'p'], '^B0 -> pi+ ^pi-', prefix=['MyB', 'MyPi'])
415 ['MyB_M', 'MyB_p', 'MyPi_M', 'MyPi_p']
418 list_of_variables (list(str)): list of variable names
419 decay_string (str): Decay string with selected particles
420 prefix (str, list(str)): Custom prefix for all selected particles
421 use_names (bool): Include the names of the particles in the aliases
422 always_include_indices (bool): If ``use_names=True`` include the decay
423 index of the particles in the alias name even if the particle could
424 be uniquely identified without them.
425 use_relative_indices (bool): If ``use_names=True`` use a relative
426 indicing which always starts at 0 for each particle appearing in
427 the alias names independent of the absolute position in the decay
431 list(str): new variables list
434 selected_particles = DecayParticleNode.build(decay_string)
435 prefixes = selected_particles.get_prefixes(always_include_indices, use_relative_indices)
438 prefixes, paths = zip(*prefixes)
443 include_indices =
False
445 if prefix
is not None:
447 if isinstance(prefix, str):
450 if len(prefix) != len(prefixes):
451 raise ValueError(
"Number of selected particles does not match number of supplied custom prefixes")
453 prefix_counts = collections.Counter(prefix)
454 if max(prefix_counts.values()) > 1:
455 raise ValueError(
"Prefixes need to be unique")
462 prefixes = [
""] * len(prefixes)
463 include_indices =
True
465 for prefix, path
in zip(prefixes, paths):
470 alias_list += create_aliases(list_of_variables,
"{variable}", prefix)
473 alias_list += list_of_variables
476 alias_list += create_daughter_aliases(list_of_variables, path, prefix, include_indices)
481 def create_mctruth_aliases(
482 list_of_variables: Iterable[str],
486 The function wraps variables from the list with 'matchedMC()'.
488 >>> list_of_variables = ['M','p']
489 >>> create_mctruth_aliases(list_of_variables)
491 >>> from variables import variables
492 >>> variables.printAliases()
493 [INFO] =========================
494 [INFO] Following aliases exists:
495 [INFO] 'mc_M' --> 'matchedMC(M)'
496 [INFO] 'mc_p' --> 'matchedMC(p)'
497 [INFO] =========================
501 list_of_variables (list(str)): list of variable names
504 list(str): list of created aliases
506 return create_aliases(list_of_variables,
'matchedMC({variable})', prefix)
509 def add_collection(list_of_variables: Iterable[str], collection_name: str) -> str:
511 The function creates variable collection from the given list of variables
512 It wraps the `VariableManager.addCollection` method which is not particularly user-friendly.
516 Defining the collection
517 >>> variables.utils.add_collection(['p','E'], "my_collection")
519 Passing it as an argument to variablesToNtuple
520 >>> modularAnalysis.variablesToNtuple(variables=['my_collection'], ...)
523 list_of_variables (list(str)): list of variable names
524 collection_name (str): name of the collection
527 str: name of the variable collection
530 _variablemanager.addCollection(collection_name, _std_vector(*tuple(list_of_variables)))
531 return collection_name
534 def create_isSignal_alias(aliasName, flags):
536 Make a `VariableManager` alias for a customized :b2:var:`isSignal`, which accepts specified mc match errors.
538 .. seealso:: see :doc:`MCMatching` for a definition of the mc match error flags.
540 The following code defines a new variable ``isSignalAcceptMissingGammaAndMissingNeutrino``, which is same
541 as :b2:var:`isSignal`, but also accepts missing gamma and missing neutrino
543 >>> create_isSignal_alias("isSignalAcceptMissingGammaAndMissingNeutrino", [16, 8])
546 ``isSignalAcceptMissingGammaAndMissingNeutrino`` =
547 :b2:var:`isSignalAcceptMissingGamma` || :b2:var:`isSignalAcceptMissingNeutrino`.
549 In the example above, create_isSignal_alias() creates ``isSignalAcceptMissingGammaAndMissingNeutrino`` by
550 unmasking (setting bits to zero)
551 the ``c_MissGamma`` bit (16 or 0b00010000) and ``c_MissNeutrino`` bit (8 or 0b00001000) in mcErrors.
553 For more information, please check this `example script <https://stash.desy.de/projects/B2/repos/software/
554 browse/analysis/examples/VariableManager/isSignalAcceptFlags.py>`_.
557 aliasName (str): the name of the alias to be set
558 flags (list(int)): a list of the bits to unmask
563 if isinstance(flag, int):
566 informationString =
"The type of input flags of create_isSignal_alias() should be integer."
567 informationString +=
"Now one of the input flags is " + str(int) +
" ."
568 raise ValueError(informationString)
570 _variablemanager.addAlias(aliasName,
"passesCut(unmask(mcErrors, %d) == %d)" % (mask, 0))