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