diff --git a/ew_projects_run3/ftuple.py b/ew_projects_run3/ftuple.py new file mode 100644 index 0000000000000000000000000000000000000000..f2d4025c819a529706d05df190febd3fa78fe0b9 --- /dev/null +++ b/ew_projects_run3/ftuple.py @@ -0,0 +1,128 @@ +from PyConf.reading import (get_particles, get_pvs, get_rec_summary, + get_mc_particles) +from RecoConf.event_filters import require_pvs +from DaVinci import make_config, Options +from DaVinci.algorithms import create_lines_filter +from FunTuple import FunTuple_Particles as Funtuple +from FunTuple import FunTuple_MCParticles as MCFuntuple +from FunTuple import FunTuple_Event as EventFuntuple +from FunTuple import FunctorCollection +import FunTuple.functorcollections as FC +import Functors as F +from .ftuple_helpers import ( + decaytree_variables, mcdecaytree_selection_to_variables, extra_event_info, + selection_to_decay_descriptor, mcdtt_selections_to_decay_descriptor, Spec, + rec_summary_variables, selection_dependent_filtering, trkeff_probes, + decaytreefitter_variables) + +hlt1_tistos_lines = ["Hlt1SingleHighPtMuon", "Hlt1SingleHighPtMuonNoMuID"] +hlt2_tistos_lines = [ + "Hlt2QEE_SingleHighPtMuonFull", "Hlt2QEE_SingleHighPtMuonNoMuIDFull", + "Hlt2QEE_ZToMuMuFull", "Hlt2QEE_ZToMuMu_SingleNoMuIDFull" +] + + +def hlt2_decision_lines(trkeff_methods): + return hlt2_tistos_lines + [ + f"Hlt2TrackEff_ZToMuMu_{trkeff_method}_{probe}_{decision_type}" + for trkeff_method in trkeff_methods for probe in trkeff_probes + for decision_type in ["Tag", "Match"] + ] + + +def make_DTT(selection, line, pvs, spec): + dec_desc = selection_to_decay_descriptor( + trkeff_methods=spec.trkeff_methods)[selection] + data = selection_dependent_filtering( + data=get_particles(f"/Event/{spec.dataLocProcess}/{line}/Particles"), + selection=selection) + + variables = decaytree_variables( + selection=selection, pvs=pvs, data=data, spec=spec) + evtinfo = FC.EventInfo() + evtinfo += extra_event_info(spec=spec) + evtinfo += FC.SelectionInfo( + selection_type="Hlt1", trigger_lines=hlt1_tistos_lines) + + evtinfo += rec_summary_variables(rec_summary=get_rec_summary()) + evtinfo += FC.SelectionInfo( + selection_type="Hlt2", + trigger_lines=hlt2_decision_lines(spec.trkeff_methods)) + + variables["ALL"] += FC.HltTisTos( + selection_type="Hlt1", + trigger_lines=[f"{line}Decision" for line in hlt1_tistos_lines], + data=data) + + if spec.isFull: + variables["ALL"] += FC.HltTisTos( + selection_type="Hlt2", + trigger_lines=[f"{line}Decision" for line in hlt2_tistos_lines], + data=data) + + if spec.isTurCal: + dtf_variables = decaytreefitter_variables( + selection=selection, pvs=pvs, data=data) + for k in dtf_variables.keys(): + variables[k] = variables.get(k, FunctorCollection( + {})) + dtf_variables[k] + + funtuple = Funtuple( + name=selection, + tuple_name="DecayTree", + fields=dec_desc, + variables=variables, + inputs=data, + store_multiple_cand_info=True, + event_variables=evtinfo) + + return funtuple + + +def make_MCDTT(selection, spec): + dec_desc = mcdtt_selections_to_decay_descriptor[selection] + mc_particles = get_mc_particles("/Event/MC/Particles") + + variables = mcdecaytree_selection_to_variables[selection] + variables["ALL"] = FunctorCollection({"ID": F.PARTICLE_ID}) + evtinfo = extra_event_info(spec=spec) + + return MCFuntuple( + name=f"{selection}_mcdtt", + tuple_name="DecayTree", + fields=dec_desc, + variables=variables, + event_variables=evtinfo, + inputs=mc_particles, + ) + + +def make_ET(spec): + return EventFuntuple( + name="EventTuple", + tuple_name="EventTuple", + variables=extra_event_info(spec=spec)) + + +def main(options: Options, stream: str, campaign: str, polarity: str, + sample: str): + spec = Spec(stream, campaign, polarity, sample) + + algs = {} + + for selection, line in spec.selection_line_map.items(): + line_prefilter = create_lines_filter( + name=f"PreFilter_{line}", lines=[line]) + pvs = get_pvs() + algs[selection] = [ + line_prefilter, + require_pvs(pvs), + make_DTT(selection=selection, line=line, pvs=pvs, spec=spec) + ] + if spec.isMC: algs["EventTuple"] = [make_ET(spec=spec)] + if spec.isMC and spec.isFull: + for mcdtt_selection in mcdtt_selections_to_decay_descriptor.keys(): + algs[f"{mcdtt_selection}_MCDecayTree"] = [ + make_MCDTT(selection=mcdtt_selection, spec=spec) + ] + return make_config(options, algs) diff --git a/ew_projects_run3/ftuple_helpers.py b/ew_projects_run3/ftuple_helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..89685f8139f35cdc99923cc083ebb2c220e99801 --- /dev/null +++ b/ew_projects_run3/ftuple_helpers.py @@ -0,0 +1,410 @@ +from PyConf.reading import get_charged_protoparticles +from PyConf.Algorithms import WeightedRelTableAlg, ThOrParticleSelection +from DaVinciMCTools import MCTruthAndBkgCat +from DecayTreeFitter import DecayTreeFitter +from Hlt2Conf.lines.qee import single_high_pt_muon +from Hlt2Conf.standard_particles import make_ismuon_long_muon +from Hlt2Conf.standard_jets import make_onlytrack_particleflow, make_charged_particles +from Hlt2Conf.algorithms_thor import ParticleFilter +from GaudiKernel.SystemOfUnits import GeV +from FunTuple import FunctorCollection +import Functors as F +from enum import Enum + +_Z_decay_descriptor = { + "Z0": "^(Z0 -> mu- mu+)", + "mum": "Z0 -> ^mu- mu+", + "mup": "Z0 -> mu- ^mu+", +} +_Jpsi_decay_descriptor = { + "Jpsi": "^(J/psi(1S) -> mu- mu+)", + "mum": "J/psi(1S) -> ^mu- mu+", + "mup": "J/psi(1S) -> mu- ^mu+", +} +_Upsilon_decay_descriptor = { + "U1S": "^(Upsilon(1S) -> mu- mu+)", + "mum": "Upsilon(1S) -> ^mu- mu+", + "mup": "Upsilon(1S) -> mu- ^mu+", +} +_Wp_decay_descriptor = { + "mu": '^mu+', +} +_Wm_decay_descriptor = { + "mu": '^mu-', +} +trkeff_probes = ["mum", "mup"] + +FSR_electron_descriptor = ' {e+} {e-} {e+} {e-} ' +mcdtt_selections_to_decay_descriptor = { + "Z": { + "Z": f"^(Z0 => mu- mu+ {FSR_electron_descriptor})", + "mum": f"Z0 => ^mu- mu+ {FSR_electron_descriptor}", + "mup": f"Z0 => mu- ^mu+ {FSR_electron_descriptor}", + }, + "W": { + "W": f"^[W+ => mu+ nu_mu {FSR_electron_descriptor}]CC", + "mu": f"[W+ => ^mu+ nu_mu {FSR_electron_descriptor}]CC", + }, + "Ztau": { + "Z": + f"^(Z0 => (tau+ => mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) (tau- => mu- nu_mu~ nu_tau {FSR_electron_descriptor}))", + "taup": + f"Z0 => ^(tau+ => mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) (tau- => mu- nu_mu~ nu_tau {FSR_electron_descriptor})", + "mup": + f"Z0 => (tau+ => ^mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) (tau- => mu- nu_mu~ nu_tau {FSR_electron_descriptor})", + "taum": + f"Z0 => (tau+ => mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) ^(tau- => mu- nu_mu~ nu_tau {FSR_electron_descriptor})", + "mum": + f"Z0 => (tau+ => mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) (tau- => ^mu- nu_mu~ nu_tau {FSR_electron_descriptor})", + }, + "Wtau": { + "W": + f"^[W+ => (tau+ => mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) nu_tau]CC", + "tau": + f"[W+ => ^(tau+ => mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) nu_tau]CC", + "mu": + f"[W+ => (tau+ => ^mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) nu_tau]CC", + } +} + +_rapidity_functor = 0.5 * F.math.log((F.ENERGY + F.PZ) / (F.ENERGY - F.PZ)) +_mcdecaytree_lepton_variables = FunctorCollection({ + "PT": F.PT, + "ETA": F.ETA, + "PHI": F.PHI +}) +_mcdecaytree_boson_variables = FunctorCollection({ + "M": F.MASS, + "PT": F.PT, + "Y": _rapidity_functor +}) +mcdecaytree_selection_to_variables = { + "W": { + "mu": _mcdecaytree_lepton_variables + }, + "Wtau": { + "mu": _mcdecaytree_lepton_variables, + "tau": _mcdecaytree_lepton_variables + }, + "Z": { + "mum": _mcdecaytree_lepton_variables, + "mup": _mcdecaytree_lepton_variables, + "Z": _mcdecaytree_boson_variables, + }, + "Ztau": { + "mum": _mcdecaytree_lepton_variables, + "mup": _mcdecaytree_lepton_variables, + "taum": _mcdecaytree_lepton_variables, + "taup": _mcdecaytree_lepton_variables, + "Z": _mcdecaytree_boson_variables, + }, +} + + +def selection_to_decay_descriptor(trkeff_methods): + return { + "Z": _Z_decay_descriptor, + "ZMuID": _Z_decay_descriptor, + "Jpsi_Detached": _Jpsi_decay_descriptor, + "Jpsi_Prompt": _Jpsi_decay_descriptor, + "U1S": _Upsilon_decay_descriptor, + "Wp": _Wp_decay_descriptor, + "Wm": _Wm_decay_descriptor, + "WpNoMuID": _Wp_decay_descriptor, + "WmNoMuID": _Wm_decay_descriptor, + "ZSS": { + "Z0": "[Z0 -> mu- mu-]CC", + "mum": "[Z0 -> ^mu- mu-]CC", + "mup": "[Z0 -> mu- ^mu-]CC", + } + } | { + f"ZTrkEff_{trkeff_method}_{probe}": _Z_decay_descriptor + for trkeff_method in trkeff_methods for probe in trkeff_probes + } + + +def rec_summary_variables(rec_summary): + # as per https://gitlab.cern.ch/lhcb/Rec/-/blob/master/Rec/RecAlgs/src/RecSummaryMaker.cpp?ref_type=heads#L71 + _rec_summary_vars = [ + "nPVs", "nLongTracks", "nFTClusters", "nVPClusters", "nEcalClusters", + "hCalTot" + ] + return FunctorCollection({ + var: F.VALUE_OR(-1) @ F.RECSUMMARY_INFO(rec_summary, var) + for var in _rec_summary_vars + }) + + +def decaytree_variables(selection, pvs, spec, data): + variables = {} + variables["ALL"] = FunctorCollection({"PID": F.PARTICLE_ID}) + + muon_FC = FunctorCollection({ + "BPVIP": F.BPVIP(pvs), + "BPVIPCHI2": F.BPVIPCHI2(pvs), + "TRCHI2NDOF": F.CHI2DOF, + "PV_X": F.BPVX(pvs), + "PV_Y": F.BPVY(pvs), + "PV_Z": F.BPVZ(pvs), + "Q": F.CHARGE, + "PX": F.PX, + "PY": F.PY, + "PZ": F.PZ, + "PERR2": F.PERR2(), + "ISMUON": F.ISMUON, + "INMUON": F.INMUON, + "PIDmu": F.PID_MU, + "HCALEOP": F.VALUE_OR(0) @ F.HCALEOP, + "ECALEOP": F.VALUE_OR(0) @ F.ELECTRONSHOWEREOP, + }) + for particle in selection_to_decay_descriptor( + trkeff_methods=spec.trkeff_methods)[selection].keys(): + if "mu" not in particle: # These don't work for muons (no endvertex) + variables[particle] = FunctorCollection({ + "ENDVCHI2": + F.CHI2 @ F.ENDVERTEX, + "ENDVCHI2DOF": + F.CHI2DOF @ F.ENDVERTEX, + }) + else: + variables[particle] = muon_FC + if spec.doIsolation: + variables[particle] += _iso_variables( + data=data, pref=particle, selection=selection) + if particle == "mu": # This needs to only be Single muons (not DiMuons) + variables[particle] += _zveto_variables(data=data) + + if spec.isMC: + # Necessary to prevent duplicated algorithms with different names + unique_selection_name = selection.replace('Wp', 'W').replace('Wm', 'W') + MCTRUTH = MCTruthAndBkgCat( + data, name=f"MCTruthAndBkgCat__{unique_selection_name}") + variables["ALL"] += FunctorCollection({ + "TRUEPX": + MCTRUTH(F.PX), + "TRUEPY": + MCTRUTH(F.PY), + "TRUEPZ": + MCTRUTH(F.PZ), + "TRUEID": + F.VALUE_OR(0) @ MCTRUTH(F.PARTICLE_ID), + }) + return variables + + +def extra_event_info(spec): + """ + Defines several event-level variables for later use. + """ + return FunctorCollection({ + 'year_int': F.ALL() * 2024, + 'isMC': F.ALL() * int(spec.isMC), + 'polarity_sign': F.ALL() * spec.magpolInt + }) + + +def decaytreefitter_variables(selection, pvs, data): + v = {} + dtf__name = "DTF_AllPV" + + dtf = DecayTreeFitter( + input_particles=data, + name="__".join([dtf__name, selection]), + input_pvs=pvs, + fit_all_pvs=True) + + _dtf_FC = lambda d: FunctorCollection({ + f"_{dtf__name}__{key}": dtf(functor) + for key, functor in d.items() + }) + for muon in trkeff_probes: + v[muon] = _dtf_FC({ + "P": F.P, + "PERR2": F.PERR2(), + }) + v["Z0"] = _dtf_FC({ + "MASS": F.MASS, + "CHI2DOF": F.VALUE_OR(-999) @ F.CHI2DOF, + }) + + return v + + +def selection_dependent_filtering(data, selection): + if selection in streams_to_map()[Stream.Turbo]: + # https://gitlab.cern.ch/lhcb/Moore/-/merge_requests/3418#note_7953141 + child_pidmu_cut = lambda i: F.CHILD(i, F.PID_MU) > -10 + return ParticleFilter( + data, + F.FILTER(F.require_all(child_pidmu_cut(1), child_pidmu_cut(2))), + name=f'{selection}__pidmu_filter') + return data + + +def _iso_variables(data, pref, selection): + """ + For each candidate muon: + generate the particles within a cone with following cuts: + - radius 0.5 in (DPHI, DETA) space around signal candidate + - ~SHARE_TRACKS i.e. don't count our signal candidate + Defines: + ISO_C{PT,PX,PY} via _safe_sumcone + """ + # extract muon particlecontainer from the main decay candidate. + mu_decdesc = {"mup": "mu+", "mum": "mu-", "mu": "mu"}[pref] + isDimuon = "W" not in selection + mu_particles = _extract_muons_from_composite_candidate( + data=data, decdesc=mu_decdesc) if isDimuon else data + + with make_charged_particles.bind( + make_protoparticles=get_charged_protoparticles): + input_cands = make_onlytrack_particleflow() + alg = WeightedRelTableAlg( + ReferenceParticles=mu_particles, + InputCandidates=input_cands, + Cut=F.require_all(F.DR2 < (0.5**2), ~F.SHARE_TRACKS())) + return FunctorCollection({ + "ISO050_CPT": _safe_sumcone(alg, F.PT), + "ISO050_CPX": _safe_sumcone(alg, F.PX), + "ISO050_CPY": _safe_sumcone(alg, F.PY), + }) + + +def _zveto_variables(data): + """ + Only makes sense for W-like candidates, i.e. not Z-like. + """ + min_muon_pt = 0.1 * GeV # Just a sanity cut + input_cands = ParticleFilter( + make_ismuon_long_muon(), + F.FILTER(F.require_all(F.PT > min_muon_pt)), + name='ZVeto_muon_filter') + + alg = WeightedRelTableAlg( + ReferenceParticles=data, + InputCandidates=input_cands, + Cut=F.require_all(~F.SHARE_TRACKS())) + + zveto_muon_variables = {"PX": F.PX, "PY": F.PY, "PZ": F.PZ, "Q": F.CHARGE} + + def _zveto_muon_functor_composition(functor, relations): + return F.VALUE_OR(0) @ functor @ F.TO @ F.ENTRY_WITH_MAX_REL_VALUE_OF( + F.PT @ F.TO @ F.FORWARDARG0).bind( + F.RELATIONS.bind(F.TES(relations), F.FORWARDARGS)) + + return FunctorCollection({ + f"OTHERMUON_{var}": _zveto_muon_functor_composition( + functor=functor, relations=alg.OutputRelations) + for var, functor in zveto_muon_variables.items() + }) + + +def _extract_muons_from_composite_candidate(data, decdesc): + """ + Navigates the descendants of a candidate particle + Returns the child muons for that candidate. + """ + return ThOrParticleSelection( + InputParticles=data, + Functor=F.FILTER(F.IS_ID(decdesc)) + @ F.GET_ALL_DESCENDANTS()).OutputSelection + + +def _safe_sumcone(reltable_alg, functor): + """ + Given a relation table and a functor: + Returns SUM(functor) over all particles within that relation table. + Safe refers to giving the physical invalid value of 0 if the relation is empty (i.e. instead of NaN) + """ + return F.VALUE_OR(0) @ F.SUMCONE( + Functor=functor, Relations=reltable_alg.OutputRelations) + + +class Sample(Enum): + MC = 0 + DATA = 1 + + +class Campaign(Enum): + s24c1 = 1 + s24c2 = 2 + s24c3 = 3 + + +class Stream(Enum): + Full = 1 + Turbo = 2 + TurCal = 3 + + +class MagPol(Enum): + MagUp = 1 + MagDown = -1 + + +def _data_loc_process(stream_enum): + return { + Stream.Full: 'Spruce', + Stream.Turbo: 'HLT2', + Stream.TurCal: 'Spruce' + }[stream_enum] + + +def _do_isolation(stream_enum): + return { + Stream.Full: True, + Stream.Turbo: False, + Stream.TurCal: True + }[stream_enum] + + +def _trkeff_methods(campaign_enum): + m_withoutUT = ["VeloMuon", "SeedMuon"] + m_withUT = m_withoutUT + ["MuonUT", "Downstream"] + return { + Campaign.s24c1: m_withoutUT, + Campaign.s24c2: m_withUT, + Campaign.s24c3: m_withUT + }[campaign_enum] + + +def streams_to_map(trkeff_methods=[""]): + return { # Stream : { selection : linename } + Stream.Turbo: { + "Jpsi_Detached": "Hlt2QEE_JpsiToMuMu_Detached", + "Jpsi_Prompt": "Hlt2QEE_JpsiToMuMu_Prompt", + "U1S": "Hlt2QEE_Upsilon1SToMuMu", + }, + Stream.Full: { + "Z": "SpruceQEE_ZToMuMu", + "ZMuID": "SpruceQEE_ZToMuMu_SingleNoMuID", + "Wp": "SpruceQEE_SingleHighPtMuon", + "Wm": "SpruceQEE_SingleHighPtMuon", + "WpNoMuID": "SpruceQEE_SingleHighPtMuonNoMuID", + "WmNoMuID": "SpruceQEE_SingleHighPtMuonNoMuID", + "ZSS": "SpruceQEE_DiMuonSameSign", + }, + Stream.TurCal: { + f"ZTrkEff_{trkeff_method}_{probe}": + f"SpruceTurCalTrackEff_ZToMuMu_{trkeff_method}_{probe}_Tag" + for trkeff_method in trkeff_methods for probe in trkeff_probes + } + } + + +class Spec(): + def __init__(self, stream, campaign, magpol, sample): + stream_enum = Stream[stream] + campaign_enum = Campaign[f's{campaign}'] + magpol_enum = MagPol[magpol] + sample_enum = Sample[sample] + + self.isFull = stream_enum == Stream.Full + self.isTurCal = stream_enum == Stream.TurCal + self.isMC = sample_enum == Sample.MC + self.magpolInt = magpol_enum.value + self.trkeff_methods = _trkeff_methods(campaign_enum) + self.selection_line_map = streams_to_map( + self.trkeff_methods)[stream_enum] + self.dataLocProcess = _data_loc_process(stream_enum) + self.doIsolation = _do_isolation(stream_enum) diff --git a/ew_projects_run3/info.yaml b/ew_projects_run3/info.yaml new file mode 100644 index 0000000000000000000000000000000000000000..56b3a3f77eac9672a1cac3c56130f866fbfb7983 --- /dev/null +++ b/ew_projects_run3/info.yaml @@ -0,0 +1,85 @@ +defaults: + inform: + - luke.grazette@cern.ch + wg: QEE + +################ +##### DATA ##### +################ + + +# https://twiki.cern.ch/twiki/bin/viewauth/LHCbInternal/OperationSummary2024 +{%- set processing_campaigns = [ + ('24c2', '/Real Data/Sprucing24c2', 'v64r9') +]%} + + +############## +##### MC ##### +############## + +{%- set davinci_detdesc_platform = 'x86_64_v2-el9-clang16+detdesc-opt' %} # a `v2` detdesc platform compatible with DV release. +{%- set dddb_tag = 'dddb-20240427' %} +{%- set candidate_datasets = { + 'Turbo': [ + ('Jpsi', '24142001'), + ('U1S' , '18112001'), + ], + 'Full': [ + ('Z' , '42112001'), + ('Ztau' , '42100004'), + ('Wtau' , '42300001'), + ('W' , '42311003'), + ('DY' , '42112015'), + ('QcdBgd', '49000011'), + ('ccbar' , '49011014'), + ('bbbar' , '49011015') + ], + 'TurCal': [ + ('Z' , '42112001'), + ] +} %} +{%- set stream_variables = [ + ('Turbo', 'TurboPass'), + ('Full' , 'Spruce'), + ('TurCal', 'Spruce') +] %} +{%- set polarity_variables = [ + ('MagUp' , 'sim10-2024.Q3.4-v1.3-mu100') +] %} + + +{%- for polarity, conddb_tag in polarity_variables %} + {%- set mc_cond_path = '/MC/2024/Beam6800GeV-2024.W31.34-' + polarity + '-Nu6.3-25ns-Pythia8/Sim10d/HLT2-2024.W31.34' %} + {%- for stream, dv_input_process in stream_variables %} + {%- for eventtype, eventcode in candidate_datasets[stream] %} + {%- for campaign, _, tuple_v in processing_campaigns %} + + +MC{{campaign}}_{{polarity}}_{{eventtype}}_{{stream}}_FTuple: + application: "DaVinci/{{tuple_v}}@{{davinci_detdesc_platform}}" + input: + bk_query: '{{mc_cond_path}}/{{eventcode}}/DST' + output: FTupleQEE.root + options: + entrypoint: ew_projects_run3.ftuple:main + extra_options: + input_raw_format: 0.5 + input_type: ROOT + simulation: True + data_type: "Upgrade" + conddb_tag: {{conddb_tag}} + dddb_tag: {{dddb_tag}} + input_process: {{dv_input_process}} + geometry_version: run3/2024.Q1.2-v00.00 + extra_args: + - -- + - {{stream}} + - {{campaign}} + - {{polarity}} + - MC + + {%- endfor %} + {%- endfor %} + {%- endfor %} +{%- endfor %}