diff --git a/phiphi/bnoc_lines.py b/phiphi/bnoc_lines.py new file mode 100644 index 0000000000000000000000000000000000000000..f029ab7f982a8d752c5db3a56ba2d839010e3404 --- /dev/null +++ b/phiphi/bnoc_lines.py @@ -0,0 +1,10 @@ +# From Matthew Monk's AProd +# put on it's own because needs to be imported at hlt but bnoc_utils.py has +# FunTuple imports etc which make it complicated +event_type_to_hlt2_lines = { + "13104012": ["Hlt2BnoC_BdsToPhiPhi"], + "11104022": ["Hlt2BnoC_BdsToPhiPhi"], + "15104020": ["Hlt2BnoC_BdsToPhiPhi"], + "13104021": ["Hlt2BnoC_BdsToPhiPhi"], + "13104016": ["Hlt2BnoC_BdsToPhiPhi"] +} \ No newline at end of file diff --git a/phiphi/bnoc_utils.py b/phiphi/bnoc_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..90802bb3fbdd0570bfea729110b2940ac8f230d0 --- /dev/null +++ b/phiphi/bnoc_utils.py @@ -0,0 +1,352 @@ +import Functors as F +from FunTuple import FunctorCollection, FunTuple_Particles as FunTuple +from DaVinciMCTools import MCTruthAndBkgCat +from PyConf.reading import get_pvs +from RecoConf.event_filters import require_pvs +from FunTuple.functorcollections import ( + DecayTreeFitterResults, + Kinematics, + ParticleID, + EventInfo, + HltTisTos, +) + + +def make_require_pvs(): + pvs = get_pvs() + return require_pvs(pvs) + + +def make_res(indices, names): + name = "".join(names) + res_fc = FunctorCollection({ + f"{name}_M": + F.SUBCOMB(Functor=F.MASS, Indices=indices), + f"{name}_PE": + F.SUBCOMB(Functor=F.SUM(F.ENERGY), Indices=indices), + f"{name}_PX": + F.SUBCOMB(Functor=F.SUM(F.PX), Indices=indices), + f"{name}_PY": + F.SUBCOMB(Functor=F.SUM(F.PY), Indices=indices), + f"{name}_PZ": + F.SUBCOMB(Functor=F.SUM(F.PZ), Indices=indices), + f"{name}_PT": + F.SUBCOMB(Functor=F.SUM(F.PT), Indices=indices), + }) + return res_fc + + +fps_4h_to_pid = { + "Kp": "K+", + "Km": "K-", + "Pip": "pi+", + "Pim": "pi-", +} + +vv_lines = { + "BdsToKstzKstzb": { + "desc": + "${B}[B_s0 -> ${Kst}(K*(892)0 -> ${Kp}K+ ${pim}pi-) ${Kstb}(K*(892)~0 -> ${Km}K- ${pip}pi+)]CC", + "mass_constraints": [] + }, + "BdsToKstzPhi": { + "desc": + "${B}[B0 -> ${phi}(phi(1020) -> ${Kp1}K+ ${Km_1}K- ) ${Kst}(K*(892)0 -> ${Kp_2}K+ ${pim_2}pi-)]CC", + "mass_constraints": [["_phi", ["phi(1020)"]]] + }, + "BdsToKstzRho": { + "desc": + "${B}[B0 -> ${Kst}(K*(892)~0 -> ${Km}K- ${pip}pi+) ${Rho}(rho(770)0 -> ${pip2}pi+ ${pim2}pi-)]CC", + "mass_constraints": [] + }, + "BdsToPhiRho": { + "desc": + "${B}[B_s0 -> ${phi}(phi(1020) -> ${Kp}K+ ${Km}K-) ${Rho}(rho(770)0 -> ${pip}pi+ ${pim}pi-)]CC", + "mass_constraints": [["_phi", ["phi(1020)"]]] + }, + "BdsToRhoRho": { + "desc": + "${B}[B0 -> ${Rho_1}(rho(770)0 -> ${pip_1}pi+ ${pim1}pi-) ${Rho_2}(rho(770)0 -> ${pip_2}pi+ ${pim_2}pi-)]CC", + "mass_constraints": [] + }, + "BdsToPhiPhi": { + "desc": + "${B_s0}[B_s0 -> ${phi_1}(phi(1020) -> ${Kp1}K+ ${Km1}K-) ${phi_2}(phi(1020) -> ${Kp2}K+ ${Km2}K-)]CC", + "mass_constraints": [["_phi", ["phi(1020)"]]] + }, +} + +hhhh_lines = { + "BdsToKpKmKpKm": { + "desc": "${B}B0 -> ${Kp1}K+ ${Km1}K- ${Kp2}K+ ${Km2}K-", + "combinations": [(1, 2), (3, 4), (1, 2, 3)] + }, + "BdsToKpKmKpPim": { + "desc": "${B}B0 -> ${Kp1}K+ ${Km1}K- ${Kp2}K+ ${pim2}pi-", + "combinations": [(1, 2), (3, 4), (1, 2, 3), (2, 3, 4)] + }, + "BdsToKpKmPipPim": { + "desc": "${B}B0 -> ${Kp1}K+ ${Km1}K- ${pip2}pi+ ${pim2}pi-", + "combinations": [(1, 2), (3, 4), (1, 4), (2, 3), (1, 2, 3), (2, 3, 4)] + }, + "BdsToKpPimPipPim": { + "desc": "${B}B0 -> ${Kp1}K+ ${pim1}pi- ${pip2}pi+ ${pim2}pi-", + "combinations": [(1, 2), (3, 4), (1, 2, 3), (2, 3, 4)] + }, + "BdsToPipPimPipPim": { + "desc": "${B}B0 -> ${pip1}pi+ ${pim1}pi- ${pip2}pi+ ${pim2}pi-", + "combinations": [(1, 2), (3, 4), (1, 2, 3)] + }, +} + + +Hlt1_decisions = [ + # main hadronic triggers for TOS + "Hlt1TrackMVA", + "Hlt1TwoTrackMVA", + "Hlt1TwoTrackMVACharmXSec", + "Hlt1TwoTrackKs", + "Hlt1KsToPiPi", + "Hlt1GECPassthrough" + # additional triggers for checking TIS signal + "Hlt1D2KK", + "Hlt1D2KPi", + "Hlt1D2PiPi", + "Hlt1DiMuonHighMass", + "Hlt1DiMuonLowMass", + "Hlt1DiMuonSoft", + "Hlt1LowPtMuon", + "Hlt1LowPtDiMuon", + "Hlt1SingleHighPtMuon", + "Hlt1TrackMuonMVA", +] + + + +def make_tistos_hlt1(data): + return HltTisTos( + selection_type="Hlt1", + trigger_lines=[ + f"{x}Decision" for x in Hlt1_decisions if "GECPassthrough" not in x + ], + data=data, + ) + + + +def make_comp_variables(data): + v2_pvs = get_pvs() + + variables = FunctorCollection({ + "Q": F.CHARGE, + "MAX_PT": F.MAX(F.PT), + "MIN_PT": F.MIN(F.PT), + "SUM_PT": F.SUM(F.PT), + "MAX_P": F.MAX(F.P), + "MIN_P": F.MIN(F.P), + "BPVDIRA": F.BPVDIRA(v2_pvs), + "CHI2VXNDOF": F.CHI2DOF, + "BPVFDCHI2": F.BPVFDCHI2(v2_pvs), + "BPVFD": F.BPVFD(v2_pvs), + "BPVVDRHO": F.BPVVDRHO(v2_pvs), + "BPVVDZ": F.BPVVDZ(v2_pvs), + "BPVIPCHI2": F.BPVIPCHI2(v2_pvs), + "BPVIP": F.BPVIP(v2_pvs), + "BPVLTIME": F.BPVLTIME(v2_pvs), + "MAX_BPVIPCHI2": F.MAX(F.BPVIPCHI2(v2_pvs)), + "MIN_BPVIPCHI2": F.MIN(F.BPVIPCHI2(v2_pvs)), + "MAXDOCACHI2": F.MAXDOCACHI2, + "MAXDOCA": F.MAXDOCA, + "MAXSDOCACHI2": F.MAXSDOCACHI2, + "MAXSDOCA": F.MAXSDOCA, + "ETA": F.ETA, + "PHI": F.PHI, + "END_VX": F.END_VX, + "END_VY": F.END_VY, + "END_VZ": F.END_VZ, + "BPVX": F.BPVX(v2_pvs), + "BPVY": F.BPVY(v2_pvs), + "BPVZ": F.BPVZ(v2_pvs), + "DOCA12": F.DOCA(Child1=1, Child2=2) + }) + + variables += Kinematics() + variables += ParticleID(extra_info=True) + variables += make_tistos_hlt1(data) + # variables += make_tistos_hlt2(data) -- NOT IMPLEMENTED YET + + return variables + + +def make_basic_variables(data): + v2_pvs = get_pvs() + + variables = FunctorCollection({ + "Q": + F.CHARGE, + "TRCHI2DOF": + F.CHI2DOF, + "TRGHOSTPROB": + F.GHOSTPROB, + "BPVIPCHI2": + F.BPVIPCHI2(v2_pvs), + "PIDK": + F.PID_K, + "PIDe": + F.PID_E, + "PIDmu": + F.PID_MU, + "PIDp": + F.PID_P, + "ISMUON": + F.ISMUON, + "BPVIP": + F.BPVIP(v2_pvs), + "ETA": + F.ETA, + "PHI": + F.PHI, + # some track-related variables + "NDOF": + F.VALUE_OR(-1) @ F.NDOF @ F.TRACK, + "NFTHITS": + F.VALUE_OR(-1) @ F.NFTHITS @ F.TRACK, + "NHITS": + F.VALUE_OR(-1) @ F.NHITS @ F.TRACK, + "NUTHITS": + F.VALUE_OR(-1) @ F.NUTHITS @ F.TRACK, + "NVPHITS": + F.VALUE_OR(-1) @ F.NVPHITS @ F.TRACK, + "TRACKHASVELO": + F.VALUE_OR(-1) @ F.TRACKHASVELO @ F.TRACK, + "TRACKHISTORY": + F.VALUE_OR(-1) @ F.TRACKHISTORY @ F.TRACK, + "TRACKPT": + F.TRACK_PT, + }) + + variables += Kinematics() + variables += ParticleID(extra_info=True) + variables += make_tistos_hlt1(data) + + return variables + + +def make_mc_variables(data): + mctruth = MCTruthAndBkgCat(data) + # add helper lambda that configures a functor to get truth information + MCTRUTH = lambda func: F.MAP_INPUT(Functor=func, Relations=mctruth.MCAssocTable) + trueid_bkgcat_info = FunctorCollection({ + "TRUEID": F.VALUE_OR(0) @ MCTRUTH(F.PARTICLE_ID), + "TRUEKEY": F.VALUE_OR(-1) @ MCTRUTH(F.OBJECT_KEY), + "TRUEPT": MCTRUTH(F.PT), + "TRUEPX": MCTRUTH(F.PX), + "TRUEPY": MCTRUTH(F.PY), + "TRUEPZ": MCTRUTH(F.PZ), + "TRUEENERGY": MCTRUTH(F.ENERGY), + "TRUEP": MCTRUTH(F.P), + "TRUEFOURMOMENTUM": MCTRUTH(F.FOURMOMENTUM), + "BKGCAT": F.BKGCAT(Relations=mctruth.BkgCatTable), + }) + return trueid_bkgcat_info + + +def make_hlt2_event_variables(line_name, line_data): + v2_pvs = get_pvs() + + evt_variables = EventInfo() + # evt_variables += SelectionInfo( + # selection_type="Hlt2", trigger_lines=Hlt2_decisions) + evt_variables += FunctorCollection({ + "NPV": F.SIZE(v2_pvs), + "ALLPVX[NPVs]": F.ALLPVX(v2_pvs), + "ALLPVY[NPVs]": F.ALLPVY(v2_pvs), + "ALLPVZ[NPVs]": F.ALLPVZ(v2_pvs), + }) + return evt_variables + + +def DecayTreeFitter(data, + pv_only_fit=True, + mass_constraints=[], + prefix="DTF", + suffix=""): + from DecayTreeFitter import DecayTreeFitter + + pvs = get_pvs() + + variables_origin = FunctorCollection() + variables_composite = FunctorCollection() + variables_basic = FunctorCollection() + + if pv_only_fit: + DTF = DecayTreeFitter( + name="PV_Fit_{hash}", + input_particles=data, + input_pvs=pvs, + ) + + variables_origin += DecayTreeFitterResults( + DTF=DTF, + prefix=prefix + "_PV" + suffix, + decay_origin=True, + with_lifetime=True, + with_kinematics=True, + ) + + variables_composite += DecayTreeFitterResults( + DTF=DTF, + prefix=prefix + "_PV" + suffix, + decay_origin=False, + with_lifetime=True, + with_kinematics=True, + ) + FunctorCollection({ + f"{prefix}_PV{suffix}_MASS": DTF.MASS, + f"{prefix}_PV{suffix}_MASSERR": DTF.MASSERR + }) + + variables_basic += DecayTreeFitterResults( + DTF=DTF, + prefix=prefix + "_PV" + suffix, + decay_origin=False, + with_lifetime=False, + with_kinematics=True, + ) + + if len(mass_constraints) > 0: + DTF_M = DecayTreeFitter( + name="PVandMass_Fit_{hash}", + input_particles=data, + input_pvs=pvs, + mass_constraints=mass_constraints, + ) + + variables_origin += DecayTreeFitterResults( + DTF=DTF_M, + prefix=prefix + "_PVandMass" + suffix, + decay_origin=True, + with_lifetime=True, + with_kinematics=True, + ) + + variables_composite += DecayTreeFitterResults( + DTF=DTF_M, + prefix=prefix + "_PVandMass" + suffix, + decay_origin=False, + with_lifetime=True, + with_kinematics=True, + ) + FunctorCollection( + { + f"{prefix}_PVandMass{suffix}_MASS": DTF_M.MASS, + f"{prefix}_PVandMass{suffix}_MASSERR": DTF_M.MASSERR + }) + + variables_basic += DecayTreeFitterResults( + DTF=DTF_M, + prefix=prefix + "_PVandMass" + suffix, + decay_origin=False, + with_lifetime=False, + with_kinematics=True, + ) + + return variables_origin, variables_composite, variables_basic \ No newline at end of file diff --git a/phiphi/decay_factory.py b/phiphi/decay_factory.py new file mode 100644 index 0000000000000000000000000000000000000000..8b9a5f0c8dd9c6300e00451806d8e61c1043f181 --- /dev/null +++ b/phiphi/decay_factory.py @@ -0,0 +1,237 @@ +from string import Template +from typing import Any +import re + + +def _dict_append(user_dict: dict, user_key: Any, user_value: Any) -> dict: + """ + Small utility function to either instantiate or add value(s) to a dictionary key + """ + if user_key not in user_dict: + user_dict[user_key] = user_value + else: + user_dict[user_key] += user_value + return user_dict + + +class DecayFactory: + """ + Use to recover the Run 1/2 DaVinci functionality of templated decay descriptors. + E.g. instead of defining each branch and decay descriptor separately, do + something like: + >>> from .decay_factory import DecayFactory + >>> decay_desc = "${B}[B_s0 -> ${Kst}(K*(892)0 -> ${Kp}K+ ${pim}pi-) ${Kstb}(K*(892)~0 -> ${Km}K- ${pip}pi+)]CC" + >>> df = DecayFactory(decay_desc) + >>> fields = df.fields + >>> print(fields) + {"B": "[B_s0 -> (K*(892)0 -> K+ pi-) (K*(892)~0 -> K- pi+)]CC", + "Kst": "[B_s0 -> ^(K*(892)0 -> K+ pi-) (K*(892)~0 -> K- pi+)]CC", + "Kp": "[B_s0 -> (K*(892)0 -> ^K+ pi-) (K*(892)~0 -> K- pi+)]CC", + "pim": "[B_s0 -> (K*(892)0 -> K+ ^pi-) (K*(892)~0 -> K- pi+)]CC", + "Kstb": "[B_s0 -> (K*(892)0 -> K+ pi-) ^(K*(892)~0 -> K- pi+)]CC", + "Km": "[B_s0 -> (K*(892)0 -> K+ pi-) (K*(892)~0 -> ^K- pi+)]CC", + "pip": "[B_s0 -> (K*(892)0 -> K+ pi-) (K*(892)~0 -> K- ^pi+)]CC"} + + Some additional functionality is added to try and automatically figure out which + paritlces are the `origin` (parent), `composite` particles (like `Kst` above) and + `basic` particles i.e. stable ones like `Kp`. + This is because often we have certain functor collections that we want include for + just the parent particle or intermediate resonances or stable particles etc. + Continuing from the above example: + >>> print(df.origin) + B + >>> print(df.composites) + ["Kst", "Kstb"] + >>> print(df.basics) + ["Kp", "pim", "Km", "pip"] + + You can then also get the `variables` dict used in FunTuple with the + `add_variables` method. This takes optional arguments so that you can add functor + collections to any or all of the branches. + E.g. + >>> from FunTuple import functorcollections as FC + >>> from FunTuple import FunctorCollection + >>> from PyConf.reading import get_pvs + >>> kinematics = FC.Kinematics() + >>> pvs = get_pvs() + >>> ltime = FunctorCollection({"BPVLTIME": F.BPVLTIME(pvs)}) + + >>> df.add_variables( + origin_vars=kinematics+ltime, + composite_vars=kinematics, + basic_vars=kinematics + ) + >>> variables = df.variables + + The `df.add_variables(...)` is equivalent to: + >>> variables = { + "B": kinematics + ltime, + "Kst": kinematics, + "Kstb": kinematics, + "Kp": kinematics, + "Km": kinematics, + "pip": kinematics, + "pim": kinematics, + } + + You can also use `add_variables` to add variables only to specific composite or + basic branches rather than all of them, using the `composite_branches` and/or + `basic_branches` arguments, e.g. as above: + >>> df.add_variables( + origin_vars=kinematics+ltime, + composite_vars=kinematics, + basic_vars=kinematics, + basic_branches=["Kp", "pim"] + ) + + will add the `kinematics` FunctorCollection only to the `Kp` and `pim` branches. + You can call `add_variables` multiple times to keep adding variables to the + `variables` dictionary. + """ + + def __init__(self, decay_desc: str) -> None: + self.decay_desc = decay_desc + # self.particle_map = self.get_particles() + self.fields, self.particles = self._get_fields() + self.origin, self.composites, self.basics = self._get_groups() + self.variables = {} + + # Copy (almost) verbatim from Run 1/2 DaVinci: + # https://gitlab.cern.ch/lhcb/Analysis/-/blob/run2-patches/Phys/DecayTreeTuple/python/DecayTreeTuple/Configuration.py#L199 + def _get_fields(self) -> tuple[dict, tuple[str]]: + template = self.decay_desc + # The argument 'template' is a Python string template + # e.g. "[${D}D0 -> ${kaon}K- ${pion}pi+]CC" + # Here ["D", "kaon", "pion"] are the branch names you want + dd = Template(template) + + # This parses the temlate to get the list of branch names, + # i.e. ["D", "kaon", "pion"] + particles = [ + y[1] if len(y[1]) else y[2] + for y in dd.pattern.findall(dd.template) if len(y[1]) or len(y[2]) + ] + + # To form the decay descriptor, we need to mark all the particles + # except for the top-level particle + mapping = { + p: '^' if particles.index(p) != 0 else '' + for p in particles + } + clean = dd.template.replace(' ', '') + for i, o in enumerate(re.findall("(\[\$|\$)", clean)): + if o == '[$': + mapping[particles[i]] = '' + + # Now make the branches + branches = {} + for p in particles: + # Need a version of the descriptor where particle 'p' is marked but nothing + # else is. + # Use mapping to ensure the parent particle is never marked. + branches[p] = dd.substitute( + {q: mapping[p] if p == q else '' + for q in particles}) + + return branches, particles + + def _get_groups(self) -> tuple[str, tuple[str], tuple[str]]: + template = self.decay_desc + dd = Template(template) + + particles = [] + origin = '' + composites = [] + basics = [] + for i, match in enumerate(dd.pattern.finditer(dd.template)): + particle = [ + y[1] if len(y[1]) else y[2] + for y in dd.pattern.findall(match.group()) + if len(y[1]) or len(y[2]) + ][0] + particles.append(particle) + if i == 0: + origin = particle + else: + next_char = dd.template[match.end()] + if next_char == '(': + composites.append(particle) + else: + basics.append(particle) + return origin, composites, basics + + def add_variables(self, + origin_vars: list = None, + composite_vars: list = None, + basic_vars: list = None, + composite_branches: list = None, + basic_branches: list = None) -> None: + """ + You can get the `variables` dict used in FunTuple with the `add_variables` + method. This takes optional arguments so that you can add functor collections + to any or all of the branches. + E.g. + >>> from FunTuple import functorcollections as FC + >>> from FunTuple import FunctorCollection + >>> from PyConf.reading import get_pvs + >>> kinematics = FC.Kinematics() + >>> pvs = get_pvs() + >>> ltime = FunctorCollection({"BPVLTIME": F.BPVLTIME(pvs)}) + + >>> df.add_variables( + origin_vars=kinematics+ltime, + composite_vars=kinematics, + basic_vars=kinematics + ) + >>> variables = df.variables + + The `df.add_variables(...)` is equivalent to: + >>> variables = { + "B": kinematics + ltime, + "Kst": kinematics, + "Kstb": kinematics, + "Kp": kinematics, + "Km": kinematics, + "pip": kinematics, + "pim": kinematics, + } + + You can also use `add_variables` to add variables only to specific composite or + basic branches rather than all of them, using the `composite_branches` and/or + `basic_branches` arguments, e.g. as above: + >>> df.add_variables( + origin_vars=kinematics+ltime, + composite_vars=kinematics, + basic_vars=kinematics, + basic_branches=["Kp", "pim"] + ) + + will add the `kinematics` FunctorCollection only to the `Kp` and `pim` branches. + You can call `add_variables` multiple times to keep adding variables to the + `variables` dictionary. + """ + if origin_vars is not None: + self.variables = _dict_append(self.variables, self.origin, + origin_vars) + if composite_vars is not None: + to_add = composite_branches or self.composites + for composite in to_add: + if composite not in self.composites: + print( + f"Unexpected composite branch {composite} not in list of \ + composites: {self.composites}" + ) + raise RuntimeError + self.variables = _dict_append(self.variables, composite, + composite_vars) + if basic_vars is not None: + to_add = basic_branches or self.basics + for basic in to_add: + if basic not in self.basics: + print( + f"Unexpected basic branch {basic} not in list of \ + basics: {self.basics}" + ) + raise RuntimeError + self.variables = _dict_append(self.variables, basic, + basic_vars) \ No newline at end of file diff --git a/phiphi/info.yaml b/phiphi/info.yaml new file mode 100644 index 0000000000000000000000000000000000000000..50e1c5a762b8b7c55b064d1447bce473207ce731 --- /dev/null +++ b/phiphi/info.yaml @@ -0,0 +1,45 @@ +defaults: + application: "DaVinci/v64r8@x86_64_v2-el9-clang16+detdesc-opt" + wg: BnoC + automatically_configure: no + inform: + - art.s@cern.ch + output: Tuple.ROOT + + +Lb2PhipK_W31_34_MagUp_MC_Hlt2BnoC_BdsToPhiPhi: + input: + bk_query: "/MC/2024/Beam6800GeV-2024.W31.34-MagUp-Nu6.3-25ns-Pythia8/Sim10d/HLT1_2024.W31.34_noUT/HLT2-2024.W31.34/15104020/HLT2.DST" + sample_fraction: 0.1 + sample_seed: HelloWorld + options: + entrypoint: phiphi.tuple_maker:make_tuples + extra_options: + input_type: ROOT + simulation: True + data_type: "Upgrade" + conddb_tag: "sim10-2024.Q3.4-v1.3-mu100" + dddb_tag: "dddb-20240427" + input_stream: bnoc + input_process: "Hlt2" + extra_args: + - "15104020" + + +Bd2PhiKst_W31_34_MagUp_MC_Hlt2BnoC_BdsToPhiPhi: + input: + bk_query: "/MC/2024/Beam6800GeV-2024.W31.34-MagUp-Nu6.3-25ns-Pythia8/Sim10d/HLT1_2024.W31.34_noUT/HLT2-2024.W31.34/13104021/HLT2.DST" + sample_fraction: 0.1 + sample_seed: HelloWorld + options: + entrypoint: phiphi.tuple_maker:make_tuples + extra_options: + input_type: ROOT + simulation: True + data_type: "Upgrade" + conddb_tag: "sim10-2024.Q3.4-v1.3-mu100" + dddb_tag: "dddb-20240427" + input_stream: bnoc + input_process: "Hlt2" + extra_args: + - "13104021" \ No newline at end of file diff --git a/phiphi/options.py b/phiphi/options.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phiphi/tuple_maker.py b/phiphi/tuple_maker.py new file mode 100644 index 0000000000000000000000000000000000000000..2524c8e979cc09a466979435c272c9edabe8fab0 --- /dev/null +++ b/phiphi/tuple_maker.py @@ -0,0 +1,317 @@ +############################################################################### +# (c) Copyright 2023 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. # +############################################################################### + +from FunTuple import FunTuple_Particles as FunTuple +from DaVinci import Options, make_config +from DaVinci.algorithms import create_lines_filter +from PyConf.reading import get_particles + +from .bnoc_lines import event_type_to_hlt2_lines +from .bnoc_utils import ( + make_require_pvs, + vv_lines, + hhhh_lines, + make_res, + make_basic_variables, + make_comp_variables, + make_hlt2_event_variables, + make_mc_variables, +) +from .bnoc_utils import DecayTreeFitter as DecayTreeFitterBnOC +from DaVinciMCTools import MCTruthAndBkgCat +from DecayTreeFitter import DecayTreeFitter +from PyConf import configurable +from .decay_factory import DecayFactory +from Hlt2Conf.standard_particles import make_long_kaons, make_long_pions,make_phi2kk +################################################### +from GaudiKernel.SystemOfUnits import MeV, mm, GeV, picosecond +from DecayTreeFitter import DecayTreeFitter +from PyConf import configurable + +import Functors as F +from FunTuple import FunTuple_Particles as FunTuple +from DaVinci import Options, make_config +from DaVinci.algorithms import create_lines_filter +from PyConf.reading import get_particles, get_pvs +import Functors.math as fmath +from DaVinci import make_config, Options +from DaVinci.algorithms import create_lines_filter +from FunTuple import FunctorCollection +from FunTuple import FunTuple_Particles as Funtuple +from Hlt2Conf.standard_particles import make_long_kaons,make_has_rich_long_kaons, make_long_pions,make_phi2kk +from RecoConf.reconstruction_objects import make_pvs +from PyConf.Algorithms import ParticleRangeFilter, TwoBodyCombiner, ThreeBodyCombiner +from Hlt2Conf.algorithms_thor import ParticleCombiner +from Functors.math import in_range +from GaudiKernel.SystemOfUnits import MeV + +from .bnoc_utils import make_basic_variables, make_comp_variables, make_hlt2_event_variables#, DecayTreeFitter +from Hlt2Conf.lines.bnoc.builders.basic_builder import make_KS_LL, make_wide_kstar0, make_phi, make_phi_fake, make_rho0, make_rho0_fake, make_omega0, make_omega0_fake, make_tight_kaons, make_tight_pions, make_soft_kaons, make_merged_pi0s, make_soft_pions +from Hlt2Conf.lines.bnoc.builders.b_builder import make_btovv, make_b2Kpi, make_BdsToVKpKm +from Hlt2Conf.lines.bnoc.utils import check_process +from PyConf.Algorithms import ( + FunctionalParticleMaker, LHCb__Phys__ParticleMakers__PhotonMaker as + PhotonMaker, LHCb__Phys__ParticleMakers__MergedPi0Maker as MergedPi0Maker, + Proto2ChargedBasic, ParticleWithBremMaker, FunctionalDiElectronMaker as + FunctionalDiElectronMakerAlg) +from PyConf import configurable +from RecoConf.reconstruction_objects import ( + make_pvs as _make_pvs, make_charged_protoparticles as + _make_charged_protoparticles, make_neutral_protoparticles as + _make_neutral_protoparticles) + +from RecoConf.ttrack_selections_reco import make_ttrack_protoparticles, make_ttrack_MVAfiltered_protoparticles + +from Hlt2Conf.algorithms_thor import ParticleCombiner, ParticleFilter +import Functors as F +from Functors.math import in_range + +from RecoConf.core_algorithms import make_unique_id_generator + +################################################### +import FunTuple.functorcollections as FC +#from Hlt2Conf.flavourTagging import run2_all_taggers +#from PyConf.Algorithms import Run2OSVertexChargeTagger +#from Hlt2Conf.flavourTagging import make_run2_oppositeside_vertex_charge_tagging_particles + +def BdsTohhhh_tuple(lines, simulation=False): + algs = {} + + require_pvs = make_require_pvs() + + for name, line_info in lines.items(): + decay_desc = line_info["desc"] + combinations = line_info["combinations"] + turbo_line = f"Hlt2BnoC_{name}" + input_data = get_particles(f"/Event/HLT2/{turbo_line}/Particles") + df = DecayFactory(decay_desc) + dtf_vars_origin, dtf_vars_composites, dtf_vars_basic = DecayTreeFitterBnOC( + input_data, prefix='DTF') + df.add_variables( + origin_vars=make_comp_variables(input_data) + dtf_vars_origin, + basic_vars=make_basic_variables(input_data) + dtf_vars_basic) + if simulation: + df.add_variables( + origin_vars=make_mc_variables(input_data), + composite_vars=make_mc_variables(input_data), + basic_vars=make_mc_variables(input_data)) + + for combination in combinations: + particles = [df.particles[i] for i in combination] + df.add_variables(origin_vars=make_res(combination, particles)) + + evt_vars = make_hlt2_event_variables(turbo_line, line_data=input_data) + my_filter = create_lines_filter( + name=f"HDRFilter_{turbo_line}", lines=[f"{turbo_line}"]) + + my_tuple = FunTuple( + name=name, + tuple_name="DecayTree", + inputs=input_data, + fields=df.fields, + variables=df.variables, + event_variables=evt_vars, + store_multiple_cand_info=True, + ) + algs[name] = [my_filter, require_pvs, my_tuple] + return algs + + +def BdsToVV_tuple(lines, simulation=False): + from Hlt2Conf.flavourTagging import run2_all_taggers + user_algorithms = {} + require_pvs = make_require_pvs() + + for name, decay_info in lines.items(): + decay_desc = decay_info["desc"] + mass_constraints = decay_info["mass_constraints"] + turbo_line = f"Hlt2BnoC_{name}" + input_data = get_particles(f"/Event/HLT2/{turbo_line}/Particles") + evt_vars = make_hlt2_event_variables(turbo_line, line_data=input_data) + df = DecayFactory(decay_desc) + dtf_pv_origin, dtf_pv_composite, dtf_pv_basic = DecayTreeFitterBnOC( + input_data, prefix='DTF', pv_only_fit=True) + all_tagging = run2_all_taggers(input_data) + ##only add tagging for B + kinematics = FC.Kinematics() +# df.add_variables( +# origin_vars=kinematics,#+FC.FlavourTaggingResults(all_tagging), +# composite_vars=kinematics, +# basic_vars=kinematics +# ) + df.add_variables( + origin_vars=make_comp_variables(input_data) + dtf_pv_origin+FC.FlavourTaggingResults(all_tagging), + composite_vars=make_comp_variables(input_data) + dtf_pv_composite, + basic_vars=make_basic_variables(input_data) + dtf_pv_basic) + + if simulation: + df.add_variables( + origin_vars=make_mc_variables(input_data), + composite_vars=make_mc_variables(input_data), + basic_vars=make_mc_variables(input_data)) + + for mass_constraint in mass_constraints: + suffix, constraints = mass_constraint + dtf_mass_origin, dtf_mass_composite, dtf_mass_basic = DecayTreeFitterBnOC( + input_data, + prefix='DTF', + pv_only_fit=False, + mass_constraints=constraints, + suffix=suffix) + df.add_variables( + origin_vars=dtf_mass_origin, + composite_vars=dtf_mass_composite, + basic_vars=dtf_mass_basic) + my_filter = create_lines_filter( + name=f"HDRFilter_{turbo_line}", lines=[f"{turbo_line}"]) + + my_tuple = FunTuple( + name=f"{name}_Tuple", + tuple_name="DecayTree", + inputs=input_data, + fields=df.fields, + variables=df.variables, + event_variables=evt_vars, + ) + user_algorithms[name] = [my_filter, require_pvs, my_tuple] + return user_algorithms + + +#def make_promptphiphi_tuple(process): +def test_alg(options: Options): + phi = make_phi2kk() + kaon = make_long_kaons() + pvs = make_pvs() + descriptor = "B0 -> phi(1020) K+ K-" +# combination_code = in_range(5000 * MeV, F.MASS, 6000 * MeV) + combination_code = F.require_all( + in_range(4000 * MeV, F.MASS, 6000 * MeV), + (F.PT) > 1.8*GeV ) + + vertex_code = F.CHI2DOF < 10 + + p = ThreeBodyCombiner( + name="TightKst2Kpi", + Input1=phi, + Input2=kaon, + Input3=kaon, + DecayDescriptor=descriptor, + CombinationCut=combination_code, + ParticleCombiner="ParticleVertexFitter", + CompositeCut=vertex_code, + PrimaryVertices=pvs, + ) + v2_pvs = get_pvs() +# all_vars = FunctorCollection({}) + all_vars = FC.Kinematics() + DTF = DecayTreeFitter( + name="DTF", + input_particles=p.OutputParticles, + mass_constraints=["phi(1020)"], + constrain_to_ownpv=True, + ) + all_vars+=FunctorCollection( + { + "DTF_PT": DTF(F.PT), + "DTF_MASS": DTF(F.MASS), + "DTF_CHI2": DTF(F.CHI2), + "DTF_NDOF": DTF.NDOF, + "DTF_CHI2DOF": DTF.CHI2DOF, + "DTF_MASS": DTF.MASS, + "DTF_MASSERR": DTF.MASSERR, + "DTF_P": DTF.P, + "DTF_PERR": DTF.PERR, + "DTF_CTAU": DTF.CTAU, + "DTF_CTAUERR": DTF.CTAUERR, + "DTF_FD": DTF.FD, + "DTF_FDERR": DTF.FDERR, + "BPVDLS": F.BPVDLS(v2_pvs), + "BPVIPCHI2": F.BPVIPCHI2(v2_pvs), + "BPVCORRM": F.BPVCORRM(v2_pvs), + "BPVCORRMERR": F.BPVCORRMERR(v2_pvs), + "BPVIP": F.BPVIP(v2_pvs), + "BPVIPCHI2": F.BPVIPCHI2(v2_pvs), + "BPVLTIME": F.BPVLTIME(v2_pvs), + "MAX_BPVIPCHI2": F.MAX(F.BPVIPCHI2(v2_pvs)), + "MIN_BPVIPCHI2": F.MIN(F.BPVIPCHI2(v2_pvs)), + "MAXDOCACHI2": F.MAXDOCACHI2, + "MAXDOCA": F.MAXDOCA, + "MAXSDOCACHI2": F.MAXSDOCACHI2, + "MAXSDOCA": F.MAXSDOCA, + # 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. + } + ) + variables = FC.Kinematics() + + kst_fields = {"B0": "B0 -> phi(1020) K+ K-", "phi": "B0 -> ^phi(1020) K+ K-"} + kst_vars = {"B0":all_vars, "phi": variables} + MCTRUTH = MCTruthAndBkgCat(p.OutputParticles, name="MCTruthAndBkgCat_spruce") + trueid_bkgcat_info = { + # 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. + "TRUEID": F.VALUE_OR(0) @ MCTRUTH(F.PARTICLE_ID), + "TRUEKEY": F.VALUE_OR(-1) @ MCTRUTH(F.OBJECT_KEY), + "TRUEPT": MCTRUTH(F.PT), + "TRUEPX": MCTRUTH(F.PX), + "TRUEPY": MCTRUTH(F.PY), + "TRUEPZ": MCTRUTH(F.PZ), + "TRUEENERGY": MCTRUTH(F.ENERGY), + "TRUEP": MCTRUTH(F.P), + "TRUEFOURMOMENTUM": MCTRUTH(F.FOURMOMENTUM), + "BKGCAT": MCTRUTH.BkgCat, + "TRUE_LIFETIME": MCTRUTH(F.MC_LIFETIME) + } + for field in kst_vars.keys(): + kst_vars[field] += FunctorCollection(trueid_bkgcat_info) + + evt_vars = make_hlt2_event_variables(p, p.OutputParticles) + ntuple = Funtuple( + name="TupleKst", + tuple_name="DecayTree", + fields=kst_fields, + variables=kst_vars, + event_variables=evt_vars, + inputs=p.OutputParticles, + ) + algs={"B2PhiKK": [ntuple]} + return make_config(options, algs) + +def BdsTohhhh_alg(options: Options): + all_user_algorithms = {} + all_user_algorithms.update(BdsTohhhh_tuple(simulation=options.simulation)) + + return make_config(options, all_user_algorithms) + + +def BdsToVV_alg(options: Options): + all_user_algorithms = {} + all_user_algorithms.update(BdsToVV_tuple(simulation=options.simulation)) + + return make_config(options, all_user_algorithms) + +#def test_alg(options: Options): +# all_user_algorithms = {} +# all_user_algorithms.update(make_promptphiphi_tuple()) +# return make_config(options, all_user_algorithms) + + +def make_tuples(options: Options, event_type=None): + all_user_algorithms = {} +# all_user_algorithms.update(make_promptphiphi_tuple(simulation=options.simulation) ) + hlt2_lines = event_type_to_hlt2_lines[event_type] + vv_to_run = {k: v for k, v in vv_lines.items() if f'Hlt2BnoC_{k}' in hlt2_lines} + hhhh_to_run = {k: v for k, v in hhhh_lines.items() if f'Hlt2BnoC_{k}' in hlt2_lines} + if len(vv_to_run) > 0: + all_user_algorithms.update(BdsToVV_tuple(vv_to_run, simulation=options.simulation)) + if len(hhhh_to_run) > 0: + all_user_algorithms.update(BdsTohhhh_tuple(hhhh_to_run, simulation=options.simulation)) + return make_config(options, all_user_algorithms) \ No newline at end of file