ATLAS Offline Software
Loading...
Searching...
No Matches
MuSAVtxFitterTool.cxx
Go to the documentation of this file.
1/*
2Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
8
9// Header include
10
13#include "GeoPrimitives/GeoPrimitives.h" //Needed for Amg::Vector3D
14#include <vector>
15#include <cmath>
16
17namespace Rec {
18
19MuSAVtxFitterTool::MuSAVtxFitterTool(const std::string& type, const std::string& name, const IInterface* parent) :
20 AthAlgTool(type, name, parent)
21{
22 declareInterface<MuSAVtxFitterTool>(this);
23}
24
26
28{
29
30 ATH_CHECK(m_vertexFitter.retrieve());
31 ATH_MSG_DEBUG("Retrieved tool " << m_vertexFitter);
32
33 ATH_CHECK(m_extrapolator.retrieve());
34 ATH_MSG_DEBUG("Retrieved tool " << m_extrapolator);
35
36
37 ATH_MSG_DEBUG("Initialization successful");
38
39 return StatusCode::SUCCESS;
40
41}
42
43std::unique_ptr<xAOD::TrackParticle> MuSAVtxFitterTool::extrapolateMuSA(const xAOD::TrackParticle& trk, const xAOD::EventInfo& eventInfo, const EventContext& ctx) const {
44
45 Amg::Vector3D refPos(eventInfo.beamPosX(), eventInfo.beamPosY(), eventInfo.beamPosZ());
46 Trk::PerigeeSurface persurf(refPos);
47 Trk::PropDirection propagationDirection = Trk::oppositeMomentum;
48
49 auto newTrack = std::make_unique<xAOD::TrackParticle>();
50 newTrack->makePrivateStore();
51
52 auto perigee = trk.perigeeParameters();
53 auto entryPars = m_extrapolator->extrapolate(ctx, perigee, persurf, propagationDirection, false, Trk::muon);
54
55 if (!entryPars) {
56 ATH_MSG_WARNING("Failed to back extrapolate MSTP, returning dummy!");
57 // Return a dummy track that will fail downstream cuts (extrapolations can sometimes fail due to various non-pathological reasons -- SA muons are not always high quality objects)
58 newTrack->setDefiningParameters(1.e19, 1.e19, 1.e19, 1.e19, 0);
59 return newTrack;
60 }
61
62 newTrack->setDefiningParameters(entryPars->parameters()[Trk::d0], entryPars->parameters()[Trk::z0], entryPars->parameters()[Trk::phi], entryPars->parameters()[Trk::theta], entryPars->parameters()[Trk::qOverP]);
63 newTrack->setDefiningParametersCovMatrix(*entryPars->covariance());
64 newTrack->setParametersOrigin(eventInfo.beamPosX(), eventInfo.beamPosY(), eventInfo.beamPosZ());
65
66 return newTrack;
67}
68
69StatusCode MuSAVtxFitterTool::doMuSAVtxFit(std::vector<MuSAVtxFitterTool::WrkVrt>& workVerticesContainer,
71 const xAOD::EventInfo& eventInfo,
72 const EventContext& ctx) const
73{
74 ATH_MSG_DEBUG("MuSAVtxFitterTool::doMuSAVtxFit");
75
76 // first gather all SA muons that pass basic checks
77 // also recover Staco-authored Combined muons with large MS-ID mismatch if configured
78 // this is done to create parity between mc20 and mc23 reconstruction, where in mc20 STACO matching was much looser
79 // erroneously reducing the number of available SA muons compared to mc23
80 std::vector<const xAOD::Muon*> candidateSAmuons;
81 for (const auto muon : muonContainer) {
82
83 // Check if this is a Staco Combined muon eligible for recovery
84 bool isStacoRecovery = false;
85 if (m_doStacoRecovery &&
86 muon->muonType() == xAOD::Muon::MuonType::Combined &&
87 muon->author() == xAOD::Muon::Author::STACO) {
88
89 //we do not try to recover LRT Stacos to avoid more likely situations where we
90 //mistakenly "recover" an MS track with a real displaced ID track
91 static const SG::AuxElement::Accessor<char> acc_isLRT("isLRT");
92 if (acc_isLRT.isAvailable(*muon) && acc_isLRT(*muon)) {
93 ATH_MSG_DEBUG("Skipping recovering LRT Staco muon!");
94 continue;
95 }
96
97 // Check the deltaR between the MS and ID tracks to see if they are likely to be a mismatched pair that could be recovered as a SA muon
98 // the default thresholds reflect the criteria used in mc23 reconstruction but can be configured as needed
99 const xAOD::TrackParticle* msTrk = muon->trackParticle(xAOD::Muon::MuonSpectrometerTrackParticle);
100 const xAOD::TrackParticle* idTrk = muon->trackParticle(xAOD::Muon::InnerDetectorTrackParticle);
101 if (msTrk && idTrk) {
102 double dEta = std::abs(msTrk->eta() - idTrk->eta());
103 double dPhi = std::abs(msTrk->phi() - idTrk->phi());
104 if (dPhi > M_PI) dPhi = 2.0 * M_PI - dPhi;
105 if (dEta > m_stacoMatchDeltaCut || dPhi > m_stacoMatchDeltaCut) {
106 ATH_MSG_DEBUG("Recovering Staco Combined muon with dEta=" << dEta << " dPhi=" << dPhi);
107 isStacoRecovery = true;
108 }
109 }
110 }
111
112 if (muon->muonType()!= xAOD::Muon::MuonType::MuonStandAlone && !isStacoRecovery && !m_doValidation) {
113 continue;
114 }
115
116 const xAOD::TrackParticle* MuSAMSTP = muon->trackParticle(xAOD::Muon::MuonSpectrometerTrackParticle);
117 if (!MuSAMSTP) {
118 ATH_MSG_WARNING("Muon has no MSTP, check your input! "<<muon->muonType()<<", "<<muon->author());
119 continue;
120 }
121
122 // pre-fit cuts at "seeding" level go here
123 if (std::abs(MuSAMSTP->eta()) > m_etaCutMSTP) {
124 ATH_MSG_VERBOSE("Skipping SA muon with |eta| > 2.5!");
125 continue;
126 }
127 // SA muons can get saved with nonphysical pT values, checking prevents extrap crashes
128 if (MuSAMSTP->pt() > 13000000) {
129 ATH_MSG_DEBUG("Skipping SA muon with pT " << (MuSAMSTP->pt() / 1000.) << " GeV!");
130 continue;
131 }
132
133 // SA muons can also be saved in regions with 0 magnetic field, which can cause extrapolation crashes
134 float spectrometerFieldIntegral = 0.0;
135 muon->parameter(spectrometerFieldIntegral, xAOD::Muon::ParamDef::spectrometerFieldIntegral);
136 if (spectrometerFieldIntegral < 0.1) {
137 ATH_MSG_DEBUG("Skipping SA muon with spectrometerFieldIntegral " << spectrometerFieldIntegral << " T*m!");
138 continue;
139 }
140
141 candidateSAmuons.push_back(muon);
142 }
143
144 // bail out if we don't have at least two valid SA muons
145 if (candidateSAmuons.size() < 2) {
146 ATH_MSG_VERBOSE("Not enough standalone muons to fit a vertex!");
147 return StatusCode::SUCCESS;
148 }
149
150 // extrapolate SA muons only if we have enough candidates to form a vertex
151 std::vector<std::shared_ptr<xAOD::TrackParticle>> extrapolatedMuSATracks;
152 std::map<const xAOD::TrackParticle*, const xAOD::Muon*> muonToExtrapolatedTrackMap;
153 for (const auto muon : candidateSAmuons) {
154 const xAOD::TrackParticle* MuSAMSTP = muon->trackParticle(xAOD::Muon::MuonSpectrometerTrackParticle);
155 auto extrapolatedMuSATrack = extrapolateMuSA(*MuSAMSTP, eventInfo, ctx);
156 if (extrapolatedMuSATrack->definingParameters()[Trk::d0] > 8000) {
157 ATH_MSG_DEBUG("Failed to extrapolate MuSA track, skipping!");
158 continue;
159 }
160 //Convert unique_ptr to shared pointer to allow ownership complications later.
161 extrapolatedMuSATracks.emplace_back(extrapolatedMuSATrack.release());
162 muonToExtrapolatedTrackMap[extrapolatedMuSATracks.back().get()] = muon;
163 ATH_MSG_VERBOSE("Extrapolated MuSA track! Total extrapolated so far: " << extrapolatedMuSATracks.size());
164 }
165
166 if (extrapolatedMuSATracks.size() < 2) {
167 ATH_MSG_VERBOSE("Not enough extrapolated tracks to fit a vertex!");
168 return StatusCode::SUCCESS;
169 }
170
171 std::unique_ptr<Trk::IVKalState> state = m_vertexFitter->makeState(ctx);
172 std::vector<const xAOD::TrackParticle*> tracksToFit(2);
173 std::vector<const xAOD::NeutralParticle*> dummyNeutrals;
174 // Loop over all unique pairs.
175 for (unsigned int i = 0; i < extrapolatedMuSATracks.size(); i++) {
176 for (unsigned int j = i+1; j < extrapolatedMuSATracks.size(); j++) {
177 MuSAVtxFitterTool::WrkVrt MuSACandidate;
178 tracksToFit[0] = extrapolatedMuSATracks[i].get();
179 tracksToFit[1] = extrapolatedMuSATracks[j].get();
180 StatusCode res = m_vertexFitter->VKalVrtFit(tracksToFit, dummyNeutrals,
181 MuSACandidate.pos, MuSACandidate.mom,
182 MuSACandidate.charge, MuSACandidate.cov,
183 MuSACandidate.chi2PerTrk, MuSACandidate.trkAtVrt,
184 MuSACandidate.chi2, *state);
185 if (res.isSuccess()) {
186 ATH_MSG_DEBUG("MuSA vertex fit successful!");
187 MuSACandidate.newExtrapolatedTracks.push_back(extrapolatedMuSATracks[i]);
188 MuSACandidate.newExtrapolatedTracks.push_back(extrapolatedMuSATracks[j]);
189 MuSACandidate.muonCandidates = {
190 muonToExtrapolatedTrackMap[tracksToFit[0]],
191 muonToExtrapolatedTrackMap[tracksToFit[1]]
192 };
193 MuSACandidate.minOpAng = muonToExtrapolatedTrackMap[tracksToFit[0]]->p4().DeltaR(
194 muonToExtrapolatedTrackMap[tracksToFit[1]]->p4());
195 workVerticesContainer.emplace_back(std::move(MuSACandidate));
196 } else {
197 ATH_MSG_DEBUG("MuSA vertex fit failed!");
198 }
199 }
200 }
201 // NOTE: Any remaining extrapolated tracks in the vector will be cleaned up automatically when they go out of scope
202 // this means the logic may need to be revisited if we want to "recover" extrapolated tracks that were not used in any fit through another mechanism
203 return StatusCode::SUCCESS;
204}
205
206StatusCode MuSAVtxFitterTool::doPostFitSelections(std::vector<MuSAVtxFitterTool::WrkVrt>& workVerticesContainer) const{
207 ATH_MSG_DEBUG("MuSAVtxFitterTool::doPostFitSelections");
208 //minimal method for post-fit selections
209 for (auto& workVertex : workVerticesContainer) {
210 if (workVertex.chi2 > m_baseChi2Cut) {
211 ATH_MSG_DEBUG("SA vertex chi2 too high, removing from consideration");
212 workVertex.isGood = false; // isGood flag has no specific utility here yet, but may be used in the future for more sophisticated recovery/reassembly
213 }
214 }
215
216 //remove any vertices marked as bad
217 workVerticesContainer.erase(std::remove_if(workVerticesContainer.begin(), workVerticesContainer.end(),
218 [](const MuSAVtxFitterTool::WrkVrt& vtx) { return !vtx.isGood; }), workVerticesContainer.end());
219
220 return StatusCode::SUCCESS;
221}
222
223StatusCode MuSAVtxFitterTool::selectBestVertices(std::vector<MuSAVtxFitterTool::WrkVrt>& workVerticesContainer) const
224{
225 ATH_MSG_DEBUG("MuSAVtxFitterTool::selectBestVertices");
226 //the default logic will create vertices with all possible combinations of SA muons
227 //in rare cases this will cause two MuSA vertices in one event that re-use a muon (more likely in signal models with two or more LLP BSM decays)
228 //if this occurs we can select based on the highest chi2 and remove the poorer-quality vertex from consideration
229
230 //first check if a muon is re-used in multiple vertices
231
232 if (workVerticesContainer.size() < 2) {
233 ATH_MSG_VERBOSE("Only one MuSA vertex, no need to select best!");
234 return StatusCode::SUCCESS;
235 }
236
237 //sort all the vertices by chi2 starting with the lowest
238 std::sort(workVerticesContainer.begin(), workVerticesContainer.end(), [](const MuSAVtxFitterTool::WrkVrt& v1, const MuSAVtxFitterTool::WrkVrt& v2) { return v1.chi2 < v2.chi2; });
239 ATH_MSG_VERBOSE("Sorted vertices by chi2! Lowest chi2: " << workVerticesContainer.front().chi2 << " Highest chi2: " << workVerticesContainer.back().chi2);
240
241 //starting with the lowest chi2 vertex, check its muons and remove any other vertices that use the same muons
242 for (unsigned int i = 0; i < workVerticesContainer.size(); i++) {
243 auto& vertex = workVerticesContainer.at(i);
244 if (!vertex.isGood) {
245 continue; // skip vertices we've already marked as bad
246 }
247 for (unsigned int j = i+1; j < workVerticesContainer.size(); j++) {
248 auto& otherVertex = workVerticesContainer.at(j);
249 for (const auto& muon : vertex.muonCandidates) {
250 if (std::find(otherVertex.muonCandidates.begin(), otherVertex.muonCandidates.end(), muon) != otherVertex.muonCandidates.end()) {
251 //remove the other vertex
252 ATH_MSG_VERBOSE("Found a vertex re-using a muon with chi2: " << otherVertex.chi2 << " removing from consideration!");
253 otherVertex.isGood = false;
254 }
255 }
256 }
257 }
258
259 //remove any vertices marked as bad
260 workVerticesContainer.erase(std::remove_if(workVerticesContainer.begin(), workVerticesContainer.end(),
261 [](const MuSAVtxFitterTool::WrkVrt& vtx) { return !vtx.isGood; }), workVerticesContainer.end());
262
263 return StatusCode::SUCCESS;
264
265
266}
267} // namespace Rec
#define M_PI
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
std::pair< std::vector< unsigned int >, bool > res
xAOD::MuonContainer * muonContainer
AthAlgTool(const std::string &type, const std::string &name, const IInterface *parent)
Constructor with parameters:
StatusCode selectBestVertices(std::vector< MuSAVtxFitterTool::WrkVrt > &workVerticesContainer) const
ToolHandle< Trk::ITrkVKalVrtFitter > m_vertexFitter
virtual StatusCode initialize() override
StatusCode doPostFitSelections(std::vector< MuSAVtxFitterTool::WrkVrt > &workVerticesContainer) const
DoubleProperty m_stacoMatchDeltaCut
StatusCode doMuSAVtxFit(std::vector< MuSAVtxFitterTool::WrkVrt > &workVerticesContainer, const xAOD::MuonContainer &muonContainer, const xAOD::EventInfo &eventInfo, const EventContext &ctx) const
std::unique_ptr< xAOD::TrackParticle > extrapolateMuSA(const xAOD::TrackParticle &trk, const xAOD::EventInfo &eventInfo, const EventContext &ctx) const
Gaudi::Property< bool > m_doValidation
ToolHandle< Trk::IExtrapolator > m_extrapolator
Gaudi::Property< bool > m_doStacoRecovery
MuSAVtxFitterTool(const std::string &type, const std::string &name, const IInterface *parent)
Class describing the Line to which the Perigee refers to.
float beamPosX() const
X coordinate of the beam spot position.
float beamPosZ() const
Z coordinate of the beam spot position.
float beamPosY() const
Y coordinate of the beam spot position.
const Trk::Perigee & perigeeParameters() const
Returns the Trk::MeasuredPerigee track parameters.
virtual double phi() const override final
The azimuthal angle ( ) of the particle (has range to .).
virtual double pt() const override final
The transverse momentum ( ) of the particle.
virtual double eta() const override final
The pseudorapidity ( ) of the particle.
Eigen::Matrix< double, 3, 1 > Vector3D
Gaudi Tools.
PropDirection
PropDirection, enum for direction of the propagation.
@ oppositeMomentum
@ theta
Definition ParamDefs.h:66
@ qOverP
perigee
Definition ParamDefs.h:67
@ phi
Definition ParamDefs.h:75
@ d0
Definition ParamDefs.h:63
@ z0
Definition ParamDefs.h:64
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
DataModel_detail::iterator< DVL > remove_if(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end, Predicate pred)
Specialization of remove_if for DataVector/List.
EventInfo_v1 EventInfo
Definition of the latest event info version.
TrackParticle_v1 TrackParticle
Reference the current persistent version:
MuonContainer_v1 MuonContainer
Definition of the current "Muon container version".
Represents a working vertex structure used in VKalVrt fitting.
double chi2
VKalVrt fit covariance.
std::vector< double > cov
VKalVrt fit vertex 4-momentum.
long int charge
list of VKalVrt fit chi2 for each track
double minOpAng
list of track parameters wrt the reconstructed vertex
Amg::Vector3D pos
list of associated muon candidates
std::vector< std::vector< double > > trkAtVrt
total charge of the vertex
std::vector< double > chi2PerTrk
VKalVrt fit chi2 result.
TLorentzVector mom
VKalVrt fit vertex position.
std::vector< const xAOD::Muon * > muonCandidates
list of associated new extrapolated tracks
std::vector< std::shared_ptr< const xAOD::TrackParticle > > newExtrapolatedTracks
list if indices in TrackParticleContainer for associatedTracks – CURRENTLY UNUSED,...