Belle II Software light-2405-quaxo
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 """
2028 Creates and fills a flat ntuple with the specified variables from the VariableManager.
2029 If a decayString is provided, then there will be one entry per candidate (for particle in list of candidates).
2030 If an empty decayString is provided, there will be one entry per event (useful for trigger studies, etc).
2031
2032 Parameters:
2033 decayString (str): specifies type of Particles and determines the name of the ParticleList
2034 variables (list(str)): the list of variables (which must be registered in the VariableManager)
2035 treename (str): name of the ntuple tree
2036 filename (str): which is used to store the variables
2037 path (basf2.Path): the basf2 path where the analysis is processed
2038 basketsize (int): size of baskets in the output ntuple in bytes
2039 signalSideParticleList (str): The name of the signal-side ParticleList.
2040 Only valid if the module is called in a for_each loop over the RestOfEvent.
2041 filenameSuffix (str): suffix to be appended to the filename before ``.root``.
2042 useFloat (bool): Use single precision (float) instead of double precision (double)
2043 for floating-point numbers.
2044 storeEventType (bool) : if true, the branch __eventType__ is added for the MC event type information.
2045 The information is available from MC16 on.
2046
2047 .. tip:: The output filename can be overridden using the ``-o`` argument of basf2.
2048 """
2049
2050 output = register_module('VariablesToNtuple')
2051 output.set_name('VariablesToNtuple_' + decayString)
2052 output.param('particleList', decayString)
2053 output.param('variables', variables)
2054 output.param('fileName', filename)
2055 output.param('treeName', treename)
2056 output.param('basketSize', basketsize)
2057 output.param('signalSideParticleList', signalSideParticleList)
2058 output.param('fileNameSuffix', filenameSuffix)
2059 output.param('useFloat', useFloat)
2060 output.param('storeEventType', storeEventType)
2061 path.add_module(output)
2062
2063
2064def variablesToHistogram(decayString,
2065 variables,
2066 variables_2d=None,
2067 filename='ntuple.root',
2068 path=None, *,
2069 directory=None,
2070 prefixDecayString=False,
2071 filenameSuffix=""):
2072 """
2073 Creates and fills a flat ntuple with the specified variables from the VariableManager
2074
2075 Parameters:
2076 decayString (str): specifies type of Particles and determines the name of the ParticleList
2077 variables (list(tuple))): variables + binning which must be registered in the VariableManager
2078 variables_2d (list(tuple)): pair of variables + binning for each which must be registered in the VariableManager
2079 filename (str): which is used to store the variables
2080 path (basf2.Path): the basf2 path where the analysis is processed
2081 directory (str): directory inside the output file where the histograms should be saved.
2082 Useful if you want to have different histograms in the same file to separate them.
2083 prefixDecayString (bool): If True the decayString will be prepended to the directory name to allow for more
2084 programmatic naming of the structure in the file.
2085 filenameSuffix (str): suffix to be appended to the filename before ``.root``.
2086
2087 .. tip:: The output filename can be overridden using the ``-o`` argument of basf2.
2088 """
2089
2090 if variables_2d is None:
2091 variables_2d = []
2092 output = register_module('VariablesToHistogram')
2093 output.set_name('VariablesToHistogram_' + decayString)
2094 output.param('particleList', decayString)
2095 output.param('variables', variables)
2096 output.param('variables_2d', variables_2d)
2097 output.param('fileName', filename)
2098 output.param('fileNameSuffix', filenameSuffix)
2099 if directory is not None or prefixDecayString:
2100 if directory is None:
2101 directory = ""
2102 if prefixDecayString:
2103 directory = decayString + "_" + directory
2104 output.param("directory", directory)
2105 path.add_module(output)
2106
2107
2108def variablesToExtraInfo(particleList, variables, option=0, path=None):
2109 """
2110 For each particle in the input list the selected variables are saved in an extra-info field with the given name.
2111 Can be used when wanting to save variables before modifying them, e.g. when performing vertex fits.
2112
2113 Parameters:
2114 particleList (str): The input ParticleList
2115 variables (dict[str,str]): Dictionary of Variables (key) and extraInfo names (value).
2116 option (int): Option to overwrite an existing extraInfo. Choose among -1, 0, 1, 2.
2117 An existing extra info with the same name will be overwritten if the new
2118 value is lower / will never be overwritten / will be overwritten if the
2119 new value is higher / will always be overwritten (option = -1/0/1/2).
2120 path (basf2.Path): modules are added to this path
2121 """
2122
2123 mod = register_module('VariablesToExtraInfo')
2124 mod.set_name('VariablesToExtraInfo_' + particleList)
2125 mod.param('particleList', particleList)
2126 mod.param('variables', variables)
2127 mod.param('overwrite', option)
2128 path.add_module(mod)
2129
2130
2131def variablesToDaughterExtraInfo(particleList, decayString, variables, option=0, path=None):
2132 """
2133 For each daughter particle specified via decay string the selected variables (estimated for the mother particle)
2134 are saved in an extra-info field with the given name. In other words, the property of mother is saved as extra-info
2135 to specified daughter particle.
2136
2137 Parameters:
2138 particleList (str): The input ParticleList
2139 decayString (str): Decay string that specifies to which daughter the extra info should be appended
2140 variables (dict[str,str]): Dictionary of Variables (key) and extraInfo names (value).
2141 option (int): Option to overwrite an existing extraInfo. Choose among -1, 0, 1, 2.
2142 An existing extra info with the same name will be overwritten if the new
2143 value is lower / will never be overwritten / will be overwritten if the
2144 new value is higher / will always be overwritten (option = -1/0/1/2).
2145 path (basf2.Path): modules are added to this path
2146 """
2147
2148 mod = register_module('VariablesToExtraInfo')
2149 mod.set_name('VariablesToDaughterExtraInfo_' + particleList)
2150 mod.param('particleList', particleList)
2151 mod.param('decayString', decayString)
2152 mod.param('variables', variables)
2153 mod.param('overwrite', option)
2154 path.add_module(mod)
2155
2156
2157def variablesToEventExtraInfo(particleList, variables, option=0, path=None):
2158 """
2159 For each particle in the input list the selected variables are saved in an event-extra-info field with the given name,
2160 Can be used to save MC truth information, for example, in a ntuple of reconstructed particles.
2161
2162 .. tip::
2163 When the function is called first time not in the main path but in a sub-path e.g. ``roe_path``,
2164 the eventExtraInfo cannot be accessed from the main path because of the shorter lifetime of the event-extra-info field.
2165 If one wants to call the function in a sub-path, one has to call the function in the main path beforehand.
2166
2167 Parameters:
2168 particleList (str): The input ParticleList
2169 variables (dict[str,str]): Dictionary of Variables (key) and extraInfo names (value).
2170 option (int): Option to overwrite an existing extraInfo. Choose among -1, 0, 1, 2.
2171 An existing extra info with the same name will be overwritten if the new
2172 value is lower / will never be overwritten / will be overwritten if the
2173 new value is higher / will always be overwritten (option = -1/0/1/2).
2174 path (basf2.Path): modules are added to this path
2175 """
2176
2177 mod = register_module('VariablesToEventExtraInfo')
2178 mod.set_name('VariablesToEventExtraInfo_' + particleList)
2179 mod.param('particleList', particleList)
2180 mod.param('variables', variables)
2181 mod.param('overwrite', option)
2182 path.add_module(mod)
2183
2184
2185def variableToSignalSideExtraInfo(particleList, varToExtraInfo, path):
2186 """
2187 Write the value of specified variables estimated for the single particle in the input list (has to contain exactly 1
2188 particle) as an extra info to the particle related to current ROE.
2189 Should be used only in the for_each roe path.
2190
2191 Parameters:
2192 particleList (str): The input ParticleList
2193 varToExtraInfo (dict[str,str]): Dictionary of Variables (key) and extraInfo names (value).
2194 path (basf2.Path): modules are added to this path
2195 """
2196
2197 mod = register_module('SignalSideVariablesToExtraInfo')
2198 mod.set_name('SigSideVarToExtraInfo_' + particleList)
2199 mod.param('particleListName', particleList)
2200 mod.param('variableToExtraInfo', varToExtraInfo)
2201 path.add_module(mod)
2202
2203
2204def signalRegion(particleList, cut, path=None, name="isSignalRegion", blind_data=True):
2205 """
2206 Define and blind a signal region.
2207 Per default, the defined signal region is cut out if ran on data.
2208 This function will provide a new variable 'isSignalRegion' as default, which is either 0 or 1 depending on the cut
2209 provided.
2210
2211 Example:
2212 .. code-block:: python
2213
2214 ma.reconstructDecay("B+:sig -> D+ pi0", "Mbc>5.2", path=path)
2215 ma.signalRegion("B+:sig",
2216 "Mbc>5.27 and abs(deltaE)<0.2",
2217 blind_data=True,
2218 path=path)
2219 ma.variablesToNtuples("B+:sig", ["isSignalRegion"], path=path)
2220
2221 Parameters:
2222 particleList (str): The input ParticleList
2223 cut (str): Cut string describing the signal region
2224 path (basf2.Path):: Modules are added to this path
2225 name (str): Name of the Signal region in the variable manager
2226 blind_data (bool): Automatically exclude signal region from data
2227
2228 """
2229
2230 from variables import variables
2231 mod = register_module('VariablesToExtraInfo')
2232 mod.set_name(f'{name}_' + particleList)
2233 mod.param('particleList', particleList)
2234 mod.param('variables', {f"passesCut({cut})": name})
2235 variables.addAlias(name, f"extraInfo({name})")
2236 path.add_module(mod)
2237
2238 # Check if we run on Data
2239 if blind_data:
2240 applyCuts(particleList, f"{name}==0 or isMC==1", path=path)
2241
2242
2243def removeExtraInfo(particleLists=None, removeEventExtraInfo=False, path=None):
2244 """
2245 Removes the ExtraInfo of the given particleLists. If specified (removeEventExtraInfo = True) also the EventExtraInfo is removed.
2246 """
2247
2248 if particleLists is None:
2249 particleLists = []
2250 mod = register_module('ExtraInfoRemover')
2251 mod.param('particleLists', particleLists)
2252 mod.param('removeEventExtraInfo', removeEventExtraInfo)
2253 path.add_module(mod)
2254
2255
2256def signalSideParticleFilter(particleList, selection, roe_path, deadEndPath):
2257 """
2258 Checks if the current ROE object in the for_each roe path (argument roe_path) is related
2259 to the particle from the input ParticleList. Additional selection criteria can be applied.
2260 If ROE is not related to any of the Particles from ParticleList or the Particle doesn't
2261 meet the selection criteria the execution of deadEndPath is started. This path, as the name
2262 suggests should be empty and its purpose is to end the execution of for_each roe path for
2263 the current ROE object.
2264
2265 @param particleList The input ParticleList
2266 @param selection Selection criteria that Particle needs meet in order for for_each ROE path to continue
2267 @param for_each roe path in which this filter is executed
2268 @param deadEndPath empty path that ends execution of or_each roe path for the current ROE object.
2269 """
2270
2271 mod = register_module('SignalSideParticleFilter')
2272 mod.set_name('SigSideParticleFilter_' + particleList)
2273 mod.param('particleLists', [particleList])
2274 mod.param('selection', selection)
2275 roe_path.add_module(mod)
2276 mod.if_false(deadEndPath)
2277
2278
2279def signalSideParticleListsFilter(particleLists, selection, roe_path, deadEndPath):
2280 """
2281 Checks if the current ROE object in the for_each roe path (argument roe_path) is related
2282 to the particle from the input ParticleList. Additional selection criteria can be applied.
2283 If ROE is not related to any of the Particles from ParticleList or the Particle doesn't
2284 meet the selection criteria the execution of deadEndPath is started. This path, as the name
2285 suggests should be empty and its purpose is to end the execution of for_each roe path for
2286 the current ROE object.
2287
2288 @param particleLists The input ParticleLists
2289 @param selection Selection criteria that Particle needs meet in order for for_each ROE path to continue
2290 @param for_each roe path in which this filter is executed
2291 @param deadEndPath empty path that ends execution of or_each roe path for the current ROE object.
2292 """
2293
2294 mod = register_module('SignalSideParticleFilter')
2295 mod.set_name('SigSideParticleFilter_' + particleLists[0])
2296 mod.param('particleLists', particleLists)
2297 mod.param('selection', selection)
2298 roe_path.add_module(mod)
2299 mod.if_false(deadEndPath)
2300
2301
2303 decayString,
2304 cut,
2305 dmID=0,
2306 writeOut=False,
2307 path=None,
2308 chargeConjugation=True,
2309):
2310 r"""
2311 Finds and creates a ``ParticleList`` from given decay string.
2312 ``ParticleList`` of daughters with sub-decay is created.
2313
2314 Only the particles made from MCParticle, which can be loaded by `fillParticleListFromMC`, are accepted as daughters.
2315
2316 Only signal particle, which means :b2:var:`isSignal` is equal to 1, is stored. One can use the decay string grammar
2317 to change the behavior of :b2:var:`isSignal`. One can find detailed information in :ref:`DecayString`.
2318
2319 .. tip::
2320 If one uses same sub-decay twice, same particles are registered to a ``ParticleList``. For example,
2321 ``K_S0:pi0pi0 =direct=> [pi0:gg =direct=> gamma:MC gamma:MC] [pi0:gg =direct=> gamma:MC gamma:MC]``.
2322 One can skip the second sub-decay, ``K_S0:pi0pi0 =direct=> [pi0:gg =direct=> gamma:MC gamma:MC] pi0:gg``.
2323
2324 .. tip::
2325 It is recommended to use only primary particles as daughter particles unless you want to explicitly study the secondary
2326 particles. The behavior of MC-matching for secondary particles from a stable particle decay is not guaranteed.
2327 Please consider to use `fillParticleListFromMC` with ``skipNonPrimary=True`` to load daughter particles.
2328 Moreover, it is recommended to load ``K_S0`` and ``Lambda0`` directly from MCParticle by `fillParticleListFromMC` rather
2329 than reconstructing from two pions or a proton-pion pair, because their direct daughters can be the secondary particle.
2330
2331
2332 @param decayString :ref:`DecayString` specifying what kind of the decay should be reconstructed
2333 (from the DecayString the mother and daughter ParticleLists are determined)
2334 @param cut created (mother) Particles are added to the mother ParticleList if they
2335 pass given cuts (in VariableManager style) and rejected otherwise
2336 isSignal==1 is always required by default.
2337 @param dmID user specified decay mode identifier
2338 @param writeOut whether RootOutput module should save the created ParticleList
2339 @param path modules are added to this path
2340 @param chargeConjugation boolean to decide whether charge conjugated mode should be reconstructed as well (on by default)
2341 """
2342
2343 pmake = register_module('ParticleCombinerFromMC')
2344 pmake.set_name('ParticleCombinerFromMC_' + decayString)
2345 pmake.param('decayString', decayString)
2346 pmake.param('cut', cut)
2347 pmake.param('decayMode', dmID)
2348 pmake.param('writeOut', writeOut)
2349 pmake.param('chargeConjugation', chargeConjugation)
2350 path.add_module(pmake)
2351
2352
2353def findMCDecay(
2354 list_name,
2355 decay,
2356 writeOut=False,
2357 appendAllDaughters=False,
2358 skipNonPrimaryDaughters=True,
2359 path=None,
2360):
2361 """
2362 Finds and creates a ``ParticleList`` for all ``MCParticle`` decays matching a given :ref:`DecayString`.
2363 The decay string is required to describe correctly what you want.
2364 In the case of inclusive decays, you can use :ref:`Grammar_for_custom_MCMatching`
2365
2366 The output particles has only the daughter particles written in the given decay string, if
2367 ``appendAllDaughters=False`` (default). If ``appendAllDaughters=True``, all daughters of the matched MCParticle are
2368 appended in the order defined at the MCParticle level. For example,
2369
2370 .. code-block:: python
2371
2372 findMCDecay('B0:Xee', 'B0 -> e+ e- ... ?gamma', appendAllDaughters=False, path=mypath)
2373
2374 The output ParticleList ``B0:Xee`` will match the inclusive ``B0 -> e+ e-`` decays (but neutrinos are not included),
2375 in both cases of ``appendAllDaughters`` is false and true.
2376 If the ``appendAllDaughters=False`` as above example, the ``B0:Xee`` has only two electrons as daughters.
2377 While, if ``appendAllDaughters=True``, all daughters of the matched MCParticles are appended. When the truth decay mode of
2378 the MCParticle is ``B0 -> [K*0 -> K+ pi-] [J/psi -> e+ e-]``, the first daughter of ``B0:Xee`` is ``K*0`` and ``e+``
2379 will be the first daughter of second daughter of ``B0:Xee``.
2380
2381 The option ``skipNonPrimaryDaughters`` only has an effect if ``appendAllDaughters=True``. If ``skipNonPrimaryDaughters=True``,
2382 all primary daughters are appended but the secondary particles are not.
2383
2384 .. tip::
2385 Daughters of ``Lambda0`` are not primary, but ``Lambda0`` is not a final state particle.
2386 In order for the MCMatching to work properly, the daughters of ``Lambda0`` are appended to
2387 ``Lambda0`` regardless of the value of the option ``skipNonPrimaryDaughters``.
2388
2389
2390 @param list_name The output particle list name
2391 @param decay The decay string which you want
2392 @param writeOut Whether `RootOutput` module should save the created ``outputList``
2393 @param skipNonPrimaryDaughters if true, skip non primary daughters, useful to study final state daughter particles
2394 @param appendAllDaughters if true, not only the daughters described in the decay string but all daughters are appended
2395 @param path modules are added to this path
2396 """
2397
2398 decayfinder = register_module('MCDecayFinder')
2399 decayfinder.set_name('MCDecayFinder_' + list_name)
2400 decayfinder.param('listName', list_name)
2401 decayfinder.param('decayString', decay)
2402 decayfinder.param('appendAllDaughters', appendAllDaughters)
2403 decayfinder.param('skipNonPrimaryDaughters', skipNonPrimaryDaughters)
2404 decayfinder.param('writeOut', writeOut)
2405 path.add_module(decayfinder)
2406
2407
2408def summaryOfLists(particleLists, outputFile=None, path=None):
2409 """
2410 Prints out Particle statistics at the end of the job: number of events with at
2411 least one candidate, average number of candidates per event, etc.
2412 If an output file name is provided the statistics is also dumped into a json file with that name.
2413
2414 @param particleLists list of input ParticleLists
2415 @param outputFile output file name (not created by default)
2416 """
2417
2418 particleStats = register_module('ParticleStats')
2419 particleStats.param('particleLists', particleLists)
2420 if outputFile is not None:
2421 particleStats.param('outputFile', outputFile)
2422 path.add_module(particleStats)
2423
2424
2425def matchMCTruth(list_name, path):
2426 """
2427 Performs MC matching (sets relation Particle->MCParticle) for
2428 all particles (and its (grand)^N-daughter particles) in the specified
2429 ParticleList.
2430
2431 @param list_name name of the input ParticleList
2432 @param path modules are added to this path
2433 """
2434
2435 mcMatch = register_module('MCMatcherParticles')
2436 mcMatch.set_name('MCMatch_' + list_name)
2437 mcMatch.param('listName', list_name)
2438 path.add_module(mcMatch)
2439
2440
2441def looseMCTruth(list_name, path):
2442 """
2443 Performs loose MC matching for all particles in the specified
2444 ParticleList.
2445 The difference between loose and normal mc matching algorithm is that
2446 the loose algorithm will find the common mother of the majority of daughter
2447 particles while the normal algorithm finds the common mother of all daughters.
2448 The results of loose mc matching algorithm are stored to the following extraInfo
2449 items:
2450
2451 - looseMCMotherPDG: PDG code of most common mother
2452 - looseMCMotherIndex: 1-based StoreArray<MCParticle> index of most common mother
2453 - looseMCWrongDaughterN: number of daughters that don't originate from the most common mother
2454 - looseMCWrongDaughterPDG: PDG code of the daughter that doesn't originate from the most common mother (only if
2455 looseMCWrongDaughterN = 1)
2456 - looseMCWrongDaughterBiB: 1 if the wrong daughter is Beam Induced Background Particle
2457
2458 @param list_name name of the input ParticleList
2459 @param path modules are added to this path
2460 """
2461
2462 mcMatch = register_module('MCMatcherParticles')
2463 mcMatch.set_name('LooseMCMatch_' + list_name)
2464 mcMatch.param('listName', list_name)
2465 mcMatch.param('looseMCMatching', True)
2466 path.add_module(mcMatch)
2467
2468
2469def buildRestOfEvent(target_list_name, inputParticlelists=None,
2470 fillWithMostLikely=True,
2471 chargedPIDPriors=None, path=None):
2472 """
2473 Creates for each Particle in the given ParticleList a RestOfEvent
2474 dataobject and makes basf2 relation between them. User can provide additional
2475 particle lists with a different particle hypothesis like ['K+:good, e+:good'], etc.
2476
2477 @param target_list_name name of the input ParticleList
2478 @param inputParticlelists list of user-defined input particle list names, which serve
2479 as source of particles to build the ROE, the FSP particles from
2480 target_list_name are automatically excluded from the ROE object
2481 @param fillWithMostLikely By default the module uses the most likely particle mass hypothesis for charged particles
2482 based on the PID likelihood. Turn this behavior off if you want to configure your own
2483 input particle lists.
2484 @param chargedPIDPriors The prior PID fractions, that are used to regulate the
2485 amount of certain charged particle species, should be a list of
2486 six floats if not None. The order of particle types is
2487 the following: [e-, mu-, pi-, K-, p+, d+]
2488 @param path modules are added to this path
2489 """
2490
2491 if inputParticlelists is None:
2492 inputParticlelists = []
2493 fillParticleList('pi+:all', '', path=path)
2494 if fillWithMostLikely:
2495 from stdCharged import stdMostLikely
2496 stdMostLikely(chargedPIDPriors, '_roe', path=path)
2497 inputParticlelists = [f'{ptype}:mostlikely_roe' for ptype in ['K+', 'p+', 'e+', 'mu+']]
2498 import b2bii
2499 if not b2bii.isB2BII():
2500 fillParticleList('gamma:all', '', path=path)
2501 fillParticleList('K_L0:roe_default', 'isFromKLM > 0', path=path)
2502 inputParticlelists += ['pi+:all', 'gamma:all', 'K_L0:roe_default']
2503 else:
2504 inputParticlelists += ['pi+:all', 'gamma:mdst']
2505 roeBuilder = register_module('RestOfEventBuilder')
2506 roeBuilder.set_name('ROEBuilder_' + target_list_name)
2507 roeBuilder.param('particleList', target_list_name)
2508 roeBuilder.param('particleListsInput', inputParticlelists)
2509 roeBuilder.param('mostLikely', fillWithMostLikely)
2510 path.add_module(roeBuilder)
2511
2512
2513def buildNestedRestOfEvent(target_list_name, maskName='all', path=None):
2514 """
2515 Creates for each Particle in the given ParticleList a RestOfEvent
2516 @param target_list_name name of the input ParticleList
2517 @param mask_name name of the ROEMask to be used
2518 @param path modules are added to this path
2519 """
2520
2521 roeBuilder = register_module('RestOfEventBuilder')
2522 roeBuilder.set_name('NestedROEBuilder_' + target_list_name)
2523 roeBuilder.param('particleList', target_list_name)
2524 roeBuilder.param('nestedROEMask', maskName)
2525 roeBuilder.param('createNestedROE', True)
2526 path.add_module(roeBuilder)
2527
2528
2529def buildRestOfEventFromMC(target_list_name, inputParticlelists=None, path=None):
2530 """
2531 Creates for each Particle in the given ParticleList a RestOfEvent
2532 @param target_list_name name of the input ParticleList
2533 @param inputParticlelists list of input particle list names, which serve
2534 as a source of particles to build ROE, the FSP particles from
2535 target_list_name are excluded from ROE object
2536 @param path modules are added to this path
2537 """
2538
2539 if inputParticlelists is None:
2540 inputParticlelists = []
2541 if (len(inputParticlelists) == 0):
2542 # Type of particles to use for ROEBuilder
2543 # K_S0 and Lambda0 are added here because some of them have interacted
2544 # with the detector material
2545 types = ['gamma', 'e+', 'mu+', 'pi+', 'K+', 'p+', 'K_L0',
2546 'n0', 'nu_e', 'nu_mu', 'nu_tau',
2547 'K_S0', 'Lambda0']
2548 for t in types:
2549 fillParticleListFromMC(f"{t}:roe_default_gen", 'mcPrimary > 0 and nDaughters == 0',
2550 True, True, path=path)
2551 inputParticlelists += [f"{t}:roe_default_gen"]
2552 roeBuilder = register_module('RestOfEventBuilder')
2553 roeBuilder.set_name('MCROEBuilder_' + target_list_name)
2554 roeBuilder.param('particleList', target_list_name)
2555 roeBuilder.param('particleListsInput', inputParticlelists)
2556 roeBuilder.param('fromMC', True)
2557 path.add_module(roeBuilder)
2558
2559
2560def appendROEMask(list_name,
2561 mask_name,
2562 trackSelection,
2563 eclClusterSelection,
2564 klmClusterSelection='',
2565 path=None):
2566 """
2567 Loads the ROE object of a particle and creates a ROE mask with a specific name. It applies
2568 selection criteria for tracks and eclClusters which will be used by variables in ROEVariables.cc.
2569
2570 - append a ROE mask with all tracks in ROE coming from the IP region
2571
2572 .. code-block:: python
2573
2574 appendROEMask('B+:sig', 'IPtracks', '[dr < 2] and [abs(dz) < 5]', path=mypath)
2575
2576 - append a ROE mask with only ECL-based particles that pass as good photon candidates
2577
2578 .. code-block:: python
2579
2580 goodPhotons = 'inCDCAcceptance and clusterErrorTiming < 1e6 and [clusterE1E9 > 0.4 or E > 0.075]'
2581 appendROEMask('B+:sig', 'goodROEGamma', '', goodPhotons, path=mypath)
2582
2583
2584 @param list_name name of the input ParticleList
2585 @param mask_name name of the appended ROEMask
2586 @param trackSelection decay string for the track-based particles in ROE
2587 @param eclClusterSelection decay string for the ECL-based particles in ROE
2588 @param klmClusterSelection decay string for the KLM-based particles in ROE
2589 @param path modules are added to this path
2590 """
2591
2592 roeMask = register_module('RestOfEventInterpreter')
2593 roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + mask_name)
2594 roeMask.param('particleList', list_name)
2595 roeMask.param('ROEMasks', [(mask_name, trackSelection, eclClusterSelection, klmClusterSelection)])
2596 path.add_module(roeMask)
2597
2598
2599def appendROEMasks(list_name, mask_tuples, path=None):
2600 """
2601 Loads the ROE object of a particle and creates a ROE mask with a specific name. It applies
2602 selection criteria for track-, ECL- and KLM-based particles which will be used by ROE variables.
2603
2604 The multiple ROE masks with their own selection criteria are specified
2605 via list of tuples (mask_name, trackParticleSelection, eclParticleSelection, klmParticleSelection) or
2606 (mask_name, trackSelection, eclClusterSelection) in case with fractions.
2607
2608 - Example for two tuples, one with and one without fractions
2609
2610 .. code-block:: python
2611
2612 ipTracks = ('IPtracks', '[dr < 2] and [abs(dz) < 5]', '', '')
2613 goodPhotons = 'inCDCAcceptance and [clusterErrorTiming < 1e6] and [clusterE1E9 > 0.4 or E > 0.075]'
2614 goodROEGamma = ('ROESel', '[dr < 2] and [abs(dz) < 5]', goodPhotons, '')
2615 goodROEKLM = ('IPtracks', '[dr < 2] and [abs(dz) < 5]', '', 'nKLMClusterTrackMatches == 0')
2616 appendROEMasks('B+:sig', [ipTracks, goodROEGamma, goodROEKLM], path=mypath)
2617
2618 @param list_name name of the input ParticleList
2619 @param mask_tuples array of ROEMask list tuples to be appended
2620 @param path modules are added to this path
2621 """
2622
2623 compatible_masks = []
2624 for mask in mask_tuples:
2625 # add empty KLM-based selection if it's absent:
2626 if len(mask) == 3:
2627 compatible_masks += [(*mask, '')]
2628 else:
2629 compatible_masks += [mask]
2630 roeMask = register_module('RestOfEventInterpreter')
2631 roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + 'MaskList')
2632 roeMask.param('particleList', list_name)
2633 roeMask.param('ROEMasks', compatible_masks)
2634 path.add_module(roeMask)
2635
2636
2637def updateROEMask(list_name,
2638 mask_name,
2639 trackSelection,
2640 eclClusterSelection='',
2641 klmClusterSelection='',
2642 path=None):
2643 """
2644 Update an existing ROE mask by applying additional selection cuts for
2645 tracks and/or clusters.
2646
2647 See function `appendROEMask`!
2648
2649 @param list_name name of the input ParticleList
2650 @param mask_name name of the ROEMask to update
2651 @param trackSelection decay string for the track-based particles in ROE
2652 @param eclClusterSelection decay string for the ECL-based particles in ROE
2653 @param klmClusterSelection decay string for the KLM-based particles in ROE
2654 @param path modules are added to this path
2655 """
2656
2657 roeMask = register_module('RestOfEventInterpreter')
2658 roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + mask_name)
2659 roeMask.param('particleList', list_name)
2660 roeMask.param('ROEMasks', [(mask_name, trackSelection, eclClusterSelection, klmClusterSelection)])
2661 roeMask.param('update', True)
2662 path.add_module(roeMask)
2663
2664
2665def updateROEMasks(list_name, mask_tuples, path):
2666 """
2667 Update existing ROE masks by applying additional selection cuts for tracks
2668 and/or clusters.
2669
2670 The multiple ROE masks with their own selection criteria are specified
2671 via list tuples (mask_name, trackSelection, eclClusterSelection, klmClusterSelection)
2672
2673 See function `appendROEMasks`!
2674
2675 @param list_name name of the input ParticleList
2676 @param mask_tuples array of ROEMask list tuples to be appended
2677 @param path modules are added to this path
2678 """
2679
2680 compatible_masks = []
2681 for mask in mask_tuples:
2682 # add empty KLM-based selection if it's absent:
2683 if len(mask) == 3:
2684 compatible_masks += [(*mask, '')]
2685 else:
2686 compatible_masks += [mask]
2687
2688 roeMask = register_module('RestOfEventInterpreter')
2689 roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + 'MaskList')
2690 roeMask.param('particleList', list_name)
2691 roeMask.param('ROEMasks', compatible_masks)
2692 roeMask.param('update', True)
2693 path.add_module(roeMask)
2694
2695
2696def keepInROEMasks(list_name, mask_names, cut_string, path=None):
2697 """
2698 This function is used to apply particle list specific cuts on one or more ROE masks (track or eclCluster).
2699 With this function one can KEEP the tracks/eclclusters used in particles from provided particle list.
2700 This function should be executed only in the for_each roe path for the current ROE object.
2701
2702 To avoid unnecessary computation, the input particle list should only contain particles from ROE
2703 (use cut 'isInRestOfEvent == 1'). To update the ECLCluster masks, the input particle list should be a photon
2704 particle list (e.g. 'gamma:someLabel'). To update the Track masks, the input particle list should be a charged
2705 pion particle list (e.g. 'pi+:someLabel').
2706
2707 Updating a non-existing mask will create a new one.
2708
2709 - keep only those tracks that were used in provided particle list
2710
2711 .. code-block:: python
2712
2713 keepInROEMasks('pi+:goodTracks', 'mask', '', path=mypath)
2714
2715 - keep only those clusters that were used in provided particle list and pass a cut, apply to several masks
2716
2717 .. code-block:: python
2718
2719 keepInROEMasks('gamma:goodClusters', ['mask1', 'mask2'], 'E > 0.1', path=mypath)
2720
2721
2722 @param list_name name of the input ParticleList
2723 @param mask_names array of ROEMasks to be updated
2724 @param cut_string decay string with which the mask will be updated
2725 @param path modules are added to this path
2726 """
2727
2728 updateMask = register_module('RestOfEventUpdater')
2729 updateMask.set_name('RestOfEventUpdater_' + list_name + '_masks')
2730 updateMask.param('particleList', list_name)
2731 updateMask.param('updateMasks', mask_names)
2732 updateMask.param('cutString', cut_string)
2733 updateMask.param('discard', False)
2734 path.add_module(updateMask)
2735
2736
2737def discardFromROEMasks(list_name, mask_names, cut_string, path=None):
2738 """
2739 This function is used to apply particle list specific cuts on one or more ROE masks (track or eclCluster).
2740 With this function one can DISCARD the tracks/eclclusters used in particles from provided particle list.
2741 This function should be executed only in the for_each roe path for the current ROE object.
2742
2743 To avoid unnecessary computation, the input particle list should only contain particles from ROE
2744 (use cut 'isInRestOfEvent == 1'). To update the ECLCluster masks, the input particle list should be a photon
2745 particle list (e.g. 'gamma:someLabel'). To update the Track masks, the input particle list should be a charged
2746 pion particle list (e.g. 'pi+:someLabel').
2747
2748 Updating a non-existing mask will create a new one.
2749
2750 - discard tracks that were used in provided particle list
2751
2752 .. code-block:: python
2753
2754 discardFromROEMasks('pi+:badTracks', 'mask', '', path=mypath)
2755
2756 - discard clusters that were used in provided particle list and pass a cut, apply to several masks
2757
2758 .. code-block:: python
2759
2760 discardFromROEMasks('gamma:badClusters', ['mask1', 'mask2'], 'E < 0.1', path=mypath)
2761
2762
2763 @param list_name name of the input ParticleList
2764 @param mask_names array of ROEMasks to be updated
2765 @param cut_string decay string with which the mask will be updated
2766 @param path modules are added to this path
2767 """
2768
2769 updateMask = register_module('RestOfEventUpdater')
2770 updateMask.set_name('RestOfEventUpdater_' + list_name + '_masks')
2771 updateMask.param('particleList', list_name)
2772 updateMask.param('updateMasks', mask_names)
2773 updateMask.param('cutString', cut_string)
2774 updateMask.param('discard', True)
2775 path.add_module(updateMask)
2776
2777
2778def optimizeROEWithV0(list_name, mask_names, cut_string, path=None):
2779 """
2780 This function is used to apply particle list specific cuts on one or more ROE masks for Tracks.
2781 It is possible to optimize the ROE selection by treating tracks from V0's separately, meaning,
2782 taking V0's 4-momentum into account instead of 4-momenta of tracks. A cut for only specific V0's
2783 passing it can be applied.
2784
2785 The input particle list should be a V0 particle list: K_S0 ('K_S0:someLabel', ''),
2786 Lambda ('Lambda:someLabel', '') or converted photons ('gamma:someLabel').
2787
2788 Updating a non-existing mask will create a new one.
2789
2790 - treat tracks from K_S0 inside mass window separately, replace track momenta with K_S0 momentum
2791
2792 .. code-block:: python
2793
2794 optimizeROEWithV0('K_S0:opt', 'mask', '0.450 < M < 0.550', path=mypath)
2795
2796 @param list_name name of the input ParticleList
2797 @param mask_names array of ROEMasks to be updated
2798 @param cut_string decay string with which the mask will be updated
2799 @param path modules are added to this path
2800 """
2801
2802 updateMask = register_module('RestOfEventUpdater')
2803 updateMask.set_name('RestOfEventUpdater_' + list_name + '_masks')
2804 updateMask.param('particleList', list_name)
2805 updateMask.param('updateMasks', mask_names)
2806 updateMask.param('cutString', cut_string)
2807 path.add_module(updateMask)
2808
2809
2810def updateROEUsingV0Lists(target_particle_list, mask_names, default_cleanup=True, selection_cuts=None,
2811 apply_mass_fit=False, fitter='treefit', path=None):
2812 """
2813 This function creates V0 particle lists (photons, :math:`K^0_S` and :math:`\\Lambda^0`)
2814 and it uses V0 candidates to update the Rest Of Event, which is associated to the target particle list.
2815 It is possible to apply a standard or customized selection and mass fit to the V0 candidates.
2816
2817
2818 @param target_particle_list name of the input ParticleList
2819 @param mask_names array of ROE masks to be applied
2820 @param default_cleanup if True, predefined cuts will be applied on the V0 lists
2821 @param selection_cuts a single string of selection cuts or tuple of three strings (photon_cuts, K_S0_cuts, Lambda0_cuts),
2822 which will be applied to the V0 lists. These cuts will have a priority over the default ones.
2823 @param apply_mass_fit if True, a mass fit will be applied to the V0 particles
2824 @param fitter string, that represent a fitter choice: "treefit" for TreeFitter and "kfit" for KFit
2825 @param path modules are added to this path
2826 """
2827
2828 roe_path = create_path()
2829 deadEndPath = create_path()
2830 signalSideParticleFilter(target_particle_list, '', roe_path, deadEndPath)
2831
2832 if (default_cleanup and selection_cuts is None):
2833 B2INFO("Using default cleanup in updateROEUsingV0Lists.")
2834 selection_cuts = 'abs(dM) < 0.1 '
2835 selection_cuts += 'and daughter(0,particleID) > 0.2 and daughter(1,particleID) > 0.2 '
2836 selection_cuts += 'and daughter(0,thetaInCDCAcceptance) and daughter(1,thetaInCDCAcceptance)'
2837 if (selection_cuts is None or selection_cuts == ''):
2838 B2INFO("No cleanup in updateROEUsingV0Lists.")
2839 selection_cuts = ('True', 'True', 'True')
2840 if (isinstance(selection_cuts, str)):
2841 selection_cuts = (selection_cuts, selection_cuts, selection_cuts)
2842 # The isInRestOfEvent variable will be applied on FSPs of composite particles automatically:
2843 roe_cuts = 'isInRestOfEvent > 0'
2844 fillConvertedPhotonsList('gamma:v0_roe -> e+ e-', f'{selection_cuts[0]} and {roe_cuts}',
2845 path=roe_path)
2846 fillParticleList('K_S0:v0_roe -> pi+ pi-', f'{selection_cuts[1]} and {roe_cuts}',
2847 path=roe_path)
2848 fillParticleList('Lambda0:v0_roe -> p+ pi-', f'{selection_cuts[2]} and {roe_cuts}',
2849 path=roe_path)
2850 fitter = fitter.lower()
2851 if (fitter != 'treefit' and fitter != 'kfit'):
2852 B2WARNING('Argument "fitter" in updateROEUsingV0Lists has only "treefit" and "kfit" options, '
2853 f'but "{fitter}" was provided! TreeFitter will be used instead.')
2854 fitter = 'treefit'
2855 from vertex import kFit, treeFit
2856 for v0 in ['gamma:v0_roe', 'K_S0:v0_roe', 'Lambda0:v0_roe']:
2857 if (apply_mass_fit and fitter == 'kfit'):
2858 kFit(v0, conf_level=0.0, fit_type='massvertex', path=roe_path)
2859 if (apply_mass_fit and fitter == 'treefit'):
2860 treeFit(v0, conf_level=0.0, massConstraint=[v0.split(':')[0]], path=roe_path)
2861 optimizeROEWithV0(v0, mask_names, '', path=roe_path)
2862 path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
2863
2864
2865def printROEInfo(mask_names=None, full_print=False,
2866 unpackComposites=True, path=None):
2867 """
2868 This function prints out the information for the current ROE, so it should only be used in the for_each path.
2869 It prints out basic ROE object info.
2870
2871 If mask names are provided, specific information for those masks will be printed out.
2872
2873 It is also possible to print out all particles in a given mask if the
2874 'full_print' is set to True.
2875
2876 @param mask_names array of ROEMask names for printing out info
2877 @param unpackComposites if true, replace composite particles by their daughters
2878 @param full_print print out particles in mask
2879 @param path modules are added to this path
2880 """
2881
2882 if mask_names is None:
2883 mask_names = []
2884 printMask = register_module('RestOfEventPrinter')
2885 printMask.set_name('RestOfEventPrinter')
2886 printMask.param('maskNames', mask_names)
2887 printMask.param('fullPrint', full_print)
2888 printMask.param('unpackComposites', unpackComposites)
2889 path.add_module(printMask)
2890
2891
2892def buildContinuumSuppression(list_name, roe_mask, path):
2893 """
2894 Creates for each Particle in the given ParticleList a ContinuumSuppression
2895 dataobject and makes basf2 relation between them.
2896
2897 :param list_name: name of the input ParticleList
2898 :param roe_mask: name of the ROE mask
2899 :param path: modules are added to this path
2900 """
2901
2902 qqBuilder = register_module('ContinuumSuppressionBuilder')
2903 qqBuilder.set_name('QQBuilder_' + list_name)
2904 qqBuilder.param('particleList', list_name)
2905 qqBuilder.param('ROEMask', roe_mask)
2906 path.add_module(qqBuilder)
2907
2908
2909def removeParticlesNotInLists(lists_to_keep, path):
2910 """
2911 Removes all Particles that are not in a given list of ParticleLists (or daughters of those).
2912 All relations from/to Particles, daughter indices, and other ParticleLists are fixed.
2913
2914 @param lists_to_keep Keep the Particles and their daughters in these ParticleLists.
2915 @param path modules are added to this path
2916 """
2917
2918 mod = register_module('RemoveParticlesNotInLists')
2919 mod.param('particleLists', lists_to_keep)
2920 path.add_module(mod)
2921
2922
2923def inclusiveBtagReconstruction(upsilon_list_name, bsig_list_name, btag_list_name, input_lists_names, path):
2924 """
2925 Reconstructs Btag from particles in given ParticleLists which do not share any final state particles (mdstSource) with Bsig.
2926
2927 @param upsilon_list_name Name of the ParticleList to be filled with 'Upsilon(4S) -> B:sig anti-B:tag'
2928 @param bsig_list_name Name of the Bsig ParticleList
2929 @param btag_list_name Name of the Bsig ParticleList
2930 @param input_lists_names List of names of the ParticleLists which are used to reconstruct Btag from
2931 """
2932
2933 btag = register_module('InclusiveBtagReconstruction')
2934 btag.set_name('InclusiveBtagReconstruction_' + bsig_list_name)
2935 btag.param('upsilonListName', upsilon_list_name)
2936 btag.param('bsigListName', bsig_list_name)
2937 btag.param('btagListName', btag_list_name)
2938 btag.param('inputListsNames', input_lists_names)
2939 path.add_module(btag)
2940
2941
2942def selectDaughters(particle_list_name, decay_string, path):
2943 """
2944 Redefine the Daughters of a particle: select from decayString
2945
2946 @param particle_list_name input particle list
2947 @param decay_string for selecting the Daughters to be preserved
2948 """
2949
2950 seld = register_module('SelectDaughters')
2951 seld.set_name('SelectDaughters_' + particle_list_name)
2952 seld.param('listName', particle_list_name)
2953 seld.param('decayString', decay_string)
2954 path.add_module(seld)
2955
2956
2957def markDuplicate(particleList, prioritiseV0, path):
2958 """
2959 Call DuplicateVertexMarker to find duplicate particles in a list and
2960 flag the ones that should be kept
2961
2962 @param particleList input particle list
2963 @param prioritiseV0 if true, give V0s a higher priority
2964 """
2965
2966 markdup = register_module('DuplicateVertexMarker')
2967 markdup.param('particleList', particleList)
2968 markdup.param('prioritiseV0', prioritiseV0)
2969 path.add_module(markdup)
2970
2971
2972PI0ETAVETO_COUNTER = 0
2973
2974
2975def oldwritePi0EtaVeto(
2976 particleList,
2977 decayString,
2978 workingDirectory='.',
2979 pi0vetoname='Pi0_Prob',
2980 etavetoname='Eta_Prob',
2981 downloadFlag=True,
2982 selection='',
2983 path=None
2984):
2985 """
2986 Give pi0/eta probability for hard photon.
2987
2988 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.
2989
2990 The current default weight files are optimised using MC9.
2991 The input variables are as below. Aliases are set to some variables during training.
2992
2993 * M: pi0/eta candidates Invariant mass
2994 * lowE: soft photon energy in lab frame
2995 * cTheta: soft photon ECL cluster's polar angle
2996 * Zmva: soft photon output of MVA using Zernike moments of the cluster
2997 * minC2Hdist: soft photon distance from eclCluster to nearest point on nearest Helix at the ECL cylindrical radius
2998
2999 If you don't have weight files in your workingDirectory,
3000 these files are downloaded from database to your workingDirectory automatically.
3001 Please refer to analysis/examples/tutorials/B2A306-B02RhoGamma-withPi0EtaVeto.py
3002 about how to use this function.
3003
3004 NOTE:
3005 Please don't use following ParticleList names elsewhere:
3006
3007 ``gamma:HARDPHOTON``, ``pi0:PI0VETO``, ``eta:ETAVETO``,
3008 ``gamma:PI0SOFT + str(PI0ETAVETO_COUNTER)``, ``gamma:ETASOFT + str(PI0ETAVETO_COUNTER)``
3009
3010 Please don't use ``lowE``, ``cTheta``, ``Zmva``, ``minC2Hdist`` as alias elsewhere.
3011
3012 @param particleList The input ParticleList
3013 @param decayString specify Particle to be added to the ParticleList
3014 @param workingDirectory The weight file directory
3015 @param downloadFlag whether download default weight files or not
3016 @param pi0vetoname extraInfo name of pi0 probability
3017 @param etavetoname extraInfo name of eta probability
3018 @param selection Selection criteria that Particle needs meet in order for for_each ROE path to continue
3019 @param path modules are added to this path
3020 """
3021
3022 import b2bii
3023 if b2bii.isB2BII():
3024 B2ERROR("The old pi0 / eta veto is not suitable for Belle analyses.")
3025
3026 import os
3027 import basf2_mva
3028
3029 global PI0ETAVETO_COUNTER
3030
3031 if PI0ETAVETO_COUNTER == 0:
3032 from variables import variables
3033 variables.addAlias('lowE', 'daughter(1,E)')
3034 variables.addAlias('cTheta', 'daughter(1,clusterTheta)')
3035 variables.addAlias('Zmva', 'daughter(1,clusterZernikeMVA)')
3036 variables.addAlias('minC2Tdist', 'daughter(1,minC2TDist)')
3037 variables.addAlias('cluNHits', 'daughter(1,clusterNHits)')
3038 variables.addAlias('E9E21', 'daughter(1,clusterE9E21)')
3039
3040 PI0ETAVETO_COUNTER = PI0ETAVETO_COUNTER + 1
3041
3042 roe_path = create_path()
3043
3044 deadEndPath = create_path()
3045
3046 signalSideParticleFilter(particleList, selection, roe_path, deadEndPath)
3047
3048 fillSignalSideParticleList('gamma:HARDPHOTON', decayString, path=roe_path)
3049
3050 pi0softname = 'gamma:PI0SOFT'
3051 etasoftname = 'gamma:ETASOFT'
3052 softphoton1 = pi0softname + str(PI0ETAVETO_COUNTER)
3053 softphoton2 = etasoftname + str(PI0ETAVETO_COUNTER)
3054
3055 fillParticleList(
3056 softphoton1,
3057 '[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]',
3058 path=roe_path)
3059 applyCuts(softphoton1, 'abs(clusterTiming)<120', path=roe_path)
3060 fillParticleList(
3061 softphoton2,
3062 '[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]',
3063 path=roe_path)
3064 applyCuts(softphoton2, 'abs(clusterTiming)<120', path=roe_path)
3065
3066 reconstructDecay('pi0:PI0VETO -> gamma:HARDPHOTON ' + softphoton1, '', path=roe_path)
3067 reconstructDecay('eta:ETAVETO -> gamma:HARDPHOTON ' + softphoton2, '', path=roe_path)
3068
3069 if not os.path.isdir(workingDirectory):
3070 os.mkdir(workingDirectory)
3071 B2INFO('oldwritePi0EtaVeto: ' + workingDirectory + ' has been created as workingDirectory.')
3072
3073 if not os.path.isfile(workingDirectory + '/pi0veto.root'):
3074 if downloadFlag:
3075 basf2_mva.download('Pi0VetoIdentifier', workingDirectory + '/pi0veto.root')
3076 B2INFO('oldwritePi0EtaVeto: pi0veto.root has been downloaded from database to workingDirectory.')
3077
3078 if not os.path.isfile(workingDirectory + '/etaveto.root'):
3079 if downloadFlag:
3080 basf2_mva.download('EtaVetoIdentifier', workingDirectory + '/etaveto.root')
3081 B2INFO('oldwritePi0EtaVeto: etaveto.root has been downloaded from database to workingDirectory.')
3082
3083 roe_path.add_module('MVAExpert', listNames=['pi0:PI0VETO'], extraInfoName='Pi0Veto',
3084 identifier=workingDirectory + '/pi0veto.root')
3085 roe_path.add_module('MVAExpert', listNames=['eta:ETAVETO'], extraInfoName='EtaVeto',
3086 identifier=workingDirectory + '/etaveto.root')
3087
3088 rankByHighest('pi0:PI0VETO', 'extraInfo(Pi0Veto)', numBest=1, path=roe_path)
3089 rankByHighest('eta:ETAVETO', 'extraInfo(EtaVeto)', numBest=1, path=roe_path)
3090
3091 variableToSignalSideExtraInfo('pi0:PI0VETO', {'extraInfo(Pi0Veto)': pi0vetoname}, path=roe_path)
3092 variableToSignalSideExtraInfo('eta:ETAVETO', {'extraInfo(EtaVeto)': etavetoname}, path=roe_path)
3093
3094 path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
3095
3096
3097def writePi0EtaVeto(
3098 particleList,
3099 decayString,
3100 mode='standard',
3101 selection='',
3102 path=None,
3103 suffix='',
3104 hardParticle='gamma',
3105 pi0PayloadNameOverride=None,
3106 pi0SoftPhotonCutOverride=None,
3107 etaPayloadNameOverride=None,
3108 etaSoftPhotonCutOverride=None
3109):
3110 """
3111 Give pi0/eta probability for hard photon.
3112
3113 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.
3114
3115 The current default weight files are optimised using MC12.
3116
3117 The input variables of the mva training are:
3118
3119 * M: pi0/eta candidates Invariant mass
3120 * daughter(1,E): soft photon energy in lab frame
3121 * daughter(1,clusterTheta): soft photon ECL cluster's polar angle
3122 * daughter(1,minC2TDist): soft photon distance from eclCluster to nearest point on nearest Helix at the ECL cylindrical radius
3123 * daughter(1,clusterZernikeMVA): soft photon output of MVA using Zernike moments of the cluster
3124 * daughter(1,clusterNHits): soft photon total crystal weights sum(w_i) with w_i<=1
3125 * daughter(1,clusterE9E21): soft photon ratio of energies in inner 3x3 crystals and 5x5 crystals without corners
3126 * cosHelicityAngleMomentum: pi0/eta candidates cosHelicityAngleMomentum
3127
3128 The following strings are available for mode:
3129
3130 * standard: loose energy cut and no clusterNHits cut are applied to soft photon
3131 * tight: tight energy cut and no clusterNHits cut are applied to soft photon
3132 * cluster: loose energy cut and clusterNHits cut are applied to soft photon
3133 * both: tight energy cut and clusterNHits cut are applied to soft photon
3134
3135 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
3136 `pi0Prob`/`etaProb`. Otherwise, it is available as '{Pi0, Eta}ProbOrigin', '{Pi0, Eta}ProbTightEnergyThreshold', '{Pi0,
3137 Eta}ProbLargeClusterSize', or '{Pi0, Eta}ProbTightEnergyThresholdAndLargeClusterSize'} for the four modes described above, with
3138 the chosen suffix appended.
3139
3140 NOTE:
3141 Please don't use following ParticleList names elsewhere:
3142
3143 ``gamma:HardPhoton``,
3144 ``gamma:Pi0Soft + ListName + '_' + particleList.replace(':', '_')``,
3145 ``gamma:EtaSoft + ListName + '_' + particleList.replace(':', '_')``,
3146 ``pi0:EtaVeto + ListName``,
3147 ``eta:EtaVeto + ListName``
3148
3149 @param particleList the input ParticleList
3150 @param decayString specify Particle to be added to the ParticleList
3151 @param mode choose one mode out of 'standard', 'tight', 'cluster' and 'both'
3152 @param selection selection criteria that Particle needs meet in order for for_each ROE path to continue
3153 @param path modules are added to this path
3154 @param suffix optional suffix to be appended to the usual extraInfo name
3155 @param hardParticle particle name which is used to calculate the pi0/eta probability (default is gamma)
3156 @param pi0PayloadNameOverride specify the payload name of pi0 veto only if one wants to use non-default one. (default is None)
3157 @param pi0SoftPhotonCutOverride specify the soft photon selection criteria of pi0 veto only if one wants to use non-default one.
3158 (default is None)
3159 @param etaPayloadNameOverride specify the payload name of eta veto only if one wants to use non-default one. (default is None)
3160 @param etaSoftPhotonCutOverride specify the soft photon selection criteria of eta veto only if one wants to use non-default one.
3161 (default is None)
3162 """
3163
3164 import b2bii
3165 if b2bii.isB2BII():
3166 B2ERROR("The pi0 / eta veto is not suitable for Belle analyses.")
3167
3168 renameSuffix = False
3169
3170 for module in path.modules():
3171 if module.type() == "SubEvent" and not renameSuffix:
3172 for subpath in [p.values for p in module.available_params() if p.name == "path"]:
3173 if renameSuffix:
3174 break
3175 for submodule in subpath.modules():
3176 if f'{hardParticle}:HardPhoton{suffix}' in submodule.name():
3177 suffix += '_0'
3178 B2WARNING("Same extension already used in writePi0EtaVeto, append '_0'")
3179 renameSuffix = True
3180 break
3181
3182 roe_path = create_path()
3183 deadEndPath = create_path()
3184 signalSideParticleFilter(particleList, selection, roe_path, deadEndPath)
3185 fillSignalSideParticleList(f'{hardParticle}:HardPhoton{suffix}', decayString, path=roe_path)
3186
3187 dictListName = {'standard': 'Origin',
3188 'tight': 'TightEnergyThreshold',
3189 'cluster': 'LargeClusterSize',
3190 'both': 'TightEnrgyThresholdAndLargeClusterSize'}
3191
3192 dictPi0EnergyCut = {'standard': '[[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]]',
3193 'tight': '[[clusterReg==1 and E>0.03] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.04]]',
3194 'cluster': '[[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]]',
3195 'both': '[[clusterReg==1 and E>0.03] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.04]]'}
3196
3197 dictEtaEnergyCut = {'standard': '[[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]]',
3198 'tight': '[[clusterReg==1 and E>0.06] or [clusterReg==2 and E>0.06] or [clusterReg==3 and E>0.06]]',
3199 'cluster': '[[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]]',
3200 'both': '[[clusterReg==1 and E>0.06] or [clusterReg==2 and E>0.06] or [clusterReg==3 and E>0.06]]'}
3201
3202 dictNHitsCut = {'standard': 'clusterNHits >= 0',
3203 'tight': 'clusterNHits >= 0',
3204 'cluster': 'clusterNHits >= 2',
3205 'both': 'clusterNHits >= 2'}
3206
3207 dictPi0PayloadName = {'standard': 'Pi0VetoIdentifierStandard',
3208 'tight': 'Pi0VetoIdentifierWithHigherEnergyThreshold',
3209 'cluster': 'Pi0VetoIdentifierWithLargerClusterSize',
3210 'both': 'Pi0VetoIdentifierWithHigherEnergyThresholdAndLargerClusterSize'}
3211
3212 dictEtaPayloadName = {'standard': 'EtaVetoIdentifierStandard',
3213 'tight': 'EtaVetoIdentifierWithHigherEnergyThreshold',
3214 'cluster': 'EtaVetoIdentifierWithLargerClusterSize',
3215 'both': 'EtaVetoIdentifierWithHigherEnergyThresholdAndLargerClusterSize'}
3216
3217 dictPi0ExtraInfoName = {'standard': 'Pi0ProbOrigin',
3218 'tight': 'Pi0ProbTightEnergyThreshold',
3219 'cluster': 'Pi0ProbLargeClusterSize',
3220 'both': 'Pi0ProbTightEnergyThresholdAndLargeClusterSize'}
3221
3222 dictEtaExtraInfoName = {'standard': 'EtaProbOrigin',
3223 'tight': 'EtaProbTightEnergyThreshold',
3224 'cluster': 'EtaProbLargeClusterSize',
3225 'both': 'EtaProbTightEnergyThresholdAndLargeClusterSize'}
3226
3227 ListName = dictListName[mode]
3228 Pi0EnergyCut = dictPi0EnergyCut[mode]
3229 EtaEnergyCut = dictEtaEnergyCut[mode]
3230 TimingCut = 'abs(clusterTiming)<clusterErrorTiming'
3231 NHitsCut = dictNHitsCut[mode]
3232 Pi0PayloadName = dictPi0PayloadName[mode]
3233 EtaPayloadName = dictEtaPayloadName[mode]
3234 Pi0ExtraInfoName = dictPi0ExtraInfoName[mode]
3235 EtaExtraInfoName = dictEtaExtraInfoName[mode]
3236
3237 # pi0 veto
3238 if pi0PayloadNameOverride is not None:
3239 Pi0PayloadName = pi0PayloadNameOverride
3240 if pi0SoftPhotonCutOverride is None:
3241 Pi0SoftPhotonCut = Pi0EnergyCut + ' and ' + NHitsCut
3242 import b2bii
3243 if not b2bii.isB2BII():
3244 # timing cut is only valid for Belle II but not for B2BII
3245 Pi0SoftPhotonCut += ' and ' + TimingCut
3246 else:
3247 Pi0SoftPhotonCut = pi0SoftPhotonCutOverride
3248
3249 # define the particleList name for soft photon
3250 pi0soft = f'gamma:Pi0Soft{suffix}' + ListName + '_' + particleList.replace(':', '_')
3251 # fill the particleList for soft photon with energy, timing and clusterNHits cuts
3252 fillParticleList(pi0soft, Pi0SoftPhotonCut, path=roe_path)
3253 # reconstruct pi0
3254 reconstructDecay('pi0:Pi0Veto' + ListName + f' -> {hardParticle}:HardPhoton{suffix} ' + pi0soft, '',
3255 allowChargeViolation=True, path=roe_path)
3256 # MVA training is conducted.
3257 roe_path.add_module('MVAExpert', listNames=['pi0:Pi0Veto' + ListName],
3258 extraInfoName=Pi0ExtraInfoName, identifier=Pi0PayloadName)
3259 # Pick up only one pi0/eta candidate with the highest pi0/eta probability.
3260 rankByHighest('pi0:Pi0Veto' + ListName, 'extraInfo(' + Pi0ExtraInfoName + ')', numBest=1, path=roe_path)
3261 # 'extraInfo(Pi0Veto)' is labeled 'Pi0_Prob'
3262 variableToSignalSideExtraInfo('pi0:Pi0Veto' + ListName,
3263 {'extraInfo(' + Pi0ExtraInfoName + ')': Pi0ExtraInfoName + suffix}, path=roe_path)
3264
3265 # eta veto
3266 if etaPayloadNameOverride is not None:
3267 EtaPayloadName = etaPayloadNameOverride
3268 if etaSoftPhotonCutOverride is None:
3269 EtaSoftPhotonCut = EtaEnergyCut + ' and ' + NHitsCut
3270 import b2bii
3271 if not b2bii.isB2BII():
3272 # timing cut is only valid for Belle II but not for B2BII
3273 EtaSoftPhotonCut += ' and ' + TimingCut
3274 else:
3275 EtaSoftPhotonCut = etaSoftPhotonCutOverride
3276
3277 etasoft = f'gamma:EtaSoft{suffix}' + ListName + '_' + particleList.replace(':', '_')
3278 fillParticleList(etasoft, EtaSoftPhotonCut, path=roe_path)
3279 reconstructDecay('eta:EtaVeto' + ListName + f' -> {hardParticle}:HardPhoton{suffix} ' + etasoft, '',
3280 allowChargeViolation=True, path=roe_path)
3281 roe_path.add_module('MVAExpert', listNames=['eta:EtaVeto' + ListName],
3282 extraInfoName=EtaExtraInfoName, identifier=EtaPayloadName)
3283 rankByHighest('eta:EtaVeto' + ListName, 'extraInfo(' + EtaExtraInfoName + ')', numBest=1, path=roe_path)
3284 variableToSignalSideExtraInfo('eta:EtaVeto' + ListName,
3285 {'extraInfo(' + EtaExtraInfoName + ')': EtaExtraInfoName + suffix}, path=roe_path)
3286
3287 path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
3288
3289
3290def lowEnergyPi0Identification(pi0List, gammaList, payloadNameSuffix,
3291 path=None):
3292 """
3293 Calculate low-energy pi0 identification.
3294 The result is stored as ExtraInfo ``lowEnergyPi0Identification`` for
3295 the list pi0List.
3296
3297 Parameters:
3298 pi0List (str): Pi0 list.
3299
3300 gammaList (str): Gamma list. First, an energy cut E > 0.2 is applied to the photons from this list.
3301 Then, all possible combinations with a pi0 daughter photon are formed except the one
3302 corresponding to the reconstructed pi0.
3303 The maximum low-energy pi0 veto value is calculated for such photon pairs
3304 and used as one of the input variables for the identification classifier.
3305
3306 payloadNameSuffix (str): Payload name suffix. The weight payloads are stored in the analysis global
3307 tag and have the following names:\n
3308 * ``'LowEnergyPi0Veto' + payloadNameSuffix``
3309 * ``'LowEnergyPi0Identification' + payloadNameSuffix``\n
3310 The possible suffixes are:\n
3311 * ``'Belle1'`` for Belle data.
3312 * ``'Belle2Release5'`` for Belle II release 5 data (MC14, proc12, buckets 16 - 25).
3313 * ``'Belle2Release6'`` for Belle II release 6 data (MC15, proc13, buckets 26 - 36).
3314
3315 path (basf2.Path): Module path.
3316 """
3317
3318 # Select photons with higher energy for formation of veto combinations.
3319 gammaListVeto = f'{gammaList}_pi0veto'
3320 cutAndCopyList(gammaListVeto, gammaList, 'E > 0.2', path=path)
3321 import b2bii
3322 payload_name = 'LowEnergyPi0Veto' + payloadNameSuffix
3323 path.add_module('LowEnergyPi0VetoExpert', identifier=payload_name,
3324 VetoPi0Daughters=True, GammaListName=gammaListVeto,
3325 Pi0ListName=pi0List, Belle1=b2bii.isB2BII())
3326 payload_name = 'LowEnergyPi0Identification' + payloadNameSuffix
3327 path.add_module('LowEnergyPi0IdentificationExpert',
3328 identifier=payload_name, Pi0ListName=pi0List,
3329 Belle1=b2bii.isB2BII())
3330
3331
3332def getNeutralHadronGeomMatches(
3333 particleLists,
3334 addKL=True,
3335 addNeutrons=False,
3336 efficiencyCorrectionKl=0.83,
3337 efficiencyCorrectionNeutrons=1.0,
3338 path=None):
3339 """
3340 For an ECL-based list, assign the mcdistanceKL and mcdistanceNeutron variables that correspond
3341 to the distance to the closest MC KL and neutron, respectively.
3342 @param particleLists the input ParticleLists, must be ECL-based lists (e.g. photons)
3343 @param addKL (default True) add distance to MC KL
3344 @param addNeutrons (default False) add distance to MC neutrons
3345 @param efficiencyCorrectionKl (default 0.83) apply overall efficiency correction
3346 @param efficiencyCorrectionNeutrons (default 1.0) apply overall efficiency correction
3347 @param path modules are added to this path
3348 """
3349 from ROOT import Belle2
3350 Const = Belle2.Const
3351
3352 if addKL:
3353 path.add_module(
3354 "NeutralHadronMatcher",
3355 particleLists=particleLists,
3356 mcPDGcode=Const.Klong.getPDGCode(),
3357 efficiencyCorrection=efficiencyCorrectionKl)
3358 if addNeutrons:
3359 path.add_module(
3360 "NeutralHadronMatcher",
3361 particleLists=particleLists,
3362 mcPDGcode=Const.neutron.getPDGCode(),
3363 efficiencyCorrection=efficiencyCorrectionNeutrons)
3364
3365
3366def getBeamBackgroundProbability(particleList, weight, path=None):
3367 """
3368 Assign a probability to each ECL cluster as being signal like (1) compared to beam background like (0)
3369 @param particleList the input ParticleList, must be a photon list
3370 @param weight type of weight file to use
3371 @param path modules are added to this path
3372 """
3373
3374 import b2bii
3375 if b2bii.isB2BII() and weight != "Belle":
3376 B2WARNING("weight type must be 'Belle' for b2bii.")
3377
3378 path.add_module('MVAExpert',
3379 listNames=particleList,
3380 extraInfoName='beamBackgroundSuppression',
3381 identifier=f'BeamBackgroundMVA_{weight}')
3382
3383
3384def getFakePhotonProbability(particleList, weight, path=None):
3385 """
3386 Assign a probability to each ECL cluster as being signal like (1) compared to fake photon like (0)
3387 @param particleList the input ParticleList, must be a photon list
3388 @param weight type of weight file to use
3389 @param path modules are added to this path
3390 """
3391
3392 import b2bii
3393 if b2bii.isB2BII() and weight != "Belle":
3394 B2WARNING("weight type must be 'Belle' for b2bii.")
3395
3396 path.add_module('MVAExpert',
3397 listNames=particleList,
3398 extraInfoName='fakePhotonSuppression',
3399 identifier=f'FakePhotonMVA_{weight}')
3400
3401
3402def buildEventKinematics(inputListNames=None, default_cleanup=True, custom_cuts=None,
3403 chargedPIDPriors=None, fillWithMostLikely=False, path=None):
3404 """
3405 Calculates the global kinematics of the event (visible energy, missing momentum, missing mass...)
3406 using ParticleLists provided. If no ParticleList is provided, default ParticleLists are used
3407 (all track and all hits in ECL without associated track).
3408
3409 The visible energy missing values are
3410 stored in a EventKinematics dataobject.
3411
3412 @param inputListNames list of ParticleLists used to calculate the global event kinematics.
3413 If the list is empty, default ParticleLists pi+:evtkin and gamma:evtkin are filled.
3414 @param fillWithMostLikely if True, the module uses the most likely particle mass hypothesis for charged particles
3415 according to the PID likelihood and the option inputListNames will be ignored.
3416 @param chargedPIDPriors The prior PID fractions, that are used to regulate
3417 amount of certain charged particle species, should be a list of
3418 six floats if not None. The order of particle types is
3419 the following: [e-, mu-, pi-, K-, p+, d+]
3420 @param default_cleanup if True and either inputListNames empty or fillWithMostLikely True, default clean up cuts are applied
3421 @param custom_cuts tuple of selection cut strings of form (trackCuts, photonCuts), default is None,
3422 which would result in a standard predefined selection cuts
3423 @param path modules are added to this path
3424 """
3425
3426 if inputListNames is None:
3427 inputListNames = []
3428 trackCuts = 'pt > 0.1'
3429 trackCuts += ' and thetaInCDCAcceptance'
3430 trackCuts += ' and abs(dz) < 3'
3431 trackCuts += ' and dr < 0.5'
3432
3433 gammaCuts = 'E > 0.05'
3434 gammaCuts += ' and thetaInCDCAcceptance'
3435 if not b2bii.isB2BII():
3436 gammaCuts += ' and abs(clusterTiming) < 200'
3437 if (custom_cuts is not None):
3438 trackCuts, gammaCuts = custom_cuts
3439
3440 if fillWithMostLikely:
3441 from stdCharged import stdMostLikely
3442 stdMostLikely(chargedPIDPriors, '_evtkin', path=path)
3443 inputListNames = [f'{ptype}:mostlikely_evtkin' for ptype in ['K+', 'p+', 'e+', 'mu+', 'pi+']]
3444 if b2bii.isB2BII():
3445 copyList('gamma:evtkin', 'gamma:mdst', path=path)
3446 else:
3447 fillParticleList('gamma:evtkin', '', path=path)
3448 inputListNames += ['gamma:evtkin']
3449 if default_cleanup:
3450 B2INFO("Using default cleanup in EventKinematics module.")
3451 for ptype in ['K+', 'p+', 'e+', 'mu+', 'pi+']:
3452 applyCuts(f'{ptype}:mostlikely_evtkin', trackCuts, path=path)
3453 applyCuts('gamma:evtkin', gammaCuts, path=path)
3454 else:
3455 B2INFO("No cleanup in EventKinematics module.")
3456 if not inputListNames:
3457 B2INFO("Creating particle lists pi+:evtkin and gamma:evtkin to get the global kinematics of the event.")
3458 fillParticleList('pi+:evtkin', '', path=path)
3459 if b2bii.isB2BII():
3460 copyList('gamma:evtkin', 'gamma:mdst', path=path)
3461 else:
3462 fillParticleList('gamma:evtkin', '', path=path)
3463 particleLists = ['pi+:evtkin', 'gamma:evtkin']
3464 if default_cleanup:
3465 if (custom_cuts is not None):
3466 B2INFO("Using default cleanup in EventKinematics module.")
3467 applyCuts('pi+:evtkin', trackCuts, path=path)
3468 applyCuts('gamma:evtkin', gammaCuts, path=path)
3469 else:
3470 B2INFO("No cleanup in EventKinematics module.")
3471 else:
3472 particleLists = inputListNames
3473
3474 eventKinematicsModule = register_module('EventKinematics')
3475 eventKinematicsModule.set_name('EventKinematics_reco')
3476 eventKinematicsModule.param('particleLists', particleLists)
3477 path.add_module(eventKinematicsModule)
3478
3479
3480def buildEventKinematicsFromMC(inputListNames=None, selectionCut='', path=None):
3481 """
3482 Calculates the global kinematics of the event (visible energy, missing momentum, missing mass...)
3483 using generated particles. If no ParticleList is provided, default generated ParticleLists are used.
3484
3485 @param inputListNames list of ParticleLists used to calculate the global event kinematics.
3486 If the list is empty, default ParticleLists are filled.
3487 @param selectionCut optional selection cuts
3488 @param path Path to append the eventKinematics module to.
3489 """
3490
3491 if inputListNames is None:
3492 inputListNames = []
3493 if (len(inputListNames) == 0):
3494 # Type of particles to use for EventKinematics
3495 # K_S0 and Lambda0 are added here because some of them have interacted
3496 # with the detector material
3497 types = ['gamma', 'e+', 'mu+', 'pi+', 'K+', 'p+',
3498 'K_S0', 'Lambda0']
3499 for t in types:
3500 fillParticleListFromMC(f"{t}:evtkin_default_gen", 'mcPrimary > 0 and nDaughters == 0',
3501 True, True, path=path)
3502 if (selectionCut != ''):
3503 applyCuts(f"{t}:evtkin_default_gen", selectionCut, path=path)
3504 inputListNames += [f"{t}:evtkin_default_gen"]
3505
3506 eventKinematicsModule = register_module('EventKinematics')
3507 eventKinematicsModule.set_name('EventKinematics_gen')
3508 eventKinematicsModule.param('particleLists', inputListNames)
3509 eventKinematicsModule.param('usingMC', True)
3510 path.add_module(eventKinematicsModule)
3511
3512
3513def buildEventShape(inputListNames=None,
3514 default_cleanup=True,
3515 custom_cuts=None,
3516 allMoments=False,
3517 cleoCones=True,
3518 collisionAxis=True,
3519 foxWolfram=True,
3520 harmonicMoments=True,
3521 jets=True,
3522 sphericity=True,
3523 thrust=True,
3524 checkForDuplicates=False,
3525 path=None):
3526 """
3527 Calculates the event-level shape quantities (thrust, sphericity, Fox-Wolfram moments...)
3528 using the particles in the lists provided by the user. If no particle list is provided,
3529 the function will internally create a list of good tracks and a list of good photons
3530 with (optionally) minimal quality cuts.
3531
3532
3533 The results of the calculation are then stored into the EventShapeContainer dataobject,
3534 and are accessible using the variables of the EventShape group.
3535
3536 The user can switch the calculation of certain quantities on or off to save computing
3537 time. By default the calculation of the high-order moments (5-8) is turned off.
3538 Switching off an option will make the corresponding variables not available.
3539
3540 Warning:
3541 The user can provide as many particle lists
3542 as needed, using also combined particles, but the function will always assume that
3543 the lists are independent.
3544 If the lists provided by the user contain several times the same track (either with
3545 different mass hypothesis, or once as an independent particle and once as daughter of a
3546 combined particle) the results won't be reliable.
3547 A basic check for duplicates is available setting the checkForDuplicate flags.
3548
3549
3550 @param inputListNames List of ParticleLists used to calculate the
3551 event shape variables. If the list is empty the default
3552 particleLists pi+:evtshape and gamma:evtshape are filled.
3553 @param default_cleanup If True, applies standard cuts on pt and cosTheta when
3554 defining the internal lists. This option is ignored if the
3555 particleLists are provided by the user.
3556 @param custom_cuts tuple of selection cut strings of form (trackCuts, photonCuts), default is None,
3557 which would result in a standard predefined selection cuts
3558 @param path Path to append the eventShape modules to.
3559 @param thrust Enables the calculation of thrust-related quantities (CLEO
3560 cones, Harmonic moments, jets).
3561 @param collisionAxis Enables the calculation of the quantities related to the
3562 collision axis .
3563 @param foxWolfram Enables the calculation of the Fox-Wolfram moments.
3564 @param harmonicMoments Enables the calculation of the Harmonic moments with respect
3565 to both the thrust axis and, if collisionAxis = True, the collision axis.
3566 @param allMoments If True, calculates also the FW and harmonic moments from order
3567 5 to 8 instead of the low-order ones only.
3568 @param cleoCones Enables the calculation of the CLEO cones with respect to both the thrust
3569 axis and, if collisionAxis = True, the collision axis.
3570 @param jets Enables the calculation of the hemisphere momenta and masses.
3571 Requires thrust = True.
3572 @param sphericity Enables the calculation of the sphericity-related quantities.
3573 @param checkForDuplicates Perform a check for duplicate particles before adding them. Regardless of the value of this option,
3574 it is recommended to consider sanitizing the lists you are passing to the function.
3575
3576 """
3577
3578 if inputListNames is None:
3579 inputListNames = []
3580 trackCuts = 'pt > 0.1'
3581 trackCuts += ' and thetaInCDCAcceptance'
3582 trackCuts += ' and abs(dz) < 3.0'
3583 trackCuts += ' and dr < 0.5'
3584
3585 gammaCuts = 'E > 0.05'
3586 gammaCuts += ' and thetaInCDCAcceptance'
3587 if not b2bii.isB2BII():
3588 gammaCuts += ' and abs(clusterTiming) < 200'
3589 if (custom_cuts is not None):
3590 trackCuts, gammaCuts = custom_cuts
3591
3592 if not inputListNames:
3593 B2INFO("Creating particle lists pi+:evtshape and gamma:evtshape to get the event shape variables.")
3594 fillParticleList('pi+:evtshape', '', path=path)
3595 if b2bii.isB2BII():
3596 copyList('gamma:evtshape', 'gamma:mdst', path=path)
3597 else:
3598 fillParticleList(
3599 'gamma:evtshape',
3600 '',
3601 path=path)
3602 particleLists = ['pi+:evtshape', 'gamma:evtshape']
3603
3604 if default_cleanup:
3605 if (custom_cuts is not None):
3606 B2INFO("Applying standard cuts")
3607 applyCuts('pi+:evtshape', trackCuts, path=path)
3608
3609 applyCuts('gamma:evtshape', gammaCuts, path=path)
3610 else:
3611 B2WARNING("Creating the default lists with no cleanup.")
3612 else:
3613 particleLists = inputListNames
3614
3615 eventShapeModule = register_module('EventShapeCalculator')
3616 eventShapeModule.set_name('EventShape')
3617 eventShapeModule.param('particleListNames', particleLists)
3618 eventShapeModule.param('enableAllMoments', allMoments)
3619 eventShapeModule.param('enableCleoCones', cleoCones)
3620 eventShapeModule.param('enableCollisionAxis', collisionAxis)
3621 eventShapeModule.param('enableFoxWolfram', foxWolfram)
3622 eventShapeModule.param('enableJets', jets)
3623 eventShapeModule.param('enableHarmonicMoments', harmonicMoments)
3624 eventShapeModule.param('enableSphericity', sphericity)
3625 eventShapeModule.param('enableThrust', thrust)
3626 eventShapeModule.param('checkForDuplicates', checkForDuplicates)
3627
3628 path.add_module(eventShapeModule)
3629
3630
3631def labelTauPairMC(printDecayInfo=False, path=None, TauolaBelle=False, mapping_minus=None, mapping_plus=None):
3632 """
3633 Search tau leptons into the MC information of the event. If confirms it's a generated tau pair decay,
3634 labels the decay generated of the positive and negative leptons using the ID of KKMC tau decay table.
3635
3636 @param printDecayInfo: If true, prints ID and prong of each tau lepton in the event.
3637 @param path: module is added to this path
3638 @param TauolaBelle: if False, TauDecayMode is set. If True, TauDecayMarker is set.
3639 @param mapping_minus: if None, the map is the default one, else the path for the map is given by the user for tau-
3640 @param mapping_plus: if None, the map is the default one, else the path for the map is given by the user for tau+
3641 """
3642
3643 from basf2 import find_file
3644 if not TauolaBelle:
3645
3646 if printDecayInfo:
3647 m_printmode = 'all'
3648 else:
3649 m_printmode = 'default'
3650
3651 if mapping_minus is None:
3652 mp_file_minus = find_file('data/analysis/modules/TauDecayMode/map_tauminus.txt')
3653 else:
3654 mp_file_minus = mapping_minus
3655
3656 if mapping_plus is None:
3657 mp_file_plus = find_file('data/analysis/modules/TauDecayMode/map_tauplus.txt')
3658 else:
3659 mp_file_plus = mapping_plus
3660
3661 path.add_module('TauDecayMode', printmode=m_printmode, file_minus=mp_file_minus, file_plus=mp_file_plus)
3662
3663 else:
3664 tauDecayMarker = register_module('TauDecayMarker')
3665 tauDecayMarker.set_name('TauDecayMarker_')
3666
3667 path.add_module(tauDecayMarker, printDecayInfo=printDecayInfo)
3668
3669
3670def tagCurlTracks(particleLists,
3671 mcTruth=False,
3672 responseCut=-1.0,
3673 selectorType='cut',
3674 ptCut=0.5,
3675 expert_train=False,
3676 expert_filename="",
3677 path=None):
3678 """
3679 Warning:
3680 The cut selector is not calibrated with Belle II data and should not be used without extensive study.
3681
3682 Identifies curl tracks and tags them with extraInfo(isCurl=1) for later removal.
3683 For Belle data with a `b2bii` analysis the available cut based selection is described in `BN1079`_.
3684
3685 .. _BN1079: https://belle.kek.jp/secured/belle_note/gn1079/bn1079.pdf
3686
3687
3688 The module loops over all particles in a given list with a transverse momentum below the pre-selection **ptCut**
3689 and assigns them to bundles based on the response of the chosen **selector** and the required minimum response set by the
3690 **responseCut**. Once all particles are assigned they are ranked by 25dr^2+dz^2. All but the lowest are tagged
3691 with extraInfo(isCurl=1) to allow for later removal by cutting the list or removing these from ROE as
3692 applicable.
3693
3694
3695 @param particleLists: list of particle lists to check for curls.
3696 @param mcTruth: bool flag to additionally assign particles with extraInfo(isTruthCurl) and
3697 extraInfo(truthBundleSize). To calculate these particles are assigned to bundles by their
3698 genParticleIndex then ranked and tagged as normal.
3699 @param responseCut: float min classifier response that considers two tracks to come from the same particle.
3700 If set to ``-1`` a cut value optimised to maximise the accuracy on a BBbar sample is used.
3701 Note 'cut' selector is binary 0/1.
3702 @param selectorType: string name of selector to use. The available options are 'cut' and 'mva'.
3703 It is strongly recommended to used the 'mva' selection. The 'cut' selection
3704 is based on BN1079 and is only calibrated for Belle data.
3705
3706 @param ptCut: Pre-selection cut on transverse momentum. Only tracks below that are considered as curler candidates.
3707
3708 @param expert_train: flag to set training mode if selector has a training mode (mva).
3709 @param expert_filename: set file name of produced training ntuple (mva).
3710 @param path: module is added to this path.
3711 """
3712
3713 import b2bii
3714 belle = b2bii.isB2BII()
3715
3716 if (not isinstance(particleLists, list)):
3717 particleLists = [particleLists] # in case user inputs a particle list as string
3718
3719 curlTagger = register_module('CurlTagger')
3720 curlTagger.set_name('CurlTagger_')
3721 curlTagger.param('particleLists', particleLists)
3722 curlTagger.param('belle', belle)
3723 curlTagger.param('mcTruth', mcTruth)
3724 curlTagger.param('responseCut', responseCut)
3725 if abs(responseCut + 1) < 1e-9:
3726 curlTagger.param('usePayloadCut', True)
3727 else:
3728 curlTagger.param('usePayloadCut', False)
3729
3730 curlTagger.param('selectorType', selectorType)
3731 curlTagger.param('ptCut', ptCut)
3732 curlTagger.param('train', expert_train)
3733 curlTagger.param('trainFilename', expert_filename)
3734
3735 path.add_module(curlTagger)
3736
3737
3738def applyChargedPidMVA(particleLists, path, trainingMode, chargeIndependent=False, binaryHypoPDGCodes=(0, 0)):
3739 """
3740 Use an MVA to perform particle identification for charged stable particles, using the `ChargedPidMVA` module.
3741
3742 The module decorates Particle objects in the input ParticleList(s) with variables
3743 containing the appropriate MVA score, which can be used to select candidates by placing a cut on it.
3744
3745 Note:
3746 The MVA algorithm used is a gradient boosted decision tree (**TMVA 4.3.0**, **ROOT 6.20/04**).
3747
3748 The module can perform either 'binary' PID between input S, B particle mass hypotheses according to the following scheme:
3749
3750 * e (11) vs. pi (211)
3751 * mu (13) vs. pi (211)
3752 * pi (211) vs. K (321)
3753 * K (321) vs. pi (211)
3754
3755 , or 'global' PID, namely "one-vs-others" separation. The latter exploits an MVA algorithm trained in multi-class mode,
3756 and it's the default behaviour. Currently, the multi-class training separates the following standard charged hypotheses:
3757
3758 - e (11), mu (13), pi (211), K (321)
3759
3760 Warning:
3761 In order to run the `ChargedPidMVA` and ensure the most up-to-date MVA training weights are applied,
3762 it is necessary to append the latest analysis global tag (GT) to the steering script.
3763
3764 Parameters:
3765 particleLists (list(str)): the input list of DecayStrings, where each selected (^) daughter should correspond to a
3766 standard charged ParticleList, e.g. ``['Lambda0:sig -> ^p+ ^pi-', 'J/psi:sig -> ^mu+ ^mu-']``.
3767 One can also directly pass a list of standard charged ParticleLists,
3768 e.g. ``['e+:my_electrons', 'pi+:my_pions']``.
3769 Note that charge-conjugated ParticleLists will automatically be included.
3770 path (basf2.Path): the module is added to this path.
3771 trainingMode (``Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode``): enum identifier of the training mode.
3772 Needed to pick up the correct payload from the DB. Available choices:
3773
3774 * c_Classification=0
3775 * c_Multiclass=1
3776 * c_ECL_Classification=2
3777 * c_ECL_Multiclass=3
3778 * c_PSD_Classification=4
3779 * c_PSD_Multiclass=5
3780 * c_ECL_PSD_Classification=6
3781 * c_ECL_PSD_Multiclass=7
3782
3783 chargeIndependent (bool, ``optional``): use a BDT trained on a sample of inclusively charged particles.
3784 binaryHypoPDGCodes (tuple(int, int), ``optional``): the pdgIds of the signal, background mass hypothesis.
3785 Required only for binary PID mode.
3786 """
3787
3788 import b2bii
3789 if b2bii.isB2BII():
3790 B2ERROR("Charged PID via MVA is not available for Belle data.")
3791
3792 from ROOT import Belle2
3793
3794 TrainingMode = Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode
3795 Const = Belle2.Const
3796
3797 plSet = set(particleLists)
3798
3799 # Map the training mode enum value to the actual name of the payload in the GT.
3800 payloadNames = {
3801 TrainingMode.c_Classification:
3802 {"mode": "Classification", "detector": "ALL"},
3803 TrainingMode.c_Multiclass:
3804 {"mode": "Multiclass", "detector": "ALL"},
3805 TrainingMode.c_ECL_Classification:
3806 {"mode": "ECL_Classification", "detector": "ECL"},
3807 TrainingMode.c_ECL_Multiclass:
3808 {"mode": "ECL_Multiclass", "detector": "ECL"},
3809 TrainingMode.c_PSD_Classification:
3810 {"mode": "PSD_Classification", "detector": "ALL"},
3811 TrainingMode.c_PSD_Multiclass:
3812 {"mode": "PSD_Multiclass", "detector": "ALL"},
3813 TrainingMode.c_ECL_PSD_Classification:
3814 {"mode": "ECL_PSD_Classification", "detector": "ECL"},
3815 TrainingMode.c_ECL_PSD_Multiclass:
3816 {"mode": "ECL_PSD_Multiclass", "detector": "ECL"},
3817 }
3818
3819 if payloadNames.get(trainingMode) is None:
3820 B2FATAL("The chosen training mode integer identifier:\n", trainingMode,
3821 "\nis not supported. Please choose among the following:\n",
3822 "\n".join(f"{key}:{val.get('mode')}" for key, val in sorted(payloadNames.items())))
3823
3824 mode = payloadNames.get(trainingMode).get("mode")
3825 detector = payloadNames.get(trainingMode).get("detector")
3826
3827 payloadName = f"ChargedPidMVAWeights_{mode}"
3828
3829 # Map pdgIds of std charged particles to name identifiers,
3830 # and binary bkg identifiers.
3831 stdChargedMap = {
3832 Const.electron.getPDGCode():
3833 {"pName": "e", "pFullName": "electron", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3834 Const.muon.getPDGCode():
3835 {"pName": "mu", "pFullName": "muon", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3836 Const.pion.getPDGCode():
3837 {"pName": "pi", "pFullName": "pion", "pNameBkg": "K", "pdgIdBkg": Const.kaon.getPDGCode()},
3838 Const.kaon.getPDGCode():
3839 {"pName": "K", "pFullName": "kaon", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3840 Const.proton.getPDGCode():
3841 {"pName": "p", "pFullName": "proton", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3842 Const.deuteron.getPDGCode():
3843 {"pName": "d", "pFullName": "deuteron", "pNameBkg": "pi", "pdgIdBkg": Const.pion.getPDGCode()},
3844 }
3845
3846 if binaryHypoPDGCodes == (0, 0):
3847
3848 # MULTI-CLASS training mode.
3849 chargedpid = register_module("ChargedPidMVAMulticlass")
3850 chargedpid.set_name(f"ChargedPidMVAMulticlass_{mode}")
3851
3852 else:
3853
3854 # BINARY training mode.
3855 # In binary mode, enforce check on input S, B hypotheses compatibility.
3856
3857 binaryOpts = [(pdgIdSig, info["pdgIdBkg"]) for pdgIdSig, info in stdChargedMap.items()]
3858
3859 if binaryHypoPDGCodes not in binaryOpts:
3860 B2FATAL("No charged pid MVA was trained to separate ", binaryHypoPDGCodes[0], " vs. ", binaryHypoPDGCodes[1],
3861 ". Please choose among the following pairs:\n",
3862 "\n".join(f"{opt[0]} vs. {opt[1]}" for opt in binaryOpts))
3863
3864 decayDescriptor = Belle2.DecayDescriptor()
3865 for name in plSet:
3866 if not decayDescriptor.init(name):
3867 raise ValueError(f"Invalid particle list {name} in applyChargedPidMVA!")
3868 msg = f"Input ParticleList: {name}"
3869 pdgs = [abs(decayDescriptor.getMother().getPDGCode())]
3870 daughter_pdgs = decayDescriptor.getSelectionPDGCodes()
3871 if len(daughter_pdgs) > 0:
3872 pdgs = daughter_pdgs
3873 for idaughter, pdg in enumerate(pdgs):
3874 if abs(pdg) not in binaryHypoPDGCodes:
3875 if daughter_pdgs:
3876 msg = f"Selected daughter {idaughter} in ParticleList: {name}"
3877 B2WARNING(
3878 f"{msg} (PDG={pdg}) is neither signal ({binaryHypoPDGCodes[0]}) nor background ({binaryHypoPDGCodes[1]}).")
3879
3880 chargedpid = register_module("ChargedPidMVA")
3881 chargedpid.set_name(f"ChargedPidMVA_{binaryHypoPDGCodes[0]}_vs_{binaryHypoPDGCodes[1]}_{mode}")
3882 chargedpid.param("sigHypoPDGCode", binaryHypoPDGCodes[0])
3883 chargedpid.param("bkgHypoPDGCode", binaryHypoPDGCodes[1])
3884
3885 chargedpid.param("particleLists", list(plSet))
3886 chargedpid.param("payloadName", payloadName)
3887 chargedpid.param("chargeIndependent", chargeIndependent)
3888
3889 # Ensure the module knows whether we are using ECL-only training mode.
3890 if detector == "ECL":
3891 chargedpid.param("useECLOnlyTraining", True)
3892
3893 path.add_module(chargedpid)
3894
3895
3896def calculateTrackIsolation(
3897 decay_string,
3898 path,
3899 *detectors,
3900 reference_list_name=None,
3901 vars_for_nearest_part=[],
3902 highest_prob_mass_for_ext=True,
3903 exclude_pid_det_weights=False):
3904 """
3905 Given an input decay string, compute variables that quantify track helix-based isolation of the charged
3906 stable particles in the input decay chain.
3907
3908 Note:
3909 An "isolation score" can be defined using the distance
3910 of each particle to its closest neighbour, defined as the segment connecting the two
3911 extrapolated track helices intersection points on a given cylindrical surface.
3912 The distance variables defined in the `VariableManager` is named `minET2ETDist`,
3913 the isolation scores are named `minET2ETIsoScore`, `minET2ETIsoScoreAsWeightedAvg`.
3914
3915 The definition of distance and the number of distances that are calculated per sub-detector is based on
3916 the following recipe:
3917
3918 * **CDC**: as the segmentation is very coarse along :math:`z`,
3919 the distance is defined as the cord length on the :math:`(\\rho=R, \\phi)` plane.
3920 A total of 9 distances are calculated: the cylindrical surfaces are defined at radiuses
3921 that correspond to the positions of the 9 CDC wire superlayers: :math:`R_{i}^{\\mathrm{CDC}}~(i \\in \\{0,...,8\\})`.
3922
3923 * **TOP**: as there is no segmentation along :math:`z`,
3924 the distance is defined as the cord length on the :math:`(\\rho=R, \\phi)` plane.
3925 Only one distance at the TOP entry radius :math:`R_{0}^{\\mathrm{TOP}}` is calculated.
3926
3927 * **ARICH**: as there is no segmentation along :math:`z`,
3928 the distance is defined as the distance on the :math:`(\\rho=R, \\phi)` plane at fixed :math:`z=Z`.
3929 Only one distance at the ARICH photon detector entry coordinate :math:`Z_{0}^{\\mathrm{ARICH}}` is calculated.
3930
3931 * **ECL**: the distance is defined on the :math:`(\\rho=R, \\phi, z)` surface in the barrel,
3932 on the :math:`(\\rho, \\phi, z=Z)` surface in the endcaps.
3933 Two distances are calculated: one at the ECL entry surface :math:`R_{0}^{\\mathrm{ECL}}` (barrel),
3934 :math:`Z_{0}^{\\mathrm{ECL}}` (endcaps), and one at :math:`R_{1}^{\\mathrm{ECL}}` (barrel),
3935 :math:`Z_{1}^{\\mathrm{ECL}}` (endcaps), corresponding roughly to the mid-point
3936 of the longitudinal size of the crystals.
3937
3938 * **KLM**: the distance is defined on the :math:`(\\rho=R, \\phi, z)` surface in the barrel,
3939 on the :math:`(\\rho, \\phi, z=Z)` surface in the endcaps.
3940 Only one distance at the KLM first strip entry surface :math:`R_{0}^{\\mathrm{KLM}}` (barrel),
3941 :math:`Z_{0}^{\\mathrm{KLM}}` (endcaps) is calculated.
3942
3943 Parameters:
3944 decay_string (str): name of the input decay string with selected charged stable daughters,
3945 for example: ``Lambda0:merged -> ^p+ ^pi-``.
3946 Alternatively, it can be a particle list for charged stable particles
3947 as defined in ``Const::chargedStableSet``, for example: ``mu+:all``.
3948 The charge-conjugate particle list will be also processed automatically.
3949 path (basf2.Path): path to which module(s) will be added.
3950 *detectors: detectors for which track isolation variables will be calculated.
3951 Choose among: ``{'CDC', 'TOP', 'ARICH', 'ECL', 'KLM'}``.
3952 reference_list_name (Optional[str]): name of the input charged stable particle list for the reference tracks.
3953 By default, the ``:all`` ParticleList of the same type
3954 of the selected particle in ``decay_string`` is used.
3955 The charge-conjugate particle list will be also processed automatically.
3956 vars_for_nearest_part (Optional[list(str)]): a list of variables to calculate for the nearest particle in the reference
3957 list at each detector surface. It uses the metavariable `minET2ETDistVar`.
3958 If unset, only the distances to the nearest neighbour
3959 per detector are calculated.
3960 highest_prob_mass_for_hex (Optional[bool]): if this option is set to True (default), the helix extrapolation
3961 for the particles will use the track fit result for the most
3962 probable mass hypothesis, namely, the one that gives the highest
3963 chi2Prob of the fit. Otherwise, it uses the mass hypothesis that
3964 corresponds to the particle lists PDG.
3965 exclude_pid_det_weights (Optional[bool]): if this option is set to False (default), the isolation score
3966 calculation will take into account the weight that each detector has on the PID
3967 for the particle species of interest.
3968
3969 Returns:
3970 dict(int, list(str)): a dictionary mapping the PDG of each reference particle list to its isolation variables.
3971
3972 """
3973
3974 import pdg
3975 from ROOT import Belle2, TDatabasePDG
3976
3977 decayDescriptor = Belle2.DecayDescriptor()
3978 if not decayDescriptor.init(decay_string):
3979 B2FATAL(f"Invalid particle list {decay_string} in calculateTrackIsolation!")
3980 no_reference_list_name = not reference_list_name
3981
3982 det_and_layers = {
3983 "CDC": list(range(9)),
3984 "TOP": [0],
3985 "ARICH": [0],
3986 "ECL": [0, 1],
3987 "KLM": [0],
3988 }
3989 if any(d not in det_and_layers for d in detectors):
3990 B2FATAL(
3991 "Your input detector list: ",
3992 detectors,
3993 " contains an invalid choice. Please select among: ",
3994 list(
3995 det_and_layers.keys()))
3996
3997 # The module allows only one daughter to be selected at a time,
3998 # that's why here we preprocess the input decay string.
3999 select_symbol = '^'
4000 processed_decay_strings = []
4001 if select_symbol in decay_string:
4002 splitted_ds = decay_string.split(select_symbol)
4003 for i in range(decay_string.count(select_symbol)):
4004 tmp = list(splitted_ds)
4005 tmp.insert(i+1, select_symbol)
4006 processed_decay_strings += [''.join(tmp)]
4007 else:
4008 processed_decay_strings += [decay_string]
4009
4010 reference_lists_to_vars = {}
4011
4012 for processed_dec in processed_decay_strings:
4013 if no_reference_list_name:
4014 decayDescriptor.init(processed_dec)
4015 selected_daughter_pdgs = decayDescriptor.getSelectionPDGCodes()
4016 if len(selected_daughter_pdgs) > 0:
4017 reference_list_name = f'{TDatabasePDG.Instance().GetParticle(abs(selected_daughter_pdgs[-1])).GetName()}:all'
4018 else:
4019 reference_list_name = f'{processed_dec.split(":")[0]}:all'
4020
4021 ref_pdg = pdg.from_name(reference_list_name.split(":")[0])
4022
4023 trackiso = path.add_module("TrackIsoCalculator",
4024 decayString=processed_dec,
4025 detectorNames=list(detectors),
4026 particleListReference=reference_list_name,
4027 useHighestProbMassForExt=highest_prob_mass_for_ext,
4028 excludePIDDetWeights=exclude_pid_det_weights)
4029 trackiso.set_name(f"TrackIsoCalculator_{'_'.join(detectors)}_{processed_dec}_VS_{reference_list_name}")
4030
4031 # Metavariables for the distances to the closest reference tracks at each detector surface.
4032 # Always calculate them.
4033 # Ensure the flag for the mass hypothesis of the fit is set.
4034 trackiso_vars = [
4035 f"minET2ETDist({d}, {d_layer}, {reference_list_name}, {int(highest_prob_mass_for_ext)})"
4036 for d in detectors for d_layer in det_and_layers[d]]
4037 # Track isolation score.
4038 trackiso_vars += [
4039 f"minET2ETIsoScore({reference_list_name}, {int(highest_prob_mass_for_ext)}, {', '.join(detectors)})",
4040 f"minET2ETIsoScoreAsWeightedAvg({reference_list_name}, {int(highest_prob_mass_for_ext)}, {', '.join(detectors)})",
4041 ]
4042 # Optionally, calculate the input variables for the nearest neighbour in the reference list.
4043 if vars_for_nearest_part:
4044 trackiso_vars.extend(
4045 [
4046 f"minET2ETDistVar({d}, {d_layer}, {reference_list_name}, {v})"
4047 for d in detectors for d_layer in det_and_layers[d] for v in vars_for_nearest_part
4048 ])
4049 trackiso_vars.sort()
4050
4051 reference_lists_to_vars[ref_pdg] = trackiso_vars
4052
4053 return reference_lists_to_vars
4054
4055
4056def calculateDistance(list_name, decay_string, mode='vertextrack', path=None):
4057 """
4058 Calculates distance between two vertices, distance of closest approach between a vertex and a track,\
4059 distance of closest approach between a vertex and btube. For track, this calculation ignores track curvature,\
4060 it's negligible for small distances.The user should use extraInfo(CalculatedDistance)\
4061 to get it. A full example steering file is at analysis/tests/test_DistanceCalculator.py
4062
4063 Example:
4064 .. code-block:: python
4065
4066 from modularAnalysis import calculateDistance
4067 calculateDistance('list_name', 'decay_string', "mode", path=user_path)
4068
4069 @param list_name name of the input ParticleList
4070 @param decay_string select particles between the distance of closest approach will be calculated
4071 @param mode Specifies how the distance is calculated
4072 vertextrack: calculate the distance of closest approach between a track and a\
4073 vertex, taking the first candidate as vertex, default
4074 trackvertex: calculate the distance of closest approach between a track and a\
4075 vertex, taking the first candidate as track
4076 2tracks: calculates the distance of closest approach between two tracks
4077 2vertices: calculates the distance between two vertices
4078 vertexbtube: calculates the distance of closest approach between a vertex and btube
4079 trackbtube: calculates the distance of closest approach between a track and btube
4080 @param path modules are added to this path
4081
4082 """
4083
4084 dist_mod = register_module('DistanceCalculator')
4085
4086 dist_mod.set_name('DistanceCalculator_' + list_name)
4087 dist_mod.param('listName', list_name)
4088 dist_mod.param('decayString', decay_string)
4089 dist_mod.param('mode', mode)
4090 path.add_module(dist_mod)
4091
4092
4093def addInclusiveDstarReconstruction(decayString, slowPionCut, DstarCut, path):
4094 """
4095 Adds the InclusiveDstarReconstruction module to the given path.
4096 This module creates a D* particle list by estimating the D* four momenta
4097 from slow pions, specified by a given cut. The D* energy is approximated
4098 as E(D*) = m(D*)/(m(D*) - m(D)) * E(pi). The absolute value of the D*
4099 momentum is calculated using the D* PDG mass and the direction is collinear
4100 to the slow pion direction. The charge of the given pion list has to be consistent
4101 with the D* charge
4102
4103 @param decayString Decay string, must be of form ``D* -> pi``
4104 @param slowPionCut Cut applied to the input pion list to identify slow pions
4105 @param DstarCut Cut applied to the output D* list
4106 @param path the module is added to this path
4107 """
4108
4109 incl_dstar = register_module("InclusiveDstarReconstruction")
4110 incl_dstar.param("decayString", decayString)
4111 incl_dstar.param("slowPionCut", slowPionCut)
4112 incl_dstar.param("DstarCut", DstarCut)
4113 path.add_module(incl_dstar)
4114
4115
4116def scaleError(outputListName, inputListName,
4117 scaleFactors=[1.149631, 1.085547, 1.151704, 1.096434, 1.086659],
4118 scaleFactorsNoPXD=[1.149631, 1.085547, 1.151704, 1.096434, 1.086659],
4119 d0Resolution=[0.00115328, 0.00134704],
4120 z0Resolution=[0.00124327, 0.0013272],
4121 d0MomThr=0.500000,
4122 z0MomThr=0.500000,
4123 path=None):
4124 """
4125 This module creates a new charged particle list.
4126 The helix errors of the new particles are scaled by constant factors.
4127 Two sets of five scale factors are defined for tracks with and without a PXD hit.
4128 The scale factors are in order of (d0, phi0, omega, z0, tanlambda).
4129 For tracks with a PXD hit, in order to avoid severe underestimation of d0 and z0 errors,
4130 lower limits (best resolution) can be set in a momentum-dependent form.
4131 This module is supposed to be used only for TDCPV analysis and for low-momentum (0-3 GeV/c) tracks in BBbar events.
4132 Details will be documented in a Belle II note, BELLE2-NOTE-PH-2021-038.
4133
4134 @param inputListName Name of input charged particle list to be scaled
4135 @param outputListName Name of output charged particle list with scaled error
4136 @param scaleFactors List of five constants to be multiplied to each of helix errors (for tracks with a PXD hit)
4137 @param scaleFactorsNoPXD List of five constants to be multiplied to each of helix errors (for tracks without a PXD hit)
4138 @param d0Resolution List of two parameters, (a [cm], b [cm/(GeV/c)]),
4139 defining d0 best resolution as sqrt{ a**2 + (b / (p*beta*sinTheta**1.5))**2 }
4140 @param z0Resolution List of two parameters, (a [cm], b [cm/(GeV/c)]),
4141 defining z0 best resolution as sqrt{ a**2 + (b / (p*beta*sinTheta**2.5))**2 }
4142 @param d0MomThr d0 best resolution is kept constant below this momentum
4143 @param z0MomThr z0 best resolution is kept constant below this momentum
4144
4145 """
4146
4147 scale_error = register_module("HelixErrorScaler")
4148 scale_error.set_name('ScaleError_' + inputListName)
4149 scale_error.param('inputListName', inputListName)
4150 scale_error.param('outputListName', outputListName)
4151 scale_error.param('scaleFactors_PXD', scaleFactors)
4152 scale_error.param('scaleFactors_noPXD', scaleFactorsNoPXD)
4153 scale_error.param('d0ResolutionParameters', d0Resolution)
4154 scale_error.param('z0ResolutionParameters', z0Resolution)
4155 scale_error.param('d0MomentumThreshold', d0MomThr)
4156 scale_error.param('z0MomentumThreshold', z0MomThr)
4157 path.add_module(scale_error)
4158
4159
4160def estimateAndAttachTrackFitResult(inputListName, path=None):
4161 """
4162 Create a TrackFitResult from the momentum of the Particle assuming it originates from the IP and make a relation between them.
4163 The covariance, detector hit information, and fit-related information (pValue, NDF) are assigned meaningless values. The input
4164 Particles must not have already Track or TrackFitResult and thus are supposed to be composite particles, recoil, dummy
4165 particles, and so on.
4166
4167
4168 .. warning:: Since the source type is not overwritten as Track, not all track-related variables are guaranteed to be available.
4169
4170
4171 @param inputListName Name of input ParticleList
4172 """
4173
4174 estimator = register_module("TrackFitResultEstimator")
4175 estimator.set_name("trackFitResultEstimator_" + inputListName)
4176 estimator.param("inputListName", inputListName)
4177 path.add_module(estimator)
4178
4179
4180def correctEnergyBias(inputListNames, tableName, path=None):
4181 """
4182 Scale energy of the particles according to the scaling factor.
4183 If the particle list contains composite particles, the energy of the daughters are scaled.
4184 Subsequently, the energy of the mother particle is updated as well.
4185
4186 Parameters:
4187 inputListNames (list(str)): input particle list names
4188 tableName : stored in localdb and created using ParticleWeightingLookUpCreator
4189 path (basf2.Path): module is added to this path
4190 """
4191
4192 import b2bii
4193 if b2bii.isB2BII():
4194 B2ERROR("The energy bias cannot be corrected with this tool for Belle data.")
4195
4196 correctenergybias = register_module('EnergyBiasCorrection')
4197 correctenergybias.param('particleLists', inputListNames)
4198 correctenergybias.param('tableName', tableName)
4199 path.add_module(correctenergybias)
4200
4201
4202def twoBodyISRPhotonCorrector(outputListName, inputListName, massiveParticle, path=None):
4203 """
4204 Sets photon kinematics to corrected values in two body decays with an ISR photon
4205 and a massive particle. The original photon kinematics are kept in the input
4206 particleList and can be accessed using the originalParticle() metavariable on the
4207 new list.
4208
4209 @param ouputListName new ParticleList filled with copied Particles
4210 @param inputListName input ParticleList with original Particles
4211 @param massiveParticle name or PDG code of massive particle participating in the two
4212 body decay with the ISR photon
4213 @param path modules are added to this path
4214 """
4215
4216 # set the corrected energy of the photon in a new list
4217 photon_energy_correction = register_module('TwoBodyISRPhotonCorrector')
4218 photon_energy_correction.set_name('TwoBodyISRPhotonCorrector_' + outputListName)
4219 photon_energy_correction.param('outputGammaList', outputListName)
4220 photon_energy_correction.param('inputGammaList', inputListName)
4221
4222 # prepare PDG code of massive particle
4223 if isinstance(massiveParticle, int):
4224 photon_energy_correction.param('massiveParticlePDGCode', massiveParticle)
4225 else:
4226 from ROOT import Belle2
4227 decayDescriptor = Belle2.DecayDescriptor()
4228 if not decayDescriptor.init(massiveParticle):
4229 raise ValueError("TwoBodyISRPhotonCorrector: value of massiveParticle must be" +
4230 " an int or valid decay string.")
4231 pdgCode = decayDescriptor.getMother().getPDGCode()
4232 photon_energy_correction.param('massiveParticlePDGCode', pdgCode)
4233
4234 path.add_module(photon_energy_correction)
4235
4236
4237def addPhotonEfficiencyRatioVariables(inputListNames, tableName, path=None):
4238 """
4239 Add photon Data/MC detection efficiency ratio weights to the specified particle list
4240
4241 Parameters:
4242 inputListNames (list(str)): input particle list names
4243 tableName : taken from database with appropriate name
4244 path (basf2.Path): module is added to this path
4245 """
4246
4247 import b2bii
4248 if b2bii.isB2BII():
4249 B2ERROR("For Belle data the photon data/MC detection efficiency ratio is not available with this tool.")
4250
4251 photon_efficiency_correction = register_module('PhotonEfficiencySystematics')
4252 photon_efficiency_correction.param('particleLists', inputListNames)
4253 photon_efficiency_correction.param('tableName', tableName)
4254 path.add_module(photon_efficiency_correction)
4255
4256
4257def addPi0VetoEfficiencySystematics(particleList, decayString, tableName, threshold, mode='standard', suffix='', path=None):
4258 """
4259 Add pi0 veto Data/MC efficiency ratio weights to the specified particle list
4260
4261 @param particleList the input ParticleList
4262 @param decayString specify hard photon to be performed pi0 veto (e.g. 'B+:sig -> rho+:sig ^gamma:hard')
4263 @param tableName table name corresponding to payload version (e.g. 'Pi0VetoEfficiencySystematics_Mar2022')
4264 @param threshold pi0 veto threshold (0.10, 0.11, ..., 0.99)
4265 @param mode choose one mode (same as writePi0EtaVeto) out of 'standard', 'tight', 'cluster' and 'both'
4266 @param suffix optional suffix to be appended to the usual extraInfo name
4267 @param path the module is added to this path
4268
4269 The following extraInfo are available related with the given particleList:
4270
4271 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_ratio : weight of Data/MC for the veto efficiency
4272 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_uncertainty_stat : the statistical uncertainty of the weight
4273 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_uncertainty_sys : the systematic uncertainty of the weight
4274 * Pi0VetoEfficiencySystematics_{mode}{suffix}_data_MC_uncertainty_total : the total uncertainty of the weight
4275 * Pi0VetoEfficiencySystematics_{mode}{suffix}_threshold : threshold of the pi0 veto
4276 """
4277
4278 import b2bii
4279 if b2bii.isB2BII():
4280 B2ERROR("For Belle data the pi0 veto data/MC efficiency ratio weights are not available via this tool.")
4281
4282 pi0veto_efficiency_correction = register_module('Pi0VetoEfficiencySystematics')
4283 pi0veto_efficiency_correction.param('particleLists', particleList)
4284 pi0veto_efficiency_correction.param('decayString', decayString)
4285 pi0veto_efficiency_correction.param('tableName', tableName)
4286 pi0veto_efficiency_correction.param('threshold', threshold)
4287 pi0veto_efficiency_correction.param('mode', mode)
4288 pi0veto_efficiency_correction.param('suffix', suffix)
4289 path.add_module(pi0veto_efficiency_correction)
4290
4291
4292def getAnalysisGlobaltag(timeout=180) -> str:
4293 """
4294 Returns a string containing the name of the latest and recommended analysis globaltag.
4295
4296 Parameters:
4297 timeout: Seconds to wait for b2conditionsdb-recommend
4298 """
4299
4300 import b2bii
4301 if b2bii.isB2BII():
4302 B2ERROR("The getAnalysisGlobaltag() function cannot be used for Belle data.")
4303
4304 # b2conditionsdb-recommend relies on a different repository, so it's better to protect
4305 # this function against potential failures of check_output.
4306 try:
4307 tags = subprocess.check_output(
4308 ['b2conditionsdb-recommend', '--oneline'],
4309 timeout=timeout
4310 ).decode('UTF-8').rstrip().split(' ')
4311 analysis_tag = ''
4312 for tag in tags:
4313 if tag.startswith('analysis_tools'):
4314 analysis_tag = tag
4315 return analysis_tag
4316 # In case of issues with git, b2conditionsdb-recommend may take too much time.
4317 except subprocess.TimeoutExpired as te:
4318 B2FATAL(f'A {te} exception was raised during the call of getAnalysisGlobaltag(). '
4319 'The function took too much time to retrieve the requested information '
4320 'from the versioning repository.\n'
4321 'Please try to re-run your job. In case of persistent failures, there may '
4322 'be issues with the DESY collaborative services, so please contact the experts.')
4323 except subprocess.CalledProcessError as ce:
4324 B2FATAL(f'A {ce} exception was raised during the call of getAnalysisGlobaltag(). '
4325 'Please try to re-run your job. In case of persistent failures, please contact '
4326 'the experts.')
4327
4328
4329def getAnalysisGlobaltagB2BII() -> str:
4330 """
4331 Get recommended global tag for B2BII analysis.
4332 """
4333
4334 import b2bii
4335 if not b2bii.isB2BII():
4336 B2ERROR('The getAnalysisGlobaltagB2BII() function cannot be used for Belle II data.')
4337 from versioning import recommended_b2bii_analysis_global_tag
4338 return recommended_b2bii_analysis_global_tag()
4339
4340
4341def getNbarIDMVA(particleList: str, path=None):
4342 """
4343 This function can give a score to predict if it is a anti-n0.
4344 It is not used to predict n0.
4345 Currently, this can be used only for ECL cluster.
4346 output will be stored in extraInfo(nbarID); -1 means MVA invalid
4347
4348 @param particleList The input ParticleList name or a decay string which contains a full mother particle list name.
4349 Only one selected daughter is supported.
4350 @param path modules are added to this path
4351 """
4352 import b2bii
4353 from ROOT import Belle2
4354
4355 if b2bii.isB2BII():
4356 B2ERROR("The MVA-based anti-neutron PID is only available for Belle II data.")
4357
4358 from variables import variables
4359
4360 variables.addAlias('V1', 'clusterHasPulseShapeDiscrimination')
4361 variables.addAlias('V2', 'clusterE')
4362 variables.addAlias('V3', 'clusterLAT')
4363 variables.addAlias('V4', 'clusterE1E9')
4364 variables.addAlias('V5', 'clusterE9E21')
4365 variables.addAlias('V6', 'clusterZernikeMVA')
4366 variables.addAlias('V7', 'clusterAbsZernikeMoment40')
4367 variables.addAlias('V8', 'clusterAbsZernikeMoment51')
4368
4369 variables.addAlias(
4370 'nbarIDValid',
4371 'passesCut(V1 == 1 and V2 >= 0 and V3 >= 0 and V4 >= 0 and V5 >= 0 and V6 >= 0 and V7 >= 0 and V8 >= 0)')
4372 variables.addAlias('nbarIDmod', 'conditionalVariableSelector(nbarIDValid == 1, extraInfo(nbarIDFromMVA), constant(-1.0))')
4373
4374 path.add_module('MVAExpert', listNames=particleList, extraInfoName='nbarIDFromMVA', identifier='db_nbarIDECL')
4375 decayDescriptor = Belle2.DecayDescriptor()
4376 if not decayDescriptor.init(particleList):
4377 raise ValueError(f"Provided decay string is invalid: {particleList}")
4378 if decayDescriptor.getNDaughters() == 0:
4379 variablesToExtraInfo(particleList, {'nbarIDmod': 'nbarID'}, option=2, path=path)
4380 else:
4381 listname = decayDescriptor.getMother().getFullName()
4382 variablesToDaughterExtraInfo(listname, particleList, {'nbarIDmod': 'nbarID'}, option=2, path=path)
4383
4384
4385def reconstructDecayWithNeutralHadron(decayString, cut, allowGamma=False, allowAnyParticleSource=False, path=None, **kwargs):
4386 r"""
4387 Reconstructs decay with a long-lived neutral hadron e.g.
4388 :math:`B^0 \to J/\psi K_L^0`,
4389 :math:`B^0 \to p \bar{n} D^*(2010)^-`.
4390
4391 The calculation is done with IP constraint and mother mass constraint.
4392
4393 The decay string passed in must satisfy the following rules:
4394
4395 - The neutral hadron must be **selected** in the decay string with the
4396 caret (``^``) e.g. ``B0:sig -> J/psi:sig ^K_L0:sig``. (Note the caret
4397 next to the neutral hadron.)
4398 - There can only be **one neutral hadron in a decay**.
4399 - The neutral hadron has to be a direct daughter of its mother.
4400
4401 .. note:: This function forwards its arguments to `reconstructDecay`,
4402 so please check the documentation of `reconstructDecay` for all
4403 possible arguments.
4404
4405 @param decayString A decay string following the mentioned rules
4406 @param cut Cut to apply to the particle list
4407 @param allowGamma Whether allow the selected particle to be ``gamma``
4408 @param allowAnyParticleSource Whether allow the selected particle to be from any source.
4409 Should only be used when studying control sample.
4410 @param path The path to put in the module
4411 """
4412
4413 reconstructDecay(decayString, cut, path=path, **kwargs)
4414 module = register_module('NeutralHadron4MomentumCalculator')
4415 module.set_name('NeutralHadron4MomentumCalculator_' + decayString)
4416 module.param('decayString', decayString)
4417 module.param('allowGamma', allowGamma)
4418 module.param('allowAnyParticleSource', allowAnyParticleSource)
4419 path.add_module(module)
4420
4421
4422func_requiring_analysisGT = [
4423 correctTrackEnergy, scaleTrackMomenta, smearTrackMomenta, oldwritePi0EtaVeto, writePi0EtaVeto, lowEnergyPi0Identification,
4424 getBeamBackgroundProbability, getFakePhotonProbability, tagCurlTracks, applyChargedPidMVA, correctEnergyBias,
4425 addPhotonEfficiencyRatioVariables, addPi0VetoEfficiencySystematics, getNbarIDMVA]
4426for _ in func_requiring_analysisGT:
4427 _.__doc__ += "\n .. note:: This function (optionally) requires a payload stored in the analysis GlobalTag. "\
4428 "Please append or prepend the latest one from `getAnalysisGlobaltag` or `getAnalysisGlobaltagB2BII`.\n"
4429
4430
4431if __name__ == '__main__':
4432 from basf2.utils import pretty_print_module
4433 pretty_print_module(__name__, "modularAnalysis")
4434
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:37
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