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