diff --git a/DaVinciExamples/python/DaVinciExamples/tupling/example-tupling-basic.py b/DaVinciExamples/python/DaVinciExamples/tupling/example-tupling-basic.py new file mode 100644 index 0000000000000000000000000000000000000000..f3f3daf7cf2eb042d061fb0123a52faef13b0e1a --- /dev/null +++ b/DaVinciExamples/python/DaVinciExamples/tupling/example-tupling-basic.py @@ -0,0 +1,70 @@ +############################################################################### +# (c) Copyright 2021 CERN for the benefit of the LHCb Collaboration # +# # +# This software is distributed under the terms of the GNU General Public # +# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". # +# # +# In applying this licence, CERN does not waive the privileges and immunities # +# granted to it by virtue of its status as an Intergovernmental Organization # +# or submit itself to any jurisdiction. # +############################################################################### +""" +Example of a typical DaVinci job: + - reconstruction and selection of two detached muons + - user algorithm printing decay trees via `PrintDecayTree` + - tuple of the selected candidates +""" + +__author__ = "Maurizio Martinelli" +__date__ = "2021-03-16" + +from PyConf.Algorithms import PrintDecayTree +from PyConf.Algorithms import FunTuple_Particles as FunTuple +from DaVinci import options, run_davinci, DVNode, DVHelper +from DaVinci.standard_particles import make_detached_mumu +from DaVinci.reco_objects import upfront_reconstruction_from_file as upfront_reconstruction + +# print control flow and data flow graphs +options.control_flow_file = 'control_flow.gv' +options.data_flow_file = 'data_flow.gv' + +options.ntuple_file = 'DV-example-tupling-basic-ntp.root' +options.histo_file = 'DV-example-tupling-basic-his.root' + +# Setup dataset +options.evt_max = 10 +options.set_input_and_conds_from_testfiledb('Upgrade_Bd2KstarMuMu') +options.input_raw_format = 4.3 +options.lumi = True + +print(options) + +# Prepare the node with the selection +dimuons = make_detached_mumu() + +# Prepare the node with the user algorithms +pdt = PrintDecayTree(name="PrintDimuons", Input=dimuons) + +# Add a FUNTuple +decay_descriptors = [ + '[J/psi(1S) -> mu+ mu-]CC', '[J/psi(1S) -> ^mu+ mu-]CC', + '[J/psi(1S) -> mu+ ^mu-]CC' +] +branch_names = ['Jpsi', 'MuPlus', 'MuMinus'] +#for Jpsi store p, pt and mu+ store p and mu- store pt +functors = [['P', 'PT'], ['P'], ['PT']] +functor_branch_names = [['P', 'PT'], ['P'], ['PT']] +ftup = FunTuple( + name="DimuonsTuple", + tree_name="DecayTree", + decay_descriptors=decay_descriptors, + branch_names=branch_names, + functors=functors, + functor_branch_names=functor_branch_names, + input_location=dimuons) + +dvh = DVHelper() +dvh.add_selections_node("Selection1", upfront_reconstruction() + [dimuons]) +dvh.add_user_node('User1', [pdt]) +dvh.add_tuples_node('Tuple1', [ftup]) +dvh.run(options) diff --git a/DaVinciExamples/tests/qmtest/tupling.qms/test_example-tupling-basic.qmt b/DaVinciExamples/tests/qmtest/tupling.qms/test_example-tupling-basic.qmt new file mode 100644 index 0000000000000000000000000000000000000000..9f80f416debd268e49c347e9ea5a7efaf186b589 --- /dev/null +++ b/DaVinciExamples/tests/qmtest/tupling.qms/test_example-tupling-basic.qmt @@ -0,0 +1,52 @@ +<?xml version="1.0" ?> +<!-- + (c) Copyright 2021 CERN for the benefit of the LHCb Collaboration + + This software is distributed under the terms of the GNU General Public + Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". + + In applying this licence, CERN does not waive the privileges and immunities + granted to it by virtue of its status as an Intergovernmental Organization + or submit itself to any jurisdiction. +--> +<!DOCTYPE extension PUBLIC '-//QM/2.3/Extension//EN' 'http://www.codesourcery.com/qm/dtds/2.3/-//qm/2.3/extension//en.dtd'> +<extension class="GaudiTest.GaudiExeTest" kind="test"> +<argument name="program"><text>gaudirun.py</text></argument> +<argument name="timeout"><integer>3600</integer></argument> +<argument name="args"><set> + <text>$DAVINCIEXAMPLESROOT/python/DaVinciExamples/tupling/example-tupling-basic.py</text> +</set></argument> +<argument name="validator"><text> +findReferenceBlock(""" +PrintDimuons.PrintDecayTreeTool INFO Name E M P Pt phi Vz P(C/K) PP(C/K) +PrintDimuons.PrintDecayTreeTool INFO MeV MeV MeV MeV mrad mm +PrintDimuons.PrintDecayTreeTool INFO J/psi(1S) 30155.22 4062.62 29880.30 3993.47 -3007.05 45.68 0/0 N/A +PrintDimuons.PrintDecayTreeTool INFO +-->mu+ 12777.17 105.66 12776.73 3572.06 -2747.17 45.41 1/99 2/99 +PrintDimuons.PrintDecayTreeTool INFO +-->mu- 17377.81 105.66 17377.49 1065.10 2238.10 43.82 1/54 2/54 +PrintDimuons.PrintDecayTreeTool INFO +PrintDimuons.PrintDecayTreeTool INFO Used TES locations :- +PrintDimuons.PrintDecayTreeTool INFO 0 = '/Event/CombineParticles/Particles' +PrintDimuons.PrintDecayTreeTool INFO 1 = '/Event/FunctionalParticleMaker/Particles' +PrintDimuons.PrintDecayTreeTool INFO 2 = '/Event/Rec/ProtoP/Charged' +""", stdout, result, causes, signature_offset = 0) +countErrorLines({"FATAL":0, "ERROR":0}) + +import sys, os, glob +import ROOT as r +from ROOT import TFile + +try: + ntuple = 'DV-example-tupling-basic-ntp.root' + f = TFile.Open(ntuple) + if f.IsZombie(): + raise AttributeError('Output ROOT file is Zombie') + print('-- DV-example-tupling-basic-ntp.root QMTest -- : found output NTuple') + f.Close() + os.system('rm %s' %ntuple) +except IOError as err: + raise IOError('Impossible to open file: ', err) +except: + print('Unexpected error while opening file: ', sys.exc_info()[0]) +<argument name="exit_code"><integer>1</integer></argument> +</text></argument> +</extension> diff --git a/Phys/DaVinci/options/application-option-defaults.yaml b/Phys/DaVinci/options/application-option-defaults.yaml index 7b1eafe9e1440efd2f695828853fe97c6eb889cf..1f3917813aaa0065feca849cc005b6f9142cfb94 100644 --- a/Phys/DaVinci/options/application-option-defaults.yaml +++ b/Phys/DaVinci/options/application-option-defaults.yaml @@ -53,6 +53,9 @@ input_files: input_type: text: '"""Type of input files, e.g. "DST", "DIGI", "RDST", "MDST", "XDST" or "LDST". Default = DST."""' value: 'DST' +lumi: + text: '"""Luminosity accounting. Default = False."""' + value: False msg_svc_format: text: '"""MessageSvc output format.Default = "% F%35W%S %7W%R%T %0W%M"."""' value: '% F%35W%S %7W%R%T %0W%M' @@ -68,7 +71,7 @@ output_level: print_freq: text: '"""Frequency at which to print event numbers. Default = 1000."""' value: 1000 -python_logging_level: +python_logging_level: text: '"""Python logger level. Default = logging.WARNING=30."""' value: 30 skip_events: diff --git a/Phys/DaVinci/python/DaVinci/__init__.py b/Phys/DaVinci/python/DaVinci/__init__.py index 59c447726c1e5ff7d6acc0b64c236bb8173b1f1b..73ced637546a5ee4724025aad881dab36e402269 100644 --- a/Phys/DaVinci/python/DaVinci/__init__.py +++ b/Phys/DaVinci/python/DaVinci/__init__.py @@ -12,7 +12,7 @@ from __future__ import absolute_import from .ConfigurationUpgrade import data, mc, main -from .config import options, run_davinci, dv_node, DVSelection +from .config import options, run_davinci, DVNode, DVHelper -__all__ = ('data', 'mc', 'main', 'options', 'run_davinci', 'dv_node', - 'DVSelection') +__all__ = ('data', 'mc', 'main', 'options', 'run_davinci', 'DVNode', + 'DVHelper') diff --git a/Phys/DaVinci/python/DaVinci/config.py b/Phys/DaVinci/python/DaVinci/config.py index becf880f32351effd8ab1b2f954f48f56197877e..ae2058b14ae572577492338630b22a079f882941 100644 --- a/Phys/DaVinci/python/DaVinci/config.py +++ b/Phys/DaVinci/python/DaVinci/config.py @@ -13,10 +13,11 @@ from __future__ import absolute_import import logging -from collections import namedtuple +from collections import namedtuple, OrderedDict from PyConf.application import configure_input, configure from PyConf.components import Algorithm from PyConf.control_flow import CompositeNode, NodeLogic +from PyConf.Algorithms import EventAccounting from DaVinci.application import DVAppOptions @@ -25,10 +26,9 @@ log = logging.getLogger(__name__) options = DVAppOptions(_enabled=False) -class DVSelection(namedtuple('DVSelection', - ['node', 'extra_outputs'])): # noqa - """Immutable object fully qualifiying an DaVinci selection. - Copied from HltLine without the prescaler +class DVNode(namedtuple('DVNode', ['node', 'extra_outputs'])): # noqa + """Immutable object fully qualifying a DaVinci node. + Copied from the `HltLine` class without the prescaler argument. Attributes: node (CompositeNode): the control flow node of the line @@ -37,7 +37,7 @@ class DVSelection(namedtuple('DVSelection', __slots__ = () # do not add __dict__ (preserve immutability) def __new__(cls, name, algs, extra_outputs=None): - """Initialize a HltLine from name, algs and prescale. + """Initialize a DaVinci node from name and a set of algorithms. Creates a control flow `CompositeNode` with the given `algs` combined with `LAZY_AND` logic. @@ -54,8 +54,7 @@ class DVSelection(namedtuple('DVSelection', force_order=True) if extra_outputs is None: extra_outputs = [] - return super(DVSelection, cls).__new__(cls, node, - frozenset(extra_outputs)) + return super(DVNode, cls).__new__(cls, node, frozenset(extra_outputs)) @property def name(self): @@ -85,41 +84,154 @@ class DVSelection(namedtuple('DVSelection', return self.output_producer is not None -def dv_node(name, algs, logic=NodeLogic.NONLAZY_OR, force_order=False): - return CompositeNode( - name, combine_logic=logic, children=algs, force_order=force_order) - - -def davinci_control_flow(options, dvsels=[]): +class DVHelper(): + """Class for collecting DV algorithms.""" + + def __init__(self): + self.selections = [] + self.user = [] + self.tuples = [] + self.writers = [] + + def add_node(self, node_name, algs, kind): + if hasattr(self, kind): + setattr(self, kind, + getattr(self, kind) + [DVNode(node_name, algs)]) + else: + print('the specified note type ({}) is not valid'.format(kind)) + return + + def add_selections_node(self, node_name, algs): + self.add_node(node_name, algs, 'selections') + return + + def add_user_node(self, node_name, algs): + self.add_node(node_name, algs, 'user') + return + + def add_tuples_node(self, node_name, algs): + self.add_node(node_name, algs, 'tuples') + return + + def add_writers_node(self, node_name, algs): + self.add_node(node_name, algs, 'writers') + return + + def run(self, options, public_tools=[]): + run_davinci( + options, + selections=self.selections, + user=self.user, + tuples=self.tuples, + writers=self.writers, + public_tools=public_tools) + + +def davinci_control_flow(options, + SelectionNodes=[], + UserAlgorithms=[], + TuplesNodes=[], + WritersNodes=[]): + ''' + DaVinci control flow is split in a few sections as described in DaVinci/issue#2 + + DaVinci (LAZY_AND) + *-- LuminosityNode (NONLAZY_OR) + | *-- EventAccounting/EventAccount + *-- DVSelectionsNode (NONLAZY_OR) + | +-- DVCandidate1Node (LAZY_AND) + | | *-- PVFilter + | | *-- ParticlesFilter + | | *-- Candidate1Combiner + | +-- DVCandidate2Node (LAZY_AND) + | | *-- PVFilter + | | *-- ParticlesFilter + | | *-- Candidate2Combiner + *-- DVUserAnalysisNode (NONLAZY_OR) + | *-- PrintHeader + | *-- PrintDecayTree/PrintMyB + *-- DVTuplesNode (NONLAZY_OR) + | +-- DVTuple1Node (LAZY_AND) + | | *-- AlgorithmsForTuple1 + | | *-- Tuple1 + | +-- DVTuple2Node (LAZY_AND) + | | *-- AlgorithmsForTuple2 + | | *-- Tuple2 + | +-- DVTuple3Node (LAZY_AND) + | | *-- AlgorithmsForTuple3 + | | *-- Tuple3 + *-- DVWriterNode (LAZY_AND) + *-- DSTWriter? + *-- TuplesWriter + + The main parts are + . LuminosityNode + . SelectionsNode + . UserAnalysisNode + . TuplesNode + . WritersNode + who are in AND among each other and can accomodate nodes (defined with the class DVNode) + in OR among themselves. + + To prepare the control flow there are a few options: + 1. take a dictionary as input where all the nodes are listed in their categories + 2. take a various lists as input each related to a specific category of nodes + This function is then used to fill the control flow + ''' options.finalize() - dec = CompositeNode( - 'dv_decision', - combine_logic=NodeLogic.NONLAZY_OR, - children=[dvsel.node for dvsel in dvsels], - force_order=False) + dv_top_children = [] + # Setup luminosity + LumiNodes = [] + if options.lumi: + LumiNodes += [ + DVNode('Lumi', [EventAccounting(name='EventAccount')]) + ] # this should be modified to reflect LumiAlgsConf (configured separately?) + ordered_nodes = OrderedDict() + ordered_nodes['Luminosity'] = LumiNodes + ordered_nodes['Selections'] = SelectionNodes + ordered_nodes['UserAlgorithms'] = UserAlgorithms + ordered_nodes['Tuples'] = TuplesNodes + ordered_nodes['Writers'] = WritersNodes + for k, v in ordered_nodes.items(): + if len(v): + cnode = CompositeNode( + k, + combine_logic=NodeLogic.NONLAZY_OR, + children=[dv_node.node for dv_node in v], + force_order=False) + dv_top_children += [cnode] return CompositeNode( - 'davinci', - combine_logic=NodeLogic.NONLAZY_OR, - children=[dec], + 'DaVinci', + combine_logic=NodeLogic.LAZY_AND, + children=dv_top_children, force_order=True) -def run_davinci(options, dvsels=[], public_tools=[]): +def run_davinci(options, + selections=[], + user=[], + tuples=[], + writers=[], + public_tools=[]): ''' DaVinci application control flow Args: options (ApplicationOptions): holder of application options + selections : list of selections nodes + user : list of user nodes + tuples : list of tuples nodes + writers : list of writers nodes public_tools (list): list of public `Tool` instances to configure ''' - if not dvsels: + if not len(selections + user + tuples + writers): dummy = CompositeNode("EmptyNode", children=[]) config = configure(options, dummy, public_tools=public_tools) else: config = configure_input(options) - top_dv_node = davinci_control_flow(options, dvsels) + top_dv_node = davinci_control_flow(options, selections, user, tuples, + writers) config.update( configure(options, top_dv_node, public_tools=public_tools)) - return config diff --git a/Phys/DaVinci/tests/selection.py b/Phys/DaVinci/tests/selection.py index 41138569b67ce1a4467f431d06cbbe3b6c1c198f..299adf078d6f190f0ce09646da3a0371b829c3cf 100644 --- a/Phys/DaVinci/tests/selection.py +++ b/Phys/DaVinci/tests/selection.py @@ -8,7 +8,9 @@ # granted to it by virtue of its status as an Intergovernmental Organization # # or submit itself to any jurisdiction. # ############################################################################### -from DaVinci import options, run_davinci, DVSelection +from DaVinci import options, run_davinci, DVNode +from DaVinci.standard_particles import make_detached_mumu +from DaVinci.reco_objects import upfront_reconstruction_from_file as upfront_reconstruction # print control flow and data flow graphs options.control_flow_file = 'control_flow.gv' @@ -21,17 +23,13 @@ options.histo_file = 'DVU_test-his.root' options.evt_max = 10 options.set_input_and_conds_from_testfiledb('Upgrade_Bd2KstarMuMu') #options.set_input_and_conds_from_testfiledb('upgrade-magdown-sim09c-up02-reco-up01-minbias-ldst') - options.input_raw_format = 4.3 print(options) -from DaVinci.standard_particles import make_detached_mumu -from DaVinci.reco_objects import upfront_reconstruction_from_file as upfront_reconstruction -sel = DVSelection( - name="DVselection", algs=upfront_reconstruction() + [make_detached_mumu()]) +sel = DVNode( + name="Selection1", algs=upfront_reconstruction() + [make_detached_mumu()]) # run davinci #public_tools = [stateProvider_with_simplified_geom()] -#run_davinci(options, [sel, sel2], public_tools) -run_davinci(options, [sel]) #, public_tools) +run_davinci(options, selections=[sel]) #, public_tools) diff --git a/Phys/DaVinci/tests/simple_histos.py b/Phys/DaVinci/tests/simple_histos.py index c4aabf706b9eb5568d38403817cf444134186a3a..0be5df58bf180c96612c8b5250d47b9ea4b8e6b1 100644 --- a/Phys/DaVinci/tests/simple_histos.py +++ b/Phys/DaVinci/tests/simple_histos.py @@ -9,7 +9,7 @@ # or submit itself to any jurisdiction. # ############################################################################### from PyConf.Algorithms import GaudiHistoAlgorithm -from DaVinci import options, run_davinci, DVSelection +from DaVinci import options, run_davinci, DVNode # print control flow and data flow graphs options.control_flow_file = 'control_flow.gv' @@ -26,5 +26,5 @@ options.set_input_and_conds_from_testfiledb('Upgrade_Bd2KstarMuMu') simple_histos = GaudiHistoAlgorithm(name="SimpleHistos", HistoPrint=True) # run davinci -algs = DVSelection(name="DVselection", algs=[simple_histos]) -run_davinci(options, [algs]) +algs = DVNode(name="DVselection", algs=[simple_histos]) +run_davinci(options, user=[algs])