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