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