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