Belle II Software development
modularAnalysis.py
1#!/usr/bin/env python3
2
3
10
11"""
12This module defines wrapper functions around the analysis modules.
13"""
14
15import b2bii
16from basf2 import register_module, create_path
17from basf2 import B2INFO, B2WARNING, B2ERROR, B2FATAL
18import basf2
19import subprocess
20
21
22def setAnalysisConfigParams(configParametersAndValues, path):
23 """
24 Sets analysis configuration parameters.
25
26 These are:
27
28 - 'tupleStyle': 'Default' (default) or 'Laconic'
29
30 - defines the style of the branch name in the ntuple
31
32 - 'mcMatchingVersion': Specifies what version of mc matching algorithm is going to be used:
33
34 - 'Belle' - analysis of Belle MC
35 - 'BelleII' (default) - all other cases
36
37 @param configParametersAndValues dictionary of parameters and their values of the form {param1: value, param2: value, ...)
38 @param modules are added to this path
39 """
40
41 conf = register_module('AnalysisConfiguration')
42
43 allParameters = ['tupleStyle', 'mcMatchingVersion']
44
45 keys = configParametersAndValues.keys()
46 for key in keys:
47 if key not in allParameters:
48 allParametersString = ', '.join(allParameters)
49 B2ERROR('Invalid analysis configuration parameter: ' + key + '.\n'
50 'Please use one of the following: ' + allParametersString)
51
52 for param in allParameters:
53 if param in configParametersAndValues:
54 conf.param(param, configParametersAndValues.get(param))
55
56 path.add_module(conf)
57
58
59def inputMdst(filename, path, environmentType='default', skipNEvents=0, entrySequence=None, *, parentLevel=0, **kwargs):
60 """
61 Loads the specified :ref:`mDST <mdst>` (or :ref:`uDST <analysis_udstoutput>`) file with the RootInput module.
62
63 The correct environment (e.g. magnetic field settings) is determined from
64 ``environmentType``. Options are either: 'default' (for Belle II MC and
65 data: falls back to database), 'Belle': for analysis of converted Belle 1
66 data and MC.
67
68 Parameters:
69 filename (str): the name of the file to be loaded
70 path (basf2.Path): modules are added to this path
71 environmentType (str): type of the environment to be loaded (either 'default' or 'Belle')
72 skipNEvents (int): N events of the input file are skipped
73 entrySequence (str): The number sequences (e.g. 23:42,101) defining the entries which are processed.
74 parentLevel (int): Number of generations of parent files (files used as input when creating a file) to be read
75 """
76
77 # FIXME remove this check of "filename" at release-07
78 if filename == 'default':
79 B2FATAL("""
80We have simplified the arguments to inputMdst! If you are running on Belle II
81data or MC, you don't have to use "default" any more.
82Please replace:
83 inputMdst("default", "/your/input/file.root", path=mypath)
84With:
85 inputMdst("/your/input/file.root", path=mypath)
86 """)
87 elif filename == "Belle":
88 B2FATAL("""
89We have reordered the arguments to inputMdst! If you are running on Belle 1
90data or MC, you need to specify the 'environmentType'.
91Please replace:
92 inputMdst("Belle", "/your/input/file.root", path=mypath)
93With:
94 inputMdst("/your/input/file.root", path=mypath, environmentType='Belle')
95 """)
96 elif filename in [f"MC{i}" for i in range(5, 10)]:
97 B2FATAL(f"We no longer support the MC version {filename}. Sorry.")
98
99 if entrySequence is not None:
100 entrySequence = [entrySequence]
101
102 inputMdstList([filename], path, environmentType, skipNEvents, entrySequence, parentLevel=parentLevel, **kwargs)
103
104
105def inputMdstList(
106 filelist,
107 path,
108 environmentType='default',
109 skipNEvents=0,
110 entrySequences=None,
111 *,
112 parentLevel=0,
113 useB2BIIDBCache=True):
114 """
115 Loads the specified list of :ref:`mDST <mdst>` (or :ref:`uDST <analysis_udstoutput>`) files with the RootInput module.
116
117 The correct environment (e.g. magnetic field settings) is determined from
118 ``environmentType``. Options are either: 'default' (for Belle II MC and
119 data: falls back to database), 'Belle': for analysis of converted Belle 1
120 data and MC.
121
122 Parameters:
123 filelist (list(str)): the filename list of files to be loaded
124 path (basf2.Path): modules are added to this path
125 environmentType (str): type of the environment to be loaded (either 'default' or 'Belle')
126 skipNEvents (int): N events of the input files are skipped
127 entrySequences (list(str)): The number sequences (e.g. 23:42,101) defining
128 the entries which are processed for each inputFileName.
129 parentLevel (int): Number of generations of parent files (files used as input when creating a file) to be read
130 useB2BIIDBCache (bool): Loading of local KEKCC database (only to be deactivated in very special cases)
131 """
132
133 # FIXME remove this check of "filename" at release-07
134 if filelist == 'default':
135 B2FATAL("""
136We have simplified the arguments to inputMdstList! If you are running on
137Belle II data or MC, you don't have to use "default" any more.
138Please replace:
139 inputMdstList("default", list_of_your_files, path=mypath)
140With:
141 inputMdstList(list_of_your_files, path=mypath)
142 """)
143 elif filelist == "Belle":
144 B2FATAL("""
145We have reordered the arguments to inputMdstList! If you are running on
146Belle 1 data or MC, you need to specify the 'environmentType'.
147Please replace:
148 inputMdstList("Belle", list_of_your_files, path=mypath)
149With:
150 inputMdstList(list_of_your_files, path=mypath, environmentType='Belle')
151 """)
152 elif filelist in [f"MC{i}" for i in range(5, 10)]:
153 B2FATAL(f"We no longer support the MC version {filelist}. Sorry.")
154
155 roinput = register_module('RootInput')
156 roinput.param('inputFileNames', filelist)
157 roinput.param('skipNEvents', skipNEvents)
158 if entrySequences is not None:
159 roinput.param('entrySequences', entrySequences)
160 roinput.param('parentLevel', parentLevel)
161
162 path.add_module(roinput)
163 path.add_module('ProgressBar')
164
165 if environmentType == 'Belle':
166 # Belle 1 constant magnetic field
167 # -------------------------------
168 # n.b. slightly unfortunate syntax: the MagneticField is a member of the
169 # Belle2 namespace but will be set to the Belle 1 values
170 from ROOT import Belle2 # reduced scope of potentially-misbehaving import
171 from ROOT.Math import XYZVector
172 belle1_field = Belle2.MagneticField()
173 belle1_field.addComponent(Belle2.MagneticFieldComponentConstant(XYZVector(0, 0, 1.5 * Belle2.Unit.T)))
174 Belle2.DBStore.Instance().addConstantOverride("MagneticField", belle1_field, False)
175 # also set the MC matching for Belle 1
176 setAnalysisConfigParams({'mcMatchingVersion': 'Belle'}, path)
178 if useB2BIIDBCache:
179 basf2.conditions.metadata_providers = ["/sw/belle/b2bii/database/conditions/b2bii.sqlite"]
180 basf2.conditions.payload_locations = ["/sw/belle/b2bii/database/conditions/"]
181
182
183def outputMdst(filename, path):
184 """
185 Saves mDST (mini-Data Summary Tables) to the output root file.
186
187 .. warning::
188
189 This function is kept for backward-compatibility.
190 Better to use `mdst.add_mdst_output` directly.
191
192 """
193
194 import mdst
195 mdst.add_mdst_output(path, mc=True, filename=filename)
196
197
198def outputUdst(filename, particleLists=None, includeArrays=None, path=None, dataDescription=None):
199 """
200 Save uDST (user-defined Data Summary Tables) = MDST + Particles + ParticleLists
201 The charge-conjugate lists of those given in particleLists are also stored.
202 Additional Store Arrays and Relations to be stored can be specified via includeArrays
203 list argument.
204
205 Note:
206 This does not reduce the amount of Particle objects saved,
207 see `udst.add_skimmed_udst_output` for a function that does.
208
209 """
210
211 import udst
213 path=path, filename=filename, particleLists=particleLists,
214 additionalBranches=includeArrays, dataDescription=dataDescription)
215
216
217def outputIndex(filename, path, includeArrays=None, keepParents=False, mc=True):
218 """
219 Write out all particle lists as an index file to be reprocessed using parentLevel flag.
220 Additional branches necessary for file to be read are automatically included.
221 Additional Store Arrays and Relations to be stored can be specified via includeArrays
222 list argument.
223
224 @param str filename the name of the output index file
225 @param str path modules are added to this path
226 @param list(str) includeArrays: datastore arrays/objects to write to the output
227 file in addition to particle lists and related information
228 @param bool keepParents whether the parents of the input event will be saved as the parents of the same event
229 in the output index file. Useful if you are only adding more information to another index file
230 @param bool mc whether the input data is MC or not
231 """
232
233 if includeArrays is None:
234 includeArrays = []
235
236 # Module to mark all branches to not be saved except particle lists
237 onlyPLists = register_module('OnlyWriteOutParticleLists')
238 path.add_module(onlyPLists)
239
240 # Set up list of all other branches we need to make index file complete
241 partBranches = [
242 'Particles',
243 'ParticlesToMCParticles',
244 'ParticlesToPIDLikelihoods',
245 'ParticleExtraInfoMap',
246 'EventExtraInfo'
247 ]
248 branches = ['EventMetaData']
249 persistentBranches = ['FileMetaData']
250 if mc:
251 branches += []
252 # persistentBranches += ['BackgroundInfos']
253 branches += partBranches
254 branches += includeArrays
255
256 r1 = register_module('RootOutput')
257 r1.param('outputFileName', filename)
258 r1.param('additionalBranchNames', branches)
259 r1.param('branchNamesPersistent', persistentBranches)
260 r1.param('keepParents', keepParents)
261 path.add_module(r1)
262
263
264def setupEventInfo(noEvents, path):
265 """
266 Prepare to generate events. This function sets up the EventInfoSetter.
267 You should call this before adding a generator from generators.
268 The experiment and run numbers are set to 0 (run independent generic MC in phase 3).
269 https://xwiki.desy.de/xwiki/rest/p/59192
270
271 Parameters:
272 noEvents (int): number of events to be generated
273 path (basf2.Path): modules are added to this path
274 """
275
276 evtnumbers = register_module('EventInfoSetter')
277 evtnumbers.param('evtNumList', [noEvents])
278 evtnumbers.param('runList', [0])
279 evtnumbers.param('expList', [0])
280 path.add_module(evtnumbers)
281
282
283def loadGearbox(path, silence_warning=False):
284 """
285 Loads Gearbox module to the path.
286
287 Warning:
288 Should be used in a job with *cosmic event generation only*
289
290 Needed for scripts which only generate cosmic events in order to
291 load the geometry.
292
293 @param path modules are added to this path
294 @param silence_warning stops a verbose warning message if you know you want to use this function
295 """
296
297 if not silence_warning:
298 B2WARNING("""You are overwriting the geometry from the database with Gearbox.
299 This is fine if you're generating cosmic events. But in most other cases you probably don't want this.
300
301 If you're really sure you know what you're doing you can suppress this message with:
302
303 >>> loadGearbox(silence_warning=True)
304
305 """)
306
307 paramloader = register_module('Gearbox')
308 path.add_module(paramloader)
309
310
311def printPrimaryMCParticles(path, **kwargs):
312 """
313 Prints all primary MCParticles, that is particles from
314 the physics generator and not particles created by the simulation
315
316 This is equivalent to `printMCParticles(onlyPrimaries=True, path=path) <printMCParticles>` and additional
317 keyword arguments are just forwarded to that function
318 """
319
320 return printMCParticles(onlyPrimaries=True, path=path, **kwargs)
321
322
323def printMCParticles(onlyPrimaries=False, maxLevel=-1, path=None, *,
324 showProperties=False, showMomenta=False, showVertices=False, showStatus=False, suppressPrint=False):
325 """
326 Prints all MCParticles or just primary MCParticles up to specified level. -1 means no limit.
327
328 By default this will print a tree of just the particle names and their pdg
329 codes in the event, for example ::
330
331 [INFO] Content of MCParticle list
332 ├── e- (11)
333 ├── e+ (-11)
334 ╰── Upsilon(4S) (300553)
335 ├── B+ (521)
336 │ ├── anti-D_0*0 (-10421)
337 │ │ ├── D- (-411)
338 │ │ │ ├── K*- (-323)
339 │ │ │ │ ├── anti-K0 (-311)
340 │ │ │ │ │ ╰── K_S0 (310)
341 │ │ │ │ │ ├── pi+ (211)
342 │ │ │ │ │ │ ╰╶╶ p+ (2212)
343 │ │ │ │ │ ╰── pi- (-211)
344 │ │ │ │ │ ├╶╶ e- (11)
345 │ │ │ │ │ ├╶╶ n0 (2112)
346 │ │ │ │ │ ├╶╶ n0 (2112)
347 │ │ │ │ │ ╰╶╶ n0 (2112)
348 │ │ │ │ ╰── pi- (-211)
349 │ │ │ │ ├╶╶ anti-nu_mu (-14)
350 │ │ │ │ ╰╶╶ mu- (13)
351 │ │ │ │ ├╶╶ nu_mu (14)
352 │ │ │ │ ├╶╶ anti-nu_e (-12)
353 │ │ │ │ ╰╶╶ e- (11)
354 │ │ │ ╰── K_S0 (310)
355 │ │ │ ├── pi0 (111)
356 │ │ │ │ ├── gamma (22)
357 │ │ │ │ ╰── gamma (22)
358 │ │ │ ╰── pi0 (111)
359 │ │ │ ├── gamma (22)
360 │ │ │ ╰── gamma (22)
361 │ │ ╰── pi+ (211)
362 │ ├── mu+ (-13)
363 │ │ ├╶╶ anti-nu_mu (-14)
364 │ │ ├╶╶ nu_e (12)
365 │ │ ╰╶╶ e+ (-11)
366 │ ├── nu_mu (14)
367 │ ╰── gamma (22)
368 ...
369
370
371 There's a distinction between primary and secondary particles. Primary particles are the ones created by the physics generator while secondary
372 particles are ones generated by the simulation of the detector interaction.
373
374 Secondaries are indicated with a dashed line leading to the particle name
375 and if the output is to the terminal they will be printed in red. If
376 ``onlyPrimaries`` is True they will not be included in the tree.
377
378 On demand, extra information on all the particles can be displayed by
379 enabling any of the ``showProperties``, ``showMomenta``, ``showVertices``
380 and ``showStatus`` flags. Enabling all of them will look like
381 this::
382
383 ...
384 ╰── pi- (-211)
385 │ mass=0.14 energy=0.445 charge=-1 lifetime=6.36
386 │ p=(0.257, -0.335, 0.0238) |p|=0.423
387 │ production vertex=(0.113, -0.0531, 0.0156), time=0.00589
388 │ status flags=PrimaryParticle, StableInGenerator, StoppedInDetector
389 │ list index=48
390 │
391 ╰╶╶ n0 (2112)
392 mass=0.94 energy=0.94 charge=0 lifetime=5.28e+03
393 p=(-0.000238, -0.0127, 0.0116) |p|=0.0172
394 production vertex=(144, 21.9, -1.29), time=39
395 status flags=StoppedInDetector
396 creation process=HadronInelastic
397 list index=66
398
399 The first line of extra information is enabled by ``showProperties``, the
400 second line by ``showMomenta``, the third line by ``showVertices`` and the
401 last two lines by ``showStatus``. Note that all values are given in Belle II
402 standard units, that is GeV, centimeter and nanoseconds.
403
404 The depth of the tree can be limited with the ``maxLevel`` argument: If it's
405 bigger than zero it will limit the tree to the given number of generations.
406 A visual indicator will be added after each particle which would have
407 additional daughters that are skipped due to this limit. An example event
408 with ``maxLevel=3`` is given below. In this case only the tau neutrino and
409 the pion don't have additional daughters. ::
410
411 [INFO] Content of MCParticle list
412 ├── e- (11)
413 ├── e+ (-11)
414 ╰── Upsilon(4S) (300553)
415 ├── B+ (521)
416 │ ├── anti-D*0 (-423) → …
417 │ ├── tau+ (-15) → …
418 │ ╰── nu_tau (16)
419 ╰── B- (-521)
420 ├── D*0 (423) → …
421 ├── K*- (-323) → …
422 ├── K*+ (323) → …
423 ╰── pi- (-211)
424
425 The same information will be stored in the branch ``__MCDecayString__`` of
426 TTree created by `VariablesToNtuple` or `VariablesToEventBasedTree` module.
427 This branch is automatically created when `PrintMCParticles` modules is called.
428 Printing the information on the log message can be suppressed if ``suppressPrint``
429 is True, while the branch ``__MCDecayString__``. This option helps to reduce the
430 size of the log message.
431
432 Parameters:
433 onlyPrimaries (bool): If True show only primary particles, that is particles coming from
434 the generator and not created by the simulation.
435 maxLevel (int): If 0 or less print the whole tree, otherwise stop after n generations
436 showProperties (bool): If True show mass, energy and charge of the particles
437 showMomenta (bool): if True show the momenta of the particles
438 showVertices (bool): if True show production vertex and production time of all particles
439 showStatus (bool): if True show some status information on the particles.
440 For secondary particles this includes creation process.
441 suppressPrint (bool): if True printing the information on the log message is suppressed.
442 Even if True, the branch ``__MCDecayString__`` is created.
443 """
444
445 return path.add_module(
446 "PrintMCParticles",
447 onlyPrimaries=onlyPrimaries,
448 maxLevel=maxLevel,
449 showProperties=showProperties,
450 showMomenta=showMomenta,
451 showVertices=showVertices,
452 showStatus=showStatus,
453 suppressPrint=suppressPrint,
454 )
455
456
457def correctBrems(outputList,
458 inputList,
459 gammaList,
460 maximumAcceptance=3.0,
461 multiplePhotons=False,
462 usePhotonOnlyOnce=True,
463 writeOut=False,
464 path=None):
465 """
466 For each particle in the given ``inputList``, copies it to the ``outputList`` and adds the
467 4-vector of the photon(s) in the ``gammaList`` which has(have) a weighted named relation to
468 the particle's track, set by the ``ECLTrackBremFinder`` module during reconstruction.
469
470 Warning:
471 This can only work if the mdst file contains the *Bremsstrahlung* named relation. Official MC samples
472 up to and including MC12 and proc9 **do not** contain this. Newer production campaigns (from proc10 and MC13) do.
473 However, studies by the tau WG revealed that the cuts applied by the ``ECLTrackBremFinder`` module are too tight.
474 These will be loosened but this will only have effect with proc13 and MC15.
475 If your analysis is very sensitive to the Bremsstrahlung corrections, it is advised to use `correctBremsBelle`.
476
477 Information:
478 A detailed description of how the weights are set can be found directly at the documentation of the
479 `BremsFinder` module.
480
481 Please note that a new particle is always generated, with the old particle and -if found- one or more
482 photons as daughters.
483
484 The ``inputList`` should contain particles with associated tracks. Otherwise, the module will exit with an error.
485
486 The ``gammaList`` should contain photons. Otherwise, the module will exit with an error.
487
488 @param outputList The output particle list name containing the corrected particles
489 @param inputList The initial particle list name containing the particles to correct. *It should already exist.*
490 @param gammaList The photon list containing possibly bremsstrahlung photons; *It should already exist.*
491 @param maximumAcceptance Maximum value of the relation weight. Should be a number between [0,3)
492 @param multiplePhotons Whether to use only one photon (the one with the smallest acceptance) or as many as possible
493 @param usePhotonOnlyOnce If true, each brems candidate is used to correct only the track with the smallest relation weight
494 @param writeOut Whether `RootOutput` module should save the created ``outputList``
495 @param path The module is added to this path
496 """
497
498 import b2bii
499 if b2bii.isB2BII():
500 B2ERROR("The BremsFinder can only be run over Belle II data.")
501
502 bremscorrector = register_module('BremsFinder')
503 bremscorrector.set_name('bremsCorrector_' + outputList)
504 bremscorrector.param('inputList', inputList)
505 bremscorrector.param('outputList', outputList)
506 bremscorrector.param('gammaList', gammaList)
507 bremscorrector.param('maximumAcceptance', maximumAcceptance)
508 bremscorrector.param('multiplePhotons', multiplePhotons)
509 bremscorrector.param('usePhotonOnlyOnce', usePhotonOnlyOnce)
510 bremscorrector.param('writeOut', writeOut)
511 path.add_module(bremscorrector)
512
513
514def copyList(outputListName, inputListName, writeOut=False, path=None):
515 """
516 Copy all Particle indices from input ParticleList to the output ParticleList.
517 Note that the Particles themselves are not copied. The original and copied
518 ParticleLists will point to the same Particles.
519
520 @param ouputListName copied ParticleList
521 @param inputListName original ParticleList to be copied
522 @param writeOut whether RootOutput module should save the created ParticleList
523 @param path modules are added to this path
524 """
525
526 copyLists(outputListName, [inputListName], writeOut, path)
527
528
529def correctBremsBelle(outputListName,
530 inputListName,
531 gammaListName,
532 multiplePhotons=True,
533 angleThreshold=0.05,
534 usePhotonOnlyOnce=False,
535 writeOut=False,
536 path=None):
537 """
538 Run the Belle - like brems finding on the ``inputListName`` of charged particles.
539 Adds all photons in ``gammaListName`` to a copy of the charged particle that are within
540 ``angleThreshold``.
541
542 Tip:
543 Studies by the tau WG show that using a rather wide opening angle (up to
544 0.2 rad) and rather low energetic photons results in good correction.
545 However, this should only serve as a starting point for your own studies
546 because the optimal criteria are likely mode-dependent
547
548 Parameters:
549 outputListName (str): The output charged particle list containing the corrected charged particles
550 inputListName (str): The initial charged particle list containing the charged particles to correct.
551 gammaListName (str): The gammas list containing possibly radiative gammas, should already exist.
552 multiplePhotons (bool): How many photons should be added to the charged particle? nearest one -> False,
553 add all the photons within the cone -> True
554 angleThreshold (float): The maximum angle in radians between the charged particle and the (radiative)
555 gamma to be accepted.
556 writeOut (bool): whether RootOutput module should save the created ParticleList
557 usePhotonOnlyOnce (bool): If true, a photon is used for correction of the closest charged particle in the inputList.
558 If false, a photon is allowed to be used for correction multiple times (Default).
559
560 Warning:
561 One cannot use a photon twice to reconstruct a composite particle. Thus, for example, if ``e+`` and ``e-`` are corrected
562 with a ``gamma``, the pair of ``e+`` and ``e-`` cannot form a ``J/psi -> e+ e-`` candidate.
563
564 path (basf2.Path): modules are added to this path
565 """
566
567 fsrcorrector = register_module('BelleBremRecovery')
568 fsrcorrector.set_name('BelleFSRCorrection_' + outputListName)
569 fsrcorrector.param('inputListName', inputListName)
570 fsrcorrector.param('outputListName', outputListName)
571 fsrcorrector.param('gammaListName', gammaListName)
572 fsrcorrector.param('multiplePhotons', multiplePhotons)
573 fsrcorrector.param('angleThreshold', angleThreshold)
574 fsrcorrector.param('usePhotonOnlyOnce', usePhotonOnlyOnce)
575 fsrcorrector.param('writeOut', writeOut)
576 path.add_module(fsrcorrector)
577
578
579def copyLists(outputListName, inputListNames, writeOut=False, path=None):
580 """
581 Copy all Particle indices from all input ParticleLists to the
582 single output ParticleList.
583 Note that the Particles themselves are not copied.
584 The original and copied ParticleLists will point to the same Particles.
585
586 Duplicates are removed based on the first-come, first-served principle.
587 Therefore, the order of the input ParticleLists matters.
588
589 .. seealso::
590 If you want to select the best duplicate based on another criterion, have
591 a look at the function `mergeListsWithBestDuplicate`.
592
593 .. note::
594 Two particles that differ only by the order of their daughters are
595 considered duplicates and one of them will be removed.
596
597 @param ouputListName copied ParticleList
598 @param inputListName vector of original ParticleLists to be copied
599 @param writeOut whether RootOutput module should save the created ParticleList
600 @param path modules are added to this path
601 """
602
603 pmanipulate = register_module('ParticleListManipulator')
604 pmanipulate.set_name('PListCopy_' + outputListName)
605 pmanipulate.param('outputListName', outputListName)
606 pmanipulate.param('inputListNames', inputListNames)
607 pmanipulate.param('writeOut', writeOut)
608 path.add_module(pmanipulate)
609
610
611def copyParticles(outputListName, inputListName, writeOut=False, path=None):
612 """
613 Create copies of Particles given in the input ParticleList and add them to the output ParticleList.
614
615 The existing relations of the original Particle (or it's (grand-)^n-daughters)
616 are copied as well. Note that only the relation is copied and that the related
617 object is not. Copied particles are therefore related to the *same* object as
618 the original ones.
619
620 @param ouputListName new ParticleList filled with copied Particles
621 @param inputListName input ParticleList with original Particles
622 @param writeOut whether RootOutput module should save the created ParticleList
623 @param path modules are added to this path
624 """
625
626 # first copy original particles to the new ParticleList
627 pmanipulate = register_module('ParticleListManipulator')
628 pmanipulate.set_name('PListCopy_' + outputListName)
629 pmanipulate.param('outputListName', outputListName)
630 pmanipulate.param('inputListNames', [inputListName])
631 pmanipulate.param('writeOut', writeOut)
632 path.add_module(pmanipulate)
633
634 # now replace original particles with their copies
635 pcopy = register_module('ParticleCopier')
636 pcopy.param('inputListNames', [outputListName])
637 path.add_module(pcopy)
638
639
640def cutAndCopyLists(outputListName, inputListNames, cut, writeOut=False, path=None):
641 """
642 Copy candidates from all lists in ``inputListNames`` to
643 ``outputListName`` if they pass ``cut`` (given selection criteria).
644
645 Note:
646 Note that the Particles themselves are not copied.
647 The original and copied ParticleLists will point to the same Particles.
648
649 Example:
650 Require energetic pions safely inside the cdc
651
652 .. code-block:: python
653
654 cutAndCopyLists("pi+:energeticPions", ["pi+:good", "pi+:loose"], "[E > 2] and thetaInCDCAcceptance", path=mypath)
655
656 Warning:
657 You must use square braces ``[`` and ``]`` for conditional statements.
658
659 Parameters:
660 outputListName (str): the new ParticleList name
661 inputListName (list(str)): list of input ParticleList names
662 cut (str): Candidates that do not pass these selection criteria are removed from the ParticleList
663 writeOut (bool): whether RootOutput module should save the created ParticleList
664 path (basf2.Path): modules are added to this path
665 """
666
667 pmanipulate = register_module('ParticleListManipulator')
668 pmanipulate.set_name('PListCutAndCopy_' + outputListName)
669 pmanipulate.param('outputListName', outputListName)
670 pmanipulate.param('inputListNames', inputListNames)
671 pmanipulate.param('cut', cut)
672 pmanipulate.param('writeOut', writeOut)
673 path.add_module(pmanipulate)
674
675
676def cutAndCopyList(outputListName, inputListName, cut, writeOut=False, path=None):
677 """
678 Copy candidates from ``inputListName`` to ``outputListName`` if they pass
679 ``cut`` (given selection criteria).
680
681 Note:
682 Note the Particles themselves are not copied.
683 The original and copied ParticleLists will point to the same Particles.
684
685 Example:
686 require energetic pions safely inside the cdc
687
688 .. code-block:: python
689
690 cutAndCopyList("pi+:energeticPions", "pi+:loose", "[E > 2] and thetaInCDCAcceptance", path=mypath)
691
692 Warning:
693 You must use square braces ``[`` and ``]`` for conditional statements.
694
695 Parameters:
696 outputListName (str): the new ParticleList name
697 inputListName (str): input ParticleList name
698 cut (str): Candidates that do not pass these selection criteria are removed from the ParticleList
699 writeOut (bool): whether RootOutput module should save the created ParticleList
700 path (basf2.Path): modules are added to this path
701 """
702
703 cutAndCopyLists(outputListName, [inputListName], cut, writeOut, path)
704
705
706def removeTracksForTrackingEfficiencyCalculation(inputListNames, fraction, path=None):
707 """
708 Randomly remove tracks from the provided particle lists to estimate the tracking efficiency.
709 Takes care of the duplicates, if any.
710
711 Parameters:
712 inputListNames (list(str)): input particle list names
713 fraction (float): fraction of particles to be removed randomly
714 path (basf2.Path): module is added to this path
715 """
716
717 trackingefficiency = register_module('TrackingEfficiency')
718 trackingefficiency.param('particleLists', inputListNames)
719 trackingefficiency.param('frac', fraction)
720 path.add_module(trackingefficiency)
721
722
723def scaleTrackMomenta(inputListNames, scale=float('nan'), payloadName="", scalingFactorName="SF", path=None):
724 """
725 Scale momenta of the particles according to a scaling factor scale.
726 This scaling factor can either be given as constant number or as the name of the payload which contains
727 the variable scale factors.
728 If the particle list contains composite particles, the momenta of the track-based daughters are scaled.
729 Subsequently, the momentum of the mother particle is updated as well.
730
731 Parameters:
732 inputListNames (list(str)): input particle list names
733 scale (float): scaling factor (1.0 -- no scaling)
734 payloadName (str): name of the payload which contains the phase-space dependent scaling factors
735 scalingFactorName (str): name of scaling factor variable in the payload.
736 path (basf2.Path): module is added to this path
737 """
738
739 import b2bii
740 if b2bii.isB2BII():
741 B2ERROR("The tracking momentum scaler can only be run over Belle II data.")
742
743 TrackingMomentumScaleFactors = register_module('TrackingMomentumScaleFactors')
744 TrackingMomentumScaleFactors.param('particleLists', inputListNames)
745 TrackingMomentumScaleFactors.param('scale', scale)
746 TrackingMomentumScaleFactors.param('payloadName', payloadName)
747 TrackingMomentumScaleFactors.param('scalingFactorName', scalingFactorName)
748
749 path.add_module(TrackingMomentumScaleFactors)
750
751
752def correctTrackEnergy(inputListNames, correction=float('nan'), payloadName="", correctionName="SF", path=None):
753 """
754 Correct the energy loss of tracks according to a 'correction' value.
755 This correction can either be given as constant number or as the name of the payload which contains
756 the variable corrections.
757 If the particle list contains composite particles, the momenta of the track-based daughters are corrected.
758 Subsequently, the momentum of the mother particle is updated as well.
759
760 Parameters:
761 inputListNames (list(str)): input particle list names
762 correction (float): correction value to be subtracted to the particle energy (0.0 -- no correction)
763 payloadName (str): name of the payload which contains the phase-space dependent scaling factors
764 correctionName (str): name of correction variable in the payload.
765 path (basf2.Path): module is added to this path
766 """
767
768 import b2bii
769 if b2bii.isB2BII():
770 B2ERROR("The tracking energy correction can only be run over Belle II data.")
771
772 TrackingEnergyLossCorrection = register_module('TrackingEnergyLossCorrection')
773 TrackingEnergyLossCorrection.param('particleLists', inputListNames)
774 TrackingEnergyLossCorrection.param('correction', correction)
775 TrackingEnergyLossCorrection.param('payloadName', payloadName)
776 TrackingEnergyLossCorrection.param('correctionName', correctionName)
777
778 path.add_module(TrackingEnergyLossCorrection)
779
780
781def smearTrackMomenta(inputListNames, payloadName="", smearingFactorName="smear", path=None):
782 """
783 Smear the momenta of the particles according the values read from the given payload.
784 If the particle list contains composite particles, the momenta of the track-based daughters are smeared.
785 Subsequently, the momentum of the mother particle is updated as well.
786
787 Parameters:
788 inputListNames (list(str)): input particle list names
789 payloadName (str): name of the payload which contains the smearing values
790 smearingFactorName (str): name of smearing factor variable in the payload.
791 path (basf2.Path): module is added to this path
792 """
793
794 TrackingMomentumScaleFactors = register_module('TrackingMomentumScaleFactors')
795 TrackingMomentumScaleFactors.param('particleLists', inputListNames)
796 TrackingMomentumScaleFactors.param('payloadName', payloadName)
797 TrackingMomentumScaleFactors.param('smearingFactorName', smearingFactorName)
798
799 path.add_module(TrackingMomentumScaleFactors)
800
801
802def mergeListsWithBestDuplicate(outputListName,
803 inputListNames,
804 variable,
805 preferLowest=True,
806 writeOut=False,
807 ignoreMotherFlavor=False,
808 path=None):
809 """
810 Merge input ParticleLists into one output ParticleList. Only the best
811 among duplicates is kept. The lowest or highest value (configurable via
812 preferLowest) of the provided variable determines which duplicate is the
813 best.
814
815 @param ouputListName name of merged ParticleList
816 @param inputListName vector of original ParticleLists to be merged
817 @param variable variable to determine best duplicate
818 @param preferLowest whether lowest or highest value of variable should be preferred
819 @param writeOut whether RootOutput module should save the created ParticleList
820 @param ignoreMotherFlavor whether the flavor of the mother particle is ignored when trying to find duplicates
821 @param path modules are added to this path
822 """
823
824 pmanipulate = register_module('ParticleListManipulator')
825 pmanipulate.set_name('PListMerger_' + outputListName)
826 pmanipulate.param('outputListName', outputListName)
827 pmanipulate.param('inputListNames', inputListNames)
828 pmanipulate.param('variable', variable)
829 pmanipulate.param('preferLowest', preferLowest)
830 pmanipulate.param('writeOut', writeOut)
831 pmanipulate.param('ignoreMotherFlavor', ignoreMotherFlavor)
832 path.add_module(pmanipulate)
833
834
835def fillSignalSideParticleList(outputListName, decayString, path):
836 """
837 This function should only be used in the ROE path, that is a path
838 that is executed for each ROE object in the DataStore.
839
840 Example: fillSignalSideParticleList('gamma:sig','B0 -> K*0 ^gamma', roe_path)
841
842 Function will create a ParticleList with name 'gamma:sig' which will be filled
843 with the existing photon Particle, being the second daughter of the B0 candidate
844 to which the ROE object has to be related.
845
846 @param ouputListName name of the created ParticleList
847 @param decayString specify Particle to be added to the ParticleList
848 """
849
850 pload = register_module('SignalSideParticleListCreator')
851 pload.set_name('SSParticleList_' + outputListName)
852 pload.param('particleListName', outputListName)
853 pload.param('decayString', decayString)
854 path.add_module(pload)
855
856
857def fillParticleLists(decayStringsWithCuts, writeOut=False, path=None, enforceFitHypothesis=False,
858 loadPhotonsFromKLM=False):
859 """
860 Creates Particles of the desired types from the corresponding ``mdst`` dataobjects,
861 loads them to the ``StoreArray<Particle>`` and fills the ParticleLists.
862
863 The multiple ParticleLists with their own selection criteria are specified
864 via list tuples (decayString, cut), for example
865
866 .. code-block:: python
867
868 kaons = ('K+:mykaons', 'kaonID>0.1')
869 pions = ('pi+:mypions','pionID>0.1')
870 fillParticleLists([kaons, pions], path=mypath)
871
872 If you are unsure what selection you want, you might like to see the
873 :doc:`StandardParticles` functions.
874
875 The type of the particles to be loaded is specified via the decayString module parameter.
876 The type of the ``mdst`` dataobject that is used as an input is determined from the type of
877 the particle. The following types of the particles can be loaded:
878
879 * charged final state particles (input ``mdst`` type = Tracks)
880 - e+, mu+, pi+, K+, p, deuteron (and charge conjugated particles)
881
882 * neutral final state particles
883 - "gamma" (input ``mdst`` type = ECLCluster)
884 - "K_S0", "Lambda0" (input ``mdst`` type = V0)
885 - "K_L0", "n0" (input ``mdst`` type = KLMCluster or ECLCluster)
886
887 Note:
888 For "K_S0" and "Lambda0" you must specify the daughter ordering.
889
890 For example, to load V0s as :math:`\\Lambda^0\\to p^+\\pi^-` decays from V0s:
891
892 .. code-block:: python
893
894 v0lambdas = ('Lambda0 -> p+ pi-', '0.9 < M < 1.3')
895 fillParticleLists([kaons, pions, v0lambdas], path=mypath)
896
897 Tip:
898 Gammas can also be loaded from KLMClusters by explicitly setting the
899 parameter ``loadPhotonsFromKLM`` to True. However, this should only be
900 done in selected use-cases and the effect should be studied carefully.
901
902 Tip:
903 For "K_L0" it is now possible to load from ECLClusters, to revert to
904 the old (Belle) behavior, you can require ``'isFromKLM > 0'``.
905
906 .. code-block:: python
907
908 klongs = ('K_L0', 'isFromKLM > 0')
909 fillParticleLists([kaons, pions, klongs], path=mypath)
910
911 * Charged kinks final state particles (input ``mdst`` type = Kink)
912
913 Note:
914 To reconstruct charged particle kink you must specify the daughter.
915
916 For example, to load Kinks as :math:`K^- \\to \\pi^-\\pi^0` decays from Kinks:
917
918 .. code-block:: python
919
920 kinkKaons = ('K- -> pi-', yourCut)
921 fillParticleLists([kaons, pions, v0lambdas, kinkKaons], path=mypath)
922
923
924 Parameters:
925 decayStringsWithCuts (list): A list of python ntuples of (decayString, cut).
926 The decay string determines the type of Particle
927 and the name of the ParticleList.
928 If the input MDST type is V0 the whole
929 decay chain needs to be specified, so that
930 the user decides and controls the daughters
931 ' order (e.g. ``K_S0 -> pi+ pi-``).
932 If the input MDST type is Kink the decay chain needs to be specified
933 with only one daughter (e.g. ``K- -> pi-``).
934 The cut is the selection criteria
935 to be added to the ParticleList. It can be an empty string.
936 writeOut (bool): whether RootOutput module should save the created ParticleList
937 path (basf2.Path): modules are added to this path
938 enforceFitHypothesis (bool): If true, Particles will be created only for the tracks which have been fitted
939 using a mass hypothesis of the exact type passed to fillParticleLists().
940 If enforceFitHypothesis is False (the default) the next closest fit hypothesis
941 in terms of mass difference will be used if the fit using exact particle
942 type is not available.
943 loadPhotonsFromKLM (bool): If true, photon candidates will be created from KLMClusters as well.
944 """
945
946 pload = register_module('ParticleLoader')
947 pload.set_name('ParticleLoader_' + 'PLists')
948 pload.param('decayStrings', [decayString for decayString, cut in decayStringsWithCuts])
949 pload.param('writeOut', writeOut)
950 pload.param("enforceFitHypothesis", enforceFitHypothesis)
951 path.add_module(pload)
952
953 from ROOT import Belle2
954 decayDescriptor = Belle2.DecayDescriptor()
955 for decayString, cut in decayStringsWithCuts:
956 if not decayDescriptor.init(decayString):
957 raise ValueError("Invalid decay string")
958 # need to check some logic to unpack possible scenarios
959 if decayDescriptor.getNDaughters() > 0:
960 # ... then we have an actual decay in the decay string which must be a V0 (if more than 1 daughter)
961 # or a kink (if 1 daughter)
962 # the particle loader automatically calls this "V0" or "kink", respectively, so we have to copy over
963 # the list to name/format that user wants
964 if (decayDescriptor.getNDaughters() == 1) and (decayDescriptor.getMother().getLabel() != 'kink'):
965 copyList(decayDescriptor.getMother().getFullName(), decayDescriptor.getMother().getName() + ':kink',
966 writeOut, path)
967 if (decayDescriptor.getNDaughters() > 1) and (decayDescriptor.getMother().getLabel() != 'V0'):
968 copyList(decayDescriptor.getMother().getFullName(), decayDescriptor.getMother().getName() + ':V0', writeOut, path)
969 elif (decayDescriptor.getMother().getLabel() != 'all' and
970 abs(decayDescriptor.getMother().getPDGCode()) != Belle2.Const.neutron.getPDGCode()):
971 # then we have a non-V0/kink particle which the particle loader automatically calls "all"
972 # as with the special V0 and kink cases we have to copy over the list to the name/format requested
973 copyList(decayString, decayDescriptor.getMother().getName() + ':all', writeOut, path)
974
975 # optionally apply a cut
976 if cut != "":
977 applyCuts(decayDescriptor.getMother().getFullName(), cut, path)
978
979 if decayString.startswith("gamma"):
980 # keep KLM-source photons as a experts-only for now: they are loaded by the particle loader,
981 # but the user has to explicitly request them.
982 if not loadPhotonsFromKLM:
983 applyCuts(decayString, 'isFromECL', path)
984
985
986def fillParticleList(decayString, cut, writeOut=False, path=None, enforceFitHypothesis=False,
987 loadPhotonsFromKLM=False):
988 """
989 Creates Particles of the desired type from the corresponding ``mdst`` dataobjects,
990 loads them to the StoreArray<Particle> and fills the ParticleList.
991
992 See also:
993 the :doc:`StandardParticles` functions.
994
995 The type of the particles to be loaded is specified via the decayString module parameter.
996 The type of the ``mdst`` dataobject that is used as an input is determined from the type of
997 the particle. The following types of the particles can be loaded:
998
999 * charged final state particles (input ``mdst`` type = Tracks)
1000 - e+, mu+, pi+, K+, p, deuteron (and charge conjugated particles)
1001
1002 * neutral final state particles
1003 - "gamma" (input ``mdst`` type = ECLCluster)
1004 - "K_S0", "Lambda0" (input ``mdst`` type = V0)
1005 - "K_L0", "n0" (input ``mdst`` type = KLMCluster or ECLCluster)
1006
1007 Note:
1008 For "K_S0" and "Lambda0" you must specify the daughter ordering.
1009
1010 For example, to load V0s as :math:`\\Lambda^0\\to p^+\\pi^-` decays from V0s:
1011
1012 .. code-block:: python
1013
1014 fillParticleList('Lambda0 -> p+ pi-', '0.9 < M < 1.3', path=mypath)
1015
1016 Tip:
1017 Gammas can also be loaded from KLMClusters by explicitly setting the
1018 parameter ``loadPhotonsFromKLM`` to True. However, this should only be
1019 done in selected use-cases and the effect should be studied carefully.
1020
1021 Tip:
1022 For "K_L0" it is now possible to load from ECLClusters, to revert to
1023 the old (Belle) behavior, you can require ``'isFromKLM > 0'``.
1024
1025 .. code-block:: python
1026
1027 fillParticleList('K_L0', 'isFromKLM > 0', path=mypath)
1028
1029 * Charged kinks final state particles (input ``mdst`` type = Kink)
1030
1031 .. note::
1032 To reconstruct charged particle kink you must specify the daughter.
1033
1034 For example, to load Kinks as :math:`K^- \\to \\pi^-\\pi^0` decays from Kinks:
1035
1036 .. code-block:: python
1037
1038 fillParticleList('K- -> pi-', yourCut, path=mypath)
1039
1040
1041 Parameters:
1042 decayString (str): Type of Particle and determines the name of the ParticleList.
1043 If the input MDST type is V0 the whole decay chain needs to be specified, so that
1044 the user decides and controls the daughters' order (e.g. ``K_S0 -> pi+ pi-``).
1045 If the input MDST type is Kink the decay chain needs to be specified
1046 with only one daughter (e.g. ``K- -> pi-``).
1047 cut (str): Particles need to pass these selection criteria to be added to the ParticleList
1048 writeOut (bool): whether RootOutput module should save the created ParticleList
1049 path (basf2.Path): modules are added to this path
1050 enforceFitHypothesis (bool): If true, Particles will be created only for the tracks which have been fitted
1051 using a mass hypothesis of the exact type passed to fillParticleLists().
1052 If enforceFitHypothesis is False (the default) the next closest fit hypothesis
1053 in terms of mass difference will be used if the fit using exact particle
1054 type is not available.
1055 loadPhotonsFromKLM (bool): If true, photon candidates will be created from KLMClusters as well.
1056 """
1057
1058 pload = register_module('ParticleLoader')
1059 pload.set_name('ParticleLoader_' + decayString)
1060 pload.param('decayStrings', [decayString])
1061 pload.param('writeOut', writeOut)
1062 pload.param("enforceFitHypothesis", enforceFitHypothesis)
1063 path.add_module(pload)
1064
1065 # need to check some logic to unpack possible scenarios
1066 from ROOT import Belle2
1067 decayDescriptor = Belle2.DecayDescriptor()
1068 if not decayDescriptor.init(decayString):
1069 raise ValueError("Invalid decay string")
1070 if decayDescriptor.getNDaughters() > 0:
1071 # ... then we have an actual decay in the decay string which must be a V0 (if more than 1 daughter)
1072 # or a kink (if 1 daughter)
1073 # the particle loader automatically calls this "V0" or "kink", respectively, so we have to copy over
1074 # the list to name/format that user wants
1075 if (decayDescriptor.getNDaughters() == 1) and (decayDescriptor.getMother().getLabel() != 'kink'):
1076 copyList(decayDescriptor.getMother().getFullName(), decayDescriptor.getMother().getName() + ':kink',
1077 writeOut, path)
1078 if (decayDescriptor.getNDaughters() > 1) and (decayDescriptor.getMother().getLabel() != 'V0'):
1079 copyList(decayDescriptor.getMother().getFullName(), decayDescriptor.getMother().getName() + ':V0', writeOut,
1080 path)
1081 elif (decayDescriptor.getMother().getLabel() != 'all' and
1082 abs(decayDescriptor.getMother().getPDGCode()) != Belle2.Const.neutron.getPDGCode()):
1083 # then we have a non-V0/kink particle which the particle loader automatically calls "all"
1084 # as with the special V0 and kink cases we have to copy over the list to the name/format requested
1085 copyList(decayString, decayDescriptor.getMother().getName() + ':all', writeOut, path)
1086
1087 # optionally apply a cut
1088 if cut != "":
1089 applyCuts(decayDescriptor.getMother().getFullName(), cut, path)
1090
1091 if decayString.startswith("gamma"):
1092 # keep KLM-source photons as a experts-only for now: they are loaded by the particle loader,
1093 # but the user has to explicitly request them.
1094 if not loadPhotonsFromKLM:
1095 applyCuts(decayString, 'isFromECL', path)
1096
1097
1098def fillParticleListWithTrackHypothesis(decayString,
1099 cut,
1100 hypothesis,
1101 writeOut=False,
1102 enforceFitHypothesis=False,
1103 path=None):
1104 """
1105 As fillParticleList, but if used for a charged FSP, loads the particle with the requested hypothesis if available
1106
1107 @param decayString specifies type of Particles and determines the name of the ParticleList
1108 @param cut Particles need to pass these selection criteria to be added to the ParticleList
1109 @param hypothesis the PDG code of the desired track hypothesis
1110 @param writeOut whether RootOutput module should save the created ParticleList
1111 @param enforceFitHypothesis If true, Particles will be created only for the tracks which have been fitted
1112 using a mass hypothesis of the exact type passed to fillParticleLists().
1113 If enforceFitHypothesis is False (the default) the next closest fit hypothesis
1114 in terms of mass difference will be used if the fit using exact particle
1115 type is not available.
1116 @param path modules are added to this path
1117 """
1118
1119 pload = register_module('ParticleLoader')
1120 pload.set_name('ParticleLoader_' + decayString)
1121 pload.param('decayStrings', [decayString])
1122 pload.param('trackHypothesis', hypothesis)
1123 pload.param('writeOut', writeOut)
1124 pload.param("enforceFitHypothesis", enforceFitHypothesis)
1125 path.add_module(pload)
1126
1127 from ROOT import Belle2
1128 decayDescriptor = Belle2.DecayDescriptor()
1129 if not decayDescriptor.init(decayString):
1130 raise ValueError("Invalid decay string")
1131 if decayDescriptor.getMother().getLabel() != 'all':
1132 # the particle loader automatically calls particle lists of charged FSPs "all"
1133 # so we have to copy over the list to the name/format requested
1134 copyList(decayString, decayDescriptor.getMother().getName() + ':all', writeOut, path)
1135
1136 # apply a cut if a non-empty cut string is provided
1137 if cut != "":
1138 applyCuts(decayString, cut, path)
1139
1140
1141def fillConvertedPhotonsList(decayString, cut, writeOut=False, path=None):
1142 """
1143 Creates photon Particle object for each e+e- combination in the V0 StoreArray.
1144
1145 Note:
1146 You must specify the daughter ordering.
1147
1148 .. code-block:: python
1149
1150 fillConvertedPhotonsList('gamma:converted -> e+ e-', '', path=mypath)
1151
1152 Parameters:
1153 decayString (str): Must be gamma to an e+e- pair. You must specify the daughter ordering.
1154 Will also determine the name of the particleList.
1155 cut (str): Particles need to pass these selection criteria to be added to the ParticleList
1156 writeOut (bool): whether RootOutput module should save the created ParticleList
1157 path (basf2.Path): modules are added to this path
1158
1159 """
1160
1161 import b2bii
1162 if b2bii.isB2BII():
1163 B2ERROR('For Belle converted photons are available in the pre-defined list "gamma:v0mdst".')
1164
1165 pload = register_module('ParticleLoader')
1166 pload.set_name('ParticleLoader_' + decayString)
1167 pload.param('decayStrings', [decayString])
1168 pload.param('addDaughters', True)
1169 pload.param('writeOut', writeOut)
1170 path.add_module(pload)
1171
1172 from ROOT import Belle2
1173 decayDescriptor = Belle2.DecayDescriptor()
1174 if not decayDescriptor.init(decayString):
1175 raise ValueError("Invalid decay string")
1176 if decayDescriptor.getMother().getLabel() != 'V0':
1177 # the particle loader automatically calls converted photons "V0" so we have to copy over
1178 # the list to name/format that user wants
1179 copyList(decayDescriptor.getMother().getFullName(), decayDescriptor.getMother().getName() + ':V0', writeOut, path)
1180
1181 # apply a cut if a non-empty cut string is provided
1182 if cut != "":
1183 applyCuts(decayDescriptor.getMother().getFullName(), cut, path)
1184
1185
1186def fillParticleListFromROE(decayString,
1187 cut,
1188 maskName='all',
1189 sourceParticleListName='',
1190 useMissing=False,
1191 writeOut=False,
1192 path=None):
1193 """
1194 Creates Particle object for each ROE of the desired type found in the
1195 StoreArray<RestOfEvent>, loads them to the StoreArray<Particle>
1196 and fills the ParticleList. If useMissing is True, then the missing
1197 momentum is used instead of ROE.
1198
1199 The type of the particles to be loaded is specified via the decayString module parameter.
1200
1201 @param decayString specifies type of Particles and determines the name of the ParticleList.
1202 Source ROEs can be taken as a daughter list, for example:
1203 'B0:tagFromROE -> B0:signal'
1204 @param cut Particles need to pass these selection criteria to be added to the ParticleList
1205 @param maskName Name of the ROE mask to use
1206 @param sourceParticleListName Use related ROEs to this particle list as a source
1207 @param useMissing Use missing momentum instead of ROE momentum
1208 @param writeOut whether RootOutput module should save the created ParticleList
1209 @param path modules are added to this path
1210 """
1211
1212 pload = register_module('ParticleLoader')
1213 pload.set_name('ParticleLoader_' + decayString)
1214 pload.param('decayStrings', [decayString])
1215 pload.param('writeOut', writeOut)
1216 pload.param('roeMaskName', maskName)
1217 pload.param('useMissing', useMissing)
1218 pload.param('sourceParticleListName', sourceParticleListName)
1219 pload.param('useROEs', True)
1220 path.add_module(pload)
1221
1222 from ROOT import Belle2
1223 decayDescriptor = Belle2.DecayDescriptor()
1224 if not decayDescriptor.init(decayString):
1225 raise ValueError("Invalid decay string")
1226
1227 # apply a cut if a non-empty cut string is provided
1228 if cut != "":
1229 applyCuts(decayDescriptor.getMother().getFullName(), cut, path)
1230
1231
1232def fillParticleListFromDummy(decayString,
1233 mdstIndex=0,
1234 covMatrix=10000.,
1235 treatAsInvisible=True,
1236 writeOut=False,
1237 path=None):
1238 """
1239 Creates a ParticleList and fills it with dummy Particles. For self-conjugated Particles one dummy
1240 Particle is created, for Particles that are not self-conjugated one Particle and one anti-Particle is
1241 created. The four-momentum is set to zero.
1242
1243 The type of the particles to be loaded is specified via the decayString module parameter.
1244
1245 @param decayString specifies type of Particles and determines the name of the ParticleList
1246 @param mdstIndex sets the mdst index of Particles
1247 @param covMatrix sets the value of the diagonal covariance matrix of Particles
1248 @param treatAsInvisible whether treeFitter should treat the Particles as invisible
1249 @param writeOut whether RootOutput module should save the created ParticleList
1250 @param path modules are added to this path
1251 """
1252
1253 pload = register_module('ParticleLoader')
1254 pload.set_name('ParticleLoader_' + decayString)
1255 pload.param('decayStrings', [decayString])
1256 pload.param('useDummy', True)
1257 pload.param('dummyMDSTIndex', mdstIndex)
1258 pload.param('dummyCovMatrix', covMatrix)
1259 pload.param('dummyTreatAsInvisible', treatAsInvisible)
1260 pload.param('writeOut', writeOut)
1261 path.add_module(pload)
1262
1263
1264def fillParticleListFromMC(decayString,
1265 cut,
1266 addDaughters=False,
1267 skipNonPrimaryDaughters=False,
1268 writeOut=False,
1269 path=None,
1270 skipNonPrimary=False,
1271 skipInitial=True):
1272 """
1273 Creates Particle object for each MCParticle of the desired type found in the StoreArray<MCParticle>,
1274 loads them to the StoreArray<Particle> and fills the ParticleList.
1275
1276 The type of the particles to be loaded is specified via the decayString module parameter.
1277
1278 @param decayString specifies type of Particles and determines the name of the ParticleList
1279 @param cut Particles need to pass these selection criteria to be added to the ParticleList
1280 @param addDaughters adds the bottom part of the decay chain of the particle to the datastore and
1281 sets mother-daughter relations
1282 @param skipNonPrimaryDaughters if true, skip non primary daughters, useful to study final state daughter particles
1283 @param writeOut whether RootOutput module should save the created ParticleList
1284 @param path modules are added to this path
1285 @param skipNonPrimary if true, skip non primary particle
1286 @param skipInitial if true, skip initial particles
1287 """
1288
1289 pload = register_module('ParticleLoader')
1290 pload.set_name('ParticleLoader_' + decayString)
1291 pload.param('decayStrings', [decayString])
1292 pload.param('addDaughters', addDaughters)
1293 pload.param('skipNonPrimaryDaughters', skipNonPrimaryDaughters)
1294 pload.param('writeOut', writeOut)
1295 pload.param('useMCParticles', True)
1296 pload.param('skipNonPrimary', skipNonPrimary)
1297 pload.param('skipInitial', skipInitial)
1298 path.add_module(pload)
1299
1300 from ROOT import Belle2
1301 decayDescriptor = Belle2.DecayDescriptor()
1302 if not decayDescriptor.init(decayString):
1303 raise ValueError("Invalid decay string")
1304
1305 # apply a cut if a non-empty cut string is provided
1306 if cut != "":
1307 applyCuts(decayString, cut, path)
1308
1309
1310def fillParticleListsFromMC(decayStringsWithCuts,
1311 addDaughters=False,
1312 skipNonPrimaryDaughters=False,
1313 writeOut=False,
1314 path=None,
1315 skipNonPrimary=False,
1316 skipInitial=True):
1317 """
1318 Creates Particle object for each MCParticle of the desired type found in the StoreArray<MCParticle>,
1319 loads them to the StoreArray<Particle> and fills the ParticleLists.
1320
1321 The types of the particles to be loaded are specified via the (decayString, cut) tuples given in a list.
1322 For example:
1323
1324 .. code-block:: python
1325
1326 kaons = ('K+:gen', '')
1327 pions = ('pi+:gen', 'pionID>0.1')
1328 fillParticleListsFromMC([kaons, pions], path=mypath)
1329
1330 .. tip::
1331 Daughters of ``Lambda0`` are not primary, but ``Lambda0`` is not final state particle.
1332 Thus, when one reconstructs a particle from ``Lambda0``, that is created with
1333 ``addDaughters=True`` and ``skipNonPrimaryDaughters=True``, the particle always has ``isSignal==0``.
1334 Please set options for ``Lambda0`` to use MC-matching variables properly as follows,
1335 ``addDaughters=True`` and ``skipNonPrimaryDaughters=False``.
1336
1337 @param decayString specifies type of Particles and determines the name of the ParticleList
1338 @param cut Particles need to pass these selection criteria to be added to the ParticleList
1339 @param addDaughters adds the bottom part of the decay chain of the particle to the datastore and
1340 sets mother-daughter relations
1341 @param skipNonPrimaryDaughters if true, skip non primary daughters, useful to study final state daughter particles
1342 @param writeOut whether RootOutput module should save the created ParticleList
1343 @param path modules are added to this path
1344 @param skipNonPrimary if true, skip non primary particle
1345 @param skipInitial if true, skip initial particles
1346 """
1347
1348 pload = register_module('ParticleLoader')
1349 pload.set_name('ParticleLoader_' + 'PLists')
1350 pload.param('decayStrings', [decayString for decayString, cut in decayStringsWithCuts])
1351 pload.param('addDaughters', addDaughters)
1352 pload.param('skipNonPrimaryDaughters', skipNonPrimaryDaughters)
1353 pload.param('writeOut', writeOut)
1354 pload.param('useMCParticles', True)
1355 pload.param('skipNonPrimary', skipNonPrimary)
1356 pload.param('skipInitial', skipInitial)
1357 path.add_module(pload)
1358
1359 from ROOT import Belle2
1360 decayDescriptor = Belle2.DecayDescriptor()
1361 for decayString, cut in decayStringsWithCuts:
1362 if not decayDescriptor.init(decayString):
1363 raise ValueError("Invalid decay string")
1364
1365 # apply a cut if a non-empty cut string is provided
1366 if cut != "":
1367 applyCuts(decayString, cut, path)
1368
1369
1370def fillParticleListFromChargedCluster(outputParticleList,
1371 inputParticleList,
1372 cut,
1373 useOnlyMostEnergeticECLCluster=True,
1374 writeOut=False,
1375 path=None):
1376 """
1377 Creates the Particle object from ECLCluster and KLMCluster that are being matched with the Track of inputParticleList.
1378
1379 @param outputParticleList The output ParticleList. Only neutral final state particles are supported.
1380 @param inputParticleList The input ParticleList that is required to have the relation to the Track object.
1381 @param cut Particles need to pass these selection criteria to be added to the ParticleList
1382 @param useOnlyMostEnergeticECLCluster If True, only the most energetic ECLCluster among ones that are matched with the Track is
1383 used. If False, all matched ECLClusters are loaded. The default is True. Regardless of
1384 this option, the KLMCluster is loaded.
1385 @param writeOut whether RootOutput module should save the created ParticleList
1386 @param path modules are added to this path
1387 """
1388
1389 pload = register_module('ParticleLoader')
1390 pload.set_name('ParticleLoader_' + outputParticleList)
1391
1392 pload.param('decayStrings', [outputParticleList])
1393 pload.param('sourceParticleListName', inputParticleList)
1394 pload.param('writeOut', writeOut)
1395 pload.param('loadChargedCluster', True)
1396 pload.param('useOnlyMostEnergeticECLCluster', useOnlyMostEnergeticECLCluster)
1397 path.add_module(pload)
1398
1399 # apply a cut if a non-empty cut string is provided
1400 if cut != "":
1401 applyCuts(outputParticleList, cut, path)
1402
1403
1404def extractParticlesFromROE(particleLists,
1405 signalSideParticleList=None,
1406 maskName='all',
1407 writeOut=False,
1408 path=None):
1409 """
1410 Extract Particle objects that belong to the Rest-Of-Events and fill them into the ParticleLists.
1411 The types of the particles other than those specified by ``particleLists`` are not stored.
1412 If one creates a ROE with ``fillWithMostLikely=True`` via `buildRestOfEvent`, for example,
1413 one should create particleLists for not only ``pi+``, ``gamma``, ``K_L0`` but also other charged final state particles.
1414
1415 When one calls the function in the main path, one has to set the argument ``signalSideParticleList`` and the signal side
1416 ParticleList must have only one candidate.
1417
1418 .. code-block:: python
1419
1420 buildRestOfEvent('B0:sig', fillWithMostLikely=True, path=mypath)
1421
1422 roe_path = create_path()
1423 deadEndPath = create_path()
1424 signalSideParticleFilter('B0:sig', '', roe_path, deadEndPath)
1425
1426 plists = ['%s:in_roe' % ptype for ptype in ['pi+', 'gamma', 'K_L0', 'K+', 'p+', 'e+', 'mu+']]
1427 extractParticlesFromROE(plists, maskName='all', path=roe_path)
1428
1429 # one can analyze these ParticleLists in the roe_path
1430
1431 mypath.for_each('RestOfEvent', 'RestOfEvents', roe_path)
1432
1433 rankByLowest('B0:sig', 'deltaE', numBest=1, path=mypath)
1434 extractParticlesFromROE(plists, signalSideParticleList='B0:sig', maskName='all', path=mypath)
1435
1436 # one can analyze these ParticleLists in the main path
1437
1438
1439 @param particleLists (str or list(str)) Name of output ParticleLists
1440 @param signalSideParticleList (str) Name of signal side ParticleList
1441 @param maskName (str) Name of the ROE mask to be applied on Particles
1442 @param writeOut (bool) whether RootOutput module should save the created ParticleList
1443 @param path (basf2.Path) modules are added to this path
1444 """
1445
1446 if isinstance(particleLists, str):
1447 particleLists = [particleLists]
1448
1449 pext = register_module('ParticleExtractorFromROE')
1450 pext.set_name('ParticleExtractorFromROE_' + '_'.join(particleLists))
1451 pext.param('outputListNames', particleLists)
1452 if signalSideParticleList is not None:
1453 pext.param('signalSideParticleListName', signalSideParticleList)
1454 pext.param('maskName', maskName)
1455 pext.param('writeOut', writeOut)
1456 path.add_module(pext)
1457
1458
1459def applyCuts(list_name, cut, path):
1460 """
1461 Removes particle candidates from ``list_name`` that do not pass ``cut``
1462 (given selection criteria).
1463
1464 Example:
1465 require energetic pions safely inside the cdc
1466
1467 .. code-block:: python
1468
1469 applyCuts("pi+:mypions", "[E > 2] and thetaInCDCAcceptance", path=mypath)
1470
1471 Warning:
1472 You must use square braces ``[`` and ``]`` for conditional statements.
1473
1474 Parameters:
1475 list_name (str): input ParticleList name
1476 cut (str): Candidates that do not pass these selection criteria are removed from the ParticleList
1477 path (basf2.Path): modules are added to this path
1478 """
1479
1480 pselect = register_module('ParticleSelector')
1481 pselect.set_name('ParticleSelector_applyCuts_' + list_name)
1482 pselect.param('decayString', list_name)
1483 pselect.param('cut', cut)
1484 path.add_module(pselect)
1485
1486
1487def applyEventCuts(cut, path, metavariables=None):
1488 """
1489 Removes events that do not pass the ``cut`` (given selection criteria).
1490
1491 Example:
1492 continuum events (in mc only) with more than 5 tracks
1493
1494 .. code-block:: python
1495
1496 applyEventCuts("[nTracks > 5] and [isContinuumEvent], path=mypath)
1497
1498 .. warning::
1499 Only event-based variables are allowed in this function
1500 and only square brackets ``[`` and ``]`` for conditional statements.
1501
1502 Parameters:
1503 cut (str): Events that do not pass these selection criteria are skipped
1504 path (basf2.Path): modules are added to this path
1505 metavariables (list(str)): List of meta variables to be considered in decomposition of cut
1506 """
1507
1508 import b2parser
1509 from variables import variables
1510
1511 def find_vars(t: tuple, var_list: list, meta_list: list) -> None:
1512 """ Recursive helper function to find variable names """
1513 if not isinstance(t, tuple):
1514 return
1515 if t[0] == b2parser.B2ExpressionParser.node_types['IdentifierNode']:
1516 var_list += [t[1]]
1517 return
1518 if t[0] == b2parser.B2ExpressionParser.node_types['FunctionNode']:
1519 meta_list.append(list(t[1:]))
1520 return
1521 for i in t:
1522 if isinstance(i, tuple):
1523 find_vars(i, var_list, meta_list)
1524
1525 def check_variable(var_list: list, metavar_ids: list) -> None:
1526 for var_string in var_list:
1527 # Check if the var_string is alias
1528 orig_name = variables.resolveAlias(var_string)
1529 if orig_name != var_string:
1530 var_list_temp = []
1531 meta_list_temp = []
1532 find_vars(b2parser.parse(orig_name), var_list_temp, meta_list_temp)
1533
1534 check_variable(var_list_temp, metavar_ids)
1535 check_meta(meta_list_temp, metavar_ids)
1536 else:
1537 # Get the variable
1538 var = variables.getVariable(var_string)
1539 if event_var_id not in var.description:
1540 B2ERROR(f'Variable {var_string} is not an event-based variable! "\
1541 "Please check your inputs to the applyEventCuts method!')
1542
1543 def check_meta(meta_list: list, metavar_ids: list) -> None:
1544 for meta_string_list in meta_list:
1545 var_list_temp = []
1546 while meta_string_list[0] in metavar_ids:
1547 # remove special meta variable
1548 meta_string_list.pop(0)
1549 for meta_string in meta_string_list[0].split(","):
1550 find_vars(b2parser.parse(meta_string), var_list_temp, meta_string_list)
1551 if len(meta_string_list) > 0:
1552 meta_string_list.pop(0)
1553 if len(meta_string_list) == 0:
1554 break
1555 if len(meta_string_list) > 1:
1556 meta_list += meta_string_list[1:]
1557 if isinstance(meta_string_list[0], list):
1558 meta_string_list = [element for element in meta_string_list[0]]
1559
1560 check_variable(var_list_temp, metavar_ids)
1561
1562 if len(meta_string_list) == 0:
1563 continue
1564 elif len(meta_string_list) == 1:
1565 var = variables.getVariable(meta_string_list[0])
1566 else:
1567 var = variables.getVariable(meta_string_list[0], meta_string_list[1].split(","))
1568 # Check if the variable's description contains event-based marker
1569 if event_var_id in var.description:
1570 continue
1571 # Throw an error message if non event-based variable is used
1572 B2ERROR(f'Variable {var.name} is not an event-based variable! Please check your inputs to the applyEventCuts method!')
1573
1574 event_var_id = '[Eventbased]'
1575 metavar_ids = ['formula', 'abs',
1576 'cos', 'acos',
1577 'tan', 'atan',
1578 'sin', 'asin',
1579 'exp', 'log', 'log10',
1580 'min', 'max',
1581 'isNAN', 'ifNANgiveX']
1582 if metavariables:
1583 metavar_ids += metavariables
1584
1585 var_list = []
1586 meta_list = []
1587 find_vars(b2parser.parse(cut), var_list=var_list, meta_list=meta_list)
1588
1589 if len(var_list) == 0 and len(meta_list) == 0:
1590 B2WARNING(f'Cut string "{cut}" has no variables for applyEventCuts helper function!')
1591
1592 check_variable(var_list, metavar_ids)
1593 check_meta(meta_list, metavar_ids)
1594
1595 eselect = register_module('VariableToReturnValue')
1596 eselect.param('variable', 'passesEventCut(' + cut + ')')
1597 path.add_module(eselect)
1598 empty_path = create_path()
1599 eselect.if_value('<1', empty_path)
1600
1601
1602def reconstructDecay(decayString,
1603 cut,
1604 dmID=0,
1605 writeOut=False,
1606 path=None,
1607 candidate_limit=None,
1608 ignoreIfTooManyCandidates=True,
1609 chargeConjugation=True,
1610 allowChargeViolation=False):
1611 r"""
1612 Creates new Particles by making combinations of existing Particles - it reconstructs unstable particles via their specified
1613 decay mode, e.g. in form of a :ref:`DecayString`: :code:`D0 -> K- pi+` or :code:`B+ -> anti-D0 pi+`, ... All possible
1614 combinations are created (particles are used only once per candidate) and combinations that pass the specified selection
1615 criteria are saved to a newly created (mother) ParticleList. By default the charge conjugated decay is reconstructed as well
1616 (meaning that the charge conjugated mother list is created as well) but this can be deactivated.
1617
1618 One can use an ``@``-sign to mark a particle as unspecified for inclusive analyses,
1619 e.g. in a DecayString: :code:`'@Xsd -> K+ pi-'`.
1620
1621 .. seealso:: :ref:`Marker_of_unspecified_particle`
1622
1623 .. warning::
1624 The input ParticleLists are typically ordered according to the upstream reconstruction algorithm.
1625 Therefore, if you combine two or more identical particles in the decay chain you should not expect to see the same
1626 distribution for the daughter kinematics as they may be sorted by geometry, momentum etc.
1627
1628 For example, in the decay :code:`D0 -> pi0 pi0` the momentum distributions of the two ``pi0`` s are not identical.
1629 This can be solved by manually randomising the lists before combining.
1630
1631 See Also:
1632
1633 * `Particle combiner how does it work? <https://questions.belle2.org/question/4318/particle-combiner-how-does-it-work/>`_
1634 * `Identical particles in decay chain <https://questions.belle2.org/question/5724/identical-particles-in-decay-chain/>`_
1635
1636 @param decayString :ref:`DecayString` specifying what kind of the decay should be reconstructed
1637 (from the DecayString the mother and daughter ParticleLists are determined)
1638 @param cut created (mother) Particles are added to the mother ParticleList if they
1639 pass give cuts (in VariableManager style) and rejected otherwise
1640 @param dmID user specified decay mode identifier
1641 @param writeOut whether RootOutput module should save the created ParticleList
1642 @param path modules are added to this path
1643 @param candidate_limit Maximum amount of candidates to be reconstructed. If
1644 the number of candidates is exceeded a Warning will be
1645 printed.
1646 By default, all these candidates will be removed and event will be ignored.
1647 This behaviour can be changed by \'ignoreIfTooManyCandidates\' flag.
1648 If no value is given the amount is limited to a sensible
1649 default. A value <=0 will disable this limit and can
1650 cause huge memory amounts so be careful.
1651 @param ignoreIfTooManyCandidates whether event should be ignored or not if number of reconstructed
1652 candidates reaches limit. If event is ignored, no candidates are reconstructed,
1653 otherwise, number of candidates in candidate_limit is reconstructed.
1654 @param chargeConjugation boolean to decide whether charge conjugated mode should be reconstructed as well (on by default)
1655 @param allowChargeViolation whether the decay string needs to conserve the electric charge
1656 """
1657
1658 pmake = register_module('ParticleCombiner')
1659 pmake.set_name('ParticleCombiner_' + decayString)
1660 pmake.param('decayString', decayString)
1661 pmake.param('cut', cut)
1662 pmake.param('decayMode', dmID)
1663 pmake.param('writeOut', writeOut)
1664 if candidate_limit is not None:
1665 pmake.param("maximumNumberOfCandidates", candidate_limit)
1666 pmake.param("ignoreIfTooManyCandidates", ignoreIfTooManyCandidates)
1667 pmake.param('chargeConjugation', chargeConjugation)
1668 pmake.param("allowChargeViolation", allowChargeViolation)
1669 path.add_module(pmake)
1670
1671
1672def combineAllParticles(inputParticleLists, outputList, cut='', writeOut=False, path=None):
1673 """
1674 Creates a new Particle as the combination of all Particles from all
1675 provided inputParticleLists. However, each particle is used only once
1676 (even if duplicates are provided) and the combination has to pass the
1677 specified selection criteria to be saved in the newly created (mother)
1678 ParticleList.
1679
1680 @param inputParticleLists List of input particle lists which are combined to the new Particle
1681 @param outputList Name of the particle combination created with this module
1682 @param cut created (mother) Particle is added to the mother ParticleList if it passes
1683 these given cuts (in VariableManager style) and is rejected otherwise
1684 @param writeOut whether RootOutput module should save the created ParticleList
1685 @param path module is added to this path
1686 """
1687
1688 pmake = register_module('AllParticleCombiner')
1689 pmake.set_name('AllParticleCombiner_' + outputList)
1690 pmake.param('inputListNames', inputParticleLists)
1691 pmake.param('outputListName', outputList)
1692 pmake.param('cut', cut)
1693 pmake.param('writeOut', writeOut)
1694 path.add_module(pmake)
1695
1696
1697def reconstructMissingKlongDecayExpert(decayString,
1698 cut,
1699 dmID=0,
1700 writeOut=False,
1701 path=None,
1702 recoList="_reco"):
1703 """
1704 Creates a list of K_L0's and of B -> K_L0 + X, with X being a fully-reconstructed state.
1705 The K_L0 momentum is determined from kinematic constraints of the two-body B decay into K_L0 and X
1706
1707 @param decayString DecayString specifying what kind of the decay should be reconstructed
1708 (from the DecayString the mother and daughter ParticleLists are determined)
1709 @param cut Particles are added to the K_L0 and B ParticleList if the B candidates
1710 pass the given cuts (in VariableManager style) and rejected otherwise
1711 @param dmID user specified decay mode identifier
1712 @param writeOut whether RootOutput module should save the created ParticleList
1713 @param path modules are added to this path
1714 @param recoList suffix appended to original K_L0 and B ParticleList that identify the newly created K_L0 and B lists
1715 """
1716
1717 pcalc = register_module('KlongMomentumCalculatorExpert')
1718 pcalc.set_name('KlongMomentumCalculatorExpert_' + decayString)
1719 pcalc.param('decayString', decayString)
1720 pcalc.param('writeOut', writeOut)
1721 pcalc.param('recoList', recoList)
1722 path.add_module(pcalc)
1723
1724 rmake = register_module('KlongDecayReconstructorExpert')
1725 rmake.set_name('KlongDecayReconstructorExpert_' + decayString)
1726 rmake.param('decayString', decayString)
1727 rmake.param('cut', cut)
1728 rmake.param('decayMode', dmID)
1729 rmake.param('writeOut', writeOut)
1730 rmake.param('recoList', recoList)
1731 path.add_module(rmake)
1732
1733
1734def setBeamConstrainedMomentum(particleList, decayStringTarget, decayStringDaughters, path=None):
1735 """
1736 Replace the four-momentum of the target Particle by p(beam) - p(selected daughters).
1737 The momentum of the mother Particle will not be changed.
1738
1739 @param particleList mother Particlelist
1740 @param decayStringTarget DecayString specifying the target particle whose momentum
1741 will be updated
1742 @param decayStringDaughters DecayString specifying the daughter particles used to replace
1743 the momentum of the target particle by p(beam)-p(daughters)
1744 """
1745
1746 mod = register_module('ParticleMomentumUpdater')
1747 mod.set_name('ParticleMomentumUpdater' + particleList)
1748 mod.param('particleList', particleList)
1749 mod.param('decayStringTarget', decayStringTarget)
1750 mod.param('decayStringDaughters', decayStringDaughters)
1751 path.add_module(mod)
1752
1753
1754def updateKlongKinematicsExpert(particleList,
1755 writeOut=False,
1756 path=None):
1757 """
1758 Calculates and updates the kinematics of B->K_L0 + something else with same method as
1759 `reconstructMissingKlongDecayExpert`. This helps to revert the kinematics after the vertex fitting.
1760
1761 @param particleList input ParticleList of B meson that decays to K_L0 + X
1762 @param writeOut whether RootOutput module should save the ParticleList
1763 @param path modules are added to this path
1764 """
1765
1766 mod = register_module('KlongMomentumUpdaterExpert')
1767 mod.set_name('KlongMomentumUpdaterExpert_' + particleList)
1768 mod.param('listName', particleList)
1769 mod.param('writeOut', writeOut)
1770 path.add_module(mod)
1771
1772
1773def replaceMass(replacerName, particleLists=None, pdgCode=22, path=None):
1774 """
1775 replaces the mass of the particles inside the given particleLists
1776 with the invariant mass of the particle corresponding to the given pdgCode.
1777
1778 @param particleLists new ParticleList filled with copied Particles
1779 @param pdgCode PDG code for mass reference
1780 @param path modules are added to this path
1781 """
1782
1783 if particleLists is None:
1784 particleLists = []
1785
1786 # first copy original particles to the new ParticleList
1787 pmassupdater = register_module('ParticleMassUpdater')
1788 pmassupdater.set_name('ParticleMassUpdater_' + replacerName)
1789 pmassupdater.param('particleLists', particleLists)
1790 pmassupdater.param('pdgCode', pdgCode)
1791 path.add_module(pmassupdater)
1792
1793
1794def reconstructRecoil(decayString,
1795 cut,
1796 dmID=0,
1797 writeOut=False,
1798 path=None,
1799 candidate_limit=None,
1800 allowChargeViolation=False):
1801 """
1802 Creates new Particles that recoil against the input particles.
1803
1804 For example the decay string M -> D1 D2 D3 will:
1805 - create mother Particle M for each unique combination of D1, D2, D3 Particles
1806 - Particles D1, D2, D3 will be appended as daughters to M
1807 - the 4-momentum of the mother Particle M is given by
1808 p(M) = p(HER) + p(LER) - Sum_i p(Di)
1809
1810 @param decayString DecayString specifying what kind of the decay should be reconstructed
1811 (from the DecayString the mother and daughter ParticleLists are determined)
1812 @param cut created (mother) Particles are added to the mother ParticleList if they
1813 pass give cuts (in VariableManager style) and rejected otherwise
1814 @param dmID user specified decay mode identifier
1815 @param writeOut whether RootOutput module should save the created ParticleList
1816 @param path modules are added to this path
1817 @param candidate_limit Maximum amount of candidates to be reconstructed. If
1818 the number of candidates is exceeded no candidate will be
1819 reconstructed for that event and a Warning will be
1820 printed.
1821 If no value is given the amount is limited to a sensible
1822 default. A value <=0 will disable this limit and can
1823 cause huge memory amounts so be careful.
1824 @param allowChargeViolation whether the decay string needs to conserve the electric charge
1825 """
1826
1827 pmake = register_module('ParticleCombiner')
1828 pmake.set_name('ParticleCombiner_' + decayString)
1829 pmake.param('decayString', decayString)
1830 pmake.param('cut', cut)
1831 pmake.param('decayMode', dmID)
1832 pmake.param('writeOut', writeOut)
1833 pmake.param('recoilParticleType', 1)
1834 if candidate_limit is not None:
1835 pmake.param("maximumNumberOfCandidates", candidate_limit)
1836 pmake.param('allowChargeViolation', allowChargeViolation)
1837 path.add_module(pmake)
1838
1839
1840def reconstructRecoilDaughter(decayString,
1841 cut,
1842 dmID=0,
1843 writeOut=False,
1844 path=None,
1845 candidate_limit=None,
1846 allowChargeViolation=False):
1847 """
1848 Creates new Particles that are daughters of the particle reconstructed in the recoil (always assumed to be the first daughter).
1849
1850 For example the decay string M -> D1 D2 D3 will:
1851 - create mother Particle M for each unique combination of D1, D2, D3 Particles
1852 - Particles D1, D2, D3 will be appended as daughters to M
1853 - the 4-momentum of the mother Particle M is given by
1854 p(M) = p(D1) - Sum_i p(Di), where i>1
1855
1856 @param decayString DecayString specifying what kind of the decay should be reconstructed
1857 (from the DecayString the mother and daughter ParticleLists are determined)
1858 @param cut created (mother) Particles are added to the mother ParticleList if they
1859 pass give cuts (in VariableManager style) and rejected otherwise
1860 @param dmID user specified decay mode identifier
1861 @param writeOut whether RootOutput module should save the created ParticleList
1862 @param path modules are added to this path
1863 @param candidate_limit Maximum amount of candidates to be reconstructed. If
1864 the number of candidates is exceeded no candidate will be
1865 reconstructed for that event and a Warning will be
1866 printed.
1867 If no value is given the amount is limited to a sensible
1868 default. A value <=0 will disable this limit and can
1869 cause huge memory amounts so be careful.
1870 @param allowChargeViolation whether the decay string needs to conserve the electric charge taking into account that the first
1871 daughter is actually the mother
1872 """
1873
1874 pmake = register_module('ParticleCombiner')
1875 pmake.set_name('ParticleCombiner_' + decayString)
1876 pmake.param('decayString', decayString)
1877 pmake.param('cut', cut)
1878 pmake.param('decayMode', dmID)
1879 pmake.param('writeOut', writeOut)
1880 pmake.param('recoilParticleType', 2)
1881 if candidate_limit is not None:
1882 pmake.param("maximumNumberOfCandidates", candidate_limit)
1883 pmake.param('allowChargeViolation', allowChargeViolation)
1884 path.add_module(pmake)
1885
1886
1887def rankByHighest(particleList,
1888 variable,
1889 numBest=0,
1890 outputVariable='',
1891 allowMultiRank=False,
1892 cut='',
1893 overwriteRank=False,
1894 path=None):
1895 """
1896 Ranks particles in the input list by the given variable (highest to lowest), and stores an integer rank for each Particle
1897 in an :b2:var:`extraInfo` field ``${variable}_rank`` starting at 1 (best).
1898 The list is also sorted from best to worst candidate
1899 (each charge, e.g. B+/B-, separately).
1900 This can be used to perform a best candidate selection by cutting on the corresponding rank value, or by specifying
1901 a non-zero value for 'numBest'.
1902
1903 .. tip::
1904 Extra-info fields can be accessed by the :b2:var:`extraInfo` metavariable.
1905 These variable names can become clunky, so it's probably a good idea to set an alias.
1906 For example if you rank your B candidates by momentum,
1907
1908 .. code:: python
1909
1910 rankByHighest("B0:myCandidates", "p", path=mypath)
1911 vm.addAlias("momentumRank", "extraInfo(p_rank)")
1912
1913
1914 @param particleList The input ParticleList
1915 @param variable Variable to order Particles by.
1916 @param numBest If not zero, only the $numBest Particles in particleList with rank <= numBest are kept.
1917 @param outputVariable Name for the variable that will be created which contains the rank, Default is '${variable}_rank'.
1918 @param allowMultiRank If true, candidates with the same value will get the same rank.
1919 @param cut Only candidates passing the cut will be ranked. The others will have rank -1
1920 @param overwriteRank If true, the extraInfo of rank is overwritten when the particle has already the extraInfo.
1921 @param path modules are added to this path
1922 """
1923
1924 bcs = register_module('BestCandidateSelection')
1925 bcs.set_name('BestCandidateSelection_' + particleList + '_' + variable)
1926 bcs.param('particleList', particleList)
1927 bcs.param('variable', variable)
1928 bcs.param('numBest', numBest)
1929 bcs.param('outputVariable', outputVariable)
1930 bcs.param('allowMultiRank', allowMultiRank)
1931 bcs.param('cut', cut)
1932 bcs.param('overwriteRank', overwriteRank)
1933 path.add_module(bcs)
1934
1935
1936def rankByLowest(particleList,
1937 variable,
1938 numBest=0,
1939 outputVariable='',
1940 allowMultiRank=False,
1941 cut='',
1942 overwriteRank=False,
1943 path=None):
1944 """
1945 Ranks particles in the input list by the given variable (lowest to highest), and stores an integer rank for each Particle
1946 in an :b2:var:`extraInfo` field ``${variable}_rank`` starting at 1 (best).
1947 The list is also sorted from best to worst candidate
1948 (each charge, e.g. B+/B-, separately).
1949 This can be used to perform a best candidate selection by cutting on the corresponding rank value, or by specifying
1950 a non-zero value for 'numBest'.
1951
1952 .. tip::
1953 Extra-info fields can be accessed by the :b2:var:`extraInfo` metavariable.
1954 These variable names can become clunky, so it's probably a good idea to set an alias.
1955 For example if you rank your B candidates by :b2:var:`dM`,
1956
1957 .. code:: python
1958
1959 rankByLowest("B0:myCandidates", "dM", path=mypath)
1960 vm.addAlias("massDifferenceRank", "extraInfo(dM_rank)")
1961
1962
1963 @param particleList The input ParticleList
1964 @param variable Variable to order Particles by.
1965 @param numBest If not zero, only the $numBest Particles in particleList with rank <= numBest are kept.
1966 @param outputVariable Name for the variable that will be created which contains the rank, Default is '${variable}_rank'.
1967 @param allowMultiRank If true, candidates with the same value will get the same rank.
1968 @param cut Only candidates passing the cut will be ranked. The others will have rank -1
1969 @param overwriteRank If true, the extraInfo of rank is overwritten when the particle has already the extraInfo.
1970 @param path modules are added to this path
1971 """
1972
1973 bcs = register_module('BestCandidateSelection')
1974 bcs.set_name('BestCandidateSelection_' + particleList + '_' + variable)
1975 bcs.param('particleList', particleList)
1976 bcs.param('variable', variable)
1977 bcs.param('numBest', numBest)
1978 bcs.param('selectLowest', True)
1979 bcs.param('allowMultiRank', allowMultiRank)
1980 bcs.param('outputVariable', outputVariable)
1981 bcs.param('cut', cut)
1982 bcs.param('overwriteRank', overwriteRank)
1983 path.add_module(bcs)
1984
1985
1986def applyRandomCandidateSelection(particleList, path=None):
1987 """
1988 If there are multiple candidates in the provided particleList, all but one of them are removed randomly.
1989 This is done on a event-by-event basis.
1990
1991 @param particleList ParticleList for which the random candidate selection should be applied
1992 @param path module is added to this path
1993 """
1994
1995 rcs = register_module('BestCandidateSelection')
1996 rcs.set_name('RandomCandidateSelection_' + particleList)
1997 rcs.param('particleList', particleList)
1998 rcs.param('variable', 'random')
1999 rcs.param('selectLowest', False)
2000 rcs.param('allowMultiRank', False)
2001 rcs.param('numBest', 1)
2002 rcs.param('cut', '')
2003 rcs.param('outputVariable', '')
2004 path.add_module(rcs)
2005
2006
2007def printDataStore(eventNumber=-1, path=None):
2008 """
2009 Prints the contents of DataStore in the first event (or a specific event number or all events).
2010 Will list all objects and arrays (including size).
2011
2012 See also:
2013 The command line tool: ``b2file-size``.
2014
2015 Parameters:
2016 eventNumber (int): Print the datastore only for this event. The default
2017 (-1) prints only the first event, 0 means print for all events (can produce large output)
2018 path (basf2.Path): the PrintCollections module is added to this path
2019
2020 Warning:
2021 This will print a lot of output if you print it for all events and process many events.
2022
2023 """
2024
2025 printDS = register_module('PrintCollections')
2026 printDS.param('printForEvent', eventNumber)
2027 path.add_module(printDS)
2028
2029
2030def printVariableValues(list_name, var_names, path):
2031 """
2032 Prints out values of specified variables of all Particles included in given ParticleList. For debugging purposes.
2033
2034 @param list_name input ParticleList name
2035 @param var_names vector of variable names to be printed
2036 @param path modules are added to this path
2037 """
2038
2039 prlist = register_module('ParticlePrinter')
2040 prlist.set_name('ParticlePrinter_' + list_name)
2041 prlist.param('listName', list_name)
2042 prlist.param('fullPrint', False)
2043 prlist.param('variables', var_names)
2044 path.add_module(prlist)
2045
2046
2047def printList(list_name, full, path):
2048 """
2049 Prints the size and executes Particle->print() (if full=True)
2050 method for all Particles in given ParticleList. For debugging purposes.
2051
2052 @param list_name input ParticleList name
2053 @param full execute Particle->print() method for all Particles
2054 @param path modules are added to this path
2055 """
2056
2057 prlist = register_module('ParticlePrinter')
2058 prlist.set_name('ParticlePrinter_' + list_name)
2059 prlist.param('listName', list_name)
2060 prlist.param('fullPrint', full)
2061 path.add_module(prlist)
2062
2063
2064def variablesToNtuple(decayString, variables, treename='variables', filename='ntuple.root', path=None, basketsize=1600,
2065 signalSideParticleList="", filenameSuffix="", useFloat=False, storeEventType=True,
2066 ignoreCommandLineOverride=False):
2067 """
2068 Creates and fills a flat ntuple with the specified variables from the VariableManager.
2069 If a decayString is provided, then there will be one entry per candidate (for particle in list of candidates).
2070 If an empty decayString is provided, there will be one entry per event (useful for trigger studies, etc).
2071
2072 Parameters:
2073 decayString (str): specifies type of Particles and determines the name of the ParticleList
2074 variables (list(str)): the list of variables (which must be registered in the VariableManager)
2075 treename (str): name of the ntuple tree
2076 filename (str): which is used to store the variables
2077 path (basf2.Path): the basf2 path where the analysis is processed
2078 basketsize (int): size of baskets in the output ntuple in bytes
2079 signalSideParticleList (str): The name of the signal-side ParticleList.
2080 Only valid if the module is called in a for_each loop over the RestOfEvent.
2081 filenameSuffix (str): suffix to be appended to the filename before ``.root``.
2082 useFloat (bool): Use single precision (float) instead of double precision (double)
2083 for floating-point numbers.
2084 storeEventType (bool) : if true, the branch __eventType__ is added for the MC event type information.
2085 The information is available from MC16 on.
2086 ignoreCommandLineOverride (bool) : if true, ignore override of file name via command line argument ``-o``.
2087
2088 .. tip:: The output filename can be overridden using the ``-o`` argument of basf2.
2089 """
2090
2091 output = register_module('VariablesToNtuple')
2092 output.set_name('VariablesToNtuple_' + decayString)
2093 output.param('particleList', decayString)
2094 output.param('variables', variables)
2095 output.param('fileName', filename)
2096 output.param('treeName', treename)
2097 output.param('basketSize', basketsize)
2098 output.param('signalSideParticleList', signalSideParticleList)
2099 output.param('fileNameSuffix', filenameSuffix)
2100 output.param('useFloat', useFloat)
2101 output.param('storeEventType', storeEventType)
2102 output.param('ignoreCommandLineOverride', ignoreCommandLineOverride)
2103 path.add_module(output)
2104
2105
2106def variablesToHistogram(decayString,
2107 variables,
2108 variables_2d=None,
2109 filename='ntuple.root',
2110 path=None, *,
2111 directory=None,
2112 prefixDecayString=False,
2113 filenameSuffix="",
2114 ignoreCommandLineOverride=False):
2115 """
2116 Creates and fills a flat ntuple with the specified variables from the VariableManager
2117
2118 Parameters:
2119 decayString (str): specifies type of Particles and determines the name of the ParticleList
2120 variables (list(tuple))): variables + binning which must be registered in the VariableManager
2121 variables_2d (list(tuple)): pair of variables + binning for each which must be registered in the VariableManager
2122 filename (str): which is used to store the variables
2123 path (basf2.Path): the basf2 path where the analysis is processed
2124 directory (str): directory inside the output file where the histograms should be saved.
2125 Useful if you want to have different histograms in the same file to separate them.
2126 prefixDecayString (bool): If True the decayString will be prepended to the directory name to allow for more
2127 programmatic naming of the structure in the file.
2128 filenameSuffix (str): suffix to be appended to the filename before ``.root``.
2129 ignoreCommandLineOverride (bool) : if true, ignore override of file name via command line argument ``-o``.
2130
2131 .. tip:: The output filename can be overridden using the ``-o`` argument of basf2.
2132 """
2133
2134 if variables_2d is None:
2135 variables_2d = []
2136 output = register_module('VariablesToHistogram')
2137 output.set_name('VariablesToHistogram_' + decayString)
2138 output.param('particleList', decayString)
2139 output.param('variables', variables)
2140 output.param('variables_2d', variables_2d)
2141 output.param('fileName', filename)
2142 output.param('fileNameSuffix', filenameSuffix)
2143 output.param('ignoreCommandLineOverride', ignoreCommandLineOverride)
2144 if directory is not None or prefixDecayString:
2145 if directory is None:
2146 directory = ""
2147 if prefixDecayString:
2148 directory = decayString + "_" + directory
2149 output.param("directory", directory)
2150 path.add_module(output)
2151
2152
2153def variablesToExtraInfo(particleList, variables, option=0, path=None):
2154 """
2155 For each particle in the input list the selected variables are saved in an extra-info field with the given name.
2156 Can be used when wanting to save variables before modifying them, e.g. when performing vertex fits.
2157
2158 Parameters:
2159 particleList (str): The input ParticleList
2160 variables (dict[str,str]): Dictionary of Variables (key) and extraInfo names (value).
2161 option (int): Option to overwrite an existing extraInfo. Choose among -1, 0, 1, 2.
2162 An existing extra info with the same name will be overwritten if the new
2163 value is lower / will never be overwritten / will be overwritten if the
2164 new value is higher / will always be overwritten (option = -1/0/1/2).
2165 path (basf2.Path): modules are added to this path
2166 """
2167
2168 mod = register_module('VariablesToExtraInfo')
2169 mod.set_name('VariablesToExtraInfo_' + particleList)
2170 mod.param('particleList', particleList)
2171 mod.param('variables', variables)
2172 mod.param('overwrite', option)
2173 path.add_module(mod)
2174
2175
2176def variablesToDaughterExtraInfo(particleList, decayString, variables, option=0, path=None):
2177 """
2178 For each daughter particle specified via decay string the selected variables (estimated for the mother particle)
2179 are saved in an extra-info field with the given name. In other words, the property of mother is saved as extra-info
2180 to specified daughter particle.
2181
2182 Parameters:
2183 particleList (str): The input ParticleList
2184 decayString (str): Decay string that specifies to which daughter the extra info should be appended
2185 variables (dict[str,str]): Dictionary of Variables (key) and extraInfo names (value).
2186 option (int): Option to overwrite an existing extraInfo. Choose among -1, 0, 1, 2.
2187 An existing extra info with the same name will be overwritten if the new
2188 value is lower / will never be overwritten / will be overwritten if the
2189 new value is higher / will always be overwritten (option = -1/0/1/2).
2190 path (basf2.Path): modules are added to this path
2191 """
2192
2193 mod = register_module('VariablesToExtraInfo')
2194 mod.set_name('VariablesToDaughterExtraInfo_' + particleList)
2195 mod.param('particleList', particleList)
2196 mod.param('decayString', decayString)
2197 mod.param('variables', variables)
2198 mod.param('overwrite', option)
2199 path.add_module(mod)
2200
2201
2202def variablesToEventExtraInfo(particleList, variables, option=0, path=None):
2203 """
2204 For each particle in the input list the selected variables are saved in an event-extra-info field with the given name,
2205 Can be used to save MC truth information, for example, in a ntuple of reconstructed particles.
2206
2207 .. tip::
2208 When the function is called first time not in the main path but in a sub-path e.g. ``roe_path``,
2209 the eventExtraInfo cannot be accessed from the main path because of the shorter lifetime of the event-extra-info field.
2210 If one wants to call the function in a sub-path, one has to call the function in the main path beforehand.
2211
2212 Parameters:
2213 particleList (str): The input ParticleList
2214 variables (dict[str,str]): Dictionary of Variables (key) and extraInfo names (value).
2215 option (int): Option to overwrite an existing extraInfo. Choose among -1, 0, 1, 2.
2216 An existing extra info with the same name will be overwritten if the new
2217 value is lower / will never be overwritten / will be overwritten if the
2218 new value is higher / will always be overwritten (option = -1/0/1/2).
2219 path (basf2.Path): modules are added to this path
2220 """
2221
2222 mod = register_module('VariablesToEventExtraInfo')
2223 mod.set_name('VariablesToEventExtraInfo_' + particleList)
2224 mod.param('particleList', particleList)
2225 mod.param('variables', variables)
2226 mod.param('overwrite', option)
2227 path.add_module(mod)
2228
2229
2230def variableToSignalSideExtraInfo(particleList, varToExtraInfo, path):
2231 """
2232 Write the value of specified variables estimated for the single particle in the input list (has to contain exactly 1
2233 particle) as an extra info to the particle related to current ROE.
2234 Should be used only in the for_each roe path.
2235
2236 Parameters:
2237 particleList (str): The input ParticleList
2238 varToExtraInfo (dict[str,str]): Dictionary of Variables (key) and extraInfo names (value).
2239 path (basf2.Path): modules are added to this path
2240 """
2241
2242 mod = register_module('SignalSideVariablesToExtraInfo')
2243 mod.set_name('SigSideVarToExtraInfo_' + particleList)
2244 mod.param('particleListName', particleList)
2245 mod.param('variableToExtraInfo', varToExtraInfo)
2246 path.add_module(mod)
2247
2248
2249def signalRegion(particleList, cut, path=None, name="isSignalRegion", blind_data=True):
2250 """
2251 Define and blind a signal region.
2252 Per default, the defined signal region is cut out if ran on data.
2253 This function will provide a new variable 'isSignalRegion' as default, which is either 0 or 1 depending on the cut
2254 provided.
2255
2256 Example:
2257 .. code-block:: python
2258
2259 ma.reconstructDecay("B+:sig -> D+ pi0", "Mbc>5.2", path=path)
2260 ma.signalRegion("B+:sig",
2261 "Mbc>5.27 and abs(deltaE)<0.2",
2262 blind_data=True,
2263 path=path)
2264 ma.variablesToNtuples("B+:sig", ["isSignalRegion"], path=path)
2265
2266 Parameters:
2267 particleList (str): The input ParticleList
2268 cut (str): Cut string describing the signal region
2269 path (basf2.Path):: Modules are added to this path
2270 name (str): Name of the Signal region in the variable manager
2271 blind_data (bool): Automatically exclude signal region from data
2272
2273 """
2274
2275 from variables import variables
2276 mod = register_module('VariablesToExtraInfo')
2277 mod.set_name(f'{name}_' + particleList)
2278 mod.param('particleList', particleList)
2279 mod.param('variables', {f"passesCut({cut})": name})
2280 variables.addAlias(name, f"extraInfo({name})")
2281 path.add_module(mod)
2282
2283 # Check if we run on Data
2284 if blind_data:
2285 applyCuts(particleList, f"{name}==0 or isMC==1", path=path)
2286
2287
2288def removeExtraInfo(particleLists=None, removeEventExtraInfo=False, path=None):
2289 """
2290 Removes the ExtraInfo of the given particleLists. If specified (removeEventExtraInfo = True) also the EventExtraInfo is removed.
2291 """
2292
2293 if particleLists is None:
2294 particleLists = []
2295 mod = register_module('ExtraInfoRemover')
2296 mod.param('particleLists', particleLists)
2297 mod.param('removeEventExtraInfo', removeEventExtraInfo)
2298 path.add_module(mod)
2299
2300
2301def signalSideParticleFilter(particleList, selection, roe_path, deadEndPath):
2302 """
2303 Checks if the current ROE object in the for_each roe path (argument roe_path) is related
2304 to the particle from the input ParticleList. Additional selection criteria can be applied.
2305 If ROE is not related to any of the Particles from ParticleList or the Particle doesn't
2306 meet the selection criteria the execution of deadEndPath is started. This path, as the name
2307 suggests should be empty and its purpose is to end the execution of for_each roe path for
2308 the current ROE object.
2309
2310 @param particleList The input ParticleList
2311 @param selection Selection criteria that Particle needs meet in order for for_each ROE path to continue
2312 @param for_each roe path in which this filter is executed
2313 @param deadEndPath empty path that ends execution of or_each roe path for the current ROE object.
2314 """
2315
2316 mod = register_module('SignalSideParticleFilter')
2317 mod.set_name('SigSideParticleFilter_' + particleList)
2318 mod.param('particleLists', [particleList])
2319 mod.param('selection', selection)
2320 roe_path.add_module(mod)
2321 mod.if_false(deadEndPath)
2322
2323
2324def signalSideParticleListsFilter(particleLists, selection, roe_path, deadEndPath):
2325 """
2326 Checks if the current ROE object in the for_each roe path (argument roe_path) is related
2327 to the particle from the input ParticleList. Additional selection criteria can be applied.
2328 If ROE is not related to any of the Particles from ParticleList or the Particle doesn't
2329 meet the selection criteria the execution of deadEndPath is started. This path, as the name
2330 suggests should be empty and its purpose is to end the execution of for_each roe path for
2331 the current ROE object.
2332
2333 @param particleLists The input ParticleLists
2334 @param selection Selection criteria that Particle needs meet in order for for_each ROE path to continue
2335 @param for_each roe path in which this filter is executed
2336 @param deadEndPath empty path that ends execution of or_each roe path for the current ROE object.
2337 """
2338
2339 mod = register_module('SignalSideParticleFilter')
2340 mod.set_name('SigSideParticleFilter_' + particleLists[0])
2341 mod.param('particleLists', particleLists)
2342 mod.param('selection', selection)
2343 roe_path.add_module(mod)
2344 mod.if_false(deadEndPath)
2345
2346
2348 decayString,
2349 cut,
2350 dmID=0,
2351 writeOut=False,
2352 path=None,
2353 chargeConjugation=True,
2354):
2355 r"""
2356 Finds and creates a ``ParticleList`` from given decay string.
2357 ``ParticleList`` of daughters with sub-decay is created.
2358
2359 Only the particles made from MCParticle, which can be loaded by `fillParticleListFromMC`, are accepted as daughters.
2360
2361 Only signal particle, which means :b2:var:`isSignal` is equal to 1, is stored. One can use the decay string grammar
2362 to change the behavior of :b2:var:`isSignal`. One can find detailed information in :ref:`DecayString`.
2363
2364 .. tip::
2365 If one uses same sub-decay twice, same particles are registered to a ``ParticleList``. For example,
2366 ``K_S0:pi0pi0 =direct=> [pi0:gg =direct=> gamma:MC gamma:MC] [pi0:gg =direct=> gamma:MC gamma:MC]``.
2367 One can skip the second sub-decay, ``K_S0:pi0pi0 =direct=> [pi0:gg =direct=> gamma:MC gamma:MC] pi0:gg``.
2368
2369 .. tip::
2370 It is recommended to use only primary particles as daughter particles unless you want to explicitly study the secondary
2371 particles. The behavior of MC-matching for secondary particles from a stable particle decay is not guaranteed.
2372 Please consider to use `fillParticleListFromMC` with ``skipNonPrimary=True`` to load daughter particles.
2373 Moreover, it is recommended to load ``K_S0`` and ``Lambda0`` directly from MCParticle by `fillParticleListFromMC` rather
2374 than reconstructing from two pions or a proton-pion pair, because their direct daughters can be the secondary particle.
2375
2376
2377 @param decayString :ref:`DecayString` specifying what kind of the decay should be reconstructed
2378 (from the DecayString the mother and daughter ParticleLists are determined)
2379 @param cut created (mother) Particles are added to the mother ParticleList if they
2380 pass given cuts (in VariableManager style) and rejected otherwise
2381 isSignal==1 is always required by default.
2382 @param dmID user specified decay mode identifier
2383 @param writeOut whether RootOutput module should save the created ParticleList
2384 @param path modules are added to this path
2385 @param chargeConjugation boolean to decide whether charge conjugated mode should be reconstructed as well (on by default)
2386 """
2387
2388 pmake = register_module('ParticleCombinerFromMC')
2389 pmake.set_name('ParticleCombinerFromMC_' + decayString)
2390 pmake.param('decayString', decayString)
2391 pmake.param('cut', cut)
2392 pmake.param('decayMode', dmID)
2393 pmake.param('writeOut', writeOut)
2394 pmake.param('chargeConjugation', chargeConjugation)
2395 path.add_module(pmake)
2396
2397
2398def findMCDecay(
2399 list_name,
2400 decay,
2401 writeOut=False,
2402 appendAllDaughters=False,
2403 skipNonPrimaryDaughters=True,
2404 path=None,
2405):
2406 """
2407 Finds and creates a ``ParticleList`` for all ``MCParticle`` decays matching a given :ref:`DecayString`.
2408 The decay string is required to describe correctly what you want.
2409 In the case of inclusive decays, you can use :ref:`Grammar_for_custom_MCMatching`
2410
2411 The output particles has only the daughter particles written in the given decay string, if
2412 ``appendAllDaughters=False`` (default). If ``appendAllDaughters=True``, all daughters of the matched MCParticle are
2413 appended in the order defined at the MCParticle level. For example,
2414
2415 .. code-block:: python
2416
2417 findMCDecay('B0:Xee', 'B0 -> e+ e- ... ?gamma', appendAllDaughters=False, path=mypath)
2418
2419 The output ParticleList ``B0:Xee`` will match the inclusive ``B0 -> e+ e-`` decays (but neutrinos are not included),
2420 in both cases of ``appendAllDaughters`` is false and true.
2421 If the ``appendAllDaughters=False`` as above example, the ``B0:Xee`` has only two electrons as daughters.
2422 While, if ``appendAllDaughters=True``, all daughters of the matched MCParticles are appended. When the truth decay mode of
2423 the MCParticle is ``B0 -> [K*0 -> K+ pi-] [J/psi -> e+ e-]``, the first daughter of ``B0:Xee`` is ``K*0`` and ``e+``
2424 will be the first daughter of second daughter of ``B0:Xee``.
2425
2426 The option ``skipNonPrimaryDaughters`` only has an effect if ``appendAllDaughters=True``. If ``skipNonPrimaryDaughters=True``,
2427 all primary daughters are appended but the secondary particles are not.
2428
2429 .. tip::
2430 Daughters of ``Lambda0`` are not primary, but ``Lambda0`` is not a final state particle.
2431 In order for the MCMatching to work properly, the daughters of ``Lambda0`` are appended to
2432 ``Lambda0`` regardless of the value of the option ``skipNonPrimaryDaughters``.
2433
2434
2435 @param list_name The output particle list name
2436 @param decay The decay string which you want
2437 @param writeOut Whether `RootOutput` module should save the created ``outputList``
2438 @param skipNonPrimaryDaughters if true, skip non primary daughters, useful to study final state daughter particles
2439 @param appendAllDaughters if true, not only the daughters described in the decay string but all daughters are appended
2440 @param path modules are added to this path
2441 """
2442
2443 decayfinder = register_module('MCDecayFinder')
2444 decayfinder.set_name('MCDecayFinder_' + list_name)
2445 decayfinder.param('listName', list_name)
2446 decayfinder.param('decayString', decay)
2447 decayfinder.param('appendAllDaughters', appendAllDaughters)
2448 decayfinder.param('skipNonPrimaryDaughters', skipNonPrimaryDaughters)
2449 decayfinder.param('writeOut', writeOut)
2450 path.add_module(decayfinder)
2451
2452
2453def summaryOfLists(particleLists, outputFile=None, path=None):
2454 """
2455 Prints out Particle statistics at the end of the job: number of events with at
2456 least one candidate, average number of candidates per event, etc.
2457 If an output file name is provided the statistics is also dumped into a json file with that name.
2458
2459 @param particleLists list of input ParticleLists
2460 @param outputFile output file name (not created by default)
2461 """
2462
2463 particleStats = register_module('ParticleStats')
2464 particleStats.param('particleLists', particleLists)
2465 if outputFile is not None:
2466 particleStats.param('outputFile', outputFile)
2467 path.add_module(particleStats)
2468
2469
2470def matchMCTruth(list_name, path):
2471 """
2472 Performs MC matching (sets relation Particle->MCParticle) for
2473 all particles (and its (grand)^N-daughter particles) in the specified
2474 ParticleList.
2475
2476 @param list_name name of the input ParticleList
2477 @param path modules are added to this path
2478 """
2479
2480 mcMatch = register_module('MCMatcherParticles')
2481 mcMatch.set_name('MCMatch_' + list_name)
2482 mcMatch.param('listName', list_name)
2483 path.add_module(mcMatch)
2484
2485
2486def looseMCTruth(list_name, path):
2487 """
2488 Performs loose MC matching for all particles in the specified
2489 ParticleList.
2490 The difference between loose and normal mc matching algorithm is that
2491 the loose algorithm will find the common mother of the majority of daughter
2492 particles while the normal algorithm finds the common mother of all daughters.
2493 The results of loose mc matching algorithm are stored to the following extraInfo
2494 items:
2495
2496 - looseMCMotherPDG: PDG code of most common mother
2497 - looseMCMotherIndex: 1-based StoreArray<MCParticle> index of most common mother
2498 - looseMCWrongDaughterN: number of daughters that don't originate from the most common mother
2499 - looseMCWrongDaughterPDG: PDG code of the daughter that doesn't originate from the most common mother (only if
2500 looseMCWrongDaughterN = 1)
2501 - looseMCWrongDaughterBiB: 1 if the wrong daughter is Beam Induced Background Particle
2502
2503 @param list_name name of the input ParticleList
2504 @param path modules are added to this path
2505 """
2506
2507 mcMatch = register_module('MCMatcherParticles')
2508 mcMatch.set_name('LooseMCMatch_' + list_name)
2509 mcMatch.param('listName', list_name)
2510 mcMatch.param('looseMCMatching', True)
2511 path.add_module(mcMatch)
2512
2513
2514def buildRestOfEvent(target_list_name, inputParticlelists=None,
2515 fillWithMostLikely=True,
2516 chargedPIDPriors=None, path=None):
2517 """
2518 Creates for each Particle in the given ParticleList a RestOfEvent
2519 dataobject and makes basf2 relation between them. User can provide additional
2520 particle lists with a different particle hypothesis like ['K+:good, e+:good'], etc.
2521
2522 @param target_list_name name of the input ParticleList
2523 @param inputParticlelists list of user-defined input particle list names, which serve
2524 as source of particles to build the ROE, the FSP particles from
2525 target_list_name are automatically excluded from the ROE object
2526 @param fillWithMostLikely By default the module uses the most likely particle mass hypothesis for charged particles
2527 based on the PID likelihood. Turn this behavior off if you want to configure your own
2528 input particle lists.
2529 @param chargedPIDPriors The prior PID fractions, that are used to regulate the
2530 amount of certain charged particle species, should be a list of
2531 six floats if not None. The order of particle types is
2532 the following: [e-, mu-, pi-, K-, p+, d+]
2533 @param path modules are added to this path
2534 """
2535
2536 if inputParticlelists is None:
2537 inputParticlelists = []
2538 fillParticleList('pi+:all', '', path=path)
2539 if fillWithMostLikely:
2540 from stdCharged import stdMostLikely
2541 stdMostLikely(chargedPIDPriors, '_roe', path=path)
2542 inputParticlelists = [f'{ptype}:mostlikely_roe' for ptype in ['K+', 'p+', 'e+', 'mu+']]
2543 import b2bii
2544 if not b2bii.isB2BII():
2545 fillParticleList('gamma:all', '', path=path)
2546 fillParticleList('K_L0:roe_default', 'isFromKLM > 0', path=path)
2547 inputParticlelists += ['pi+:all', 'gamma:all', 'K_L0:roe_default']
2548 else:
2549 inputParticlelists += ['pi+:all', 'gamma:mdst']
2550 roeBuilder = register_module('RestOfEventBuilder')
2551 roeBuilder.set_name('ROEBuilder_' + target_list_name)
2552 roeBuilder.param('particleList', target_list_name)
2553 roeBuilder.param('particleListsInput', inputParticlelists)
2554 roeBuilder.param('mostLikely', fillWithMostLikely)
2555 path.add_module(roeBuilder)
2556
2557
2558def buildNestedRestOfEvent(target_list_name, maskName='all', path=None):
2559 """
2560 Creates for each Particle in the given ParticleList a RestOfEvent
2561 @param target_list_name name of the input ParticleList
2562 @param mask_name name of the ROEMask to be used
2563 @param path modules are added to this path
2564 """
2565
2566 roeBuilder = register_module('RestOfEventBuilder')
2567 roeBuilder.set_name('NestedROEBuilder_' + target_list_name)
2568 roeBuilder.param('particleList', target_list_name)
2569 roeBuilder.param('nestedROEMask', maskName)
2570 roeBuilder.param('createNestedROE', True)
2571 path.add_module(roeBuilder)
2572
2573
2574def buildRestOfEventFromMC(target_list_name, inputParticlelists=None, path=None):
2575 """
2576 Creates for each Particle in the given ParticleList a RestOfEvent
2577 @param target_list_name name of the input ParticleList
2578 @param inputParticlelists list of input particle list names, which serve
2579 as a source of particles to build ROE, the FSP particles from
2580 target_list_name are excluded from ROE object
2581 @param path modules are added to this path
2582 """
2583
2584 if inputParticlelists is None:
2585 inputParticlelists = []
2586 if (len(inputParticlelists) == 0):
2587 # Type of particles to use for ROEBuilder
2588 # K_S0 and Lambda0 are added here because some of them have interacted
2589 # with the detector material
2590 types = ['gamma', 'e+', 'mu+', 'pi+', 'K+', 'p+', 'K_L0',
2591 'n0', 'nu_e', 'nu_mu', 'nu_tau',
2592 'K_S0', 'Lambda0']
2593 for t in types:
2594 fillParticleListFromMC(f"{t}:roe_default_gen", 'mcPrimary > 0 and nDaughters == 0',
2595 True, True, path=path)
2596 inputParticlelists += [f"{t}:roe_default_gen"]
2597 roeBuilder = register_module('RestOfEventBuilder')
2598 roeBuilder.set_name('MCROEBuilder_' + target_list_name)
2599 roeBuilder.param('particleList', target_list_name)
2600 roeBuilder.param('particleListsInput', inputParticlelists)
2601 roeBuilder.param('fromMC', True)
2602 path.add_module(roeBuilder)
2603
2604
2605def appendROEMask(list_name,
2606 mask_name,
2607 trackSelection,
2608 eclClusterSelection,
2609 klmClusterSelection='',
2610 path=None):
2611 """
2612 Loads the ROE object of a particle and creates a ROE mask with a specific name. It applies
2613 selection criteria for tracks and eclClusters which will be used by variables in ROEVariables.cc.
2614
2615 - append a ROE mask with all tracks in ROE coming from the IP region
2616
2617 .. code-block:: python
2618
2619 appendROEMask('B+:sig', 'IPtracks', '[dr < 2] and [abs(dz) < 5]', path=mypath)
2620
2621 - append a ROE mask with only ECL-based particles that pass as good photon candidates
2622
2623 .. code-block:: python
2624
2625 goodPhotons = 'inCDCAcceptance and clusterErrorTiming < 1e6 and [clusterE1E9 > 0.4 or E > 0.075]'
2626 appendROEMask('B+:sig', 'goodROEGamma', '', goodPhotons, path=mypath)
2627
2628
2629 @param list_name name of the input ParticleList
2630 @param mask_name name of the appended ROEMask
2631 @param trackSelection decay string for the track-based particles in ROE
2632 @param eclClusterSelection decay string for the ECL-based particles in ROE
2633 @param klmClusterSelection decay string for the KLM-based particles in ROE
2634 @param path modules are added to this path
2635 """
2636
2637 roeMask = register_module('RestOfEventInterpreter')
2638 roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + mask_name)
2639 roeMask.param('particleList', list_name)
2640 roeMask.param('ROEMasks', [(mask_name, trackSelection, eclClusterSelection, klmClusterSelection)])
2641 path.add_module(roeMask)
2642
2643
2644def appendROEMasks(list_name, mask_tuples, path=None):
2645 """
2646 Loads the ROE object of a particle and creates a ROE mask with a specific name. It applies
2647 selection criteria for track-, ECL- and KLM-based particles which will be used by ROE variables.
2648
2649 The multiple ROE masks with their own selection criteria are specified
2650 via list of tuples (mask_name, trackParticleSelection, eclParticleSelection, klmParticleSelection) or
2651 (mask_name, trackSelection, eclClusterSelection) in case with fractions.
2652
2653 - Example for two tuples, one with and one without fractions
2654
2655 .. code-block:: python
2656
2657 ipTracks = ('IPtracks', '[dr < 2] and [abs(dz) < 5]', '', '')
2658 goodPhotons = 'inCDCAcceptance and [clusterErrorTiming < 1e6] and [clusterE1E9 > 0.4 or E > 0.075]'
2659 goodROEGamma = ('ROESel', '[dr < 2] and [abs(dz) < 5]', goodPhotons, '')
2660 goodROEKLM = ('IPtracks', '[dr < 2] and [abs(dz) < 5]', '', 'nKLMClusterTrackMatches == 0')
2661 appendROEMasks('B+:sig', [ipTracks, goodROEGamma, goodROEKLM], path=mypath)
2662
2663 @param list_name name of the input ParticleList
2664 @param mask_tuples array of ROEMask list tuples to be appended
2665 @param path modules are added to this path
2666 """
2667
2668 compatible_masks = []
2669 for mask in mask_tuples:
2670 # add empty KLM-based selection if it's absent:
2671 if len(mask) == 3:
2672 compatible_masks += [(*mask, '')]
2673 else:
2674 compatible_masks += [mask]
2675 roeMask = register_module('RestOfEventInterpreter')
2676 roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + 'MaskList')
2677 roeMask.param('particleList', list_name)
2678 roeMask.param('ROEMasks', compatible_masks)
2679 path.add_module(roeMask)
2680
2681
2682def updateROEMask(list_name,
2683 mask_name,
2684 trackSelection,
2685 eclClusterSelection='',
2686 klmClusterSelection='',
2687 path=None):
2688 """
2689 Update an existing ROE mask by applying additional selection cuts for
2690 tracks and/or clusters.
2691
2692 See function `appendROEMask`!
2693
2694 @param list_name name of the input ParticleList
2695 @param mask_name name of the ROEMask to update
2696 @param trackSelection decay string for the track-based particles in ROE
2697 @param eclClusterSelection decay string for the ECL-based particles in ROE
2698 @param klmClusterSelection decay string for the KLM-based particles in ROE
2699 @param path modules are added to this path
2700 """
2701
2702 roeMask = register_module('RestOfEventInterpreter')
2703 roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + mask_name)
2704 roeMask.param('particleList', list_name)
2705 roeMask.param('ROEMasks', [(mask_name, trackSelection, eclClusterSelection, klmClusterSelection)])
2706 roeMask.param('update', True)
2707 path.add_module(roeMask)
2708
2709
2710def updateROEMasks(list_name, mask_tuples, path):
2711 """
2712 Update existing ROE masks by applying additional selection cuts for tracks
2713 and/or clusters.
2714
2715 The multiple ROE masks with their own selection criteria are specified
2716 via list tuples (mask_name, trackSelection, eclClusterSelection, klmClusterSelection)
2717
2718 See function `appendROEMasks`!
2719
2720 @param list_name name of the input ParticleList
2721 @param mask_tuples array of ROEMask list tuples to be appended
2722 @param path modules are added to this path
2723 """
2724
2725 compatible_masks = []
2726 for mask in mask_tuples:
2727 # add empty KLM-based selection if it's absent:
2728 if len(mask) == 3:
2729 compatible_masks += [(*mask, '')]
2730 else:
2731 compatible_masks += [mask]
2732
2733 roeMask = register_module('RestOfEventInterpreter')
2734 roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + 'MaskList')
2735 roeMask.param('particleList', list_name)
2736 roeMask.param('ROEMasks', compatible_masks)
2737 roeMask.param('update', True)
2738 path.add_module(roeMask)
2739
2740
2741def keepInROEMasks(list_name, mask_names, cut_string, path=None):
2742 """
2743 This function is used to apply particle list specific cuts on one or more ROE masks (track or eclCluster).
2744 With this function one can KEEP the tracks/eclclusters used in particles from provided particle list.
2745 This function should be executed only in the for_each roe path for the current ROE object.
2746
2747 To avoid unnecessary computation, the input particle list should only contain particles from ROE
2748 (use cut 'isInRestOfEvent == 1'). To update the ECLCluster masks, the input particle list should be a photon
2749 particle list (e.g. 'gamma:someLabel'). To update the Track masks, the input particle list should be a charged
2750 pion particle list (e.g. 'pi+:someLabel').
2751
2752 Updating a non-existing mask will create a new one.
2753
2754 - keep only those tracks that were used in provided particle list
2755
2756 .. code-block:: python
2757
2758 keepInROEMasks('pi+:goodTracks', 'mask', '', path=mypath)
2759
2760 - keep only those clusters that were used in provided particle list and pass a cut, apply to several masks
2761
2762 .. code-block:: python
2763
2764 keepInROEMasks('gamma:goodClusters', ['mask1', 'mask2'], 'E > 0.1', path=mypath)
2765
2766
2767 @param list_name name of the input ParticleList
2768 @param mask_names array of ROEMasks to be updated
2769 @param cut_string decay string with which the mask will be updated
2770 @param path modules are added to this path
2771 """
2772
2773 updateMask = register_module('RestOfEventUpdater')
2774 updateMask.set_name('RestOfEventUpdater_' + list_name + '_masks')
2775 updateMask.param('particleList', list_name)
2776 updateMask.param('updateMasks', mask_names)
2777 updateMask.param('cutString', cut_string)
2778 updateMask.param('discard', False)
2779 path.add_module(updateMask)
2780
2781
2782def discardFromROEMasks(list_name, mask_names, cut_string, path=None):
2783 """
2784 This function is used to apply particle list specific cuts on one or more ROE masks (track or eclCluster).
2785 With this function one can DISCARD the tracks/eclclusters used in particles from provided particle list.
2786 This function should be executed only in the for_each roe path for the current ROE object.
2787
2788 To avoid unnecessary computation, the input particle list should only contain particles from ROE
2789 (use cut 'isInRestOfEvent == 1'). To update the ECLCluster masks, the input particle list should be a photon
2790 particle list (e.g. 'gamma:someLabel'). To update the Track masks, the input particle list should be a charged
2791 pion particle list (e.g. 'pi+:someLabel').
2792
2793 Updating a non-existing mask will create a new one.
2794
2795 - discard tracks that were used in provided particle list
2796
2797 .. code-block:: python
2798
2799 discardFromROEMasks('pi+:badTracks', 'mask', '', path=mypath)
2800
2801 - discard clusters that were used in provided particle list and pass a cut, apply to several masks
2802
2803 .. code-block:: python
2804
2805 discardFromROEMasks('gamma:badClusters', ['mask1', 'mask2'], 'E < 0.1', path=mypath)
2806
2807
2808 @param list_name name of the input ParticleList
2809 @param mask_names array of ROEMasks to be updated
2810 @param cut_string decay string with which the mask will be updated
2811 @param path modules are added to this path
2812 """
2813
2814 updateMask = register_module('RestOfEventUpdater')
2815 updateMask.set_name('RestOfEventUpdater_' + list_name + '_masks')
2816 updateMask.param('particleList', list_name)
2817 updateMask.param('updateMasks', mask_names)
2818 updateMask.param('cutString', cut_string)
2819 updateMask.param('discard', True)
2820 path.add_module(updateMask)
2821
2822
2823def optimizeROEWithV0(list_name, mask_names, cut_string, path=None):
2824 """
2825 This function is used to apply particle list specific cuts on one or more ROE masks for Tracks.
2826 It is possible to optimize the ROE selection by treating tracks from V0's separately, meaning,
2827 taking V0's 4-momentum into account instead of 4-momenta of tracks. A cut for only specific V0's
2828 passing it can be applied.
2829
2830 The input particle list should be a V0 particle list: K_S0 ('K_S0:someLabel', ''),
2831 Lambda ('Lambda:someLabel', '') or converted photons ('gamma:someLabel').
2832
2833 Updating a non-existing mask will create a new one.
2834
2835 - treat tracks from K_S0 inside mass window separately, replace track momenta with K_S0 momentum
2836
2837 .. code-block:: python
2838
2839 optimizeROEWithV0('K_S0:opt', 'mask', '0.450 < M < 0.550', path=mypath)
2840
2841 @param list_name name of the input ParticleList
2842 @param mask_names array of ROEMasks to be updated
2843 @param cut_string decay string with which the mask will be updated
2844 @param path modules are added to this path
2845 """
2846
2847 updateMask = register_module('RestOfEventUpdater')
2848 updateMask.set_name('RestOfEventUpdater_' + list_name + '_masks')
2849 updateMask.param('particleList', list_name)
2850 updateMask.param('updateMasks', mask_names)
2851 updateMask.param('cutString', cut_string)
2852 path.add_module(updateMask)
2853
2854
2855def updateROEUsingV0Lists(target_particle_list, mask_names, default_cleanup=True, selection_cuts=None,
2856 apply_mass_fit=False, fitter='treefit', path=None):
2857 """
2858 This function creates V0 particle lists (photons, :math:`K^0_S` and :math:`\\Lambda^0`)
2859 and it uses V0 candidates to update the Rest Of Event, which is associated to the target particle list.
2860 It is possible to apply a standard or customized selection and mass fit to the V0 candidates.
2861
2862
2863 @param target_particle_list name of the input ParticleList
2864 @param mask_names array of ROE masks to be applied
2865 @param default_cleanup if True, predefined cuts will be applied on the V0 lists
2866 @param selection_cuts a single string of selection cuts or tuple of three strings (photon_cuts, K_S0_cuts, Lambda0_cuts),
2867 which will be applied to the V0 lists. These cuts will have a priority over the default ones.
2868 @param apply_mass_fit if True, a mass fit will be applied to the V0 particles
2869 @param fitter string, that represent a fitter choice: "treefit" for TreeFitter and "kfit" for KFit
2870 @param path modules are added to this path
2871 """
2872
2873 roe_path = create_path()
2874 deadEndPath = create_path()
2875 signalSideParticleFilter(target_particle_list, '', roe_path, deadEndPath)
2876
2877 if (default_cleanup and selection_cuts is None):
2878 B2INFO("Using default cleanup in updateROEUsingV0Lists.")
2879 selection_cuts = 'abs(dM) < 0.1 '
2880 selection_cuts += 'and daughter(0,particleID) > 0.2 and daughter(1,particleID) > 0.2 '
2881 selection_cuts += 'and daughter(0,thetaInCDCAcceptance) and daughter(1,thetaInCDCAcceptance)'
2882 if (selection_cuts is None or selection_cuts == ''):
2883 B2INFO("No cleanup in updateROEUsingV0Lists.")
2884 selection_cuts = ('True', 'True', 'True')
2885 if (isinstance(selection_cuts, str)):
2886 selection_cuts = (selection_cuts, selection_cuts, selection_cuts)
2887 # The isInRestOfEvent variable will be applied on FSPs of composite particles automatically:
2888 roe_cuts = 'isInRestOfEvent > 0'
2889 fillConvertedPhotonsList('gamma:v0_roe -> e+ e-', f'{selection_cuts[0]} and {roe_cuts}',
2890 path=roe_path)
2891 fillParticleList('K_S0:v0_roe -> pi+ pi-', f'{selection_cuts[1]} and {roe_cuts}',
2892 path=roe_path)
2893 fillParticleList('Lambda0:v0_roe -> p+ pi-', f'{selection_cuts[2]} and {roe_cuts}',
2894 path=roe_path)
2895 fitter = fitter.lower()
2896 if (fitter != 'treefit' and fitter != 'kfit'):
2897 B2WARNING('Argument "fitter" in updateROEUsingV0Lists has only "treefit" and "kfit" options, '
2898 f'but "{fitter}" was provided! TreeFitter will be used instead.')
2899 fitter = 'treefit'
2900 from vertex import kFit, treeFit
2901 for v0 in ['gamma:v0_roe', 'K_S0:v0_roe', 'Lambda0:v0_roe']:
2902 if (apply_mass_fit and fitter == 'kfit'):
2903 kFit(v0, conf_level=0.0, fit_type='massvertex', path=roe_path)
2904 if (apply_mass_fit and fitter == 'treefit'):
2905 treeFit(v0, conf_level=0.0, massConstraint=[v0.split(':')[0]], path=roe_path)
2906 optimizeROEWithV0(v0, mask_names, '', path=roe_path)
2907 path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
2908
2909
2910def printROEInfo(mask_names=None, full_print=False,
2911 unpackComposites=True, path=None):
2912 """
2913 This function prints out the information for the current ROE, so it should only be used in the for_each path.
2914 It prints out basic ROE object info.
2915
2916 If mask names are provided, specific information for those masks will be printed out.
2917
2918 It is also possible to print out all particles in a given mask if the
2919 'full_print' is set to True.
2920
2921 @param mask_names array of ROEMask names for printing out info
2922 @param unpackComposites if true, replace composite particles by their daughters
2923 @param full_print print out particles in mask
2924 @param path modules are added to this path
2925 """
2926
2927 if mask_names is None:
2928 mask_names = []
2929 printMask = register_module('RestOfEventPrinter')
2930 printMask.set_name('RestOfEventPrinter')
2931 printMask.param('maskNames', mask_names)
2932 printMask.param('fullPrint', full_print)
2933 printMask.param('unpackComposites', unpackComposites)
2934 path.add_module(printMask)
2935
2936
2937def buildContinuumSuppression(list_name, roe_mask, path):
2938 """
2939 Creates for each Particle in the given ParticleList a ContinuumSuppression
2940 dataobject and makes basf2 relation between them.
2941
2942 :param list_name: name of the input ParticleList
2943 :param roe_mask: name of the ROE mask
2944 :param path: modules are added to this path
2945 """
2946
2947 qqBuilder = register_module('ContinuumSuppressionBuilder')
2948 qqBuilder.set_name('QQBuilder_' + list_name)
2949 qqBuilder.param('particleList', list_name)
2950 qqBuilder.param('ROEMask', roe_mask)
2951 path.add_module(qqBuilder)
2952
2953
2954def removeParticlesNotInLists(lists_to_keep, path):
2955 """
2956 Removes all Particles that are not in a given list of ParticleLists (or daughters of those).
2957 All relations from/to Particles, daughter indices, and other ParticleLists are fixed.
2958
2959 @param lists_to_keep Keep the Particles and their daughters in these ParticleLists.
2960 @param path modules are added to this path
2961 """
2962
2963 mod = register_module('RemoveParticlesNotInLists')
2964 mod.param('particleLists', lists_to_keep)
2965 path.add_module(mod)
2966
2967
2968def inclusiveBtagReconstruction(upsilon_list_name, bsig_list_name, btag_list_name, input_lists_names, path):
2969 """
2970 Reconstructs Btag from particles in given ParticleLists which do not share any final state particles (mdstSource) with Bsig.
2971
2972 @param upsilon_list_name Name of the ParticleList to be filled with 'Upsilon(4S) -> B:sig anti-B:tag'
2973 @param bsig_list_name Name of the Bsig ParticleList
2974 @param btag_list_name Name of the Bsig ParticleList
2975 @param input_lists_names List of names of the ParticleLists which are used to reconstruct Btag from
2976 """
2977
2978 btag = register_module('InclusiveBtagReconstruction')
2979 btag.set_name('InclusiveBtagReconstruction_' + bsig_list_name)
2980 btag.param('upsilonListName', upsilon_list_name)
2981 btag.param('bsigListName', bsig_list_name)
2982 btag.param('btagListName', btag_list_name)
2983 btag.param('inputListsNames', input_lists_names)
2984 path.add_module(btag)
2985
2986
2987def selectDaughters(particle_list_name, decay_string, path):
2988 """
2989 Redefine the Daughters of a particle: select from decayString
2990
2991 @param particle_list_name input particle list
2992 @param decay_string for selecting the Daughters to be preserved
2993 """
2994
2995 seld = register_module('SelectDaughters')
2996 seld.set_name('SelectDaughters_' + particle_list_name)
2997 seld.param('listName', particle_list_name)
2998 seld.param('decayString', decay_string)
2999 path.add_module(seld)
3000
3001
3002def markDuplicate(particleList, prioritiseV0, path):
3003 """
3004 Call DuplicateVertexMarker to find duplicate particles in a list and
3005 flag the ones that should be kept
3006
3007 @param particleList input particle list
3008 @param prioritiseV0 if true, give V0s a higher priority
3009 """
3010
3011 markdup = register_module('DuplicateVertexMarker')
3012 markdup.param('particleList', particleList)
3013 markdup.param('prioritiseV0', prioritiseV0)
3014 path.add_module(markdup)
3015
3016
3017PI0ETAVETO_COUNTER = 0
3018
3019
3020def oldwritePi0EtaVeto(
3021 particleList,
3022 decayString,
3023 workingDirectory='.',
3024 pi0vetoname='Pi0_Prob',
3025 etavetoname='Eta_Prob',
3026 downloadFlag=True,
3027 selection='',
3028 path=None
3029):
3030 """
3031 Give pi0/eta probability for hard photon.
3032
3033 In the default weight files a value of 1.4 GeV is set as the lower limit for the hard photon energy in the CMS frame.
3034
3035 The current default weight files are optimised using MC9.
3036 The input variables are as below. Aliases are set to some variables during training.
3037
3038 * M: pi0/eta candidates Invariant mass
3039 * lowE: soft photon energy in lab frame
3040 * cTheta: soft photon ECL cluster's polar angle
3041 * Zmva: soft photon output of MVA using Zernike moments of the cluster
3042 * minC2Hdist: soft photon distance from eclCluster to nearest point on nearest Helix at the ECL cylindrical radius
3043
3044 If you don't have weight files in your workingDirectory,
3045 these files are downloaded from database to your workingDirectory automatically.
3046 Please refer to analysis/examples/tutorials/B2A306-B02RhoGamma-withPi0EtaVeto.py
3047 about how to use this function.
3048
3049 NOTE:
3050 Please don't use following ParticleList names elsewhere:
3051
3052 ``gamma:HARDPHOTON``, ``pi0:PI0VETO``, ``eta:ETAVETO``,
3053 ``gamma:PI0SOFT + str(PI0ETAVETO_COUNTER)``, ``gamma:ETASOFT + str(PI0ETAVETO_COUNTER)``
3054
3055 Please don't use ``lowE``, ``cTheta``, ``Zmva``, ``minC2Hdist`` as alias elsewhere.
3056
3057 @param particleList The input ParticleList
3058 @param decayString specify Particle to be added to the ParticleList
3059 @param workingDirectory The weight file directory
3060 @param downloadFlag whether download default weight files or not
3061 @param pi0vetoname extraInfo name of pi0 probability
3062 @param etavetoname extraInfo name of eta probability
3063 @param selection Selection criteria that Particle needs meet in order for for_each ROE path to continue
3064 @param path modules are added to this path
3065 """
3066
3067 import b2bii
3068 if b2bii.isB2BII():
3069 B2ERROR("The old pi0 / eta veto is not suitable for Belle analyses.")
3070
3071 import os
3072 import basf2_mva
3073
3074 global PI0ETAVETO_COUNTER
3075
3076 if PI0ETAVETO_COUNTER == 0:
3077 from variables import variables
3078 variables.addAlias('lowE', 'daughter(1,E)')
3079 variables.addAlias('cTheta', 'daughter(1,clusterTheta)')
3080 variables.addAlias('Zmva', 'daughter(1,clusterZernikeMVA)')
3081 variables.addAlias('minC2Tdist', 'daughter(1,minC2TDist)')
3082 variables.addAlias('cluNHits', 'daughter(1,clusterNHits)')
3083 variables.addAlias('E9E21', 'daughter(1,clusterE9E21)')
3084
3085 PI0ETAVETO_COUNTER = PI0ETAVETO_COUNTER + 1
3086
3087 roe_path = create_path()
3088
3089 deadEndPath = create_path()
3090
3091 signalSideParticleFilter(particleList, selection, roe_path, deadEndPath)
3092
3093 fillSignalSideParticleList('gamma:HARDPHOTON', decayString, path=roe_path)
3094
3095 pi0softname = 'gamma:PI0SOFT'
3096 etasoftname = 'gamma:ETASOFT'
3097 softphoton1 = pi0softname + str(PI0ETAVETO_COUNTER)
3098 softphoton2 = etasoftname + str(PI0ETAVETO_COUNTER)
3099
3100 fillParticleList(
3101 softphoton1,
3102 '[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]',
3103 path=roe_path)
3104 applyCuts(softphoton1, 'abs(clusterTiming)<120', path=roe_path)
3105 fillParticleList(
3106 softphoton2,
3107 '[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]',
3108 path=roe_path)
3109 applyCuts(softphoton2, 'abs(clusterTiming)<120', path=roe_path)
3110
3111 reconstructDecay('pi0:PI0VETO -> gamma:HARDPHOTON ' + softphoton1, '', path=roe_path)
3112 reconstructDecay('eta:ETAVETO -> gamma:HARDPHOTON ' + softphoton2, '', path=roe_path)
3113
3114 if not os.path.isdir(workingDirectory):
3115 os.mkdir(workingDirectory)
3116 B2INFO('oldwritePi0EtaVeto: ' + workingDirectory + ' has been created as workingDirectory.')
3117
3118 if not os.path.isfile(workingDirectory + '/pi0veto.root'):
3119 if downloadFlag:
3120 basf2_mva.download('Pi0VetoIdentifier', workingDirectory + '/pi0veto.root')
3121 B2INFO('oldwritePi0EtaVeto: pi0veto.root has been downloaded from database to workingDirectory.')
3122
3123 if not os.path.isfile(workingDirectory + '/etaveto.root'):
3124 if downloadFlag:
3125 basf2_mva.download('EtaVetoIdentifier', workingDirectory + '/etaveto.root')
3126 B2INFO('oldwritePi0EtaVeto: etaveto.root has been downloaded from database to workingDirectory.')
3127
3128 roe_path.add_module('MVAExpert', listNames=['pi0:PI0VETO'], extraInfoName='Pi0Veto',
3129 identifier=workingDirectory + '/pi0veto.root')
3130 roe_path.add_module('MVAExpert', listNames=['eta:ETAVETO'], extraInfoName='EtaVeto',
3131 identifier=workingDirectory + '/etaveto.root')
3132
3133 rankByHighest('pi0:PI0VETO', 'extraInfo(Pi0Veto)', numBest=1, path=roe_path)
3134 rankByHighest('eta:ETAVETO', 'extraInfo(EtaVeto)', numBest=1, path=roe_path)
3135
3136 variableToSignalSideExtraInfo('pi0:PI0VETO', {'extraInfo(Pi0Veto)': pi0vetoname}, path=roe_path)
3137 variableToSignalSideExtraInfo('eta:ETAVETO', {'extraInfo(EtaVeto)': etavetoname}, path=roe_path)
3138
3139 path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
3140
3141
3142def writePi0EtaVeto(
3143 particleList,
3144 decayString,
3145 mode='standardMC15rd',
3146 selection='',
3147 path=None,
3148 suffix='',
3149 hardParticle='gamma',
3150 pi0PayloadNameOverride=None,
3151 pi0SoftPhotonCutOverride=None,
3152 etaPayloadNameOverride=None,
3153 etaSoftPhotonCutOverride=None,
3154 requireSoftPhotonIsInROE=False,
3155 pi0Selection='[0.03 < M < 0.23]',
3156 etaSelection='[0.25 < M < 0.75]'
3157):
3158 """
3159 Give pi0/eta probability for hard photon.
3160
3161 In the default weight files a value of 1.4 GeV is set as the lower limit for the hard photon energy in the CMS frame.
3162 For MC15rd weight files, the BtoXGamma skim is applied during the MVA training.
3163
3164 The current default weight files are optimised using MC15rd. The weight files for MC12 (last version) are still available.
3165
3166 The input variables of the mva training for pi0 veto using MC15rd are:
3167
3168 * M: Invariant mass of pi0 candidates
3169 * cosHelicityAngleMomentum: Cosine of angle between momentum difference of the photons in the pi0 rest frame
3170 and momentum of pi0 in lab frame
3171 * daughter(1,E): soft photon energy in lab frame
3172 * daughter(1,clusterTheta): soft photon ECL cluster's polar angle
3173 * daughter(1,clusterLAT): soft photon lateral energy distribution
3174
3175 The input variables of the mva training for eta veto using MC15rd are:
3176
3177 * M: Invariant mass of eta candidates
3178 * cosHelicityAngleMomentum: Cosine of angle between momentum difference of the photons in the eta rest frame
3179 and momentum of eta in lab frame
3180 * daughter(1,E): soft photon energy in lab frame
3181 * daughter(1,clusterTheta): soft photon ECL cluster's polar angle
3182 * daughter(1,clusterLAT): soft photon lateral energy distribution
3183 * daughter(1,clusterNHits): soft photon total crystal weights sum(w_i) with w_i<=1
3184 * daughter(1,clusterE1E9): soft photon ratio between energies of central crystal and inner 3x3 crystals
3185 * daughter(1,clusterE9E21): soft photon ratio of energies in inner 3x3 crystals and 5x5 crystals without corners
3186 * daughter(1,clusterSecondMoment): soft photon second moment
3187 * daughter(1,clusterAbsZernikeMoment40): soft photon Zernike moment 40
3188 * daughter(1,clusterAbsZernikeMoment51): soft photon Zernike moment 51
3189
3190 The input variables of the mva training using MC12 are:
3191
3192 * M: Invariant mass of pi0/eta candidates
3193 * daughter(1,E): soft photon energy in lab frame
3194 * daughter(1,clusterTheta): soft photon ECL cluster's polar angle
3195 * daughter(1,minC2TDist): soft photon distance from eclCluster to nearest point on nearest Helix at the ECL cylindrical radius
3196 * daughter(1,clusterZernikeMVA): soft photon output of MVA using Zernike moments of the cluster
3197 * daughter(1,clusterNHits): soft photon total crystal weights sum(w_i) with w_i<=1
3198 * daughter(1,clusterE9E21): soft photon ratio of energies in inner 3x3 crystals and 5x5 crystals without corners
3199 * cosHelicityAngleMomentum: Cosine of angle between momentum difference of the photons in the pi0/eta rest frame
3200 and momentum of pi0/eta in lab frame
3201
3202 The following strings are available for mode:
3203
3204 * standard: loose energy cut and no clusterNHits cut are applied to soft photon
3205 * tight: tight energy cut and no clusterNHits cut are applied to soft photon
3206 * cluster: loose energy cut and clusterNHits cut are applied to soft photon
3207 * both: tight energy cut and clusterNHits cut are applied to soft photon
3208 * standardMC15rd: loose energy cut is applied to soft photon and the weight files are trained using MC15rd
3209 * tightMC15rd: tight energy cut is applied to soft photon and the weight files are trained using MC15rd
3210
3211 The final probability of the pi0/eta veto is stored as an extraInfo. If no suffix is set it can be obtained from the variables
3212 `pi0Prob`/`etaProb`. Otherwise, it is available as '{Pi0, Eta}ProbOrigin', '{Pi0, Eta}ProbTightEnergyThreshold', '{Pi0,
3213 Eta}ProbLargeClusterSize', '{Pi0, Eta}ProbTightEnergyThresholdAndLargeClusterSize', '{Pi0, Eta}ProbOriginMC15rd', or
3214 '{Pi0, Eta}ProbTightEnergyThresholdMC15rd' for the six modes described above, with the chosen suffix appended. If one would
3215 like to call this veto twice in one script, add suffix in the second time!
3216 The second highest probability of the pi0/eta veto also is stored as an extraInfo, with a prefix of 'second' to the previous
3217 ones, e.g. secondPi0ProbOrigin{suffix}. This can be used to do validation/systematics study.
3218
3219 NOTE:
3220 Please don't use following ParticleList names elsewhere:
3221
3222 ``gamma:HardPhoton``,
3223 ``gamma:Pi0Soft + ListName + '_' + particleList.replace(':', '_')``,
3224 ``gamma:EtaSoft + ListName + '_' + particleList.replace(':', '_')``,
3225 ``pi0:EtaVeto + ListName``,
3226 ``eta:EtaVeto + ListName``
3227
3228 @param particleList the input ParticleList
3229 @param decayString specify Particle to be added to the ParticleList
3230 @param mode choose one mode out of 'standardMC15rd', 'tightMC15rd', 'standard', 'tight', 'cluster' and 'both'
3231 @param selection selection criteria that Particle needs meet in order for for_each ROE path to continue
3232 @param path modules are added to this path
3233 @param suffix optional suffix to be appended to the usual extraInfo name
3234 @param hardParticle particle name which is used to calculate the pi0/eta probability (default is gamma)
3235 @param pi0PayloadNameOverride specify the payload name of pi0 veto only if one wants to use non-default one. (default is None)
3236 @param pi0SoftPhotonCutOverride specify the soft photon selection criteria of pi0 veto only if one wants to use non-default one.
3237 (default is None)
3238 @param etaPayloadNameOverride specify the payload name of eta veto only if one wants to use non-default one. (default is None)
3239 @param etaSoftPhotonCutOverride specify the soft photon selection criteria of eta veto only if one wants to use non-default one.
3240 (default is None)
3241 @param requireSoftPhotonIsInROE specify if the soft photons used to build pi0 and eta candidates have to be in the current ROE
3242 or not. Default is False, i.e. all soft photons in the event are used.
3243 @param pi0Selection Selection for the pi0 reconstruction. Default is '(0.03 < M < 0.23)'.
3244 @param etaSelection Selection for the eta reconstruction. Default is '(0.25 < M < 0.75)'.
3245 """
3246
3247 import b2bii
3248 if b2bii.isB2BII():
3249 B2ERROR("The pi0 / eta veto is not suitable for Belle analyses.")
3250
3251 if (requireSoftPhotonIsInROE):
3252 B2WARNING("Requiring the soft photon to being in the ROE was not done for the MVA training. "
3253 "Please check the results carefully.")
3254 if (mode == 'standardMC15rd' or mode == 'tightMC15rd'):
3255 if (pi0Selection != '[0.03 < M < 0.23]' or etaSelection != '[0.25 < M < 0.75]'):
3256 B2WARNING(
3257 "Personal selection criteria for the pi0 or the eta during reconstructDecay were not used during the MVA training. "
3258 "Please check the results carefully.")
3259 else:
3260 if (pi0Selection != '' or etaSelection != ''):
3261 B2WARNING(
3262 "Personal selection criteria for the pi0 or the eta during reconstructDecay were not used during the MVA training. "
3263 "Please check the results carefully.")
3264
3265 renameSuffix = False
3266
3267 for module in path.modules():
3268 if module.type() == "SubEvent" and not renameSuffix:
3269 for subpath in [p.values for p in module.available_params() if p.name == "path"]:
3270 if renameSuffix:
3271 break
3272 for submodule in subpath.modules():
3273 if f'{hardParticle}:HardPhoton{suffix}' in submodule.name():
3274 suffix += '_0'
3275 B2WARNING("Same extension already used in writePi0EtaVeto, append '_0'")
3276 renameSuffix = True
3277 break
3278
3279 roe_path = create_path()
3280 deadEndPath = create_path()
3281 signalSideParticleFilter(particleList, selection, roe_path, deadEndPath)
3282 fillSignalSideParticleList(f'{hardParticle}:HardPhoton{suffix}', decayString, path=roe_path)
3283
3284 dictListName = {'standard': 'Origin',
3285 'tight': 'TightEnergyThreshold',
3286 'cluster': 'LargeClusterSize',
3287 'both': 'TightEnrgyThresholdAndLargeClusterSize',
3288 'standardMC15rd': 'OriginMC15rd',
3289 'tightMC15rd': 'TightEnergyThresholdMC15rd'}
3290
3291 dictPi0EnergyCut = {
3292 'standard': '[[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]]',
3293 'tight': '[[clusterReg==1 and E>0.03] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.04]]',
3294 'cluster': '[[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]]',
3295 'both': '[[clusterReg==1 and E>0.03] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.04]]',
3296 'standardMC15rd': '[[clusterReg==1 and E>0.0225] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]]',
3297 'tightMC15rd': '[[clusterReg==1 and E>0.03] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.04]]'}
3298
3299 dictEtaEnergyCut = {
3300 'standard': '[[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]]',
3301 'tight': '[[clusterReg==1 and E>0.06] or [clusterReg==2 and E>0.06] or [clusterReg==3 and E>0.06]]',
3302 'cluster': '[[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]]',
3303 'both': '[[clusterReg==1 and E>0.06] or [clusterReg==2 and E>0.06] or [clusterReg==3 and E>0.06]]',
3304 'standardMC15rd': '[[clusterReg==1 and E>0.0225] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]]',
3305 'tightMC15rd': '[[clusterReg==1 and E>0.03] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.04]]'}
3306
3307 dictNHitsTimingCut = {'standard': 'clusterNHits >= 0 and abs(clusterTiming)<clusterErrorTiming',
3308 'tight': 'clusterNHits >= 0 and abs(clusterTiming)<clusterErrorTiming',
3309 'cluster': 'clusterNHits >= 2 and abs(clusterTiming)<clusterErrorTiming',
3310 'both': 'clusterNHits >= 2 and abs(clusterTiming)<clusterErrorTiming',
3311 'standardMC15rd': 'clusterNHits > 1.5 and abs(clusterTiming) < 200',
3312 'tightMC15rd': 'clusterNHits > 1.5 and abs(clusterTiming) < 200'}
3313
3314 dictPi0PayloadName = {'standard': 'Pi0VetoIdentifierStandard',
3315 'tight': 'Pi0VetoIdentifierWithHigherEnergyThreshold',
3316 'cluster': 'Pi0VetoIdentifierWithLargerClusterSize',
3317 'both': 'Pi0VetoIdentifierWithHigherEnergyThresholdAndLargerClusterSize',
3318 'standardMC15rd': 'Pi0VetoIdentifierStandardMC15rd',
3319 'tightMC15rd': 'Pi0VetoIdentifierWithHigherEnergyThresholdMC15rd'}
3320
3321 dictEtaPayloadName = {'standard': 'EtaVetoIdentifierStandard',
3322 'tight': 'EtaVetoIdentifierWithHigherEnergyThreshold',
3323 'cluster': 'EtaVetoIdentifierWithLargerClusterSize',
3324 'both': 'EtaVetoIdentifierWithHigherEnergyThresholdAndLargerClusterSize',
3325 'standardMC15rd': 'EtaVetoIdentifierStandardMC15rd',
3326 'tightMC15rd': 'EtaVetoIdentifierWithHigherEnergyThresholdMC15rd'}
3327
3328 dictPi0ExtraInfoName = {'standard': 'Pi0ProbOrigin',
3329 'tight': 'Pi0ProbTightEnergyThreshold',
3330 'cluster': 'Pi0ProbLargeClusterSize',
3331 'both': 'Pi0ProbTightEnergyThresholdAndLargeClusterSize',
3332 'standardMC15rd': 'Pi0ProbOriginMC15rd',
3333 'tightMC15rd': 'Pi0ProbTightEnergyThresholdMC15rd'}
3334
3335 dictEtaExtraInfoName = {'standard': 'EtaProbOrigin',
3336 'tight': 'EtaProbTightEnergyThreshold',
3337 'cluster': 'EtaProbLargeClusterSize',
3338 'both': 'EtaProbTightEnergyThresholdAndLargeClusterSize',
3339 'standardMC15rd': 'EtaProbOriginMC15rd',
3340 'tightMC15rd': 'EtaProbTightEnergyThresholdMC15rd'}
3341
3342 ListName = dictListName[mode]
3343 Pi0EnergyCut = dictPi0EnergyCut[mode]
3344 EtaEnergyCut = dictEtaEnergyCut[mode]
3345 NHitsTimingCut = dictNHitsTimingCut[mode]
3346 Pi0PayloadName = dictPi0PayloadName[mode]
3347 EtaPayloadName = dictEtaPayloadName[mode]
3348 Pi0ExtraInfoName = dictPi0ExtraInfoName[mode]
3349 EtaExtraInfoName = dictEtaExtraInfoName[mode]
3350
3351 # pi0 veto
3352 if pi0PayloadNameOverride is not None:
3353 Pi0PayloadName = pi0PayloadNameOverride
3354 B2WARNING("You're using personal weight files, be careful. ")
3355 if pi0SoftPhotonCutOverride is None:
3356 Pi0SoftPhotonCut = Pi0EnergyCut + ' and ' + NHitsTimingCut
3357 else:
3358 Pi0SoftPhotonCut = pi0SoftPhotonCutOverride
3359 B2WARNING("You're applying personal cuts on the soft photon candidates, be careful. ")
3360
3361 if requireSoftPhotonIsInROE:
3362 Pi0SoftPhotonCut += ' and isInRestOfEvent==1'
3363
3364 # define the particleList name for soft photon
3365 pi0soft = f'gamma:Pi0Soft{suffix}' + ListName + '_' + particleList.replace(':', '_')
3366 # fill the particleList for soft photon with energy, timing and clusterNHits cuts
3367 fillParticleList(pi0soft, Pi0SoftPhotonCut, path=roe_path)
3368 # reconstruct pi0
3369 reconstructDecay('pi0:Pi0Veto' + ListName + suffix + f' -> {hardParticle}:HardPhoton{suffix} ' + pi0soft, pi0Selection,
3370 allowChargeViolation=True, path=roe_path)
3371 # MVA training is conducted.
3372 roe_path.add_module('MVAExpert', listNames=['pi0:Pi0Veto' + ListName + suffix],
3373 extraInfoName=Pi0ExtraInfoName, identifier=Pi0PayloadName)
3374 # Pick up the pi0/eta candidate with the highest pi0/eta probability.
3375 rankByHighest(
3376 'pi0:Pi0Veto' + ListName + suffix,
3377 'extraInfo(' + Pi0ExtraInfoName + ')',
3378 numBest=2,
3379 outputVariable="Pi0VetoRank",
3380 path=roe_path)
3381 cutAndCopyList(outputListName='pi0:Pi0VetoFirst' + ListName + suffix,
3382 inputListName='pi0:Pi0Veto' + ListName + suffix,
3383 cut='extraInfo(Pi0VetoRank)==1',
3384 path=roe_path)
3385 variableToSignalSideExtraInfo('pi0:Pi0VetoFirst' + ListName + suffix,
3386 {'extraInfo(' + Pi0ExtraInfoName + ')': Pi0ExtraInfoName + suffix}, path=roe_path)
3387 # Pick up the pi0/eta candidate with the second highest pi0/eta probability.
3388 cutAndCopyList(outputListName='pi0:Pi0VetoSecond' + ListName + suffix,
3389 inputListName='pi0:Pi0Veto' + ListName + suffix,
3390 cut='extraInfo(Pi0VetoRank)==2',
3391 path=roe_path)
3392 variableToSignalSideExtraInfo('pi0:Pi0VetoSecond' + ListName + suffix,
3393 {'extraInfo(' + Pi0ExtraInfoName + ')': 'second' + Pi0ExtraInfoName + suffix}, path=roe_path)
3394
3395 # eta veto
3396 if etaPayloadNameOverride is not None:
3397 EtaPayloadName = etaPayloadNameOverride
3398 B2WARNING("You're using personal weight files, be careful. ")
3399 if etaSoftPhotonCutOverride is None:
3400 EtaSoftPhotonCut = EtaEnergyCut + ' and ' + NHitsTimingCut
3401 else:
3402 EtaSoftPhotonCut = etaSoftPhotonCutOverride
3403 B2WARNING("You're applying personal cuts on the soft photon candidates, be careful. ")
3404
3405 if requireSoftPhotonIsInROE:
3406 EtaSoftPhotonCut += ' and isInRestOfEvent==1'
3407
3408 etasoft = f'gamma:EtaSoft{suffix}' + ListName + '_' + particleList.replace(':', '_')
3409 fillParticleList(etasoft, EtaSoftPhotonCut, path=roe_path)
3410 reconstructDecay('eta:EtaVeto' + ListName + suffix + f' -> {hardParticle}:HardPhoton{suffix} ' + etasoft, etaSelection,
3411 allowChargeViolation=True, path=roe_path)
3412 roe_path.add_module('MVAExpert', listNames=['eta:EtaVeto' + ListName + suffix],
3413 extraInfoName=EtaExtraInfoName, identifier=EtaPayloadName)
3414 rankByHighest(
3415 'eta:EtaVeto' + ListName + suffix,
3416 'extraInfo(' + EtaExtraInfoName + ')',
3417 numBest=2,
3418 outputVariable="EtaVetoRank",
3419 path=roe_path)
3420 cutAndCopyList(outputListName='eta:EtaVetoFirst' + ListName + suffix,
3421 inputListName='eta:EtaVeto' + ListName + suffix,
3422 cut='extraInfo(EtaVetoRank)==1',
3423 path=roe_path)
3424 variableToSignalSideExtraInfo('eta:EtaVetoFirst' + ListName + suffix,
3425 {'extraInfo(' + EtaExtraInfoName + ')': EtaExtraInfoName + suffix}, path=roe_path)
3426 cutAndCopyList(outputListName='eta:EtaVetoSecond' + ListName + suffix,
3427 inputListName='eta:EtaVeto' + ListName + suffix,
3428 cut='extraInfo(EtaVetoRank)==2',
3429 path=roe_path)
3430 variableToSignalSideExtraInfo('eta:EtaVetoSecond' + ListName + suffix,
3431 {'extraInfo(' + EtaExtraInfoName + ')': 'second' + EtaExtraInfoName + suffix}, path=roe_path)
3432
3433 path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
3434
3435
3436def lowEnergyPi0Identification(pi0List, gammaList, payloadNameSuffix,
3437 path=None):
3438 """
3439 Calculate low-energy pi0 identification.
3440 The result is stored as ExtraInfo ``lowEnergyPi0Identification`` for
3441 the list pi0List.
3442
3443 Parameters:
3444 pi0List (str): Pi0 list.
3445
3446 gammaList (str): Gamma list. First, an energy cut E > 0.2 is applied to the photons from this list.
3447 Then, all possible combinations with a pi0 daughter photon are formed except the one
3448 corresponding to the reconstructed pi0.
3449 The maximum low-energy pi0 veto value is calculated for such photon pairs
3450 and used as one of the input variables for the identification classifier.
3451
3452 payloadNameSuffix (str): Payload name suffix. The weight payloads are stored in the analysis global
3453 tag and have the following names:\n
3454 * ``'LowEnergyPi0Veto' + payloadNameSuffix``
3455 * ``'LowEnergyPi0Identification' + payloadNameSuffix``\n
3456 The possible suffixes are:\n
3457 * ``'Belle1'`` for Belle data.
3458 * ``'Belle2Release5'`` for Belle II release 5 data (MC14, proc12, buckets 16 - 25).
3459 * ``'Belle2Release6'`` for Belle II release 6 data (MC15, proc13, buckets 26 - 36).
3460
3461 path (basf2.Path): Module path.
3462 """
3463
3464 # Select photons with higher energy for formation of veto combinations.
3465 gammaListVeto = f'{gammaList}_pi0veto'
3466 cutAndCopyList(gammaListVeto, gammaList, 'E > 0.2', path=path)
3467 import b2bii
3468 payload_name = 'LowEnergyPi0Veto' + payloadNameSuffix
3469 path.add_module('LowEnergyPi0VetoExpert', identifier=payload_name,
3470 VetoPi0Daughters=True, GammaListName=gammaListVeto,
3471 Pi0ListName=pi0List, Belle1=b2bii.isB2BII())
3472 payload_name = 'LowEnergyPi0Identification' + payloadNameSuffix
3473 path.add_module('LowEnergyPi0IdentificationExpert',
3474 identifier=payload_name, Pi0ListName=pi0List,
3475 Belle1=b2bii.isB2BII())
3476
3477
3478def getNeutralHadronGeomMatches(
3479 particleLists,
3480 addKL=True,
3481 addNeutrons=False,
3482 efficiencyCorrectionKl=0.83,
3483 efficiencyCorrectionNeutrons=1.0,
3484 path=None):
3485 """
3486 For an ECL-based list, assign the mcdistanceKL and mcdistanceNeutron variables that correspond
3487 to the distance to the closest MC KL and neutron, respectively.
3488 @param particleLists the input ParticleLists, must be ECL-based lists (e.g. photons)
3489 @param addKL (default True) add distance to MC KL
3490 @param addNeutrons (default False) add distance to MC neutrons
3491 @param efficiencyCorrectionKl (default 0.83) apply overall efficiency correction
3492 @param efficiencyCorrectionNeutrons (default 1.0) apply overall efficiency correction
3493 @param path modules are added to this path
3494 """
3495 from ROOT import Belle2
3496 Const = Belle2.Const
3497
3498 if addKL:
3499 path.add_module(
3500 "NeutralHadronMatcher",
3501 particleLists=particleLists,
3502 mcPDGcode=Const.Klong.getPDGCode(),
3503 efficiencyCorrection=efficiencyCorrectionKl)
3504 if addNeutrons:
3505 path.add_module(
3506 "NeutralHadronMatcher",
3507 particleLists=particleLists,
3508 mcPDGcode=Const.neutron.getPDGCode(),
3509 efficiencyCorrection=efficiencyCorrectionNeutrons)
3510
3511
3512def getBeamBackgroundProbability(particleList, weight, path=None):
3513 """
3514 Assign a probability to each ECL cluster as being signal like (1) compared to beam background like (0)
3515 @param particleList the input ParticleList, must be a photon list
3516 @param weight type of weight file to use
3517 @param path modules are added to this path
3518 """
3519
3520 import b2bii
3521 if b2bii.isB2BII() and weight != "Belle":
3522 B2WARNING("weight type must be 'Belle' for b2bii.")
3523
3524 path.add_module('MVAExpert',
3525 listNames=particleList,
3526 extraInfoName='beamBackgroundSuppression',
3527 identifier=f'BeamBackgroundMVA_{weight}')
3528
3529
3530def getFakePhotonProbability(particleList, weight, path=None):
3531 """
3532 Assign a probability to each ECL cluster as being signal like (1) compared to fake photon like (0)
3533 @param particleList the input ParticleList, must be a photon list
3534 @param weight type of weight file to use
3535 @param path modules are added to this path
3536 """
3537
3538 import b2bii
3539 if b2bii.isB2BII() and weight != "Belle":
3540 B2WARNING("weight type must be 'Belle' for b2bii.")
3541
3542 path.add_module('MVAExpert',
3543 listNames=particleList,
3544 extraInfoName='fakePhotonSuppression',
3545 identifier=f'FakePhotonMVA_{weight}')
3546
3547
3548def buildEventKinematics(inputListNames=None, default_cleanup=True, custom_cuts=None,
3549 chargedPIDPriors=None, fillWithMostLikely=False, path=None):
3550 """
3551 Calculates the global kinematics of the event (visible energy, missing momentum, missing mass...)
3552 using ParticleLists provided. If no ParticleList is provided, default ParticleLists are used
3553 (all track and all hits in ECL without associated track).
3554
3555 The visible energy missing values are
3556 stored in a EventKinematics dataobject.
3557
3558 @param inputListNames list of ParticleLists used to calculate the global event kinematics.
3559 If the list is empty, default ParticleLists pi+:evtkin and gamma:evtkin are filled.
3560 @param fillWithMostLikely if True, the module uses the most likely particle mass hypothesis for charged particles
3561 according to the PID likelihood and the option inputListNames will be ignored.
3562 @param chargedPIDPriors The prior PID fractions, that are used to regulate
3563 amount of certain charged particle species, should be a list of
3564 six floats if not None. The order of particle types is
3565 the following: [e-, mu-, pi-, K-, p+, d+]
3566 @param default_cleanup if True and either inputListNames empty or fillWithMostLikely True, default clean up cuts are applied
3567 @param custom_cuts tuple of selection cut strings of form (trackCuts, photonCuts), default is None,
3568 which would result in a standard predefined selection cuts
3569 @param path modules are added to this path
3570 """
3571
3572 if inputListNames is None:
3573 inputListNames = []
3574 trackCuts = 'pt > 0.1'
3575 trackCuts += ' and thetaInCDCAcceptance'
3576 trackCuts += ' and abs(dz) < 3'
3577 trackCuts += ' and dr < 0.5'
3578
3579 gammaCuts = 'E > 0.05'
3580 gammaCuts += ' and thetaInCDCAcceptance'
3581 if not b2bii.isB2BII():
3582 gammaCuts += ' and abs(clusterTiming) < 200'
3583 if (custom_cuts is not None):
3584 trackCuts, gammaCuts = custom_cuts
3585
3586 if fillWithMostLikely:
3587 from stdCharged import stdMostLikely
3588 stdMostLikely(chargedPIDPriors, '_evtkin', path=path)
3589 inputListNames = [f'{ptype}:mostlikely_evtkin' for ptype in ['K+', 'p+', 'e+', 'mu+', 'pi+']]
3590 if b2bii.isB2BII():
3591 copyList('gamma:evtkin', 'gamma:mdst', path=path)
3592 else:
3593 fillParticleList('gamma:evtkin', '', path=path)
3594 inputListNames += ['gamma:evtkin']
3595 if default_cleanup:
3596 B2INFO("Using default cleanup in EventKinematics module.")
3597 for ptype in ['K+', 'p+', 'e+', 'mu+', 'pi+']:
3598 applyCuts(f'{ptype}:mostlikely_evtkin', trackCuts, path=path)
3599 applyCuts('gamma:evtkin', gammaCuts, path=path)
3600 else:
3601 B2INFO("No cleanup in EventKinematics module.")
3602 if not inputListNames:
3603 B2INFO("Creating particle lists pi+:evtkin and gamma:evtkin to get the global kinematics of the event.")
3604 fillParticleList('pi+:evtkin', '', path=path)
3605 if b2bii.isB2BII():
3606 copyList('gamma:evtkin', 'gamma:mdst', path=path)
3607 else:
3608 fillParticleList('gamma:evtkin', '', path=path)
3609 particleLists = ['pi+:evtkin', 'gamma:evtkin']
3610 if default_cleanup:
3611 if (custom_cuts is not None):
3612 B2INFO("Using default cleanup in EventKinematics module.")
3613 applyCuts('pi+:evtkin', trackCuts, path=path)
3614 applyCuts('gamma:evtkin', gammaCuts, path=path)
3615 else:
3616 B2INFO("No cleanup in EventKinematics module.")
3617 else:
3618 particleLists = inputListNames
3619
3620 eventKinematicsModule = register_module('EventKinematics')
3621 eventKinematicsModule.set_name('EventKinematics_reco')
3622 eventKinematicsModule.param('particleLists', particleLists)
3623 path.add_module(eventKinematicsModule)
3624
3625
3626def buildEventKinematicsFromMC(inputListNames=None, selectionCut='', path=None):
3627 """
3628 Calculates the global kinematics of the event (visible energy, missing momentum, missing mass...)
3629 using generated particles. If no ParticleList is provided, default generated ParticleLists are used.
3630
3631 @param inputListNames list of ParticleLists used to calculate the global event kinematics.
3632 If the list is empty, default ParticleLists are filled.
3633 @param selectionCut optional selection cuts
3634 @param path Path to append the eventKinematics module to.
3635 """
3636
3637 if inputListNames is None:
3638 inputListNames = []
3639 if (len(inputListNames) == 0):
3640 # Type of particles to use for EventKinematics
3641 # K_S0 and Lambda0 are added here because some of them have interacted
3642 # with the detector material
3643 types = ['gamma', 'e+', 'mu+', 'pi+', 'K+', 'p+',
3644 'K_S0', 'Lambda0']
3645 for t in types:
3646 fillParticleListFromMC(f"{t}:evtkin_default_gen", 'mcPrimary > 0 and nDaughters == 0',
3647 True, True, path=path)
3648 if (selectionCut != ''):
3649 applyCuts(f"{t}:evtkin_default_gen", selectionCut, path=path)
3650 inputListNames += [f"{t}:evtkin_default_gen"]
3651
3652 eventKinematicsModule = register_module('EventKinematics')
3653 eventKinematicsModule.set_name('EventKinematics_gen')
3654 eventKinematicsModule.param('particleLists', inputListNames)
3655 eventKinematicsModule.param('usingMC', True)
3656 path.add_module(eventKinematicsModule)
3657
3658
3659def buildEventShape(inputListNames=None,
3660 default_cleanup=True,
3661 custom_cuts=None,
3662 allMoments=False,
3663 cleoCones=True,
3664 collisionAxis=True,
3665 foxWolfram=True,
3666 harmonicMoments=True,
3667 jets=True,
3668 sphericity=True,
3669 thrust=True,
3670 checkForDuplicates=False,
3671 path=None):
3672 """
3673 Calculates the event-level shape quantities (thrust, sphericity, Fox-Wolfram moments...)
3674 using the particles in the lists provided by the user. If no particle list is provided,
3675 the function will internally create a list of good tracks and a list of good photons
3676 with (optionally) minimal quality cuts.
3677
3678
3679 The results of the calculation are then stored into the EventShapeContainer dataobject,
3680 and are accessible using the variables of the EventShape group.
3681
3682 The user can switch the calculation of certain quantities on or off to save computing
3683 time. By default the calculation of the high-order moments (5-8) is turned off.
3684 Switching off an option will make the corresponding variables not available.
3685
3686 Info:
3687 The user can provide as many particle lists as needed, using also composite particles.
3688 In these cases, it is recommended to activate the checkForDuplicates flag since it
3689 will eliminate duplicates, e.g., if the same track is provided multiple times
3690 (either with different mass hypothesis or once as an independent particle and once
3691 as daughter of a composite particle). The first occurrence will be used in the
3692 calculations so the order in which the particle lists are given as well as within
3693 the particle lists matters.
3694
3695 @param inputListNames List of ParticleLists used to calculate the
3696 event shape variables. If the list is empty the default
3697 particleLists pi+:evtshape and gamma:evtshape are filled.
3698 @param default_cleanup If True, applies standard cuts on pt and cosTheta when
3699 defining the internal lists. This option is ignored if the
3700 particleLists are provided by the user.
3701 @param custom_cuts tuple of selection cut strings of form (trackCuts, photonCuts), default is None,
3702 which would result in a standard predefined selection cuts
3703 @param path Path to append the eventShape modules to.
3704 @param thrust Enables the calculation of thrust-related quantities (CLEO
3705 cones, Harmonic moments, jets).
3706 @param collisionAxis Enables the calculation of the quantities related to the
3707 collision axis .
3708 @param foxWolfram Enables the calculation of the Fox-Wolfram moments.
3709 @param harmonicMoments Enables the calculation of the Harmonic moments with respect
3710 to both the thrust axis and, if collisionAxis = True, the collision axis.
3711 @param allMoments If True, calculates also the FW and harmonic moments from order
3712 5 to 8 instead of the low-order ones only.
3713 @param cleoCones Enables the calculation of the CLEO cones with respect to both the thrust
3714 axis and, if collisionAxis = True, the collision axis.
3715 @param jets Enables the calculation of the hemisphere momenta and masses.
3716 Requires thrust = True.
3717 @param sphericity Enables the calculation of the sphericity-related quantities.
3718 @param checkForDuplicates Perform a check for duplicate particles before adding them. Regardless of the value of this option,
3719 it is recommended to consider sanitizing the lists you are passing to the function since this will
3720 speed up the processing.
3721
3722 """
3723
3724 if inputListNames is None:
3725 inputListNames = []
3726 trackCuts = 'pt > 0.1'
3727 trackCuts += ' and thetaInCDCAcceptance'
3728 trackCuts += ' and abs(dz) < 3.0'
3729 trackCuts += ' and dr < 0.5'
3730
3731 gammaCuts = 'E > 0.05'
3732 gammaCuts += ' and thetaInCDCAcceptance'
3733 if not b2bii.isB2BII():
3734 gammaCuts += ' and abs(clusterTiming) < 200'
3735 if (custom_cuts is not None):
3736 trackCuts, gammaCuts = custom_cuts
3737
3738 if not inputListNames:
3739 B2INFO("Creating particle lists pi+:evtshape and gamma:evtshape to get the event shape variables.")
3740 fillParticleList('pi+:evtshape', '', path=path)
3741 if b2bii.isB2BII():
3742 copyList('gamma:evtshape', 'gamma:mdst', path=path)
3743 else:
3744 fillParticleList(
3745 'gamma:evtshape',
3746 '',
3747 path=path)
3748 particleLists = ['pi+:evtshape', 'gamma:evtshape']
3749
3750 if default_cleanup:
3751 if (custom_cuts is not None):
3752 B2INFO("Applying standard cuts")
3753 applyCuts('pi+:evtshape', trackCuts, path=path)
3754
3755 applyCuts('gamma:evtshape', gammaCuts, path=path)
3756 else:
3757 B2WARNING("Creating the default lists with no cleanup.")
3758 else:
3759 particleLists = inputListNames
3760
3761 eventShapeModule = register_module('EventShapeCalculator')
3762 eventShapeModule.set_name('EventShape')
3763 eventShapeModule.param('particleListNames', particleLists)
3764 eventShapeModule.param('enableAllMoments', allMoments)
3765 eventShapeModule.param('enableCleoCones', cleoCones)
3766 eventShapeModule.param('enableCollisionAxis', collisionAxis)
3767 eventShapeModule.param('enableFoxWolfram', foxWolfram)
3768 eventShapeModule.param('enableJets', jets)
3769 eventShapeModule.param('enableHarmonicMoments', harmonicMoments)
3770 eventShapeModule.param('enableSphericity', sphericity)
3771 eventShapeModule.param('enableThrust', thrust)
3772 eventShapeModule.param('checkForDuplicates', checkForDuplicates)
3773
3774 path.add_module(eventShapeModule)
3775
3776
3777def labelTauPairMC(printDecayInfo=False, path=None, TauolaBelle=False, mapping_minus=None, mapping_plus=None):
3778 """
3779 Search tau leptons into the MC information of the event. If confirms it's a generated tau pair decay,
3780 labels the decay generated of the positive and negative leptons using the ID of KKMC tau decay table.
3781
3782 @param printDecayInfo: If true, prints ID and prong of each tau lepton in the event.
3783 @param path: module is added to this path
3784 @param TauolaBelle: if False, TauDecayMode is set. If True, TauDecayMarker is set.
3785 @param mapping_minus: if None, the map is the default one, else the path for the map is given by the user for tau-
3786 @param mapping_plus: if None, the map is the default one, else the path for the map is given by the user for tau+
3787 """
3788
3789 from basf2 import find_file
3790 if not TauolaBelle:
3791
3792 if printDecayInfo:
3793 m_printmode = 'all'
3794 else:
3795 m_printmode = 'default'
3796
3797 if mapping_minus is None:
3798 mp_file_minus = find_file('data/analysis/modules/TauDecayMode/map_tauminus.txt')
3799 else:
3800 mp_file_minus = mapping_minus
3801
3802 if mapping_plus is None:
3803 mp_file_plus = find_file('data/analysis/modules/TauDecayMode/map_tauplus.txt')
3804 else:
3805 mp_file_plus = mapping_plus
3806
3807 path.add_module('TauDecayMode', printmode=m_printmode, file_minus=mp_file_minus, file_plus=mp_file_plus)
3808
3809 else:
3810 tauDecayMarker = register_module('TauDecayMarker')
3811 tauDecayMarker.set_name('TauDecayMarker_')
3812
3813 path.add_module(tauDecayMarker, printDecayInfo=printDecayInfo)
3814
3815
3816def tagCurlTracks(particleLists,
3817 mcTruth=False,
3818 responseCut=-1.0,
3819 selectorType='cut',
3820 ptCut=0.5,
3821 expert_train=False,
3822 expert_filename="",
3823 path=None):
3824 """
3825 Warning:
3826 The cut selector is not calibrated with Belle II data and should not be used without extensive study.
3827
3828 Identifies curl tracks and tags them with extraInfo(isCurl=1) for later removal.
3829 For Belle data with a `b2bii` analysis the available cut based selection is described in `BN1079`_.
3830
3831 .. _BN1079: https://belle.kek.jp/secured/belle_note/gn1079/bn1079.pdf
3832
3833
3834 The module loops over all particles in a given list with a transverse momentum below the pre-selection **ptCut**
3835 and assigns them to bundles based on the response of the chosen **selector** and the required minimum response set by the
3836 **responseCut**. Once all particles are assigned they are ranked by 25dr^2+dz^2. All but the lowest are tagged
3837 with extraInfo(isCurl=1) to allow for later removal by cutting the list or removing these from ROE as
3838 applicable.
3839
3840
3841 @param particleLists: list of particle lists to check for curls.
3842 @param mcTruth: bool flag to additionally assign particles with extraInfo(isTruthCurl) and
3843 extraInfo(truthBundleSize). To calculate these particles are assigned to bundles by their
3844 genParticleIndex then ranked and tagged as normal.
3845 @param responseCut: float min classifier response that considers two tracks to come from the same particle.
3846 If set to ``-1`` a cut value optimised to maximise the accuracy on a BBbar sample is used.
3847 Note 'cut' selector is binary 0/1.
3848 @param selectorType: string name of selector to use. The available options are 'cut' and 'mva'.
3849 It is strongly recommended to used the 'mva' selection. The 'cut' selection
3850 is based on BN1079 and is only calibrated for Belle data.
3851
3852 @param ptCut: Pre-selection cut on transverse momentum. Only tracks below that are considered as curler candidates.
3853
3854 @param expert_train: flag to set training mode if selector has a training mode (mva).
3855 @param expert_filename: set file name of produced training ntuple (mva).
3856 @param path: module is added to this path.
3857 """
3858
3859 import b2bii
3860 belle = b2bii.isB2BII()
3861
3862 if (not isinstance(particleLists, list)):
3863 particleLists = [particleLists] # in case user inputs a particle list as string
3864
3865 curlTagger = register_module('CurlTagger')
3866 curlTagger.set_name('CurlTagger_')
3867 curlTagger.param('particleLists', particleLists)
3868 curlTagger.param('belle', belle)
3869 curlTagger.param('mcTruth', mcTruth)
3870 curlTagger.param('responseCut', responseCut)
3871 if abs(responseCut + 1) < 1e-9:
3872 curlTagger.param('usePayloadCut', True)
3873 else:
3874 curlTagger.param('usePayloadCut', False)
3875
3876 curlTagger.param('selectorType', selectorType)
3877 curlTagger.param('ptCut', ptCut)
3878 curlTagger.param('train', expert_train)
3879 curlTagger.param('trainFilename', expert_filename)
3880
3881 path.add_module(curlTagger)
3882
3883
3884def applyChargedPidMVA(particleLists, path, trainingMode, chargeIndependent=False, binaryHypoPDGCodes=(0, 0)):
3885 """
3886 Use an MVA to perform particle identification for charged stable particles, using the `ChargedPidMVA` module.
3887
3888 The module decorates Particle objects in the input ParticleList(s) with variables
3889 containing the appropriate MVA score, which can be used to select candidates by placing a cut on it.
3890
3891 Note:
3892 The MVA algorithm used is a gradient boosted decision tree (**TMVA 4.3.0**, **ROOT 6.20/04**).
3893
3894 The module can perform either 'binary' PID between input S, B particle mass hypotheses according to the following scheme:
3895
3896 * e (11) vs. pi (211)
3897 * mu (13) vs. pi (211)
3898 * pi (211) vs. K (321)
3899 * K (321) vs. pi (211)
3900
3901 , or 'global' PID, namely "one-vs-others" separation. The latter exploits an MVA algorithm trained in multi-class mode,
3902 and it's the default behaviour. Currently, the multi-class training separates the following standard charged hypotheses:
3903
3904 - e (11), mu (13), pi (211), K (321)
3905
3906 Warning:
3907 In order to run the `ChargedPidMVA` and ensure the most up-to-date MVA training weights are applied,
3908 it is necessary to append the latest analysis global tag (GT) to the steering script.
3909
3910 Parameters:
3911 particleLists (list(str)): the input list of DecayStrings, where each selected (^) daughter should correspond to a
3912 standard charged ParticleList, e.g. ``['Lambda0:sig -> ^p+ ^pi-', 'J/psi:sig -> ^mu+ ^mu-']``.
3913 One can also directly pass a list of standard charged ParticleLists,
3914 e.g. ``['e+:my_electrons', 'pi+:my_pions']``.
3915 Note that charge-conjugated ParticleLists will automatically be included.
3916 path (basf2.Path): the module is added to this path.
3917 trainingMode (``Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode``): enum identifier of the training mode.
3918 Needed to pick up the correct payload from the DB. Available choices:
3919
3920 * c_Classification=0
3921 * c_Multiclass=1
3922 * c_ECL_Classification=2
3923 * c_ECL_Multiclass=3
3924 * c_PSD_Classification=4
3925 * c_PSD_Multiclass=5
3926 * c_ECL_PSD_Classification=6
3927 * c_ECL_PSD_Multiclass=7
3928
3929 chargeIndependent (bool, ``optional``): use a BDT trained on a sample of inclusively charged particles.
3930 binaryHypoPDGCodes (tuple(int, int), ``optional``): the pdgIds of the signal, background mass hypothesis.
3931 Required only for binary PID mode.
3932 """
3933
3934 import b2bii
3935 if b2bii.isB2BII():
3936 B2ERROR("Charged PID via MVA is not available for Belle data.")
3937
3938 from ROOT import Belle2
3939
3940 TrainingMode = Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode
3941 Const = Belle2.Const
3942
3943 plSet = set(particleLists)
3944
3945 # Map the training mode enum value to the actual name of the payload in the GT.
3946 payloadNames = {
3947 TrainingMode.c_Classification:
3948 {"mode": "Classification", "detector": "ALL"},
3949 TrainingMode.c_Multiclass:
3950 {"mode": "Multiclass", "detector": "ALL"},
3951 TrainingMode.c_ECL_Classification:
3952 {"mode": "ECL_Classification", "detector": "ECL"},
3953 TrainingMode.c_ECL_Multiclass:
3954 {"mode": "ECL_Multiclass", "detector": "ECL"},
3955 TrainingMode.c_PSD_Classification:
3956 {"mode": "PSD_Classification", "detector": "ALL"},
3957 TrainingMode.c_PSD_Multiclass:
3958 {"mode": "PSD_Multiclass", "detector": "ALL"},
3959 TrainingMode.c_ECL_PSD_Classification:
3960 {"mode": "ECL_PSD_Classification", "detector": "ECL"},
3961 TrainingMode.c_ECL_PSD_Multiclass:
3962 {"mode": "ECL_PSD_Multiclass", "detector": "ECL"},
3963 }
3964
3965 if payloadNames.get(trainingMode) is None:
3966 B2FATAL("The chosen training mode integer identifier:\n", trainingMode,
3967 "\nis not supported. Please choose among the following:\n",
3968 "\n".join(f"{key}:{val.get('mode')}" for key, val in sorted(payloadNames.items())))
3969
3970 mode = payloadNames.get(trainingMode).get("mode")
3971 detector = payloadNames.get(trainingMode).get("detector")
3972
3973 payloadName = f"ChargedPidMVAWeights_{mode}"
3974
3975 # Map pdgIds of std charged particles to name identifiers,
3976 # and binary bkg identifiers.
3977 stdChargedMap = {
3978 Const.electron.getPDGCode():
3979 {"pName": "e", "pFullName": "electron", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3980 Const.muon.getPDGCode():
3981 {"pName": "mu", "pFullName": "muon", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3982 Const.pion.getPDGCode():
3983 {"pName": "pi", "pFullName": "pion", "pNameBkg": "K", "pdgIdBkg": Const.kaon.getPDGCode()},
3984 Const.kaon.getPDGCode():
3985 {"pName": "K", "pFullName": "kaon", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3986 Const.proton.getPDGCode():
3987 {"pName": "p", "pFullName": "proton", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3988 Const.deuteron.getPDGCode():
3989 {"pName": "d", "pFullName": "deuteron", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3990 }
3991
3992 if binaryHypoPDGCodes == (0, 0):
3993
3994 # MULTI-CLASS training mode.
3995 chargedpid = register_module("ChargedPidMVAMulticlass")
3996 chargedpid.set_name(f"ChargedPidMVAMulticlass_{mode}")
3997
3998 else:
3999
4000 # BINARY training mode.
4001 # In binary mode, enforce check on input S, B hypotheses compatibility.
4002
4003 binaryOpts = [(pdgIdSig, info["pdgIdBkg"]) for pdgIdSig, info in stdChargedMap.items()]
4004
4005 if binaryHypoPDGCodes not in binaryOpts:
4006 B2FATAL("No charged pid MVA was trained to separate ", binaryHypoPDGCodes[0], " vs. ", binaryHypoPDGCodes[1],
4007 ". Please choose among the following pairs:\n",
4008 "\n".join(f"{opt[0]} vs. {opt[1]}" for opt in binaryOpts))
4009
4010 decayDescriptor = Belle2.DecayDescriptor()
4011 for name in plSet:
4012 if not decayDescriptor.init(name):
4013 raise ValueError(f"Invalid particle list {name} in applyChargedPidMVA!")
4014 msg = f"Input ParticleList: {name}"
4015 pdgs = [abs(decayDescriptor.getMother().getPDGCode())]
4016 daughter_pdgs = decayDescriptor.getSelectionPDGCodes()
4017 if len(daughter_pdgs) > 0:
4018 pdgs = daughter_pdgs
4019 for idaughter, pdg in enumerate(pdgs):
4020 if abs(pdg) not in binaryHypoPDGCodes:
4021 if daughter_pdgs:
4022 msg = f"Selected daughter {idaughter} in ParticleList: {name}"
4023 B2WARNING(
4024 f"{msg} (PDG={pdg}) is neither signal ({binaryHypoPDGCodes[0]}) nor background ({binaryHypoPDGCodes[1]}).")
4025
4026 chargedpid = register_module("ChargedPidMVA")
4027 chargedpid.set_name(f"ChargedPidMVA_{binaryHypoPDGCodes[0]}_vs_{binaryHypoPDGCodes[1]}_{mode}")
4028 chargedpid.param("sigHypoPDGCode", binaryHypoPDGCodes[0])
4029 chargedpid.param("bkgHypoPDGCode", binaryHypoPDGCodes[1])
4030
4031 chargedpid.param("particleLists", list(plSet))
4032 chargedpid.param("payloadName", payloadName)
4033 chargedpid.param("chargeIndependent", chargeIndependent)
4034
4035 # Ensure the module knows whether we are using ECL-only training mode.
4036 if detector == "ECL":
4037 chargedpid.param("useECLOnlyTraining", True)
4038
4039 path.add_module(chargedpid)
4040
4041
4042def calculateTrackIsolation(
4043 decay_string,
4044 path,
4045 *detectors,
4046 reference_list_name=None,
4047 vars_for_nearest_part=[],
4048 highest_prob_mass_for_ext=True,
4049 exclude_pid_det_weights=False):
4050 """
4051 Given an input decay string, compute variables that quantify track helix-based isolation of the charged
4052 stable particles in the input decay chain.
4053
4054 Note:
4055 An "isolation score" can be defined using the distance
4056 of each particle to its closest neighbour, defined as the segment connecting the two
4057 extrapolated track helices intersection points on a given cylindrical surface.
4058 The distance variables defined in the `VariableManager` is named `minET2ETDist`,
4059 the isolation scores are named `minET2ETIsoScore`, `minET2ETIsoScoreAsWeightedAvg`.
4060
4061 The definition of distance and the number of distances that are calculated per sub-detector is based on
4062 the following recipe:
4063
4064 * **CDC**: as the segmentation is very coarse along :math:`z`,
4065 the distance is defined as the cord length on the :math:`(\\rho=R, \\phi)` plane.
4066 A total of 9 distances are calculated: the cylindrical surfaces are defined at radiuses
4067 that correspond to the positions of the 9 CDC wire superlayers: :math:`R_{i}^{\\mathrm{CDC}}~(i \\in \\{0,...,8\\})`.
4068
4069 * **TOP**: as there is no segmentation along :math:`z`,
4070 the distance is defined as the cord length on the :math:`(\\rho=R, \\phi)` plane.
4071 Only one distance at the TOP entry radius :math:`R_{0}^{\\mathrm{TOP}}` is calculated.
4072
4073 * **ARICH**: as there is no segmentation along :math:`z`,
4074 the distance is defined as the distance on the :math:`(\\rho=R, \\phi)` plane at fixed :math:`z=Z`.
4075 Only one distance at the ARICH photon detector entry coordinate :math:`Z_{0}^{\\mathrm{ARICH}}` is calculated.
4076
4077 * **ECL**: the distance is defined on the :math:`(\\rho=R, \\phi, z)` surface in the barrel,
4078 on the :math:`(\\rho, \\phi, z=Z)` surface in the endcaps.
4079 Two distances are calculated: one at the ECL entry surface :math:`R_{0}^{\\mathrm{ECL}}` (barrel),
4080 :math:`Z_{0}^{\\mathrm{ECL}}` (endcaps), and one at :math:`R_{1}^{\\mathrm{ECL}}` (barrel),
4081 :math:`Z_{1}^{\\mathrm{ECL}}` (endcaps), corresponding roughly to the mid-point
4082 of the longitudinal size of the crystals.
4083
4084 * **KLM**: the distance is defined on the :math:`(\\rho=R, \\phi, z)` surface in the barrel,
4085 on the :math:`(\\rho, \\phi, z=Z)` surface in the endcaps.
4086 Only one distance at the KLM first strip entry surface :math:`R_{0}^{\\mathrm{KLM}}` (barrel),
4087 :math:`Z_{0}^{\\mathrm{KLM}}` (endcaps) is calculated.
4088
4089 Parameters:
4090 decay_string (str): name of the input decay string with selected charged stable daughters,
4091 for example: ``Lambda0:merged -> ^p+ ^pi-``.
4092 Alternatively, it can be a particle list for charged stable particles
4093 as defined in ``Const::chargedStableSet``, for example: ``mu+:all``.
4094 The charge-conjugate particle list will be also processed automatically.
4095 path (basf2.Path): path to which module(s) will be added.
4096 *detectors: detectors for which track isolation variables will be calculated.
4097 Choose among: ``{'CDC', 'TOP', 'ARICH', 'ECL', 'KLM'}``.
4098 reference_list_name (Optional[str]): name of the input charged stable particle list for the reference tracks.
4099 By default, the ``:all`` ParticleList of the same type
4100 of the selected particle in ``decay_string`` is used.
4101 The charge-conjugate particle list will be also processed automatically.
4102 vars_for_nearest_part (Optional[list(str)]): a list of variables to calculate for the nearest particle in the reference
4103 list at each detector surface. It uses the metavariable `minET2ETDistVar`.
4104 If unset, only the distances to the nearest neighbour
4105 per detector are calculated.
4106 highest_prob_mass_for_hex (Optional[bool]): if this option is set to True (default), the helix extrapolation
4107 for the particles will use the track fit result for the most
4108 probable mass hypothesis, namely, the one that gives the highest
4109 chi2Prob of the fit. Otherwise, it uses the mass hypothesis that
4110 corresponds to the particle lists PDG.
4111 exclude_pid_det_weights (Optional[bool]): if this option is set to False (default), the isolation score
4112 calculation will take into account the weight that each detector has on the PID
4113 for the particle species of interest.
4114
4115 Returns:
4116 dict(int, list(str)): a dictionary mapping the PDG of each reference particle list to its isolation variables.
4117
4118 """
4119
4120 import pdg
4121 from ROOT import Belle2, TDatabasePDG
4122
4123 decayDescriptor = Belle2.DecayDescriptor()
4124 if not decayDescriptor.init(decay_string):
4125 B2FATAL(f"Invalid particle list {decay_string} in calculateTrackIsolation!")
4126 no_reference_list_name = not reference_list_name
4127
4128 det_and_layers = {
4129 "CDC": list(range(9)),
4130 "TOP": [0],
4131 "ARICH": [0],
4132 "ECL": [0, 1],
4133 "KLM": [0],
4134 }
4135 if any(d not in det_and_layers for d in detectors):
4136 B2FATAL(
4137 "Your input detector list: ",
4138 detectors,
4139 " contains an invalid choice. Please select among: ",
4140 list(
4141 det_and_layers.keys()))
4142
4143 # The module allows only one daughter to be selected at a time,
4144 # that's why here we preprocess the input decay string.
4145 select_symbol = '^'
4146 processed_decay_strings = []
4147 if select_symbol in decay_string:
4148 splitted_ds = decay_string.split(select_symbol)
4149 for i in range(decay_string.count(select_symbol)):
4150 tmp = list(splitted_ds)
4151 tmp.insert(i+1, select_symbol)
4152 processed_decay_strings += [''.join(tmp)]
4153 else:
4154 processed_decay_strings += [decay_string]
4155
4156 reference_lists_to_vars = {}
4157
4158 for processed_dec in processed_decay_strings:
4159 if no_reference_list_name:
4160 decayDescriptor.init(processed_dec)
4161 selected_daughter_pdgs = decayDescriptor.getSelectionPDGCodes()
4162 if len(selected_daughter_pdgs) > 0:
4163 reference_list_name = f'{TDatabasePDG.Instance().GetParticle(abs(selected_daughter_pdgs[-1])).GetName()}:all'
4164 else:
4165 reference_list_name = f'{processed_dec.split(":")[0]}:all'
4166
4167 ref_pdg = pdg.from_name(reference_list_name.split(":")[0])
4168
4169 trackiso = path.add_module("TrackIsoCalculator",
4170 decayString=processed_dec,
4171 detectorNames=list(detectors),
4172 particleListReference=reference_list_name,
4173 useHighestProbMassForExt=highest_prob_mass_for_ext,
4174 excludePIDDetWeights=exclude_pid_det_weights)
4175 trackiso.set_name(f"TrackIsoCalculator_{'_'.join(detectors)}_{processed_dec}_VS_{reference_list_name}")
4176
4177 # Metavariables for the distances to the closest reference tracks at each detector surface.
4178 # Always calculate them.
4179 # Ensure the flag for the mass hypothesis of the fit is set.
4180 trackiso_vars = [
4181 f"minET2ETDist({d}, {d_layer}, {reference_list_name}, {int(highest_prob_mass_for_ext)})"
4182 for d in detectors for d_layer in det_and_layers[d]]
4183 # Track isolation score.
4184 trackiso_vars += [
4185 f"minET2ETIsoScore({reference_list_name}, {int(highest_prob_mass_for_ext)}, {', '.join(detectors)})",
4186 f"minET2ETIsoScoreAsWeightedAvg({reference_list_name}, {int(highest_prob_mass_for_ext)}, {', '.join(detectors)})",
4187 ]
4188 # Optionally, calculate the input variables for the nearest neighbour in the reference list.
4189 if vars_for_nearest_part:
4190 trackiso_vars.extend(
4191 [
4192 f"minET2ETDistVar({d}, {d_layer}, {reference_list_name}, {v})"
4193 for d in detectors for d_layer in det_and_layers[d] for v in vars_for_nearest_part
4194 ])
4195 trackiso_vars.sort()
4196
4197 reference_lists_to_vars[ref_pdg] = trackiso_vars
4198
4199 return reference_lists_to_vars
4200
4201
4202def calculateDistance(list_name, decay_string, mode='vertextrack', path=None):
4203 """
4204 Calculates distance between two vertices, distance of closest approach between a vertex and a track,\
4205 distance of closest approach between a vertex and btube. For track, this calculation ignores track curvature,\
4206 it's negligible for small distances.The user should use extraInfo(CalculatedDistance)\
4207 to get it. A full example steering file is at analysis/tests/test_DistanceCalculator.py
4208
4209 Example:
4210 .. code-block:: python
4211
4212 from modularAnalysis import calculateDistance
4213 calculateDistance('list_name', 'decay_string', "mode", path=user_path)
4214
4215 @param list_name name of the input ParticleList
4216 @param decay_string select particles between the distance of closest approach will be calculated
4217 @param mode Specifies how the distance is calculated
4218 vertextrack: calculate the distance of closest approach between a track and a\
4219 vertex, taking the first candidate as vertex, default
4220 trackvertex: calculate the distance of closest approach between a track and a\
4221 vertex, taking the first candidate as track
4222 2tracks: calculates the distance of closest approach between two tracks
4223 2vertices: calculates the distance between two vertices
4224 vertexbtube: calculates the distance of closest approach between a vertex and btube
4225 trackbtube: calculates the distance of closest approach between a track and btube
4226 @param path modules are added to this path
4227
4228 """
4229
4230 dist_mod = register_module('DistanceCalculator')
4231
4232 dist_mod.set_name('DistanceCalculator_' + list_name)
4233 dist_mod.param('listName', list_name)
4234 dist_mod.param('decayString', decay_string)
4235 dist_mod.param('mode', mode)
4236 path.add_module(dist_mod)
4237
4238
4239def addInclusiveDstarReconstruction(decayString, slowPionCut, DstarCut, path):
4240 """
4241 Adds the InclusiveDstarReconstruction module to the given path.
4242 This module creates a D* particle list by estimating the D* four momenta
4243 from slow pions, specified by a given cut. The D* energy is approximated
4244 as E(D*) = m(D*)/(m(D*) - m(D)) * E(pi). The absolute value of the D*
4245 momentum is calculated using the D* PDG mass and the direction is collinear
4246 to the slow pion direction. The charge of the given pion list has to be consistent
4247 with the D* charge
4248
4249 @param decayString Decay string, must be of form ``D* -> pi``
4250 @param slowPionCut Cut applied to the input pion list to identify slow pions
4251 @param DstarCut Cut applied to the output D* list
4252 @param path the module is added to this path
4253 """
4254
4255 incl_dstar = register_module("InclusiveDstarReconstruction")
4256 incl_dstar.param("decayString", decayString)
4257 incl_dstar.param("slowPionCut", slowPionCut)
4258 incl_dstar.param("DstarCut", DstarCut)
4259 path.add_module(incl_dstar)
4260
4261
4262def scaleError(outputListName, inputListName,
4263 scaleFactors=[1.149631, 1.085547, 1.151704, 1.096434, 1.086659],
4264 scaleFactorsNoPXD=[1.149631, 1.085547, 1.151704, 1.096434, 1.086659],
4265 d0Resolution=[0.00115328, 0.00134704],
4266 z0Resolution=[0.00124327, 0.0013272],
4267 d0MomThr=0.500000,
4268 z0MomThr=0.500000,
4269 path=None):
4270 """
4271 This module creates a new charged particle list.
4272 The helix errors of the new particles are scaled by constant factors.
4273 Two sets of five scale factors are defined for tracks with and without a PXD hit.
4274 The scale factors are in order of (d0, phi0, omega, z0, tanlambda).
4275 For tracks with a PXD hit, in order to avoid severe underestimation of d0 and z0 errors,
4276 lower limits (best resolution) can be set in a momentum-dependent form.
4277 This module is supposed to be used only for TDCPV analysis and for low-momentum (0-3 GeV/c) tracks in BBbar events.
4278 Details will be documented in a Belle II note, BELLE2-NOTE-PH-2021-038.
4279
4280 @param inputListName Name of input charged particle list to be scaled
4281 @param outputListName Name of output charged particle list with scaled error
4282 @param scaleFactors List of five constants to be multiplied to each of helix errors (for tracks with a PXD hit)
4283 @param scaleFactorsNoPXD List of five constants to be multiplied to each of helix errors (for tracks without a PXD hit)
4284 @param d0Resolution List of two parameters, (a [cm], b [cm/(GeV/c)]),
4285 defining d0 best resolution as sqrt{ a**2 + (b / (p*beta*sinTheta**1.5))**2 }
4286 @param z0Resolution List of two parameters, (a [cm], b [cm/(GeV/c)]),
4287 defining z0 best resolution as sqrt{ a**2 + (b / (p*beta*sinTheta**2.5))**2 }
4288 @param d0MomThr d0 best resolution is kept constant below this momentum
4289 @param z0MomThr z0 best resolution is kept constant below this momentum
4290
4291 """
4292
4293 scale_error = register_module("HelixErrorScaler")
4294 scale_error.set_name('ScaleError_' + inputListName)
4295 scale_error.param('inputListName', inputListName)
4296 scale_error.param('outputListName', outputListName)
4297 scale_error.param('scaleFactors_PXD', scaleFactors)
4298 scale_error.param('scaleFactors_noPXD', scaleFactorsNoPXD)
4299 scale_error.param('d0ResolutionParameters', d0Resolution)
4300 scale_error.param('z0ResolutionParameters', z0Resolution)
4301 scale_error.param('d0MomentumThreshold', d0MomThr)
4302 scale_error.param('z0MomentumThreshold', z0MomThr)
4303 path.add_module(scale_error)
4304
4305
4306def estimateAndAttachTrackFitResult(inputListName, path=None):
4307 """
4308 Create a TrackFitResult from the momentum of the Particle assuming it originates from the IP and make a relation between them.
4309 The covariance, detector hit information, and fit-related information (pValue, NDF) are assigned meaningless values. The input
4310 Particles must not have already Track or TrackFitResult and thus are supposed to be composite particles, recoil, dummy
4311 particles, and so on.
4312
4313
4314 .. warning:: Since the source type is not overwritten as Track, not all track-related variables are guaranteed to be available.
4315
4316
4317 @param inputListName Name of input ParticleList
4318 """
4319
4320 estimator = register_module("TrackFitResultEstimator")
4321 estimator.set_name("trackFitResultEstimator_" + inputListName)
4322 estimator.param("inputListName", inputListName)
4323 path.add_module(estimator)
4324
4325
4326def correctEnergyBias(inputListNames, tableName, path=None):
4327 """
4328 Scale energy of the particles according to the scaling factor.
4329 If the particle list contains composite particles, the energy of the daughters are scaled.
4330 Subsequently, the energy of the mother particle is updated as well.
4331
4332 Parameters:
4333 inputListNames (list(str)): input particle list names
4334 tableName : stored in localdb and created using ParticleWeightingLookUpCreator
4335 path (basf2.Path): module is added to this path
4336 """
4337
4338 import b2bii
4339 if b2bii.isB2BII():
4340 B2ERROR("The energy bias cannot be corrected with this tool for Belle data.")
4341
4342 correctenergybias = register_module('EnergyBiasCorrection')
4343 correctenergybias.param('particleLists', inputListNames)
4344 correctenergybias.param('tableName', tableName)
4345 path.add_module(correctenergybias)
4346
4347
4348def twoBodyISRPhotonCorrector(outputListName, inputListName, massiveParticle, path=None):
4349 """
4350 Sets photon kinematics to corrected values in two body decays with an ISR photon
4351 and a massive particle. The original photon kinematics are kept in the input
4352 particleList and can be accessed using the originalParticle() metavariable on the
4353 new list.
4354
4355 @param ouputListName new ParticleList filled with copied Particles
4356 @param inputListName input ParticleList with original Particles
4357 @param massiveParticle name or PDG code of massive particle participating in the two
4358 body decay with the ISR photon
4359 @param path modules are added to this path
4360 """
4361
4362 # set the corrected energy of the photon in a new list
4363 photon_energy_correction = register_module('TwoBodyISRPhotonCorrector')
4364 photon_energy_correction.set_name('TwoBodyISRPhotonCorrector_' + outputListName)
4365 photon_energy_correction.param('outputGammaList', outputListName)
4366 photon_energy_correction.param('inputGammaList', inputListName)
4367
4368 # prepare PDG code of massive particle
4369 if isinstance(massiveParticle, int):
4370 photon_energy_correction.param('massiveParticlePDGCode', massiveParticle)
4371 else:
4372 from ROOT import Belle2
4373 decayDescriptor = Belle2.DecayDescriptor()
4374 if not decayDescriptor.init(massiveParticle):
4375 raise ValueError("TwoBodyISRPhotonCorrector: value of massiveParticle must be" +
4376 " an int or valid decay string.")
4377 pdgCode = decayDescriptor.getMother().getPDGCode()
4378 photon_energy_correction.param('massiveParticlePDGCode', pdgCode)
4379
4380 path.add_module(photon_energy_correction)
4381
4382
4383def addPhotonEfficiencyRatioVariables(inputListNames, tableName, path=None):
4384 """
4385 Add photon Data/MC detection efficiency ratio weights to the specified particle list
4386
4387 Parameters:
4388 inputListNames (list(str)): input particle list names
4389 tableName : taken from database with appropriate name
4390 path (basf2.Path): module is added to this path
4391 """
4392
4393 import b2bii
4394 if b2bii.isB2BII():
4395 B2ERROR("For Belle data the photon data/MC detection efficiency ratio is not available with this tool.")
4396
4397 photon_efficiency_correction = register_module('PhotonEfficiencySystematics')
4398 photon_efficiency_correction.param('particleLists', inputListNames)
4399 photon_efficiency_correction.param('tableName', tableName)
4400 path.add_module(photon_efficiency_correction)
4401
4402
4403def addPi0VetoEfficiencySystematics(particleList, decayString, tableName, threshold, mode='standard', suffix='', path=None):
4404 """
4405 Add pi0 veto Data/MC efficiency ratio weights to the specified particle list
4406
4407 @param particleList the input ParticleList
4408 @param decayString specify hard photon to be performed pi0 veto (e.g. 'B+:sig -> rho+:sig ^gamma:hard')
4409 @param tableName table name corresponding to payload version (e.g. 'Pi0VetoEfficiencySystematics_Mar2022')
4410 @param threshold pi0 veto threshold (0.10, 0.11, ..., 0.99)
4411 @param mode choose one mode (same as writePi0EtaVeto) out of 'standard', 'tight', 'cluster' and 'both'
4412 @param suffix optional suffix to be appended to the usual extraInfo name
4413 @param path the module is added to this path
4414
4415 The following extraInfo are available related with the given particleList:
4416
4417 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_ratio : weight of Data/MC for the veto efficiency
4418 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_uncertainty_stat : the statistical uncertainty of the weight
4419 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_uncertainty_sys : the systematic uncertainty of the weight
4420 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_uncertainty_total : the total uncertainty of the weight
4421 * Pi0VetoEfficiencySystematics_{mode}{suffix}_threshold : threshold of the pi0 veto
4422 """
4423
4424 import b2bii
4425 if b2bii.isB2BII():
4426 B2ERROR("For Belle data the pi0 veto data/MC efficiency ratio weights are not available via this tool.")
4427
4428 pi0veto_efficiency_correction = register_module('Pi0VetoEfficiencySystematics')
4429 pi0veto_efficiency_correction.param('particleLists', particleList)
4430 pi0veto_efficiency_correction.param('decayString', decayString)
4431 pi0veto_efficiency_correction.param('tableName', tableName)
4432 pi0veto_efficiency_correction.param('threshold', threshold)
4433 pi0veto_efficiency_correction.param('mode', mode)
4434 pi0veto_efficiency_correction.param('suffix', suffix)
4435 path.add_module(pi0veto_efficiency_correction)
4436
4437
4438def getAnalysisGlobaltag(timeout=180) -> str:
4439 """
4440 Returns a string containing the name of the latest and recommended analysis globaltag.
4441
4442 Parameters:
4443 timeout: Seconds to wait for b2conditionsdb-recommend
4444 """
4445
4446 import b2bii
4447 if b2bii.isB2BII():
4448 B2ERROR("The getAnalysisGlobaltag() function cannot be used for Belle data.")
4449
4450 # b2conditionsdb-recommend relies on a different repository, so it's better to protect
4451 # this function against potential failures of check_output.
4452 try:
4453 tags = subprocess.check_output(
4454 ['b2conditionsdb-recommend', '--oneline'],
4455 timeout=timeout
4456 ).decode('UTF-8').rstrip().split(' ')
4457 analysis_tag = ''
4458 for tag in tags:
4459 if tag.startswith('analysis_tools'):
4460 analysis_tag = tag
4461 return analysis_tag
4462 # In case of issues with git, b2conditionsdb-recommend may take too much time.
4463 except subprocess.TimeoutExpired as te:
4464 B2FATAL(f'A {te} exception was raised during the call of getAnalysisGlobaltag(). '
4465 'The function took too much time to retrieve the requested information '
4466 'from the versioning repository.\n'
4467 'Please try to re-run your job. In case of persistent failures, there may '
4468 'be issues with the DESY collaborative services, so please contact the experts.')
4469 except subprocess.CalledProcessError as ce:
4470 B2FATAL(f'A {ce} exception was raised during the call of getAnalysisGlobaltag(). '
4471 'Please try to re-run your job. In case of persistent failures, please contact '
4472 'the experts.')
4473
4474
4475def getAnalysisGlobaltagB2BII() -> str:
4476 """
4477 Get recommended global tag for B2BII analysis.
4478 """
4479
4480 import b2bii
4481 if not b2bii.isB2BII():
4482 B2ERROR('The getAnalysisGlobaltagB2BII() function cannot be used for Belle II data.')
4483 from versioning import recommended_b2bii_analysis_global_tag
4484 return recommended_b2bii_analysis_global_tag()
4485
4486
4487def getECLKLID(particleList: str, variable='ECLKLID', path=None):
4488 """
4489 The function calculates the PID value for Klongs that are constructed from ECL cluster.
4490
4491 @param particleList the input ParticleList
4492 @param variable the variable name for Klong ID
4493 @param path modules are added to this path
4494 """
4495
4496 import b2bii
4497
4498 if b2bii.isB2BII():
4499 B2ERROR("The ECL variables based Klong Identification is only available for Belle II data.")
4500
4501 from variables import variables
4502 path.add_module('MVAExpert', listNames=particleList, extraInfoName='ECLKLID', identifier='ECLKLID')
4503
4504 variables.addAlias(variable, 'conditionalVariableSelector(isFromECL and PDG==130, extraInfo(ECLKLID), constant(NaN))')
4505
4506
4507def getNbarIDMVA(particleList: str, path=None):
4508 """
4509 This function can give a score to predict if it is a anti-n0.
4510 It is not used to predict n0.
4511 Currently, this can be used only for ECL cluster.
4512 output will be stored in extraInfo(nbarID); -1 means MVA invalid
4513
4514 @param particleList The input ParticleList name or a decay string which contains a full mother particle list name.
4515 Only one selected daughter is supported.
4516 @param path modules are added to this path
4517 """
4518 import b2bii
4519 from ROOT import Belle2
4520
4521 if b2bii.isB2BII():
4522 B2ERROR("The MVA-based anti-neutron PID is only available for Belle II data.")
4523
4524 from variables import variables
4525
4526 variables.addAlias('V1', 'clusterHasPulseShapeDiscrimination')
4527 variables.addAlias('V2', 'clusterE')
4528 variables.addAlias('V3', 'clusterLAT')
4529 variables.addAlias('V4', 'clusterE1E9')
4530 variables.addAlias('V5', 'clusterE9E21')
4531 variables.addAlias('V6', 'clusterZernikeMVA')
4532 variables.addAlias('V7', 'clusterAbsZernikeMoment40')
4533 variables.addAlias('V8', 'clusterAbsZernikeMoment51')
4534
4535 variables.addAlias(
4536 'nbarIDValid',
4537 'passesCut(V1 == 1 and V2 >= 0 and V3 >= 0 and V4 >= 0 and V5 >= 0 and V6 >= 0 and V7 >= 0 and V8 >= 0)')
4538 variables.addAlias('nbarIDmod', 'conditionalVariableSelector(nbarIDValid == 1, extraInfo(nbarIDFromMVA), constant(-1.0))')
4539
4540 path.add_module('MVAExpert', listNames=particleList, extraInfoName='nbarIDFromMVA', identifier='db_nbarIDECL')
4541 decayDescriptor = Belle2.DecayDescriptor()
4542 if not decayDescriptor.init(particleList):
4543 raise ValueError(f"Provided decay string is invalid: {particleList}")
4544 if decayDescriptor.getNDaughters() == 0:
4545 variablesToExtraInfo(particleList, {'nbarIDmod': 'nbarID'}, option=2, path=path)
4546 else:
4547 listname = decayDescriptor.getMother().getFullName()
4548 variablesToDaughterExtraInfo(listname, particleList, {'nbarIDmod': 'nbarID'}, option=2, path=path)
4549
4550
4551def reconstructDecayWithNeutralHadron(decayString, cut, allowGamma=False, allowAnyParticleSource=False, path=None, **kwargs):
4552 r"""
4553 Reconstructs decay with a long-lived neutral hadron e.g.
4554 :math:`B^0 \to J/\psi K_L^0`,
4555 :math:`B^0 \to p \bar{n} D^*(2010)^-`.
4556
4557 The calculation is done with IP constraint and mother mass constraint.
4558
4559 The decay string passed in must satisfy the following rules:
4560
4561 - The neutral hadron must be **selected** in the decay string with the
4562 caret (``^``) e.g. ``B0:sig -> J/psi:sig ^K_L0:sig``. (Note the caret
4563 next to the neutral hadron.)
4564 - There can only be **one neutral hadron in a decay**.
4565 - The neutral hadron has to be a direct daughter of its mother.
4566
4567 .. note:: This function forwards its arguments to `reconstructDecay`,
4568 so please check the documentation of `reconstructDecay` for all
4569 possible arguments.
4570
4571 @param decayString A decay string following the mentioned rules
4572 @param cut Cut to apply to the particle list
4573 @param allowGamma Whether allow the selected particle to be ``gamma``
4574 @param allowAnyParticleSource Whether allow the selected particle to be from any source.
4575 Should only be used when studying control sample.
4576 @param path The path to put in the module
4577 """
4578
4579 reconstructDecay(decayString, cut, path=path, **kwargs)
4580 module = register_module('NeutralHadron4MomentumCalculator')
4581 module.set_name('NeutralHadron4MomentumCalculator_' + decayString)
4582 module.param('decayString', decayString)
4583 module.param('allowGamma', allowGamma)
4584 module.param('allowAnyParticleSource', allowAnyParticleSource)
4585 path.add_module(module)
4586
4587
4588def updateMassHypothesis(particleList, pdg, writeOut=False, path=None):
4589 """
4590 Module to update the mass hypothesis of a given input particle list with the chosen PDG.
4591 A new particle list is created with updated mass hypothesis.
4592 The allowed mass hypotheses for both input and output are electrons, muons, pions, kaons and protons.
4593
4594 .. note:
4595 The new particle list is named after the input one, with the additional suffix ``_converted_from_OLDHYPOTHESIS``,
4596 e.g. ``e+:all`` converted to muons becomes ``mu+:all_converted_from_e``.
4597
4598 @param particleList The input particle list name
4599 @param pdg The PDG code for the new mass hypothesis, in [11, 13, 211, 321, 2212]
4600 @param writeOut Whether `RootOutput` module should save the new particle list
4601 @param path Modules are added to this path
4602 """
4603 mass_updater = register_module("ParticleMassHypothesesUpdater")
4604 mass_updater.set_name("ParticleMassHypothesesUpdater_" + particleList + "_to_" + str(pdg))
4605 mass_updater.param("particleList", particleList)
4606 mass_updater.param("writeOut", writeOut)
4607 mass_updater.param("pdgCode", pdg)
4608 path.add_module(mass_updater)
4609
4610
4611func_requiring_analysisGT = [
4612 correctTrackEnergy, scaleTrackMomenta, smearTrackMomenta, oldwritePi0EtaVeto, writePi0EtaVeto, lowEnergyPi0Identification,
4613 getBeamBackgroundProbability, getFakePhotonProbability, tagCurlTracks, applyChargedPidMVA, correctEnergyBias,
4614 addPhotonEfficiencyRatioVariables, addPi0VetoEfficiencySystematics, getNbarIDMVA, getECLKLID]
4615for _ in func_requiring_analysisGT:
4616 _.__doc__ += "\n .. note:: This function (optionally) requires a payload stored in the analysis GlobalTag. "\
4617 "Please append or prepend the latest one from `getAnalysisGlobaltag` or `getAnalysisGlobaltagB2BII`.\n"
4618
4619
4620if __name__ == '__main__':
4621 from basf2.utils import pretty_print_module
4622 pretty_print_module(__name__, "modularAnalysis")
4623
def setB2BII()
Definition: b2bii.py:21
def isB2BII()
Definition: b2bii.py:14
tuple parse(str cut, verbose=False)
Definition: b2parser.py:975
This class provides a set of constants for the framework.
Definition: Const.h:34
The DecayDescriptor stores information about a decay tree or parts of a decay tree.
Describe one component of the Geometry.
Magnetic field map.
Definition: MagneticField.h:32
static DBStore & Instance()
Instance of a singleton DBStore.
Definition: DBStore.cc:26
def add_mdst_output(path, mc=True, filename='mdst.root', additionalBranches=[], dataDescription=None)
Definition: mdst.py:38
def from_name(name)
Definition: pdg.py:63
def add_udst_output(path, filename, particleLists=None, additionalBranches=None, dataDescription=None, mc=True)
Definition: udst.py:27