Source code for Hlt2Conf.standard_particles

###############################################################################
# (c) Copyright 2019 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.                                       #
###############################################################################
"""Maker functions for Particle definitions common across HLT2.

The Run 2 code makes the sensible choice of creating Particle objects first,
and then filtering these with FilterDesktop instances. Because the
FunctionalParticleMaker can apply  ThOr predicates directly to Track and
ProtoParticle objects, we just do the one step.

Implemented filters and builders:
- get_{all, long, down, upstream, ttrack}_track_selector : selectors of specific track type containers
- standard_protoparticle_filter : basic protoparticle filter
- _make_particles : basic particle maker
- _make_{ , all, long}_ChargedBasics : basic maker using Proto2ChargedBasic
- make_long_cb_{electrons, muons, pions, kaons, protons} : maker of long ChargedBasic particles per type
- make_has_rich_long_cb_{pions, kaons} : maker of long ChargedBasic particles per type requiring RICH
- make_photons : basic builder for photons
- make_long_electrons_{no, with}_brem : basic builders for long electrons with or without brem corrections
- make_long_{muons, pions, kaons, protons} : basic builder for long muons, pions, kaons, protons
- make_ismuon_long_muon : basic builder for long muons with ISMUON condition
- make_{up, down}_{electrons_no_brem, muons, pions, kaons, protons} : basic builders for upstream or downstream electrons (no brem correction), muons, pions, kaons, protons
- make_ttrack_{pions, protons, muons, kaons} : basic builders for ttrack protons or pions
- make_has_rich_{long, down, up}_{pions, kaons, protons} : basic builders for longstream, downstream and upstream pions, kaons, protons
- make_has_rich_ttrack_{pions, protons, muons, kaons} : basic builders for ttrack pions, protons
- make_{resolved, merged}_pi0s : basic builder to make resolved and merged pi0
- filter_leptons_loose : basic filter for preselection of leptons
- _make_dielectron_with_brem :
- make_detached_{dielectron, mue, mumu} : detached dilepton builded
- make_detached_{dielectron, mue}_with_brem : detached dilepton builder with included brem correction
- make_dimuon_base : basic maker for dimuon combination
- make_mass_constrained_jpsi2mumu : JpsiToMuMu builder using dimuon_base
- make_phi2kk : basic builder for PhiToKK
- _make_{long, down, up}_for_V0 : basic builder base for V0 combination basic particles
- make_{long, down, up}_{pions, kaons, protons}_for_V0 : builders of particles for V0
- _make_V0{LL, DD, LD_UL} : basic builder for V0 particles
- make_Ks{LL, DD, LD, UL} : builder of KS for LL, DD, LD, UL combinations
- make_Ks{LL, DD}_fromSV : builder for KS originating from SV
- make_Lambda{LL, DD} : builder for Lambda0, LL and DD combination
- make_LambdaTT : builder for Lambda0, TT combination, using MVA filtered T tracks, uses a single RK extrapolation in first iteration of vertex fit
- make_LambdaTT_gated : builder for Lambda0, TT combination, to go after a rare process in the control flow, uses a single RK extrapolation in first iteration of vertex fit
- make_KsTT : builder for KS, TT combination, using MVA filtered T tracks, uses a single RK extrapolation in first iteration of vertex fit
- make_KsTT_gated : builder for KS, TT combination, to go after a rare process in the control flow, uses a single RK extrapolation in first iteration of vertex fit


TO DO:
- port LoKi-based track selectors to a proper ThOr-based track selectors

"""
from GaudiKernel.SystemOfUnits import GeV, MeV, mm, meter, picosecond

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

masses = {
    'pi0': 134.9768 *
    MeV,  # +/- 0.0005 PDG, P.A. Zyla et al. (Particle Data Group), Prog. Theor. Exp. Phys. 2020, 083C01 (2020) and 2021 update.
    'KS0': 497.611 * MeV,  # +/- 0.013, PDG, PR D98, 030001 and 2019 update
    'Lambda0':
    1115.683 * MeV,  # +/- 0.006, PDG, PR D98, 030001 and 2019 update
    'J/psi(1S)': 3096.900 *
    MeV,  # +/- 0.006 PDG, P.A. Zyla et al. (Particle Data Group), Prog. Theor. Exp. Phys. 2020, 083C01 (2020) and 2021 update.
}


# Basic tracks selectors
[docs]@configurable def get_all_track_selector(Code=F.ALL): return Code
[docs]@configurable def get_long_track_selector(Code=None): if Code: return F.require_all(F.TRACKISLONG, Code) return F.TRACKISLONG
[docs]@configurable def get_down_track_selector(Code=None): if Code: return F.require_all(F.TRACKISDOWNSTREAM, Code) return F.TRACKISDOWNSTREAM
[docs]@configurable def get_upstream_track_selector(Code=None): if Code: return F.require_all(F.TRACKISUPSTREAM, Code) return F.TRACKISUPSTREAM
[docs]@configurable def get_ttrack_track_selector(Code=None): if Code: return F.require_all(F.TRACKISTTRACK, Code) return F.TRACKISTTRACK
[docs]@configurable def standard_protoparticle_filter(Code=F.ALL): return Code
@configurable def _make_particles(species, make_protoparticles=_make_charged_protoparticles, track_type='Charged', get_track_selector=get_all_track_selector, make_protoparticle_filter=standard_protoparticle_filter): """ creates LHCb::Particles from LHCb::ProtoParticles """ tp = get_track_selector() pp = make_protoparticle_filter() pp_maker = make_protoparticles( track_type=track_type ) if track_type != 'Charged' else make_protoparticles() particles = FunctionalParticleMaker( InputProtoParticles=pp_maker, ParticleID=species, TrackPredicate=tp, ProtoParticlePredicate=pp, PrimaryVertices=_make_pvs()).Particles return particles # ChargedBasics @configurable def _make_ChargedBasics( species, make_protoparticles=_make_charged_protoparticles, track_type='Charged', get_track_selector=get_all_track_selector, make_protoparticle_filter=standard_protoparticle_filter): """ creates LHCb::v2::ChargedBasics from LHCb::ProtoParticles """ pp_maker = make_protoparticles( track_type=track_type ) if track_type != 'Charged' else make_protoparticles() particles = Proto2ChargedBasic( InputUniqueIDGenerator=make_unique_id_generator(), InputProtoParticles=pp_maker, ParticleID=species, TrackPredicate=get_track_selector(), ProtoParticlePredicate=make_protoparticle_filter(), ).Particles return particles @configurable def _make_long_ChargedBasics(species): return _make_ChargedBasics( species=species, track_type='Long', make_protoparticle_filter=standard_protoparticle_filter) # Basic particles (e/mu/pi/K/p) per track type (long/down/up)
[docs]def make_long_cb_electrons(): return _make_long_ChargedBasics('electron')
[docs]def make_long_cb_muons(): return _make_long_ChargedBasics('muon')
[docs]def make_long_cb_pions(): return _make_long_ChargedBasics('pion')
[docs]def make_long_cb_kaons(): return _make_long_ChargedBasics('kaon')
[docs]def make_long_cb_protons(): return _make_long_ChargedBasics('proton')
[docs]def make_has_rich_long_cb_kaons(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_long_cb_kaons()
[docs]def make_has_rich_long_cb_pions(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_long_cb_pions()
[docs]@configurable def make_photons(make_neutral_protoparticles=_make_neutral_protoparticles, pv_maker=_make_pvs, **kwargs): """ creates photon LHCb::Particles from LHCb::ProtoParticles (PVs are optional) """ pvs = pv_maker() particles = PhotonMaker( InputProtoParticles=make_neutral_protoparticles(), InputPrimaryVertices=pvs, **kwargs).Particles return particles
#Longstream particles
[docs]def make_long_electrons_no_brem(): return _make_particles( species="electron", track_type='Long', make_protoparticle_filter=standard_protoparticle_filter)
[docs]@configurable def make_long_electrons_with_brem(bremadder='SelectiveBremAdder'): """Make long electrons adding bremsstrahlung correction to the track momentum. No extra cuts are applied. """ return ParticleWithBremMaker( InputParticles=make_long_electrons_no_brem(), BremAdder=bremadder, ).Particles
[docs]def make_long_pions(): return _make_particles( species="pion", track_type='Long', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_long_kaons(): return _make_particles( species="kaon", track_type='Long', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_long_protons(): return _make_particles( species="proton", track_type='Long', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_long_muons(): return _make_particles( species="muon", track_type='Long', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_long_sigmaps(): return _make_particles( species="sigmap", track_type='Long', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_long_sigmams(): return _make_particles( species="sigmam", track_type='Long', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_long_xis(): return _make_particles( species="xim", track_type='Long', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_long_omegas(): return _make_particles( species="omegam", track_type='Long', make_protoparticle_filter=standard_protoparticle_filter)
# Make muons with ismuon
[docs]@configurable def make_ismuon_long_muon(): with standard_protoparticle_filter.bind(Code=F.ISMUON): return make_long_muons()
#Upstream particles
[docs]def make_up_electrons_no_brem(): return _make_particles( species="electron", track_type='Upstream', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_up_pions(): return _make_particles( species="pion", track_type='Upstream', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_up_kaons(): return _make_particles( species="kaon", track_type='Upstream', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_up_protons(): return _make_particles( species="proton", track_type='Upstream', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_up_muons(): return _make_particles( species="muon", track_type='Upstream', make_protoparticle_filter=standard_protoparticle_filter)
#Downstream particles
[docs]def make_down_pions(): return _make_particles( species="pion", track_type='Downstream', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_down_kaons(): return _make_particles( species="kaon", track_type='Downstream', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_down_protons(): return _make_particles( species="proton", track_type='Downstream', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_down_electrons(): return _make_particles( species="electron", track_type='Downstream', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_down_electrons_no_brem(): return _make_particles( species="electron", track_type='Downstream', make_protoparticle_filter=standard_protoparticle_filter)
[docs]def make_down_muons(): return _make_particles( species="muon", track_type='Downstream', make_protoparticle_filter=standard_protoparticle_filter)
# Ttrack particles
[docs]@configurable def make_ttrack_pions(make_protoparticles=make_ttrack_protoparticles): return _make_particles( species="pion", make_protoparticles=make_protoparticles, make_protoparticle_filter=standard_protoparticle_filter)
[docs]@configurable def make_ttrack_protons(make_protoparticles=make_ttrack_protoparticles): return _make_particles( species="proton", make_protoparticles=make_protoparticles, make_protoparticle_filter=standard_protoparticle_filter)
[docs]@configurable def make_ttrack_muons(make_protoparticles=make_ttrack_protoparticles): return _make_particles( species="muon", make_protoparticles=make_protoparticles, make_protoparticle_filter=standard_protoparticle_filter)
[docs]@configurable def make_ttrack_kaons(make_protoparticles=make_ttrack_protoparticles): return _make_particles( species="kaon", make_protoparticles=make_protoparticles, make_protoparticle_filter=standard_protoparticle_filter)
# HASRICH definitions of pions/K: long, down, up # Make long, down, up pions
[docs]@configurable def make_has_rich_long_pions(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_long_pions()
[docs]@configurable def make_has_rich_down_pions(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_down_pions()
[docs]@configurable def make_has_rich_up_pions(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_up_pions()
# Make long, down, up kaons
[docs]@configurable def make_has_rich_long_kaons(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_long_kaons()
[docs]@configurable def make_has_rich_down_kaons(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_down_kaons()
[docs]@configurable def make_has_rich_up_kaons(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_up_kaons()
# Make long, down, up protons
[docs]@configurable def make_has_rich_long_protons(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_long_protons()
[docs]@configurable def make_has_rich_down_protons(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_down_protons()
[docs]@configurable def make_has_rich_up_protons(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_up_protons()
# Ttrack particles
[docs]def make_has_rich_ttrack_pions(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_ttrack_pions()
[docs]def make_has_rich_ttrack_protons(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_ttrack_protons()
[docs]def make_has_rich_ttrack_muons(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_ttrack_muons()
[docs]def make_has_rich_ttrack_kaons(): with standard_protoparticle_filter.bind(Code=F.PPHASRICH): return make_ttrack_kaons()
## MVA filtered Ttracks
[docs]def make_mva_ttrack_pions(): return make_ttrack_pions( make_protoparticles=make_ttrack_MVAfiltered_protoparticles)
[docs]def make_mva_ttrack_protons(): return make_ttrack_protons( make_protoparticles=make_ttrack_MVAfiltered_protoparticles)
[docs]def make_mva_ttrack_muons(): return make_ttrack_muons( make_protoparticles=make_ttrack_MVAfiltered_protoparticles)
[docs]def make_mva_ttrack_kaons(): return make_ttrack_kaons( make_protoparticles=make_ttrack_MVAfiltered_protoparticles)
# Definitions of pi0
[docs]@configurable def make_resolved_pi0s(particles=make_photons, nominal_mass=masses['pi0'], mass_window=30. * MeV, PtCut=0. * MeV, **kwargs): combination_code = in_range(nominal_mass - mass_window, F.MASS, nominal_mass + mass_window) vertex_code = F.require_all(F.PT > PtCut) return ParticleCombiner( Inputs=[particles(**kwargs), particles(**kwargs)], DecayDescriptor="pi0 -> gamma gamma", CombinationCut=combination_code, CompositeCut=vertex_code, ParticleCombiner="ParticleAdder")
[docs]@configurable def make_merged_pi0s(mass_window=60. * MeV, PtCut=2000. * MeV, make_neutral_protoparticles=_make_neutral_protoparticles, pvs=_make_pvs, **kwargs): particles = MergedPi0Maker( InputProtoParticles=make_neutral_protoparticles(), InputPrimaryVertices=pvs(), MassWindow=mass_window, PtCut=PtCut, **kwargs).Particles return particles
[docs]@configurable def filter_leptons_loose(particles, lepton, pv_maker=_make_pvs, fake=False, pt_min=500 * MeV, p_min=0. * MeV, probnn_e=None, probnn_mu=None, pid_e=-999, pid_mu=None, ismuon=False, minipchi2=-999, trghostprob=None): """Returns loosely preselected leptons """ assert lepton in ("electron", "muon"), Exception( 'in HLT2Conf.standard_particles.filter_leptons_loose need specify leptons (str) as either "muon" or "electron"' ) pvs = pv_maker() code = F.require_all(F.PT > pt_min, F.P > p_min, F.MINIPCHI2(pvs) > minipchi2) if fake: if trghostprob is not None: code = code & (F.GHOSTPROB > trghostprob) if lepton == 'muon': if probnn_mu is not None: code = code & (F.PROBNN_MU < probnn_mu) if pid_mu is not None: code = code & (F.PID_MU < pid_mu) if ismuon: code = code & (~F.ISMUON) elif lepton == 'electron': if pid_e is not None: code = code & (F.PID_E < pid_e) if probnn_e is not None: code = code & (F.PROBNN_E < probnn_e) else: if trghostprob is not None: code = code & (F.GHOSTPROB < trghostprob) if lepton == 'muon': if probnn_mu is not None: code = code & (F.PROBNN_MU > probnn_mu) if pid_mu is not None: code = code & (F.PID_MU > pid_mu) if ismuon: code = code & (F.ISMUON) elif lepton == 'electron': if pid_e is not None: code = code & (F.PID_E > pid_e) if probnn_e is not None: code = code & (F.PROBNN_E > probnn_e) return ParticleFilter(particles, F.FILTER(code))
#Basic dilepton builders
[docs]@configurable def make_detached_dielectron(name='std_make_detached_dielectron_{hash}', pv_maker=_make_pvs, opposite_sign=True, dilepton_ID='J/psi(1S)', electron_maker=make_long_electrons_no_brem, pid_e=2, probnn_e=None, pt_e=0.25 * GeV, minipchi2=9., trghostprob=None, adocachi2cut=30, bpvvdchi2=30, vfaspfchi2ndof=10): if opposite_sign: DecayDescriptor = f'{dilepton_ID} -> e+ e-' name = name + '_rs_{hash}' else: DecayDescriptor = f'[{dilepton_ID} -> e+ e+]cc' name = name + '_ws_{hash}' pvs = pv_maker() electrons = filter_leptons_loose( particles=electron_maker(), lepton='electron', pt_min=pt_e, pid_e=pid_e, probnn_e=probnn_e, minipchi2=minipchi2, trghostprob=trghostprob) combination_code = F.require_all(F.MAXDOCACHI2CUT(float(adocachi2cut))) vertex_code = F.require_all(F.CHI2DOF < vfaspfchi2ndof, F.BPVFDCHI2(pvs) > bpvvdchi2) return ParticleCombiner( Inputs=[electrons, electrons], name=name, DecayDescriptor=DecayDescriptor, CombinationCut=combination_code, CompositeCut=vertex_code)
[docs]def FunctionalDiElectronMaker(InputParticles, **kwargs): return FunctionalDiElectronMakerAlg( InputParticles=InputParticles, PrimaryVertices=_make_pvs(), **kwargs)
@configurable def _make_dielectron_with_brem(electrons, pt_diE=0 * MeV, m_diE_min=0 * MeV, m_diE_max=6000 * MeV, m_diE_ID="J/psi(1S)", opposite_sign=True, bremadder='SelectiveBremAdder'): """Make Jpsi -> e+ e- or [Jpsi -> e+ e+ ]cc candidates adding bremsstrahlung correction to the electrons while ensuring the same photon is not used to correct both electrons. No PT and a very loose mass cut is applied to the dielectron initially. No cuts can be applied to the input electrons, but it is encouraged to filter them first, to reduce the combinatorics. See make_detached_dielectron_with_brem for a usage example. """ return FunctionalDiElectronMaker( InputParticles=electrons, MinDiElecPT=pt_diE, MinDiElecMass=m_diE_min, MaxDiElecMass=m_diE_max, DiElecID=m_diE_ID, OppositeSign=opposite_sign, BremAdder=bremadder).Particles
[docs]@configurable def make_prompt_dielectron_with_brem( electron_maker=make_long_electrons_no_brem, opposite_sign=True, pv_maker=_make_pvs, PIDe_min=2., probnn_e=None, pt_e=0.25 * GeV, p_e=0. * GeV, maxipchi2=9., trghostprob=0.25, dielectron_ID="J/psi(1S)", pt_diE=0 * MeV, m_diE_min=0 * MeV, m_diE_max=6000 * MeV, adocachi2cut=30, bpvvdchi2=30, vfaspfchi2ndof=10): """Make prompt Jpsi -> e+ e- or [Jpsi -> e+ e+ ]cc candidates adding bremsstrahlung correction to the electrons. The selection follows make_detached_dielectron_with_brem but the dielectron combination is built through _make_dielectron_with_brem, providing an example of the usage of this private method. """ pvs = pv_maker() particles = electron_maker() code_e = F.require_all(F.PT > pt_e, F.P > p_e, F.MINIPCHI2(pvs) < maxipchi2, F.GHOSTPROB < trghostprob) if PIDe_min is not None: code_e = code_e & (F.PID_E > PIDe_min) if probnn_e is not None: code_e = code_e & (F.PROBNN_E > probnn_e) prompt_e = ParticleFilter(particles, F.FILTER(code_e)) prompt_dielectron_with_brem = _make_dielectron_with_brem( prompt_e, pt_diE=pt_diE, m_diE_min=m_diE_min, m_diE_max=m_diE_max, m_diE_ID=dielectron_ID, opposite_sign=opposite_sign) code_dielectron = F.require_all( F.MAXDOCACHI2CUT(float(adocachi2cut)), F.CHI2DOF < vfaspfchi2ndof) if bpvvdchi2 is not None: code_dielectron = code_dielectron & (F.BPVFDCHI2(pvs) < bpvvdchi2) return ParticleFilter(prompt_dielectron_with_brem, F.FILTER(code_dielectron))
[docs]@configurable def make_detached_dielectron_with_brem( electron_maker=make_long_electrons_no_brem, opposite_sign=True, fake_electrons=0, pv_maker=_make_pvs, PIDe_min=2., probnn_e=None, pt_e=0.25 * GeV, p_e=0. * GeV, minipchi2=9., trghostprob=None, dielectron_ID="J/psi(1S)", pt_diE=0 * MeV, m_diE_min=0 * MeV, m_diE_max=6000 * MeV, adocachi2cut=30, bpvvdchi2=30, vfaspfchi2ndof=10): """Make detached Jpsi -> e+ e- or [Jpsi -> e+ e+ ]cc candidates adding bremsstrahlung correction to the electrons. The selection follows make_detached_dielectron but the dielectron combination is built through _make_dielectron_with_brem, providing an example of the usage of this private method. """ assert type(fake_electrons) == int and 0 <= fake_electrons <= 2, Exception( "Invalid number of fake electrons in builder, fake_electrons must be an integer between 0 and 2." ) pvs = pv_maker() electrons = filter_leptons_loose( particles=electron_maker(), lepton='electron', fake=(fake_electrons == 2), pt_min=pt_e, pid_e=PIDe_min if fake_electrons != 1 else -999, probnn_e=probnn_e if fake_electrons != 1 else None, minipchi2=minipchi2, trghostprob=trghostprob if fake_electrons != 1 else None) detached_dielectron_with_brem = _make_dielectron_with_brem( electrons, pt_diE=pt_diE, m_diE_min=m_diE_min, m_diE_max=m_diE_max, m_diE_ID=dielectron_ID, opposite_sign=opposite_sign) code_dielectron = F.require_all( F.MAXDOCACHI2CUT(float(adocachi2cut)), F.CHI2DOF < vfaspfchi2ndof, F.BPVFDCHI2(pvs) > bpvvdchi2) if fake_electrons == 1: if trghostprob is not None: code_dielectron = code_dielectron & (F.CHILD( 1, F.GHOSTPROB) > trghostprob) & (F.CHILD(2, F.GHOSTPROB) < trghostprob) if PIDe_min is not None: code_dielectron = code_dielectron & (F.CHILD( 1, F.PID_E) < PIDe_min) & (F.CHILD(2, F.PID_E) > PIDe_min) if probnn_e is not None: code_dielectron = code_dielectron & (F.CHILD(1, F.PROBNN_E) < probnn_e) & (F.CHILD( 2, F.PROBNN_E) > probnn_e) return ParticleFilter(detached_dielectron_with_brem, F.FILTER(code_dielectron))
[docs]@configurable def make_detached_mue(name='std_make_detached_mue_{hash}', opposite_sign=True, dilepton_ID='J/psi(1S)', fake_electrons=0, fake_muons=0, pv_maker=_make_pvs, pid_mu=0.2, probnn_mu=None, pt_mu=0. * GeV, pid_e=2., probnn_e=None, pt_e=0.25 * GeV, minipchi2=9., trghostprob=None, adocachi2cut=30, bpvvdchi2=30, vfaspfchi2ndof=10): assert type(fake_electrons) == int and 0 <= fake_electrons <= 1, Exception( "Invalid number of fake electrons in builder, fake_electrons must be an integer between 0 and 1." ) assert type(fake_muons) == int and 0 <= fake_muons <= 1, Exception( "Invalid number of fake muons in builder, fake_muons must be an integer between 0 and 1." ) if opposite_sign: DecayDescriptor = f'[{dilepton_ID} -> mu+ e-]cc' name = name + '_rs' else: DecayDescriptor = f'[{dilepton_ID} -> mu+ e+]cc' name = name + '_ws' pvs = pv_maker() electrons = filter_leptons_loose( particles=make_long_electrons_no_brem(), lepton='electron', fake=bool(fake_electrons), pt_min=pt_e, pid_e=pid_e, probnn_e=probnn_e, minipchi2=minipchi2, trghostprob=trghostprob) muons = filter_leptons_loose( particles=make_long_muons(), lepton='muon', fake=bool(fake_muons), pt_min=pt_mu, pid_mu=pid_mu, probnn_mu=probnn_mu, minipchi2=minipchi2, trghostprob=trghostprob) combination_code = F.require_all(F.MAXDOCACHI2CUT(float(adocachi2cut))) vertex_code = F.require_all(F.CHI2DOF < vfaspfchi2ndof, F.BPVFDCHI2(pvs) > bpvvdchi2) return ParticleCombiner( Inputs=[muons, electrons], name=name, DecayDescriptor=DecayDescriptor, CombinationCut=combination_code, CompositeCut=vertex_code)
[docs]@configurable def make_detached_mue_with_brem(name='std_make_detached_mue_with_brem_{hash}', opposite_sign=True, dilepton_ID='J/psi(1S)', fake_electrons=0, fake_muons=0, pv_maker=_make_pvs, min_probnn_mu=None, min_PIDmu=0., IsMuon=False, min_pt_mu=0. * GeV, min_PIDe=2, probnn_e=None, min_pt_e=0.25 * GeV, minipchi2_track=9., max_trghostprob=None, max_adocachi2=30, min_bpvvdchi2=30, max_vchi2ndof=10): """Make detached J/psi(1S) -> mu e candidates with ismuon muons and adding bremsstrahlung correction to the electrons. The opposite_sign flag is a boolean that can be activated. """ assert type(fake_electrons) == int and 0 <= fake_electrons <= 1, Exception( "Invalid number of fake electrons in builder, fake_electrons must be an integer between 0 and 1." ) assert type(fake_muons) == int and 0 <= fake_muons <= 1, Exception( "Invalid number of fake muons in builder, fake_muons must be an integer between 0 and 1." ) if opposite_sign: DecayDescriptor = f'[{dilepton_ID} -> mu+ e-]cc' name = name + '_rs_{hash}' else: DecayDescriptor = f'[{dilepton_ID} -> mu+ e+]cc' name = name + '_ws_{hash}' pvs = pv_maker() electrons = filter_leptons_loose( particles=make_long_electrons_no_brem(), lepton='electron', fake=bool(fake_electrons), pt_min=min_pt_e, pid_e=min_PIDe, probnn_e=probnn_e, minipchi2=minipchi2_track, trghostprob=max_trghostprob) muons = filter_leptons_loose( particles=make_ismuon_long_muon(), fake=bool(fake_muons), lepton='muon', pt_min=min_pt_mu, probnn_mu=min_probnn_mu, pid_mu=min_PIDmu, ismuon=IsMuon, minipchi2=minipchi2_track, trghostprob=max_trghostprob) combination_code = F.require_all(F.MAXDOCACHI2CUT(float(max_adocachi2))) vertex_code = F.require_all(F.CHI2DOF < max_vchi2ndof, F.BPVFDCHI2(pvs) > min_bpvvdchi2) return ParticleCombiner( Inputs=[muons, electrons], name=name, DecayDescriptor=DecayDescriptor, CombinationCut=combination_code, CompositeCut=vertex_code)
[docs]@configurable def make_detached_mumu(name='std_make_detached_mumu_{hash}', opposite_sign=True, fake_muons=0, dilepton_ID='J/psi(1S)', pv_maker=_make_pvs, pid_mu=2, IsMuon=False, probnn_mu=None, pt_mu=0. * GeV, p_mu=0. * GeV, minipchi2=9., trghostprob=None, adocachi2cut=30, bpvvdchi2=30, vfaspfchi2ndof=10): #def make_detached_mumu(probnn_mu=-0.2, pt_mu=0.*GeV, minipchi2=0., trghostprob=0.925, adocachi2cut=30, bpvvdchi2=30, vfaspfchi2ndof=10): assert type(fake_muons) == int and 0 <= fake_muons <= 2, Exception( "Invalid number of fake muons in builder, fake_muons must be an integer between 0 and 2." ) if opposite_sign: DecayDescriptor = f'{dilepton_ID} -> mu+ mu-' name = name + '_rs' else: DecayDescriptor = f'[{dilepton_ID} -> mu+ mu+]cc' name = name + '_ws' pvs = pv_maker() if fake_muons == 1: particles = [ filter_leptons_loose( particles=make_long_muons(), lepton='muon', fake=fake, pt_min=pt_mu, p_min=p_mu, pid_mu=pid_mu, ismuon=IsMuon, probnn_mu=probnn_mu, minipchi2=minipchi2, trghostprob=trghostprob) for fake in (True, False) ] else: muons = filter_leptons_loose( particles=make_long_muons(), lepton='muon', fake=bool(fake_muons), pt_min=pt_mu, p_min=p_mu, pid_mu=pid_mu, ismuon=IsMuon, probnn_mu=probnn_mu, minipchi2=minipchi2, trghostprob=trghostprob) particles = [muons, muons] combination_code = F.require_all(F.MAXDOCACHI2CUT(float(adocachi2cut))) vertex_code = F.require_all(F.CHI2DOF < vfaspfchi2ndof, F.BPVFDCHI2(pvs) > bpvvdchi2) return ParticleCombiner( Inputs=particles, name=name, AllowDiffInputsForSameIDChildren=True, DecayDescriptor=DecayDescriptor, CombinationCut=combination_code, CompositeCut=vertex_code)
[docs]@configurable def make_dimuon_base(name='std_DiMuonBaseCombiner_{hash}', maxVCHI2PDOF=25, pt=None, pid_mu=None, probnn_mu=None, adoca_chi2=None): """Basic dimuon without any requirements but common vertex Please DO NOT add pt requirements here: a dedicated (tighter) dimuon filter is implemented in the dimuon module. """ # get the long muons muons = filter_leptons_loose( particles=make_ismuon_long_muon(), lepton='muon', pt_min=pt if pt else 0 * MeV, pid_mu=pid_mu, probnn_mu=probnn_mu) # require that the muons come from the same vertex combination_code = F.ALL if adoca_chi2 is None else F.MAXDOCACHI2CUT( float(adoca_chi2)) vertex_code = F.require_all(F.CHI2DOF < maxVCHI2PDOF) return ParticleCombiner( Inputs=[muons, muons], name=name, DecayDescriptor='J/psi(1S) -> mu+ mu-', CombinationCut=combination_code, CompositeCut=vertex_code)
[docs]@configurable def make_mass_constrained_jpsi2mumu(name='std_MassConstrJpsi2MuMuMaker_{hash}', jpsi_maker=make_dimuon_base, nominal_mass=masses['J/psi(1S)'], pid_mu=0, probnn_mu=None, pt_mu=0.5 * GeV, admass=250. * MeV, adoca_chi2=20, vchi2=16): """Make the Jpsi, starting from dimuons""" # get the dimuons with basic cuts (only vertexing) # note that the make_dimuon_base combiner uses vertexChi2/ndof < 25, # which is looser than the vertexChi2 < 16 required here dimuons = jpsi_maker( pt=pt_mu, pid_mu=pid_mu, probnn_mu=probnn_mu, adoca_chi2=adoca_chi2) code = F.require_all( in_range(nominal_mass - admass, F.MASS, nominal_mass + admass), F.CHI2 < vchi2) return ParticleFilter(dimuons, F.FILTER(code))
[docs]@configurable def make_phi2kk(pv_maker=_make_pvs, am_max=1060. * MeV, asumpt_min=500 * MeV, kaon_p_min=2500 * MeV, kaon_pt_min=250 * MeV, kaon_pidk_min=-5, adoca12_max=0.3 * mm, vchi2=25.0, mipchi2_min=None, name='std_make_phi2kk_{hash}'): pvs = pv_maker() kaons = ParticleFilter( make_has_rich_long_kaons(), F.FILTER( F.require_all(F.P > kaon_p_min, F.PT > kaon_pt_min, F.PID_K > kaon_pidk_min))) descriptors = 'phi(1020) -> K+ K-' combination_code = F.require_all(F.MASS < 1.1 * am_max, F.SUM(F.PT) > asumpt_min, F.DOCA(1, 2) < adoca12_max) vertex_code = F.require_all(F.MASS < am_max, F.CHI2 < vchi2) if mipchi2_min is not None: vertex_code &= F.MINIPCHI2(pvs) > mipchi2_min return ParticleCombiner( Inputs=[kaons, kaons], name=name, DecayDescriptor=descriptors, CombinationCut=combination_code, CompositeCut=vertex_code)
# Set of builders for V0 (KS0/L0) particles including filtering of daughters # Make V0s -- All lines using V0 particles must filter on PVs ! def _make_long_for_V0(particles, pvs): code = F.require_all(F.MINIPCHI2(pvs) > 36) return ParticleFilter(particles, F.FILTER(code)) def _make_down_for_V0(particles): code = F.require_all(F.P > 3000 * MeV, F.PT > 175 * MeV) return ParticleFilter(particles, F.FILTER(code)) def _make_up_for_V0(particles): code = F.require_all(F.P > 1000 * MeV, F.PT > 175 * MeV) return ParticleFilter(particles, F.FILTER(code))
[docs]def make_long_pions_for_V0(): return _make_long_for_V0(make_long_pions(), _make_pvs())
[docs]def make_long_protons_for_V0(): return _make_long_for_V0(make_long_protons(), _make_pvs())
[docs]def make_down_pions_for_V0(): return _make_down_for_V0(make_down_pions())
[docs]def make_down_kaons_for_V0(): return _make_down_for_V0(make_down_kaons())
[docs]def make_down_protons_for_V0(): return _make_down_for_V0(make_down_protons())
[docs]def make_up_pions_for_V0(): return _make_up_for_V0(make_up_pions())
[docs]def make_up_kaons_for_V0(): return _make_up_for_V0(make_up_kaons())
[docs]def make_up_protons_for_V0(): return _make_up_for_V0(make_up_protons())
# All lines using V0 particles must filter on PVs ! @configurable def _make_V0LL(particles, descriptors, pv_maker=_make_pvs, name='std_make_V0LL_{hash}', nominal_mass=masses['KS0'], am_dmass=50 * MeV, m_dmass=35 * MeV, vchi2pdof_max=30, bpvltime_min=2.0 * picosecond): """Make long-long V0 -> h+ h'- candidates Initial implementation a replication of the old Hlt2SharedParticles """ pvs = pv_maker() combination_code = F.require_all( in_range(nominal_mass - am_dmass, F.MASS, nominal_mass + am_dmass)) if bpvltime_min is not None: vertex_code = F.require_all( in_range(nominal_mass - am_dmass, F.MASS, nominal_mass + am_dmass), F.CHI2DOF < vchi2pdof_max, F.BPVLTIME(pvs) > bpvltime_min) else: vertex_code = F.require_all( in_range(nominal_mass - m_dmass, F.MASS, nominal_mass + m_dmass), F.CHI2DOF < vchi2pdof_max) return ParticleCombiner( Inputs=particles, DecayDescriptor=descriptors, name=name, CombinationCut=combination_code, CompositeCut=vertex_code) @configurable def _make_V0DD(particles, descriptors, pv_maker=_make_pvs, name='std_make_V0DD_{hash}', nominal_mass=masses['KS0'], am_dmass=80 * MeV, m_dmass=64 * MeV, vchi2pdof_max=30, bpvvdz_min=400 * mm): """Make down-down V0 -> h+ h'- candidates Initial implementation a replication of the old Hlt2SharedParticles """ pvs = pv_maker() combination_code = F.require_all( in_range(nominal_mass - am_dmass, F.MASS, nominal_mass + am_dmass)) vertex_code = F.require_all( in_range(nominal_mass - m_dmass, F.MASS, nominal_mass + m_dmass), F.CHI2DOF < vchi2pdof_max, F.BPVVDZ(pvs) > bpvvdz_min) return ParticleCombiner( Inputs=particles, DecayDescriptor=descriptors, name=name, CombinationCut=combination_code, CompositeCut=vertex_code) @configurable def _make_V0LD_UL(particles, descriptors, pv_maker=_make_pvs, name='std_make_V0LD_{hash}', nominal_mass=masses['KS0'], am_dmass=80 * MeV, m_dmass=64 * MeV, vchi2pdof_max=30): """Make long-down or long-up V0 -> h+ h'- candidates Initial implementation a replication of the _make_V0DD function without the BPVVDZ() requirement """ combination_code = in_range(nominal_mass - am_dmass, F.MASS, nominal_mass + am_dmass) vertex_code = F.require_all( in_range(nominal_mass - m_dmass, F.MASS, nominal_mass + m_dmass), F.CHI2DOF < vchi2pdof_max) return ParticleCombiner( Inputs=particles, DecayDescriptor=descriptors, name=name, CombinationCut=combination_code, CompositeCut=vertex_code)
[docs]def make_KsLL(pions_long=None): if pions_long is None: pions_long = make_long_pions_for_V0() descriptors = "KS0 -> pi+ pi-" return _make_V0LL( particles=[pions_long, pions_long], descriptors=descriptors, pv_maker=_make_pvs)
[docs]def make_KsDD(pions_down=None): if pions_down is None: pions_down = make_down_pions_for_V0() descriptors = "KS0 -> pi+ pi-" return _make_V0DD( particles=[pions_down, pions_down], descriptors=descriptors, pv_maker=_make_pvs)
[docs]def make_KsLD(pions_down=None, pions_long=None): if pions_down is None: pions_down = make_down_pions_for_V0() if pions_long is None: pions_long = make_long_pions_for_V0() descriptors = "KS0 -> pi+ pi-" return _make_V0LD_UL( particles=[pions_long, pions_down], descriptors=descriptors, pv_maker=_make_pvs)
[docs]def make_KsUL(pions_long=None, pions_up=None): if pions_up is None: pions_up = make_up_pions_for_V0() if pions_long is None: pions_long = make_long_pions_for_V0() descriptors = "KS0 -> pi+ pi-" return _make_V0LD_UL( particles=[pions_long, pions_up], descriptors=descriptors, pv_maker=_make_pvs)
[docs]def make_KsLL_fromSV(): pions_long = make_long_pions_for_V0() descriptors = "KS0 -> pi+ pi-" return _make_V0LL( particles=[pions_long, pions_long], descriptors=descriptors, pv_maker=_make_pvs, vchi2pdof_max=25.0, bpvltime_min=None)
[docs]def make_KsDD_fromSV(): pions_down = make_down_pions_for_V0() descriptors = "KS0 -> pi+ pi-" return _make_V0DD( particles=[pions_down, pions_down], descriptors=descriptors, pv_maker=_make_pvs, vchi2pdof_max=25.0)
[docs]def make_LambdaLL(): pions = make_long_pions_for_V0() protons = make_long_protons_for_V0() descriptors = "[Lambda0 -> p+ pi-]cc" return _make_V0LL( particles=[protons, pions], descriptors=descriptors, pv_maker=_make_pvs, nominal_mass=masses['Lambda0'], am_dmass=50 * MeV, m_dmass=20 * MeV, vchi2pdof_max=30, bpvltime_min=2.0 * picosecond)
[docs]@configurable def make_LambdaDD(): pions = make_down_pions_for_V0() protons = make_down_protons_for_V0() descriptors = "[Lambda0 -> p+ pi-]cc" return _make_V0DD( particles=[protons, pions], descriptors=descriptors, pv_maker=_make_pvs, nominal_mass=masses['Lambda0'], am_dmass=80 * MeV, m_dmass=24 * MeV, vchi2pdof_max=30, bpvvdz_min=400 * mm)
## T track builders for V0 particles, to reconstruct decays in the magnet region. Need to take into account the increased extrapolation time and reduced mass resolution. from RecoConf.ttrack_selections_reco import PVF_with_single_extrapolation
[docs]@configurable def make_ttrack_protons_for_V0(make_protons=make_ttrack_protons, make_pvs=_make_pvs, p_min=20. * GeV, pt_min=400. * MeV, minip_max=400., minipchi2_max=30000., minipchi2_min=0.): pvs = make_pvs() protons = make_protons() proton_cuts = F.require_all( F.P > p_min, F.PT > pt_min, in_range(2., F.ETA, 5.), # F.GHOSTPROB < 0.4, F.MINIP(pvs) < minip_max, in_range(minipchi2_min, F.MINIPCHI2(pvs), minipchi2_max)) return ParticleFilter(protons, F.FILTER(proton_cuts))
[docs]@configurable def make_ttrack_pions_for_V0(make_pions=make_ttrack_pions, make_pvs=_make_pvs, pt_min=75. * MeV, p_min=0. * GeV, minipchi2_min=0.): pvs = make_pvs() pions = make_pions() pion_cuts = F.require_all( F.PT > pt_min, F.P > p_min, # F.ECALPIDE < 1., # electron veto in_range(2., F.ETA, 5.), # F.GHOSTPROB < 0.4, F.MINIPCHI2(pvs) > minipchi2_min, ) return ParticleFilter(pions, F.FILTER(pion_cuts))
[docs]def make_mva_filtered_ttrack_pions_for_V0(): return make_ttrack_pions_for_V0(make_pions=make_mva_ttrack_pions)
[docs]def make_mva_filtered_ttrack_protons_for_V0(): return make_ttrack_protons_for_V0(make_protons=make_mva_ttrack_protons)
@configurable def _make_V0TT(particles, descriptors, make_pvs=_make_pvs, yz_intersection_z_min=1000., maxdoca=50., maxdocachi2=300., vchi2pdof_max=50, p_min=10 * GeV, pt_min=1 * GeV, max_chi2=150, vertex_z_min=2.5 * meter, vertex_z_max=8. * meter, bpv_dira_min=0.9995, bpv_ip_max=100., bpv_ip_chi2_max=200., bpv_vdrho_min=80., name="std_make_V0TT_{hash}"): """ Make T-T V0 -> h+ h'- candidates. It does not make sense to cut on combination mass, since the first measurement can be metres away from decay position. """ pvs = make_pvs() combination_code = F.require_all( F.TWOBODY_YZ_INTERSECTION_Z > yz_intersection_z_min, # this reduces the number of combinations originating from the interaction point F.TWOBODY_TILT("Y") > 0., # essentially a cut that limits the size of the opening angle in y F.CHILD(1, F.math.sign(F.TY)) * F.math.sign( F.POD(F.TWOBODY_YZ_INTERSECTION_Y)) > 0, F.MAXSDOCACUT(maxdoca), F.MAXSDOCACHI2CUT(maxdocachi2) ) # DOCA does not take into account the B field so behaviour for T tracks is different to Long and Down and simpler SDOCA gives same results as DOCA vertex_code = F.require_all(F.P > p_min, F.PT > pt_min, F.CHI2 < max_chi2, F.BPVDIRA(pvs) > bpv_dira_min, F.BPVIP(pvs) < bpv_ip_max, F.BPVIPCHI2(pvs) < bpv_ip_chi2_max, F.BPVVDRHO(pvs) > bpv_vdrho_min, in_range(vertex_z_min, F.END_VZ, vertex_z_max)) return ParticleCombiner( Inputs=particles, DecayDescriptor=descriptors, CombinationCut=combination_code, CompositeCut=vertex_code, ParticleCombiner=PVF_with_single_extrapolation(), name=name, )
[docs]def make_LambdaTT(): ''' Takes as input MVA filtered T track protons and pions, to reduce reconstruction time, and combinatorics (to control extrapolation time in the vertex fit). This should be used as standard if the control flow cannot be configured with a clear, infrequent process to control timing. Applies the same selections as for the "gated" combiner, but uses different inputs ''' pions = make_mva_filtered_ttrack_pions_for_V0() protons = make_mva_filtered_ttrack_protons_for_V0() descriptors = "[Lambda0 -> p+ pi-]cc" return _make_V0TT( particles=[protons, pions], descriptors=descriptors, name="std_make_Lambda0TT_{hash}")
[docs]def make_KsTT(): ''' Takes as input MVA filtered T track protons and pions, to reduce reconstruction time, and combinatorics (to control extrapolation time in the vertex fit). This should be used as standard if the control flow cannot be configured with a clear, infrequent process to control timing. Applies the same selections as for the "gated" combiner, but uses different inputs ''' pions = make_mva_filtered_ttrack_pions_for_V0() descriptors = "KS0 -> pi+ pi-" return _make_V0TT( name="std_make_KsTT_{hash}", particles=[pions, pions], descriptors=descriptors, pt_min=1000 * MeV, bpv_vdrho_min=150., p_min=2 * GeV)
[docs]def make_LambdaTT_gated(): ''' Make Lambda -> p pi (TT) candidates with a Runge-Kutta (RK) extrapolation in the first iteration only of vertex fit, linear extrapolation in subsequent iterations. To be used when there is a clear, infrequent signature prior in the control flow. Using the RK extrapolator once improves the vertex bias and mass bias compared to linear extrapolator, but mass resolution is still poor. This improves efficiency in the first half of the magnet wrt purely linear extrapolation. ''' pions = make_ttrack_pions_for_V0() protons = make_ttrack_protons_for_V0() descriptors = "[Lambda0 -> p+ pi-]cc" return _make_V0TT( particles=[protons, pions], descriptors=descriptors, name="std_make_Lambda0TT_gated_{hash}")
[docs]def make_KsTT_gated(): ''' Make KS0 -> pi pi (TT) candidates with a Runge-Kutta (RK) extrapolation in the first iteration only of vertex fit, linear extrapolation in subsequent iterations. To be used when there is a clear, infrequent signature prior in the control flow. Using the RK extrapolator once improves the vertex bias and mass bias compared to linear extrapolator, but mass resolution is still poor. This improves efficiency in the first half of the magnet wrt purely linear extrapolation. ''' pions = make_ttrack_pions_for_V0() descriptors = "KS0 -> pi+ pi-" return _make_V0TT( name="std_make_KsTT_gated_{hash}", particles=[pions, pions], descriptors=descriptors, pt_min=1000 * MeV, bpv_vdrho_min=150., p_min=2 * GeV)