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