3. Advanced ThOr Functors

This tutorial shows how to use ThOr functors to add perform some advanced calculation on the decay candidates and add the results to the Funtuple. In particular the CHILD functor is used to refer directly to the daughters of the \(B^0_s\). It is furthermore shown how to load the PVs to calculate the impact parameters of the tracks with PyConf.reading.get_pvs and how to perform calculations with a different mass hypothesis for the candidates.

from PyConf.reading import get_particles, get_pvs
import Functors as F
from FunTuple import FunctorCollection
from FunTuple import FunTuple_Particles as Funtuple
from DaVinci import Options, make_config
from DaVinci.algorithms import create_lines_filter


def main(options: Options):
    # Define a dictionary of "field name" -> "decay descriptor component".
    # Can get daughter information from the head of the decay using F.CHILD functors see below.
    fields = {
        "Bs": "B_s0 -> (J/psi(1S) -> mu+ mu-) (phi(1020) ->K+ K-)",
        "Phi": "B_s0 -> (J/psi(1S) -> mu+ mu-) ^(phi(1020) ->K+ K-)",
        "Kp": "B_s0 ->  (J/psi(1S) -> mu+ mu-) (phi(1020) ->^K+ K-)",
        "Km": "B_s0 ->  (J/psi(1S) -> mu+ mu-) (phi(1020) ->K+ ^K-)",
    }

    # Load PVs onto TES from data, like we did with input_data below
    # Creating v2 reconstructed vertices to be used in the following functor
    # For the time being there's a mix of legacy and v2 event classes. That will eventually be cleaned once the
    # event model is fixed. In the meantime there are helper functions in DaVinci.
    pvs = get_pvs()

    # Evaluate the impact parameter
    all_vars = {}
    # The ThOr functor F.BPVIPCHI2 is data dependent. It takes as input list of pvs.
    # It calculates impact parameter chisq wrt best PV.
    # - best PV is the PV which fits best the FD of the B candidate.
    # - impact parameter chisq is the difference in the vertex-fit chisq of a given PV reconstructed with and w/o the track under consideration.
    all_vars["BPVIPCHI2"] = F.BPVIPCHI2(pvs)

    # define dictionary
    bs_vars = {}

    # Tupling vector functors
    bs_vars["BPVFDVEC_"] = F.BPVFDVEC(pvs)  # Returns 3-vector
    bs_vars["FOURMOM_P"] = F.FOURMOMENTUM  # Returns 4-vector

    # define some helpful lambda function to simplify syntax
    # This is bit like LoKi preamble of renaming functors that we encountred in previous tutorial.
    CHILD_1 = lambda func: F.CHILD(1, func)
    CHILD_2 = lambda func: F.CHILD(2, func)
    SUBCOMB_12 = lambda func: F.SUBCOMB(Functor=func, Indices=(1, 2))

    # Store the ID of the two daughters of B_s0
    bs_vars["jpsi_ID"] = CHILD_1(F.PARTICLE_ID)
    bs_vars["phi_ID"] = CHILD_2(F.PARTICLE_ID)
    bs_vars["Kp_ID"] = CHILD_2(CHILD_1(F.PARTICLE_ID))

    # Calculate sum of pT of jpsi daughter tracks
    bs_vars["jpsi_TRACKSUMPT"] = CHILD_1(F.SUM(F.PT))

    # Calculate impact parameter of K+
    bs_vars["Kp_BPVIP"] = CHILD_2(CHILD_1(F.BPVIP(pvs)))

    # Calculate invariant mass of K+ and K- combination
    bs_vars["phi_M_comb"] = CHILD_2(SUBCOMB_12(F.MASS))

    # Calculate the difference in end vertex between phi and Bs
    bs_vars["Delta_END_VZ_PhiBs0"] = CHILD_2(F.END_VZ) - F.END_VZ

    # Calculate inv mass of K+pi- where the K- is given the mass hypothesis of pi-
    bs_vars["phi_mass_kpi"] = CHILD_2(F.MASSWITHHYPOTHESES(("K+", "pi-")))

    # Calculate inv mass of K+K-
    # There three functors for computing this i.e. F.MASS, CHILD_2(SUBCOMB_12(F.MASS)) and CHILD_2(F.MASSWITHHYPOTHESES(('K+', 'K-'))) but why?
    # (see issue: https://gitlab.cern.ch/lhcb/Rec/-/issues/307)
    bs_vars["phi_mass_kk"] = CHILD_2(F.MASSWITHHYPOTHESES(("K+", "K-")))
    all_vars["M"] = F.MASS

    # Define variables dictionary "field name" -> Collections of functor
    variables = {
        "ALL": FunctorCollection(all_vars),
        "Bs": FunctorCollection(bs_vars),
    }

    # Load data from dst onto a TES
    turbo_line = "Hlt2B2CC_BsToJpsiPhi_Detached"
    input_data = get_particles(f"/Event/HLT2/{turbo_line}/Particles")

    # Add a filter
    my_filter = create_lines_filter("HDRFilter_SeeNoEvil", lines=[f"{turbo_line}"])

    # Define instance of FunTuple
    mytuple = Funtuple(
        "TDirectoryName",
        "TTreeName",
        fields=fields,
        variables=variables,
        inputs=input_data,
    )

    config = make_config(options, [my_filter, mytuple])
    return config

To run the example:

lbexec DaVinciTutorials.tutorial3_ThOrfunctors:main $DAVINCITUTORIALSROOT/options.yaml

For reference, these are the options of this example

input_files:
   - 'root://eoslhcb.cern.ch//eos/lhcb/wg/dpa/wp3/tests/hlt2_passthrough_thor_lines.dst'
input_manifest_file: 'root://eoslhcb.cern.ch//eos/lhcb/wg/dpa/wp3/tests/hlt2_passthrough_thor_lines.tck.json'
input_type: ROOT
evt_max: 100
ntuple_file: davinci_ntuple.root
input_process: TurboPass
print_freq: 1
simulation: True
lumi: False
conddb_tag: sim-20180530-vc-md100
dddb_tag: dddb-20180815
conditions_version: master
geometry_version: run3/trunk
persistreco_version: 0.0