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