4 from abc
import ABC, abstractmethod
12 from modularAnalysis
import applyCuts, removeParticlesNotInLists, skimOutputUdst, summaryOfLists
16 def _get_test_sample_info(sampleName):
17 """Read in the YAML file of test samples (``skim/scripts/TestFiles.yaml``) and
18 return the info for a sample as a dict.
21 with open(b2.find_file(
"skim/scripts/TestFiles.yaml"))
as f:
22 skimTestFilesInfo = yaml.safe_load(f)
25 return skimTestFilesInfo[sampleName]
27 msg = f
"Sample {sampleName} not listed in skim/scripts/TestFiles.yaml."
34 Helper class for returning an hashable list.
39 The core method of this class.
41 return hash(tuple(self))
44 def get_test_file(sampleName):
46 Returns the KEKCC location of files used specifically for skim testing
49 sampleName (str): Name of the sample. MC samples are named *e.g.* "MC12_chargedBGx1", "MC9_ccbarBGx0"
51 sampleFileName (str): The path to the test file on KEKCC.
53 sampleInfo = _get_test_sample_info(sampleName)
55 if "location" in sampleInfo
and sampleInfo[
"location"]
is not None:
56 return sampleInfo[
"location"]
58 msg = f
"No test file location listed for {sampleName} sample."
63 def get_total_infiles(sampleName):
65 Returns the total number of input MDST files for a given sample. This is useful for resource estimate.
68 sampleName (str): Name of the sample. MC samples are named *e.g.* "MC12_chargedBGx1", "MC9_ccbarBGx0"
70 nInFiles (int): Total number of input files for sample.
72 sampleInfo = _get_test_sample_info(sampleName)
74 if "total_input_files" in sampleInfo
and sampleInfo[
"total_input_files"]
is not None:
75 return sampleInfo[
"total_input_files"]
77 msg = f
"'total_input_files' not listed for {sampleName} sample."
81 def get_events_per_file(sampleName):
83 Returns an estimate for the average number of events in an input MDST file of the given sample type.
86 sampleName (str): Name of the sample. MC samples are named *e.g.* "MC12_chargedBGx1", "MC9_ccbarBGx0"
88 nEventsPerFile (int): The average number of events in file of the given sample type.
90 sampleInfo = _get_test_sample_info(sampleName)
92 if "average_events_per_file" in sampleInfo
and sampleInfo[
"average_events_per_file"]
is not None:
93 return sampleInfo[
"average_events_per_file"]
95 msg = f
"'average_events_per_file' not listed for {sampleName} sample."
99 def add_skim(label, lists, path):
101 create uDST skim for given lists, saving into $label.udst.root
102 Particles not necessary for the given particle lists are not saved.
105 label (str): the registered skim name
106 lists (list(str)): the list of ParticleList names that have been created by a skim list builder function
107 path (basf2.Path): modules are added to this path
110 skimCode = Registry.encode_skim_name(label)
111 skimOutputUdst(skimCode, lists, path=path)
112 summaryOfLists(lists, path=path)
115 def setSkimLogging(path, additional_modules=[]):
117 Turns the log level to ERROR for several modules to decrease
118 the total size of the skim log files
121 skim_path (basf2.Path): modules are added to this path
122 additional_modules (list(str)): an optional list of extra noisy module
123 names that should be silenced
125 noisy_modules = [
'ParticleLoader',
'ParticleVertexFitter'] + additional_modules
126 for module
in path.modules():
127 if module.type()
in noisy_modules:
128 module.set_log_level(b2.LogLevel.ERROR)
132 def ifEventPasses(cut, conditional_path, path):
134 If the event passes the given ``cut`` proceed to process everything in ``conditional_path``.
135 Afterwards return here and continue processing with the next module.
138 cut (str): selection criteria which needs to be fulfilled in order to continue with ``conditional_path``
139 conditional_path (basf2.Path): path to execute if the event fulfills the criteria ``cut``
140 path (basf2.Path): modules are added to this path
142 eselect = path.add_module(
"VariableToReturnValue", variable=f
"passesEventCut({cut})")
143 eselect.if_value(
'=1', conditional_path, b2.AfterConditionPath.CONTINUE)
146 def get_eventN(fileName):
148 Returns the number of events in a specific file
151 filename: Name of the file as clearly indicated in the argument's name.
154 process = subprocess.Popen([
'b2file-metadata-show',
'--json', fileName], stdout=subprocess.PIPE)
155 out = process.communicate()[0]
156 if process.returncode == 0:
157 metadata = json.loads(out)
158 nevents = metadata[
'nEvents']
161 b2.B2ERROR(
"FILE INVALID OR NOT FOUND.")
164 def skimOutputMdst(skimDecayMode, path=None, skimParticleLists=[], outputParticleLists=[], includeArrays=[], *,
165 outputFile=None, dataDescription=None):
167 Create a new path for events that contain a non-empty particle list specified via skimParticleLists.
168 Write the accepted events as a mdst file, saving only particles from skimParticleLists
169 and from outputParticleLists. It outputs a .mdst file.
170 Additional Store Arrays and Relations to be stored can be specified via includeArrays
173 :param str skimDecayMode: Name of the skim. If no outputFile is given this is
174 also the name of the output filename. This name will be added to the
175 FileMetaData as an extra data description "skimDecayMode"
176 :param list(str) skimParticleLists: Names of the particle lists to skim for.
177 An event will be accepted if at least one of the particle lists is not empty
178 :param list(str) outputParticleLists: Names of the particle lists to store in
179 the output in addition to the ones in skimParticleLists
180 :param list(str) includeArrays: datastore arrays/objects to write to the output
181 file in addition to mdst and particle information
182 :param basf2.Path path: Path to add the skim output to. Defaults to the default analysis path
183 :param str outputFile: Name of the output file if different from the skim name
184 :param dict dataDescription: Additional data descriptions to add to the output file. For example {"mcEventType":"mixed"}
189 from mdst
import add_mdst_output
192 if outputFile
is None:
193 outputFile = skimDecayMode
196 if not outputFile.endswith(
".mdst.root"):
197 outputFile +=
".mdst.root"
199 skimfilter = b2.register_module(
'SkimFilter')
200 skimfilter.set_name(
'SkimFilter_' + skimDecayMode)
201 skimfilter.param(
'particleLists', skimParticleLists)
202 path.add_module(skimfilter)
203 filter_path = b2.create_path()
204 skimfilter.if_value(
'=1', filter_path, b2.AfterConditionPath.CONTINUE)
207 skim_path = b2.create_path()
208 saveParticleLists = skimParticleLists + outputParticleLists
213 if dataDescription
is None:
216 dataDescription.setdefault(
"skimDecayMode", skimDecayMode)
217 add_mdst_output(skim_path, filename=outputFile)
218 filter_path.add_independent_path(skim_path,
"skim_" + skimDecayMode)
221 def resolve_skim_modules(SkimsOrModules, *, LocalModule=None):
223 Produce an ordered list of skims, by expanding any Python skim module names into a
224 list of skims in that module. Also produce a dict of skims grouped by Python module.
227 RuntimeError: Raised if a skim is listed twice.
228 ValueError: Raised if ``LocalModule`` is passed and skims are normally expected
229 from more than one module.
233 for name
in SkimsOrModules:
234 if name
in Registry.names:
236 elif name
in Registry.modules:
237 skims.extend(Registry.get_skims_in_module(name))
239 duplicates = set([skim
for skim
in skims
if skims.count(skim) > 1])
242 f
"Skim{'s'*(len(duplicates)>1)} requested more than once: {', '.join(duplicates)}"
245 modules = sorted({Registry.get_skim_module(skim)
for skim
in skims})
249 f
"Local module {LocalModule} specified, but the combined skim expects "
250 "skims from more than one module. No steering file written."
252 modules = {LocalModule.rstrip(
".py"): sorted(skims)}
255 f
"skim.{module}": sorted(
256 [skim
for skim
in skims
if Registry.get_skim_module(skim) == module]
258 for module
in modules
261 return skims, modules
266 *[Module for skim expert usage]* Create the EventExtraInfo DataStore object, and set
267 all required flag variables to zero.
271 Add this module to the path before adding any skims, so that the skim flags are
272 defined in the datastore for all events.
280 skims (skimExpertFunctions.BaseSkim): Skim to initialise event flag for.
283 from variables
import variables
as vm
284 from ROOT
import Belle2
292 vm.addAlias(skim.flag, f
"eventExtraInfo({skim.flag})")
296 Register EventExtraInfo in datastore if it has not been registered already.
303 Initialise flags to zero.
307 for skim
in self.
skims:
313 *[Module for skim expert usage]* Update the skim flag to be 1 if there is at least
314 one candidate in any of the skim lists.
318 Add this module to the post-skim path of each skim in the combined skim, as the
319 skim lists are only guaranteed to exist on the conditional path (if a
320 conditional path was used).
328 skim (skimExpertFunctions.BaseSkim): Skim to update event flag for.
331 from ROOT
import Belle2
339 Check EventExtraInfo has been registered previously. This registration should be
340 done by InitialiseSkimFlag.
346 Check if at least one skim list is non-empty; if so, update the skim flag to 1.
349 from ROOT
import Belle2
354 if any([
not ListObj.isValid()
for ListObj
in ListObjects]):
356 f
"Error in UpdateSkimFlag for {self.skim}: particle lists not built. "
357 "Did you add this module to the pre-skim path rather than the post-skim path?"
360 nCandidates = sum(ListObj.getListSize()
for ListObj
in ListObjects)
367 def _sphinxify_decay(decay_string):
368 """Format the given decay string by using LaTeX commands instead of plain-text.
369 Output is formatted for use with Sphinx (ReStructured Text).
371 This is a utility function for autogenerating skim documentation.
374 decay_string (str): A decay descriptor.
377 sphinxed_string (str): LaTeX version of the decay descriptor.
380 decay_string = re.sub(
"^(B.):generic",
"\\1_{\\\\text{had}}", decay_string)
381 decay_string = decay_string.replace(
":generic",
"")
382 decay_string = decay_string.replace(
":semileptonic",
"_{\\text{SL}}")
383 decay_string = decay_string.replace(
":FSP",
"_{FSP}")
384 decay_string = decay_string.replace(
":V0",
"_{V0}")
385 decay_string = re.sub(
"_[0-9]+",
"", decay_string)
391 (
"gamma",
"\\gamma"),
393 (
"anti-p-",
"\\bar{p}"),
408 (
"J/psi",
"J/\\psi"),
409 (
"anti-Lambda_c-",
"\\Lambda^{-}_{c}"),
410 (
"anti-Sigma+",
"\\overline{\\Sigma}^{+}"),
411 (
"anti-Lambda0",
"\\overline{\\Lambda}^{0}"),
412 (
"anti-D0*",
"\\overline{D}^{0*}"),
413 (
"anti-D*0",
"\\overline{D}^{0*}"),
414 (
"anti-D0",
"\\overline{D}^0"),
415 (
"anti-B0",
"\\overline{B}^0"),
416 (
"Sigma+",
"\\Sigma^{+}"),
417 (
"Lambda_c+",
"\\Lambda^{+}_{c}"),
418 (
"Lambda0",
"\\Lambda^{0}"),
427 (
"D_s*+",
"D^{+*}_s"),
428 (
"D_s*-",
"D^{-*}_s"),
434 tex_string = decay_string
435 for (key, value)
in substitutes:
436 tex_string = tex_string.replace(key, value)
437 return f
":math:`{tex_string}`"
440 def fancy_skim_header(SkimClass):
441 """Decorator to generate a fancy header to skim documentation and prepend it to the
442 docstring. Add this just above the definition of a skim.
444 Also ensures the documentation of the template functions like `BaseSkim.build_lists`
445 is not repeated in every skim documentation.
447 .. code-block:: python
450 class MySkimName(BaseSkim):
451 # docstring here describing your skim, and explaining cuts.
453 SkimName = SkimClass.__name__
454 SkimCode = Registry.encode_skim_name(SkimName)
455 authors = SkimClass.__authors__
or [
"(no authors listed)"]
456 description = SkimClass.__description__
or "(no description)"
457 contact = SkimClass.__contact__
or "(no contact listed)"
458 category = SkimClass.__category__
or "(no category listed)"
460 if isinstance(authors, str):
462 authors = re.split(
r",\s+and\s+|\s+and\s+|,\s+&\s+|\s+&\s+|,\s+|\s*\n\s*", authors)
464 authors = [re.sub(
r"^\s+|\s+$",
"", author)
for author
in authors]
466 if isinstance(category, list):
467 category =
", ".join(category)
470 match = re.match(
"([^<>()`]+) [<(]([^<>()`]+@[^<>()`]+)[>)]", contact)
472 name, email = match[1], match[2]
473 contact = f
"`{name} <mailto:{email}>`_"
477 * **Skim description**: {description}
478 * **Skim name**: {SkimName}
479 * **Skim LFN code**: {SkimCode}
480 * **Category**: {category}
481 * **Author{"s"*(len(authors) > 1)}**: {", ".join(authors)}
482 * **Contact**: {contact}
485 if SkimClass.ApplyHLTHadronCut:
486 HLTLine =
"*This skim includes a selection on the HLT flag* ``hlt_hadron``."
487 header = f
"{header.rstrip()}\n\n {HLTLine}\n"
489 if SkimClass.__doc__:
490 SkimClass.__doc__ = header +
"\n\n" + SkimClass.__doc__.lstrip(
"\n")
493 SkimClass.__doc__ = header
496 SkimClass.load_standard_lists.__doc__ = SkimClass.load_standard_lists.__doc__
or ""
497 SkimClass.build_lists.__doc__ = SkimClass.build_lists.__doc__
or ""
498 SkimClass.validation_histograms.__doc__ = SkimClass.validation_histograms.__doc__
or ""
499 SkimClass.additional_setup.__doc__ = SkimClass.additional_setup.__doc__
or ""
505 """Base class for skims. Initialises a skim name, and creates template functions
506 required for each skim.
508 See `writing-skims` for information on how to use this to define a new skim.
512 """List of module types to be silenced. This may be necessary in certain skims in
513 order to keep log file sizes small.
516 The elements of this list should be the module *type*, which is not necessarily
517 the same as the module name. The module type can be inspected in Python via
521 This attribute is used by `BaseSkim.set_skim_logging`.
524 TestFiles = [get_test_file(
"MC13_mixedBGx1")]
525 """Location of an MDST file to test the skim on. Defaults to an MC13 mixed BGx1
526 sample. If you want to use a different test file for your skim, set it using
530 MergeDataStructures = {}
531 """Dict of ``str -> function`` pairs to determine if any special data structures
532 should be merged when combining skims. Currently, this is only used to merge FEI
533 config parameters when running multiple FEI skims at once, so that it can be run
534 just once with all the necessary arguments."""
536 ApplyHLTHadronCut =
False
537 """If this property is set to True, then the HLT selection for ``hlt_hadron`` will
538 be applied to the skim lists when the skim is added to the path.
541 produce_on_tau_samples =
True
542 """If this property is set to False, then ``b2skim-prod`` will not produce data
543 production requests for this skim on taupair MC samples. This decision may be made
544 for one of two reasons:
546 * The retention rate of the skim on taupair samples is basically zero, so there is
547 no point producing the skim for these samples.
549 * The retention rate of the skim on taupair samples is too high (>20%), so the
550 production system may struggle to handle the jobs.
555 def __description__(self):
560 def __category__(self):
565 def __authors__(self):
570 def __contact__(self):
575 """Eight-digit code assigned to this skim in the registry."""
576 return Registry.encode_skim_name(self.
name)
578 def __init__(self, *, OutputFileName=None, additionalDataDescription=None, udstOutput=True, validation=False):
579 """Initialise the BaseSkim class.
582 OutputFileName (str): Name to give output uDST files. If none given, then
583 defaults to eight-number skim code.
584 additionalDataDescription (dict): additional data description to be added to the output file metadata.
585 udstOutput (bool): If True, add uDST output to the path.
586 validation (bool): If True, build lists and write validation histograms
587 instead of writing uDSTs.
589 self.
name = self.__class__.__name__
601 Load any standard lists. This code will be run before any
602 `BaseSkim.additional_setup` and `BaseSkim.build_lists`.
605 This is separated into its own function so that when skims are combined, any
606 standard lists used by two skims can be loaded just once.
609 path (basf2.Path): Skim path to be processed.
614 Perform any setup steps necessary before running the skim. This may include:
616 * applying event-level cuts using `ifEventPasses`,
617 * adding the `MCMatcherParticles` module to the path,
621 Standard particle lists should *not* be loaded in here. This should be done
622 by overriding the method `BaseSkim.load_standard_lists`. This is crucial for
623 avoiding loading lists twice when combining skims for production.
626 path (basf2.Path): Skim path to be processed.
632 """Create the skim lists to be saved in the output uDST. This function is where
633 the main skim cuts should be applied. At the end of this method, the attribute
634 ``SkimLists`` must be set so it can be used by `output_udst`.
637 path (basf2.Path): Skim path to be processed.
641 """Create validation histograms for the skim.
644 path (basf2.Path): Skim path to be processed.
648 def __call__(self, path, *, udstOutput=None, validation=None):
649 """Produce the skim particle lists and write uDST file.
652 path (basf2.Path): Skim path to be processed.
653 udstOutput (bool): [DEPRECATED ARGUMENT] If True, add uDST output to the path.
654 validation (bool): [DEPRECATED ARGUMENT] If True, build lists and write
655 validation histograms instead of writing uDSTs.
659 "Passing the `{arg}` argument to `BaseSkim.__call__` is deprecated. "
660 "Please pass all configuration parameters to the initialisation of "
663 if udstOutput
is not None:
664 b2.B2WARNING(warning.format(arg=
"udstOutput"))
666 if validation
is not None:
667 b2.B2WARNING(warning.format(arg=
"validation"))
688 b2.B2FATAL(f
"No validation histograms defined for {self} skim.")
696 Return the skim path.
698 * If `BaseSkim.skim_event_cuts` has been run, then the skim lists will only be
699 created on a conditional path, so subsequent modules should be added to the
702 * If `BaseSkim.skim_event_cuts` has not been run, then the main analysis path is
707 raise ValueError(
"Skim has not been added to the path yet!")
711 """Main analysis path."""
713 _ConditionalPath =
None
714 """Conditional path to be set by `BaseSkim.skim_event_cuts` if event-level cuts are applied."""
717 """Apply event-level cuts in a skim-safe way.
720 cut (str): Event-level cut to be applied.
721 path (basf2.Path): Skim path to be processed.
724 ConditionalPath (basf2.Path): Path on which the rest of this skim should be
725 processed. On this path, only events which passed the event-level cut
726 will be processed further.
729 If running this function in `BaseSkim.additional_setup` or
730 `BaseSkim.build_lists`, redefine the ``path`` to the path returned by
731 `BaseSkim.skim_event_cuts`, *e.g.*
733 .. code-block:: python
735 def build_lists(self, path):
736 path = self.skim_event_cuts("nTracks>4", path=path)
737 # rest of skim list building...
740 The motivation for using this function over `applyEventCuts` is that
741 `applyEventCuts` completely removes events from processing. If we combine
742 multiple skims in a single steering file (which is done in production), and
743 the first has a set of event-level cuts, then all the remaining skims will
744 never even see those events.
746 Internally, this function creates a new path, which is only processed for
747 events passing the event-level cut. To avoid issues around particles not
748 being available on the main path (leading to noisy error logs), we need to
749 add the rest of the skim to this path. So this new path is assigned to the
750 attribute ``BaseSkim._ConditionalPath``, and ``BaseSkim.__call__`` will run
751 all remaining methods on this path.
755 "BaseSkim.skim_event_cuts cannot be run twice in one skim. "
756 "Please join your event-level cut strings into a single string."
759 ConditionalPath = b2.Path()
762 eselect = path.add_module(
"VariableToReturnValue", variable=f
"passesEventCut({cut})")
763 eselect.if_value(
'=1', ConditionalPath, b2.AfterConditionPath.CONTINUE)
765 return ConditionalPath
770 Event-level variable indicating whether an event passes the skim or not. To use
771 the skim flag without writing uDST output, use the argument ``udstOutput=False``
772 when instantiating the skim class.
774 return f
"passes_{self}"
778 Add the module `skimExpertFunctions.InitialiseSkimFlag` to the path, which
779 initialises flag for this skim to zero.
785 Add the module `skimExpertFunctions.InitialiseSkimFlag` to the path, which
786 initialises flag for this skim to zero.
790 If a conditional path has been created before this, then this function
791 *must* run on the conditional path, since the skim lists are not guaranteed
792 to exist for all events on the main path.
798 Get the list of skim particle list names, without creating the particle lists on
801 DummyPath = b2.Path()
811 """Check if the method of the class is the same as in its parent class, or if it has
814 Useful for determining if *e.g.* `validation_histograms` has been defined for a
818 ParentsWithAttr = [parent
for parent
in cls.__mro__[1:]
if hasattr(parent, method)]
823 OldestParentWithAttr = ParentsWithAttr[-1]
824 return getattr(cls, method) == getattr(OldestParentWithAttr, method)
835 """Turns the log level to ERROR for selected modules to decrease the total size
836 of the skim log files. Additional modules can be silenced by setting the attribute
837 `NoisyModules` for an individual skim.
840 path (basf2.Path): Skim path to be processed.
844 This method works by inspecting the modules added to the path, and setting
845 the log level to ERROR. This method should be called *after* all
846 skim-related modules are added to the path.
848 b2.set_log_level(b2.LogLevel.INFO)
850 NoisyModules = [
"ParticleLoader",
"ParticleVertexFitter"] + self.
NoisyModules
854 modules = [module
for path
in paths
for module
in path.modules()]
856 for module
in modules:
857 if module.type()
in set(NoisyModules):
858 module.set_log_level(b2.LogLevel.ERROR)
861 """Write the skim particle lists to an output uDST and print a summary of the
862 skim list statistics.
865 path (basf2.Path): Skim path to be processed.
871 f
"No skim list names defined in self.SkimLists for {self} skim!"
875 skimDecayMode=self.
code,
881 summaryOfLists(self.
SkimLists, path=path)
884 """Apply the ``hlt_hadron`` selection if the property ``ApplyHLTHadronCut`` is True.
887 path (basf2.Path): Skim path to be processed.
889 hlt_hadron =
"SoftwareTriggerResult(software_trigger_cut&skim&accept_hadron)"
892 applyCuts(SkimList, f
"{hlt_hadron}==1", path=path)
896 """Class for creating combined skims which can be run using similar-looking methods
897 to `BaseSkim` objects.
899 A steering file which combines skims can be as simple as the following:
901 .. code-block:: python
904 import modularAnalysis as ma
905 from skim.foo import OneSkim, TwoSkim, RedSkim, BlueSkim
908 ma.inputMdstList("default", [], path=path)
909 skims = CombinedSkim(OneSkim(), TwoSkim(), RedSkim(), BlueSkim())
910 skims(path) # load standard lists, create skim lists, and save to uDST
913 When skims are combined using this class, the `BaseSkim.NoisyModules` lists of each
914 skim are combined and all silenced.
916 The heavy-lifting functions `additional_setup`, `build_lists` and `output_udst` are
917 modified to loop over the corresponding functions of each invididual skim. The
918 `load_standard_lists` method is also modified to load all required lists, without
919 accidentally loading a list twice.
921 Calling an instance of the `CombinedSkim` class will load all the required particle
922 lists, then run all the setup steps, then the list building functions, and then all
926 __authors__ = [
"Phil Grace"]
927 __description__ =
None
928 __category__ =
"combined"
935 additionalDataDescription=None,
939 CombinedSkimName="CombinedSkim",
942 """Initialise the CombinedSkim class.
945 *skims (BaseSkim): One or more (instantiated) skim objects.
946 NoisyModules (list(str)): Additional modules to silence.
947 additionalDataDescription (dict): Overrides corresponding setting of all individual skims.
948 udstOutput (bool): Overrides corresponding setting of all individual skims.
949 mdstOutput (bool): Write a single MDST output file containing events which
950 pass any of the skims in this combined skim.
951 mdst_kwargs (dict): kwargs to be passed to `mdst.add_mdst_output`. Only used
952 if ``mdstOutput`` is True.
953 CombinedSkimName (str): Sets output of ``__str__`` method of this combined skim.
954 OutputFileName (str): If mdstOutput=True, this option sets the name of the combined output file.
955 If mdstOutput=False, this option does nothing.
958 if NoisyModules
is None:
961 if not all([isinstance(skim, BaseSkim)
for skim
in skims]):
962 raise NotImplementedError(
963 "Must pass only `BaseSkim` type objects to `CombinedSkim`."
967 self.
name = CombinedSkimName
969 skim.NoisyModules += NoisyModules
970 self.
TestFiles = list({f
for skim
in skims
for f
in skim.TestFiles})
975 if additionalDataDescription
is not None:
977 skim.additionalDataDescription = additionalDataDescription
980 if udstOutput
is not None:
982 skim._udstOutput = udstOutput
986 self.
mdst_kwargs.update(OutputFileName=OutputFileName)
996 def __call__(self, path):
998 skim._MainPath = path
1012 yield from self.
Skims
1015 """Add all required standard list loading to the path.
1018 To avoid loading standard lists twice, this function creates dummy paths
1019 that are passed through ``load_standard_lists`` for each skim. These dummy
1020 paths are then inspected, and a list of unique module-parameter combinations
1021 is added to the main skim path.
1024 path (basf2.Path): Skim path to be processed.
1026 ModulesAndParams = []
1028 DummyPath = b2.Path()
1029 skim.load_standard_lists(DummyPath)
1033 ModulesAndParams.extend(tuple([
1037 (param.name,
_hashable_list(param.values)
if isinstance(param.values, list)
else param.values)
1038 for param
in module.available_params()
1039 if param.values != param.default
1042 for module
in DummyPath.modules()
1046 ModulesAndParams = dict.fromkeys(ModulesAndParams)
1049 for module, params
in ModulesAndParams:
1050 path.add_module(module, **dict(params))
1053 """Run the `BaseSkim.additional_setup` function of each skim.
1056 path (basf2.Path): Skim path to be processed.
1059 skim.additional_setup(path)
1062 """Run the `BaseSkim.build_lists` function of each skim.
1065 path (basf2.Path): Skim path to be processed.
1068 skim.build_lists(skim._ConditionalPath
or path)
1071 """Run the `BaseSkim.output_udst` function of each skim.
1074 path (basf2.Path): Skim path to be processed.
1077 if skim._udstOutput:
1078 skim.output_udst(skim._ConditionalPath
or path)
1082 Add MDST output to the path if the event passes any of the skim flags.
1083 EventExtraInfo is included in the MDST output so that the flags are available in
1086 The ``CombinedSkimName`` parameter in the `CombinedSkim` initialisation is used
1087 for the output filename if ``filename`` is not included in kwargs.
1090 path (basf2.Path): Skim path to be processed.
1091 **kwargs: Passed on to `mdst.add_mdst_output`.
1093 from mdst
import add_mdst_output
1098 sum_flags =
" + ".join(f
"eventExtraInfo({f})" for f
in self.
flags)
1099 variable = f
"formula({sum_flags})"
1101 passes_flag_path = b2.Path()
1102 passes_flag = path.add_module(
"VariableToReturnValue", variable=variable)
1103 passes_flag.if_value(
">0", passes_flag_path, b2.AfterConditionPath.CONTINUE)
1105 filename = kwargs.get(
"filename", kwargs.get(
"OutputFileName", self.
name))
1107 if filename
is None:
1108 filename = self.
name
1110 if not filename.endswith(
".mdst.root"):
1111 filename +=
".mdst.root"
1113 kwargs[
"filename"] = filename
1115 if "OutputFileName" in kwargs.keys():
1116 del kwargs[
"OutputFileName"]
1118 kwargs.setdefault(
"dataDescription", {})
1124 skim_code = self.
code
1127 kwargs[
"dataDescription"].setdefault(
"skimDecayMode", skim_code)
1130 kwargs[
"additionalBranches"] += [
"EventExtraInfo"]
1132 kwargs[
"additionalBranches"] = [
"EventExtraInfo"]
1134 add_mdst_output(path=passes_flag_path, **kwargs)
1137 """Run the `BaseSkim.apply_hlt_hadron_cut_if_required` function for each skim.
1140 path (basf2.Path): Skim path to be processed.
1143 skim.apply_hlt_hadron_cut_if_required(skim._ConditionalPath
or path)
1146 """Run `BaseSkim.set_skim_logging` for each skim."""
1148 skim.set_skim_logging()
1153 List of flags for each skim in combined skim.
1155 return [skim.flag
for skim
in self]
1160 Event-level variable indicating whether an event passes the combinedSkim or not.
1162 return f
"passes_{self}"
1166 Add the module `skimExpertFunctions.InitialiseSkimFlag` to the path, to
1167 initialise flags for each skim.
1173 Add the module `skimExpertFunctions.InitialiseSkimFlag` to the conditional path
1180 def produce_on_tau_samples(self):
1182 Corresponding value of this attribute for each individual skim.
1185 RuntimeError: Raised if the individual skims in combined skim contain a mix
1186 of True and False for this property.
1188 produce_on_tau = [skim.produce_on_tau_samples
for skim
in self]
1189 if all(produce_on_tau):
1191 elif all(
not TauBool
for TauBool
in produce_on_tau):
1195 "The individual skims in the combined skim contain a mix of True and "
1196 "False for the attribute `produce_on_tau_samples`.\n"
1197 " It is unclear what should be done in this situation."
1198 "Please reorganise the combined skims to address this.\n"
1199 " Skims included in the problematic combined skim: "
1200 f
"{', '.join(skim.name for skim in self)}"
1204 """Read the values of `BaseSkim.MergeDataStructures` and merge data structures
1207 For example, if ``MergeDataStructures`` has the value ``{"FEIChannelArgs":
1208 _merge_boolean_dicts.__func__}``, then ``_merge_boolean_dicts`` is run on all
1209 input skims with the attribute ``FEIChannelArgs``, and the value of
1210 ``FEIChannelArgs`` for that skim is set to the result.
1212 In the FEI skims, this is used to merge configs which are passed to a cached
1213 function, thus allowing us to apply the FEI once with all the required particles
1216 for iSkim, skim
in enumerate(self.
Skims):
1217 for attribute, MergingFunction
in skim.MergeDataStructures.items():
1218 SkimsWithAttribute = [skim
for skim
in self
if hasattr(skim, attribute)]
1222 MergingFunction(*[getattr(skim, attribute)
for skim
in SkimsWithAttribute])
1226 """Check for duplicate particle list names.
1230 Skims cannot be relied on to define their particle list names in advance, so
1231 this function can only be run after `build_lists` is run.
1233 ParticleListLists = [skim.SkimLists
for skim
in self]
1234 ParticleLists = [lst
for L
in ParticleListLists
for lst
in L]
1235 DuplicatedParticleLists = {
1237 for ParticleList
in ParticleLists
1238 if ParticleLists.count(ParticleList) > 1
1240 if DuplicatedParticleLists:
1242 f
"Non-unique output particle list names in combined skim! "
1243 f
"{', '.join(DuplicatedParticleLists)}"