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