13 from variables
import variables
as _variablemanager
14 from variables
import std_vector
as _std_vector
15 from typing
import Iterable, Union, List, Tuple, Optional
18 def create_aliases(list_of_variables: Iterable[str], wrapper: str, prefix: str) -> List[str]:
20 The function creates aliases for variables from the variables list with given wrapper
21 and returns list of the aliases.
23 If the variables in the list have arguments (like ``useLabFrame(p)``) all
24 non-alphanumeric characters in the variable will be replaced by underscores
25 (for example ``useLabFrame_x``) for the alias name.
27 >>> list_of_variables = ['M','p','matchedMC(useLabFrame(px))']
28 >>> wrapper = 'daughter(1,{variable})'
30 >>> print(create_aliases(list_of_variables, wrapper, prefix))
31 ['pref_M', 'pref_p', 'pref_matchedMC_useLabFrame_px']
32 >>> from variables import variables
33 >>> variables.printAliases()
34 [INFO] =====================================
35 [INFO] The following aliases are registered:
36 [INFO] pref_M --> daughter(1,M)
37 [INFO] pref_matchedMC_useLabFrame_px --> daughter(1,matchedMC(useLabFrame(px)))
38 [INFO] pref_p --> daughter(1,p)
39 [INFO] =====================================
42 list_of_variables (list(str)): list of variable names
43 wrapper (str): metafunction taking variables from list_of_variables as a parameter \
44 (``<metafunction>(<some configs>, {variable} ,<some other configs>)``
45 prefix (str): alias prefix used for wrapped variables.
48 list(str): new variables list
50 replacement = re.compile(
'[^a-zA-Z0-9]+')
52 for var
in list_of_variables:
54 safe = replacement.sub(
"_", var).strip(
"_")
55 aliases.append(f
"{prefix}_{safe}")
56 _variablemanager.addAlias(aliases[-1], wrapper.format(variable=var))
61 def get_hierarchy_of_decay(decay_string: str) -> List[List[Tuple[int, str]]]:
63 This function returns paths of the particles selected in decay string. For
64 each selected particle return a list of (index, name) tuples which indicate
65 which daughter index to choose to arrive at the selected particle.
67 For example for the decay string ``B+ -> [ D+ -> pi0 ^K+ ] pi0`` the
68 resulting path for the K+ would be ``[(0, 'D'), (1, 'K')]``: The K is the
69 second daughter of the first daughter of the B+
71 >>> get_hierarchy_of_decay('B+ -> [ D+ -> ^K+ pi0 ] pi0')
72 [[(0, 'D'), (0, 'K')]]
74 Every selected particle has its own path so if multiple particles are
75 collected a list of paths is returned
77 >>> get_hierarchy_of_decay('B+ -> [ D+ -> ^K+ pi0 ] ^pi0')
78 [[(0, 'D'), (0, 'K')], [(1, 'pi0')]]
80 If the mother particle is selected an empty list will be returned as its path
82 >>> get_hierarchy_of_decay('^B+ -> ^pi+ pi-')
86 decay_string (str): Decay string with selected particles
89 list(list(tuple(int, str))): list of hierarchies of selected particles.
91 from ROOT
import Belle2
93 if not d.init(decay_string):
94 raise ValueError(
"Invalid decay string")
96 selected_particles = []
97 for path
in d.getHierarchyOfSelected():
98 selected_particles.append([tuple(e)
for e
in path[1:]])
99 return selected_particles
102 def create_daughter_aliases(
103 list_of_variables: Iterable[str],
104 indices: Union[int, Iterable[int]],
105 prefix=
"", include_indices=
True
107 """Create Aliases for all variables for a given daughter hierarchy
110 list_of_variables (list(str)): list of variables to create aliases for
111 indices (int or list(int)): index of the daughter, grand-daughter, grand-grand-daughter,
113 prefix (str): optional prefix to prepend to the aliases
114 include_indices(bool): if set to True (default) the aliases will contain
115 the daughter indices as dX_dY_dZ...
118 list(str): new variables list
120 * create aliases for the second daughter as "d1_E", "d1_M" (daughters start at 0)
122 >>> create_daughter_aliases(["E", "m"], 1)
124 >>> from variables import variables
125 >>> variables.printAliases()
126 [INFO] =========================
127 [INFO] Following aliases exists:
128 [INFO] 'd1_E' --> 'daughter(1,E)'
129 [INFO] 'd1_m' --> 'daughter(1,m)'
130 [INFO] =========================
133 * create aliases for the first grand daughter of the second daughter,
134 starting with "my" and without including the indices, resulting in "my_E", "my_m"
136 >>> create_daughter_aliases(["E", "m"], [1, 0], prefix="my", include_indices=False)
138 >>> from variables import variables
139 >>> variables.printAliases()
140 [INFO] =========================
141 [INFO] Following aliases exists:
142 [INFO] 'my_E' --> 'daughter(1,daughter(0,E))'
143 [INFO] 'my_m' --> 'daughter(1,daughter(0,m))'
144 [INFO] =========================
146 * create aliases for the second grand grand daughter of the third grand
147 daughter of the fifth daugther, starting with my and including the
148 indices, resulting in "my_d4_d2_d1_E", "my_d4_d2_d1_m"
150 >>> create_daughter_aliases(["E", "m"], [4, 2, 1], prefix="my")
151 ['my_d4_d2_d1_E', 'my_d4_d2_d1_m']
152 >>> from variables import variables
153 >>> variables.printAliases()
154 [INFO] =========================
155 [INFO] Following aliases exists:
156 [INFO] 'my_d4_d2_d1_E' --> 'daughter(4,daughter(2,daughter(1,E))'
157 [INFO] 'my_d4_d2_d1_m' --> 'daughter(4,daughter(2,daughter(1,m))'
158 [INFO] =========================
162 if not isinstance(indices, collections.Iterable):
166 prefix = functools.reduce(
lambda x, y: f
"{x}_d{y}", indices, prefix).lstrip(
"_")
168 template = functools.reduce(
lambda x, y: f
"daughter({y},{x})", reversed(indices),
"{variable}")
169 return create_aliases(list_of_variables, template, prefix)
174 Class to present selected particles from a DecayString as tree structure.
175 For each node of the tree we safe the name of the particle, whether it is
176 selected and a dictionary of all children (as mapping decayIndex -> Node)
180 """Just set default values"""
188 def get_prefixes(self, always_include_indices=False, use_relative_indices=False):
190 Recursively walk through the tree of selected particles and return a list
191 of prefixes for aliases and a tuple of decay indexes for that prefix.
193 For example for ``B0 -> [D0 -> ^pi+] ^pi0`` it might return
195 >>> DecayParticleNode.build('^B0 -> [D0 -> ^pi+] ^pi0').get_prefixes()
196 [ ("", None), ("D0_pi", (0, 0)), ("pi0", (1,)) ]
198 and to create aliases from these one would use the indices as arguments for
199 the b2:var:`daughter` meta variable.
201 This function will make sure that prefix names are unique: If there are
202 multiple siblings of one node with the same particle name they will be
203 distinguised by either suffixing them with the decay index (if
204 ``use_relative_indices=False``) or they will just be enumerated
205 starting at 0 otherwise.
208 always_include_indices (bool): If True always add the index of the
209 particle to the prefix, otherwise the index is only added if
210 more than one sibling of the same particle exist.
211 use_relative_indices (bool): If True the indices used will **not**
212 be the daughter indices in the full decay string but just the
213 relative indices: If multiple sibling particles with the same
214 name they will be just numbered starting at zero as they appear
217 return self.
__walk__walk(always_include_indices, use_relative_indices,
"", tuple())
219 def __walk(self, always_include_indices, use_relative_indices, current_prefix, current_path):
220 """Recursively walk the tree and collect all prefixes
226 always_include_indices (bool): see `get_prefixes()`
227 use_relative_indices (bool): see `get_prefixes()`
228 current_prefix: the current prefix so far collected from any parent
230 current_path: the current path of indices so far collected from any
236 if not current_path
and self.
selectedselected:
237 result.append((
"",
None))
241 names = collections.Counter(e.name
for e
in self.
childrenchildren.values())
243 relative_indices = collections.defaultdict(int)
246 for index, c
in sorted(self.
childrenchildren.items()):
248 full_path = current_path + (index,)
250 prefix = current_prefix + c.name
252 if always_include_indices
or names[c.name] > 1:
253 prefix +=
"_{}".format(relative_indices[c.name]
if use_relative_indices
else index)
255 relative_indices[c.name] += 1
259 result.append((prefix, full_path))
262 result += c.__walk(always_include_indices, use_relative_indices, prefix +
"_", full_path)
269 """Build a tree of selected particles from a `DecayString`
271 This will return a `DecayParticleNode` instance which is the top of a
272 tree of all the selected particles from the decat string.
275 decay_string (str): `DecayString` containing at least one selected particle
277 selected = get_hierarchy_of_decay(decay_string)
279 raise ValueError(
"No particle selected in decay string")
283 for path
in selected:
286 for index, name
in path:
288 if index
not in current.children:
289 current.children[index] = cls(name)
291 current = current.children[index]
294 current.selected =
True
300 def create_aliases_for_selected(
301 list_of_variables: List[str],
303 prefix: Optional[Union[str, List[str]]] =
None,
306 always_include_indices=
False,
307 use_relative_indices=
False
310 The function creates list of aliases for given variables so that they are calculated for
311 particles selected in decay string. That is for each particle selected in
312 the decay string an alias is created to calculate each variable in the
313 ``list_of_variables``.
315 If ``use_names=True`` (the default) then the names of the aliases are assigned as follows:
317 * If names are unambiguous, it's semi-laconic :doc:`DecayString` style: The
318 aliases will be prefixed with the names of all parent particle names
319 separated by underscore. For example given the decay string ``B0 -> [D0 -> ^pi+ K-] pi0``
320 the aliases for the ``pi+` will start with ``D0_pi_`` followed by the
323 >>> list_of_variables = ['M','p']
324 >>> decay_string = 'B0 -> [D0 -> ^pi+ K-] pi0'
325 >>> create_aliases_for_selected(list_of_variables, decay_string)
326 ['D0_pi_M', 'D0_pi_p']
327 >>> from variables import variables
328 >>> variables.printAliases()
329 [INFO] =========================
330 [INFO] Following aliases exists:
331 [INFO] 'D0_pi_M' --> 'daughter(0,daughter(0,M))'
332 [INFO] 'D0_pi_p' --> 'daughter(0,daughter(0,p))'
333 [INFO] =========================
336 * If names are ambiguous because there are multiple daughters with the same
337 name these particles will be followed by their daughter index. For example
338 given the decay string ``B0 -> [D0 -> ^pi+:1 ^pi-:2 ^pi0:3 ] ^pi0:4``
339 will create aliases with the following prefixes for particle with the
340 corresponding number as list name:
347 >>> list_of_variables = ['M','p']
348 >>> decay_string = 'B0 -> [D0 -> ^pi+ ^pi- ^pi0] ^pi0'
349 >>> create_aliases_for_selected(list_of_variables, decay_string)
350 ['D0_pi_0_M', 'D0_pi_0_p', 'D0_pi_1_M', 'D0_pi_1_p',
351 'D0_pi0_M', 'D0_pi0_p', 'pi0_M', 'pi0_p']
352 >>> from variables import variables
353 >>> variables.printAliases()
354 [INFO] =========================
355 [INFO] Following aliases exists:
356 [INFO] 'D0_pi0_M' --> 'daughter(0,daughter(2,M))'
357 [INFO] 'D0_pi0_p' --> 'daughter(0,daughter(2,p))'
358 [INFO] 'D0_pi_0_M' --> 'daughter(0,daughter(0,M))'
359 [INFO] 'D0_pi_0_p' --> 'daughter(0,daughter(0,p))'
360 [INFO] 'D0_pi_1_M' --> 'daughter(0,daughter(1,M))'
361 [INFO] 'D0_pi_1_p' --> 'daughter(0,daughter(1,p))'
362 [INFO] 'D0_pi_M' --> 'daughter(0,daughter(0,M))'
363 [INFO] 'D0_pi_p' --> 'daughter(0,daughter(0,p))'
364 [INFO] 'pi0_M' --> 'daughter(1,M)'
365 [INFO] 'pi0_p' --> 'daughter(1,p)'
366 [INFO] =========================
368 * The user can select to always include the index even for unambiguous
369 particles by passing ``always_include_indices=True``
371 * The user can choose two different numbering schemes: If
372 ``use_relative_indices=False`` the original decay string indices will be
373 used if a index is added to a particle name.
375 But if ``use_relative_indices=True`` the indices will just start at zero for each
376 particle which is part of the prefixes. For example for ``B0-> e+ ^e-``
378 >>> create_aliases_for_selected(['M'], 'B0-> mu+ e- ^e+ ^e-', use_relative_indices=False)
380 >>> create_aliases_for_selected(['M'], 'B0-> mu+ e- ^e+ ^e-', use_relative_indices=True)
383 If ``use_names=False`` the aliases will just start with the daughter indices
384 of all parent particles prefixed with a ``d`` and separated by underscore. So
385 for the previous example ``B0 -> [D0 -> ^pi+:1 ^pi-:2 ^pi0:3 ] ^pi0:4``
386 this would result in aliases starting with
393 In this case the ``always_include_indices`` and ``use_relative_indices``
394 arguments are ignored.
396 The naming can be modified by providing a custom prefix for each selected
397 particle. In this case the parameter ``prefix`` needs to be either a simple
398 string if only one particle is selected or a list of strings with one
399 prefix for each selected particle.
401 >>> list_of_variables = ['M','p']
402 >>> decay_string = 'B0 -> [D0 -> ^pi+ ^pi- pi0] pi0'
403 >>> create_aliases_for_selected(list_of_variables, decay_string, prefix=['pip', 'pim'])
404 ['pip_M', 'pip_p', 'pim_M', 'pim_p']
405 >>> from variables import variables
406 >>> variables.printAliases()
407 [INFO] =========================
408 [INFO] Following aliases exists:
409 [INFO] 'pim_M' --> 'daughter(0,daughter(1,M))'
410 [INFO] 'pim_p' --> 'daughter(0,daughter(1,p))'
411 [INFO] 'pip_M' --> 'daughter(0,daughter(0,M))'
412 [INFO] 'pip_p' --> 'daughter(0,daughter(0,p))'
413 [INFO] =========================
415 If the mother particle itself is selected the input list of variables will
416 also be added to the returned list of created aliases. If custom prefixes
417 are supplied then aliases will be created for the mother particle as well:
419 >>> create_aliases_for_selected(['M', 'p'], '^B0 -> pi+ ^pi-')
420 ['M', 'p', 'pi_M', 'pi_p']
421 >>> create_aliases_for_selected(['M', 'p'], '^B0 -> pi+ ^pi-', prefix=['MyB', 'MyPi'])
422 ['MyB_M', 'MyB_p', 'MyPi_M', 'MyPi_p']
425 list_of_variables (list(str)): list of variable names
426 decay_string (str): Decay string with selected particles
427 prefix (str, list(str)): Custom prefix for all selected particles
428 use_names (bool): Include the names of the particles in the aliases
429 always_include_indices (bool): If ``use_names=True`` include the decay
430 index of the particles in the alias name even if the particle could
431 be uniquely identified without them.
432 use_relative_indices (bool): If ``use_names=True`` use a relative
433 indicing which always starts at 0 for each particle appearing in
434 the alias names independent of the absolute position in the decay
438 list(str): new variables list
441 selected_particles = DecayParticleNode.build(decay_string)
442 prefixes = selected_particles.get_prefixes(always_include_indices, use_relative_indices)
445 prefixes, paths = zip(*prefixes)
450 include_indices =
False
452 if prefix
is not None:
454 if isinstance(prefix, str):
457 if len(prefix) != len(prefixes):
458 raise ValueError(
"Number of selected particles does not match number of supplied custom prefixes")
460 prefix_counts = collections.Counter(prefix)
461 if max(prefix_counts.values()) > 1:
462 raise ValueError(
"Prefixes need to be unique")
469 prefixes = [
""] * len(prefixes)
470 include_indices =
True
472 for prefix, path
in zip(prefixes, paths):
477 alias_list += create_aliases(list_of_variables,
"{variable}", prefix)
480 alias_list += list_of_variables
483 alias_list += create_daughter_aliases(list_of_variables, path, prefix, include_indices)
488 def create_mctruth_aliases(
489 list_of_variables: Iterable[str],
493 The function wraps variables from the list with 'matchedMC()'.
495 >>> list_of_variables = ['M','p']
496 >>> create_mctruth_aliases(list_of_variables)
498 >>> from variables import variables
499 >>> variables.printAliases()
500 [INFO] =========================
501 [INFO] Following aliases exists:
502 [INFO] 'mc_M' --> 'matchedMC(M)'
503 [INFO] 'mc_p' --> 'matchedMC(p)'
504 [INFO] =========================
508 list_of_variables (list(str)): list of variable names
511 list(str): list of created aliases
513 return create_aliases(list_of_variables,
'matchedMC({variable})', prefix)
516 def add_collection(list_of_variables: Iterable[str], collection_name: str) -> str:
518 The function creates variable collection from the given list of variables
519 It wraps the `VariableManager.addCollection` method which is not particularly user-friendly.
523 Defining the collection
524 >>> variables.utils.add_collection(['p','E'], "my_collection")
526 Passing it as an argument to variablesToNtuple
527 >>> modularAnalysis.variablesToNtuple(variables=['my_collection'], ...)
530 list_of_variables (list(str)): list of variable names
531 collection_name (str): name of the collection
534 str: name of the variable collection
537 _variablemanager.addCollection(collection_name, _std_vector(*tuple(list_of_variables)))
538 return collection_name
541 def create_isSignal_alias(aliasName, flags):
543 Make a `VariableManager` alias for a customized :b2:var:`isSignal`, which accepts specified mc match errors.
545 .. seealso:: see :doc:`MCMatching` for a definition of the mc match error flags.
547 The following code defines a new variable ``isSignalAcceptMissingGammaAndMissingNeutrino``, which is same
548 as :b2:var:`isSignal`, but also accepts missing gamma and missing neutrino
550 >>> create_isSignal_alias("isSignalAcceptMissingGammaAndMissingNeutrino", [16, 8])
553 ``isSignalAcceptMissingGammaAndMissingNeutrino`` =
554 :b2:var:`isSignalAcceptMissingGamma` || :b2:var:`isSignalAcceptMissingNeutrino`.
556 In the example above, create_isSignal_alias() creates ``isSignalAcceptMissingGammaAndMissingNeutrino`` by
557 unmasking (setting bits to zero)
558 the ``c_MissGamma`` bit (16 or 0b00010000) and ``c_MissNeutrino`` bit (8 or 0b00001000) in mcErrors.
560 For more information, please check this `example script <https://stash.desy.de/projects/B2/repos/basf2/
561 browse/analysis/examples/VariableManager/isSignalAcceptFlags.py>`_.
564 aliasName (str): the name of the alias to be set
565 flags (list(int)): a list of the bits to unmask
570 if isinstance(flag, int):
573 informationString =
"The type of input flags of create_isSignal_alias() should be integer."
574 informationString +=
"Now one of the input flags is " + str(int) +
" ."
575 raise ValueError(informationString)
577 _variablemanager.addAlias(aliasName,
"passesCut(unmask(mcErrors, %d) == %d)" % (mask, 0))
The DecayDescriptor stores information about a decay tree or parts of a decay tree.
children
mapping of children decayIndex->Node
selected
whether or not this particle is selected
def __walk(self, always_include_indices, use_relative_indices, current_prefix, current_path)
def get_prefixes(self, always_include_indices=False, use_relative_indices=False)
def build(cls, decay_string)