13 (Semi-)Leptonic Working Group Skims for missing energy modes that use the `FullEventInterpretation` (FEI) algorithm.
16 from functools
import lru_cache, wraps
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
25 __liaison__ =
"Shanette De La Motte <shanette.delamotte@adelaide.edu.au>"
26 _VALIDATION_SAMPLE =
"mdst14.root"
29 def _merge_boolean_dicts(*dicts):
30 """Merge dicts of boolean, with `True` values taking precedence if values
33 This is a utility function for combining FEI configs. It acts in the following
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}
42 dicts (dict(str -> bool)): Any number of dicts of keyword-boolean pairs.
45 merged (dict(str -> bool)): A single dict, containing all the keys of the
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}
53 merged = dict(sorted(merged.items()))
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`.
62 This is a utility function for autogenerating FEI skim documentation.
65 particleName (str): the PDG name of a particle, e.g. ``'K+'``, ``'pi-'``, ``'D*0'``.
67 particleList = fei.get_default_channels(**kwargs)
68 particleDict = {particle.name: particle
for particle
in particleList}
71 particle = particleDict[particleName]
73 print(f
"Error! Couldn't find particle with name {particleName}")
76 channels = [channel.decayString
for channel
in particle.channels]
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.
85 class HashableDict(dict):
87 return hash(frozenset(self.items()))
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)
98 """Base class for FEI skims. Applies event-level pre-cuts and applies the FEI."""
100 __authors__ = [
"Racha Cheaib",
"Hannah Wakeling",
"Phil Grace"]
101 __contact__ = __liaison__
102 __category__ =
"physics, Full Event Interpretation"
104 FEIPrefix =
"FEIv4_2022_MC15_light-2205-abys"
105 """Prefix label for the FEI training used in the FEI skims."""
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."""
112 MergeDataStructures = {
"FEIChannelArgs": _merge_boolean_dicts}
114 NoisyModules = [
"ParticleCombiner"]
116 ApplyHLTHadronCut =
True
117 produce_on_tau_samples =
False
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:
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}`
131 We define "cleaned" tracks and clusters as:
133 * Cleaned tracks (``pi+:FEI_cleaned``): :math:`d_0 < 0.5~{\\rm cm}`,
134 :math:`|z_0| < 2~{\\rm cm}`, and :math:`p_T > 0.1~{\\rm GeV}` * Cleaned ECL
135 clusters (``gamma:FEI_cleaned``): :math:`0.296706 < \\theta < 2.61799`, and
136 :math:`E>0.1~{\\rm GeV}`
140 CleanedTrackCuts =
"abs(z0) < 2.0 and abs(d0) < 0.5 and pt > 0.1"
141 CleanedClusterCuts =
"E > 0.1 and 0.296706 < theta < 2.61799"
143 ma.fillParticleList(decayString=
"pi+:FEI_cleaned",
144 cut=CleanedTrackCuts, path=path)
145 ma.fillParticleList(decayString=
"gamma:FEI_cleaned",
146 cut=CleanedClusterCuts, path=path, loadPhotonBeamBackgroundMVA=
False)
148 ma.buildEventKinematics(inputListNames=[
"pi+:FEI_cleaned",
149 "gamma:FEI_cleaned"],
152 EventCuts =
" and ".join(
154 f
"nCleanedTracks({CleanedTrackCuts})>=3",
155 f
"nCleanedECLClusters({CleanedClusterCuts})>=3",
156 "visibleEnergyOfEventCMS>4",
164 ConditionalPath = b2.Path()
165 eselect = path.add_module(
"VariableToReturnValue", variable=f
"passesEventCut({EventCuts})")
166 eselect.if_value(
'=1', ConditionalPath, b2.AfterConditionPath.CONTINUE)
168 return ConditionalPath
177 """Reconstruct hadronic and semileptonic :math:`B^0` and :math:`B^+` tags using
178 the generically trained FEI.
181 FEIChannelArgs (dict(str, bool)): A dict of keyword-boolean pairs to be
182 passed to `fei.get_default_channels`.
183 FEIPrefix (str): Prefix label for the FEI training used in the FEI skims.
184 path (`basf2.Path`): The skim path to be processed.
187 if analysisGlobaltag
is None:
188 b2.B2FATAL(
"The analysis globaltag is not set in the FEI skim.")
189 b2.conditions.prepend_globaltag(analysisGlobaltag)
190 particles = fei.get_default_channels(**FEIChannelArgs)
191 configuration = fei.config.FeiConfiguration(
195 feistate = fei.get_path(particles, configuration)
196 path.add_path(feistate.path)
201 def setup_fei_aliases(FEIChannelArgs):
203 vm.addAlias(
"E_ECL_pi_FEI",
204 "totalECLEnergyOfParticlesInList(pi+:FEI_cleaned)")
205 vm.addAlias(
"E_ECL_gamma_FEI",
206 "totalECLEnergyOfParticlesInList(gamma:FEI_cleaned)")
207 vm.addAlias(
"E_ECL_FEI",
"formula(E_ECL_pi_FEI+E_ECL_gamma_FEI)")
210 vm.addAlias(
"sigProb",
"extraInfo(SignalProbability)")
211 vm.addAlias(
"log10_sigProb",
"log10(extraInfo(SignalProbability))")
212 vm.addAlias(
"dmID",
"extraInfo(decayModeID)")
213 vm.addAlias(
"decayModeID",
"extraInfo(decayModeID)")
215 if "semileptonic" in FEIChannelArgs
and FEIChannelArgs[
"semileptonic"]:
217 vm.addAlias(
"cosThetaBY",
"cosThetaBetweenParticleAndNominalB")
218 vm.addAlias(
"d1_p_CMSframe",
"useCMSFrame(daughter(1,p))")
219 vm.addAlias(
"d2_p_CMSframe",
"useCMSFrame(daughter(2,p))")
222 "conditionalVariableSelector(dmID<4, d1_p_CMSframe, d2_p_CMSframe)"
226 """Apply pre-FEI event-level cuts and apply the FEI. This setup function is run
227 by all FEI skims, so they all have the save event-level pre-cuts.
229 This function passes `FEIChannelArgs` to the cached function `run_fei_for_skims`
230 to avoid applying the FEI twice.
233 `fei_precuts` for event-level cut definitions.
244 def _FEI_skim_header(ParticleNames):
245 """Decorator factory for applying the `fancy_skim_header` header and replacing
246 <CHANNELS> in the class docstring with a list of FEI channels.
248 The list is numbered with all of the corresponding decay mode IDs, and the decay
249 modes are formatted in beautiful LaTeX.
251 .. code-block:: python
253 @FEI_skim_header("B0")
254 class feiSLB0(BaseFEISkim):
255 # docstring here including the string '<CHANNELS>' somewhere
258 ParticleNames (str, list(str)): One of either ``B0`` or ``B+``, or a list of both.
261 def decorator(SkimClass):
262 if isinstance(ParticleNames, str):
263 particles = [ParticleNames]
265 particles = ParticleNames
267 ChannelsString =
"List of reconstructed channels and corresponding decay mode IDs:"
268 for particle
in particles:
269 channels = _get_fei_channel_names(particle, **SkimClass.FEIChannelArgs)
270 FormattedChannels = [_sphinxify_decay(channel)
for channel
in channels]
271 ChannelList =
"\n".join(
272 [f
" {dmID}. {channel}"
273 for (dmID, channel)
in enumerate(FormattedChannels)]
275 if len(particles) == 1:
276 ChannelsString +=
"\n\n" + ChannelList
278 ChannelsString += f
"\n\n ``{particle}`` channels:\n\n" + ChannelList
280 if SkimClass.__doc__
is None:
283 SkimClass.__doc__ = SkimClass.__doc__.replace(
"<CHANNELS>", ChannelsString)
285 return fancy_skim_header(SkimClass)
290 @_FEI_skim_header("B0")
293 Tag side :math:`B` cuts:
295 * :math:`M_{\\text{bc}} > 5.2~{\\rm GeV}`
296 * :math:`|\\Delta E| < 0.3~{\\rm GeV}`
297 * :math:`\\text{signal probability} > 0.001` (omitted for decay mode 23)
299 All available FEI :math:`B^0` hadronic tags are reconstructed. From `Thomas Keck's
300 thesis <https://docs.belle2.org/record/275/files/BELLE2-MTHESIS-2015-001.pdf>`_,
301 "the channel :math:`B^0 \\to \\overline{D}^0 \\pi^0` was used by the FR, but is not
302 yet used in the FEI due to unexpected technical restrictions in the KFitter
308 `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
309 event-level cuts made before applying the FEI.
311 __description__ =
"FEI-tagged neutral :math:`B`'s decaying hadronically."
312 validation_sample = _VALIDATION_SAMPLE
318 "semileptonic":
False,
324 ma.applyCuts(
"B0:generic",
"Mbc>5.2", path=path)
325 ma.applyCuts(
"B0:generic",
"abs(deltaE)<0.300", path=path)
326 ma.applyCuts(
"B0:generic",
"sigProb>0.001 or extraInfo(dmID)==23", path=path)
328 return [
"B0:generic"]
335 vm.addAlias(
'sigProb',
'extraInfo(SignalProbability)')
336 vm.addAlias(
'log10_sigProb',
'log10(extraInfo(SignalProbability))')
337 vm.addAlias(
'd0_massDiff',
'daughter(0,massDifference(0))')
338 vm.addAlias(
'd0_M',
'daughter(0,M)')
339 vm.addAlias(
'decayModeID',
'extraInfo(decayModeID)')
340 vm.addAlias(
'nDaug',
'countDaughters(1>0)')
342 histogramFilename = f
"{self}_Validation.root"
344 create_validation_histograms(
345 rootfile=histogramFilename,
346 particlelist=
'B0:generic',
348 (
'sigProb', 100, 0.0, 1.0,
'Signal probability', __liaison__,
349 'Signal probability of the reconstructed tag B candidates',
350 'Most around zero, with a tail at non-zero values.',
'Signal probability',
'Candidates',
'logy'),
351 (
'nDaug', 6, 0.0, 6,
'Number of daughters of tag B', __liaison__,
352 'Number of daughters of tag B',
'Some distribution of number of daughters',
'n_{daughters}',
'Candidates'),
353 (
'd0_massDiff', 100, 0.0, 0.5,
'Mass difference of D* and D', __liaison__,
354 'Mass difference of D^{*} and D',
'Peak at 0.14 GeV',
'm(D^{*})-m(D) [GeV]',
'Candidates',
'shifter'),
355 (
'd0_M', 100, 0.0, 3.0,
'Mass of zeroth daughter (D* or D)', __liaison__,
356 'Mass of zeroth daughter of tag B (either a $D^{*}$ or a D)',
'Peaks at 1.86 GeV and 2.00 GeV',
357 'm(D^{(*)}) [GeV]',
'Candidates',
'shifter'),
358 (
'deltaE', 40, -0.3, 0.3,
'#Delta E', __liaison__,
359 '$\\Delta E$ of event',
'Peak around zero',
'#Delta E [GeV]',
'Candidates',
'shifter'),
360 (
'Mbc', 40, 5.2, 5.3,
'Mbc', __liaison__,
361 'Beam-constrained mass of event',
'Peaking around B mass (5.28 GeV)',
'M_{bc} [GeV]',
'Candidates',
'shifter')],
362 variables_2d=[(
'deltaE', 100, -0.3, 0.3,
'Mbc', 100, 5.2, 5.3,
'Mbc vs deltaE', __liaison__,
363 'Plot of the $\\Delta E$ of the event against the beam constrained mass',
364 'Peak of $\\Delta E$ around zero, and $M_{bc}$ around B mass (5.28 GeV)',
365 '#Delta E [GeV]',
'M_{bc} [GeV]',
'colz'),
366 (
'decayModeID', 26, 0, 26,
'log10_sigProb', 100, -3.0, 0.0,
367 'Signal probability for each decay mode ID', __liaison__,
368 'Signal probability for each decay mode ID',
369 'Some distribtuion of candidates in the first few decay mode IDs',
370 'Decay mode ID',
'#log_10(signal probability)',
'colz')],
374 @_FEI_skim_header("B+")
377 Tag side :math:`B` cuts:
379 * :math:`M_{\\text{bc}} > 5.2~{\\rm GeV}`
380 * :math:`|\\Delta E| < 0.3~{\\rm GeV}`
381 * :math:`\\text{signal probability} > 0.001` (omitted for decay mode 25)
383 All available FEI :math:`B^+` hadronic tags are reconstructed.
388 `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
389 event-level cuts made before applying the FEI.
391 __description__ =
"FEI-tagged charged :math:`B`'s decaying hadronically."
392 validation_sample = _VALIDATION_SAMPLE
398 "semileptonic":
False,
404 ma.applyCuts(
"B+:generic",
"Mbc>5.2", path=path)
405 ma.applyCuts(
"B+:generic",
"abs(deltaE)<0.300", path=path)
406 ma.applyCuts(
"B+:generic",
"sigProb>0.001 or extraInfo(dmID)==25", path=path)
408 return [
"B+:generic"]
415 vm.addAlias(
'sigProb',
'extraInfo(SignalProbability)')
416 vm.addAlias(
'log10_sigProb',
'log10(extraInfo(SignalProbability))')
417 vm.addAlias(
'd0_massDiff',
'daughter(0,massDifference(0))')
418 vm.addAlias(
'd0_M',
'daughter(0,M)')
419 vm.addAlias(
'decayModeID',
'extraInfo(decayModeID)')
420 vm.addAlias(
'nDaug',
'countDaughters(1>0)')
422 histogramFilename = f
"{self}_Validation.root"
424 create_validation_histograms(
425 rootfile=histogramFilename,
426 particlelist=
'B+:generic',
428 (
'sigProb', 100, 0.0, 1.0,
'Signal probability', __liaison__,
429 'Signal probability of the reconstructed tag B candidates',
'Most around zero, with a tail at non-zero values.',
430 'Signal probability',
'Candidates',
'logy'),
431 (
'nDaug', 6, 0.0, 6,
'Number of daughters of tag B', __liaison__,
432 'Number of daughters of tag B',
'Some distribution of number of daughters',
'n_{daughters}',
'Candidates'),
433 (
'd0_massDiff', 100, 0.0, 0.5,
'Mass difference of D* and D', __liaison__,
434 'Mass difference of D^{*} and D',
'Peak at 0.14 GeV',
'm(D^{*})-m(D) [GeV]',
'Candidates',
'shifter'),
435 (
'd0_M', 100, 0.0, 3.0,
'Mass of zeroth daughter (D* or D)', __liaison__,
436 'Mass of zeroth daughter of tag B (either a $D^{*}$ or a D)',
'Peaks at 1.86 GeV and 2.00 GeV',
437 'm(D^{(*)}) [GeV]',
'Candidates',
'shifter'),
438 (
'deltaE', 40, -0.3, 0.3,
'#Delta E', __liaison__,
439 '$\\Delta E$ of event',
'Peak around zero',
'#Delta E [GeV]',
'Candidates',
'shifter'),
440 (
'Mbc', 40, 5.2, 5.3,
'Mbc', __liaison__,
441 'Beam-constrained mass of event',
'Peak around B mass (5.28 GeV)',
'M_{bc} [GeV]',
'Candidates',
'shifter')],
442 variables_2d=[(
'deltaE', 100, -0.3, 0.3,
'Mbc', 100, 5.2, 5.3,
'Mbc vs deltaE', __liaison__,
443 'Plot of the $\\Delta E$ of the event against the beam constrained mass',
444 'Peak of $\\Delta E$ around zero, and $M_{bc}$ around B mass (5.28 GeV)',
445 '#Delta E [GeV]',
'M_{bc} [GeV]',
'colz'),
446 (
'decayModeID', 29, 0, 29,
'log10_sigProb', 100, -3.0, 0.0,
447 'Signal probability for each decay mode ID', __liaison__,
448 'Signal probability for each decay mode ID',
449 'Some distribtuion of candidates in the first few decay mode IDs',
450 'Decay mode ID',
'#log_10(signal probability)',
'colz')],
454 @_FEI_skim_header("B0")
457 Tag side :math:`B` cuts:
459 * :math:`-4 < \\cos\\theta_{BY} < 3`
460 * :math:`\\log_{10}(\\text{signal probability}) > -2.4`
461 * :math:`p_{\\ell}^{*} > 1.0~{\\rm GeV}` in CMS frame
463 SL :math:`B^0` tags are reconstructed. Hadronic :math:`B` with SL :math:`D` are not
464 reconstructed, as these are rare and time-intensive.
469 `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
470 event-level cuts made before applying the FEI.
472 __description__ =
"FEI-tagged neutral :math:`B`'s decaying semileptonically."
473 validation_sample = _VALIDATION_SAMPLE
479 "semileptonic":
True,
486 ma.applyCuts(
"B0:semileptonic",
"dmID<8", path=path)
487 ma.applyCuts(
"B0:semileptonic",
"log10(sigProb)>-2.4", path=path)
488 ma.applyCuts(
"B0:semileptonic",
"-4.0<cosThetaBY<3.0", path=path)
489 ma.applyCuts(
"B0:semileptonic",
"p_lepton_CMSframe>1.0", path=path)
491 return [
"B0:semileptonic"]
498 vm.addAlias(
'sigProb',
'extraInfo(SignalProbability)')
499 vm.addAlias(
'log10_sigProb',
'log10(extraInfo(SignalProbability))')
500 vm.addAlias(
'd0_massDiff',
'daughter(0,massDifference(0))')
501 vm.addAlias(
'd0_M',
'daughter(0,M)')
502 vm.addAlias(
'decayModeID',
'extraInfo(decayModeID)')
503 vm.addAlias(
'nDaug',
'countDaughters(1>0)')
505 histogramFilename = f
"{self}_Validation.root"
507 create_validation_histograms(
508 rootfile=histogramFilename,
509 particlelist=
'B0:semileptonic',
511 (
'sigProb', 100, 0.0, 1.0,
'Signal probability', __liaison__,
512 'Signal probability of the reconstructed tag B candidates',
'Most around zero, with a tail at non-zero values.',
513 'Signal probability',
'Candidates',
'logy'),
514 (
'nDaug', 6, 0.0, 6,
'Number of daughters of tag B', __liaison__,
515 'Number of daughters of tag B',
'Some distribution of number of daughters',
'n_{daughters}',
'Candidates'),
516 (
'cosThetaBetweenParticleAndNominalB', 100, -6.0, 4.0,
'#cos#theta_{BY}', __liaison__,
517 'Cosine of angle between the reconstructed B and the nominal B',
'Distribution peaking between -1 and 1',
518 '#cos#theta_{BY}',
'Candidates'),
519 (
'd0_massDiff', 100, 0.0, 0.5,
'Mass difference of D* and D', __liaison__,
520 'Mass difference of $D^{*}$ and D',
'Peak at 0.14 GeV',
'm(D^{*})-m(D) [GeV]',
'Candidates',
'shifter'),
521 (
'd0_M', 100, 0.0, 3.0,
'Mass of zeroth daughter (D* or D)', __liaison__,
522 'Mass of zeroth daughter of tag B (either a $D^{*}$ or a D)',
'Peaks at 1.86 GeV and 2.00 GeV',
523 'm(D^{(*)}) [GeV]',
'Candidates',
'shifter')],
524 variables_2d=[(
'decayModeID', 8, 0, 8,
'log10_sigProb', 100, -3.0, 0.0,
525 'Signal probability for each decay mode ID', __liaison__,
526 'Signal probability for each decay mode ID',
527 'Some distribtuion of candidates in the first few decay mode IDs',
528 'Decay mode ID',
'#log_10(signal probability)',
'colz')],
532 @_FEI_skim_header("B0")
535 Tag side :math:`B` cuts:
537 * :math:`\\text{FoxWolframR2} < 0.4`
538 * :math:`-1.75 < \\cos\\theta_{BY} < 1.1`
539 * :math:`\\log_{10}(\\text{signal probability}) > -2.0`
540 * :math:`p_{\\ell}^{*} > 1.0~{\\rm GeV}` in CMS frame
541 * :math:`\\text{BCS:signal probability}`
543 SL :math:`B^0` tags are reconstructed. Hadronic :math:`B` with SL :math:`D` are not
544 reconstructed, as these are rare and time-intensive.
549 `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
550 event-level cuts made before applying the FEI.
552 __description__ = (
"FEI-tagged neutral :math:`B`'s decaying semileptonically",
553 "Analysis cuts included, best sigProb candidate kept"
555 validation_sample = _VALIDATION_SAMPLE
561 "semileptonic":
True,
568 ma.buildEventShape(inputListNames=[
'pi+:FEI_cleaned',
'gamma:FEI_cleaned'],
570 harmonicMoments=
False,
576 checkForDuplicates=
True,
580 vm.addAlias(
'foxWolframR2_maskedNaN',
'ifNANgiveX(foxWolframR2,1)')
581 TighterCuts =
" and ".join(
583 "foxWolframR2_maskedNaN<0.4",
585 "log10(sigProb) > -2.0",
586 "-1.75 < cosThetaBY < 1.1",
587 "p_lepton_CMSframe > 1.0"
590 ma.cutAndCopyList(
"B0:SLRDstar",
"B0:semileptonic", TighterCuts, path=path)
593 ma.rankByHighest(
"B0:SLRDstar",
"sigProb", numBest=1,
594 allowMultiRank=
True, outputVariable=
'sigProb_rank_tag',
597 return [
"B0:SLRDstar"]
604 vm.addAlias(
'sigProb',
'extraInfo(SignalProbability)')
605 vm.addAlias(
'log10_sigProb',
'log10(extraInfo(SignalProbability))')
606 vm.addAlias(
'd0_massDiff',
'daughter(0,massDifference(0))')
607 vm.addAlias(
'd0_M',
'daughter(0,M)')
608 vm.addAlias(
'decayModeID',
'extraInfo(decayModeID)')
609 vm.addAlias(
'nDaug',
'countDaughters(1>0)')
611 histogramFilename = f
"{self}_Validation.root"
613 create_validation_histograms(
614 rootfile=histogramFilename,
615 particlelist=
'B0:SLRDstar',
617 (
'sigProb', 100, 0.0, 1.0,
'Signal probability', __liaison__,
618 'Signal probability of the reconstructed tag B candidates',
'Most around zero, with a tail at non-zero values.',
619 'Signal probability',
'Candidates',
'logy'),
620 (
'nDaug', 6, 0.0, 6,
'Number of daughters of tag B', __liaison__,
621 'Number of daughters of tag B',
'Some distribution of number of daughters',
'n_{daughters}',
'Candidates'),
622 (
'cosThetaBetweenParticleAndNominalB', 100, -6.0, 4.0,
'#cos#theta_{BY}', __liaison__,
623 'Cosine of angle between the reconstructed B and the nominal B',
'Distribution peaking between -1 and 1',
624 '#cos#theta_{BY}',
'Candidates'),
625 (
'd0_massDiff', 100, 0.0, 0.5,
'Mass difference of D* and D', __liaison__,
626 'Mass difference of $D^{*}$ and D',
'Peak at 0.14 GeV',
'm(D^{*})-m(D) [GeV]',
'Candidates',
'shifter'),
627 (
'd0_M', 100, 0.0, 3.0,
'Mass of zeroth daughter (D* or D)', __liaison__,
628 'Mass of zeroth daughter of tag B (either a $D^{*}$ or a D)',
'Peaks at 1.86 GeV and 2.00 GeV',
629 'm(D^{(*)}) [GeV]',
'Candidates',
'shifter')],
630 variables_2d=[(
'decayModeID', 8, 0, 8,
'log10_sigProb', 100, -3.0, 0.0,
631 'Signal probability for each decay mode ID', __liaison__,
632 'Signal probability for each decay mode ID',
633 'Some distribution of candidates in the first few decay mode IDs',
634 'Decay mode ID',
'#log_10(signal probability)',
'colz')],
638 @_FEI_skim_header("B+")
641 Tag side :math:`B` cuts:
643 * :math:`-4 < \\cos\\theta_{BY} < 3`
644 * :math:`\\log_{10}(\\text{signal probability}) > -2.4`
645 * :math:`p_{\\ell}^{*} > 1.0~{\\rm GeV}` in CMS frame
647 SL :math:`B^+` tags are reconstructed. Hadronic :math:`B^+` with SL :math:`D` are
648 not reconstructed, as these are rare and time-intensive.
653 `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
654 event-level cuts made before applying the FEI.
656 __description__ =
"FEI-tagged charged :math:`B`'s decaying semileptonically."
657 validation_sample = _VALIDATION_SAMPLE
663 "semileptonic":
True,
670 ma.applyCuts(
"B+:semileptonic",
"dmID<8", path=path)
671 ma.applyCuts(
"B+:semileptonic",
"log10_sigProb>-2.4", path=path)
672 ma.applyCuts(
"B+:semileptonic",
"-4.0<cosThetaBY<3.0", path=path)
673 ma.applyCuts(
"B+:semileptonic",
"p_lepton_CMSframe>1.0", path=path)
675 return [
"B+:semileptonic"]
682 vm.addAlias(
'sigProb',
'extraInfo(SignalProbability)')
683 vm.addAlias(
'log10_sigProb',
'log10(extraInfo(SignalProbability))')
684 vm.addAlias(
'd0_massDiff',
'daughter(0,massDifference(0))')
685 vm.addAlias(
'd0_M',
'daughter(0,M)')
686 vm.addAlias(
'decayModeID',
'extraInfo(decayModeID)')
687 vm.addAlias(
'nDaug',
'countDaughters(1>0)')
689 histogramFilename = f
"{self}_Validation.root"
691 create_validation_histograms(
692 rootfile=histogramFilename,
693 particlelist=
'B+:semileptonic',
695 (
'sigProb', 100, 0.0, 1.0,
'Signal probability', __liaison__,
696 'Signal probability of the reconstructed tag B candidates',
697 'Most around zero, with a tail at non-zero values.',
'Signal probability',
'Candidates',
'logy'),
698 (
'nDaug', 6, 0.0, 6,
'Number of daughters of tag B', __liaison__,
699 'Number of daughters of tag B',
'Some distribution of number of daughters',
'n_{daughters}',
'Candidates'),
700 (
'cosThetaBetweenParticleAndNominalB', 100, -6.0, 4.0,
'#cos#theta_{BY}', __liaison__,
701 'Cosine of angle between the reconstructed B and the nominal B',
'Distribution peaking between -1 and 1',
702 '#cos#theta_{BY}',
'Candidates'),
703 (
'd0_massDiff', 100, 0.0, 0.5,
'Mass difference of D* and D', __liaison__,
704 'Mass difference of $D^{*}$ and D',
'Peak at 0.14 GeV',
'm(D^{*})-m(D) [GeV]',
'Candidates',
'shifter'),
705 (
'd0_M', 100, 0.0, 3.0,
'Mass of zeroth daughter (D* or D)', __liaison__,
706 'Mass of zeroth daughter of tag B (either a $D^{*}$ or a D)',
'Peaks at 1.86 GeV and 2.00 GeV',
707 'm(D^{(*)}) [GeV]',
'Candidates',
'shifter')],
708 variables_2d=[(
'decayModeID', 8, 0, 8,
'log10_sigProb', 100, -3.0, 0.0,
709 'Signal probability for each decay mode ID', __liaison__,
710 'Signal probability for each decay mode ID',
711 'Some distribtuion of candidates in the first few decay mode IDs',
712 'Decay mode ID',
'#log_10(signal probability)',
'colz')],
716 @_FEI_skim_header(["B0", "B+"])
719 Tag side :math:`B` cuts:
721 * :math:`M_{\\text{bc}} > 5.2~{\\rm GeV}`
722 * :math:`|\\Delta E| < 0.3~{\\rm GeV}`
723 * :math:`\\text{signal probability} > 0.001` (omitted for decay mode 23 for
724 :math:`B^+`, and decay mode 25 for :math:`B^0`)
726 All available FEI :math:`B^0` and :math:`B^+` hadronic tags are reconstructed. From
727 `Thomas Keck's thesis
728 <https://docs.belle2.org/record/275/files/BELLE2-MTHESIS-2015-001.pdf>`_, "the
729 channel :math:`B^0 \\to \\overline{D}^0 \\pi^0` was used by the FR, but is not yet
730 used in the FEI due to unexpected technical restrictions in the KFitter algorithm".
735 `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
736 event-level cuts made before applying the FEI.
738 __description__ =
"FEI-tagged neutral and charged :math:`B`'s decaying hadronically."
744 "semileptonic":
False,
750 ma.copyList(
"B0:feiHadronic",
"B0:generic", path=path)
751 ma.copyList(
"B+:feiHadronic",
"B+:generic", path=path)
752 HadronicBLists = [
"B0:feiHadronic",
"B+:feiHadronic"]
754 for BList
in HadronicBLists:
755 ma.applyCuts(BList,
"Mbc>5.2", path=path)
756 ma.applyCuts(BList,
"abs(deltaE)<0.300", path=path)
758 ma.applyCuts(
"B+:feiHadronic",
"sigProb>0.001 or extraInfo(dmID)==25", path=path)
759 ma.applyCuts(
"B0:feiHadronic",
"sigProb>0.001 or extraInfo(dmID)==23", path=path)
761 return HadronicBLists
764 @_FEI_skim_header(["B0", "B+"])
767 Tag side :math:`B` cuts:
769 * :math:`M_{\\text{bc}} > 5.27~{\\rm GeV}`
770 * :math:`-0.150 < \\Delta E < 0.100~{\\rm GeV}`
771 * :math:`\\text{signal probability} > 0.001` (omitted for decay mode 23 for
772 :math:`B^+`, and decay mode 25 for :math:`B^0`)
773 * :math:`\\cos{TBTO} < 0.9`
774 * Selects only the two best candidates that survive based on the signalProbability
776 All available FEI :math:`B^0` and :math:`B^+` hadronic tags are reconstructed. From
777 `Thomas Keck's thesis
778 <https://docs.belle2.org/record/275/files/BELLE2-MTHESIS-2015-001.pdf>`_, "the
779 channel :math:`B^0 \\to \\overline{D}^0 \\pi^0` was used by the FR, but is not yet
780 used in the FEI due to unexpected technical restrictions in the KFitter algorithm".
785 `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
786 event-level cuts made before applying the FEI.
789 __description__ = (
"FEI-tagged neutral and charged :math:`B`'s decaying hadronically. "
790 "Analysis specific cuts applied during skimming. Best 2 candidates ranked by sigProb kept"
797 "semileptonic":
False,
803 ma.copyList(
"B0:feiHadronicDstEllNu",
"B0:generic", path=path)
804 ma.copyList(
"B+:feiHadronicDstEllNu",
"B+:generic", path=path)
805 HadronicBLists = [
"B0:feiHadronicDstEllNu",
"B+:feiHadronicDstEllNu"]
808 "B+:feiHadronicDstEllNu",
"sigProb>0.001 or extraInfo(dmID)==25", path=path
811 "B0:feiHadronicDstEllNu",
"sigProb>0.001 or extraInfo(dmID)==23", path=path
814 for BList
in HadronicBLists:
815 ma.applyCuts(BList,
"Mbc>5.27", path=path)
816 ma.applyCuts(BList,
"-0.150 <= deltaE <= 0.100", path=path)
819 ma.applyCuts(BList,
"cosTBTO < 0.9", path=path)
825 variable=
"extraInfo(SignalProbability)",
830 return HadronicBLists
833 """Builds continuum suppression for a given b-meson list.
834 This module is required to save the CS variables.
837 list_name (str) : name of the b meson list. Can be list of signal or tag b meson lists
838 path (b2.Path): the basf2 path to append modules
843 mask_name =
"btag_cs"
846 ma.buildRestOfEvent(target_list_name=particle_list, path=path)
855 ma.buildContinuumSuppression(
856 list_name=particle_list, roe_mask=mask_name, path=path
861 """Prepares a tuple with the ROE mask name and the ROE cuts for tracks and clusters
862 The actual cuts are read from a selections dictionary
864 The cuts are aligning with the continuum rejection that is applied for the centrally
865 produced FEI calibration
868 mask_name (str): The name of the mask. This is the name that will be used to append the mask
871 tuple of mask_name, tracking cuts and cluster_cuts
879 "thetaInCDCAcceptance == 1",
882 "clusterNHits > 1.5",
883 "[[clusterReg==1 and E > 0.080] or [clusterReg==2 and E > 0.030] or [clusterReg==3 and E > 0.060]]",
884 "[[clusterTheta > 0.2967] and [clusterTheta < 2.6180]]",
885 "abs(clusterTiming) < 200",
889 track_cuts =
"".join([
"[",
" and ".join(selections[
"tracks"]),
"]"])
890 cluster_cuts =
"".join([
"[",
" and ".join(selections[
"clusters"]),
"]"])
892 return (mask_name, track_cuts, cluster_cuts)
895 @_FEI_skim_header(["B0", "B+"])
898 Tag side :math:`B` cuts:
900 * :math:`-4 < \\cos\\theta_{BY} < 3`
901 * :math:`\\log_{10}(\\text{signal probability}) > -2.4`
902 * :math:`p_{\\ell}^{*} > 1.0~{\\rm GeV}` in CMS frame
904 SL :math:`B^0` and :math:`B^+` tags are reconstructed. Hadronic :math:`B` with SL
905 :math:`D` are not reconstructed, as these are rare and time-intensive.
910 `BaseFEISkim.FEIPrefix` for FEI training used, and `BaseFEISkim.fei_precuts` for
911 event-level cuts made before applying the FEI.
913 __description__ =
"FEI-tagged neutral and charged :math:`B`'s decaying semileptonically."
919 "semileptonic":
True,
926 ma.copyList(
"B0:feiSL",
"B0:semileptonic", path=path)
927 ma.copyList(
"B+:feiSL",
"B+:semileptonic", path=path)
928 SLBLists = [
"B0:feiSL",
"B+:feiSL"]
930 Bcuts = [
"log10_sigProb>-2.4",
"-4.0<cosThetaBY<3.0",
"p_lepton_CMSframe>1.0"]
932 for BList
in SLBLists:
934 ma.applyCuts(BList, cut, path=path)
def run_fei_for_skims(FEIChannelArgs, FEIPrefix, analysisGlobaltag, *path)
def additional_setup(self, path)
def setup_fei_aliases(FEIChannelArgs)
dictionary FEIChannelArgs
def build_lists(self, path)
def validation_histograms(self, path)
def build_lists(self, path)
def validation_histograms(self, path)
def _build_btag_roe_mask(str mask_name)
def build_lists(self, path)
def _build_continuum_suppression(self, str particle_list, b2.Path path)
def build_lists(self, path)
def build_lists(self, path)
def validation_histograms(self, path)
def build_lists(self, path)
def validation_histograms(self, path)
def build_lists(self, path)
def validation_histograms(self, path)
def build_lists(self, path)