Belle II Software development
modularAnalysis.py
1#!/usr/bin/env python3
2
3
10
11"""
12This module defines wrapper functions around the analysis modules.
13"""
14
15import b2bii
16from basf2 import register_module, create_path
17from basf2 import B2INFO, B2WARNING, B2ERROR, B2FATAL
18import basf2
19import subprocess
20
21
22def setAnalysisConfigParams(configParametersAndValues, path):
23 """
24 Sets analysis configuration parameters.
25
26 These are:
27
28 - 'tupleStyle': 'Default' (default) or 'Laconic'
29
30 - defines the style of the branch name in the ntuple
31
32 - 'mcMatchingVersion': Specifies what version of mc matching algorithm is going to be used:
33
34 - 'Belle' - analysis of Belle MC
35 - 'BelleII' (default) - all other cases
36
37 @param configParametersAndValues dictionary of parameters and their values of the form {param1: value, param2: value, ...)
38 @param modules are added to this path
39 """
40
41 conf = register_module('AnalysisConfiguration')
42
43 allParameters = ['tupleStyle', 'mcMatchingVersion']
44
45 keys = configParametersAndValues.keys()
46 for key in keys:
47 if key not in allParameters:
48 allParametersString = ', '.join(allParameters)
49 B2ERROR('Invalid analysis configuration parameter: ' + key + '.\n'
50 'Please use one of the following: ' + allParametersString)
51
52 for param in allParameters:
53 if param in configParametersAndValues:
54 conf.param(param, configParametersAndValues.get(param))
55
56 path.add_module(conf)
57
58
59def inputMdst(filename, path, environmentType='default', skipNEvents=0, entrySequence=None, *, parentLevel=0, **kwargs):
60 """
61 Loads the specified :ref:`mDST <mdst>` (or :ref:`uDST <analysis_udstoutput>`) file with the RootInput module.
62
63 The correct environment (e.g. magnetic field settings) is determined from
64 ``environmentType``. Options are either: 'default' (for Belle II MC and
65 data: falls back to database), 'Belle': for analysis of converted Belle 1
66 data and MC.
67
68 Parameters:
69 filename (str): the name of the file to be loaded
70 path (basf2.Path): modules are added to this path
71 environmentType (str): type of the environment to be loaded (either 'default' or 'Belle')
72 skipNEvents (int): N events of the input file are skipped
73 entrySequence (str): The number sequences (e.g. 23:42,101) defining the entries which are processed.
74 parentLevel (int): Number of generations of parent files (files used as input when creating a file) to be read
75 """
76
77 # FIXME remove this check of "filename" at release-07
78 if filename == 'default':
79 B2FATAL("""
80We have simplified the arguments to inputMdst! If you are running on Belle II
81data or MC, you don't have to use "default" any more.
82Please replace:
83 inputMdst("default", "/your/input/file.root", path=mypath)
84With:
85 inputMdst("/your/input/file.root", path=mypath)
86 """)
87 elif filename == "Belle":
88 B2FATAL("""
89We have reordered the arguments to inputMdst! If you are running on Belle 1
90data or MC, you need to specify the 'environmentType'.
91Please replace:
92 inputMdst("Belle", "/your/input/file.root", path=mypath)
93With:
94 inputMdst("/your/input/file.root", path=mypath, environmentType='Belle')
95 """)
96 elif filename in [f"MC{i}" for i in range(5, 10)]:
97 B2FATAL(f"We no longer support the MC version {filename}. Sorry.")
98
99 if entrySequence is not None:
100 entrySequence = [entrySequence]
101
102 inputMdstList([filename], path, environmentType, skipNEvents, entrySequence, parentLevel=parentLevel, **kwargs)
103
104
105def inputMdstList(
106 filelist,
107 path,
108 environmentType='default',
109 skipNEvents=0,
110 entrySequences=None,
111 *,
112 parentLevel=0,
113 useB2BIIDBCache=True):
114 """
115 Loads the specified list of :ref:`mDST <mdst>` (or :ref:`uDST <analysis_udstoutput>`) files with the RootInput module.
116
117 The correct environment (e.g. magnetic field settings) is determined from
118 ``environmentType``. Options are either: 'default' (for Belle II MC and
119 data: falls back to database), 'Belle': for analysis of converted Belle 1
120 data and MC.
121
122 Parameters:
123 filelist (list(str)): the filename list of files to be loaded
124 path (basf2.Path): modules are added to this path
125 environmentType (str): type of the environment to be loaded (either 'default' or 'Belle')
126 skipNEvents (int): N events of the input files are skipped
127 entrySequences (list(str)): The number sequences (e.g. 23:42,101) defining
128 the entries which are processed for each inputFileName.
129 parentLevel (int): Number of generations of parent files (files used as input when creating a file) to be read
130 useB2BIIDBCache (bool): Loading of local KEKCC database (only to be deactivated in very special cases)
131 """
132
133 # FIXME remove this check of "filename" at release-07
134 if filelist == 'default':
135 B2FATAL("""
136We have simplified the arguments to inputMdstList! If you are running on
137Belle II data or MC, you don't have to use "default" any more.
138Please replace:
139 inputMdstList("default", list_of_your_files, path=mypath)
140With:
141 inputMdstList(list_of_your_files, path=mypath)
142 """)
143 elif filelist == "Belle":
144 B2FATAL("""
145We have reordered the arguments to inputMdstList! If you are running on
146Belle 1 data or MC, you need to specify the 'environmentType'.
147Please replace:
148 inputMdstList("Belle", list_of_your_files, path=mypath)
149With:
150 inputMdstList(list_of_your_files, path=mypath, environmentType='Belle')
151 """)
152 elif filelist in [f"MC{i}" for i in range(5, 10)]:
153 B2FATAL(f"We no longer support the MC version {filelist}. Sorry.")
154
155 roinput = register_module('RootInput')
156 roinput.param('inputFileNames', filelist)
157 roinput.param('skipNEvents', skipNEvents)
158 if entrySequences is not None:
159 roinput.param('entrySequences', entrySequences)
160 roinput.param('parentLevel', parentLevel)
161
162 path.add_module(roinput)
163 path.add_module('ProgressBar')
164
165 if environmentType == 'Belle':
166 # Belle 1 constant magnetic field
167 # -------------------------------
168 # n.b. slightly unfortunate syntax: the MagneticField is a member of the
169 # Belle2 namespace but will be set to the Belle 1 values
170 from ROOT import Belle2 # reduced scope of potentially-misbehaving import
171 from ROOT.Math import XYZVector
172 belle1_field = Belle2.MagneticField()
173 belle1_field.addComponent(Belle2.MagneticFieldComponentConstant(XYZVector(0, 0, 1.5 * Belle2.Unit.T)))
174 Belle2.DBStore.Instance().addConstantOverride("MagneticField", belle1_field, False)
175 # also set the MC matching for Belle 1
176 setAnalysisConfigParams({'mcMatchingVersion': 'Belle'}, path)
178 if useB2BIIDBCache:
179 basf2.conditions.metadata_providers = ["/sw/belle/b2bii/database/conditions/b2bii.sqlite"]
180 basf2.conditions.payload_locations = ["/sw/belle/b2bii/database/conditions/"]
181
182
183def outputMdst(filename, path):
184 """
185 Saves mDST (mini-Data Summary Tables) to the output root file.
186
187 .. warning::
188
189 This function is kept for backward-compatibility.
190 Better to use `mdst.add_mdst_output` directly.
191
192 """
193
194 import mdst
195 mdst.add_mdst_output(path, mc=True, filename=filename)
196
197
198def outputUdst(filename, particleLists=None, includeArrays=None, path=None, dataDescription=None):
199 """
200 Save uDST (user-defined Data Summary Tables) = MDST + Particles + ParticleLists
201 The charge-conjugate lists of those given in particleLists are also stored.
202 Additional Store Arrays and Relations to be stored can be specified via includeArrays
203 list argument.
204
205 Note:
206 This does not reduce the amount of Particle objects saved,
207 see `udst.add_skimmed_udst_output` for a function that does.
208
209 """
210
211 import udst
213 path=path, filename=filename, particleLists=particleLists,
214 additionalBranches=includeArrays, dataDescription=dataDescription)
215
216
217def outputIndex(filename, path, includeArrays=None, keepParents=False, mc=True):
218 """
219 Write out all particle lists as an index file to be reprocessed using parentLevel flag.
220 Additional branches necessary for file to be read are automatically included.
221 Additional Store Arrays and Relations to be stored can be specified via includeArrays
222 list argument.
223
224 @param str filename the name of the output index file
225 @param str path modules are added to this path
226 @param list(str) includeArrays: datastore arrays/objects to write to the output
227 file in addition to particle lists and related information
228 @param bool keepParents whether the parents of the input event will be saved as the parents of the same event
229 in the output index file. Useful if you are only adding more information to another index file
230 @param bool mc whether the input data is MC or not
231 """
232
233 if includeArrays is None:
234 includeArrays = []
235
236 # Module to mark all branches to not be saved except particle lists
237 onlyPLists = register_module('OnlyWriteOutParticleLists')
238 path.add_module(onlyPLists)
239
240 # Set up list of all other branches we need to make index file complete
241 partBranches = [
242 'Particles',
243 'ParticlesToMCParticles',
244 'ParticlesToPIDLikelihoods',
245 'ParticleExtraInfoMap',
246 'EventExtraInfo'
247 ]
248 branches = ['EventMetaData']
249 persistentBranches = ['FileMetaData']
250 if mc:
251 branches += []
252 # persistentBranches += ['BackgroundInfos']
253 branches += partBranches
254 branches += includeArrays
255
256 r1 = register_module('RootOutput')
257 r1.param('outputFileName', filename)
258 r1.param('additionalBranchNames', branches)
259 r1.param('branchNamesPersistent', persistentBranches)
260 r1.param('keepParents', keepParents)
261 path.add_module(r1)
262
263
264def setupEventInfo(noEvents, path):
265 """
266 Prepare to generate events. This function sets up the EventInfoSetter.
267 You should call this before adding a generator from generators.
268 The experiment and run numbers are set to 0 (run independent generic MC in phase 3).
269 https://confluence.desy.de/display/BI/Experiment+numbering
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):
3116 """
3117 Give pi0/eta probability for hard photon.
3118
3119 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.
3120
3121 The current default weight files are optimised using MC12.
3122
3123 The input variables of the mva training are:
3124
3125 * M: pi0/eta candidates Invariant mass
3126 * daughter(1,E): soft photon energy in lab frame
3127 * daughter(1,clusterTheta): soft photon ECL cluster's polar angle
3128 * daughter(1,minC2TDist): soft photon distance from eclCluster to nearest point on nearest Helix at the ECL cylindrical radius
3129 * daughter(1,clusterZernikeMVA): soft photon output of MVA using Zernike moments of the cluster
3130 * daughter(1,clusterNHits): soft photon total crystal weights sum(w_i) with w_i<=1
3131 * daughter(1,clusterE9E21): soft photon ratio of energies in inner 3x3 crystals and 5x5 crystals without corners
3132 * cosHelicityAngleMomentum: pi0/eta candidates cosHelicityAngleMomentum
3133
3134 The following strings are available for mode:
3135
3136 * standard: loose energy cut and no clusterNHits cut are applied to soft photon
3137 * tight: tight energy cut and no clusterNHits cut are applied to soft photon
3138 * cluster: loose energy cut and clusterNHits cut are applied to soft photon
3139 * both: tight energy cut and clusterNHits cut are applied to soft photon
3140
3141 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
3142 `pi0Prob`/`etaProb`. Otherwise, it is available as '{Pi0, Eta}ProbOrigin', '{Pi0, Eta}ProbTightEnergyThreshold', '{Pi0,
3143 Eta}ProbLargeClusterSize', or '{Pi0, Eta}ProbTightEnergyThresholdAndLargeClusterSize'} for the four modes described above, with
3144 the chosen suffix appended.
3145
3146 NOTE:
3147 Please don't use following ParticleList names elsewhere:
3148
3149 ``gamma:HardPhoton``,
3150 ``gamma:Pi0Soft + ListName + '_' + particleList.replace(':', '_')``,
3151 ``gamma:EtaSoft + ListName + '_' + particleList.replace(':', '_')``,
3152 ``pi0:EtaVeto + ListName``,
3153 ``eta:EtaVeto + ListName``
3154
3155 @param particleList the input ParticleList
3156 @param decayString specify Particle to be added to the ParticleList
3157 @param mode choose one mode out of 'standard', 'tight', 'cluster' and 'both'
3158 @param selection selection criteria that Particle needs meet in order for for_each ROE path to continue
3159 @param path modules are added to this path
3160 @param suffix optional suffix to be appended to the usual extraInfo name
3161 @param hardParticle particle name which is used to calculate the pi0/eta probability (default is gamma)
3162 @param pi0PayloadNameOverride specify the payload name of pi0 veto only if one wants to use non-default one. (default is None)
3163 @param pi0SoftPhotonCutOverride specify the soft photon selection criteria of pi0 veto only if one wants to use non-default one.
3164 (default is None)
3165 @param etaPayloadNameOverride specify the payload name of eta veto only if one wants to use non-default one. (default is None)
3166 @param etaSoftPhotonCutOverride specify the soft photon selection criteria of eta veto only if one wants to use non-default one.
3167 (default is None)
3168 """
3169
3170 import b2bii
3171 if b2bii.isB2BII():
3172 B2ERROR("The pi0 / eta veto is not suitable for Belle analyses.")
3173
3174 renameSuffix = False
3175
3176 for module in path.modules():
3177 if module.type() == "SubEvent" and not renameSuffix:
3178 for subpath in [p.values for p in module.available_params() if p.name == "path"]:
3179 if renameSuffix:
3180 break
3181 for submodule in subpath.modules():
3182 if f'{hardParticle}:HardPhoton{suffix}' in submodule.name():
3183 suffix += '_0'
3184 B2WARNING("Same extension already used in writePi0EtaVeto, append '_0'")
3185 renameSuffix = True
3186 break
3187
3188 roe_path = create_path()
3189 deadEndPath = create_path()
3190 signalSideParticleFilter(particleList, selection, roe_path, deadEndPath)
3191 fillSignalSideParticleList(f'{hardParticle}:HardPhoton{suffix}', decayString, path=roe_path)
3192
3193 dictListName = {'standard': 'Origin',
3194 'tight': 'TightEnergyThreshold',
3195 'cluster': 'LargeClusterSize',
3196 'both': 'TightEnrgyThresholdAndLargeClusterSize'}
3197
3198 dictPi0EnergyCut = {'standard': '[[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]]',
3199 'tight': '[[clusterReg==1 and E>0.03] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.04]]',
3200 'cluster': '[[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]]',
3201 'both': '[[clusterReg==1 and E>0.03] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.04]]'}
3202
3203 dictEtaEnergyCut = {'standard': '[[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]]',
3204 'tight': '[[clusterReg==1 and E>0.06] or [clusterReg==2 and E>0.06] or [clusterReg==3 and E>0.06]]',
3205 'cluster': '[[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]]',
3206 'both': '[[clusterReg==1 and E>0.06] or [clusterReg==2 and E>0.06] or [clusterReg==3 and E>0.06]]'}
3207
3208 dictNHitsCut = {'standard': 'clusterNHits >= 0',
3209 'tight': 'clusterNHits >= 0',
3210 'cluster': 'clusterNHits >= 2',
3211 'both': 'clusterNHits >= 2'}
3212
3213 dictPi0PayloadName = {'standard': 'Pi0VetoIdentifierStandard',
3214 'tight': 'Pi0VetoIdentifierWithHigherEnergyThreshold',
3215 'cluster': 'Pi0VetoIdentifierWithLargerClusterSize',
3216 'both': 'Pi0VetoIdentifierWithHigherEnergyThresholdAndLargerClusterSize'}
3217
3218 dictEtaPayloadName = {'standard': 'EtaVetoIdentifierStandard',
3219 'tight': 'EtaVetoIdentifierWithHigherEnergyThreshold',
3220 'cluster': 'EtaVetoIdentifierWithLargerClusterSize',
3221 'both': 'EtaVetoIdentifierWithHigherEnergyThresholdAndLargerClusterSize'}
3222
3223 dictPi0ExtraInfoName = {'standard': 'Pi0ProbOrigin',
3224 'tight': 'Pi0ProbTightEnergyThreshold',
3225 'cluster': 'Pi0ProbLargeClusterSize',
3226 'both': 'Pi0ProbTightEnergyThresholdAndLargeClusterSize'}
3227
3228 dictEtaExtraInfoName = {'standard': 'EtaProbOrigin',
3229 'tight': 'EtaProbTightEnergyThreshold',
3230 'cluster': 'EtaProbLargeClusterSize',
3231 'both': 'EtaProbTightEnergyThresholdAndLargeClusterSize'}
3232
3233 ListName = dictListName[mode]
3234 Pi0EnergyCut = dictPi0EnergyCut[mode]
3235 EtaEnergyCut = dictEtaEnergyCut[mode]
3236 TimingCut = 'abs(clusterTiming)<clusterErrorTiming'
3237 NHitsCut = dictNHitsCut[mode]
3238 Pi0PayloadName = dictPi0PayloadName[mode]
3239 EtaPayloadName = dictEtaPayloadName[mode]
3240 Pi0ExtraInfoName = dictPi0ExtraInfoName[mode]
3241 EtaExtraInfoName = dictEtaExtraInfoName[mode]
3242
3243 # pi0 veto
3244 if pi0PayloadNameOverride is not None:
3245 Pi0PayloadName = pi0PayloadNameOverride
3246 if pi0SoftPhotonCutOverride is None:
3247 Pi0SoftPhotonCut = Pi0EnergyCut + ' and ' + NHitsCut
3248 import b2bii
3249 if not b2bii.isB2BII():
3250 # timing cut is only valid for Belle II but not for B2BII
3251 Pi0SoftPhotonCut += ' and ' + TimingCut
3252 else:
3253 Pi0SoftPhotonCut = pi0SoftPhotonCutOverride
3254
3255 # define the particleList name for soft photon
3256 pi0soft = f'gamma:Pi0Soft{suffix}' + ListName + '_' + particleList.replace(':', '_')
3257 # fill the particleList for soft photon with energy, timing and clusterNHits cuts
3258 fillParticleList(pi0soft, Pi0SoftPhotonCut, path=roe_path)
3259 # reconstruct pi0
3260 reconstructDecay('pi0:Pi0Veto' + ListName + f' -> {hardParticle}:HardPhoton{suffix} ' + pi0soft, '',
3261 allowChargeViolation=True, path=roe_path)
3262 # MVA training is conducted.
3263 roe_path.add_module('MVAExpert', listNames=['pi0:Pi0Veto' + ListName],
3264 extraInfoName=Pi0ExtraInfoName, identifier=Pi0PayloadName)
3265 # Pick up only one pi0/eta candidate with the highest pi0/eta probability.
3266 rankByHighest('pi0:Pi0Veto' + ListName, 'extraInfo(' + Pi0ExtraInfoName + ')', numBest=1, path=roe_path)
3267 # 'extraInfo(Pi0Veto)' is labeled 'Pi0_Prob'
3268 variableToSignalSideExtraInfo('pi0:Pi0Veto' + ListName,
3269 {'extraInfo(' + Pi0ExtraInfoName + ')': Pi0ExtraInfoName + suffix}, path=roe_path)
3270
3271 # eta veto
3272 if etaPayloadNameOverride is not None:
3273 EtaPayloadName = etaPayloadNameOverride
3274 if etaSoftPhotonCutOverride is None:
3275 EtaSoftPhotonCut = EtaEnergyCut + ' and ' + NHitsCut
3276 import b2bii
3277 if not b2bii.isB2BII():
3278 # timing cut is only valid for Belle II but not for B2BII
3279 EtaSoftPhotonCut += ' and ' + TimingCut
3280 else:
3281 EtaSoftPhotonCut = etaSoftPhotonCutOverride
3282
3283 etasoft = f'gamma:EtaSoft{suffix}' + ListName + '_' + particleList.replace(':', '_')
3284 fillParticleList(etasoft, EtaSoftPhotonCut, path=roe_path)
3285 reconstructDecay('eta:EtaVeto' + ListName + f' -> {hardParticle}:HardPhoton{suffix} ' + etasoft, '',
3286 allowChargeViolation=True, path=roe_path)
3287 roe_path.add_module('MVAExpert', listNames=['eta:EtaVeto' + ListName],
3288 extraInfoName=EtaExtraInfoName, identifier=EtaPayloadName)
3289 rankByHighest('eta:EtaVeto' + ListName, 'extraInfo(' + EtaExtraInfoName + ')', numBest=1, path=roe_path)
3290 variableToSignalSideExtraInfo('eta:EtaVeto' + ListName,
3291 {'extraInfo(' + EtaExtraInfoName + ')': EtaExtraInfoName + suffix}, path=roe_path)
3292
3293 path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
3294
3295
3296def lowEnergyPi0Identification(pi0List, gammaList, payloadNameSuffix,
3297 path=None):
3298 """
3299 Calculate low-energy pi0 identification.
3300 The result is stored as ExtraInfo ``lowEnergyPi0Identification`` for
3301 the list pi0List.
3302
3303 Parameters:
3304 pi0List (str): Pi0 list.
3305
3306 gammaList (str): Gamma list. First, an energy cut E > 0.2 is applied to the photons from this list.
3307 Then, all possible combinations with a pi0 daughter photon are formed except the one
3308 corresponding to the reconstructed pi0.
3309 The maximum low-energy pi0 veto value is calculated for such photon pairs
3310 and used as one of the input variables for the identification classifier.
3311
3312 payloadNameSuffix (str): Payload name suffix. The weight payloads are stored in the analysis global
3313 tag and have the following names:\n
3314 * ``'LowEnergyPi0Veto' + payloadNameSuffix``
3315 * ``'LowEnergyPi0Identification' + payloadNameSuffix``\n
3316 The possible suffixes are:\n
3317 * ``'Belle1'`` for Belle data.
3318 * ``'Belle2Release5'`` for Belle II release 5 data (MC14, proc12, buckets 16 - 25).
3319 * ``'Belle2Release6'`` for Belle II release 6 data (MC15, proc13, buckets 26 - 36).
3320
3321 path (basf2.Path): Module path.
3322 """
3323
3324 # Select photons with higher energy for formation of veto combinations.
3325 gammaListVeto = f'{gammaList}_pi0veto'
3326 cutAndCopyList(gammaListVeto, gammaList, 'E > 0.2', path=path)
3327 import b2bii
3328 payload_name = 'LowEnergyPi0Veto' + payloadNameSuffix
3329 path.add_module('LowEnergyPi0VetoExpert', identifier=payload_name,
3330 VetoPi0Daughters=True, GammaListName=gammaListVeto,
3331 Pi0ListName=pi0List, Belle1=b2bii.isB2BII())
3332 payload_name = 'LowEnergyPi0Identification' + payloadNameSuffix
3333 path.add_module('LowEnergyPi0IdentificationExpert',
3334 identifier=payload_name, Pi0ListName=pi0List,
3335 Belle1=b2bii.isB2BII())
3336
3337
3338def getNeutralHadronGeomMatches(
3339 particleLists,
3340 addKL=True,
3341 addNeutrons=False,
3342 efficiencyCorrectionKl=0.83,
3343 efficiencyCorrectionNeutrons=1.0,
3344 path=None):
3345 """
3346 For an ECL-based list, assign the mcdistanceKL and mcdistanceNeutron variables that correspond
3347 to the distance to the closest MC KL and neutron, respectively.
3348 @param particleLists the input ParticleLists, must be ECL-based lists (e.g. photons)
3349 @param addKL (default True) add distance to MC KL
3350 @param addNeutrons (default False) add distance to MC neutrons
3351 @param efficiencyCorrectionKl (default 0.83) apply overall efficiency correction
3352 @param efficiencyCorrectionNeutrons (default 1.0) apply overall efficiency correction
3353 @param path modules are added to this path
3354 """
3355 from ROOT import Belle2
3356 Const = Belle2.Const
3357
3358 if addKL:
3359 path.add_module(
3360 "NeutralHadronMatcher",
3361 particleLists=particleLists,
3362 mcPDGcode=Const.Klong.getPDGCode(),
3363 efficiencyCorrection=efficiencyCorrectionKl)
3364 if addNeutrons:
3365 path.add_module(
3366 "NeutralHadronMatcher",
3367 particleLists=particleLists,
3368 mcPDGcode=Const.neutron.getPDGCode(),
3369 efficiencyCorrection=efficiencyCorrectionNeutrons)
3370
3371
3372def getBeamBackgroundProbability(particleList, weight, path=None):
3373 """
3374 Assign a probability to each ECL cluster as being signal like (1) compared to beam background like (0)
3375 @param particleList the input ParticleList, must be a photon list
3376 @param weight type of weight file to use
3377 @param path modules are added to this path
3378 """
3379
3380 import b2bii
3381 if b2bii.isB2BII() and weight != "Belle":
3382 B2WARNING("weight type must be 'Belle' for b2bii.")
3383
3384 path.add_module('MVAExpert',
3385 listNames=particleList,
3386 extraInfoName='beamBackgroundSuppression',
3387 identifier=f'BeamBackgroundMVA_{weight}')
3388
3389
3390def getFakePhotonProbability(particleList, weight, path=None):
3391 """
3392 Assign a probability to each ECL cluster as being signal like (1) compared to fake photon like (0)
3393 @param particleList the input ParticleList, must be a photon list
3394 @param weight type of weight file to use
3395 @param path modules are added to this path
3396 """
3397
3398 import b2bii
3399 if b2bii.isB2BII() and weight != "Belle":
3400 B2WARNING("weight type must be 'Belle' for b2bii.")
3401
3402 path.add_module('MVAExpert',
3403 listNames=particleList,
3404 extraInfoName='fakePhotonSuppression',
3405 identifier=f'FakePhotonMVA_{weight}')
3406
3407
3408def buildEventKinematics(inputListNames=None, default_cleanup=True, custom_cuts=None,
3409 chargedPIDPriors=None, fillWithMostLikely=False, path=None):
3410 """
3411 Calculates the global kinematics of the event (visible energy, missing momentum, missing mass...)
3412 using ParticleLists provided. If no ParticleList is provided, default ParticleLists are used
3413 (all track and all hits in ECL without associated track).
3414
3415 The visible energy missing values are
3416 stored in a EventKinematics dataobject.
3417
3418 @param inputListNames list of ParticleLists used to calculate the global event kinematics.
3419 If the list is empty, default ParticleLists pi+:evtkin and gamma:evtkin are filled.
3420 @param fillWithMostLikely if True, the module uses the most likely particle mass hypothesis for charged particles
3421 according to the PID likelihood and the option inputListNames will be ignored.
3422 @param chargedPIDPriors The prior PID fractions, that are used to regulate
3423 amount of certain charged particle species, should be a list of
3424 six floats if not None. The order of particle types is
3425 the following: [e-, mu-, pi-, K-, p+, d+]
3426 @param default_cleanup if True and either inputListNames empty or fillWithMostLikely True, default clean up cuts are applied
3427 @param custom_cuts tuple of selection cut strings of form (trackCuts, photonCuts), default is None,
3428 which would result in a standard predefined selection cuts
3429 @param path modules are added to this path
3430 """
3431
3432 if inputListNames is None:
3433 inputListNames = []
3434 trackCuts = 'pt > 0.1'
3435 trackCuts += ' and thetaInCDCAcceptance'
3436 trackCuts += ' and abs(dz) < 3'
3437 trackCuts += ' and dr < 0.5'
3438
3439 gammaCuts = 'E > 0.05'
3440 gammaCuts += ' and thetaInCDCAcceptance'
3441 if not b2bii.isB2BII():
3442 gammaCuts += ' and abs(clusterTiming) < 200'
3443 if (custom_cuts is not None):
3444 trackCuts, gammaCuts = custom_cuts
3445
3446 if fillWithMostLikely:
3447 from stdCharged import stdMostLikely
3448 stdMostLikely(chargedPIDPriors, '_evtkin', path=path)
3449 inputListNames = [f'{ptype}:mostlikely_evtkin' for ptype in ['K+', 'p+', 'e+', 'mu+', 'pi+']]
3450 if b2bii.isB2BII():
3451 copyList('gamma:evtkin', 'gamma:mdst', path=path)
3452 else:
3453 fillParticleList('gamma:evtkin', '', path=path)
3454 inputListNames += ['gamma:evtkin']
3455 if default_cleanup:
3456 B2INFO("Using default cleanup in EventKinematics module.")
3457 for ptype in ['K+', 'p+', 'e+', 'mu+', 'pi+']:
3458 applyCuts(f'{ptype}:mostlikely_evtkin', trackCuts, path=path)
3459 applyCuts('gamma:evtkin', gammaCuts, path=path)
3460 else:
3461 B2INFO("No cleanup in EventKinematics module.")
3462 if not inputListNames:
3463 B2INFO("Creating particle lists pi+:evtkin and gamma:evtkin to get the global kinematics of the event.")
3464 fillParticleList('pi+:evtkin', '', path=path)
3465 if b2bii.isB2BII():
3466 copyList('gamma:evtkin', 'gamma:mdst', path=path)
3467 else:
3468 fillParticleList('gamma:evtkin', '', path=path)
3469 particleLists = ['pi+:evtkin', 'gamma:evtkin']
3470 if default_cleanup:
3471 if (custom_cuts is not None):
3472 B2INFO("Using default cleanup in EventKinematics module.")
3473 applyCuts('pi+:evtkin', trackCuts, path=path)
3474 applyCuts('gamma:evtkin', gammaCuts, path=path)
3475 else:
3476 B2INFO("No cleanup in EventKinematics module.")
3477 else:
3478 particleLists = inputListNames
3479
3480 eventKinematicsModule = register_module('EventKinematics')
3481 eventKinematicsModule.set_name('EventKinematics_reco')
3482 eventKinematicsModule.param('particleLists', particleLists)
3483 path.add_module(eventKinematicsModule)
3484
3485
3486def buildEventKinematicsFromMC(inputListNames=None, selectionCut='', path=None):
3487 """
3488 Calculates the global kinematics of the event (visible energy, missing momentum, missing mass...)
3489 using generated particles. If no ParticleList is provided, default generated ParticleLists are used.
3490
3491 @param inputListNames list of ParticleLists used to calculate the global event kinematics.
3492 If the list is empty, default ParticleLists are filled.
3493 @param selectionCut optional selection cuts
3494 @param path Path to append the eventKinematics module to.
3495 """
3496
3497 if inputListNames is None:
3498 inputListNames = []
3499 if (len(inputListNames) == 0):
3500 # Type of particles to use for EventKinematics
3501 # K_S0 and Lambda0 are added here because some of them have interacted
3502 # with the detector material
3503 types = ['gamma', 'e+', 'mu+', 'pi+', 'K+', 'p+',
3504 'K_S0', 'Lambda0']
3505 for t in types:
3506 fillParticleListFromMC(f"{t}:evtkin_default_gen", 'mcPrimary > 0 and nDaughters == 0',
3507 True, True, path=path)
3508 if (selectionCut != ''):
3509 applyCuts(f"{t}:evtkin_default_gen", selectionCut, path=path)
3510 inputListNames += [f"{t}:evtkin_default_gen"]
3511
3512 eventKinematicsModule = register_module('EventKinematics')
3513 eventKinematicsModule.set_name('EventKinematics_gen')
3514 eventKinematicsModule.param('particleLists', inputListNames)
3515 eventKinematicsModule.param('usingMC', True)
3516 path.add_module(eventKinematicsModule)
3517
3518
3519def buildEventShape(inputListNames=None,
3520 default_cleanup=True,
3521 custom_cuts=None,
3522 allMoments=False,
3523 cleoCones=True,
3524 collisionAxis=True,
3525 foxWolfram=True,
3526 harmonicMoments=True,
3527 jets=True,
3528 sphericity=True,
3529 thrust=True,
3530 checkForDuplicates=False,
3531 path=None):
3532 """
3533 Calculates the event-level shape quantities (thrust, sphericity, Fox-Wolfram moments...)
3534 using the particles in the lists provided by the user. If no particle list is provided,
3535 the function will internally create a list of good tracks and a list of good photons
3536 with (optionally) minimal quality cuts.
3537
3538
3539 The results of the calculation are then stored into the EventShapeContainer dataobject,
3540 and are accessible using the variables of the EventShape group.
3541
3542 The user can switch the calculation of certain quantities on or off to save computing
3543 time. By default the calculation of the high-order moments (5-8) is turned off.
3544 Switching off an option will make the corresponding variables not available.
3545
3546 Warning:
3547 The user can provide as many particle lists
3548 as needed, using also combined particles, but the function will always assume that
3549 the lists are independent.
3550 If the lists provided by the user contain several times the same track (either with
3551 different mass hypothesis, or once as an independent particle and once as daughter of a
3552 combined particle) the results won't be reliable.
3553 A basic check for duplicates is available setting the checkForDuplicate flags.
3554
3555
3556 @param inputListNames List of ParticleLists used to calculate the
3557 event shape variables. If the list is empty the default
3558 particleLists pi+:evtshape and gamma:evtshape are filled.
3559 @param default_cleanup If True, applies standard cuts on pt and cosTheta when
3560 defining the internal lists. This option is ignored if the
3561 particleLists are provided by the user.
3562 @param custom_cuts tuple of selection cut strings of form (trackCuts, photonCuts), default is None,
3563 which would result in a standard predefined selection cuts
3564 @param path Path to append the eventShape modules to.
3565 @param thrust Enables the calculation of thrust-related quantities (CLEO
3566 cones, Harmonic moments, jets).
3567 @param collisionAxis Enables the calculation of the quantities related to the
3568 collision axis .
3569 @param foxWolfram Enables the calculation of the Fox-Wolfram moments.
3570 @param harmonicMoments Enables the calculation of the Harmonic moments with respect
3571 to both the thrust axis and, if collisionAxis = True, the collision axis.
3572 @param allMoments If True, calculates also the FW and harmonic moments from order
3573 5 to 8 instead of the low-order ones only.
3574 @param cleoCones Enables the calculation of the CLEO cones with respect to both the thrust
3575 axis and, if collisionAxis = True, the collision axis.
3576 @param jets Enables the calculation of the hemisphere momenta and masses.
3577 Requires thrust = True.
3578 @param sphericity Enables the calculation of the sphericity-related quantities.
3579 @param checkForDuplicates Perform a check for duplicate particles before adding them. Regardless of the value of this option,
3580 it is recommended to consider sanitizing the lists you are passing to the function.
3581
3582 """
3583
3584 if inputListNames is None:
3585 inputListNames = []
3586 trackCuts = 'pt > 0.1'
3587 trackCuts += ' and thetaInCDCAcceptance'
3588 trackCuts += ' and abs(dz) < 3.0'
3589 trackCuts += ' and dr < 0.5'
3590
3591 gammaCuts = 'E > 0.05'
3592 gammaCuts += ' and thetaInCDCAcceptance'
3593 if not b2bii.isB2BII():
3594 gammaCuts += ' and abs(clusterTiming) < 200'
3595 if (custom_cuts is not None):
3596 trackCuts, gammaCuts = custom_cuts
3597
3598 if not inputListNames:
3599 B2INFO("Creating particle lists pi+:evtshape and gamma:evtshape to get the event shape variables.")
3600 fillParticleList('pi+:evtshape', '', path=path)
3601 if b2bii.isB2BII():
3602 copyList('gamma:evtshape', 'gamma:mdst', path=path)
3603 else:
3604 fillParticleList(
3605 'gamma:evtshape',
3606 '',
3607 path=path)
3608 particleLists = ['pi+:evtshape', 'gamma:evtshape']
3609
3610 if default_cleanup:
3611 if (custom_cuts is not None):
3612 B2INFO("Applying standard cuts")
3613 applyCuts('pi+:evtshape', trackCuts, path=path)
3614
3615 applyCuts('gamma:evtshape', gammaCuts, path=path)
3616 else:
3617 B2WARNING("Creating the default lists with no cleanup.")
3618 else:
3619 particleLists = inputListNames
3620
3621 eventShapeModule = register_module('EventShapeCalculator')
3622 eventShapeModule.set_name('EventShape')
3623 eventShapeModule.param('particleListNames', particleLists)
3624 eventShapeModule.param('enableAllMoments', allMoments)
3625 eventShapeModule.param('enableCleoCones', cleoCones)
3626 eventShapeModule.param('enableCollisionAxis', collisionAxis)
3627 eventShapeModule.param('enableFoxWolfram', foxWolfram)
3628 eventShapeModule.param('enableJets', jets)
3629 eventShapeModule.param('enableHarmonicMoments', harmonicMoments)
3630 eventShapeModule.param('enableSphericity', sphericity)
3631 eventShapeModule.param('enableThrust', thrust)
3632 eventShapeModule.param('checkForDuplicates', checkForDuplicates)
3633
3634 path.add_module(eventShapeModule)
3635
3636
3637def labelTauPairMC(printDecayInfo=False, path=None, TauolaBelle=False, mapping_minus=None, mapping_plus=None):
3638 """
3639 Search tau leptons into the MC information of the event. If confirms it's a generated tau pair decay,
3640 labels the decay generated of the positive and negative leptons using the ID of KKMC tau decay table.
3641
3642 @param printDecayInfo: If true, prints ID and prong of each tau lepton in the event.
3643 @param path: module is added to this path
3644 @param TauolaBelle: if False, TauDecayMode is set. If True, TauDecayMarker is set.
3645 @param mapping_minus: if None, the map is the default one, else the path for the map is given by the user for tau-
3646 @param mapping_plus: if None, the map is the default one, else the path for the map is given by the user for tau+
3647 """
3648
3649 from basf2 import find_file
3650 if not TauolaBelle:
3651
3652 if printDecayInfo:
3653 m_printmode = 'all'
3654 else:
3655 m_printmode = 'default'
3656
3657 if mapping_minus is None:
3658 mp_file_minus = find_file('data/analysis/modules/TauDecayMode/map_tauminus.txt')
3659 else:
3660 mp_file_minus = mapping_minus
3661
3662 if mapping_plus is None:
3663 mp_file_plus = find_file('data/analysis/modules/TauDecayMode/map_tauplus.txt')
3664 else:
3665 mp_file_plus = mapping_plus
3666
3667 path.add_module('TauDecayMode', printmode=m_printmode, file_minus=mp_file_minus, file_plus=mp_file_plus)
3668
3669 else:
3670 tauDecayMarker = register_module('TauDecayMarker')
3671 tauDecayMarker.set_name('TauDecayMarker_')
3672
3673 path.add_module(tauDecayMarker, printDecayInfo=printDecayInfo)
3674
3675
3676def tagCurlTracks(particleLists,
3677 mcTruth=False,
3678 responseCut=-1.0,
3679 selectorType='cut',
3680 ptCut=0.5,
3681 expert_train=False,
3682 expert_filename="",
3683 path=None):
3684 """
3685 Warning:
3686 The cut selector is not calibrated with Belle II data and should not be used without extensive study.
3687
3688 Identifies curl tracks and tags them with extraInfo(isCurl=1) for later removal.
3689 For Belle data with a `b2bii` analysis the available cut based selection is described in `BN1079`_.
3690
3691 .. _BN1079: https://belle.kek.jp/secured/belle_note/gn1079/bn1079.pdf
3692
3693
3694 The module loops over all particles in a given list with a transverse momentum below the pre-selection **ptCut**
3695 and assigns them to bundles based on the response of the chosen **selector** and the required minimum response set by the
3696 **responseCut**. Once all particles are assigned they are ranked by 25dr^2+dz^2. All but the lowest are tagged
3697 with extraInfo(isCurl=1) to allow for later removal by cutting the list or removing these from ROE as
3698 applicable.
3699
3700
3701 @param particleLists: list of particle lists to check for curls.
3702 @param mcTruth: bool flag to additionally assign particles with extraInfo(isTruthCurl) and
3703 extraInfo(truthBundleSize). To calculate these particles are assigned to bundles by their
3704 genParticleIndex then ranked and tagged as normal.
3705 @param responseCut: float min classifier response that considers two tracks to come from the same particle.
3706 If set to ``-1`` a cut value optimised to maximise the accuracy on a BBbar sample is used.
3707 Note 'cut' selector is binary 0/1.
3708 @param selectorType: string name of selector to use. The available options are 'cut' and 'mva'.
3709 It is strongly recommended to used the 'mva' selection. The 'cut' selection
3710 is based on BN1079 and is only calibrated for Belle data.
3711
3712 @param ptCut: Pre-selection cut on transverse momentum. Only tracks below that are considered as curler candidates.
3713
3714 @param expert_train: flag to set training mode if selector has a training mode (mva).
3715 @param expert_filename: set file name of produced training ntuple (mva).
3716 @param path: module is added to this path.
3717 """
3718
3719 import b2bii
3720 belle = b2bii.isB2BII()
3721
3722 if (not isinstance(particleLists, list)):
3723 particleLists = [particleLists] # in case user inputs a particle list as string
3724
3725 curlTagger = register_module('CurlTagger')
3726 curlTagger.set_name('CurlTagger_')
3727 curlTagger.param('particleLists', particleLists)
3728 curlTagger.param('belle', belle)
3729 curlTagger.param('mcTruth', mcTruth)
3730 curlTagger.param('responseCut', responseCut)
3731 if abs(responseCut + 1) < 1e-9:
3732 curlTagger.param('usePayloadCut', True)
3733 else:
3734 curlTagger.param('usePayloadCut', False)
3735
3736 curlTagger.param('selectorType', selectorType)
3737 curlTagger.param('ptCut', ptCut)
3738 curlTagger.param('train', expert_train)
3739 curlTagger.param('trainFilename', expert_filename)
3740
3741 path.add_module(curlTagger)
3742
3743
3744def applyChargedPidMVA(particleLists, path, trainingMode, chargeIndependent=False, binaryHypoPDGCodes=(0, 0)):
3745 """
3746 Use an MVA to perform particle identification for charged stable particles, using the `ChargedPidMVA` module.
3747
3748 The module decorates Particle objects in the input ParticleList(s) with variables
3749 containing the appropriate MVA score, which can be used to select candidates by placing a cut on it.
3750
3751 Note:
3752 The MVA algorithm used is a gradient boosted decision tree (**TMVA 4.3.0**, **ROOT 6.20/04**).
3753
3754 The module can perform either 'binary' PID between input S, B particle mass hypotheses according to the following scheme:
3755
3756 * e (11) vs. pi (211)
3757 * mu (13) vs. pi (211)
3758 * pi (211) vs. K (321)
3759 * K (321) vs. pi (211)
3760
3761 , or 'global' PID, namely "one-vs-others" separation. The latter exploits an MVA algorithm trained in multi-class mode,
3762 and it's the default behaviour. Currently, the multi-class training separates the following standard charged hypotheses:
3763
3764 - e (11), mu (13), pi (211), K (321)
3765
3766 Warning:
3767 In order to run the `ChargedPidMVA` and ensure the most up-to-date MVA training weights are applied,
3768 it is necessary to append the latest analysis global tag (GT) to the steering script.
3769
3770 Parameters:
3771 particleLists (list(str)): the input list of DecayStrings, where each selected (^) daughter should correspond to a
3772 standard charged ParticleList, e.g. ``['Lambda0:sig -> ^p+ ^pi-', 'J/psi:sig -> ^mu+ ^mu-']``.
3773 One can also directly pass a list of standard charged ParticleLists,
3774 e.g. ``['e+:my_electrons', 'pi+:my_pions']``.
3775 Note that charge-conjugated ParticleLists will automatically be included.
3776 path (basf2.Path): the module is added to this path.
3777 trainingMode (``Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode``): enum identifier of the training mode.
3778 Needed to pick up the correct payload from the DB. Available choices:
3779
3780 * c_Classification=0
3781 * c_Multiclass=1
3782 * c_ECL_Classification=2
3783 * c_ECL_Multiclass=3
3784 * c_PSD_Classification=4
3785 * c_PSD_Multiclass=5
3786 * c_ECL_PSD_Classification=6
3787 * c_ECL_PSD_Multiclass=7
3788
3789 chargeIndependent (bool, ``optional``): use a BDT trained on a sample of inclusively charged particles.
3790 binaryHypoPDGCodes (tuple(int, int), ``optional``): the pdgIds of the signal, background mass hypothesis.
3791 Required only for binary PID mode.
3792 """
3793
3794 import b2bii
3795 if b2bii.isB2BII():
3796 B2ERROR("Charged PID via MVA is not available for Belle data.")
3797
3798 from ROOT import Belle2
3799
3800 TrainingMode = Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode
3801 Const = Belle2.Const
3802
3803 plSet = set(particleLists)
3804
3805 # Map the training mode enum value to the actual name of the payload in the GT.
3806 payloadNames = {
3807 TrainingMode.c_Classification:
3808 {"mode": "Classification", "detector": "ALL"},
3809 TrainingMode.c_Multiclass:
3810 {"mode": "Multiclass", "detector": "ALL"},
3811 TrainingMode.c_ECL_Classification:
3812 {"mode": "ECL_Classification", "detector": "ECL"},
3813 TrainingMode.c_ECL_Multiclass:
3814 {"mode": "ECL_Multiclass", "detector": "ECL"},
3815 TrainingMode.c_PSD_Classification:
3816 {"mode": "PSD_Classification", "detector": "ALL"},
3817 TrainingMode.c_PSD_Multiclass:
3818 {"mode": "PSD_Multiclass", "detector": "ALL"},
3819 TrainingMode.c_ECL_PSD_Classification:
3820 {"mode": "ECL_PSD_Classification", "detector": "ECL"},
3821 TrainingMode.c_ECL_PSD_Multiclass:
3822 {"mode": "ECL_PSD_Multiclass", "detector": "ECL"},
3823 }
3824
3825 if payloadNames.get(trainingMode) is None:
3826 B2FATAL("The chosen training mode integer identifier:\n", trainingMode,
3827 "\nis not supported. Please choose among the following:\n",
3828 "\n".join(f"{key}:{val.get('mode')}" for key, val in sorted(payloadNames.items())))
3829
3830 mode = payloadNames.get(trainingMode).get("mode")
3831 detector = payloadNames.get(trainingMode).get("detector")
3832
3833 payloadName = f"ChargedPidMVAWeights_{mode}"
3834
3835 # Map pdgIds of std charged particles to name identifiers,
3836 # and binary bkg identifiers.
3837 stdChargedMap = {
3838 Const.electron.getPDGCode():
3839 {"pName": "e", "pFullName": "electron", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3840 Const.muon.getPDGCode():
3841 {"pName": "mu", "pFullName": "muon", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3842 Const.pion.getPDGCode():
3843 {"pName": "pi", "pFullName": "pion", "pNameBkg": "K", "pdgIdBkg": Const.kaon.getPDGCode()},
3844 Const.kaon.getPDGCode():
3845 {"pName": "K", "pFullName": "kaon", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3846 Const.proton.getPDGCode():
3847 {"pName": "p", "pFullName": "proton", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3848 Const.deuteron.getPDGCode():
3849 {"pName": "d", "pFullName": "deuteron", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3850 }
3851
3852 if binaryHypoPDGCodes == (0, 0):
3853
3854 # MULTI-CLASS training mode.
3855 chargedpid = register_module("ChargedPidMVAMulticlass")
3856 chargedpid.set_name(f"ChargedPidMVAMulticlass_{mode}")
3857
3858 else:
3859
3860 # BINARY training mode.
3861 # In binary mode, enforce check on input S, B hypotheses compatibility.
3862
3863 binaryOpts = [(pdgIdSig, info["pdgIdBkg"]) for pdgIdSig, info in stdChargedMap.items()]
3864
3865 if binaryHypoPDGCodes not in binaryOpts:
3866 B2FATAL("No charged pid MVA was trained to separate ", binaryHypoPDGCodes[0], " vs. ", binaryHypoPDGCodes[1],
3867 ". Please choose among the following pairs:\n",
3868 "\n".join(f"{opt[0]} vs. {opt[1]}" for opt in binaryOpts))
3869
3870 decayDescriptor = Belle2.DecayDescriptor()
3871 for name in plSet:
3872 if not decayDescriptor.init(name):
3873 raise ValueError(f"Invalid particle list {name} in applyChargedPidMVA!")
3874 msg = f"Input ParticleList: {name}"
3875 pdgs = [abs(decayDescriptor.getMother().getPDGCode())]
3876 daughter_pdgs = decayDescriptor.getSelectionPDGCodes()
3877 if len(daughter_pdgs) > 0:
3878 pdgs = daughter_pdgs
3879 for idaughter, pdg in enumerate(pdgs):
3880 if abs(pdg) not in binaryHypoPDGCodes:
3881 if daughter_pdgs:
3882 msg = f"Selected daughter {idaughter} in ParticleList: {name}"
3883 B2WARNING(
3884 f"{msg} (PDG={pdg}) is neither signal ({binaryHypoPDGCodes[0]}) nor background ({binaryHypoPDGCodes[1]}).")
3885
3886 chargedpid = register_module("ChargedPidMVA")
3887 chargedpid.set_name(f"ChargedPidMVA_{binaryHypoPDGCodes[0]}_vs_{binaryHypoPDGCodes[1]}_{mode}")
3888 chargedpid.param("sigHypoPDGCode", binaryHypoPDGCodes[0])
3889 chargedpid.param("bkgHypoPDGCode", binaryHypoPDGCodes[1])
3890
3891 chargedpid.param("particleLists", list(plSet))
3892 chargedpid.param("payloadName", payloadName)
3893 chargedpid.param("chargeIndependent", chargeIndependent)
3894
3895 # Ensure the module knows whether we are using ECL-only training mode.
3896 if detector == "ECL":
3897 chargedpid.param("useECLOnlyTraining", True)
3898
3899 path.add_module(chargedpid)
3900
3901
3902def calculateTrackIsolation(
3903 decay_string,
3904 path,
3905 *detectors,
3906 reference_list_name=None,
3907 vars_for_nearest_part=[],
3908 highest_prob_mass_for_ext=True,
3909 exclude_pid_det_weights=False):
3910 """
3911 Given an input decay string, compute variables that quantify track helix-based isolation of the charged
3912 stable particles in the input decay chain.
3913
3914 Note:
3915 An "isolation score" can be defined using the distance
3916 of each particle to its closest neighbour, defined as the segment connecting the two
3917 extrapolated track helices intersection points on a given cylindrical surface.
3918 The distance variables defined in the `VariableManager` is named `minET2ETDist`,
3919 the isolation scores are named `minET2ETIsoScore`, `minET2ETIsoScoreAsWeightedAvg`.
3920
3921 The definition of distance and the number of distances that are calculated per sub-detector is based on
3922 the following recipe:
3923
3924 * **CDC**: as the segmentation is very coarse along :math:`z`,
3925 the distance is defined as the cord length on the :math:`(\\rho=R, \\phi)` plane.
3926 A total of 9 distances are calculated: the cylindrical surfaces are defined at radiuses
3927 that correspond to the positions of the 9 CDC wire superlayers: :math:`R_{i}^{\\mathrm{CDC}}~(i \\in \\{0,...,8\\})`.
3928
3929 * **TOP**: as there is no segmentation along :math:`z`,
3930 the distance is defined as the cord length on the :math:`(\\rho=R, \\phi)` plane.
3931 Only one distance at the TOP entry radius :math:`R_{0}^{\\mathrm{TOP}}` is calculated.
3932
3933 * **ARICH**: as there is no segmentation along :math:`z`,
3934 the distance is defined as the distance on the :math:`(\\rho=R, \\phi)` plane at fixed :math:`z=Z`.
3935 Only one distance at the ARICH photon detector entry coordinate :math:`Z_{0}^{\\mathrm{ARICH}}` is calculated.
3936
3937 * **ECL**: the distance is defined on the :math:`(\\rho=R, \\phi, z)` surface in the barrel,
3938 on the :math:`(\\rho, \\phi, z=Z)` surface in the endcaps.
3939 Two distances are calculated: one at the ECL entry surface :math:`R_{0}^{\\mathrm{ECL}}` (barrel),
3940 :math:`Z_{0}^{\\mathrm{ECL}}` (endcaps), and one at :math:`R_{1}^{\\mathrm{ECL}}` (barrel),
3941 :math:`Z_{1}^{\\mathrm{ECL}}` (endcaps), corresponding roughly to the mid-point
3942 of the longitudinal size of the crystals.
3943
3944 * **KLM**: the distance is defined on the :math:`(\\rho=R, \\phi, z)` surface in the barrel,
3945 on the :math:`(\\rho, \\phi, z=Z)` surface in the endcaps.
3946 Only one distance at the KLM first strip entry surface :math:`R_{0}^{\\mathrm{KLM}}` (barrel),
3947 :math:`Z_{0}^{\\mathrm{KLM}}` (endcaps) is calculated.
3948
3949 Parameters:
3950 decay_string (str): name of the input decay string with selected charged stable daughters,
3951 for example: ``Lambda0:merged -> ^p+ ^pi-``.
3952 Alternatively, it can be a particle list for charged stable particles
3953 as defined in ``Const::chargedStableSet``, for example: ``mu+:all``.
3954 The charge-conjugate particle list will be also processed automatically.
3955 path (basf2.Path): path to which module(s) will be added.
3956 *detectors: detectors for which track isolation variables will be calculated.
3957 Choose among: ``{'CDC', 'TOP', 'ARICH', 'ECL', 'KLM'}``.
3958 reference_list_name (Optional[str]): name of the input charged stable particle list for the reference tracks.
3959 By default, the ``:all`` ParticleList of the same type
3960 of the selected particle in ``decay_string`` is used.
3961 The charge-conjugate particle list will be also processed automatically.
3962 vars_for_nearest_part (Optional[list(str)]): a list of variables to calculate for the nearest particle in the reference
3963 list at each detector surface. It uses the metavariable `minET2ETDistVar`.
3964 If unset, only the distances to the nearest neighbour
3965 per detector are calculated.
3966 highest_prob_mass_for_hex (Optional[bool]): if this option is set to True (default), the helix extrapolation
3967 for the particles will use the track fit result for the most
3968 probable mass hypothesis, namely, the one that gives the highest
3969 chi2Prob of the fit. Otherwise, it uses the mass hypothesis that
3970 corresponds to the particle lists PDG.
3971 exclude_pid_det_weights (Optional[bool]): if this option is set to False (default), the isolation score
3972 calculation will take into account the weight that each detector has on the PID
3973 for the particle species of interest.
3974
3975 Returns:
3976 dict(int, list(str)): a dictionary mapping the PDG of each reference particle list to its isolation variables.
3977
3978 """
3979
3980 import pdg
3981 from ROOT import Belle2, TDatabasePDG
3982
3983 decayDescriptor = Belle2.DecayDescriptor()
3984 if not decayDescriptor.init(decay_string):
3985 B2FATAL(f"Invalid particle list {decay_string} in calculateTrackIsolation!")
3986 no_reference_list_name = not reference_list_name
3987
3988 det_and_layers = {
3989 "CDC": list(range(9)),
3990 "TOP": [0],
3991 "ARICH": [0],
3992 "ECL": [0, 1],
3993 "KLM": [0],
3994 }
3995 if any(d not in det_and_layers for d in detectors):
3996 B2FATAL(
3997 "Your input detector list: ",
3998 detectors,
3999 " contains an invalid choice. Please select among: ",
4000 list(
4001 det_and_layers.keys()))
4002
4003 # The module allows only one daughter to be selected at a time,
4004 # that's why here we preprocess the input decay string.
4005 select_symbol = '^'
4006 processed_decay_strings = []
4007 if select_symbol in decay_string:
4008 splitted_ds = decay_string.split(select_symbol)
4009 for i in range(decay_string.count(select_symbol)):
4010 tmp = list(splitted_ds)
4011 tmp.insert(i+1, select_symbol)
4012 processed_decay_strings += [''.join(tmp)]
4013 else:
4014 processed_decay_strings += [decay_string]
4015
4016 reference_lists_to_vars = {}
4017
4018 for processed_dec in processed_decay_strings:
4019 if no_reference_list_name:
4020 decayDescriptor.init(processed_dec)
4021 selected_daughter_pdgs = decayDescriptor.getSelectionPDGCodes()
4022 if len(selected_daughter_pdgs) > 0:
4023 reference_list_name = f'{TDatabasePDG.Instance().GetParticle(abs(selected_daughter_pdgs[-1])).GetName()}:all'
4024 else:
4025 reference_list_name = f'{processed_dec.split(":")[0]}:all'
4026
4027 ref_pdg = pdg.from_name(reference_list_name.split(":")[0])
4028
4029 trackiso = path.add_module("TrackIsoCalculator",
4030 decayString=processed_dec,
4031 detectorNames=list(detectors),
4032 particleListReference=reference_list_name,
4033 useHighestProbMassForExt=highest_prob_mass_for_ext,
4034 excludePIDDetWeights=exclude_pid_det_weights)
4035 trackiso.set_name(f"TrackIsoCalculator_{'_'.join(detectors)}_{processed_dec}_VS_{reference_list_name}")
4036
4037 # Metavariables for the distances to the closest reference tracks at each detector surface.
4038 # Always calculate them.
4039 # Ensure the flag for the mass hypothesis of the fit is set.
4040 trackiso_vars = [
4041 f"minET2ETDist({d}, {d_layer}, {reference_list_name}, {int(highest_prob_mass_for_ext)})"
4042 for d in detectors for d_layer in det_and_layers[d]]
4043 # Track isolation score.
4044 trackiso_vars += [
4045 f"minET2ETIsoScore({reference_list_name}, {int(highest_prob_mass_for_ext)}, {', '.join(detectors)})",
4046 f"minET2ETIsoScoreAsWeightedAvg({reference_list_name}, {int(highest_prob_mass_for_ext)}, {', '.join(detectors)})",
4047 ]
4048 # Optionally, calculate the input variables for the nearest neighbour in the reference list.
4049 if vars_for_nearest_part:
4050 trackiso_vars.extend(
4051 [
4052 f"minET2ETDistVar({d}, {d_layer}, {reference_list_name}, {v})"
4053 for d in detectors for d_layer in det_and_layers[d] for v in vars_for_nearest_part
4054 ])
4055 trackiso_vars.sort()
4056
4057 reference_lists_to_vars[ref_pdg] = trackiso_vars
4058
4059 return reference_lists_to_vars
4060
4061
4062def calculateDistance(list_name, decay_string, mode='vertextrack', path=None):
4063 """
4064 Calculates distance between two vertices, distance of closest approach between a vertex and a track,\
4065 distance of closest approach between a vertex and btube. For track, this calculation ignores track curvature,\
4066 it's negligible for small distances.The user should use extraInfo(CalculatedDistance)\
4067 to get it. A full example steering file is at analysis/tests/test_DistanceCalculator.py
4068
4069 Example:
4070 .. code-block:: python
4071
4072 from modularAnalysis import calculateDistance
4073 calculateDistance('list_name', 'decay_string', "mode", path=user_path)
4074
4075 @param list_name name of the input ParticleList
4076 @param decay_string select particles between the distance of closest approach will be calculated
4077 @param mode Specifies how the distance is calculated
4078 vertextrack: calculate the distance of closest approach between a track and a\
4079 vertex, taking the first candidate as vertex, default
4080 trackvertex: calculate the distance of closest approach between a track and a\
4081 vertex, taking the first candidate as track
4082 2tracks: calculates the distance of closest approach between two tracks
4083 2vertices: calculates the distance between two vertices
4084 vertexbtube: calculates the distance of closest approach between a vertex and btube
4085 trackbtube: calculates the distance of closest approach between a track and btube
4086 @param path modules are added to this path
4087
4088 """
4089
4090 dist_mod = register_module('DistanceCalculator')
4091
4092 dist_mod.set_name('DistanceCalculator_' + list_name)
4093 dist_mod.param('listName', list_name)
4094 dist_mod.param('decayString', decay_string)
4095 dist_mod.param('mode', mode)
4096 path.add_module(dist_mod)
4097
4098
4099def addInclusiveDstarReconstruction(decayString, slowPionCut, DstarCut, path):
4100 """
4101 Adds the InclusiveDstarReconstruction module to the given path.
4102 This module creates a D* particle list by estimating the D* four momenta
4103 from slow pions, specified by a given cut. The D* energy is approximated
4104 as E(D*) = m(D*)/(m(D*) - m(D)) * E(pi). The absolute value of the D*
4105 momentum is calculated using the D* PDG mass and the direction is collinear
4106 to the slow pion direction. The charge of the given pion list has to be consistent
4107 with the D* charge
4108
4109 @param decayString Decay string, must be of form ``D* -> pi``
4110 @param slowPionCut Cut applied to the input pion list to identify slow pions
4111 @param DstarCut Cut applied to the output D* list
4112 @param path the module is added to this path
4113 """
4114
4115 incl_dstar = register_module("InclusiveDstarReconstruction")
4116 incl_dstar.param("decayString", decayString)
4117 incl_dstar.param("slowPionCut", slowPionCut)
4118 incl_dstar.param("DstarCut", DstarCut)
4119 path.add_module(incl_dstar)
4120
4121
4122def scaleError(outputListName, inputListName,
4123 scaleFactors=[1.149631, 1.085547, 1.151704, 1.096434, 1.086659],
4124 scaleFactorsNoPXD=[1.149631, 1.085547, 1.151704, 1.096434, 1.086659],
4125 d0Resolution=[0.00115328, 0.00134704],
4126 z0Resolution=[0.00124327, 0.0013272],
4127 d0MomThr=0.500000,
4128 z0MomThr=0.500000,
4129 path=None):
4130 """
4131 This module creates a new charged particle list.
4132 The helix errors of the new particles are scaled by constant factors.
4133 Two sets of five scale factors are defined for tracks with and without a PXD hit.
4134 The scale factors are in order of (d0, phi0, omega, z0, tanlambda).
4135 For tracks with a PXD hit, in order to avoid severe underestimation of d0 and z0 errors,
4136 lower limits (best resolution) can be set in a momentum-dependent form.
4137 This module is supposed to be used only for TDCPV analysis and for low-momentum (0-3 GeV/c) tracks in BBbar events.
4138 Details will be documented in a Belle II note, BELLE2-NOTE-PH-2021-038.
4139
4140 @param inputListName Name of input charged particle list to be scaled
4141 @param outputListName Name of output charged particle list with scaled error
4142 @param scaleFactors List of five constants to be multiplied to each of helix errors (for tracks with a PXD hit)
4143 @param scaleFactorsNoPXD List of five constants to be multiplied to each of helix errors (for tracks without a PXD hit)
4144 @param d0Resolution List of two parameters, (a [cm], b [cm/(GeV/c)]),
4145 defining d0 best resolution as sqrt{ a**2 + (b / (p*beta*sinTheta**1.5))**2 }
4146 @param z0Resolution List of two parameters, (a [cm], b [cm/(GeV/c)]),
4147 defining z0 best resolution as sqrt{ a**2 + (b / (p*beta*sinTheta**2.5))**2 }
4148 @param d0MomThr d0 best resolution is kept constant below this momentum
4149 @param z0MomThr z0 best resolution is kept constant below this momentum
4150
4151 """
4152
4153 scale_error = register_module("HelixErrorScaler")
4154 scale_error.set_name('ScaleError_' + inputListName)
4155 scale_error.param('inputListName', inputListName)
4156 scale_error.param('outputListName', outputListName)
4157 scale_error.param('scaleFactors_PXD', scaleFactors)
4158 scale_error.param('scaleFactors_noPXD', scaleFactorsNoPXD)
4159 scale_error.param('d0ResolutionParameters', d0Resolution)
4160 scale_error.param('z0ResolutionParameters', z0Resolution)
4161 scale_error.param('d0MomentumThreshold', d0MomThr)
4162 scale_error.param('z0MomentumThreshold', z0MomThr)
4163 path.add_module(scale_error)
4164
4165
4166def estimateAndAttachTrackFitResult(inputListName, path=None):
4167 """
4168 Create a TrackFitResult from the momentum of the Particle assuming it originates from the IP and make a relation between them.
4169 The covariance, detector hit information, and fit-related information (pValue, NDF) are assigned meaningless values. The input
4170 Particles must not have already Track or TrackFitResult and thus are supposed to be composite particles, recoil, dummy
4171 particles, and so on.
4172
4173
4174 .. warning:: Since the source type is not overwritten as Track, not all track-related variables are guaranteed to be available.
4175
4176
4177 @param inputListName Name of input ParticleList
4178 """
4179
4180 estimator = register_module("TrackFitResultEstimator")
4181 estimator.set_name("trackFitResultEstimator_" + inputListName)
4182 estimator.param("inputListName", inputListName)
4183 path.add_module(estimator)
4184
4185
4186def correctEnergyBias(inputListNames, tableName, path=None):
4187 """
4188 Scale energy of the particles according to the scaling factor.
4189 If the particle list contains composite particles, the energy of the daughters are scaled.
4190 Subsequently, the energy of the mother particle is updated as well.
4191
4192 Parameters:
4193 inputListNames (list(str)): input particle list names
4194 tableName : stored in localdb and created using ParticleWeightingLookUpCreator
4195 path (basf2.Path): module is added to this path
4196 """
4197
4198 import b2bii
4199 if b2bii.isB2BII():
4200 B2ERROR("The energy bias cannot be corrected with this tool for Belle data.")
4201
4202 correctenergybias = register_module('EnergyBiasCorrection')
4203 correctenergybias.param('particleLists', inputListNames)
4204 correctenergybias.param('tableName', tableName)
4205 path.add_module(correctenergybias)
4206
4207
4208def twoBodyISRPhotonCorrector(outputListName, inputListName, massiveParticle, path=None):
4209 """
4210 Sets photon kinematics to corrected values in two body decays with an ISR photon
4211 and a massive particle. The original photon kinematics are kept in the input
4212 particleList and can be accessed using the originalParticle() metavariable on the
4213 new list.
4214
4215 @param ouputListName new ParticleList filled with copied Particles
4216 @param inputListName input ParticleList with original Particles
4217 @param massiveParticle name or PDG code of massive particle participating in the two
4218 body decay with the ISR photon
4219 @param path modules are added to this path
4220 """
4221
4222 # set the corrected energy of the photon in a new list
4223 photon_energy_correction = register_module('TwoBodyISRPhotonCorrector')
4224 photon_energy_correction.set_name('TwoBodyISRPhotonCorrector_' + outputListName)
4225 photon_energy_correction.param('outputGammaList', outputListName)
4226 photon_energy_correction.param('inputGammaList', inputListName)
4227
4228 # prepare PDG code of massive particle
4229 if isinstance(massiveParticle, int):
4230 photon_energy_correction.param('massiveParticlePDGCode', massiveParticle)
4231 else:
4232 from ROOT import Belle2
4233 decayDescriptor = Belle2.DecayDescriptor()
4234 if not decayDescriptor.init(massiveParticle):
4235 raise ValueError("TwoBodyISRPhotonCorrector: value of massiveParticle must be" +
4236 " an int or valid decay string.")
4237 pdgCode = decayDescriptor.getMother().getPDGCode()
4238 photon_energy_correction.param('massiveParticlePDGCode', pdgCode)
4239
4240 path.add_module(photon_energy_correction)
4241
4242
4243def addPhotonEfficiencyRatioVariables(inputListNames, tableName, path=None):
4244 """
4245 Add photon Data/MC detection efficiency ratio weights to the specified particle list
4246
4247 Parameters:
4248 inputListNames (list(str)): input particle list names
4249 tableName : taken from database with appropriate name
4250 path (basf2.Path): module is added to this path
4251 """
4252
4253 import b2bii
4254 if b2bii.isB2BII():
4255 B2ERROR("For Belle data the photon data/MC detection efficiency ratio is not available with this tool.")
4256
4257 photon_efficiency_correction = register_module('PhotonEfficiencySystematics')
4258 photon_efficiency_correction.param('particleLists', inputListNames)
4259 photon_efficiency_correction.param('tableName', tableName)
4260 path.add_module(photon_efficiency_correction)
4261
4262
4263def addPi0VetoEfficiencySystematics(particleList, decayString, tableName, threshold, mode='standard', suffix='', path=None):
4264 """
4265 Add pi0 veto Data/MC efficiency ratio weights to the specified particle list
4266
4267 @param particleList the input ParticleList
4268 @param decayString specify hard photon to be performed pi0 veto (e.g. 'B+:sig -> rho+:sig ^gamma:hard')
4269 @param tableName table name corresponding to payload version (e.g. 'Pi0VetoEfficiencySystematics_Mar2022')
4270 @param threshold pi0 veto threshold (0.10, 0.11, ..., 0.99)
4271 @param mode choose one mode (same as writePi0EtaVeto) out of 'standard', 'tight', 'cluster' and 'both'
4272 @param suffix optional suffix to be appended to the usual extraInfo name
4273 @param path the module is added to this path
4274
4275 The following extraInfo are available related with the given particleList:
4276
4277 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_ratio : weight of Data/MC for the veto efficiency
4278 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_uncertainty_stat : the statistical uncertainty of the weight
4279 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_uncertainty_sys : the systematic uncertainty of the weight
4280 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_uncertainty_total : the total uncertainty of the weight
4281 * Pi0VetoEfficiencySystematics_{mode}{suffix}_threshold : threshold of the pi0 veto
4282 """
4283
4284 import b2bii
4285 if b2bii.isB2BII():
4286 B2ERROR("For Belle data the pi0 veto data/MC efficiency ratio weights are not available via this tool.")
4287
4288 pi0veto_efficiency_correction = register_module('Pi0VetoEfficiencySystematics')
4289 pi0veto_efficiency_correction.param('particleLists', particleList)
4290 pi0veto_efficiency_correction.param('decayString', decayString)
4291 pi0veto_efficiency_correction.param('tableName', tableName)
4292 pi0veto_efficiency_correction.param('threshold', threshold)
4293 pi0veto_efficiency_correction.param('mode', mode)
4294 pi0veto_efficiency_correction.param('suffix', suffix)
4295 path.add_module(pi0veto_efficiency_correction)
4296
4297
4298def getAnalysisGlobaltag(timeout=180) -> str:
4299 """
4300 Returns a string containing the name of the latest and recommended analysis globaltag.
4301
4302 Parameters:
4303 timeout: Seconds to wait for b2conditionsdb-recommend
4304 """
4305
4306 import b2bii
4307 if b2bii.isB2BII():
4308 B2ERROR("The getAnalysisGlobaltag() function cannot be used for Belle data.")
4309
4310 # b2conditionsdb-recommend relies on a different repository, so it's better to protect
4311 # this function against potential failures of check_output.
4312 try:
4313 tags = subprocess.check_output(
4314 ['b2conditionsdb-recommend', '--oneline'],
4315 timeout=timeout
4316 ).decode('UTF-8').rstrip().split(' ')
4317 analysis_tag = ''
4318 for tag in tags:
4319 if tag.startswith('analysis_tools'):
4320 analysis_tag = tag
4321 return analysis_tag
4322 # In case of issues with git, b2conditionsdb-recommend may take too much time.
4323 except subprocess.TimeoutExpired as te:
4324 B2FATAL(f'A {te} exception was raised during the call of getAnalysisGlobaltag(). '
4325 'The function took too much time to retrieve the requested information '
4326 'from the versioning repository.\n'
4327 'Please try to re-run your job. In case of persistent failures, there may '
4328 'be issues with the DESY collaborative services, so please contact the experts.')
4329 except subprocess.CalledProcessError as ce:
4330 B2FATAL(f'A {ce} exception was raised during the call of getAnalysisGlobaltag(). '
4331 'Please try to re-run your job. In case of persistent failures, please contact '
4332 'the experts.')
4333
4334
4335def getAnalysisGlobaltagB2BII() -> str:
4336 """
4337 Get recommended global tag for B2BII analysis.
4338 """
4339
4340 import b2bii
4341 if not b2bii.isB2BII():
4342 B2ERROR('The getAnalysisGlobaltagB2BII() function cannot be used for Belle II data.')
4343 from versioning import recommended_b2bii_analysis_global_tag
4344 return recommended_b2bii_analysis_global_tag()
4345
4346
4347def getNbarIDMVA(particleList: str, path=None):
4348 """
4349 This function can give a score to predict if it is a anti-n0.
4350 It is not used to predict n0.
4351 Currently, this can be used only for ECL cluster.
4352 output will be stored in extraInfo(nbarID); -1 means MVA invalid
4353
4354 @param particleList The input ParticleList name or a decay string which contains a full mother particle list name.
4355 Only one selected daughter is supported.
4356 @param path modules are added to this path
4357 """
4358 import b2bii
4359 from ROOT import Belle2
4360
4361 if b2bii.isB2BII():
4362 B2ERROR("The MVA-based anti-neutron PID is only available for Belle II data.")
4363
4364 from variables import variables
4365
4366 variables.addAlias('V1', 'clusterHasPulseShapeDiscrimination')
4367 variables.addAlias('V2', 'clusterE')
4368 variables.addAlias('V3', 'clusterLAT')
4369 variables.addAlias('V4', 'clusterE1E9')
4370 variables.addAlias('V5', 'clusterE9E21')
4371 variables.addAlias('V6', 'clusterZernikeMVA')
4372 variables.addAlias('V7', 'clusterAbsZernikeMoment40')
4373 variables.addAlias('V8', 'clusterAbsZernikeMoment51')
4374
4375 variables.addAlias(
4376 'nbarIDValid',
4377 'passesCut(V1 == 1 and V2 >= 0 and V3 >= 0 and V4 >= 0 and V5 >= 0 and V6 >= 0 and V7 >= 0 and V8 >= 0)')
4378 variables.addAlias('nbarIDmod', 'conditionalVariableSelector(nbarIDValid == 1, extraInfo(nbarIDFromMVA), constant(-1.0))')
4379
4380 path.add_module('MVAExpert', listNames=particleList, extraInfoName='nbarIDFromMVA', identifier='db_nbarIDECL')
4381 decayDescriptor = Belle2.DecayDescriptor()
4382 if not decayDescriptor.init(particleList):
4383 raise ValueError(f"Provided decay string is invalid: {particleList}")
4384 if decayDescriptor.getNDaughters() == 0:
4385 variablesToExtraInfo(particleList, {'nbarIDmod': 'nbarID'}, option=2, path=path)
4386 else:
4387 listname = decayDescriptor.getMother().getFullName()
4388 variablesToDaughterExtraInfo(listname, particleList, {'nbarIDmod': 'nbarID'}, option=2, path=path)
4389
4390
4391def reconstructDecayWithNeutralHadron(decayString, cut, allowGamma=False, allowAnyParticleSource=False, path=None, **kwargs):
4392 r"""
4393 Reconstructs decay with a long-lived neutral hadron e.g.
4394 :math:`B^0 \to J/\psi K_L^0`,
4395 :math:`B^0 \to p \bar{n} D^*(2010)^-`.
4396
4397 The calculation is done with IP constraint and mother mass constraint.
4398
4399 The decay string passed in must satisfy the following rules:
4400
4401 - The neutral hadron must be **selected** in the decay string with the
4402 caret (``^``) e.g. ``B0:sig -> J/psi:sig ^K_L0:sig``. (Note the caret
4403 next to the neutral hadron.)
4404 - There can only be **one neutral hadron in a decay**.
4405 - The neutral hadron has to be a direct daughter of its mother.
4406
4407 .. note:: This function forwards its arguments to `reconstructDecay`,
4408 so please check the documentation of `reconstructDecay` for all
4409 possible arguments.
4410
4411 @param decayString A decay string following the mentioned rules
4412 @param cut Cut to apply to the particle list
4413 @param allowGamma Whether allow the selected particle to be ``gamma``
4414 @param allowAnyParticleSource Whether allow the selected particle to be from any source.
4415 Should only be used when studying control sample.
4416 @param path The path to put in the module
4417 """
4418
4419 reconstructDecay(decayString, cut, path=path, **kwargs)
4420 module = register_module('NeutralHadron4MomentumCalculator')
4421 module.set_name('NeutralHadron4MomentumCalculator_' + decayString)
4422 module.param('decayString', decayString)
4423 module.param('allowGamma', allowGamma)
4424 module.param('allowAnyParticleSource', allowAnyParticleSource)
4425 path.add_module(module)
4426
4427
4428def updateMassHypothesis(particleList, pdg, writeOut=False, path=None):
4429 """
4430 Module to update the mass hypothesis of a given input particle list with the chosen PDG.
4431 A new particle list is created with updated mass hypothesis.
4432 The allowed mass hypotheses for both input and output are electrons, muons, pions, kaons and protons.
4433
4434 .. note:
4435 The new particle list is named after the input one, with the additional suffix ``_converted_from_OLDHYPOTHESIS``,
4436 e.g. ``e+:all`` converted to muons becomes ``mu+:all_converted_from_e``.
4437
4438 @param particleList The input particle list name
4439 @param pdg The PDG code for the new mass hypothesis, in [11, 13, 211, 321, 2212]
4440 @param writeOut Whether `RootOutput` module should save the new particle list
4441 @param path Modules are added to this path
4442 """
4443 mass_updater = register_module("ParticleMassHypothesesUpdater")
4444 mass_updater.set_name("ParticleMassHypothesesUpdater_" + particleList + "_to_" + str(pdg))
4445 mass_updater.param("particleList", particleList)
4446 mass_updater.param("writeOut", writeOut)
4447 mass_updater.param("pdgCode", pdg)
4448 path.add_module(mass_updater)
4449
4450
4451func_requiring_analysisGT = [
4452 correctTrackEnergy, scaleTrackMomenta, smearTrackMomenta, oldwritePi0EtaVeto, writePi0EtaVeto, lowEnergyPi0Identification,
4453 getBeamBackgroundProbability, getFakePhotonProbability, tagCurlTracks, applyChargedPidMVA, correctEnergyBias,
4454 addPhotonEfficiencyRatioVariables, addPi0VetoEfficiencySystematics, getNbarIDMVA]
4455for _ in func_requiring_analysisGT:
4456 _.__doc__ += "\n .. note:: This function (optionally) requires a payload stored in the analysis GlobalTag. "\
4457 "Please append or prepend the latest one from `getAnalysisGlobaltag` or `getAnalysisGlobaltagB2BII`.\n"
4458
4459
4460if __name__ == '__main__':
4461 from basf2.utils import pretty_print_module
4462 pretty_print_module(__name__, "modularAnalysis")
4463
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