diff --git a/Hlt/RecoConf/python/RecoConf/offline_calibration/__init__.py b/Hlt/RecoConf/python/RecoConf/offline_calibration/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5f0dad8b69685ce81ac64a5d130030460d28bd1a --- /dev/null +++ b/Hlt/RecoConf/python/RecoConf/offline_calibration/__init__.py @@ -0,0 +1,34 @@ +############################################################################### +# (c) Copyright 2025 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 .combined_corrections import apply_corrections + +SIMULATION = "simulation" +SPRUCING_24c1 = "sprucing24c1" +SPRUCING_24c2 = "sprucing24c2" +SPRUCING_24c3 = "sprucing24c3" +SPRUCING_24c4 = "sprucing24c4" +SPRUCING_24c5 = "sprucing24c5" + + +def get_default_configuration(dataset): + if dataset == SPRUCING_24c2: + return { + "refit_tracks": True, + "update_pvs": True, + "apply_momentum_scaling": True, + } + else: + return { + "refit_tracks": False, + "apply_momentum_scaling": False, + "update_pvs": False, + } diff --git a/Hlt/RecoConf/python/RecoConf/offline_calibration/combined_corrections.py b/Hlt/RecoConf/python/RecoConf/offline_calibration/combined_corrections.py new file mode 100644 index 0000000000000000000000000000000000000000..3841ca9a8246f3ae0d1c2480655bf4c446aff2e4 --- /dev/null +++ b/Hlt/RecoConf/python/RecoConf/offline_calibration/combined_corrections.py @@ -0,0 +1,102 @@ +############################################################################### +# (c) Copyright 2025 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. # +############################################################################### + +import logging + +import Functors as F +from PyConf.Algorithms import ( + FlattenDecayTree, + LHCbIDOverlapRelationTableParticleToTrack, + PVsEmptyProducer, + RecombineDecayTrees, + SelectTracksForParticles, +) +from PyConf.reading import get_odin + +from RecoConf.algorithms_thor import ParticleFilter + +from .. import track_refitting +from . import momentum_scale_calibration + +log = logging.getLogger(__name__) + + +def apply_corrections( + input_particles, + input_pvs, + refit_tracks=True, + update_pvs=True, + refit_pvs=False, + apply_momentum_scaling=True, + update_ghost_prob=True, + disable_UT=False, + track_fitter=track_refitting.TRACK_FIT_TYPE_PRKALMAN, + odin=None, +): + # first, update the PVs if needed + if refit_pvs and not update_pvs: + log.warning("Asking to refit the PVs, but not update them.") + + if odin is None: + odin = get_odin() + + if input_pvs is None: + new_pvs = PVsEmptyProducer() + else: + if update_pvs and not refit_pvs: + new_pvs = track_refitting.update_vertex_positions( + input_pvs=input_pvs + ).OutputVertices + elif update_pvs and refit_pvs: + new_pvs = track_refitting.refit_pvs(input_pvs=input_pvs).OutputVertices + else: + new_pvs = input_pvs # don't update them at all + + # next, update the particles + flat_particles = FlattenDecayTree(InputParticles=input_particles) + basic_particles = ParticleFilter( + flat_particles.OutputParticles, + F.FILTER(F.ISBASICPARTICLE), # ideally swap to some F.HASTRACK functor + name="SelectBasicParticlesForOfflineCalib_{hash}", + ) + + tracks_from_dst = SelectTracksForParticles(Inputs=[basic_particles]).OutputTracks + + if refit_tracks: + tracks_from_dst = track_refitting.refit_tracks( + tracks_from_dst, + get_clusters_from_track=True, + track_fitter=track_fitter, + update_ghost_prob=update_ghost_prob, + disable_UT=disable_UT, + ) + + if apply_momentum_scaling: + tracks_from_dst = momentum_scale_calibration.scale_tracks( + input_tracks=tracks_from_dst, odin=odin + ).OutputTracks + + relation_table_match_by_veloid_to_track = LHCbIDOverlapRelationTableParticleToTrack( + MatchFrom=basic_particles, + MatchTo=tracks_from_dst, + IncludeVP=True, + IncludeFT=True, + IncludeUT=True, + ).OutputRelations + + recombined_trees = RecombineDecayTrees( + InputParticles=input_particles, + InputPVs=input_pvs, + InputTracks=tracks_from_dst, # the refitted tracks + InputTrackRelations=relation_table_match_by_veloid_to_track, + ) # the relation table + + return recombined_trees, new_pvs diff --git a/Hlt/RecoConf/python/RecoConf/offline_calibration/momentum_scale_calibration.py b/Hlt/RecoConf/python/RecoConf/offline_calibration/momentum_scale_calibration.py new file mode 100644 index 0000000000000000000000000000000000000000..bfe1893c0ebf85c4173b501acebc8fda2a39a200 --- /dev/null +++ b/Hlt/RecoConf/python/RecoConf/offline_calibration/momentum_scale_calibration.py @@ -0,0 +1,78 @@ +############################################################################### +# (c) Copyright 2025 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. # +############################################################################### +import Functors as F +from PyConf.Algorithms import ( + FlattenDecayTree, + LHCbIDOverlapRelationTableParticleToTrack, + LHCbIDOverlapRelationTableTrackToTrack, + PVsEmptyProducer, + RecombineDecayTrees, + SelectTracksForParticles, + TrackScaleState, +) + +from RecoConf.algorithms_thor import ParticleFilter + + +def scale_tracks(input_tracks, odin): + return TrackScaleState(InputTracks=input_tracks, ODIN=odin) + + +def scale_decay_tree( + input_particles, + input_pvs, + odin, + **kwargs, +): + """Takes an input DataHandle that contains LHCb::Particles (such as the + output of a trigger line), and: + (1) finds out which particles are basic particles, and calls the + momentum scaling on those + (2) takes the scaled tracks, along with the relation table, + to then call the DecayTree recombiner which re-combines the + particles together, and fits again the decay vertices. + + The output contains particles, the new vertices, the new protoparticles, + and again a relation table between the old particles and the new ones. + """ + + flat_particles = FlattenDecayTree(InputParticles=input_particles) + basic_particles = ParticleFilter( + flat_particles.OutputParticles, + F.FILTER(F.ISBASICPARTICLE), # ideally swap to some F.HASTRACK functor + name="SelectBasicParticlesForScaling_{hash}", + ) + + tracks_from_particles = SelectTracksForParticles( + Inputs=[basic_particles] + ).OutputTracks + + tracks_from_dst = scale_tracks(tracks_from_particles, odin).OutputTracks + + relation_table_match_by_veloid_to_track = LHCbIDOverlapRelationTableParticleToTrack( + MatchFrom=basic_particles, + MatchTo=tracks_from_dst, + IncludeVP=True, + IncludeFT=True, + IncludeUT=True, + ).OutputRelations + + if input_pvs is None: + input_pvs = PVsEmptyProducer() + + recombined_trees = RecombineDecayTrees( + InputParticles=input_particles, + InputPVs=input_pvs, + InputTracks=tracks_from_dst, # the refitted tracks + InputTrackRelations=relation_table_match_by_veloid_to_track, + ) # the relation table + + return recombined_trees diff --git a/Hlt/RecoConf/python/RecoConf/track_refitting.py b/Hlt/RecoConf/python/RecoConf/track_refitting.py index 629256650602afc60e4fc9e6da903faca3c1ea51..b6e5ae17bc73105292e4651ffbd483ac2d4b090a 100644 --- a/Hlt/RecoConf/python/RecoConf/track_refitting.py +++ b/Hlt/RecoConf/python/RecoConf/track_refitting.py @@ -32,6 +32,7 @@ from PyConf.Algorithms import ( SelectUTClustersFromTracks, SelectVPMicroClustersFromTracks, SharedTrackEventFitter, + TrackBestTrackCreator, UpdateVertexCoordinatesOffline, UTHitClustersToPrUTHitsConverter, UTHitClustersToUTHitHandlerConverter, @@ -42,10 +43,12 @@ from PyConf.Algorithms import ( from RecoConf.algorithms_thor import ParticleFilter +# to add ghost probability again after refit # Uncomment when Rec!587 is merged # from PyConf.Algorithms import VeloKalmanTrackV1 # Algotihms to perform the fit from RecoConf.hlt2_tracking import ( + get_GhostProbabilityTools, get_global_measurement_provider, get_track_master_fitter, ) @@ -66,7 +69,13 @@ TRACK_FIT_TYPE_VELOKALMAN = "VeloKalman" TRACK_FIT_TYPE_TRACKMASTERFITTER = "TrackMasterFitter" -def refit_tracks(tracks, get_clusters_from_track, track_fitter): +def refit_tracks( + tracks, + get_clusters_from_track, + track_fitter, + update_ghost_prob=False, + disable_UT=False, +): if get_clusters_from_track: velo_micro_clusters_on_tracks = SelectVPMicroClustersFromTracks(Inputs=[tracks]) scifi_clusters_on_tracks = SelectFTClustersFromTracks(Inputs=[tracks]) @@ -161,6 +170,22 @@ def refit_tracks(tracks, get_clusters_from_track, track_fitter): # TracksLocation=tracks).OutputTracksLocation refitted_tracks = None + if update_ghost_prob and refitted_tracks: + print([tool() for tool in get_GhostProbabilityTools(without_UT=disable_UT)]) + refitted_tracks = TrackBestTrackCreator( + TracksInContainers=[refitted_tracks], + GhostProbTools=[ + tool() for tool in get_GhostProbabilityTools(without_UT=disable_UT) + ], + MaxChi2DoF=9999999.0, # do not apply any chi2 cut + MaxOverlapFracVelo=1.0, # do not clone kill + MaxOverlapFracT=1.0, # do not clone kill + MaxOverlapFracUT=1.0, # do not clone kill + DoNotRefit=True, # also don't refit + AddGhostProb=True, # but just add the ghost prob + FitTracks=False, + ).TracksOutContainer + return refitted_tracks @@ -168,6 +193,8 @@ def refit_decay_tree( input_particles, input_pvs, get_clusters_from_track=True, + update_ghost_prob=True, + disable_UT=False, track_fitter=TRACK_FIT_TYPE_PRKALMAN, **kwargs, ): @@ -216,7 +243,11 @@ def refit_decay_tree( ).OutputTracks tracks_from_dst = refit_tracks( - tracks_from_particles, get_clusters_from_track, track_fitter=track_fitter + tracks_from_particles, + get_clusters_from_track, + track_fitter=track_fitter, + disable_UT=disable_UT, + update_ghost_prob=update_ghost_prob, ) relation_table_match_by_veloid_to_track = LHCbIDOverlapRelationTableParticleToTrack( @@ -264,7 +295,10 @@ def refit_pvs( pv_tracks = SelectTracksForRecVertices(Inputs=[input_pvs]) refitted_pv_tracks = refit_tracks( - tracks=pv_tracks, track_fitter=track_fitter, get_clusters_from_track=True + tracks=pv_tracks, + track_fitter=track_fitter, + get_clusters_from_track=True, + update_ghost_prob=False, ) relation_table_match_by_veloid_to_track = LHCbIDOverlapRelationTableTrackToTrack(