Belle II Software  release-06-00-14
fei.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 
11 
12 """
13 (Semi-)Leptonic Working Group Skims for missing energy modes that use the `FullEventInterpretation` (FEI) algorithm.
14 """
15 
16 from functools import lru_cache, wraps
17 
18 import basf2 as b2
19 import fei
20 import modularAnalysis as ma
21 from skim import BaseSkim, fancy_skim_header
22 from skim.utils.misc import _sphinxify_decay
23 from variables import variables as vm
24 
25 __liaison__ = "Shanette De La Motte <shanette.delamotte@adelaide.edu.au>"
26 _VALIDATION_SAMPLE = "mdst14.root"
27 
28 
29 def _merge_boolean_dicts(*dicts):
30  """Merge dicts of boolean, with `True` values taking precedence if values
31  differ.
32 
33  This is a utility function for combining FEI configs. It acts in the following
34  way:
35 
36  >>> d1 = {"neutralB": True, "chargedB": False, "hadronic": True}
37  >>> d2 = {"chargedB": True, "semileptonic": True}
38  >>> _merge_FEI_configs(d1, d2)
39  {"chargedB": True, "hadronic": True, "neutralB": True, "semileptonic": True}
40 
41  Parameters:
42  dicts (dict(str -> bool)): Any number of dicts of keyword-boolean pairs.
43 
44  Returns:
45  merged (dict(str -> bool)): A single dict, containing all the keys of the
46  input dicts.
47  """
48  keys = {k for d in dicts for k in d}
49  occurances = {k: [d for d in dicts if k in d] for k in keys}
50  merged = {k: any(d[k] for d in occurances[k]) for k in keys}
51 
52  # Sort the merged dict before returning
53  merged = dict(sorted(merged.items()))
54 
55  return merged
56 
57 
58 def _get_fei_channel_names(particleName, **kwargs):
59  """Create a list containing the decay strings of all decay channels available to a
60  particle. Any keyword arguments are passed to `fei.get_default_channels`.
61 
62  This is a utility function for autogenerating FEI skim documentation.
63 
64  Args:
65  particleName (str): the PDG name of a particle, e.g. ``'K+'``, ``'pi-'``, ``'D*0'``.
66  """
67  particleList = fei.get_default_channels(**kwargs)
68  particleDict = {particle.name: particle for particle in particleList}
69 
70  try:
71  particle = particleDict[particleName]
72  except KeyError:
73  print(f"Error! Couldn't find particle with name {particleName}")
74  return []
75 
76  channels = [channel.decayString for channel in particle.channels]
77  return channels
78 
79 
80 def _hash_dict(func):
81  """Wrapper for `functools.lru_cache` to deal with dictionaries. Dictionaries are
82  mutable, so cannot be cached. This wrapper turns all dict arguments into a hashable
83  dict type, so we can use caching.
84  """
85  class HashableDict(dict):
86  def __hash__(self):
87  return hash(frozenset(self.items()))
88 
89  @wraps(func)
90  def wrapped(*args, **kwargs):
91  args = tuple([HashableDict(arg) if isinstance(arg, dict) else arg for arg in args])
92  kwargs = {k: HashableDict(v) if isinstance(v, dict) else v for k, v in kwargs.items()}
93  return func(*args, **kwargs)
94  return wrapped
95 
96 
98  """Base class for FEI skims. Applies event-level pre-cuts and applies the FEI."""
99 
100  __authors__ = ["Racha Cheaib", "Hannah Wakeling", "Phil Grace"]
101  __contact__ = __liaison__
102  __category__ = "physics, Full Event Interpretation"
103 
104  FEIPrefix = "FEIv4_2021_MC14_release_05_01_12"
105  """Prefix label for the FEI training used in the FEI skims."""
106 
107  FEIChannelArgs = {}
108  """Dict of ``str -> bool`` pairs to be passed to `fei.get_default_channels`. When
109  inheriting from `BaseFEISkim`, override this value to apply the FEI for only *e.g.*
110  SL charged :math:`B`'s."""
111 
112  MergeDataStructures = {"FEIChannelArgs": _merge_boolean_dicts}
113 
114  NoisyModules = ["ParticleCombiner"]
115 
116  ApplyHLTHadronCut = True
117  produce_on_tau_samples = False # retention is very close to zero on taupair
118 
119  @staticmethod
120  @lru_cache()
121  def fei_precuts(path):
122  """
123  Skim pre-cuts are applied before running the FEI, to reduce computation time.
124  This setup function is run by all FEI skims, so they all have the save
125  event-level pre-cuts:
126 
127  * :math:`n_{\\text{cleaned tracks}} \\geq 3`
128  * :math:`n_{\\text{cleaned ECL clusters}} \\geq 3`
129  * :math:`\\text{Visible energy of event (CMS frame)}>4~{\\rm GeV}`
130  * :math:`2~{\\rm GeV}<E_{\\text{cleaned tracks & clusters in
131  ECL}}<7~{\\rm GeV}`
132 
133  We define "cleaned" tracks and clusters as:
134 
135  * Cleaned tracks (``pi+:FEI_cleaned``): :math:`d_0 < 0.5~{\\rm cm}`,
136  :math:`|z_0| < 2~{\\rm cm}`, and :math:`p_T > 0.1~{\\rm GeV}` * Cleaned ECL
137  clusters (``gamma:FEI_cleaned``): :math:`0.296706 < \\theta < 2.61799`, and
138  :math:`E>0.1~{\\rm GeV}`
139  """
140 
141  # Pre-selection cuts
142  CleanedTrackCuts = "abs(z0) < 2.0 and abs(d0) < 0.5 and pt > 0.1"
143  CleanedClusterCuts = "E > 0.1 and 0.296706 < theta < 2.61799"
144 
145  ma.fillParticleList(decayString="pi+:FEI_cleaned",
146  cut=CleanedTrackCuts, path=path)
147  ma.fillParticleList(decayString="gamma:FEI_cleaned",
148  cut=CleanedClusterCuts, path=path, loadPhotonBeamBackgroundMVA=False)
149 
150  ma.buildEventKinematics(inputListNames=["pi+:FEI_cleaned",
151  "gamma:FEI_cleaned"],
152  path=path)
153 
154  EventCuts = " and ".join(
155  [
156  f"nCleanedTracks({CleanedTrackCuts})>=3",
157  f"nCleanedECLClusters({CleanedClusterCuts})>=3",
158  "visibleEnergyOfEventCMS>4",
159  "2<E_ECL_FEI<7",
160  ]
161  )
162 
163  # NOTE: The FEI skims are somewhat complicated, and require some manual handling
164  # of conditional paths to avoid adding the FEI to the path twice. In general, DO
165  # NOT do this kind of path handling in your own skim. Instead, use:
166  # >>> path = self.skim_event_cuts(EventLevelCuts, path=path)
167  ConditionalPath = b2.Path()
168  eselect = path.add_module("VariableToReturnValue", variable=f"passesEventCut({EventCuts})")
169  eselect.if_value('=1', ConditionalPath, b2.AfterConditionPath.CONTINUE)
170 
171  return ConditionalPath
172 
173  # This is a cached static method so that we can avoid adding FEI path twice.
174  # In combined skims, FEIChannelArgs must be combined across skims first, so that all
175  # the required particles are included in the FEI.
176  @staticmethod
177  @_hash_dict
178  @lru_cache()
179  def run_fei_for_skims(FEIChannelArgs, FEIPrefix, analysisGlobaltag, *, path):
180  """Reconstruct hadronic and semileptonic :math:`B^0` and :math:`B^+` tags using
181  the generically trained FEI.
182 
183  Parameters:
184  FEIChannelArgs (dict(str, bool)): A dict of keyword-boolean pairs to be
185  passed to `fei.get_default_channels`.
186  FEIPrefix (str): Prefix label for the FEI training used in the FEI skims.
187  path (`basf2.Path`): The skim path to be processed.
188  """
189  # Run FEI
190  if analysisGlobaltag is None:
191  b2.B2FATAL("The analysis globaltag is not set in the FEI skim.")
192  b2.conditions.prepend_globaltag(analysisGlobaltag)
193  particles = fei.get_default_channels(**FEIChannelArgs)
194  configuration = fei.config.FeiConfiguration(
195  prefix=FEIPrefix,
196  training=False,
197  monitor=False)
198  feistate = fei.get_path(particles, configuration)
199  path.add_path(feistate.path)
200 
201  @staticmethod
202  @_hash_dict
203  @lru_cache()
204  def setup_fei_aliases(FEIChannelArgs):
205  # Aliases for pre-FEI event-level cuts
206  vm.addAlias("E_ECL_pi_FEI",
207  "totalECLEnergyOfParticlesInList(pi+:FEI_cleaned)")
208  vm.addAlias("E_ECL_gamma_FEI",
209  "totalECLEnergyOfParticlesInList(gamma:FEI_cleaned)")
210  vm.addAlias("E_ECL_FEI", "formula(E_ECL_pi_FEI+E_ECL_gamma_FEI)")
211 
212  # Aliases for variables available after running the FEI
213  vm.addAlias("sigProb", "extraInfo(SignalProbability)")
214  vm.addAlias("log10_sigProb", "log10(extraInfo(SignalProbability))")
215  vm.addAlias("dmID", "extraInfo(decayModeID)")
216  vm.addAlias("decayModeID", "extraInfo(decayModeID)")
217 
218  if "semileptonic" in FEIChannelArgs and FEIChannelArgs["semileptonic"]:
219  # Aliases specific to SL FEI
220  vm.addAlias("cosThetaBY", "cosThetaBetweenParticleAndNominalB")
221  vm.addAlias("d1_p_CMSframe", "useCMSFrame(daughter(1,p))")
222  vm.addAlias("d2_p_CMSframe", "useCMSFrame(daughter(2,p))")
223  vm.addAlias(
224  "p_lepton_CMSframe",
225  "conditionalVariableSelector(dmID<4, d1_p_CMSframe, d2_p_CMSframe)"
226  )
227 
228  def additional_setup(self, path):
229  """Apply pre-FEI event-level cuts and apply the FEI. This setup function is run
230  by all FEI skims, so they all have the save event-level pre-cuts.
231 
232  This function passes `FEIChannelArgs` to the cached function `run_fei_for_skims`
233  to avoid applying the FEI twice.
234 
235  See also:
236  `fei_precuts` for event-level cut definitions.
237  """
238  self.setup_fei_aliasessetup_fei_aliases(self.FEIChannelArgsFEIChannelArgs)
239  path = self.fei_precutsfei_precuts(path)
240  # The FEI skims require some manual handling of paths that is not necessary in
241  # any other skim.
242  self._ConditionalPath_ConditionalPath_ConditionalPath = path
243 
244  self.run_fei_for_skimsrun_fei_for_skims(self.FEIChannelArgsFEIChannelArgs, self.FEIPrefixFEIPrefix, self.analysisGlobaltaganalysisGlobaltag, path=path)
245 
246 
247 def _FEI_skim_header(ParticleNames):
248  """Decorator factory for applying the `fancy_skim_header` header and replacing
249  <CHANNELS> in the class docstring with a list of FEI channels.
250 
251  The list is numbered with all of the corresponding decay mode IDs, and the decay
252  modes are formatted in beautiful LaTeX.
253 
254  .. code-block:: python
255 
256  @FEI_skim_header("B0")
257  class feiSLB0(BaseFEISkim):
258  # docstring here including the string '<CHANNELS>' somewhere
259 
260  Parameters:
261  ParticleNames (str, list(str)): One of either ``B0`` or ``B+``, or a list of both.
262  """
263 
264  def decorator(SkimClass):
265  if isinstance(ParticleNames, str):
266  particles = [ParticleNames]
267  else:
268  particles = ParticleNames
269 
270  ChannelsString = "List of reconstructed channels and corresponding decay mode IDs:"
271  for particle in particles:
272  channels = _get_fei_channel_names(particle, **SkimClass.FEIChannelArgs)
273  FormattedChannels = [_sphinxify_decay(channel) for channel in channels]
274  ChannelList = "\n".join(
275  [f" {dmID}. {channel}"
276  for (dmID, channel) in enumerate(FormattedChannels)]
277  )
278  if len(particles) == 1:
279  ChannelsString += "\n\n" + ChannelList
280  else:
281  ChannelsString += f"\n\n ``{particle}`` channels:\n\n" + ChannelList
282 
283  if SkimClass.__doc__ is None:
284  return SkimClass
285  else:
286  SkimClass.__doc__ = SkimClass.__doc__.replace("<CHANNELS>", ChannelsString)
287 
288  return fancy_skim_header(SkimClass)
289 
290  return decorator
291 
292 
293 @_FEI_skim_header("B0")
295  """
296  Tag side :math:`B` cuts:
297 
298  * :math:`M_{\\text{bc}} > 5.24~{\\rm GeV}`
299  * :math:`|\\Delta E| < 0.2~{\\rm GeV}`
300  * :math:`\\text{signal probability} > 0.001` (omitted for decay mode 23)
301 
302  All available FEI :math:`B^0` hadronic tags are reconstructed. From `Thomas Keck's
303  thesis <https://docs.belle2.org/record/275/files/BELLE2-MTHESIS-2015-001.pdf>`_,
304  "the channel :math:`B^0 \\to \\overline{D}^0 \\pi^0` was used by the FR, but is not
305  yet used in the FEI due to unexpected technical restrictions in the KFitter
306  algorithm".
307 
308  <CHANNELS>
309 
310  See also:
311  `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
312  event-level cuts made before applying the FEI.
313  """
314  __description__ = "FEI-tagged neutral :math:`B`'s decaying hadronically."
315  validation_sample = _VALIDATION_SAMPLE
316 
317  FEIChannelArgs = {
318  "neutralB": True,
319  "chargedB": False,
320  "hadronic": True,
321  "semileptonic": False,
322  "KLong": False,
323  "baryonic": True
324  }
325 
326  def build_lists(self, path):
327  ma.applyCuts("B0:generic", "Mbc>5.24", path=path)
328  ma.applyCuts("B0:generic", "abs(deltaE)<0.200", path=path)
329  ma.applyCuts("B0:generic", "sigProb>0.001 or extraInfo(dmID)==23", path=path)
330 
331  return ["B0:generic"]
332 
333  def validation_histograms(self, path):
334  # NOTE: the validation package is not part of the light releases, so this import
335  # must be made here rather than at the top of the file.
336  from validation_tools.metadata import create_validation_histograms
337 
338  vm.addAlias('sigProb', 'extraInfo(SignalProbability)')
339  vm.addAlias('log10_sigProb', 'log10(extraInfo(SignalProbability))')
340  vm.addAlias('d0_massDiff', 'daughter(0,massDifference(0))')
341  vm.addAlias('d0_M', 'daughter(0,M)')
342  vm.addAlias('decayModeID', 'extraInfo(decayModeID)')
343  vm.addAlias('nDaug', 'countDaughters(1>0)') # Dummy cut so all daughters are selected.
344 
345  histogramFilename = f"{self}_Validation.root"
346 
347  create_validation_histograms(
348  rootfile=histogramFilename,
349  particlelist='B0:generic',
350  variables_1d=[
351  ('sigProb', 100, 0.0, 1.0, 'Signal probability', __liaison__,
352  'Signal probability of the reconstructed tag B candidates',
353  'Most around zero, with a tail at non-zero values.', 'Signal probability', 'Candidates', 'logy'),
354  ('nDaug', 6, 0.0, 6, 'Number of daughters of tag B', __liaison__,
355  'Number of daughters of tag B', 'Some distribution of number of daughters', 'n_{daughters}', 'Candidates'),
356  ('d0_massDiff', 100, 0.0, 0.5, 'Mass difference of D* and D', __liaison__,
357  'Mass difference of D^{*} and D', 'Peak at 0.14 GeV', 'm(D^{*})-m(D) [GeV]', 'Candidates', 'shifter'),
358  ('d0_M', 100, 0.0, 3.0, 'Mass of zeroth daughter (D* or D)', __liaison__,
359  'Mass of zeroth daughter of tag B (either a $D^{*}$ or a D)', 'Peaks at 1.86 GeV and 2.00 GeV',
360  'm(D^{(*)}) [GeV]', 'Candidates', 'shifter'),
361  ('deltaE', 40, -0.2, 0.2, '#Delta E', __liaison__,
362  '$\\Delta E$ of event', 'Peak around zero', '#Delta E [GeV]', 'Candidates', 'shifter'),
363  ('Mbc', 40, 5.2, 5.3, 'Mbc', __liaison__,
364  'Beam-constrained mass of event', 'Peaking around B mass (5.28 GeV)', 'M_{bc} [GeV]', 'Candidates', 'shifter')],
365  variables_2d=[('deltaE', 100, -0.2, 0.2, 'Mbc', 100, 5.2, 5.3, 'Mbc vs deltaE', __liaison__,
366  'Plot of the $\\Delta E$ of the event against the beam constrained mass',
367  'Peak of $\\Delta E$ around zero, and $M_{bc}$ around B mass (5.28 GeV)',
368  '#Delta E [GeV]', 'M_{bc} [GeV]', 'colz'),
369  ('decayModeID', 26, 0, 26, 'log10_sigProb', 100, -3.0, 0.0,
370  'Signal probability for each decay mode ID', __liaison__,
371  'Signal probability for each decay mode ID',
372  'Some distribtuion of candidates in the first few decay mode IDs',
373  'Decay mode ID', '#log_10(signal probability)', 'colz')],
374  path=path)
375 
376 
377 @_FEI_skim_header("B+")
379  """
380  Tag side :math:`B` cuts:
381 
382  * :math:`M_{\\text{bc}} > 5.24~{\\rm GeV}`
383  * :math:`|\\Delta E| < 0.2~{\\rm GeV}`
384  * :math:`\\text{signal probability} > 0.001` (omitted for decay mode 25)
385 
386  All available FEI :math:`B^+` hadronic tags are reconstructed.
387 
388  <CHANNELS>
389 
390  See also:
391  `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
392  event-level cuts made before applying the FEI.
393  """
394  __description__ = "FEI-tagged charged :math:`B`'s decaying hadronically."
395  validation_sample = _VALIDATION_SAMPLE
396 
397  FEIChannelArgs = {
398  "neutralB": False,
399  "chargedB": True,
400  "hadronic": True,
401  "semileptonic": False,
402  "KLong": False,
403  "baryonic": True
404  }
405 
406  def build_lists(self, path):
407  ma.applyCuts("B+:generic", "Mbc>5.24", path=path)
408  ma.applyCuts("B+:generic", "abs(deltaE)<0.200", path=path)
409  ma.applyCuts("B+:generic", "sigProb>0.001 or extraInfo(dmID)==25", path=path)
410 
411  return ["B+:generic"]
412 
413  def validation_histograms(self, path):
414  # NOTE: the validation package is not part of the light releases, so this import
415  # must be made here rather than at the top of the file.
416  from validation_tools.metadata import create_validation_histograms
417 
418  vm.addAlias('sigProb', 'extraInfo(SignalProbability)')
419  vm.addAlias('log10_sigProb', 'log10(extraInfo(SignalProbability))')
420  vm.addAlias('d0_massDiff', 'daughter(0,massDifference(0))')
421  vm.addAlias('d0_M', 'daughter(0,M)')
422  vm.addAlias('decayModeID', 'extraInfo(decayModeID)')
423  vm.addAlias('nDaug', 'countDaughters(1>0)') # Dummy cut so all daughters are selected.
424 
425  histogramFilename = f"{self}_Validation.root"
426 
427  create_validation_histograms(
428  rootfile=histogramFilename,
429  particlelist='B+:generic',
430  variables_1d=[
431  ('sigProb', 100, 0.0, 1.0, 'Signal probability', __liaison__,
432  'Signal probability of the reconstructed tag B candidates', 'Most around zero, with a tail at non-zero values.',
433  'Signal probability', 'Candidates', 'logy'),
434  ('nDaug', 6, 0.0, 6, 'Number of daughters of tag B', __liaison__,
435  'Number of daughters of tag B', 'Some distribution of number of daughters', 'n_{daughters}', 'Candidates'),
436  ('d0_massDiff', 100, 0.0, 0.5, 'Mass difference of D* and D', __liaison__,
437  'Mass difference of D^{*} and D', 'Peak at 0.14 GeV', 'm(D^{*})-m(D) [GeV]', 'Candidates', 'shifter'),
438  ('d0_M', 100, 0.0, 3.0, 'Mass of zeroth daughter (D* or D)', __liaison__,
439  'Mass of zeroth daughter of tag B (either a $D^{*}$ or a D)', 'Peaks at 1.86 GeV and 2.00 GeV',
440  'm(D^{(*)}) [GeV]', 'Candidates', 'shifter'),
441  ('deltaE', 40, -0.2, 0.2, '#Delta E', __liaison__,
442  '$\\Delta E$ of event', 'Peak around zero', '#Delta E [GeV]', 'Candidates', 'shifter'),
443  ('Mbc', 40, 5.2, 5.3, 'Mbc', __liaison__,
444  'Beam-constrained mass of event', 'Peak around B mass (5.28 GeV)', 'M_{bc} [GeV]', 'Candidates', 'shifter')],
445  variables_2d=[('deltaE', 100, -0.2, 0.2, 'Mbc', 100, 5.2, 5.3, 'Mbc vs deltaE', __liaison__,
446  'Plot of the $\\Delta E$ of the event against the beam constrained mass',
447  'Peak of $\\Delta E$ around zero, and $M_{bc}$ around B mass (5.28 GeV)',
448  '#Delta E [GeV]', 'M_{bc} [GeV]', 'colz'),
449  ('decayModeID', 29, 0, 29, 'log10_sigProb', 100, -3.0, 0.0,
450  'Signal probability for each decay mode ID', __liaison__,
451  'Signal probability for each decay mode ID',
452  'Some distribtuion of candidates in the first few decay mode IDs',
453  'Decay mode ID', '#log_10(signal probability)', 'colz')],
454  path=path)
455 
456 
457 @_FEI_skim_header("B0")
459  """
460  Tag side :math:`B` cuts:
461 
462  * :math:`-4 < \\cos\\theta_{BY} < 3`
463  * :math:`\\log_{10}(\\text{signal probability}) > -2.4`
464  * :math:`p_{\\ell}^{*} > 1.0~{\\rm GeV}` in CMS frame
465 
466  SL :math:`B^0` tags are reconstructed. Hadronic :math:`B` with SL :math:`D` are not
467  reconstructed, as these are rare and time-intensive.
468 
469  <CHANNELS>
470 
471  See also:
472  `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
473  event-level cuts made before applying the FEI.
474  """
475  __description__ = "FEI-tagged neutral :math:`B`'s decaying semileptonically."
476  validation_sample = _VALIDATION_SAMPLE
477 
478  FEIChannelArgs = {
479  "neutralB": True,
480  "chargedB": False,
481  "hadronic": False,
482  "semileptonic": True,
483  "KLong": False,
484  "baryonic": True,
485  "removeSLD": True
486  }
487 
488  def build_lists(self, path):
489  ma.applyCuts("B0:semileptonic", "dmID<8", path=path)
490  ma.applyCuts("B0:semileptonic", "log10(sigProb)>-2.4", path=path)
491  ma.applyCuts("B0:semileptonic", "-4.0<cosThetaBY<3.0", path=path)
492  ma.applyCuts("B0:semileptonic", "p_lepton_CMSframe>1.0", path=path)
493 
494  return ["B0:semileptonic"]
495 
496  def validation_histograms(self, path):
497  # NOTE: the validation package is not part of the light releases, so this import
498  # must be made here rather than at the top of the file.
499  from validation_tools.metadata import create_validation_histograms
500 
501  vm.addAlias('sigProb', 'extraInfo(SignalProbability)')
502  vm.addAlias('log10_sigProb', 'log10(extraInfo(SignalProbability))')
503  vm.addAlias('d0_massDiff', 'daughter(0,massDifference(0))')
504  vm.addAlias('d0_M', 'daughter(0,M)')
505  vm.addAlias('decayModeID', 'extraInfo(decayModeID)')
506  vm.addAlias('nDaug', 'countDaughters(1>0)') # Dummy cut so all daughters are selected.
507 
508  histogramFilename = f"{self}_Validation.root"
509 
510  create_validation_histograms(
511  rootfile=histogramFilename,
512  particlelist='B0:semileptonic',
513  variables_1d=[
514  ('sigProb', 100, 0.0, 1.0, 'Signal probability', __liaison__,
515  'Signal probability of the reconstructed tag B candidates', 'Most around zero, with a tail at non-zero values.',
516  'Signal probability', 'Candidates', 'logy'),
517  ('nDaug', 6, 0.0, 6, 'Number of daughters of tag B', __liaison__,
518  'Number of daughters of tag B', 'Some distribution of number of daughters', 'n_{daughters}', 'Candidates'),
519  ('cosThetaBetweenParticleAndNominalB', 100, -6.0, 4.0, '#cos#theta_{BY}', __liaison__,
520  'Cosine of angle between the reconstructed B and the nominal B', 'Distribution peaking between -1 and 1',
521  '#cos#theta_{BY}', 'Candidates'),
522  ('d0_massDiff', 100, 0.0, 0.5, 'Mass difference of D* and D', __liaison__,
523  'Mass difference of $D^{*}$ and D', 'Peak at 0.14 GeV', 'm(D^{*})-m(D) [GeV]', 'Candidates', 'shifter'),
524  ('d0_M', 100, 0.0, 3.0, 'Mass of zeroth daughter (D* or D)', __liaison__,
525  'Mass of zeroth daughter of tag B (either a $D^{*}$ or a D)', 'Peaks at 1.86 GeV and 2.00 GeV',
526  'm(D^{(*)}) [GeV]', 'Candidates', 'shifter')],
527  variables_2d=[('decayModeID', 8, 0, 8, 'log10_sigProb', 100, -3.0, 0.0,
528  'Signal probability for each decay mode ID', __liaison__,
529  'Signal probability for each decay mode ID',
530  'Some distribtuion of candidates in the first few decay mode IDs',
531  'Decay mode ID', '#log_10(signal probability)', 'colz')],
532  path=path)
533 
534 
535 @_FEI_skim_header("B+")
537  """
538  Tag side :math:`B` cuts:
539 
540  * :math:`-4 < \\cos\\theta_{BY} < 3`
541  * :math:`\\log_{10}(\\text{signal probability}) > -2.4`
542  * :math:`p_{\\ell}^{*} > 1.0~{\\rm GeV}` in CMS frame
543 
544  SL :math:`B^+` tags are reconstructed. Hadronic :math:`B^+` with SL :math:`D` are
545  not reconstructed, as these are rare and time-intensive.
546 
547  <CHANNELS>
548 
549  See also:
550  `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
551  event-level cuts made before applying the FEI.
552  """
553  __description__ = "FEI-tagged charged :math:`B`'s decaying semileptonically."
554  validation_sample = _VALIDATION_SAMPLE
555 
556  FEIChannelArgs = {
557  "neutralB": False,
558  "chargedB": True,
559  "hadronic": False,
560  "semileptonic": True,
561  "KLong": False,
562  "baryonic": True,
563  "removeSLD": True
564  }
565 
566  def build_lists(self, path):
567  ma.applyCuts("B+:semileptonic", "dmID<8", path=path)
568  ma.applyCuts("B+:semileptonic", "log10_sigProb>-2.4", path=path)
569  ma.applyCuts("B+:semileptonic", "-4.0<cosThetaBY<3.0", path=path)
570  ma.applyCuts("B+:semileptonic", "p_lepton_CMSframe>1.0", path=path)
571 
572  return ["B+:semileptonic"]
573 
574  def validation_histograms(self, path):
575  # NOTE: the validation package is not part of the light releases, so this import
576  # must be made here rather than at the top of the file.
577  from validation_tools.metadata import create_validation_histograms
578 
579  vm.addAlias('sigProb', 'extraInfo(SignalProbability)')
580  vm.addAlias('log10_sigProb', 'log10(extraInfo(SignalProbability))')
581  vm.addAlias('d0_massDiff', 'daughter(0,massDifference(0))')
582  vm.addAlias('d0_M', 'daughter(0,M)')
583  vm.addAlias('decayModeID', 'extraInfo(decayModeID)')
584  vm.addAlias('nDaug', 'countDaughters(1>0)') # Dummy cut so all daughters are selected.
585 
586  histogramFilename = f"{self}_Validation.root"
587 
588  create_validation_histograms(
589  rootfile=histogramFilename,
590  particlelist='B+:semileptonic',
591  variables_1d=[
592  ('sigProb', 100, 0.0, 1.0, 'Signal probability', __liaison__,
593  'Signal probability of the reconstructed tag B candidates',
594  'Most around zero, with a tail at non-zero values.', 'Signal probability', 'Candidates', 'logy'),
595  ('nDaug', 6, 0.0, 6, 'Number of daughters of tag B', __liaison__,
596  'Number of daughters of tag B', 'Some distribution of number of daughters', 'n_{daughters}', 'Candidates'),
597  ('cosThetaBetweenParticleAndNominalB', 100, -6.0, 4.0, '#cos#theta_{BY}', __liaison__,
598  'Cosine of angle between the reconstructed B and the nominal B', 'Distribution peaking between -1 and 1',
599  '#cos#theta_{BY}', 'Candidates'),
600  ('d0_massDiff', 100, 0.0, 0.5, 'Mass difference of D* and D', __liaison__,
601  'Mass difference of $D^{*}$ and D', 'Peak at 0.14 GeV', 'm(D^{*})-m(D) [GeV]', 'Candidates', 'shifter'),
602  ('d0_M', 100, 0.0, 3.0, 'Mass of zeroth daughter (D* or D)', __liaison__,
603  'Mass of zeroth daughter of tag B (either a $D^{*}$ or a D)', 'Peaks at 1.86 GeV and 2.00 GeV',
604  'm(D^{(*)}) [GeV]', 'Candidates', 'shifter')],
605  variables_2d=[('decayModeID', 8, 0, 8, 'log10_sigProb', 100, -3.0, 0.0,
606  'Signal probability for each decay mode ID', __liaison__,
607  'Signal probability for each decay mode ID',
608  'Some distribtuion of candidates in the first few decay mode IDs',
609  'Decay mode ID', '#log_10(signal probability)', 'colz')],
610  path=path)
611 
612 
613 @_FEI_skim_header(["B0", "B+"])
615  """
616  Tag side :math:`B` cuts:
617 
618  * :math:`M_{\\text{bc}} > 5.24~{\\rm GeV}`
619  * :math:`|\\Delta E| < 0.2~{\\rm GeV}`
620  * :math:`\\text{signal probability} > 0.001` (omitted for decay mode 23 for
621  :math:`B^+`, and decay mode 25 for :math:`B^0`)
622 
623  All available FEI :math:`B^0` and :math:`B^+` hadronic tags are reconstructed. From
624  `Thomas Keck's thesis
625  <https://docs.belle2.org/record/275/files/BELLE2-MTHESIS-2015-001.pdf>`_, "the
626  channel :math:`B^0 \\to \\overline{D}^0 \\pi^0` was used by the FR, but is not yet
627  used in the FEI due to unexpected technical restrictions in the KFitter algorithm".
628 
629  <CHANNELS>
630 
631  See also:
632  `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
633  event-level cuts made before applying the FEI.
634  """
635  __description__ = "FEI-tagged neutral and charged :math:`B`'s decaying hadronically."
636 
637  FEIChannelArgs = {
638  "neutralB": True,
639  "chargedB": True,
640  "hadronic": True,
641  "semileptonic": False,
642  "KLong": False,
643  "baryonic": True
644  }
645 
646  def build_lists(self, path):
647  ma.copyList("B0:feiHadronic", "B0:generic", path=path)
648  ma.copyList("B+:feiHadronic", "B+:generic", path=path)
649  HadronicBLists = ["B0:feiHadronic", "B+:feiHadronic"]
650 
651  for BList in HadronicBLists:
652  ma.applyCuts(BList, "Mbc>5.24", path=path)
653  ma.applyCuts(BList, "abs(deltaE)<0.200", path=path)
654 
655  ma.applyCuts("B+:feiHadronic", "sigProb>0.001 or extraInfo(dmID)==25", path=path)
656  ma.applyCuts("B0:feiHadronic", "sigProb>0.001 or extraInfo(dmID)==23", path=path)
657 
658  return HadronicBLists
659 
660 
661 @_FEI_skim_header(["B0", "B+"])
663  """
664  Tag side :math:`B` cuts:
665 
666  * :math:`-4 < \\cos\\theta_{BY} < 3`
667  * :math:`\\log_{10}(\\text{signal probability}) > -2.4`
668  * :math:`p_{\\ell}^{*} > 1.0~{\\rm GeV}` in CMS frame
669 
670  SL :math:`B^0` and :math:`B^+` tags are reconstructed. Hadronic :math:`B` with SL
671  :math:`D` are not reconstructed, as these are rare and time-intensive.
672 
673  <CHANNELS>
674 
675  See also:
676  `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
677  event-level cuts made before applying the FEI.
678  """
679  __description__ = "FEI-tagged neutral and charged :math:`B`'s decaying semileptonically."
680 
681  FEIChannelArgs = {
682  "neutralB": True,
683  "chargedB": True,
684  "hadronic": False,
685  "semileptonic": True,
686  "KLong": False,
687  "baryonic": True,
688  "removeSLD": True
689  }
690 
691  def build_lists(self, path):
692  ma.copyList("B0:feiSL", "B0:semileptonic", path=path)
693  ma.copyList("B+:feiSL", "B+:semileptonic", path=path)
694  SLBLists = ["B0:feiSL", "B+:feiSL"]
695 
696  Bcuts = ["log10_sigProb>-2.4", "-4.0<cosThetaBY<3.0", "p_lepton_CMSframe>1.0"]
697 
698  for BList in SLBLists:
699  for cut in Bcuts:
700  ma.applyCuts(BList, cut, path=path)
701 
702  return SLBLists
def run_fei_for_skims(FEIChannelArgs, FEIPrefix, analysisGlobaltag, *path)
Definition: fei.py:179
_ConditionalPath
Definition: fei.py:242
def additional_setup(self, path)
Definition: fei.py:228
string FEIPrefix
Definition: fei.py:104
def setup_fei_aliases(FEIChannelArgs)
Definition: fei.py:204
def fei_precuts(path)
Definition: fei.py:121
dictionary FEIChannelArgs
Definition: fei.py:107
def build_lists(self, path)
Definition: fei.py:326
def validation_histograms(self, path)
Definition: fei.py:333
def build_lists(self, path)
Definition: fei.py:406
def validation_histograms(self, path)
Definition: fei.py:413
def build_lists(self, path)
Definition: fei.py:646
def build_lists(self, path)
Definition: fei.py:488
def validation_histograms(self, path)
Definition: fei.py:496
def build_lists(self, path)
Definition: fei.py:566
def validation_histograms(self, path)
Definition: fei.py:574
def build_lists(self, path)
Definition: fei.py:691