diff --git a/DaVinciExamples/python/DaVinciExamples/tupling/AllFunctors.py b/DaVinciExamples/python/DaVinciExamples/tupling/AllFunctors.py index 52ae9e13f40e676788959e189f93b5be057ad32a..738f0546bf475d9cb786e15319896d5efe791438 100644 --- a/DaVinciExamples/python/DaVinciExamples/tupling/AllFunctors.py +++ b/DaVinciExamples/python/DaVinciExamples/tupling/AllFunctors.py @@ -20,7 +20,7 @@ from FunTuple import FunTuple_Particles as Funtuple from PyConf.reading import get_particles, get_pvs from DaVinci.algorithms import add_filter from PyConf.reading import get_decreports, get_odin -from DecayTreeFitter import DTFAlg +from DecayTreeFitter import DecayTreeFitter from DaVinci.truth_matching import configured_MCTruthAndBkgCatAlg from PyConf.Algorithms import PrintDecayTree @@ -36,7 +36,7 @@ _composite = 'composite' _toplevel = 'toplevel' -def all_variables(pvs, DTFR, mctruth, ptype): +def all_variables(pvs, DTF, mctruth, ptype): """ function that returns dictonary of functors that work. @@ -129,9 +129,8 @@ def all_variables(pvs, DTFR, mctruth, ptype): if basic: all_vars['IS_PHOTON'] = F.IS_PHOTON - all_vars['DTF_PT'] = F.MAP_INPUT(Functor=F.PT, Relations=DTFR) - all_vars['DTF_BPVIPCHI2'] = F.MAP_INPUT( - Functor=F.BPVIPCHI2(pvs), Relations=DTFR) + all_vars['DTF_PT'] = DTF.get_info(F.PT) + all_vars['DTF_BPVIPCHI2'] = DTF.get_info(F.BPVIPCHI2(pvs)) # MAP_INPUT_ARRAY all_vars['MASS'] = F.MASS @@ -266,9 +265,7 @@ def alg_config(options: Options): # # DecayTreeFitter Algorithm # - DTF = DTFAlg(Input=bd2dsk_data) - # DTFParts = DTF.Output # Particles (not needed) - DTFRelations = DTF.OutputRelations # Relations + DTF = DecayTreeFitter(name='DTF_Bd2DsK', input=bd2dsk_data) # # MC truth # @@ -283,18 +280,12 @@ def alg_config(options: Options): 'pip': "[B0 -> (D_s- -> ^pi+ pi- pi-) K+]CC", } variables_dsk = { - 'B0': - FunctorCollection( - all_variables(v2_pvs, DTFRelations, mctruth, _toplevel)), - 'Kaon': - FunctorCollection( - all_variables(v2_pvs, DTFRelations, mctruth, _basic)), - 'Ds': - FunctorCollection( - all_variables(v2_pvs, DTFRelations, mctruth, _composite)), - 'pip': - FunctorCollection( - all_variables(v2_pvs, DTFRelations, mctruth, _basic)), + 'B0': FunctorCollection( + all_variables(v2_pvs, DTF, mctruth, _toplevel)), + 'Kaon': FunctorCollection(all_variables(v2_pvs, DTF, mctruth, _basic)), + 'Ds': FunctorCollection( + all_variables(v2_pvs, DTF, mctruth, _composite)), + 'pip': FunctorCollection(all_variables(v2_pvs, DTF, mctruth, _basic)), } # diff --git a/DaVinciExamples/python/DaVinciExamples/tupling/DTF_filtered.py b/DaVinciExamples/python/DaVinciExamples/tupling/DTF_filtered.py index 3ca2d53045295a016eace6d797681ca582015267..8d2b5b73cba82e4c757dea25fa4e7d0edab9f917 100644 --- a/DaVinciExamples/python/DaVinciExamples/tupling/DTF_filtered.py +++ b/DaVinciExamples/python/DaVinciExamples/tupling/DTF_filtered.py @@ -19,7 +19,7 @@ import Functors as F from Gaudi.Configuration import INFO from DaVinci import Options, make_config from DaVinci.algorithms import add_filter #, filter_on -from DecayTreeFitter import DTFAlg +from DecayTreeFitter import DecayTreeFitter from FunTuple import FunctorCollection as FC from FunTuple import FunTuple_Particles as Funtuple from PyConf.reading import get_particles @@ -42,12 +42,11 @@ def main(options: Options): data_filtered = get_particles(f"/Event/Spruce/{spruce_line}/Particles") # DecayTreeFitter Algorithm. - DTF = DTFAlg( - Input=data_filtered, MassConstraints=["D_s-"], OutputLevel=INFO) - DTFRelations = DTF.OutputRelations # Relations - - #make a helper lambda function - DTF_func = lambda func: F.MAP_INPUT(Functor=func, Relations=DTFRelations) + DTF = DecayTreeFitter( + name='DTF_filtered', + input=data_filtered, + mass_constraints=["D_s-"], + output_level=INFO) #make collection of functors for all particles variables_all = FC({ @@ -59,15 +58,15 @@ def main(options: Options): #make collection of functors for Ds meson variables_ds = FC({ 'DTF_PT': - DTF_func(F.PT), + DTF.get_info(F.PT), 'DTF_MASS': - DTF_func(F.MASS), + DTF.get_info(F.MASS), # Important note: specify an invalid value for integer functors if there exists no truth info. # The invalid value for floating point functors is set to nan. 'DTF_CHILD1_ID': - F.VALUE_OR(0) @ DTF_func(F.CHILD(1, F.PARTICLE_ID)), + F.VALUE_OR(0) @ DTF.get_info(F.CHILD(1, F.PARTICLE_ID)), 'DTF_CHILD1_MASS': - DTF_func(F.CHILD(1, F.MASS)), + DTF.get_info(F.CHILD(1, F.MASS)), }) #associate FunctorCollection to field (branch) name diff --git a/DaVinciExamples/python/DaVinciExamples/tupling/DTF_run_mc.py b/DaVinciExamples/python/DaVinciExamples/tupling/DTF_run_mc.py index e0466923d296a99add552c534a730fe8b2c720ae..bb023fddc348d3487fa1ad7ee2f59eedbb4a8216 100644 --- a/DaVinciExamples/python/DaVinciExamples/tupling/DTF_run_mc.py +++ b/DaVinciExamples/python/DaVinciExamples/tupling/DTF_run_mc.py @@ -20,7 +20,7 @@ from RecoConf.reconstruction_objects import upfront_reconstruction from RecoConf.reconstruction_objects import make_pvs_v1 from FunTuple import FunctorCollection from FunTuple import FunTuple_Particles as Funtuple -from DecayTreeFitter import DTFAlg, DTF_functors +from DecayTreeFitter import DecayTreeFitter from DaVinci import Options, make_config @@ -32,9 +32,11 @@ def main(options: Options): # DecayTreeFitter Algorithm. # One with PV constraint and one without - DTF = DTFAlg(Input=dimuons, MassConstraints=["J/psi(1S)"], OutputLevel=3) - # DTFParts = DTF.Output # Particles - DTFRelations = DTF.OutputRelations # Relations + DTF = DecayTreeFitter( + name='DTF_dimuons', + input=dimuons, + mass_constraints=["J/psi(1S)"], + output_level=3) #FunTuple: Jpsi info fields = {} @@ -62,26 +64,27 @@ def main(options: Options): 'THOR_MASS': F.MASS, 'DTF_PT': - F.MAP_INPUT(Functor=F.PT, Relations=DTFRelations), + DTF.get_info(F.PT), 'DTF_MASS': - F.MAP_INPUT(Functor=F.MASS, Relations=DTFRelations), + DTF.get_info(F.MASS), }) # # Another way of adding variables. # - DTF_pv = DTFAlg(Input=dimuons, InputPVs=pvs, MassConstraints=["J/psi(1S)"]) + DTF_pv = DecayTreeFitter( + name='DTF_PVConstraints', + input=dimuons, + input_pvs=pvs, + mass_constraints=["J/psi(1S)"]) variables_jpsi.update( - DTF_functors(DTF_pv, functors=[F.PT, F.MASS], head='DTF_PV_')) + DTF_pv.apply_functors(functors=[F.PT, F.MASS], head='DTF_PV_')) #make collection of functors for Muplus variables_muplus = FunctorCollection({ - 'LOKI_P': - 'P', - 'THOR_P': - F.P, - 'DTF_PT': - F.MAP_INPUT(Functor=F.PT, Relations=DTFRelations) + 'LOKI_P': 'P', + 'THOR_P': F.P, + 'DTF_PT': DTF.get_info(F.PT), }) #associate FunctorCollection to field (branch) name diff --git a/DaVinciExamples/python/DaVinciExamples/tupling/option_davinci_tupling_DTF_substitutePID.py b/DaVinciExamples/python/DaVinciExamples/tupling/option_davinci_tupling_DTF_substitutePID.py new file mode 100644 index 0000000000000000000000000000000000000000..2a8e188a28823d75db136caa94a00ff193c118b6 --- /dev/null +++ b/DaVinciExamples/python/DaVinciExamples/tupling/option_davinci_tupling_DTF_substitutePID.py @@ -0,0 +1,112 @@ +############################################################################### +# (c) Copyright 2022 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. # +############################################################################### +"""option_davinci_tupling_DTF_substitutePID.py +Example options to show the usage of the new DaVinciTools: DecayTreeFitter. +""" + +from Gaudi.Configuration import INFO +from FunTuple import FunTuple_Particles as Funtuple +from FunTuple import FunctorCollection as FC +from DaVinci.algorithms import add_filter +from PyConf.reading import get_particles, get_pvs_v1 +from DecayTreeFitter import DecayTreeFitter + +import Functors as F +from DaVinci import Options, make_config + + +def main(options: Options): + + B_Line = "Hlt2BsToJpsiPhi_JPsi2MuMu_PhiToKK_Line" + B_Data = get_particles(f'/Event/HLT2/{B_Line}/Particles') + + my_filter = add_filter("HDRFilter_Bs2JpsiPhi", f"HLT_PASS('{B_Line}')") + + pvs_v1 = get_pvs_v1() + DTF_JpsiPhi = DecayTreeFitter( + name='DTF_JpsiPhi', + input=B_Data, + mass_constraints=['B_s0', 'J/psi(1S)'], + input_pvs=pvs_v1, + output_level=INFO) + + DTF_JpsiKst = DecayTreeFitter( + name='DTF_JpsiKst', + input=B_Data, + substitutions=[ + 'B_s0{{B0}} -> (J/psi(1S) -> mu+ mu-) (phi(1020){{K*(892)0}} -> K+ K-{{pi-}})', + 'B_s~0{{B0}} -> (J/psi(1S) -> mu+ mu-) (phi(1020){{K*(892)~0}} -> K- K+{{pi+}})', + ], + mass_constraints=['B0', 'J/psi(1S)'], + input_pvs=pvs_v1, + output_level=INFO) + + fields = { + 'Bs': "[ B_s0 -> (J/psi(1S) -> mu+ mu-) (phi(1020) -> K+ K-) ]CC", + 'Jpsi': "[ B_s0 -> ^(J/psi(1S) -> mu+ mu-) (phi(1020) -> K+ K-) ]CC", + 'Phi': "[ B_s0 -> (J/psi(1S) -> mu+ mu-) ^(phi(1020) -> K+ K-) ]CC", + 'MuP': "[ B_s0 -> (J/psi(1S) -> ^mu+ mu-) (phi(1020) -> K+ K-) ]CC", + 'MuM': "[ B_s0 -> (J/psi(1S) -> mu+ ^mu-) (phi(1020) -> K+ K-) ]CC", + 'KP': "[ B_s0 -> (J/psi(1S) -> mu+ mu-) (phi(1020) -> ^K+ K-) ]CC", + 'KM': "[ B_s0 -> (J/psi(1S) -> mu+ mu-) (phi(1020) -> K+ ^K-) ]CC", + } + + variables_all = FC({ + # Original particle + 'ORIGINAL_ID': + F.PARTICLE_ID, + 'ORIGINAL_M': + F.MASS, + 'ORIGINAL_P': + F.P, + 'ORIGINAL_ENERGY': + F.ENERGY, + 'ORIGINAL_CHI2DOF': + F.CHI2DOF, + # DTF Bs2JpsiPhi + 'DTF_JpsiPhi_ID': + F.VALUE_OR(-1) @ DTF_JpsiPhi.get_info(F.PARTICLE_ID), + 'DTF_JpsiPhi_M': + DTF_JpsiPhi.get_info(F.MASS), + 'DTF_JpsiPhi_P': + DTF_JpsiPhi.get_info(F.P), + 'DTF_JpsiPhi_ENERGY': + DTF_JpsiPhi.get_info(F.ENERGY), + 'DTF_JpsiPhi_CHI2DOF': + DTF_JpsiPhi.get_info(F.CHI2DOF), + # DTF Bd2JpsiKst + 'DTF_JpsiKst_ID': + F.VALUE_OR(-1) @ DTF_JpsiKst.get_info(F.PARTICLE_ID), + 'DTF_JpsiKst_M': + DTF_JpsiKst.get_info(F.MASS), + 'DTF_JpsiKst_P': + DTF_JpsiKst.get_info(F.P), + 'DTF_JpsiKst_ENERGY': + DTF_JpsiKst.get_info(F.ENERGY), + 'DTF_JpsiKst_CHI2DOF': + DTF_JpsiKst.get_info(F.CHI2DOF), + }) + + variables = {'ALL': variables_all} + + #Configure Funtuple algorithm + tuple_data = Funtuple( + name="Bs2JpsiPhi_Tuple", + tuple_name="DecayTree", + fields=fields, + variables=variables, + inputs=B_Data) + + # Run + algs = { + "Bs2JpsiPhi": [my_filter, tuple_data], + } + return make_config(options, algs) diff --git a/DaVinciExamples/python/DaVinciExamples/tupling/option_davinci_tupling_substitutePID.py b/DaVinciExamples/python/DaVinciExamples/tupling/option_davinci_tupling_substitutePID.py new file mode 100644 index 0000000000000000000000000000000000000000..e24757acf7c78a22190da5eb612e0fca27307263 --- /dev/null +++ b/DaVinciExamples/python/DaVinciExamples/tupling/option_davinci_tupling_substitutePID.py @@ -0,0 +1,113 @@ +############################################################################### +# (c) Copyright 2022 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. # +############################################################################### +""" +There are many situations where you may want to change the hypothesis on PID or +swap the PID of two particles. For the decay Ds- -> K- pi+ pi-, the K- and the +pi- may be misidentified. Swapping the PID of K- and pi- allows us to recover +the misidentified case. In this example, we show how to swap the PID of two +particles, store the result in different trees, and merge two particle +containers to tuple the swapped decay and the original decay in the same tree. +""" + +from Gaudi.Configuration import INFO +from FunTuple import FunTuple_Particles as Funtuple +from FunTuple import FunctorCollection as FC +from DaVinci.algorithms import add_filter +from PyConf.reading import get_particles, get_odin +from DaVinciTools import SubstitutePID +from PyConf.Algorithms import ParticleContainerMerger +from FunTuple.functorcollections import EventInfo +from DaVinci import Options, make_config +import Functors as F + + +def main(options: Options): + + # Define the input + B_Line = "SpruceB2OC_BdToDsmK_DsmToHHH_FEST_Line" + B_Data = get_particles(f'/Event/Spruce/{B_Line}/Particles') + + # Filter + my_filter = add_filter("HDRFilter_BdToDsmK", f"HLT_PASS('{B_Line}')") + + # Define the swap + Swapped_Data = SubstitutePID( + name='Subs_SwapKpi', + input=B_Data, + substitutions=[ + 'B0 -> ( D_s- -> K-{{pi-}} pi+ pi-{{K-}} ) K+', + 'B~0 -> ( D_s+ -> K+{{pi+}} pi- pi+{{K+}} ) K-' + ]).Particles + + # Merge the swapped decays and the original decays + MergedContainer = ParticleContainerMerger( + name='HypothesisMerger', + InputContainers=[B_Data, Swapped_Data], + OutputLevel=INFO).OutputContainer + + # Prepare output for funtuple + fields = { + 'B': "[B0 -> ( D_s- -> K- pi+ pi- ) K+]CC", + 'Ds': "[B0 -> ^( D_s- -> K- pi+ pi- ) K+]CC", + 'Kminus': "[B0 -> ( D_s- -> ^K- pi+ pi- ) K+]CC", + 'piplus': "[B0 -> ( D_s- -> K- ^pi+ pi- ) K+]CC", + 'piminus': "[B0 -> ( D_s- -> K- pi+ ^pi- ) K+]CC", + 'Kplus': "[B0 -> ( D_s- -> K- pi+ pi- ) ^K+]CC", + } + + variables_all = FC({ + 'ID': F.PARTICLE_ID, + 'M': F.MASS, + 'P': F.P, + 'PT': F.PT, + 'ENERGY': F.ENERGY, + }) + + variables = {'ALL': variables_all} + + # Get event information + odin = get_odin(options) + evt_vars = EventInfo(odin) + + # + # Configure Funtuple algorithms + # + + # 1. Original decays + tuple_original = Funtuple( + name='OriginalTuple', + tuple_name='DecayTree', + fields=fields, + variables=variables, + event_variables=evt_vars, + inputs=B_Data) + # 2. Swapped decays + tuple_swapped = Funtuple( + name='SwappedTuple', + tuple_name='DecayTree', + fields=fields, + variables=variables, + event_variables=evt_vars, + inputs=Swapped_Data) + # 3. Original decays + Swapped decays (Merged) + tuple_merged = Funtuple( + name='MergedTuple', + tuple_name='DecayTree', + fields=fields, + variables=variables, + event_variables=evt_vars, + inputs=MergedContainer) + + # Run + algs = { + "Bd2DsmK": [my_filter, tuple_original, tuple_swapped, tuple_merged], + } + return make_config(options, algs) diff --git a/DaVinciExamples/tests/qmtest/tupling.qms/test_davinci_tupling_DTF_SubsPID.qmt b/DaVinciExamples/tests/qmtest/tupling.qms/test_davinci_tupling_DTF_SubsPID.qmt new file mode 100644 index 0000000000000000000000000000000000000000..b90910348603a280a3c4c8acb15dcfe70544dbc8 --- /dev/null +++ b/DaVinciExamples/tests/qmtest/tupling.qms/test_davinci_tupling_DTF_SubsPID.qmt @@ -0,0 +1,57 @@ +<?xml version="1.0" ?> +<!-- +############################################################################### +# (c) Copyright 2022 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>lbexec</text></argument> + <argument name="args"><set> + <text>DaVinciExamples.tupling.option_davinci_tupling_DTF_substitutePID:main</text> + </set></argument> + <argument name="options_yaml_fn"><text>$DAVINCIEXAMPLESROOT/example_data/test_passthrough_thor_lines.yaml</text></argument> + <argument name="extra_options_yaml"><text> + print_freq: 100 + </text></argument> + <argument name="validator"><text> +from DaVinciTests.QMTest.DaVinciExclusions import remove_known_warnings +countErrorLines({"FATAL": 0, "ERROR": 0}, + stdout=remove_known_warnings(stdout)) +import sys, os +from ROOT import TFile + + +B_vars_stored = ['Bs_DTF_JpsiKst_CHI2DOF', 'Bs_DTF_JpsiKst_ENERGY', 'Bs_DTF_JpsiKst_ID', 'Bs_DTF_JpsiKst_M', 'Bs_DTF_JpsiKst_P', 'Bs_DTF_JpsiPhi_CHI2DOF', 'Bs_DTF_JpsiPhi_ENERGY', 'Bs_DTF_JpsiPhi_ID', 'Bs_DTF_JpsiPhi_M', 'Bs_DTF_JpsiPhi_P', 'Bs_ORIGINAL_CHI2DOF', 'Bs_ORIGINAL_ENERGY', 'Bs_ORIGINAL_ID', 'Bs_ORIGINAL_M', 'Bs_ORIGINAL_P', 'Jpsi_DTF_JpsiKst_CHI2DOF', 'Jpsi_DTF_JpsiKst_ENERGY', 'Jpsi_DTF_JpsiKst_ID', 'Jpsi_DTF_JpsiKst_M', 'Jpsi_DTF_JpsiKst_P', 'Jpsi_DTF_JpsiPhi_CHI2DOF', 'Jpsi_DTF_JpsiPhi_ENERGY', 'Jpsi_DTF_JpsiPhi_ID', 'Jpsi_DTF_JpsiPhi_M', 'Jpsi_DTF_JpsiPhi_P', 'Jpsi_ORIGINAL_CHI2DOF', 'Jpsi_ORIGINAL_ENERGY', 'Jpsi_ORIGINAL_ID', 'Jpsi_ORIGINAL_M', 'Jpsi_ORIGINAL_P', 'KM_DTF_JpsiKst_CHI2DOF', 'KM_DTF_JpsiKst_ENERGY', 'KM_DTF_JpsiKst_ID', 'KM_DTF_JpsiKst_M', 'KM_DTF_JpsiKst_P', 'KM_DTF_JpsiPhi_CHI2DOF', 'KM_DTF_JpsiPhi_ENERGY', 'KM_DTF_JpsiPhi_ID', 'KM_DTF_JpsiPhi_M', 'KM_DTF_JpsiPhi_P', 'KM_ORIGINAL_CHI2DOF', 'KM_ORIGINAL_ENERGY', 'KM_ORIGINAL_ID', 'KM_ORIGINAL_M', 'KM_ORIGINAL_P', 'KP_DTF_JpsiKst_CHI2DOF', 'KP_DTF_JpsiKst_ENERGY', 'KP_DTF_JpsiKst_ID', 'KP_DTF_JpsiKst_M', 'KP_DTF_JpsiKst_P', 'KP_DTF_JpsiPhi_CHI2DOF', 'KP_DTF_JpsiPhi_ENERGY', 'KP_DTF_JpsiPhi_ID', 'KP_DTF_JpsiPhi_M', 'KP_DTF_JpsiPhi_P', 'KP_ORIGINAL_CHI2DOF', 'KP_ORIGINAL_ENERGY', 'KP_ORIGINAL_ID', 'KP_ORIGINAL_M', 'KP_ORIGINAL_P', 'MuM_DTF_JpsiKst_CHI2DOF', 'MuM_DTF_JpsiKst_ENERGY', 'MuM_DTF_JpsiKst_ID', 'MuM_DTF_JpsiKst_M', 'MuM_DTF_JpsiKst_P', 'MuM_DTF_JpsiPhi_CHI2DOF', 'MuM_DTF_JpsiPhi_ENERGY', 'MuM_DTF_JpsiPhi_ID', 'MuM_DTF_JpsiPhi_M', 'MuM_DTF_JpsiPhi_P', 'MuM_ORIGINAL_CHI2DOF', 'MuM_ORIGINAL_ENERGY', 'MuM_ORIGINAL_ID', 'MuM_ORIGINAL_M', 'MuM_ORIGINAL_P', 'MuP_DTF_JpsiKst_CHI2DOF', 'MuP_DTF_JpsiKst_ENERGY', 'MuP_DTF_JpsiKst_ID', 'MuP_DTF_JpsiKst_M', 'MuP_DTF_JpsiKst_P', 'MuP_DTF_JpsiPhi_CHI2DOF', 'MuP_DTF_JpsiPhi_ENERGY', 'MuP_DTF_JpsiPhi_ID', 'MuP_DTF_JpsiPhi_M', 'MuP_DTF_JpsiPhi_P', 'MuP_ORIGINAL_CHI2DOF', 'MuP_ORIGINAL_ENERGY', 'MuP_ORIGINAL_ID', 'MuP_ORIGINAL_M', 'MuP_ORIGINAL_P', 'Phi_DTF_JpsiKst_CHI2DOF', 'Phi_DTF_JpsiKst_ENERGY', 'Phi_DTF_JpsiKst_ID', 'Phi_DTF_JpsiKst_M', 'Phi_DTF_JpsiKst_P', 'Phi_DTF_JpsiPhi_CHI2DOF', 'Phi_DTF_JpsiPhi_ENERGY', 'Phi_DTF_JpsiPhi_ID', 'Phi_DTF_JpsiPhi_M', 'Phi_DTF_JpsiPhi_P', 'Phi_ORIGINAL_CHI2DOF', 'Phi_ORIGINAL_ENERGY', 'Phi_ORIGINAL_ID', 'Phi_ORIGINAL_M', 'Phi_ORIGINAL_P'] + + +#sort the expected vars +B_vars_stored = sorted(B_vars_stored) + +#open the TFile and TTree +ntuple = './passthrough_tuple.root' +if not os.path.isfile(ntuple): raise Exception(f"File: {ntuple} does not exist!") +f = TFile.Open(ntuple) +t_B = f.Get('Bs2JpsiPhi_Tuple/DecayTree') + +#sort the stores vars +b_names = sorted([b.GetName() for b in t_B.GetListOfLeaves()]) + +B_excluded_1 = set(B_vars_stored) - set(b_names) +B_excluded_2 = set(b_names) - set(B_vars_stored) +if len(B_excluded_1) != 0: raise Exception('Number of stored variables is less than what is expected. The extra variables expected are: ' , B_excluded_1) +if len(B_excluded_2) != 0: raise Exception('Number of stored variables is greater than what is expected. The extra variables stored are: ', B_excluded_2) + +f.Close() +print('Test successfully completed!') +os.system(f"rm {ntuple}") + + </text></argument> +</extension> diff --git a/DaVinciExamples/tests/qmtest/tupling.qms/test_davinci_tupling_SubsPID.qmt b/DaVinciExamples/tests/qmtest/tupling.qms/test_davinci_tupling_SubsPID.qmt new file mode 100644 index 0000000000000000000000000000000000000000..ea409a2c31fa0d6b95d661d539e376160693f77e --- /dev/null +++ b/DaVinciExamples/tests/qmtest/tupling.qms/test_davinci_tupling_SubsPID.qmt @@ -0,0 +1,65 @@ +<?xml version="1.0" ?> +<!-- +############################################################################### +# (c) Copyright 2022 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>lbexec</text></argument> + <argument name="args"><set> + <text>DaVinciExamples.tupling.option_davinci_tupling_substitutePID:main</text> + </set></argument> + <argument name="options_yaml_fn"><text>$DAVINCIEXAMPLESROOT/example_data/Spruce_all_lines_dst.yaml</text></argument> + <argument name="extra_options_yaml"><text> + print_freq: 100 + </text></argument> + <argument name="validator"><text> +from DaVinciTests.QMTest.DaVinciExclusions import remove_known_warnings +countErrorLines({"FATAL": 0, "ERROR": 0}, + stdout=remove_known_warnings(stdout)) +import sys, os +from ROOT import TFile + + +B_vars_stored = ['BUNCHCROSSING_ID', 'BUNCHCROSSING_TYPE', 'B_ENERGY', 'B_ID', 'B_M', 'B_P', 'B_PT', 'Ds_ENERGY', 'Ds_ID', 'Ds_M', 'Ds_P', 'Ds_PT', 'EVENTNUMBER', 'GPSTIME', 'Kminus_ENERGY', 'Kminus_ID', 'Kminus_M', 'Kminus_P', 'Kminus_PT', 'Kplus_ENERGY', 'Kplus_ID', 'Kplus_M', 'Kplus_P', 'Kplus_PT', 'ODINTCK', 'RUNNUMBER', 'piminus_ENERGY', 'piminus_ID', 'piminus_M', 'piminus_P', 'piminus_PT', 'piplus_ENERGY', 'piplus_ID', 'piplus_M', 'piplus_P', 'piplus_PT'] + + +#sort the expected vars +B_vars_stored = sorted(B_vars_stored) + +#open the TFile and TTree +ntuple = './sprucing_tuple.root' +if not os.path.isfile(ntuple): raise Exception(f"File: {ntuple} does not exist!") +f = TFile.Open(ntuple) +t_Original = f.Get('OriginalTuple/DecayTree') +t_Swapped = f.Get('SwappedTuple/DecayTree') +t_Merged = f.Get('MergedTuple/DecayTree') + +def check_tree(t_B, B_vars_stored): + #sort the stores vars + b_names = sorted([b.GetName() for b in t_B.GetListOfLeaves()]) + + B_excluded_1 = set(B_vars_stored) - set(b_names) + B_excluded_2 = set(b_names) - set(B_vars_stored) + if len(B_excluded_1) != 0: raise Exception('Number of stored variables is less than what is expected. The extra variables expected are: ' , B_excluded_1) + if len(B_excluded_2) != 0: raise Exception('Number of stored variables is greater than what is expected. The extra variables stored are: ', B_excluded_2) + +check_tree(t_Original, B_vars_stored) +check_tree(t_Swapped , B_vars_stored) +check_tree(t_Merged , B_vars_stored) + +f.Close() + +print('Test successfully completed!') +os.system(f"rm {ntuple}") + + </text></argument> +</extension> diff --git a/DaVinciTests/python/DaVinciTests/DTF_test.py b/DaVinciTests/python/DaVinciTests/DTF_test.py index 0aabcd3914005e6843e638c28f3810406c7c646e..f1295ed49b4928dab802c7d9d8153f1bd88ce5c2 100644 --- a/DaVinciTests/python/DaVinciTests/DTF_test.py +++ b/DaVinciTests/python/DaVinciTests/DTF_test.py @@ -13,13 +13,20 @@ Example of a typical DaVinci job: - selection of two detached opposite-charge muons - tuple of the selected candidates - runs DecayTreeFitterAlg and stores some output + +Note: +Below mass constraints are applied to J/psi(1S) and psi(2S) going to dimuon pairs +whereas the tupling is only performed for the former. +The latter constraint raises an expected warning to ensure that the DTF +does not silently ignore such spurious constraints, see https://gitlab.cern.ch/lhcb/Rec/-/issues/408. """ import Functors as F +from Gaudi.Configuration import INFO from Hlt2Conf.standard_particles import make_detached_mumu from RecoConf.reconstruction_objects import upfront_reconstruction from FunTuple import FunctorCollection from FunTuple import FunTuple_Particles as Funtuple -from DecayTreeFitter import DTFAlg +from DecayTreeFitter import DecayTreeFitter from DaVinci import Options, make_config @@ -30,10 +37,11 @@ def main(options: Options): # DecayTreeFitter Algorithm. # One with PV constraint and one without - DTF = DTFAlg( - Input=dimuons, MassConstraints=["J/psi(1S)", "psi(2S)"], OutputLevel=3) - # DTFParts = DTF.Output # Particles - DTFRelations = DTF.OutputRelations # Relations + DTF = DecayTreeFitter( + name='DTF_dimuons', + input=dimuons, + mass_constraints=["J/psi(1S)", "psi(2S)"], + output_level=INFO) #FunTuple: Jpsi info fields = {} @@ -44,9 +52,9 @@ def main(options: Options): 'THOR_MASS': F.MASS, 'DTF_PT': - F.MAP_INPUT(Functor=F.PT, Relations=DTFRelations), + DTF.get_info(Functor=F.PT), 'DTF_MASS': - F.MAP_INPUT(Functor=F.MASS, Relations=DTFRelations), + DTF.get_info(Functor=F.MASS), }) #associate FunctorCollection to field (branch) name diff --git a/DaVinciTests/tests/qmtest/davinci.qms/test_DTF.qmt b/DaVinciTests/tests/qmtest/davinci.qms/test_DTF.qmt index 65a931090b297ca4681051882909c7de1c47d4a1..f5c08dd5c74da3da4ebb3a8f2756f8d0244fc74d 100755 --- a/DaVinciTests/tests/qmtest/davinci.qms/test_DTF.qmt +++ b/DaVinciTests/tests/qmtest/davinci.qms/test_DTF.qmt @@ -11,6 +11,12 @@ # or submit itself to any jurisdiction. # ############################################################################### --> + +<!-- +This is a test that checks that DTF does not silently ignore spurious constraints. +See https://gitlab.cern.ch/lhcb/Rec/-/issues/408 +--> + <!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>lbexec</text></argument> @@ -28,7 +34,7 @@ <argument name="exit_code"><integer>3</integer></argument> <argument name="exit_value"><text>Failed</text></argument> <argument name="validator"><text> -findReferenceBlock("""DecayTreeFitterAlg ERROR DecayTreeFitter::DecayChain : To-be constrained particle is not in chain.""") +findReferenceBlock("""DTF_dimuons ERROR DecayTreeFitter::DecayChain : To-be constrained particle is not in chain.""") countErrorLines({"FATAL":14, "ERROR":9}) </text></argument> </extension> diff --git a/DaVinciTutorials/python/DaVinciTutorials/tutorial6_DecayTreeFit.py b/DaVinciTutorials/python/DaVinciTutorials/tutorial6_DecayTreeFit.py index 8148896c8dcdf86f35b89eead75df3f3421eae7f..bd547fbd9f63280dc886554be545f3be7bcac841 100644 --- a/DaVinciTutorials/python/DaVinciTutorials/tutorial6_DecayTreeFit.py +++ b/DaVinciTutorials/python/DaVinciTutorials/tutorial6_DecayTreeFit.py @@ -25,7 +25,7 @@ def main(options: Options): - https://twiki.cern.ch/twiki/bin/view/LHCb/DecayTreeFitter - https://www.nikhef.nl/~wouterh/topicallectures/TrackingAndVertexing/part6.pdf """ - from DecayTreeFitter import DTFAlg + from DecayTreeFitter import DecayTreeFitter #Define a dictionary of "field name" -> "decay descriptor component". fields = { @@ -42,19 +42,19 @@ def main(options: Options): kin = Kinematics() ####### Mass constraint - #For DTFAlg, as with MC Truth algorithm (previous example), this algorithm builds a relation + #For DecayTreeFitter, as with MC Truth algorithm (previous example), this algorithm builds a relation # table i.e. one-to-one map b/w B candidate -> Refitted B candidate. - # The relation table is output to the TES location "DTF.OutputRelations" + # The relation table is output to the TES location "DTF.Algorithm.OutputRelations" + # You can apply functors to refitted candidates using get_info member function. # Note: the Jpsi constraint is applied but the phi constraint seems not to be applied (see issue: https://gitlab.cern.ch/lhcb/Rec/-/issues/309) - DTF = DTFAlg(MassConstraints=["J/psi(1S)"], Input=input_data) - - #Define a helper lambda function that takes variable name (k) prepends it with "DTF_" and functor (v) which is functor - DTFMAP = lambda func: F.MAP_INPUT(func, DTF.OutputRelations) + DTF = DecayTreeFitter( + name='DTF', mass_constraints=["J/psi(1S)"], input=input_data) #Loop over the functors in kinematics function and create a new functor collection - dtf_kin = FC( - {'DTF_' + k: DTFMAP(v) - for k, v in kin.get_thor_functors().items()}) + dtf_kin = FC({ + 'DTF_' + k: DTF.get_info(v) + for k, v in kin.get_thor_functors().items() + }) #print(dtf_kin) ######### @@ -67,16 +67,11 @@ def main(options: Options): pvs_v2 = get_pvs() #Add not only mass but also constrain Bs to be coming from primary vertex - DTFpv = DTFAlg( - InputPVs=pvs, - MassConstraints=["J/psi(1S)", "phi(1020)"], - Input=input_data) - - #Helper function for decay tree fitting with PV constaint. - # We make here a lambda function that takes as input a functor - # the lambda function loads this functor into MAP_INPUT functor - # which we encountered previously and returns it. - DTFPV_MAP = lambda func: F.MAP_INPUT(func, DTFpv.OutputRelations) + DTFpv = DecayTreeFitter( + name='DTFpv', + input_pvs=pvs, + mass_constraints=["J/psi(1S)", "phi(1020)"], + input=input_data) #define the functors pv_fun = {} @@ -90,7 +85,7 @@ def main(options: Options): # should have improved compared to lifetime variable pre-DTF ("BPVLTIME"). # Below we make use of the helper function ("DTFPV_MAP") defined previously. pv_coll += FC({ - 'DTFPV_' + k: DTFPV_MAP(v) + 'DTFPV_' + k: DTFpv.get_info(v) for k, v in pv_coll.get_thor_functors().items() }) diff --git a/DaVinciTutorials/tests/refs/test_tutorial6_DecayTreeFit.ref b/DaVinciTutorials/tests/refs/test_tutorial6_DecayTreeFit.ref index 6ddb7507cd58d71dd9d050503c82d37815fc936b..473fee726f5166506e655482ae3beffaa996a88a 100644 --- a/DaVinciTutorials/tests/refs/test_tutorial6_DecayTreeFit.ref +++ b/DaVinciTutorials/tests/refs/test_tutorial6_DecayTreeFit.ref @@ -132,13 +132,13 @@ TFile: name=tutorial6_DecayTreeFit.root, title=Gaudi Trees, option=CREATE NTupleSvc INFO NTuples saved successfully ApplicationMgr INFO Application Manager Finalized successfully ApplicationMgr INFO Application Manager Terminated successfully -DecayTreeFitterAlg INFO Number of counters : 4 +DTF INFO Number of counters : 4 | Counter | # | sum | mean/eff^* | rms/err^* | min | max | | "Events" | 12 | | "Fitted Particles" | 30 | 0 | 0.0000 | 0.0000 | 4.2950e+09 | 0.0000 | | "Input Particles" | 12 | 30 | 2.5000 | 1.6073 | 1.0000 | 6.0000 | | "saved Particles" | 12 | 210 | 17.500 | 11.251 | 7.0000 | 42.000 | -DecayTreeFitterAlgWithPV INFO Number of counters : 4 +DTFpv INFO Number of counters : 4 | Counter | # | sum | mean/eff^* | rms/err^* | min | max | | "Events" | 12 | | "Fitted Particles" | 30 | 0 | 0.0000 | 0.0000 | 4.2950e+09 | 0.0000 |