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