diff --git a/Phys/TrackRefitting/CMakeLists.txt b/Phys/TrackRefitting/CMakeLists.txt index 09c8cb554d9ef8905de7cc787c5988763dd8a290..93073694378ef5963818f271efb123c4b969c7ba 100644 --- a/Phys/TrackRefitting/CMakeLists.txt +++ b/Phys/TrackRefitting/CMakeLists.txt @@ -20,6 +20,7 @@ gaudi_add_module(TrackRefitting src/SelectTracksForParticles.cpp src/SelectTracksForRecVertices.cpp src/UpdateVertexCoordinatesOffline.cpp + src/TrackScaleState.cpp LINK Gaudi::GaudiAlgLib Gaudi::GaudiKernel diff --git a/Phys/TrackRefitting/src/TrackScaleState.cpp b/Phys/TrackRefitting/src/TrackScaleState.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cec40c85fb98ef2d7be72c5eb9c7f27b791b77e3 --- /dev/null +++ b/Phys/TrackRefitting/src/TrackScaleState.cpp @@ -0,0 +1,215 @@ +/*****************************************************************************\ +* (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. * +\*****************************************************************************/ + +/** + * @class TrackScaleState + * + * Uses a table of corrections (from the conditions) to update + * the states on tracks, per the method presented in LHCb-DP-2023-003. + * + * As the objects on the TES are immutable, the results of + * this algorithm can be described as: + * (1) tracks; + * (2) a relation table + * + * The intended use-case for this is in DaVinci, in which + * an offline calibration can be applied to the momenta + * of the particles. + * + * By default, when provided with VELO tracks, nothing changes. + * All states on the track are adjusted. + * + * @author Ozlem Ozcelik <ozlem.ozcelik@cern.ch> + * @author Matthew David Needham <Matthew.Needham@cern.ch> + * @author Laurent Dufour <laurent.dufour@cern.ch> (only technical aspects) + */ +#include "Event/Track.h" +#include "GaudiKernel/ISvcLocator.h" + +#include "LHCbAlgs/Transformer.h" +#include "Relations/Relation2D.h" + +#include "DetDesc/DetectorElement.h" +#include "DetDesc/GenericConditionAccessorHolder.h" + +#include "Kernel/STLExtensions.h" + +#include <Event/ODIN.h> + +#ifdef USE_DD4HEP +# include <Detector/LHCb/DeLHCb.h> +# include <Detector/LHCb/OfflineMomentumScale.h> + +namespace { + using OutputRelationTable = LHCb::Relation2D<LHCb::Track, LHCb::Track>; + using Output_t = std::tuple<LHCb::Tracks, OutputRelationTable>; + + using MultiTransformer_t = LHCb::Algorithm::MultiTransformer<Output_t( LHCb::Track::Range const&, LHCb::ODIN const&, + LHCb::Detector::DeLHCb const& ), + LHCb::DetDesc::usesConditions<LHCb::Detector::DeLHCb>>; + + inline float value_within_range( float value, std::set<float> const& bins ) { + if ( value < *bins.begin() ) return *bins.begin(); + + if ( value > *bins.rbegin() ) return *bins.rbegin(); + + return value; + } +} // namespace + +namespace LHCb { + class TrackScaleState final : public MultiTransformer_t { + + public: + TrackScaleState( const std::string& name, ISvcLocator* pSvcLocator ) + : MultiTransformer_t( name, pSvcLocator, + { KeyValue{ "InputTracks", "" }, KeyValue{ "ODIN", "" }, + KeyValue{ "StandardGeometryTop", LHCb::standard_geometry_top } }, + { + KeyValue{ "OutputTracks", "" }, + KeyValue{ "OutputRelations", "" }, + } ) {} + + Output_t operator()( LHCb::Track::Range const& inputTracks, LHCb::ODIN const& odin, + LHCb::Detector::DeLHCb const& lhcb ) const override { + + const auto& momentumScaleCalibration = lhcb.offlineMomentumScale(); + + Output_t myOutput{}; + + auto& outputTracks = std::get<0>( myOutput ); + auto& outputRelations = std::get<1>( myOutput ); + + if ( !momentumScaleCalibration || !momentumScaleCalibration->enabled() ) { + std::for_each( inputTracks.begin(), inputTracks.end(), [&outputTracks, &outputRelations]( const auto& track ) { + const auto& newTrack = new LHCb::Track( *track, track->key() ); + + outputTracks.insert( newTrack, track->key() ); + outputRelations.relate( newTrack, track ).ignore(); + } ); + + m_nSkippedTracks += inputTracks.size(); + + return myOutput; + } + + outputTracks.reserve( inputTracks.size() ); + const auto runnumber = odin.runNumber(); + + for ( const auto& track : inputTracks ) { + auto newTrack = std::make_unique<Track>( *track, track->key() ); + + if ( !( track->isVeloBackward() || track->checkType( LHCb::Track::Types::Velo ) ) ) { + + // actually scale the states + for ( auto& pState : newTrack->states() ) { + StatusCode stateUpdated = updateState( *pState, *track, *momentumScaleCalibration, runnumber ); + if ( !stateUpdated.isSuccess() ) ++m_msg_track_state_update_fail; + } + + ++m_nScaledTracks; + } else { + ++m_nSkippedTracks; + } + + auto* newTrackPtr = newTrack.release(); + outputTracks.insert( newTrackPtr ); // once we put the pointer on the TES, memory is done by Gaudi + StatusCode relationResult = outputRelations.relate( newTrackPtr, track ); + + if ( !relationResult.isSuccess() ) { ++m_msg_relation_add_fail; } + } + + return myOutput; + } + + private: + StatusCode updateState( LHCb::State& state, LHCb::Track const& track, + LHCb::Detector::OfflineMomentumScale const& momentumScale, + const unsigned int runnumber ) const { + // Determine the scaling factor based on momentum + float scaleFactor = 1.0; + bool highMomentum = track.p() > momentumScale.highMomentumThreshold(); + bool positiveCharge = track.charge() > 0; + float tx = track.firstState().tx(); + float ty = track.firstState().ty(); + if ( this->msgLevel( MSG::DEBUG ) ) { this->debug() << "State (tx,ty): " << tx << ", " << ty << endmsg; } + + if ( m_allowOverflowUnderflow.value() ) { + // force the value of tx to be in the range. + tx = value_within_range( tx, momentumScale.binsTx() ); + ty = value_within_range( ty, momentumScale.binsTy() ); + + // take care of the float comparison + if ( fabs( tx - track.firstState().tx() ) > 1.e-5 || fabs( ty - track.firstState().ty() ) > 1.e-5 ) { + ++m_nOverflowUnderflow; + if ( this->msgLevel( MSG::DEBUG ) ) { + this->debug() << "(tx,ty) after under/overflow protection: " << tx << ", " << ty << endmsg; + } + if ( this->msgLevel( MSG::DEBUG ) ) { + this->debug() << "Difference: " << ( tx - track.firstState().tx() ) << ", " << ty - track.firstState().ty() + << endmsg; + } + } + } + + const auto& scaleMap = momentumScale.correctionMap( highMomentum, positiveCharge ); + + const auto index = momentumScale.getSequentialIndex( tx, ty ); + + if ( !index ) { + if ( this->msgLevel( MSG::DEBUG ) ) { this->debug() << "(tx,ty) out of bounds: " << tx << "," << ty << endmsg; } + + ++m_msg_out_of_bounds; + return StatusCode::FAILURE; + } + + scaleFactor = 1. - ( ( scaleMap[*index] / 1000.0 ) ); + + // Apply additional scale factor based on the run number + auto alpha = momentumScale.getAlpha( runnumber ); + if ( !alpha ) + ++m_msg_no_alpha; + else { scaleFactor *= 1. - ( *alpha ) / 1000.0; } + + // std::cout << scaleFactor << std::endl; + + // Scale the momentum + state.setQOverP( ( 1. / scaleFactor ) * state.qOverP() ); + + if ( this->msgLevel( MSG::DEBUG ) ) { this->debug() << "Scalefactor applied: " << scaleFactor << endmsg; } + + return StatusCode::SUCCESS; + } + + mutable Gaudi::Accumulators::Counter<> m_nScaledTracks{ this, "# scaled tracks" }; + mutable Gaudi::Accumulators::Counter<> m_nSkippedTracks{ this, + "# skipped tracks (no momentum scale numbers enabled)" }; + mutable Gaudi::Accumulators::Counter<> m_nSkippedVeloTracks{ this, "# skipped tracks (VELO)" }; + mutable Gaudi::Accumulators::Counter<> m_nOverflowUnderflow{ + this, "# used under/overflow bins in (tx,ty) of momentum scaling" }; + + mutable Gaudi::Accumulators::MsgCounter<MSG::ERROR> m_msg_relation_add_fail{ this, + "Adding of relationship failed" }; + + mutable Gaudi::Accumulators::MsgCounter<MSG::WARNING> m_msg_no_alpha{ this, "Fill-dependent scale factor absent." }; + + mutable Gaudi::Accumulators::MsgCounter<MSG::ERROR> m_msg_track_state_update_fail{ this, + "Failed to update track state" }; + + mutable Gaudi::Accumulators::MsgCounter<MSG::ERROR> m_msg_out_of_bounds{ + this, "(tx, ty) are out of bounds for this calibration map" }; + + Gaudi::Property<bool> m_allowOverflowUnderflow{ this, "AllowUnderOverflow", true }; + }; + + DECLARE_COMPONENT_WITH_ID( TrackScaleState, "TrackScaleState" ) +} // namespace LHCb +#endif diff --git a/Tr/PrKalmanFilter/src/KalmanFilterTool.cpp b/Tr/PrKalmanFilter/src/KalmanFilterTool.cpp index e8e41b541228336b34c2edf893a9a69905052df8..46e95fdb9cb4d4da3dfee40a09677d2cae649ce7 100644 --- a/Tr/PrKalmanFilter/src/KalmanFilterTool.cpp +++ b/Tr/PrKalmanFilter/src/KalmanFilterTool.cpp @@ -269,6 +269,7 @@ namespace LHCb::Pr { auto down_chi2 = LHCb::ChiSquare{ 0, -n_track_parameters }; auto upstream_chi2 = LHCb::ChiSquare{}; // not initialized because always used combined with one of the above auto muon_chi2 = LHCb::ChiSquare{}; + unsigned int n_ut_outliers = 0; for ( auto const& node : fitnodes ) { switch ( node.type() ) { @@ -277,6 +278,7 @@ namespace LHCb::Pr { break; case Node::Type::UTHit: upstream_chi2 += node.delta_chi2[Node::backward]; + if ( node.m_is_outlier ) ++n_ut_outliers; break; case Node::Type::FTHit: down_chi2 += node.delta_chi2[Node::forward]; @@ -296,6 +298,7 @@ namespace LHCb::Pr { new_track.addInfo( TrackV1::AdditionalInfo::FitTNDoF, down_chi2.nDoF() ); new_track.addInfo( TrackV1::AdditionalInfo::FitVeloChi2, velo_chi2.chi2() ); new_track.addInfo( TrackV1::AdditionalInfo::FitVeloNDoF, velo_chi2.nDoF() ); + new_track.addInfo( TrackV1::AdditionalInfo::NUTOutliers, n_ut_outliers ); new_track.addInfo( TrackV1::AdditionalInfo::FitMatchChi2, prev_chi2.chi2() - upstream_chi2.chi2() - down_chi2.chi2() ); if ( add_fitted_states<Long::Tracks>( new_track, fitnodes, scatteringMomentum, geo, extrap, @@ -311,6 +314,7 @@ namespace LHCb::Pr { new_track.addInfo( TrackV1::AdditionalInfo::FitVeloChi2, velo_chi2.chi2() ); new_track.addInfo( TrackV1::AdditionalInfo::FitVeloNDoF, velo_chi2.nDoF() ); new_track.addInfo( TrackV1::AdditionalInfo::FitMuonChi2, muon_chi2.chi2() ); + new_track.addInfo( TrackV1::AdditionalInfo::NUTOutliers, n_ut_outliers ); new_track.addInfo( TrackV1::AdditionalInfo::FitMatchChi2, prev_chi2.chi2() - upstream_chi2.chi2() - down_chi2.chi2() - muon_chi2.chi2() ); if ( add_fitted_states<Long::Tracks>( new_track, fitnodes, scatteringMomentum, geo, extrap,