Belle II Software  release-05-01-25
modularAnalysis.py
1 # !/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 """
5 This module defines wrapper functions around the analysis modules.
6 """
7 
8 from basf2 import register_module, create_path
9 from basf2 import B2INFO, B2WARNING, B2ERROR, B2FATAL
10 import basf2
11 import subprocess
12 
13 
14 def setAnalysisConfigParams(configParametersAndValues, path):
15  """
16  Sets analysis configuration parameters.
17 
18  These are:
19 
20  - 'tupleStyle': 'Default' (default) or 'Laconic'
21  o) defines the style of the branch name in the ntuple
22 
23  - 'mcMatchingVersion': Specifies what version of mc matching algorithm is going to be used:
24 
25  - 'MC5' - analysis of BelleII MC5
26  - 'Belle' - analysis of Belle MC
27  - 'BelleII' (default) - all other cases
28 
29  @param configParametersAndValues dictionary of parameters and their values of the form {param1: value, param2: value, ...)
30  @param modules are added to this path
31  """
32 
33  conf = register_module('AnalysisConfiguration')
34 
35  allParameters = ['tupleStyle', 'mcMatchingVersion']
36 
37  keys = configParametersAndValues.keys()
38  for key in keys:
39  if key not in allParameters:
40  allParametersString = ', '.join(allParameters)
41  B2ERROR('Invalid analysis configuration parameter: ' + key + '.\n'
42  'Please use one of the following: ' + allParametersString)
43 
44  for param in allParameters:
45  if param in configParametersAndValues:
46  conf.param(param, configParametersAndValues.get(param))
47 
48  path.add_module(conf)
49 
50 
51 def inputMdst(environmentType, filename, path, skipNEvents=0, entrySequence=None, *, parentLevel=0):
52  """
53  Loads the specified ROOT (DST/mDST/muDST) file with the RootInput module.
54 
55  The correct environment (e.g. magnetic field settings) are determined from
56  the specified environment type. For the possible values please see
57  `inputMdstList()`
58 
59  Parameters:
60  environmentType (str): type of the environment to be loaded
61  filename (str): the name of the file to be loaded
62  path (basf2.Path): modules are added to this path
63  skipNEvents (int): N events of the input file are skipped
64  entrySequence (str): The number sequences (e.g. 23:42,101) defining the entries which are processed.
65  parentLevel (int): Number of generations of parent files (files used as input when creating a file) to be read
66  """
67  if entrySequence is not None:
68  entrySequence = [entrySequence]
69 
70  inputMdstList(environmentType, [filename], path, skipNEvents, entrySequence, parentLevel=parentLevel)
71 
72 
73 def inputMdstList(environmentType, filelist, path, skipNEvents=0, entrySequences=None, *, parentLevel=0):
74  """
75  Loads the specified ROOT (DST/mDST/muDST) files with the RootInput module.
76 
77  The correct environment (e.g. magnetic field settings) are determined from the specified environment type.
78  The currently available environments are:
79 
80  - 'MC5': for analysis of Belle II MC samples produced with releases prior to build-2016-05-01.
81  This environment sets the constant magnetic field (B = 1.5 T)
82  - 'MC6': for analysis of Belle II MC samples produced with build-2016-05-01 or newer but prior to release-00-08-00
83  - 'MC7': for analysis of Belle II MC samples produced with build-2016-05-01 or newer but prior to release-00-08-00
84  - 'MC8', for analysis of Belle II MC samples produced with release-00-08-00 or newer but prior to release-02-00-00
85  - 'MC9', for analysis of Belle II MC samples produced with release-00-08-00 or newer but prior to release-02-00-00
86  - 'MC10', for analysis of Belle II MC samples produced with release-00-08-00 or newer but prior to release-02-00-00
87  - 'default': for analysis of Belle II MC samples produced with releases with release-02-00-00 or newer.
88  This environment sets the default magnetic field (see geometry settings)
89  - 'Belle': for analysis of converted (or during of conversion of) Belle MC/DATA samples
90  - 'None': for analysis of generator level information or during simulation/reconstruction of
91  previously generated events
92 
93  Note that there is no difference between MC6 and MC7. Both are given for sake of completion.
94  The same is true for MC8, MC9 and MC10
95 
96  Parameters:
97  environmentType (str): type of the environment to be loaded
98  filelist (list(str)): the filename list of files to be loaded
99  path (basf2.Path): modules are added to this path
100  skipNEvents (int): N events of the input files are skipped
101  entrySequences (list(str)): The number sequences (e.g. 23:42,101) defining
102  the entries which are processed for each inputFileName.
103  parentLevel (int): Number of generations of parent files (files used as input when creating a file) to be read
104  """
105 
106  roinput = register_module('RootInput')
107  roinput.param('inputFileNames', filelist)
108  roinput.param('skipNEvents', skipNEvents)
109  if entrySequences is not None:
110  roinput.param('entrySequences', entrySequences)
111  roinput.param('parentLevel', parentLevel)
112 
113  path.add_module(roinput)
114  progress = register_module('ProgressBar')
115  path.add_module(progress)
116 
117  # None means don't create custom magnetic field, use whatever comes from the
118  # DB
119  environToMagneticField = {'MC5': 'MagneticFieldConstant',
120  'MC6': None,
121  'MC7': None,
122  'default': None,
123  'Belle': 'MagneticFieldConstantBelle'}
124 
125  fixECLClusters = {'MC5': True,
126  'MC6': True,
127  'MC7': True,
128  'default': False,
129  'Belle': False}
130 
131  if environmentType in environToMagneticField:
132  fieldType = environToMagneticField[environmentType]
133  if fieldType is not None:
134  from ROOT import Belle2 # reduced scope of potentially-misbehaving import
135  field = Belle2.MagneticField()
136  field.addComponent(Belle2.MagneticFieldComponentConstant(Belle2.B2Vector3D(0, 0, 1.5 * Belle2.Unit.T)))
137  Belle2.DBStore.Instance().addConstantOverride("MagneticField", field, False)
138  elif environmentType in ["MC8", "MC9", "MC10"]:
139  # make sure the last database setup is the magnetic field for MC8-10
140  from basf2 import conditions
141  conditions.globaltags += ["Legacy_MagneticField_MC8_MC9_MC10"]
142  elif environmentType is 'None':
143  B2INFO('No magnetic field is loaded. This is OK, if generator level information only is studied.')
144  else:
145  environments = ' '.join(list(environToMagneticField.keys()) + ["MC8", "MC9", "MC10"])
146  B2FATAL('Incorrect environment type provided: ' + environmentType + '! Please use one of the following:' + environments)
147 
148  # set the correct MCMatching algorithm for MC5 and Belle MC
149  if environmentType is 'Belle':
150  setAnalysisConfigParams({'mcMatchingVersion': 'Belle'}, path)
151  import b2bii
153  if environmentType is 'MC5':
154  setAnalysisConfigParams({'mcMatchingVersion': 'MC5'}, path)
155 
156  # fixECLCluster for MC5/MC6/MC7
157  if fixECLClusters.get(environmentType) is True:
158  fixECL = register_module('FixECLClusters')
159  path.add_module(fixECL)
160 
161 
162 def outputMdst(filename, path):
163  """
164  Saves mDST (mini-Data Summary Tables) to the output root file.
165  """
166 
167  import mdst
168  mdst.add_mdst_output(path, mc=True, filename=filename)
169 
170 
171 def outputUdst(filename, particleLists=[], includeArrays=[], path=None, dataDescription=None):
172  """
173  Save uDST (micro-Data Summary Tables) = MDST + Particles + ParticleLists
174  The charge-conjugate lists of those given in particleLists are also stored.
175  Additional Store Arrays and Relations to be stored can be specified via includeArrays
176  list argument.
177 
178  Note that this does not reduce the amount of Particle objects saved,
179  see skimOutputUdst() for a function that does.
180  """
181 
182  import mdst
183  import pdg
184  # also add anti-particle lists
185  plSet = set(particleLists)
186  for List in particleLists:
187  name, label = List.split(':')
188  plSet.add(pdg.conjugate(name) + ':' + label)
189 
190  partBranches = ['Particles', 'ParticlesToMCParticles',
191  'ParticlesToPIDLikelihoods', 'ParticleExtraInfoMap',
192  'EventExtraInfo'] + includeArrays + list(plSet)
193 
194  # set dataDescription: dictionary is mutable and thus not a good
195  # default argument.
196  if dataDescription is None:
197  dataDescription = {}
198 
199  dataDescription.update(dataLevel="udst")
200 
201  return mdst.add_mdst_output(path, mc=True, filename=filename,
202  additionalBranches=partBranches,
203  dataDescription=dataDescription)
204 
205 
206 def skimOutputUdst(skimDecayMode, skimParticleLists=[], outputParticleLists=[],
207  includeArrays=[], path=None, *,
208  outputFile=None, dataDescription=None):
209  """
210  Create a new path for events that contain a non-empty particle list specified via skimParticleLists.
211  Write the accepted events as a udst file, saving only particles from skimParticleLists
212  and from outputParticleLists.
213  Additional Store Arrays and Relations to be stored can be specified via includeArrays
214  list argument.
215 
216  :param str skimDecayMode: Name of the skim. If no outputFile is given this is
217  also the name of the output filename. This name will be added to the
218  FileMetaData as an extra data description "skimDecayMode"
219  :param list(str) skimParticleLists: Names of the particle lists to skim for.
220  An event will be accepted if at least one of the particle lists is not empty
221  :param list(str) outputParticleLists: Names of the particle lists to store in
222  the output in addition to the ones in skimParticleLists
223  :param list(str) includeArrays: datastore arrays/objects to write to the output
224  file in addition to mdst and particle information
225  :param basf2.Path path: Path to add the skim output to. Defaults to the default analysis path
226  :param str outputFile: Name of the output file if different from the skim name
227  :param dict dataDescription: Additional data descriptions to add to the output file. For example {"mcEventType":"mixed"}
228  """
229 
230  from basf2 import AfterConditionPath
231  # if no outputfile is specified, set it to the skim name
232  if outputFile is None:
233  outputFile = skimDecayMode
234 
235  # make sure the output filename has the correct extension
236  if not outputFile.endswith(".udst.root"):
237  outputFile += ".udst.root"
238 
239  skimfilter = register_module('SkimFilter')
240  skimfilter.set_name('SkimFilter_' + skimDecayMode)
241  skimfilter.param('particleLists', skimParticleLists)
242  path.add_module(skimfilter)
243  filter_path = create_path()
244  skimfilter.if_value('=1', filter_path, AfterConditionPath.CONTINUE)
245 
246  # add_independent_path() is rather expensive, only do this for skimmed events
247  skim_path = create_path()
248  saveParticleLists = skimParticleLists + outputParticleLists
249  removeParticlesNotInLists(saveParticleLists, path=skim_path)
250 
251  # set dataDescription: dictionary is mutable and thus not a good
252  # default argument.
253  if dataDescription is None:
254  dataDescription = {}
255 
256  dataDescription.setdefault("skimDecayMode", skimDecayMode)
257  outputUdst(outputFile, saveParticleLists, includeArrays, path=skim_path,
258  dataDescription=dataDescription)
259  filter_path.add_independent_path(skim_path, "skim_" + skimDecayMode)
260 
261 
262 def outputIndex(filename, path, includeArrays=[], keepParents=False, mc=True):
263  """
264  Write out all particle lists as an index file to be reprocessed using parentLevel flag.
265  Additional branches necessary for file to be read are automatically included.
266  Additional Store Arrays and Relations to be stored can be specified via includeArrays
267  list argument.
268 
269  @param str filename the name of the output index file
270  @param str path modules are added to this path
271  @param list(str) includeArrays: datastore arrays/objects to write to the output
272  file in addition to particle lists and related information
273  @param bool keepParents whether the parents of the input event will be saved as the parents of the same event
274  in the output index file. Useful if you are only adding more information to another index file
275  @param bool mc whether the input data is MC or not
276  """
277 
278  # Module to mark all branches to not be saved except particle lists
279  onlyPLists = register_module('OnlyWriteOutParticleLists')
280  path.add_module(onlyPLists)
281 
282  # Set up list of all other branches we need to make index file complete
283  partBranches = [
284  'Particles',
285  'ParticlesToMCParticles',
286  'ParticlesToPIDLikelihoods',
287  'ParticleExtraInfoMap',
288  'EventExtraInfo'
289  ]
290  branches = ['EventMetaData']
291  persistentBranches = ['FileMetaData']
292  if mc:
293  branches += []
294  # persistentBranches += ['BackgroundInfos']
295  branches += partBranches
296  branches += includeArrays
297 
298  r1 = register_module('RootOutput')
299  r1.param('outputFileName', filename)
300  r1.param('additionalBranchNames', branches)
301  r1.param('branchNamesPersistent', persistentBranches)
302  r1.param('keepParents', keepParents)
303  path.add_module(r1)
304 
305 
306 def setupEventInfo(noEvents, path):
307  """
308  Prepare to generate events. This function sets up the EventInfoSetter.
309  You should call this before adding a generator from generators.
310  The experiment and run numbers are set to 0 (run independent generic MC in phase 3).
311  https://confluence.desy.de/display/BI/Experiment+numbering
312 
313  Parameters:
314  noEvents (int): number of events to be generated
315  path (basf2.Path): modules are added to this path
316  """
317  evtnumbers = register_module('EventInfoSetter')
318  evtnumbers.param('evtNumList', [noEvents])
319  evtnumbers.param('runList', [0])
320  evtnumbers.param('expList', [0])
321  path.add_module(evtnumbers)
322 
323 
324 def loadGearbox(path, silence_warning=False):
325  """
326  Loads Gearbox module to the path.
327 
328  Warning:
329  Should be used in a job with *cosmic event generation only*
330 
331  Needed for scripts which only generate cosmic events in order to
332  load the geometry.
333 
334  @param path modules are added to this path
335  @param silence_warning stops a verbose warning message if you know you want to use this function
336  """
337 
338  if not silence_warning:
339  B2WARNING("""You are overwriting the geometry from the database with Gearbox.
340  This is fine if you're generating cosmic events. But in most other cases you probably don't want this.
341 
342  If you're really sure you know what you're doing you can suppress this message with:
343 
344  >>> loadGearbox(silence_warning=True)
345 
346  """)
347 
348  paramloader = register_module('Gearbox')
349  path.add_module(paramloader)
350 
351 
352 def printPrimaryMCParticles(path):
353  """
354  Prints all primary MCParticles.
355  """
356 
357  mcparticleprinter = register_module('PrintMCParticles')
358  path.add_module(mcparticleprinter)
359 
360 
361 def printMCParticles(onlyPrimaries=False, maxLevel=-1, path=None):
362  """
363  Prints all MCParticles or just primary MCParticles up to specified level. -1 means no limit.
364  """
365 
366  mcparticleprinter = register_module('PrintMCParticles')
367  mcparticleprinter.param('onlyPrimaries', onlyPrimaries)
368  mcparticleprinter.param('maxLevel', maxLevel)
369  path.add_module(mcparticleprinter)
370 
371 
372 def correctBrems(outputList,
373  inputList,
374  gammaList,
375  maximumAcceptance=3.0,
376  multiplePhotons=False,
377  usePhotonOnlyOnce=True,
378  writeOut=False,
379  path=None):
380  """
381  For each particle in the given ``inputList``, copies it to the ``outputList`` and adds the
382  4-vector of the photon(s) in the ``gammaList`` which has(have) a weighted named relation to
383  the particle's track, set by the ``ECLTrackBremFinder`` module during reconstruction.
384 
385  Warning:
386  This can only work if the mdst file contains the *Bremsstrahlung* named relation. Official MC samples
387  up to and including MC12 and proc9 **do not** contain this. Newer production campaigns (from proc10 and MC13) will.
388 
389  Information:
390  Please note that a new particle is always generated, with the old particle and -if found- one or more
391  photons as daughters.
392 
393  The ``inputList`` should contain particles with associated tracks. Otherwise the module will exit with an error.
394 
395  The ``gammaList`` should contain photons. Otherwise the module will exit with an error.
396 
397  @param outputList The output particle list name containing the corrected particles
398  @param inputList The initial particle list name containing the particles to correct. *It should already exist.*
399  @param gammaList The photon list containing possibly bremsstrahlung photons; *It should already exist.*
400  @param maximumAcceptance Maximum value of the relation weight. Should be a number between [0,3)
401  @param multiplePhotons Whether to use only one photon (the one with the smallest acceptance) or as many as possible
402  @param usePhotonOnlyOnce If true, each brems candidate is used to correct only the track with the smallest relation weight
403  @param writeOut Whether `RootOutput` module should save the created ``outputList``
404  @param path The module is added to this path
405  """
406 
407  bremscorrector = register_module('BremsFinder')
408  bremscorrector.set_name('bremsCorrector_' + outputList)
409  bremscorrector.param('inputList', inputList)
410  bremscorrector.param('outputList', outputList)
411  bremscorrector.param('gammaList', gammaList)
412  bremscorrector.param('maximumAcceptance', maximumAcceptance)
413  bremscorrector.param('multiplePhotons', multiplePhotons)
414  bremscorrector.param('usePhotonOnlyOnce', usePhotonOnlyOnce)
415  bremscorrector.param('writeOut', writeOut)
416  path.add_module(bremscorrector)
417 
418 
419 def copyList(outputListName, inputListName, writeOut=False, path=None):
420  """
421  Copy all Particle indices from input ParticleList to the output ParticleList.
422  Note that the Particles themselves are not copied. The original and copied
423  ParticleLists will point to the same Particles.
424 
425  @param ouputListName copied ParticleList
426  @param inputListName original ParticleList to be copied
427  @param writeOut whether RootOutput module should save the created ParticleList
428  @param path modules are added to this path
429  """
430 
431  copyLists(outputListName, [inputListName], writeOut, path)
432 
433 
434 def correctBremsBelle(outputListName,
435  inputListName,
436  gammaListName,
437  multiplePhotons=True,
438  minimumEnergy=0.05,
439  angleThreshold=0.05,
440  writeOut=False,
441  path=None):
442  """
443  Run the Belle - like brems finding on the ``inputListName`` of charged particles.
444  Adds all photons in ``gammaListName`` to a copy of the charged particle that are within
445  ``angleThreshold`` and above ``minimumEnergy``.
446 
447  Parameters:
448  outputListName (str): The output charged particle list containing the corrected charged particles
449  inputListName (str): The initial charged particle list containing the charged particles to correct.
450  gammaListName (str): The gammas list containing possibly radiative gammas, should already exist.
451  multiplePhotons (bool): How many photons should be added to the charged particle? nearest one -> False,
452  add all the photons within the cone -> True
453  angleThreshold (float): The maximum angle in radians between the charged particle and the (radiative)
454  gamma to be accepted.
455  minimumEnergy (float): The minimum energy in GeV of the (radiative) gamma to be accepted.
456  writeOut (bool): whether RootOutput module should save the created ParticleList
457  path (basf2.Path): modules are added to this path
458  """
459 
460  fsrcorrector = register_module('BelleBremRecovery')
461  fsrcorrector.set_name('BelleFSRCorrection_' + outputListName)
462  fsrcorrector.param('inputListName', inputListName)
463  fsrcorrector.param('outputListName', outputListName)
464  fsrcorrector.param('gammaListName', gammaListName)
465  fsrcorrector.param('multiplePhotons', multiplePhotons)
466  fsrcorrector.param('angleThreshold', angleThreshold)
467  fsrcorrector.param('minimumEnergy', minimumEnergy)
468  fsrcorrector.param('writeOut', writeOut)
469  path.add_module(fsrcorrector)
470 
471 
472 def copyLists(outputListName, inputListNames, writeOut=False, path=None):
473  """
474  Copy all Particle indices from all input ParticleLists to the
475  single output ParticleList.
476  Note that the Particles themselves are not copied.
477  The original and copied ParticleLists will point to the same Particles.
478 
479  Duplicates are removed based on the first-come, first-served principle.
480  Therefore, the order of the input ParticleLists matters.
481 
482  .. seealso::
483  If you want to select the best duplicate based on another criterion, have
484  a look at the function `mergeListsWithBestDuplicate`.
485 
486  .. note::
487  Two particles that differ only by the order of their daughters are
488  considered duplicates and one of them will be removed.
489 
490  @param ouputListName copied ParticleList
491  @param inputListName vector of original ParticleLists to be copied
492  @param writeOut whether RootOutput module should save the created ParticleList
493  @param path modules are added to this path
494  """
495 
496  pmanipulate = register_module('ParticleListManipulator')
497  pmanipulate.set_name('PListCopy_' + outputListName)
498  pmanipulate.param('outputListName', outputListName)
499  pmanipulate.param('inputListNames', inputListNames)
500  pmanipulate.param('writeOut', writeOut)
501  path.add_module(pmanipulate)
502 
503 
504 def copyParticles(outputListName, inputListName, writeOut=False, path=None):
505  """
506  Create copies of Particles given in the input ParticleList and add them to the output ParticleList.
507 
508  The existing relations of the original Particle (or it's (grand-)^n-daughters)
509  are copied as well. Note that only the relation is copied and that the related
510  object is not. Copied particles are therefore related to the *same* object as
511  the original ones.
512 
513  @param ouputListName new ParticleList filled with copied Particles
514  @param inputListName input ParticleList with original Particles
515  @param writeOut whether RootOutput module should save the created ParticleList
516  @param path modules are added to this path
517  """
518 
519  # first copy original particles to the new ParticleList
520  pmanipulate = register_module('ParticleListManipulator')
521  pmanipulate.set_name('PListCopy_' + outputListName)
522  pmanipulate.param('outputListName', outputListName)
523  pmanipulate.param('inputListNames', [inputListName])
524  pmanipulate.param('writeOut', writeOut)
525  path.add_module(pmanipulate)
526 
527  # now replace original particles with their copies
528  pcopy = register_module('ParticleCopier')
529  pcopy.param('inputListNames', [outputListName])
530  path.add_module(pcopy)
531 
532 
533 def cutAndCopyLists(outputListName, inputListNames, cut, writeOut=False, path=None):
534  """
535  Copy candidates from all lists in ``inputListNames`` to
536  ``outputListName`` if they pass ``cut`` (given selection criteria).
537 
538  Note:
539  Note that the Particles themselves are not copied.
540  The original and copied ParticleLists will point to the same Particles.
541 
542  Example:
543  Require energetic pions safely inside the cdc
544 
545  >>> cutAndCopyLists("pi+:energeticPions", ["pi+:good", "pi+:loose"], "[E > 2] and [0.3 < theta < 2.6]", path=mypath)
546 
547  Warning:
548  You must use square braces ``[`` and ``]`` for conditional statements.
549 
550  Parameters:
551  outputListName (str): the new ParticleList name
552  inputListName (list(str)): list of input ParticleList names
553  cut (str): Candidates that do not pass these selection criteria are removed from the ParticleList
554  writeOut (bool): whether RootOutput module should save the created ParticleList
555  path (basf2.Path): modules are added to this path
556  """
557  pmanipulate = register_module('ParticleListManipulator')
558  pmanipulate.set_name('PListCutAndCopy_' + outputListName)
559  pmanipulate.param('outputListName', outputListName)
560  pmanipulate.param('inputListNames', inputListNames)
561  pmanipulate.param('cut', cut)
562  pmanipulate.param('writeOut', writeOut)
563  path.add_module(pmanipulate)
564 
565 
566 def cutAndCopyList(outputListName, inputListName, cut, writeOut=False, path=None):
567  """
568  Copy candidates from ``inputListName`` to ``outputListName`` if they pass
569  ``cut`` (given selection criteria).
570 
571  Note:
572  Note the Particles themselves are not copied.
573  The original and copied ParticleLists will point to the same Particles.
574 
575  Example:
576  require energetic pions safely inside the cdc
577 
578  >>> cutAndCopyLists("pi+:energeticPions", "pi+:loose", "[E > 2] and [0.3 < theta < 2.6]", path=mypath)
579 
580  Warning:
581  You must use square braces ``[`` and ``]`` for conditional statements.
582 
583  Parameters:
584  outputListName (str): the new ParticleList name
585  inputListName (str): input ParticleList name
586  cut (str): Candidates that do not pass these selection criteria are removed from the ParticleList
587  writeOut (bool): whether RootOutput module should save the created ParticleList
588  path (basf2.Path): modules are added to this path
589  """
590  cutAndCopyLists(outputListName, [inputListName], cut, writeOut, path)
591 
592 
593 def trackingEfficiency(inputListNames, fraction, path=None):
594  """
595  Randomly remove tracks from the provided particle lists to estimate the tracking efficiency.
596  Takes care of the duplicates, if any.
597 
598  Parameters:
599  inputListNames (list(str)): input particle list names
600  fraction (float): fraction of particles to be removed randomly
601  path (basf2.Path): module is added to this path
602  """
603 
604  trackingefficiency = register_module('TrackingEfficiency')
605  trackingefficiency.param('particleLists', inputListNames)
606  trackingefficiency.param('frac', fraction)
607  path.add_module(trackingefficiency)
608 
609 
610 def trackingMomentum(inputListNames, scale, path=None):
611  """
612  Scale momenta of the particles (based on charged tracks) according to the scaling factor scale.
613 
614  Parameters:
615  inputListNames (list(str)): input particle list names
616  scale (float): scaling factor (1.0 -- no scaling)
617  path (basf2.Path): module is added to this path
618  """
619 
620  trackingmomentum = register_module('TrackingMomentum')
621  trackingmomentum.param('particleLists', inputListNames)
622  trackingmomentum.param('scale', scale)
623  path.add_module(trackingmomentum)
624 
625 
626 def mergeListsWithBestDuplicate(outputListName,
627  inputListNames,
628  variable,
629  preferLowest=True,
630  writeOut=False,
631  path=None):
632  """
633  Merge input ParticleLists into one output ParticleList. Only the best
634  among duplicates is kept. The lowest or highest value (configurable via
635  preferLowest) of the provided variable determines which duplicate is the
636  best.
637 
638  @param ouputListName name of merged ParticleList
639  @param inputListName vector of original ParticleLists to be merged
640  @param variable variable to determine best duplicate
641  @param preferLowest whether lowest or highest value of variable should be preferred
642  @param writeOut whether RootOutput module should save the created ParticleList
643  @param path modules are added to this path
644  """
645 
646  pmanipulate = register_module('ParticleListManipulator')
647  pmanipulate.set_name('PListMerger_' + outputListName)
648  pmanipulate.param('outputListName', outputListName)
649  pmanipulate.param('inputListNames', inputListNames)
650  pmanipulate.param('variable', variable)
651  pmanipulate.param('preferLowest', preferLowest)
652  pmanipulate.param('writeOut', writeOut)
653  path.add_module(pmanipulate)
654 
655 
656 def fillSignalSideParticleList(outputListName, decayString, path):
657  """
658  This function should only be used in the ROE path, that is a path
659  that is executed for each ROE object in the DataStore.
660 
661  Example: fillSignalSideParticleList('gamma:sig','B0 -> K*0 ^gamma', roe_path)
662 
663  Function will create a ParticleList with name 'gamma:sig' which will be filled
664  with the existing photon Particle, being the second daughter of the B0 candidate
665  to which the ROE object has to be related.
666 
667  @param ouputListName name of the created ParticleList
668  @param decayString specify Particle to be added to the ParticleList
669  """
670 
671  pload = register_module('SignalSideParticleListCreator')
672  pload.set_name('SSParticleList_' + outputListName)
673  pload.param('particleListName', outputListName)
674  pload.param('decayString', decayString)
675  path.add_module(pload)
676 
677 
678 def fillParticleLists(decayStringsWithCuts, writeOut=False, path=None, enforceFitHypothesis=False):
679  """
680  Creates Particles of the desired types from the corresponding ``mdst`` dataobjects,
681  loads them to the ``StoreArray<Particle>`` and fills the ParticleLists.
682 
683  The multiple ParticleLists with their own selection criteria are specified
684  via list tuples (decayString, cut), for example
685 
686  .. code-block:: python
687 
688  kaons = ('K+:mykaons', 'kaonID>0.1')
689  pions = ('pi+:mypions','pionID>0.1')
690  fillParticleLists([kaons, pions])
691 
692  If you are unsure what selection you want, you might like to see the
693  :doc:`StandardParticles` functions.
694 
695  The type of the particles to be loaded is specified via the decayString module parameter.
696  The type of the ``mdst`` dataobject that is used as an input is determined from the type of
697  the particle. The following types of the particles can be loaded:
698 
699  * charged final state particles (input ``mdst`` type = Tracks)
700  - e+, mu+, pi+, K+, p, deuteron (and charge conjugated particles)
701 
702  * neutral final state particles
703  - "gamma" (input ``mdst`` type = ECLCluster)
704  - "K_S0", "Lambda0" (input ``mdst`` type = V0)
705  - "K_L0" (input ``mdst`` type = KLMCluster or ECLCluster)
706 
707  Note:
708  For "K_S0" and "Lambda0" you must specify the daughter ordering.
709 
710  For example, to load V0s as :math:`\\Lambda^0\\to p^+\\pi^-` decays from V0s:
711 
712  .. code-block:: python
713 
714  v0lambdas = ('Lambda0 -> p+ pi-', '0.9 < M < 1.3')
715  fillParticleLists([kaons, pions, v0lambdas], path=mypath)
716 
717  Tip:
718  For "K_L0" it is now possible to load from ECLClusters, to revert to
719  the old (Belle) behavior, you can require ``'isFromKLM > 0'``.
720 
721  .. code-block:: python
722 
723  klongs = ('K_L0', 'isFromKLM > 0')
724  fillParticleLists([kaons, pions, klongs], path=mypath)
725 
726 
727  Parameters:
728  decayStringsWithCuts (list): A list of python ntuples of (decayString, cut).
729  The decay string determines the type of Particle
730  and determines the of the ParticleList.
731  If the input MDST type is V0 the whole
732  decay chain needs to be specified, so that
733  the user decides and controls the daughters
734  ' order (e.g. ``K_S0 -> pi+ pi-``)
735  The cut is the selection criteria
736  to be added to the ParticleList. It can be an empty string.
737  writeOut (bool): whether RootOutput module should save the created ParticleList
738  path (basf2.Path): modules are added to this path
739  enforceFitHypothesis (bool): If true, Particles will be created only for the tracks which have been fitted
740  using a mass hypothesis of the exact type passed to fillParticleLists().
741  If enforceFitHypothesis is False (the default) the next closest fit hypothesis
742  in terms of mass difference will be used if the fit using exact particle
743  type is not available.
744  """
745 
746  pload = register_module('ParticleLoader')
747  pload.set_name('ParticleLoader_' + 'PLists')
748  pload.param('decayStringsWithCuts', decayStringsWithCuts)
749  pload.param('writeOut', writeOut)
750  pload.param("enforceFitHypothesis", enforceFitHypothesis)
751  path.add_module(pload)
752 
753 
754 def fillParticleList(decayString, cut, writeOut=False, path=None, enforceFitHypothesis=False):
755  """
756  Creates Particles of the desired type from the corresponding ``mdst`` dataobjects,
757  loads them to the StoreArray<Particle> and fills the ParticleList.
758 
759  See also:
760  the :doc:`StandardParticles` functions.
761 
762  The type of the particles to be loaded is specified via the decayString module parameter.
763  The type of the ``mdst`` dataobject that is used as an input is determined from the type of
764  the particle. The following types of the particles can be loaded:
765 
766  * charged final state particles (input ``mdst`` type = Tracks)
767  - e+, mu+, pi+, K+, p, deuteron (and charge conjugated particles)
768 
769  * neutral final state particles
770  - "gamma" (input ``mdst`` type = ECLCluster)
771  - "K_S0", "Lambda0" (input ``mdst`` type = V0)
772  - "K_L0" (input ``mdst`` type = KLMCluster or ECLCluster)
773 
774  Note:
775  For "K_S0" and "Lambda0" you must specify the daughter ordering.
776 
777  For example, to load V0s as :math:`\\Lambda^0\\to p^+\\pi^-` decays from V0ss:
778 
779  .. code-block:: python
780 
781  fillParticleList('Lambda0 -> p+ pi-', '0.9 < M < 1.3', path=mypath)
782 
783  Tip:
784  For "K_L0" it is now possible to load from ECLClusters, to revert to
785  the old (Belle) behavior, you can require ``'isFromKLM > 0'``.
786 
787  .. code-block:: python
788 
789  fillParticleList('K_L0', 'isFromKLM > 0', path=mypath)
790 
791  Parameters:
792  decayString (str): Type of Particle and determines the name of the ParticleList.
793  If the input MDST type is V0 the whole decay chain needs to be specified, so that
794  the user decides and controls the daughters' order (e.g. ``K_S0 -> pi+ pi-``)
795  cut (str): Particles need to pass these selection criteria to be added to the ParticleList
796  writeOut (bool): whether RootOutput module should save the created ParticleList
797  path (basf2.Path): modules are added to this path
798  enforceFitHypothesis (bool): If true, Particles will be created only for the tracks which have been fitted
799  using a mass hypothesis of the exact type passed to fillParticleLists().
800  If enforceFitHypothesis is False (the default) the next closest fit hypothesis
801  in terms of mass difference will be used if the fit using exact particle
802  type is not available.
803  """
804 
805  pload = register_module('ParticleLoader')
806  pload.set_name('ParticleLoader_' + decayString)
807  pload.param('decayStringsWithCuts', [(decayString, cut)])
808  pload.param('writeOut', writeOut)
809  pload.param("enforceFitHypothesis", enforceFitHypothesis)
810  path.add_module(pload)
811 
812 
813 def fillParticleListWithTrackHypothesis(decayString,
814  cut,
815  hypothesis,
816  writeOut=False,
817  enforceFitHypothesis=False,
818  path=None):
819  """
820  As fillParticleList, but if used for a charged FSP, loads the particle with the requested hypothesis if available
821 
822  @param decayString specifies type of Particles and determines the name of the ParticleList
823  @param cut Particles need to pass these selection criteria to be added to the ParticleList
824  @param hypothesis the PDG code of the desired track hypothesis
825  @param writeOut whether RootOutput module should save the created ParticleList
826  @param enforceFitHypothesis If true, Particles will be created only for the tracks which have been fitted
827  using a mass hypothesis of the exact type passed to fillParticleLists().
828  If enforceFitHypothesis is False (the default) the next closest fit hypothesis
829  in terms of mass difference will be used if the fit using exact particle
830  type is not available.
831  @param path modules are added to this path
832  """
833 
834  pload = register_module('ParticleLoader')
835  pload.set_name('ParticleLoader_' + decayString)
836  pload.param('decayStringsWithCuts', [(decayString, cut)])
837  pload.param('trackHypothesis', hypothesis)
838  pload.param('writeOut', writeOut)
839  pload.param("enforceFitHypothesis", enforceFitHypothesis)
840  path.add_module(pload)
841 
842 
843 def fillConvertedPhotonsList(decayString, cut, writeOut=False, path=None):
844  """
845  Creates photon Particle object for each e+e- combination in the V0 StoreArray.
846 
847  Note:
848  You must specify the daughter ordering.
849 
850  .. code-block:: python
851 
852  fillConvertedPhotonsList('gamma:converted -> e+ e-', '')
853 
854  Parameters:
855  decayString (str): Must be gamma to an e+e- pair. You muse specify the daughter ordering.
856  Will also determine the name of the particleList.
857  cut (str): Particles need to pass these selection criteria to be added to the ParticleList
858  writeOut (bool): whether RootOutput module should save the created ParticleList
859  path (basf2.Path): modules are added to this path
860 
861  """
862  pload = register_module('ParticleLoader')
863  pload.set_name('ParticleLoader_' + decayString)
864  pload.param('decayStringsWithCuts', [(decayString, cut)])
865  pload.param('addDaughters', True)
866  pload.param('writeOut', writeOut)
867  path.add_module(pload)
868 
869 
870 def fillParticleListFromROE(decayString,
871  cut,
872  maskName='',
873  sourceParticleListName='',
874  useMissing=False,
875  writeOut=False,
876  path=None):
877  """
878  Creates Particle object for each ROE of the desired type found in the
879  StoreArray<RestOfEvent>, loads them to the StoreArray<Particle>
880  and fills the ParticleList. If useMissing is True, then the missing
881  momentum is used instead of ROE.
882 
883  The type of the particles to be loaded is specified via the decayString module parameter.
884 
885  @param decayString specifies type of Particles and determines the name of the ParticleList.
886  Source ROEs can be taken as a daughter list, for example:
887  'B0:tagFromROE -> B0:signal'
888  @param cut Particles need to pass these selection criteria to be added to the ParticleList
889  @param maskName Name of the ROE mask to use
890  @param sourceParticleListName Use related ROEs to this particle list as a source
891  @param useMissing Use missing momentum instead of ROE momentum
892  @param writeOut whether RootOutput module should save the created ParticleList
893  @param path modules are added to this path
894  """
895 
896  pload = register_module('ParticleLoader')
897  pload.set_name('ParticleLoader_' + decayString)
898  pload.param('decayStringsWithCuts', [(decayString, cut)])
899  pload.param('writeOut', writeOut)
900  pload.param('roeMaskName', maskName)
901  pload.param('useMissing', useMissing)
902  pload.param('sourceParticleListName', sourceParticleListName)
903  pload.param('useROEs', True)
904  path.add_module(pload)
905 
906 
907 def fillParticleListFromMC(decayString,
908  cut,
909  addDaughters=False,
910  skipNonPrimaryDaughters=False,
911  writeOut=False,
912  path=None):
913  """
914  Creates Particle object for each MCParticle of the desired type found in the StoreArray<MCParticle>,
915  loads them to the StoreArray<Particle> and fills the ParticleList.
916 
917  The type of the particles to be loaded is specified via the decayString module parameter.
918 
919  @param decayString specifies type of Particles and determines the name of the ParticleList
920  @param cut Particles need to pass these selection criteria to be added to the ParticleList
921  @param addDaughters adds the bottom part of the decay chain of the particle to the datastore and
922  sets mother-daughter relations
923  @param skipNonPrimaryDaughters if true, skip non primary daughters, useful to study final state daughter particles
924  @param writeOut whether RootOutput module should save the created ParticleList
925  @param path modules are added to this path
926  """
927 
928  pload = register_module('ParticleLoader')
929  pload.set_name('ParticleLoader_' + decayString)
930  pload.param('decayStringsWithCuts', [(decayString, cut)])
931  pload.param('addDaughters', addDaughters)
932  pload.param('skipNonPrimaryDaughters', skipNonPrimaryDaughters)
933  pload.param('writeOut', writeOut)
934  pload.param('useMCParticles', True)
935  path.add_module(pload)
936 
937 
938 def fillParticleListsFromMC(decayStringsWithCuts,
939  addDaughters=False,
940  skipNonPrimaryDaughters=False,
941  writeOut=False,
942  path=None):
943  """
944  Creates Particle object for each MCParticle of the desired type found in the StoreArray<MCParticle>,
945  loads them to the StoreArray<Particle> and fills the ParticleLists.
946 
947  The types of the particles to be loaded are specified via the (decayString, cut) tuples given in a list.
948  For example:
949  kaons = ('K+:gen', '')
950  pions = ('pi+:gen', 'pionID>0.1')
951  fillParticleListsFromMC([kaons, pions])
952 
953  @param decayString specifies type of Particles and determines the name of the ParticleList
954  @param cut Particles need to pass these selection criteria to be added to the ParticleList
955  @param addDaughters adds the bottom part of the decay chain of the particle to the datastore and
956  sets mother-daughter relations
957  @param skipNonPrimaryDaughters if true, skip non primary daughters, useful to study final state daughter particles
958  @param writeOut whether RootOutput module should save the created ParticleList
959  @param path modules are added to this path
960  """
961 
962  pload = register_module('ParticleLoader')
963  pload.set_name('ParticleLoader_' + 'PLists')
964  pload.param('decayStringsWithCuts', decayStringsWithCuts)
965  pload.param('addDaughters', addDaughters)
966  pload.param('skipNonPrimaryDaughters', skipNonPrimaryDaughters)
967  pload.param('writeOut', writeOut)
968  pload.param('useMCParticles', True)
969  path.add_module(pload)
970 
971 
972 def applyCuts(list_name, cut, path):
973  """
974  Removes particle candidates from ``list_name`` that do not pass ``cut``
975  (given selection criteria).
976 
977  Example:
978  require energetic pions safely inside the cdc
979 
980  >>> applyCuts("pi+:mypions", "[E > 2] and [0.3 < theta < 2.6]", path=mypath)
981 
982  Warning:
983  You must use square braces ``[`` and ``]`` for conditional statements.
984 
985  Parameters:
986  list_name (str): input ParticleList name
987  cut (str): Candidates that do not pass these selection criteria are removed from the ParticleList
988  path (basf2.Path): modules are added to this path
989  """
990 
991  pselect = register_module('ParticleSelector')
992  pselect.set_name('ParticleSelector_applyCuts_' + list_name)
993  pselect.param('decayString', list_name)
994  pselect.param('cut', cut)
995  path.add_module(pselect)
996 
997 
998 def applyEventCuts(cut, path):
999  """
1000  Removes events that do not pass the ``cut`` (given selection criteria).
1001 
1002  Example:
1003  continuum events (in mc only) with more than 5 tracks
1004 
1005  >>> applyEventCuts("[nTracks > 5] and [isContinuumEvent], path=mypath)
1006 
1007  Warning:
1008  You must use square braces ``[`` and ``]`` for conditional statements.
1009 
1010  Parameters:
1011  cut (str): Events that do not pass these selection criteria are skipped
1012  path (basf2.Path): modules are added to this path
1013  """
1014 
1015  eselect = register_module('VariableToReturnValue')
1016  eselect.param('variable', 'passesEventCut(' + cut + ')')
1017  path.add_module(eselect)
1018  empty_path = create_path()
1019  eselect.if_value('<1', empty_path)
1020 
1021 
1022 def reconstructDecay(decayString,
1023  cut,
1024  dmID=0,
1025  writeOut=False,
1026  path=None,
1027  candidate_limit=None,
1028  ignoreIfTooManyCandidates=True,
1029  chargeConjugation=True,
1030  allowChargeViolation=False):
1031  r"""
1032  Creates new Particles by making combinations of existing Particles - it reconstructs unstable particles via their specified
1033  decay mode, e.g. in form of a :ref:`DecayString`: :code:`D0 -> K- pi+` or :code:`B+ -> anti-D0 pi+`, ... All possible
1034  combinations are created (particles are used only once per candidate) and combinations that pass the specified selection
1035  criteria are saved to a newly created (mother) ParticleList. By default the charge conjugated decay is reconstructed as well
1036  (meaning that the charge conjugated mother list is created as well) but this can be deactivated.
1037 
1038  One can use an ``@``-sign to mark a particle as unspecified, e.g. in form of a DecayString: :code:`\@Xsd -> K+ pi-`. If the
1039  particle is marked as unspecified, its identity will not be checked when doing :ref:`MCMatching`. Any particle which decays into
1040  the correct daughters will be flagged as correct. For example the DecayString :code:`\@Xsd -> K+ pi-` would match all particles
1041  which decay into a kaon and a pion, for example :math:`K^*`, :math:`B^0`, :math:`D^0`. Still the daughters need to be stated
1042  correctly so this can be used for "sum of exclusive" decays.
1043 
1044  .. warning::
1045  The input ParticleLists are typically ordered according to the upstream reconstruction algorithm.
1046  Therefore, if you combine two or more identical particles in the decay chain you should not expect to see the same
1047  distribution for the daughter kinematics as they may be sorted by geometry, momentum etc.
1048 
1049  For example, in the decay :code:`D0 -> pi0 pi0` the momentum distributions of the two ``pi0`` s are not identical.
1050  This can be solved by manually randomising the lists before combining.
1051 
1052  See Also:
1053 
1054  * `Particle combiner how does it work? <https://questions.belle2.org/question/4318/particle-combiner-how-does-it-work/>`_
1055  * `Identical particles in decay chain <https://questions.belle2.org/question/5724/identical-particles-in-decay-chain/>`_
1056 
1057  @param decayString :ref:`DecayString` specifying what kind of the decay should be reconstructed
1058  (from the DecayString the mother and daughter ParticleLists are determined)
1059  @param cut created (mother) Particles are added to the mother ParticleList if they
1060  pass give cuts (in VariableManager style) and rejected otherwise
1061  @param dmID user specified decay mode identifier
1062  @param writeOut whether RootOutput module should save the created ParticleList
1063  @param path modules are added to this path
1064  @param candidate_limit Maximum amount of candidates to be reconstructed. If
1065  the number of candidates is exceeded a Warning will be
1066  printed.
1067  By default, all these candidates will be removed and event will be ignored.
1068  This behaviour can be changed by \'ignoreIfTooManyCandidates\' flag.
1069  If no value is given the amount is limited to a sensible
1070  default. A value <=0 will disable this limit and can
1071  cause huge memory amounts so be careful.
1072  @param ignoreIfTooManyCandidates whether event should be ignored or not if number of reconstructed
1073  candidates reaches limit. If event is ignored, no candidates are reconstructed,
1074  otherwise, number of candidates in candidate_limit is reconstructed.
1075  @param chargeConjugation boolean to decide whether charge conjugated mode should be reconstructed as well (on by default)
1076  @param allowChargeViolation whether the decay string needs to conserve the electric charge
1077  """
1078 
1079  pmake = register_module('ParticleCombiner')
1080  pmake.set_name('ParticleCombiner_' + decayString)
1081  pmake.param('decayString', decayString)
1082  pmake.param('cut', cut)
1083  pmake.param('decayMode', dmID)
1084  pmake.param('writeOut', writeOut)
1085  if candidate_limit is not None:
1086  pmake.param("maximumNumberOfCandidates", candidate_limit)
1087  pmake.param("ignoreIfTooManyCandidates", ignoreIfTooManyCandidates)
1088  pmake.param('chargeConjugation', chargeConjugation)
1089  pmake.param("allowChargeViolation", allowChargeViolation)
1090  path.add_module(pmake)
1091 
1092 
1093 def combineAllParticles(inputParticleLists, outputList, cut='', writeOut=False, path=None):
1094  """
1095  Creates a new Particle as the combination of all Particles from all
1096  provided inputParticleLists. However, each particle is used only once
1097  (even if duplicates are provided) and the combination has to pass the
1098  specified selection criteria to be saved in the newly created (mother)
1099  ParticleList.
1100 
1101  @param inputParticleLists List of input particle lists which are combined to the new Particle
1102  @param outputList Name of the particle combination created with this module
1103  @param cut created (mother) Particle is added to the mother ParticleList if it passes
1104  these given cuts (in VariableManager style) and is rejected otherwise
1105  @param writeOut whether RootOutput module should save the created ParticleList
1106  @param path module is added to this path
1107  """
1108 
1109  pmake = register_module('AllParticleCombiner')
1110  pmake.set_name('AllParticleCombiner_' + outputList)
1111  pmake.param('inputListNames', inputParticleLists)
1112  pmake.param('outputListName', outputList)
1113  pmake.param('cut', cut)
1114  pmake.param('writeOut', writeOut)
1115  path.add_module(pmake)
1116 
1117 
1118 def reconstructMissingKlongDecayExpert(decayString,
1119  cut,
1120  dmID=0,
1121  writeOut=False,
1122  path=None,
1123  recoList="_reco"):
1124  """
1125  Creates a list of K_L0's with their momentum determined from kinematic constraints of B->K_L0 + something else.
1126 
1127  @param decayString DecayString specifying what kind of the decay should be reconstructed
1128  (from the DecayString the mother and daughter ParticleLists are determined)
1129  @param cut Particles are added to the K_L0 ParticleList if they
1130  pass the given cuts (in VariableManager style) and rejected otherwise
1131  @param dmID user specified decay mode identifier
1132  @param writeOut whether RootOutput module should save the created ParticleList
1133  @param path modules are added to this path
1134  @param recoList suffix appended to original K_L0 ParticleList that identifies the newly created K_L0 list
1135  """
1136 
1137  pcalc = register_module('KlongMomentumCalculatorExpert')
1138  pcalc.set_name('KlongMomentumCalculatorExpert_' + decayString)
1139  pcalc.param('decayString', decayString)
1140  pcalc.param('cut', cut)
1141  pcalc.param('decayMode', dmID)
1142  pcalc.param('writeOut', writeOut)
1143  pcalc.param('recoList', recoList)
1144  path.add_module(pcalc)
1145 
1146  rmake = register_module('KlongDecayReconstructorExpert')
1147  rmake.set_name('KlongDecayReconstructorExpert_' + decayString)
1148  rmake.param('decayString', decayString)
1149  rmake.param('cut', cut)
1150  rmake.param('decayMode', dmID)
1151  rmake.param('writeOut', writeOut)
1152  rmake.param('recoList', recoList)
1153  path.add_module(rmake)
1154 
1155 
1156 def replaceMass(replacerName, particleLists=[], pdgCode=22, path=None):
1157  """
1158  replaces the mass of the particles inside the given particleLists
1159  with the invariant mass of the particle corresponding to the given pdgCode.
1160 
1161  @param particleLists new ParticleList filled with copied Particles
1162  @param pdgCode PDG code for mass reference
1163  @param path modules are added to this path
1164  """
1165 
1166  # first copy original particles to the new ParticleList
1167  pmassupdater = register_module('ParticleMassUpdater')
1168  pmassupdater.set_name('ParticleMassUpdater_' + replacerName)
1169  pmassupdater.param('particleLists', particleLists)
1170  pmassupdater.param('pdgCode', pdgCode)
1171  path.add_module(pmassupdater)
1172 
1173 
1174 def reconstructRecoil(decayString,
1175  cut,
1176  dmID=0,
1177  writeOut=False,
1178  path=None,
1179  candidate_limit=None,
1180  allowChargeViolation=False):
1181  """
1182  Creates new Particles that recoil against the input particles.
1183 
1184  For example the decay string M -> D1 D2 D3 will:
1185  - create mother Particle M for each unique combination of D1, D2, D3 Particles
1186  - Particles D1, D2, D3 will be appended as daughters to M
1187  - the 4-momentum of the mother Particle M is given by
1188  p(M) = p(HER) + p(LER) - Sum_i p(Di)
1189 
1190  @param decayString DecayString specifying what kind of the decay should be reconstructed
1191  (from the DecayString the mother and daughter ParticleLists are determined)
1192  @param cut created (mother) Particles are added to the mother ParticleList if they
1193  pass give cuts (in VariableManager style) and rejected otherwise
1194  @param dmID user specified decay mode identifier
1195  @param writeOut whether RootOutput module should save the created ParticleList
1196  @param path modules are added to this path
1197  @param candidate_limit Maximum amount of candidates to be reconstructed. If
1198  the number of candidates is exceeded no candidate will be
1199  reconstructed for that event and a Warning will be
1200  printed.
1201  If no value is given the amount is limited to a sensible
1202  default. A value <=0 will disable this limit and can
1203  cause huge memory amounts so be careful.
1204  @param allowChargeViolation whether the decay string needs to conserve the electric charge
1205  """
1206 
1207  pmake = register_module('ParticleCombiner')
1208  pmake.set_name('ParticleCombiner_' + decayString)
1209  pmake.param('decayString', decayString)
1210  pmake.param('cut', cut)
1211  pmake.param('decayMode', dmID)
1212  pmake.param('writeOut', writeOut)
1213  pmake.param('recoilParticleType', 1)
1214  if candidate_limit is not None:
1215  pmake.param("maximumNumberOfCandidates", candidate_limit)
1216  pmake.param('allowChargeViolation', allowChargeViolation)
1217  path.add_module(pmake)
1218 
1219 
1220 def reconstructRecoilDaughter(decayString,
1221  cut,
1222  dmID=0,
1223  writeOut=False,
1224  path=None,
1225  candidate_limit=None,
1226  allowChargeViolation=False):
1227  """
1228  Creates new Particles that are daughters of the particle reconstructed in the recoil (always assumed to be the first daughter).
1229 
1230  For example the decay string M -> D1 D2 D3 will:
1231  - create mother Particle M for each unique combination of D1, D2, D3 Particles
1232  - Particles D1, D2, D3 will be appended as daughters to M
1233  - the 4-momentum of the mother Particle M is given by
1234  p(M) = p(D1) - Sum_i p(Di), where i>1
1235 
1236  @param decayString DecayString specifying what kind of the decay should be reconstructed
1237  (from the DecayString the mother and daughter ParticleLists are determined)
1238  @param cut created (mother) Particles are added to the mother ParticleList if they
1239  pass give cuts (in VariableManager style) and rejected otherwise
1240  @param dmID user specified decay mode identifier
1241  @param writeOut whether RootOutput module should save the created ParticleList
1242  @param path modules are added to this path
1243  @param candidate_limit Maximum amount of candidates to be reconstructed. If
1244  the number of candidates is exceeded no candidate will be
1245  reconstructed for that event and a Warning will be
1246  printed.
1247  If no value is given the amount is limited to a sensible
1248  default. A value <=0 will disable this limit and can
1249  cause huge memory amounts so be careful.
1250  @param allowChargeViolation whether the decay string needs to conserve the electric charge taking into account that the first
1251  daughter is actually the mother
1252  """
1253 
1254  pmake = register_module('ParticleCombiner')
1255  pmake.set_name('ParticleCombiner_' + decayString)
1256  pmake.param('decayString', decayString)
1257  pmake.param('cut', cut)
1258  pmake.param('decayMode', dmID)
1259  pmake.param('writeOut', writeOut)
1260  pmake.param('recoilParticleType', 2)
1261  if candidate_limit is not None:
1262  pmake.param("maximumNumberOfCandidates", candidate_limit)
1263  pmake.param('allowChargeViolation', allowChargeViolation)
1264  path.add_module(pmake)
1265 
1266 
1267 def rankByHighest(particleList,
1268  variable,
1269  numBest=0,
1270  outputVariable='',
1271  allowMultiRank=False,
1272  cut='',
1273  path=None):
1274  """
1275  Ranks particles in the input list by the given variable (highest to lowest), and stores an integer rank for each Particle
1276  in an :b2:var:`extraInfo` field ``${variable}_rank`` starting at 1 (best).
1277  The list is also sorted from best to worst candidate
1278  (each charge, e.g. B+/B-, separately).
1279  This can be used to perform a best candidate selection by cutting on the corresponding rank value, or by specifying
1280  a non-zero value for 'numBest'.
1281 
1282  .. tip::
1283  Extra-info fields can be accessed by the :b2:var:`extraInfo` metavariable.
1284  These variable names can become clunky, so it's probably a good idea to set an alias.
1285  For example if you rank your B candidates by momentum,
1286 
1287  .. code:: python
1288 
1289  rankByHighest("B0:myCandidates", "p", path=mypath)
1290  vm.addAlias("momentumRank", "extraInfo(p_rank)")
1291 
1292 
1293  @param particleList The input ParticleList
1294  @param variable Variable to order Particles by.
1295  @param numBest If not zero, only the $numBest Particles in particleList with rank <= numBest are kept.
1296  @param outputVariable Name for the variable that will be created which contains the rank, Default is '${variable}_rank'.
1297  @param allowMultiRank If true, candidates with the same value will get the same rank.
1298  @param cut Only candidates passing the cut will be ranked. The others will have rank -1
1299  @param path modules are added to this path
1300  """
1301 
1302  bcs = register_module('BestCandidateSelection')
1303  bcs.set_name('BestCandidateSelection_' + particleList + '_' + variable)
1304  bcs.param('particleList', particleList)
1305  bcs.param('variable', variable)
1306  bcs.param('numBest', numBest)
1307  bcs.param('outputVariable', outputVariable)
1308  bcs.param('allowMultiRank', allowMultiRank)
1309  bcs.param('cut', cut)
1310  path.add_module(bcs)
1311 
1312 
1313 def rankByLowest(particleList,
1314  variable,
1315  numBest=0,
1316  outputVariable='',
1317  allowMultiRank=False,
1318  cut='',
1319  path=None):
1320  """
1321  Ranks particles in the input list by the given variable (lowest to highest), and stores an integer rank for each Particle
1322  in an :b2:var:`extraInfo` field ``${variable}_rank`` starting at 1 (best).
1323  The list is also sorted from best to worst candidate
1324  (each charge, e.g. B+/B-, separately).
1325  This can be used to perform a best candidate selection by cutting on the corresponding rank value, or by specifying
1326  a non-zero value for 'numBest'.
1327 
1328  .. tip::
1329  Extra-info fields can be accessed by the :b2:var:`extraInfo` metavariable.
1330  These variable names can become clunky, so it's probably a good idea to set an alias.
1331  For example if you rank your B candidates by :b2:var:`dM`,
1332 
1333  .. code:: python
1334 
1335  rankByLowest("B0:myCandidates", "dM", path=mypath)
1336  vm.addAlias("massDifferenceRank", "extraInfo(dM_rank)")
1337 
1338 
1339  @param particleList The input ParticleList
1340  @param variable Variable to order Particles by.
1341  @param numBest If not zero, only the $numBest Particles in particleList with rank <= numBest are kept.
1342  @param outputVariable Name for the variable that will be created which contains the rank, Default is '${variable}_rank'.
1343  @param allowMultiRank If true, candidates with the same value will get the same rank.
1344  @param cut Only candidates passing the cut will be ranked. The others will have rank -1
1345  @param path modules are added to this path
1346  """
1347 
1348  bcs = register_module('BestCandidateSelection')
1349  bcs.set_name('BestCandidateSelection_' + particleList + '_' + variable)
1350  bcs.param('particleList', particleList)
1351  bcs.param('variable', variable)
1352  bcs.param('numBest', numBest)
1353  bcs.param('selectLowest', True)
1354  bcs.param('allowMultiRank', allowMultiRank)
1355  bcs.param('outputVariable', outputVariable)
1356  bcs.param('cut', cut)
1357  path.add_module(bcs)
1358 
1359 
1360 def applyRandomCandidateSelection(particleList, path=None):
1361  """
1362  If there are multiple candidates in the provided particleList, all but one of them are removed randomly.
1363  This is done on a event-by-event basis.
1364 
1365  @param particleList ParticleList for which the random candidate selection should be applied
1366  @param path module is added to this path
1367  """
1368 
1369  rcs = register_module('BestCandidateSelection')
1370  rcs.set_name('RandomCandidateSelection_' + particleList)
1371  rcs.param('particleList', particleList)
1372  rcs.param('variable', 'random')
1373  rcs.param('selectLowest', False)
1374  rcs.param('allowMultiRank', False)
1375  rcs.param('numBest', 1)
1376  rcs.param('cut', '')
1377  rcs.param('outputVariable', '')
1378  path.add_module(rcs)
1379 
1380 
1381 def printDataStore(eventNumber=-1, path=None):
1382  """
1383  Prints the contents of DataStore in the first event (or a specific event number or all events).
1384  Will list all objects and arrays (including size).
1385 
1386  See also:
1387  The command line tool: ``b2file-size``.
1388 
1389  Parameters:
1390  eventNumber (int): Print the datastore only for this event. The default
1391  (-1) prints only the first event, 0 means print for all events (can produce large output)
1392  path (basf2.Path): the PrintCollections module is added to this path
1393 
1394  Warning:
1395  This will print a lot of output if you print it for all events and process many events.
1396 
1397  """
1398 
1399  printDS = register_module('PrintCollections')
1400  printDS.param('printForEvent', eventNumber)
1401  path.add_module(printDS)
1402 
1403 
1404 def printVariableValues(list_name, var_names, path):
1405  """
1406  Prints out values of specified variables of all Particles included in given ParticleList. For debugging purposes.
1407 
1408  @param list_name input ParticleList name
1409  @param var_names vector of variable names to be printed
1410  @param path modules are added to this path
1411  """
1412 
1413  prlist = register_module('ParticlePrinter')
1414  prlist.set_name('ParticlePrinter_' + list_name)
1415  prlist.param('listName', list_name)
1416  prlist.param('fullPrint', False)
1417  prlist.param('variables', var_names)
1418  path.add_module(prlist)
1419 
1420 
1421 def printList(list_name, full, path):
1422  """
1423  Prints the size and executes Particle->print() (if full=True)
1424  method for all Particles in given ParticleList. For debugging purposes.
1425 
1426  @param list_name input ParticleList name
1427  @param full execute Particle->print() method for all Particles
1428  @param path modules are added to this path
1429  """
1430 
1431  prlist = register_module('ParticlePrinter')
1432  prlist.set_name('ParticlePrinter_' + list_name)
1433  prlist.param('listName', list_name)
1434  prlist.param('fullPrint', full)
1435  path.add_module(prlist)
1436 
1437 
1438 def variablesToNtuple(decayString, variables, treename='variables', filename='ntuple.root', path=None):
1439  """
1440  Creates and fills a flat ntuple with the specified variables from the VariableManager.
1441  If a decayString is provided, then there will be one entry per candidate (for particle in list of candidates).
1442  If an empty decayString is provided, there will be one entry per event (useful for trigger studies, etc).
1443 
1444  Parameters:
1445  decayString (str): specifies type of Particles and determines the name of the ParticleList
1446  variables (list(str)): the list of variables (which must be registered in the VariableManager)
1447  treename (str): name of the ntuple tree
1448  filename (str): which is used to store the variables
1449  path (basf2.Path): the basf2 path where the analysis is processed
1450  """
1451 
1452  output = register_module('VariablesToNtuple')
1453  output.set_name('VariablesToNtuple_' + decayString)
1454  output.param('particleList', decayString)
1455  output.param('variables', variables)
1456  output.param('fileName', filename)
1457  output.param('treeName', treename)
1458  path.add_module(output)
1459 
1460 
1461 def variablesToHistogram(decayString,
1462  variables,
1463  variables_2d=[],
1464  filename='ntuple.root',
1465  path=None, *,
1466  directory=None,
1467  prefixDecayString=False):
1468  """
1469  Creates and fills a flat ntuple with the specified variables from the VariableManager
1470 
1471  Parameters:
1472  decayString (str): specifies type of Particles and determines the name of the ParticleList
1473  variables (list(tuple))): variables + binning which must be registered in the VariableManager
1474  variables_2d (list(tuple)): pair of variables + binning for each which must be registered in the VariableManager
1475  filename (str): which is used to store the variables
1476  path (basf2.Path): the basf2 path where the analysis is processed
1477  directory (str): directory inside the output file where the histograms should be saved.
1478  Useful if you want to have different histograms in the same file to separate them.
1479  prefixDecayString (bool): If True the decayString will be prepended to the directory name to allow for more
1480  programmatic naming of the structure in the file.
1481  """
1482 
1483  output = register_module('VariablesToHistogram')
1484  output.set_name('VariablesToHistogram_' + decayString)
1485  output.param('particleList', decayString)
1486  output.param('variables', variables)
1487  output.param('variables_2d', variables_2d)
1488  output.param('fileName', filename)
1489  if directory is not None or prefixDecayString:
1490  if directory is None:
1491  directory = ""
1492  if prefixDecayString:
1493  directory = decayString + "_" + directory
1494  output.param("directory", directory)
1495  path.add_module(output)
1496 
1497 
1498 def variablesToExtraInfo(particleList, variables, option=0, path=None):
1499  """
1500  For each particle in the input list the selected variables are saved in an extra-info field with the given name.
1501  Can be used when wanting to save variables before modifying them, e.g. when performing vertex fits.
1502 
1503  An existing extra info with the same name will be overwritten if the new
1504  value is lower / will never be overwritten / will be overwritten if the
1505  new value is higher / will always be overwritten (-1/0/1/2).
1506 
1507  @param particleList The input ParticleList
1508  @param variables Dictionary of Variables and extraInfo names.
1509  @param path modules are added to this path
1510  """
1511 
1512  mod = register_module('VariablesToExtraInfo')
1513  mod.set_name('VariablesToExtraInfo_' + particleList)
1514  mod.param('particleList', particleList)
1515  mod.param('variables', variables)
1516  mod.param('overwrite', option)
1517  path.add_module(mod)
1518 
1519 
1520 def variablesToDaughterExtraInfo(particleList, decayString, variables, option=0, path=None):
1521  """
1522  For each daughter particle specified via decay string the selected variables (estimated for the mother particle)
1523  are saved in an extra-info field with the given name. In other words, the property of mother is saved as extra-info
1524  to specified daughter particle.
1525 
1526  An existing extra info with the same name will be overwritten if the new
1527  value is lower / will never be overwritten / will be overwritten if the
1528  new value is higher / will always be overwritten (-1/0/1/2).
1529 
1530  @param particleList The input ParticleList
1531  @param decayString Decay string that specifies to which daughter the extra info should be appended
1532  @param variables Dictionary of Variables and extraInfo names.
1533  @param option Various options for overwriting
1534  @param path modules are added to this path
1535  """
1536 
1537  mod = register_module('VariablesToExtraInfo')
1538  mod.set_name('VariablesToDaughterExtraInfo_' + particleList)
1539  mod.param('particleList', particleList)
1540  mod.param('decayString', decayString)
1541  mod.param('variables', variables)
1542  mod.param('overwrite', option)
1543  path.add_module(mod)
1544 
1545 
1546 def variablesToEventExtraInfo(particleList, variables, option=0, path=None):
1547  """
1548  For each particle in the input list the selected variables are saved in an event-extra-info field with the given name,
1549  Can be used to save MC truth information, for example, in a ntuple of reconstructed particles.
1550 
1551  An existing extra info with the same name will be overwritten if the new
1552  value is lower / will never be overwritten / will be overwritten if the
1553  new value is higher / will always be overwritten (-1/0/1/2).
1554 
1555  @param particleList The input ParticleList
1556  @param variables Dictionary of Variables and extraInfo names.
1557  @param path modules are added to this path
1558  """
1559 
1560  mod = register_module('VariablesToEventExtraInfo')
1561  mod.set_name('VariablesToEventExtraInfo_' + particleList)
1562  mod.param('particleList', particleList)
1563  mod.param('variables', variables)
1564  mod.param('overwrite', option)
1565  path.add_module(mod)
1566 
1567 
1568 def variableToSignalSideExtraInfo(particleList, varToExtraInfo, path):
1569  """
1570  Write the value of specified variables estimated for the single particle in the input list (has to contain exactly 1
1571  particle) as an extra info to the particle related to current ROE.
1572  Should be used only in the for_each roe path.
1573 
1574  @param particleList The input ParticleList
1575  @param varToExtraInfo Dictionary of Variables and extraInfo names.
1576  @param path modules are added to this path
1577  """
1578  mod = register_module('SignalSideVariablesToExtraInfo')
1579  mod.set_name('SigSideVarToExtraInfo_' + particleList)
1580  mod.param('particleListName', particleList)
1581  mod.param('variableToExtraInfo', varToExtraInfo)
1582  path.add_module(mod)
1583 
1584 
1585 def signalRegion(particleList, cut, path=None, name="isSignalRegion", blind_data=True):
1586  """
1587  Define and blind a signal region.
1588  Per default, the defined signal region is cut out if ran on data.
1589  This function will provide a new variable 'isSignalRegion' as default, which is either 0 or 1 depending on the cut
1590  provided.
1591 
1592  Example:
1593  >>> ma.reconstructDecay("B+:sig -> D+ pi0", "Mbc>5.2", path=path)
1594  >>> ma.signalRegion("B+:sig",
1595  >>> "Mbc>5.27 and abs(deltaE)<0.2",
1596  >>> blind_data=True,
1597  >>> path=path)
1598  >>> ma.variablesToNtuples("B+:sig", ["isSignalRegion"], path=path)
1599 
1600  Parameters:
1601  particleList (str): The input ParticleList
1602  cut (str): Cut string describing the signal region
1603  path (basf2.Path):: Modules are added to this path
1604  name (str): Name of the Signal region in the variable manager
1605  blind_data (bool): Automatically exclude signal region from data
1606 
1607  """
1608 
1609  from variables import variables
1610  mod = register_module('VariablesToExtraInfo')
1611  mod.set_name(f'{name}_' + particleList)
1612  mod.param('particleList', particleList)
1613  mod.param('variables', {f"passesCut({cut})": name})
1614  variables.addAlias(name, f"extraInfo({name})")
1615  path.add_module(mod)
1616 
1617  # Check if we run on Data
1618  if blind_data:
1619  applyCuts(particleList, f"{name}==0 or isMC==1", path=path)
1620 
1621 
1622 def removeExtraInfo(particleLists=[], removeEventExtraInfo=False, path=None):
1623  """
1624  Removes the ExtraInfo of the given particleLists. If specified (removeEventExtraInfo = True) also the EventExtraInfo is removed.
1625  """
1626 
1627  mod = register_module('ExtraInfoRemover')
1628  mod.param('particleLists', particleLists)
1629  mod.param('removeEventExtraInfo', removeEventExtraInfo)
1630  path.add_module(mod)
1631 
1632 
1633 def signalSideParticleFilter(particleList, selection, roe_path, deadEndPath):
1634  """
1635  Checks if the current ROE object in the for_each roe path (argument roe_path) is related
1636  to the particle from the input ParticleList. Additional selection criteria can be applied.
1637  If ROE is not related to any of the Particles from ParticleList or the Particle doesn't
1638  meet the selection criteria the execution of deadEndPath is started. This path, as the name
1639  suggests should be empty and its purpose is to end the execution of for_each roe path for
1640  the current ROE object.
1641 
1642  @param particleList The input ParticleList
1643  @param selection Selection criteria that Particle needs meet in order for for_each ROE path to continue
1644  @param for_each roe path in which this filter is executed
1645  @param deadEndPath empty path that ends execution of or_each roe path for the current ROE object.
1646  """
1647  mod = register_module('SignalSideParticleFilter')
1648  mod.set_name('SigSideParticleFilter_' + particleList)
1649  mod.param('particleLists', [particleList])
1650  mod.param('selection', selection)
1651  roe_path.add_module(mod)
1652  mod.if_false(deadEndPath)
1653 
1654 
1655 def signalSideParticleListsFilter(particleLists, selection, roe_path, deadEndPath):
1656  """
1657  Checks if the current ROE object in the for_each roe path (argument roe_path) is related
1658  to the particle from the input ParticleList. Additional selection criteria can be applied.
1659  If ROE is not related to any of the Particles from ParticleList or the Particle doesn't
1660  meet the selection criteria the execution of deadEndPath is started. This path, as the name
1661  suggests should be empty and its purpose is to end the execution of for_each roe path for
1662  the current ROE object.
1663 
1664  @param particleLists The input ParticleLists
1665  @param selection Selection criteria that Particle needs meet in order for for_each ROE path to continue
1666  @param for_each roe path in which this filter is executed
1667  @param deadEndPath empty path that ends execution of or_each roe path for the current ROE object.
1668  """
1669  mod = register_module('SignalSideParticleFilter')
1670  mod.set_name('SigSideParticleFilter_' + particleLists[0])
1671  mod.param('particleLists', particleLists)
1672  mod.param('selection', selection)
1673  roe_path.add_module(mod)
1674  mod.if_false(deadEndPath)
1675 
1676 
1677 def reconstructMCDecay(
1678  decayString,
1679  cut,
1680  dmID=0,
1681  writeOut=False,
1682  path=None,
1683  chargeConjugation=True,
1684 ):
1685  r"""
1686  Finds and creates a ``ParticleList`` from given decay string.
1687  ``ParticleList`` of daughters with sub-decay is created.
1688 
1689  Only signal particle, which means :b2:var:`isSignal` is equal to 1, is stored. One can use the decay string grammar
1690  to change the behavior of :b2:var:`isSignal`. One can find detailed information in :ref:`DecayString`.
1691 
1692  .. tip::
1693  If one uses same sub-decay twice, same particles are registered to a ``ParticleList``. For example,
1694  ``K_S0:pi0pi0 =direct=> [pi0:gg =direct=> gamma:MC gamma:MC] [pi0:gg =direct=> gamma:MC gamma:MC]``.
1695  One can skip the second sub-decay, ``K_S0:pi0pi0 =direct=> [pi0:gg =direct=> gamma:MC gamma:MC] pi0:gg``.
1696 
1697 
1698  @param decayString :ref:`DecayString` specifying what kind of the decay should be reconstructed
1699  (from the DecayString the mother and daughter ParticleLists are determined)
1700  @param cut created (mother) Particles are added to the mother ParticleList if they
1701  pass given cuts (in VariableManager style) and rejected otherwise
1702  isSignal==1 is always required by default.
1703  @param dmID user specified decay mode identifier
1704  @param writeOut whether RootOutput module should save the created ParticleList
1705  @param path modules are added to this path
1706  @param chargeConjugation boolean to decide whether charge conjugated mode should be reconstructed as well (on by default)
1707  """
1708 
1709  pmake = register_module('ParticleCombinerFromMC')
1710  pmake.set_name('ParticleCombinerFromMC_' + decayString)
1711  pmake.param('decayString', decayString)
1712  pmake.param('cut', cut)
1713  pmake.param('decayMode', dmID)
1714  pmake.param('writeOut', writeOut)
1715  pmake.param('chargeConjugation', chargeConjugation)
1716  path.add_module(pmake)
1717 
1718 
1719 def findMCDecay(
1720  list_name,
1721  decay,
1722  writeOut=False,
1723  path=None,
1724 ):
1725  """
1726  .. warning::
1727  This function is not fully tested and maintained.
1728  Please consider to use reconstructMCDecay() instead.
1729 
1730  Finds and creates a ``ParticleList`` for all ``MCParticle`` decays matching a given :ref:`DecayString`.
1731  The decay string is required to describe correctly what you want.
1732  In the case of inclusive decays, you can use :ref:`Grammar_for_custom_MCMatching`
1733 
1734  @param list_name The output particle list name
1735  @param decay The decay string which you want
1736  @param writeOut Whether `RootOutput` module should save the created ``outputList``
1737  @param path modules are added to this path
1738  """
1739  B2WARNING("This function is not fully tested and maintained."
1740  "Please consider to use reconstructMCDecay() instead.")
1741 
1742  decayfinder = register_module('MCDecayFinder')
1743  decayfinder.set_name('MCDecayFinder_' + list_name)
1744  decayfinder.param('listName', list_name)
1745  decayfinder.param('decayString', decay)
1746  decayfinder.param('writeOut', writeOut)
1747  path.add_module(decayfinder)
1748 
1749 
1750 def summaryOfLists(particleLists, path):
1751  """
1752  Prints out Particle statistics at the end of the job: number of events with at
1753  least one candidate, average number of candidates per event, etc.
1754 
1755  @param particleLists list of input ParticleLists
1756  """
1757 
1758  particleStats = register_module('ParticleStats')
1759  particleStats.param('particleLists', particleLists)
1760  path.add_module(particleStats)
1761 
1762 
1763 def matchMCTruth(list_name, path):
1764  """
1765  Performs MC matching (sets relation Particle->MCParticle) for
1766  all particles (and its (grand)^N-daughter particles) in the specified
1767  ParticleList.
1768 
1769  @param list_name name of the input ParticleList
1770  @param path modules are added to this path
1771  """
1772 
1773  mcMatch = register_module('MCMatcherParticles')
1774  mcMatch.set_name('MCMatch_' + list_name)
1775  mcMatch.param('listName', list_name)
1776  path.add_module(mcMatch)
1777 
1778 
1779 def looseMCTruth(list_name, path):
1780  """
1781  Performs loose MC matching for all particles in the specified
1782  ParticleList.
1783  The difference between loose and normal mc matching algorithm is that
1784  the loose algorithm will find the common mother of the majority of daughter
1785  particles while the normal algorithm finds the common mother of all daughters.
1786  The results of loose mc matching algorithm are stored to the following extraInfo
1787  items:
1788 
1789  - looseMCMotherPDG: PDG code of most common mother
1790  - looseMCMotherIndex: 1-based StoreArray<MCParticle> index of most common mother
1791  - looseMCWrongDaughterN: number of daughters that don't originate from the most
1792  common mother
1793  - looseMCWrongDaughterPDG: PDG code of the daughter that doesn't originate from
1794  the most common mother
1795  (only if looseMCWrongDaughterN = 1)
1796  - looseMCWrongDaughterBiB: 1 if the wrong daughter is Beam Induced Background
1797  Particle
1798 
1799  @param list_name name of the input ParticleList
1800  @param path modules are added to this path
1801  """
1802 
1803  mcMatch = register_module('MCMatcherParticles')
1804  mcMatch.set_name('LooseMCMatch_' + list_name)
1805  mcMatch.param('listName', list_name)
1806  mcMatch.param('looseMCMatching', True)
1807  path.add_module(mcMatch)
1808 
1809 
1810 def buildRestOfEvent(target_list_name, inputParticlelists=None,
1811  fillWithMostLikely=False,
1812  chargedPIDPriors=None, path=None):
1813  """
1814  Creates for each Particle in the given ParticleList a RestOfEvent
1815  dataobject and makes BASF2 relation between them. User can provide additional
1816  particle lists with a different particle hypotheses like ['K+:good, e+:good'], etc.
1817 
1818  @param target_list_name name of the input ParticleList
1819  @param inputParticlelists list of input particle list names, which serve
1820  as a source of particles to build ROE, the FSP particles from
1821  target_list_name are excluded from ROE object
1822  @param fillWithMostLikely if True, the module uses particle mass hypothesis for charged particles
1823  according to PID likelihood and the inputParticlelists
1824  option will be ignored.
1825  @param chargedPIDPriors The prior PID fractions, that are used to regulate
1826  amount of certain charged particle species, should be a list of
1827  six floats if not None. The order of particle types is
1828  the following: [e-, mu-, pi-, K-, p+, d+]
1829  @param path modules are added to this path
1830  """
1831  if inputParticlelists is None:
1832  inputParticlelists = []
1833  fillParticleList('pi+:roe_default', '', path=path)
1834  if fillWithMostLikely:
1835  from stdCharged import stdMostLikely
1836  stdMostLikely(chargedPIDPriors, '_roe', path=path)
1837  inputParticlelists = ['%s:mostlikely_roe' % ptype for ptype in ['K+', 'p+', 'e+', 'mu+']]
1838  import b2bii
1839  if not b2bii.isB2BII():
1840  fillParticleList('gamma:roe_default', '', path=path)
1841  fillParticleList('K_L0:roe_default', 'isFromKLM > 0', path=path)
1842  inputParticlelists += ['pi+:roe_default', 'gamma:roe_default', 'K_L0:roe_default']
1843  else:
1844  inputParticlelists += ['pi+:roe_default', 'gamma:mdst']
1845  roeBuilder = register_module('RestOfEventBuilder')
1846  roeBuilder.set_name('ROEBuilder_' + target_list_name)
1847  roeBuilder.param('particleList', target_list_name)
1848  roeBuilder.param('particleListsInput', inputParticlelists)
1849  path.add_module(roeBuilder)
1850 
1851 
1852 def buildNestedRestOfEvent(target_list_name, maskName='', path=None):
1853  """
1854  Creates for each Particle in the given ParticleList a RestOfEvent
1855  @param target_list_name name of the input ParticleList
1856  @param mask_name name of the ROEMask to be used
1857  @param path modules are added to this path
1858  """
1859  roeBuilder = register_module('RestOfEventBuilder')
1860  roeBuilder.set_name('NestedROEBuilder_' + target_list_name)
1861  roeBuilder.param('particleList', target_list_name)
1862  roeBuilder.param('nestedROEMask', maskName)
1863  roeBuilder.param('createNestedROE', True)
1864  path.add_module(roeBuilder)
1865 
1866 
1867 def buildRestOfEventFromMC(target_list_name, inputParticlelists=[], path=None):
1868  """
1869  Creates for each Particle in the given ParticleList a RestOfEvent
1870  @param target_list_name name of the input ParticleList
1871  @param inputParticlelists list of input particle list names, which serve
1872  as a source of particles to build ROE, the FSP particles from
1873  target_list_name are excluded from ROE object
1874  @param path modules are added to this path
1875  """
1876  if (len(inputParticlelists) == 0):
1877  # Type of particles to use for ROEBuilder
1878  # K_S0 and Lambda0 are added here because some of them have interacted
1879  # with the detector material
1880  types = ['gamma', 'e+', 'mu+', 'pi+', 'K+', 'p+', 'K_L0',
1881  'n0', 'nu_e', 'nu_mu', 'nu_tau',
1882  'K_S0', 'Lambda0']
1883  for t in types:
1884  fillParticleListFromMC("%s:roe_default_gen" % t, 'mcPrimary > 0 and nDaughters == 0',
1885  True, True, path=path)
1886  inputParticlelists += ["%s:roe_default_gen" % t]
1887  roeBuilder = register_module('RestOfEventBuilder')
1888  roeBuilder.set_name('MCROEBuilder_' + target_list_name)
1889  roeBuilder.param('particleList', target_list_name)
1890  roeBuilder.param('particleListsInput', inputParticlelists)
1891  roeBuilder.param('fromMC', True)
1892  path.add_module(roeBuilder)
1893 
1894 
1895 def appendROEMask(list_name,
1896  mask_name,
1897  trackSelection,
1898  eclClusterSelection,
1899  path=None):
1900  """
1901  Loads the ROE object of a particle and creates a ROE mask with a specific name. It applies
1902  selection criteria for tracks and eclClusters which will be used by variables in ROEVariables.cc.
1903 
1904  - append a ROE mask with all tracks in ROE coming from the IP region
1905 
1906  >>> appendROEMask('B+:sig', 'IPtracks', 'abs(d0) < 0.05 and abs(z0) < 0.1', '')
1907 
1908  - append a ROE mask with only ECLClusters that pass as good photon candidates
1909 
1910  >>> good_photons = 'theta > 0.296706 and theta < 2.61799 and clusterErrorTiming < 1e6 and [clusterE1E9 > 0.4 or E > 0.075]'
1911  >>> appendROEMask('B+:sig', 'goodROEGamma', '', good_photons)
1912 
1913 
1914  @param list_name name of the input ParticleList
1915  @param mask_name name of the appended ROEMask
1916  @param trackSelection decay string for the tracks in ROE
1917  @param eclClusterSelection decay string for the tracks in ROE
1918  @param path modules are added to this path
1919  """
1920 
1921  roeMask = register_module('RestOfEventInterpreter')
1922  roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + mask_name)
1923  roeMask.param('particleList', list_name)
1924  roeMask.param('ROEMasks', [(mask_name, trackSelection, eclClusterSelection)])
1925  path.add_module(roeMask)
1926 
1927 
1928 def appendROEMasks(list_name, mask_tuples, path=None):
1929  """
1930  Loads the ROE object of a particle and creates a ROE mask with a specific name. It applies
1931  selection criteria for tracks and eclClusters which will be used by variables in ROEVariables.cc.
1932 
1933  The multiple ROE masks with their own selection criteria are specified
1934  via list of tuples (mask_name, trackSelection, eclClusterSelection) or
1935  (mask_name, trackSelection, eclClusterSelection) in case with fractions.
1936 
1937  - Example for two tuples, one with and one without fractions
1938 
1939  >>> ipTracks = ('IPtracks', 'abs(d0) < 0.05 and abs(z0) < 0.1', '')
1940  >>> good_photons = 'theta > 0.296706 and theta < 2.61799 and clusterErrorTiming < 1e6 and [clusterE1E9 > 0.4 or E > 0.075]'
1941  >>> goodROEGamma = ('ROESel', 'abs(d0) < 0.05 and abs(z0) < 0.1', good_photons)
1942  >>> appendROEMasks('B+:sig', [ipTracks, goodROEGamma])
1943 
1944  @param list_name name of the input ParticleList
1945  @param mask_tuples array of ROEMask list tuples to be appended
1946  @param path modules are added to this path
1947  """
1948 
1949  roeMask = register_module('RestOfEventInterpreter')
1950  roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + 'MaskList')
1951  roeMask.param('particleList', list_name)
1952  roeMask.param('ROEMasks', mask_tuples)
1953  path.add_module(roeMask)
1954 
1955 
1956 def updateROEMask(list_name,
1957  mask_name,
1958  trackSelection,
1959  eclClusterSelection='',
1960  path=None):
1961  """
1962  Update an existing ROE mask by applying additional selection cuts for
1963  tracks and/or clusters.
1964 
1965  See function `appendROEMask`!
1966 
1967  @param list_name name of the input ParticleList
1968  @param mask_name name of the ROEMask to update
1969  @param trackSelection decay string for the tracks in ROE
1970  @param eclClusterSelection decay string for the tracks in ROE
1971  @param path modules are added to this path
1972  """
1973 
1974  roeMask = register_module('RestOfEventInterpreter')
1975  roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + mask_name)
1976  roeMask.param('particleList', list_name)
1977  roeMask.param('ROEMasks', [(mask_name, trackSelection, eclClusterSelection)])
1978  roeMask.param('update', True)
1979  path.add_module(roeMask)
1980 
1981 
1982 def updateROEMasks(list_name, mask_tuples, path):
1983  """
1984  Update existing ROE masks by applying additional selection cuts for tracks
1985  and/or clusters.
1986 
1987  The multiple ROE masks with their own selection criteria are specified
1988  via list tuples (mask_name, trackSelection, eclClusterSelection)
1989 
1990  See function `appendROEMasks`!
1991 
1992  @param list_name name of the input ParticleList
1993  @param mask_tuples array of ROEMask list tuples to be appended
1994  @param path modules are added to this path
1995  """
1996 
1997  roeMask = register_module('RestOfEventInterpreter')
1998  roeMask.set_name('RestOfEventInterpreter_' + list_name + '_' + 'MaskList')
1999  roeMask.param('particleList', list_name)
2000  roeMask.param('ROEMasks', mask_tuples)
2001  roeMask.param('update', True)
2002  path.add_module(roeMask)
2003 
2004 
2005 def keepInROEMasks(list_name, mask_names, cut_string, path=None):
2006  """
2007  This function is used to apply particle list specific cuts on one or more ROE masks (track or eclCluster).
2008  With this function one can KEEP the tracks/eclclusters used in particles from provided particle list.
2009  This function should be executed only in the for_each roe path for the current ROE object.
2010 
2011  To avoid unnecessary computation, the input particle list should only contain particles from ROE
2012  (use cut 'isInRestOfEvent == 1'). To update the ECLCluster masks, the input particle list should be a photon
2013  particle list (e.g. 'gamma:someLabel'). To update the Track masks, the input particle list should be a charged
2014  pion particle list (e.g. 'pi+:someLabel').
2015 
2016  Updating a non-existing mask will create a new one.
2017 
2018  - keep only those tracks that were used in provided particle list
2019 
2020  >>> keepInROEMasks('pi+:goodTracks', 'mask', '')
2021 
2022  - keep only those clusters that were used in provided particle list and pass a cut, apply to several masks
2023 
2024  >>> keepInROEMasks('gamma:goodClusters', ['mask1', 'mask2'], 'E > 0.1')
2025 
2026 
2027  @param list_name name of the input ParticleList
2028  @param mask_names array of ROEMasks to be updated
2029  @param cut_string decay string with which the mask will be updated
2030  @param path modules are added to this path
2031  """
2032 
2033  updateMask = register_module('RestOfEventUpdater')
2034  updateMask.set_name('RestOfEventUpdater_' + list_name + '_masks')
2035  updateMask.param('particleList', list_name)
2036  updateMask.param('updateMasks', mask_names)
2037  updateMask.param('cutString', cut_string)
2038  updateMask.param('discard', False)
2039  path.add_module(updateMask)
2040 
2041 
2042 def discardFromROEMasks(list_name, mask_names, cut_string, path=None):
2043  """
2044  This function is used to apply particle list specific cuts on one or more ROE masks (track or eclCluster).
2045  With this function one can DISCARD the tracks/eclclusters used in particles from provided particle list.
2046  This function should be executed only in the for_each roe path for the current ROE object.
2047 
2048  To avoid unnecessary computation, the input particle list should only contain particles from ROE
2049  (use cut 'isInRestOfEvent == 1'). To update the ECLCluster masks, the input particle list should be a photon
2050  particle list (e.g. 'gamma:someLabel'). To update the Track masks, the input particle list should be a charged
2051  pion particle list (e.g. 'pi+:someLabel').
2052 
2053  Updating a non-existing mask will create a new one.
2054 
2055  - discard tracks that were used in provided particle list
2056 
2057  >>> discardFromROEMasks('pi+:badTracks', 'mask', '')
2058 
2059  - discard clusters that were used in provided particle list and pass a cut, apply to several masks
2060 
2061  >>> discardFromROEMasks('gamma:badClusters', ['mask1', 'mask2'], 'E < 0.1')
2062 
2063 
2064  @param list_name name of the input ParticleList
2065  @param mask_names array of ROEMasks to be updated
2066  @param cut_string decay string with which the mask will be updated
2067  @param path modules are added to this path
2068  """
2069 
2070  updateMask = register_module('RestOfEventUpdater')
2071  updateMask.set_name('RestOfEventUpdater_' + list_name + '_masks')
2072  updateMask.param('particleList', list_name)
2073  updateMask.param('updateMasks', mask_names)
2074  updateMask.param('cutString', cut_string)
2075  updateMask.param('discard', True)
2076  path.add_module(updateMask)
2077 
2078 
2079 def optimizeROEWithV0(list_name, mask_names, cut_string, path=None):
2080  """
2081  This function is used to apply particle list specific cuts on one or more ROE masks for Tracks.
2082  It is possible to optimize the ROE selection by treating tracks from V0's separately, meaning,
2083  taking V0's 4-momentum into account instead of 4-momenta of tracks. A cut for only specific V0's
2084  passing it can be applied.
2085 
2086  The input particle list should be a V0 particle list: K_S0 ('K_S0:someLabel', ''),
2087  Lambda ('Lambda:someLabel', '') or converted photons ('gamma:someLabel').
2088 
2089  Updating a non-existing mask will create a new one.
2090 
2091  - treat tracks from K_S0 inside mass window separately, replace track momenta with K_S0 momentum
2092 
2093  >>> optimizeROEWithV0('K_S0:opt', 'mask', '0.450 < M < 0.550')
2094 
2095  @param list_name name of the input ParticleList
2096  @param mask_names array of ROEMasks to be updated
2097  @param cut_string decay string with which the mask will be updated
2098  @param path modules are added to this path
2099  """
2100 
2101  updateMask = register_module('RestOfEventUpdater')
2102  updateMask.set_name('RestOfEventUpdater_' + list_name + '_masks')
2103  updateMask.param('particleList', list_name)
2104  updateMask.param('updateMasks', mask_names)
2105  updateMask.param('cutString', cut_string)
2106  path.add_module(updateMask)
2107 
2108 
2109 def printROEInfo(mask_names=[], which_mask='both', full_print=False, path=None):
2110  """
2111  This function prints out the information for the current ROE, so it should only be used in the for_each path.
2112  It prints out basic ROE object info.
2113 
2114  If mask names are provided, specific information for those masks will be printed out. By default, basic
2115  ECLCluster and Track mask info will be printed out, but it is possible to do this only for one, if needed.
2116 
2117  It is also possible to print out the specific mask values for each Track and ECLCluster by setting the 'full_print'
2118  option to True.
2119 
2120  @param mask_names array of ROEMask names for printing out info
2121  @param which_mask print out info for Tracks ('track'), ECLClusters ('cluster') or ('both')
2122  @param full_print print out mask values for each Track/ECLCLuster in mask
2123  @param path modules are added to this path
2124  """
2125  if not isinstance(path, Path):
2126  B2FATAL("Error from printROEInfo, please add this to the for_each path")
2127 
2128  printMask = register_module('RestOfEventPrinter')
2129  printMask.set_name('RestOfEventPrinter')
2130  printMask.param('maskNames', mask_names)
2131  printMask.param('whichMask', which_mask)
2132  printMask.param('fullPrint', full_print)
2133  path.add_module(printMask)
2134 
2135 
2136 def buildContinuumSuppression(list_name, roe_mask, path):
2137  """
2138  Creates for each Particle in the given ParticleList a ContinuumSuppression
2139  dataobject and makes BASF2 relation between them.
2140 
2141  @param list_name name of the input ParticleList
2142  @param path modules are added to this path
2143  """
2144 
2145  qqBuilder = register_module('ContinuumSuppressionBuilder')
2146  qqBuilder.set_name('QQBuilder_' + list_name)
2147  qqBuilder.param('particleList', list_name)
2148  qqBuilder.param('ROEMask', roe_mask)
2149  path.add_module(qqBuilder)
2150 
2151 
2152 def removeParticlesNotInLists(lists_to_keep, path):
2153  """
2154  Removes all Particles that are not in a given list of ParticleLists (or daughters of those).
2155  All relations from/to Particles, daughter indices, and other ParticleLists are fixed.
2156 
2157  @param lists_to_keep Keep the Particles and their daughters in these ParticleLists.
2158  @param path modules are added to this path
2159  """
2160 
2161  mod = register_module('RemoveParticlesNotInLists')
2162  mod.param('particleLists', lists_to_keep)
2163  path.add_module(mod)
2164 
2165 
2166 def inclusiveBtagReconstruction(upsilon_list_name, bsig_list_name, btag_list_name, input_lists_names, path):
2167  """
2168  Reconstructs Btag from particles in given ParticleLists which do not share any final state particles (mdstSource) with Bsig.
2169 
2170  @param upsilon_list_name Name of the ParticleList to be filled with 'Upsilon(4S) -> B:sig anti-B:tag'
2171  @param bsig_list_name Name of the Bsig ParticleList
2172  @param btag_list_name Name of the Bsig ParticleList
2173  @param input_lists_names List of names of the ParticleLists which are used to reconstruct Btag from
2174  """
2175  btag = register_module('InclusiveBtagReconstruction')
2176  btag.set_name('InclusiveBtagReconstruction_' + bsig_list_name)
2177  btag.param('upsilonListName', upsilon_list_name)
2178  btag.param('bsigListName', bsig_list_name)
2179  btag.param('btagListName', btag_list_name)
2180  btag.param('inputListsNames', input_lists_names)
2181  path.add_module(btag)
2182 
2183 
2184 def selectDaughters(particle_list_name, decay_string, path):
2185  """
2186  Redefine the Daughters of a particle: select from decayString
2187 
2188  @param particle_list_name input particle list
2189  @param decay_string for selecting the Daughters to be preserved
2190  """
2191  seld = register_module('SelectDaughters')
2192  seld.set_name('SelectDaughters_' + particle_list_name)
2193  seld.param('listName', particle_list_name)
2194  seld.param('decayString', decay_string)
2195  path.add_module(seld)
2196 
2197 
2198 def markDuplicate(particleList, prioritiseV0, path):
2199  """
2200  Call DuplicateVertexMarker to find duplicate particles in a list and
2201  flag the ones that should be kept
2202 
2203  @param particleList input particle list
2204  @param prioritiseV0 if true, give V0s a higher priority
2205  """
2206  markdup = register_module('DuplicateVertexMarker')
2207  markdup.param('particleList', particleList)
2208  markdup.param('prioritiseV0', prioritiseV0)
2209  path.add_module(markdup)
2210 
2211 
2212 PI0ETAVETO_COUNTER = 0
2213 
2214 
2215 def oldwritePi0EtaVeto(
2216  particleList,
2217  decayString,
2218  workingDirectory='.',
2219  pi0vetoname='Pi0_Prob',
2220  etavetoname='Eta_Prob',
2221  downloadFlag=True,
2222  selection='',
2223  path=None
2224 ):
2225  """
2226  Give pi0/eta probability for hard photon.
2227 
2228  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.
2229 
2230  The current default weight files are optimised using MC9.
2231  The input variables are as below. Aliases are set to some variables during training.
2232 
2233  * M: pi0/eta candidates Invariant mass
2234  * lowE: soft photon energy in lab frame
2235  * cTheta: soft photon ECL cluster's polar angle
2236  * Zmva: soft photon output of MVA using Zernike moments of the cluster
2237  * minC2Hdist: soft photon distance from eclCluster to nearest point on nearest Helix at the ECL cylindrical radius
2238 
2239  If you don't have weight files in your workingDirectory,
2240  these files are downloaded from database to your workingDirectory automatically.
2241  Please refer to analysis/examples/tutorials/B2A306-B02RhoGamma-withPi0EtaVeto.py
2242  about how to use this function.
2243 
2244  NOTE:
2245  Please don't use following ParticleList names elsewhere:
2246 
2247  ``gamma:HARDPHOTON``, ``pi0:PI0VETO``, ``eta:ETAVETO``,
2248  ``gamma:PI0SOFT + str(PI0ETAVETO_COUNTER)``, ``gamma:ETASOFT + str(PI0ETAVETO_COUNTER)``
2249 
2250  Please don't use ``lowE``, ``cTheta``, ``Zmva``, ``minC2Hdist`` as alias elsewhere.
2251 
2252  @param particleList The input ParticleList
2253  @param decayString specify Particle to be added to the ParticleList
2254  @param workingDirectory The weight file directory
2255  @param downloadFlag whether download default weight files or not
2256  @param pi0vetoname extraInfo name of pi0 probability
2257  @param etavetoname extraInfo name of eta probability
2258  @param selection Selection criteria that Particle needs meet in order for for_each ROE path to continue
2259  @param path modules are added to this path
2260  """
2261 
2262  import os
2263  import basf2_mva
2264 
2265  global PI0ETAVETO_COUNTER
2266 
2267  if PI0ETAVETO_COUNTER == 0:
2268  from variables import variables
2269  variables.addAlias('lowE', 'daughter(1,E)')
2270  variables.addAlias('cTheta', 'daughter(1,clusterTheta)')
2271  variables.addAlias('Zmva', 'daughter(1,clusterZernikeMVA)')
2272  variables.addAlias('minC2Tdist', 'daughter(1,minC2TDist)')
2273  variables.addAlias('cluNHits', 'daughter(1,clusterNHits)')
2274  variables.addAlias('E9E21', 'daughter(1,clusterE9E21)')
2275 
2276  PI0ETAVETO_COUNTER = PI0ETAVETO_COUNTER + 1
2277 
2278  roe_path = create_path()
2279 
2280  deadEndPath = create_path()
2281 
2282  signalSideParticleFilter(particleList, selection, roe_path, deadEndPath)
2283 
2284  fillSignalSideParticleList('gamma:HARDPHOTON', decayString, path=roe_path)
2285 
2286  pi0softname = 'gamma:PI0SOFT'
2287  etasoftname = 'gamma:ETASOFT'
2288  softphoton1 = pi0softname + str(PI0ETAVETO_COUNTER)
2289  softphoton2 = etasoftname + str(PI0ETAVETO_COUNTER)
2290 
2291  fillParticleList(
2292  softphoton1,
2293  '[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]',
2294  path=roe_path)
2295  applyCuts(softphoton1, 'abs(clusterTiming)<120', path=roe_path)
2296  fillParticleList(
2297  softphoton2,
2298  '[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]',
2299  path=roe_path)
2300  applyCuts(softphoton2, 'abs(clusterTiming)<120', path=roe_path)
2301 
2302  reconstructDecay('pi0:PI0VETO -> gamma:HARDPHOTON ' + softphoton1, '', path=roe_path)
2303  reconstructDecay('eta:ETAVETO -> gamma:HARDPHOTON ' + softphoton2, '', path=roe_path)
2304 
2305  if not os.path.isdir(workingDirectory):
2306  os.mkdir(workingDirectory)
2307  B2INFO('oldwritePi0EtaVeto: ' + workingDirectory + ' has been created as workingDirectory.')
2308 
2309  if not os.path.isfile(workingDirectory + '/pi0veto.root'):
2310  if downloadFlag:
2311  basf2_mva.download('Pi0VetoIdentifier', workingDirectory + '/pi0veto.root')
2312  B2INFO('oldwritePi0EtaVeto: pi0veto.root has been downloaded from database to workingDirectory.')
2313 
2314  if not os.path.isfile(workingDirectory + '/etaveto.root'):
2315  if downloadFlag:
2316  basf2_mva.download('EtaVetoIdentifier', workingDirectory + '/etaveto.root')
2317  B2INFO('oldwritePi0EtaVeto: etaveto.root has been downloaded from database to workingDirectory.')
2318 
2319  roe_path.add_module('MVAExpert', listNames=['pi0:PI0VETO'], extraInfoName='Pi0Veto',
2320  identifier=workingDirectory + '/pi0veto.root')
2321  roe_path.add_module('MVAExpert', listNames=['eta:ETAVETO'], extraInfoName='EtaVeto',
2322  identifier=workingDirectory + '/etaveto.root')
2323 
2324  rankByHighest('pi0:PI0VETO', 'extraInfo(Pi0Veto)', numBest=1, path=roe_path)
2325  rankByHighest('eta:ETAVETO', 'extraInfo(EtaVeto)', numBest=1, path=roe_path)
2326 
2327  variableToSignalSideExtraInfo('pi0:PI0VETO', {'extraInfo(Pi0Veto)': pi0vetoname}, path=roe_path)
2328  variableToSignalSideExtraInfo('eta:ETAVETO', {'extraInfo(EtaVeto)': etavetoname}, path=roe_path)
2329 
2330  path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
2331 
2332 
2333 def writePi0EtaVeto(
2334  particleList,
2335  decayString,
2336  workingDirectory='.',
2337  mode='standard',
2338  downloadFlag=True,
2339  selection='',
2340  path=None
2341 ):
2342  """
2343  Give pi0/eta probability for hard photon.
2344 
2345  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.
2346 
2347  The current default weight files are optimised using MC12.
2348 
2349  The input variables of the mva training are:
2350 
2351  * M: pi0/eta candidates Invariant mass
2352  * daughter(1,E): soft photon energy in lab frame
2353  * daughter(1,clusterTheta): soft photon ECL cluster's polar angle
2354  * daughter(1,minC2TDist): soft photon distance from eclCluster to nearest point on nearest Helix at the ECL cylindrical radius
2355  * daughter(1,clusterZernikeMVA): soft photon output of MVA using Zernike moments of the cluster
2356  * daughter(1,clusterNHits): soft photon total crystal weights sum(w_i) with w_i<=1
2357  * daughter(1,clusterE9E21): soft photon ratio of energies in inner 3x3 crystals and 5x5 crystals without corners
2358  * cosHelicityAngleMomentum: pi0/eta candidates cosHelicityAngleMomentum
2359 
2360  If you don't have weight files in your workingDirectory,
2361  these files are downloaded from the database to your workingDirectory automatically.
2362 
2363  The following strings are available for mode:
2364 
2365  * standard: loose energy cut and no clusterNHits cut are applied to soft photon
2366  * tight: tight energy cut and no clusterNHits cut are applied to soft photon
2367  * cluster: loose energy cut and clusterNHits cut are applied to soft photon
2368  * both: tight energy cut and clusterNHits cut are applied to soft photon
2369 
2370  One can obtain the result of pi0/eta veto from `pi0Prob`/`etaProb`
2371 
2372  NOTE:
2373  Please don't use following ParticleList names elsewhere:
2374 
2375  ``gamma:HardPhoton``,
2376  ``gamma:Pi0Soft + ListName + '_' + particleList.replace(':', '_')``,
2377  ``gamma:EtaSoft + ListName + '_' + particleList.replace(':', '_')``,
2378  ``pi0:EtaVeto + ListName``,
2379  ``eta:EtaVeto + ListName``
2380 
2381  @param particleList the input ParticleList
2382  @param decayString specify Particle to be added to the ParticleList
2383  @param workingDirectory the weight file directory
2384  @param mode choose one mode out of 'standard', 'tight', 'cluster' and 'both'
2385  @param downloadFlag whether download default weight files or not
2386  @param selection selection criteria that Particle needs meet in order for for_each ROE path to continue
2387  @param path modules are added to this path
2388  """
2389 
2390  import os
2391  import basf2_mva
2392 
2393  roe_path = create_path()
2394  deadEndPath = create_path()
2395  signalSideParticleFilter(particleList, selection, roe_path, deadEndPath)
2396  fillSignalSideParticleList('gamma:HardPhoton', decayString, path=roe_path)
2397  if not os.path.isdir(workingDirectory):
2398  os.mkdir(workingDirectory)
2399  B2INFO('writePi0EtaVeto: ' + workingDirectory + ' has been created as workingDirectory.')
2400 
2401  dictListName = {'standard': 'Origin',
2402  'tight': 'TightEnergyThreshold',
2403  'cluster': 'LargeClusterSize',
2404  'both': 'TightEnrgyThresholdAndLargeClusterSize'}
2405 
2406  dictPi0EnergyCut = {'standard': '[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]',
2407  'tight': '[clusterReg==1 and E>0.03] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.04]',
2408  'cluster': '[clusterReg==1 and E>0.025] or [clusterReg==2 and E>0.02] or [clusterReg==3 and E>0.02]',
2409  'both': '[clusterReg==1 and E>0.03] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.04]'}
2410 
2411  dictEtaEnergyCut = {'standard': '[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]',
2412  'tight': '[clusterReg==1 and E>0.06] or [clusterReg==2 and E>0.06] or [clusterReg==3 and E>0.06]',
2413  'cluster': '[clusterReg==1 and E>0.035] or [clusterReg==2 and E>0.03] or [clusterReg==3 and E>0.03]',
2414  'both': '[clusterReg==1 and E>0.06] or [clusterReg==2 and E>0.06] or [clusterReg==3 and E>0.06]'}
2415 
2416  dictTimingAndNHitsCut = {'standard': 'abs(clusterTiming)<clusterErrorTiming',
2417  'tight': 'abs(clusterTiming)<clusterErrorTiming',
2418  'cluster': 'abs(clusterTiming)<clusterErrorTiming and clusterNHits >= 2',
2419  'both': 'abs(clusterTiming)<clusterErrorTiming and clusterNHits >= 2'}
2420 
2421  dictPi0WeightFileName = {'standard': 'pi0veto_origin.root',
2422  'tight': 'pi0veto_tight.root',
2423  'cluster': 'pi0veto_cluster.root',
2424  'both': 'pi0veto_both.root'}
2425 
2426  dictEtaWeightFileName = {'standard': 'etaveto_origin.root',
2427  'tight': 'etaveto_tight.root',
2428  'cluster': 'etaveto_cluster.root',
2429  'both': 'etaveto_both.root'}
2430 
2431  dictPi0PayloadName = {'standard': 'Pi0VetoIdentifierStandard',
2432  'tight': 'Pi0VetoIdentifierWithHigherEnergyThreshold',
2433  'cluster': 'Pi0VetoIdentifierWithLargerClusterSize',
2434  'both': 'Pi0VetoIdentifierWithHigherEnergyThresholdAndLargerClusterSize'}
2435 
2436  dictEtaPayloadName = {'standard': 'EtaVetoIdentifierStandard',
2437  'tight': 'EtaVetoIdentifierWithHigherEnergyThreshold',
2438  'cluster': 'EtaVetoIdentifierWithLargerClusterSize',
2439  'both': 'EtaVetoIdentifierWithHigherEnergyThresholdAndLargerClusterSize'}
2440 
2441  dictPi0ExtraInfoName = {'standard': 'Pi0VetoOrigin',
2442  'tight': 'Pi0VetoTightEnergyThreshold',
2443  'cluster': 'Pi0VetoLargeClusterSize',
2444  'both': 'Pi0VetoTightEnergyThresholdAndLargeClusterSize'}
2445 
2446  dictEtaExtraInfoName = {'standard': 'EtaVetoOrigin',
2447  'tight': 'EtaVetoTightEnergyThreshold',
2448  'cluster': 'EtaVetoLargeClusterSize',
2449  'both': 'EtaVetoTightEnergyThresholdAndLargeClusterSize'}
2450 
2451  dictPi0ExtraInfoRename = {'standard': 'Pi0ProbOrigin',
2452  'tight': 'Pi0ProbTightEnergyThreshold',
2453  'cluster': 'Pi0ProbLargeClusterSize',
2454  'both': 'Pi0ProbTightEnergyThresholdAndLargeClusterSize'}
2455 
2456  dictEtaExtraInfoRename = {'standard': 'EtaProbOrigin',
2457  'tight': 'EtaProbTightEnergyThreshold',
2458  'cluster': 'EtaProbLargeClusterSize',
2459  'both': 'EtaProbTightEnergyThresholdAndLargeClusterSize'}
2460 
2461  ListName = dictListName[mode]
2462  Pi0EnergyCut = dictPi0EnergyCut[mode]
2463  EtaEnergyCut = dictEtaEnergyCut[mode]
2464  TimingAndNHitsCut = dictTimingAndNHitsCut[mode]
2465  Pi0WeightFileName = dictPi0WeightFileName[mode]
2466  EtaWeightFileName = dictEtaWeightFileName[mode]
2467  Pi0PayloadName = dictPi0PayloadName[mode]
2468  EtaPayloadName = dictEtaPayloadName[mode]
2469  Pi0ExtraInfoName = dictPi0ExtraInfoName[mode]
2470  EtaExtraInfoName = dictEtaExtraInfoName[mode]
2471  Pi0ExtraInfoRename = dictPi0ExtraInfoRename[mode]
2472  EtaExtraInfoRename = dictEtaExtraInfoRename[mode]
2473 
2474  """
2475  pi0 veto
2476  """
2477  # define the particleList name for soft photon
2478  pi0soft = 'gamma:Pi0Soft' + ListName + '_' + particleList.replace(':', '_')
2479  # fill the particleList for soft photon with energy cut
2480  fillParticleList(pi0soft, Pi0EnergyCut, path=roe_path)
2481  # apply an additional cut for soft photon
2482  applyCuts(pi0soft, TimingAndNHitsCut, path=roe_path)
2483  # reconstruct pi0
2484  reconstructDecay('pi0:Pi0Veto' + ListName + ' -> gamma:HardPhoton ' + pi0soft, '', path=roe_path)
2485  # if you don't have wight files in your workingDirectory,
2486  # these files are downloaded from database to your workingDirectory automatically.
2487  if not os.path.isfile(workingDirectory + '/' + Pi0WeightFileName):
2488  if downloadFlag:
2489  basf2_mva.download(Pi0PayloadName, workingDirectory + '/' + Pi0WeightFileName)
2490  B2INFO('writePi0EtaVeto: ' + Pi0WeightFileName + ' has been downloaded from database to workingDirectory.')
2491  # MVA training is conducted.
2492  roe_path.add_module('MVAExpert', listNames=['pi0:Pi0Veto' + ListName],
2493  extraInfoName=Pi0ExtraInfoName, identifier=workingDirectory + '/' + Pi0WeightFileName)
2494  # Pick up only one pi0/eta candidate with the highest pi0/eta probability.
2495  rankByHighest('pi0:Pi0Veto' + ListName, 'extraInfo(' + Pi0ExtraInfoName + ')', numBest=1, path=roe_path)
2496  # 'extraInfo(Pi0Veto)' is labeled 'Pi0_Prob'
2497  variableToSignalSideExtraInfo('pi0:Pi0Veto' + ListName,
2498  {'extraInfo(' + Pi0ExtraInfoName + ')': Pi0ExtraInfoRename}, path=roe_path)
2499 
2500  """
2501  eta veto
2502  """
2503  etasoft = 'gamma:EtaSoft' + ListName + '_' + particleList.replace(':', '_')
2504  fillParticleList(etasoft, EtaEnergyCut, path=roe_path)
2505  applyCuts(etasoft, TimingAndNHitsCut, path=roe_path)
2506  reconstructDecay('eta:EtaVeto' + ListName + ' -> gamma:HardPhoton ' + etasoft, '', path=roe_path)
2507  if not os.path.isfile(workingDirectory + '/' + EtaWeightFileName):
2508  if downloadFlag:
2509  basf2_mva.download(EtaPayloadName, workingDirectory + '/' + EtaWeightFileName)
2510  B2INFO('writePi0EtaVeto: ' + EtaWeightFileName + 'has been downloaded from database to workingDirectory.')
2511  roe_path.add_module('MVAExpert', listNames=['eta:EtaVeto' + ListName],
2512  extraInfoName=EtaExtraInfoName, identifier=workingDirectory + '/' + EtaWeightFileName)
2513  rankByHighest('eta:EtaVeto' + ListName, 'extraInfo(' + EtaExtraInfoName + ')', numBest=1, path=roe_path)
2514  variableToSignalSideExtraInfo('eta:EtaVeto' + ListName,
2515  {'extraInfo(' + EtaExtraInfoName + ')': EtaExtraInfoRename}, path=roe_path)
2516 
2517  path.for_each('RestOfEvent', 'RestOfEvents', roe_path)
2518 
2519 
2520 def buildEventKinematics(inputListNames=[], default_cleanup=True,
2521  chargedPIDPriors=None, fillWithMostLikely=False, path=None):
2522  """
2523  Calculates the global kinematics of the event (visible energy, missing momentum, missing mass...)
2524  using ParticleLists provided. If no ParticleList is provided, default ParticleLists are used
2525  (all track and all hits in ECL without associated track).
2526 
2527  The visible energy missing values are
2528  stored in a EventKinematics dataobject.
2529 
2530  @param inputListNames list of ParticleLists used to calculate the global event kinematics.
2531  If the list is empty, default ParticleLists pi+:evtkin and gamma:evtkin are filled.
2532  @param fillWithMostLikely if True, the module uses the most likely particle mass hypothesis for charged particles
2533  according to the PID likelihood and the option inputListNames will be ignored.
2534  @param chargedPIDPriors The prior PID fractions, that are used to regulate
2535  amount of certain charged particle species, should be a list of
2536  six floats if not None. The order of particle types is
2537  the following: [e-, mu-, pi-, K-, p+, d+]
2538  @param default_cleanup if True and either inputListNames empty or fillWithMostLikely True, default clean up cuts are applied
2539  @param path modules are added to this path
2540  """
2541  trackCuts = 'pt > 0.1'
2542  trackCuts += ' and thetaInCDCAcceptance'
2543  trackCuts += ' and abs(dz) < 3'
2544  trackCuts += ' and dr < 0.5'
2545 
2546  gammaCuts = 'E > 0.05'
2547  gammaCuts += ' and thetaInCDCAcceptance'
2548 
2549  if fillWithMostLikely:
2550  from stdCharged import stdMostLikely
2551  stdMostLikely(chargedPIDPriors, '_evtkin', path=path)
2552  inputListNames = ['%s:mostlikely_evtkin' % ptype for ptype in ['K+', 'p+', 'e+', 'mu+', 'pi+']]
2553  fillParticleList('gamma:evtkin', '', path=path)
2554  inputListNames += ['gamma:evtkin']
2555  if default_cleanup:
2556  B2INFO("Using default cleanup in EventKinematics module.")
2557  for ptype in ['K+', 'p+', 'e+', 'mu+', 'pi+']:
2558  applyCuts(f'{ptype}:mostlikely_evtkin', trackCuts, path=path)
2559  applyCuts('gamma:evtkin', gammaCuts, path=path)
2560  else:
2561  B2INFO("No cleanup in EventKinematics module.")
2562  if not inputListNames:
2563  B2INFO("Creating particle lists pi+:evtkin and gamma:evtkin to get the global kinematics of the event.")
2564  fillParticleList('pi+:evtkin', '', path=path)
2565  fillParticleList('gamma:evtkin', '', path=path)
2566  particleLists = ['pi+:evtkin', 'gamma:evtkin']
2567  if default_cleanup:
2568  B2INFO("Using default cleanup in EventKinematics module.")
2569  applyCuts('pi+:evtkin', trackCuts, path=path)
2570  applyCuts('gamma:evtkin', gammaCuts, path=path)
2571  else:
2572  B2INFO("No cleanup in EventKinematics module.")
2573  else:
2574  particleLists = inputListNames
2575 
2576  eventKinematicsModule = register_module('EventKinematics')
2577  eventKinematicsModule.set_name('EventKinematics_')
2578  eventKinematicsModule.param('particleLists', particleLists)
2579  path.add_module(eventKinematicsModule)
2580 
2581 
2582 def buildEventShape(inputListNames=[],
2583  default_cleanup=True,
2584  allMoments=False,
2585  cleoCones=True,
2586  collisionAxis=True,
2587  foxWolfram=True,
2588  harmonicMoments=True,
2589  jets=True,
2590  sphericity=True,
2591  thrust=True,
2592  checkForDuplicates=False,
2593  path=None):
2594  """
2595  Calculates the event-level shape quantities (thrust, sphericity, Fox-Wolfram moments...)
2596  using the particles in the lists provided by the user. If no particle list is provided,
2597  the function will internally create a list of good tracks and a list of good photons
2598  with (optionally) minimal quality cuts.
2599 
2600 
2601  The results of the calculation are then stored into the EventShapeContainer dataobject,
2602  and are accessible using the variables of the EventShape group.
2603 
2604  The user can switch the calculation of certain quantities on or off to save computing
2605  time. By default the calculation of the high-order moments (5-8) is turned off.
2606  Switching off an option will make the corresponding variables not available.
2607 
2608  Warning:
2609  The user can provide as many particle lists
2610  as needed, using also combined particles, but the function will always assume that
2611  the lists are independent.
2612  If the lists provided by the user contain several times the same track (either with
2613  different mass hypothesis, or once as an independent particle and once as daughter of a
2614  combined particle) the results won't be reliable.
2615  A basic check for duplicates is available setting the checkForDuplicate flags,
2616  but is usually quite time consuming.
2617 
2618 
2619  @param inputListNames List of ParticleLists used to calculate the
2620  event shape variables. If the list is empty the default
2621  particleLists pi+:evtshape and gamma:evtshape are filled.
2622  @param default_cleanup If True, applies standard cuts on pt and cosTheta when
2623  defining the internal lists. This option is ignored if the
2624  particleLists are provided by the user.
2625  @param path Path to append the eventShape modules to.
2626  @param thrust Enables the calculation of thrust-related quantities (CLEO
2627  cones, Harmonic moments, jets).
2628  @param collisionAxis Enables the calculation of the quantities related to the
2629  collision axis .
2630  @param foxWolfram Enables the calculation of the Fox-Wolfram moments.
2631  @param harmonicMoments Enables the calculation of the Harmonic moments with respect
2632  to both the thrust axis and, if collisionAxis = True, the collision axis.
2633  @param allMoments If True, calculates also the FW and harmonic moments from order
2634  5 to 8 instead of the low-order ones only.
2635  @param cleoCones Enables the calculation of the CLEO cones with respect to both the thrust
2636  axis and, if collisionAxis = True, the collision axis.
2637  @param jets Enables the calculation of the hemisphere momenta and masses.
2638  Requires thrust = True.
2639  @param sphericity Enables the calculation of the sphericity-related quantities.
2640  @param checkForDuplicates Perform a check for duplicate particles before adding them. This option
2641  is quite time consuming, instead of using it consider sanitizing
2642  the lists you are passing to the function.
2643  """
2644  if not inputListNames:
2645  B2INFO("Creating particle lists pi+:evtshape and gamma:evtshape to get the event shape variables.")
2646  fillParticleList('pi+:evtshape', '', path=path)
2647  fillParticleList('gamma:evtshape', '', path=path)
2648  particleLists = ['pi+:evtshape', 'gamma:evtshape']
2649 
2650  if default_cleanup:
2651  B2INFO("Applying standard cuts")
2652  trackCuts = 'pt > 0.1'
2653  trackCuts += ' and thetaInCDCAcceptance'
2654  trackCuts += ' and abs(dz) < 3.0'
2655  trackCuts += ' and dr < 0.5'
2656  applyCuts('pi+:evtshape', trackCuts, path=path)
2657 
2658  gammaCuts = 'E > 0.05'
2659  gammaCuts += ' and thetaInCDCAcceptance'
2660  applyCuts('gamma:evtshape', gammaCuts, path=path)
2661  else:
2662  B2WARNING("Creating the default lists with no cleanup.")
2663  else:
2664  particleLists = inputListNames
2665 
2666  eventShapeModule = register_module('EventShapeCalculator')
2667  eventShapeModule.set_name('EventShape')
2668  eventShapeModule.param('particleListNames', particleLists)
2669  eventShapeModule.param('enableAllMoments', allMoments)
2670  eventShapeModule.param('enableCleoCones', cleoCones)
2671  eventShapeModule.param('enableCollisionAxis', collisionAxis)
2672  eventShapeModule.param('enableFoxWolfram', foxWolfram)
2673  eventShapeModule.param('enableJets', jets)
2674  eventShapeModule.param('enableHarmonicMoments', harmonicMoments)
2675  eventShapeModule.param('enableSphericity', sphericity)
2676  eventShapeModule.param('enableThrust', thrust)
2677  eventShapeModule.param('checkForDuplicates', checkForDuplicates)
2678 
2679  path.add_module(eventShapeModule)
2680 
2681 
2682 def labelTauPairMC(printDecayInfo=False, path=None):
2683  """
2684  Search tau leptons into the MC information of the event. If confirms it's a generated tau pair decay,
2685  labels the decay generated of the positive and negative leptons using the ID of KKMC tau decay table.
2686 
2687  @param printDecayInfo: If true, prints ID and prong of each tau lepton in the event.
2688  @param path: module is added to this path
2689  """
2690  tauDecayMarker = register_module('TauDecayMarker')
2691  tauDecayMarker.set_name('TauDecayMarker_')
2692 
2693  path.add_module(tauDecayMarker, printDecayInfo=printDecayInfo)
2694 
2695 
2696 def tagCurlTracks(particleLists,
2697  mcTruth=False,
2698  responseCut=0.324,
2699  selectorType='cUt',
2700  ptCut=0.6,
2701  train=False,
2702  path=None):
2703  """
2704  Warning:
2705  The cut selector is not calibrated with Belle II data and should not be used without extensive study.
2706 
2707  Identifies curl tracks and tags them with extraInfo(isCurl=1) for later removal.
2708  For Belle data with a `b2bii` analysis the available cut based selection is described in `BN1079`_.
2709 
2710  .. _BN1079: https://belle.kek.jp/secured/belle_note/gn1079/bn1079.pdf
2711 
2712 
2713  The module loops over all particles in a given list that meet the preselection **ptCut** and assigns them to
2714  bundles based on the response of the chosen **selector** and the required minimum response set by the
2715  **responseCut**. Once all particles are assigned they are ranked by 25dr^2+dz^2. All but the lowest are tagged
2716  with extraInfo(isCurl=1) to allow for later removal by cutting the list or removing these from ROE as
2717  applicable.
2718 
2719 
2720  @param particleLists: list of particle lists to check for curls.
2721  @param mcTruth: bool flag to additionally assign particles with extraInfo(isTruthCurl) and
2722  extraInfo(truthBundleSize). To calculate these particles are assigned to bundles by their
2723  genParticleIndex then ranked and tagged as normal.
2724  @param responseCut: float min classifier response that considers two tracks to come from the same particle.
2725  Note 'cut' selector is binary 0/1.
2726  @param selectorType: string name of selector to use. The available options are 'cut' and 'mva'.
2727  It is strongly recommended to used the 'mva' selection. The 'cut' selection
2728  is based on BN1079 and is only calibrated for Belle data.
2729  @param ptCut: pre-selection cut on transverse momentum.
2730  @param train: flag to set training mode if selector has a training mode (mva).
2731  @param path: module is added to this path.
2732  """
2733 
2734  import b2bii
2735  belle = b2bii.isB2BII()
2736 
2737  if (not isinstance(particleLists, list)):
2738  particleLists = [particleLists] # in case user inputs a particle list as string
2739 
2740  curlTagger = register_module('CurlTagger')
2741  curlTagger.set_name('CurlTagger_')
2742  curlTagger.param('particleLists', particleLists)
2743  curlTagger.param('belle', belle)
2744  curlTagger.param('mcTruth', mcTruth)
2745  curlTagger.param('responseCut', responseCut)
2746  curlTagger.param('selectorType', selectorType)
2747  curlTagger.param('ptCut', ptCut)
2748  curlTagger.param('train', train)
2749 
2750  path.add_module(curlTagger)
2751 
2752 
2753 def applyChargedPidMVA(particleLists, path, trainingMode, binaryHypoPDGCodes=(0, 0)):
2754  """
2755  Use an MVA to perform particle identification for charged stable particles, using the `ChargedPidMVA` module.
2756 
2757  The module decorates Particle objects in the input ParticleList(s) with variables
2758  containing the appropriate MVA score, which can be used to select candidates by placing a cut on it.
2759 
2760  Note:
2761  The MVA algorithm used is a gradient boosted decision tree (**TMVA 4.2.1**, **ROOT 6.14/06**).
2762 
2763  The module can perform either 'binary' PID between input S, B particle mass hypotheses according to the following scheme:
2764 
2765  - e (11) vs. pi (211)
2766  - mu (13) vs. pi (211)
2767  - pi (211) vs. K (321)
2768  - K (321) vs. pi (211)
2769  - p (2212) vs. pi (211)
2770  - d (1000010020) vs pi (211)
2771 
2772  , or 'global' PID, namely "one-vs-others" separation. The latter makes use of an MVA algorithm trained in multi-class mode,
2773  and it's the default behaviour.
2774 
2775  Note:
2776  Currently the MVA is charge-agnostic, i.e. the training is not done independently for +/- charged particles.
2777 
2778  Parameters:
2779  particleLists (list(str)): list of names of ParticleList objects for charged stable particles.
2780  The charge-conjugate ParticleLists will be also processed automatically.
2781  path (basf2.Path): the module is added to this path.
2782  trainingMode (``Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode``): enum identifier of the training mode.
2783  Needed to pick up the correct payload from the DB. Available choices:
2784 
2785  * c_Classification=0
2786  * c_Multiclass=1
2787  * c_ECL_Classification=2
2788  * c_ECL_Multiclass=3
2789  * c_PSD_Classification=4
2790  * c_PSD_Multiclass=5
2791  * c_ECL_PSD_Classification=6
2792  * c_ECL_PSD_Multiclass=7
2793 
2794  binaryHypoPDGCodes (tuple(int, int), ``optional``): the pdgIds of the signal, background mass hypothesis.
2795  Required only for binary PID mode.
2796  """
2797 
2798  from ROOT import Belle2
2799 
2800  plSet = set(particleLists)
2801 
2802  # Map the training mode enum value to the actual name of the payload in the GT.
2803  payloadNames = {
2804  Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_Classification:
2805  {"mode": "Classification", "detector": "ALL"},
2806  Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_Multiclass:
2807  {"mode": "Multiclass", "detector": "ALL"},
2808  Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_ECL_Classification:
2809  {"mode": "ECL_Classification", "detector": "ECL"},
2810  Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_ECL_Multiclass:
2811  {"mode": "ECL_Multiclass", "detector": "ECL"},
2812  Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_PSD_Classification:
2813  {"mode": "PSD_Classification", "detector": "ALL"},
2814  Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_PSD_Multiclass:
2815  {"mode": "PSD_Multiclass", "detector": "ALL"},
2816  Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_ECL_PSD_Classification:
2817  {"mode": "ECL_PSD_Classification", "detector": "ECL"},
2818  Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_ECL_PSD_Multiclass:
2819  {"mode": "ECL_PSD_Multiclass", "detector": "ECL"},
2820  }
2821 
2822  if payloadNames.get(trainingMode) is None:
2823  B2FATAL("The chosen training mode integer identifier:\n", trainingMode,
2824  "\nis not supported. Please choose among the following:\n",
2825  "\n".join(f"{key}:{val.get('mode')}" for key, val in sorted(payloadNames.items())))
2826 
2827  mode = payloadNames.get(trainingMode).get("mode")
2828  detector = payloadNames.get(trainingMode).get("detector")
2829 
2830  payloadName = f"ChargedPidMVAWeights_{mode}"
2831 
2832  if binaryHypoPDGCodes == (0, 0):
2833  # MULTI-CLASS training mode.
2834 
2835  chargedpid = register_module("ChargedPidMVAMulticlass")
2836  chargedpid.set_name(f"ChargedPidMVAMulticlass_{mode}")
2837 
2838  else:
2839  # BINARY training mode.
2840 
2841  # In binary mode, enforce check on input S, B hypotheses compatibility.
2842  binaryOpts = [
2843  (Belle2.Const.electron.getPDGCode(), Belle2.Const.pion.getPDGCode()),
2844  (Belle2.Const.muon.getPDGCode(), Belle2.Const.pion.getPDGCode()),
2845  (Belle2.Const.pion.getPDGCode(), Belle2.Const.kaon.getPDGCode()),
2846  (Belle2.Const.kaon.getPDGCode(), Belle2.Const.pion.getPDGCode()),
2847  (Belle2.Const.proton.getPDGCode(), Belle2.Const.pion.getPDGCode()),
2848  (Belle2.Const.deuteron.getPDGCode(), Belle2.Const.pion.getPDGCode())
2849  ]
2850 
2851  if binaryHypoPDGCodes not in binaryOpts:
2852  B2FATAL("No charged pid MVA was trained to separate ", binaryHypoPDGCodes[0], " vs. ", binaryHypoPDGCodes[1],
2853  ". Please choose among the following pairs:\n",
2854  "\n".join(f"{opt[0]} vs. {opt[1]}" for opt in binaryOpts))
2855 
2856  chargedpid = register_module("ChargedPidMVA")
2857  chargedpid.set_name(f"ChargedPidMVA_{binaryHypoPDGCodes[0]}_vs_{binaryHypoPDGCodes[1]}_{mode}")
2858  chargedpid.param("sigHypoPDGCode", binaryHypoPDGCodes[0])
2859  chargedpid.param("bkgHypoPDGCode", binaryHypoPDGCodes[1])
2860 
2861  chargedpid.param("particleLists", list(plSet))
2862 
2863  chargedpid.param("payloadName", payloadName)
2864 
2865  # Ensure the module knows whether we are using ECL-only training mode.
2866  if detector == "ECL":
2867  chargedpid.param("useECLOnlyTraining", True)
2868 
2869  path.add_module(chargedpid)
2870 
2871 
2872 def calculateDistance(list_name, decay_string, mode='vertextrack', path=None):
2873  """
2874  Calculates distance between two vertices, distance of closest approach between a vertex and a track,\
2875  distance of closest approach between a vertex and btube. For track, this calculation ignores track curvature,\
2876  it's negligible for small distances.The user should use extraInfo(CalculatedDistance)\
2877  to get it. A full example steering file is at analysis/tests/test_DistanceCalculator.py
2878 
2879  Example:
2880  >>> from modularAnalysis import calculateDistance
2881  >>>calculateDistance('list_name', 'decay_string', "mode", path=user_path)
2882 
2883  @param list_name name of the input ParticleList
2884  @param decay_string select particles between the distance of closest approch will be calculated
2885  @param mode Specifies how the distance is calculated
2886  vertextrack: calculate the distance of closest appreach between a track and a\
2887  vertex, taking the first candidate as vertex, default
2888  trackvertex: calculate the distance of closest appreach between a track and a\
2889  vertex, taking the first candidate as track
2890  2tracks: calculates the distance of closest appreach between two tracks
2891  2vertices: calculates the distance between two vertices
2892  vertexbtube: calculates the distance of closest appreach between a vertex and btube
2893  trackbtube: calculates the distance of closest appreach between a track and btube
2894  @param path modules are added to this path
2895 
2896  """
2897 
2898  dist_mod = register_module('DistanceCalculator')
2899 
2900  dist_mod.set_name('DistanceCalculator_' + list_name)
2901  dist_mod.param('listName', list_name)
2902  dist_mod.param('decayString', decay_string)
2903  dist_mod.param('mode', mode)
2904  path.add_module(dist_mod)
2905 
2906 
2907 def addInclusiveDstarReconstruction(inputPionList, outputDstarList, slowPionCut, path):
2908  """
2909  Adds the InclusiveDstarReconstruction module to the given path.
2910  This module creates a D* particle list by estimating the D* four momenta
2911  from slow pions, specified by a given cut. The D* energy is approximated
2912  as E(D*) = m(D*)/(m(D*) - m(D)) * E(pi). The absolute value of the D*
2913  momentum is calculated using the D* PDG mass and the direction is collinear
2914  to the slow pion direction. The charge of the given pion list has to be consistent
2915  with the D* charge
2916 
2917  @param inputPionList Name of the input pion particle list
2918  @param outputDstarList Name of the output D* particle list
2919  @param slowPionCut Cut applied to the pion list to identify slow pions
2920  @param path the module is added to this path
2921  """
2922  incl_dstar = register_module("InclusiveDstarReconstruction")
2923  incl_dstar.param("pionListName", inputPionList)
2924  incl_dstar.param("DstarListName", outputDstarList)
2925  incl_dstar.param("slowPionCut", slowPionCut)
2926  path.add_module(incl_dstar)
2927 
2928 
2929 def getAnalysisGlobaltag():
2930  """
2931  Returns a string containing the name of the latest and recommended analysis globaltag.
2932  """
2933  tags = subprocess.check_output(['b2conditionsdb-recommend', '--oneline']).decode('UTF-8').rstrip().split(' ')
2934  analysis_tag = ''
2935  for tag in tags:
2936  if tag.startswith('analysis_tools'):
2937  analysis_tag = tag
2938  return analysis_tag
2939 
2940 
2941 if __name__ == '__main__':
2942  from basf2.utils import pretty_print_module
2943  pretty_print_module(__name__, "modularAnalysis")
b2bii.isB2BII
def isB2BII()
Definition: b2bii.py:6
reconstructMCDecay
Definition: reconstructMCDecay.py:1
printDataStore
Definition: printDataStore.py:1
basf2.utils
Definition: utils.py:1
variablesToNtuple
Definition: variablesToNtuple.py:1
mdst.add_mdst_output
def add_mdst_output(path, mc=True, filename='mdst.root', additionalBranches=[], dataDescription=None)
Definition: mdst.py:8
removeParticlesNotInLists
Definition: removeParticlesNotInLists.py:1
variablesToExtraInfo
Definition: variablesToExtraInfo.py:1
Belle2::B2Vector3< double >
variablesToHistogram
Definition: variablesToHistogram.py:1
Belle2::DBStore::Instance
static DBStore & Instance()
Instance of a singleton DBStore.
Definition: DBStore.cc:36
b2bii.setB2BII
def setB2BII()
Definition: b2bii.py:13
Belle2::MagneticField
Magnetic field map.
Definition: MagneticField.h:43
Belle2::MagneticFieldComponentConstant
Describe one component of the Geometry.
Definition: MagneticFieldComponentConstant.h:29
pdg.conjugate
def conjugate(name)
Definition: pdg.py:98