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 std::vector<const xAOD::Muon*> candidateSAmuons;
79 for (const auto muon : muonContainer) {
80 bool isSA = (muon->muonType() == xAOD::Muon::MuonStandAlone);
81 bool isCalo = (muon->muonType() == xAOD::Muon::CaloTagged);
82 bool isSegment = (muon->muonType() == xAOD::Muon::SegmentTagged);
83 bool isSiForward = (muon->muonType() == xAOD::Muon::SiliconAssociatedForwardMuon);
84 bool isCombined = (muon->muonType() == xAOD::Muon::Combined);
85 bool isStaco = (muon->author() == xAOD::Muon::STACO);
86
87 // Check if this is a Staco Combined muon eligible for recovery
88 bool isStacoRecovery = false;
89 if (m_doStacoRecovery && isCombined && isStaco) {
90 const xAOD::TrackParticle* msTrk = muon->trackParticle(xAOD::Muon::MuonSpectrometerTrackParticle);
91 const xAOD::TrackParticle* idTrk = muon->trackParticle(xAOD::Muon::InnerDetectorTrackParticle);
92 if (msTrk && idTrk) {
93 double dEta = std::abs(msTrk->eta() - idTrk->eta());
94 double dPhi = std::abs(msTrk->phi() - idTrk->phi());
95 if (dPhi > M_PI) dPhi = 2.0 * M_PI - dPhi;
96 if (dEta > m_stacoMatchDeltaCut || dPhi > m_stacoMatchDeltaCut) {
97 ATH_MSG_DEBUG("Recovering Staco Combined muon with dEta=" << dEta << " dPhi=" << dPhi);
98 isStacoRecovery = true;
99 }
100 }
101 }
102
103 if (!isSA && !isStacoRecovery && !m_doValidation) {
104 continue;
105 }
106
107 const xAOD::TrackParticle* MuSAMSTP = muon->trackParticle(xAOD::Muon::MuonSpectrometerTrackParticle);
108 if (!MuSAMSTP) {
109 if (isCalo || isSegment || isSiForward) {
110 ATH_MSG_VERBOSE("Skipping non-SA, non-Combined muon type in validation mode!");
111 } else {
112 ATH_MSG_WARNING("Muon has no MSTP, check your input!");
113 }
114 continue;
115 }
116
117 // pre-fit cuts at "seeding" level go here
118 if (std::abs(MuSAMSTP->eta()) > m_etaCutMSTP) {
119 ATH_MSG_VERBOSE("Skipping SA muon with |eta| > 2.5!");
120 continue;
121 }
122 // SA muons can get saved with nonphysical pT values, checking prevents extrap crashes
123 if (MuSAMSTP->pt() > 13000000) {
124 ATH_MSG_DEBUG("Skipping SA muon with pT " << (MuSAMSTP->pt() / 1000.) << " GeV!");
125 continue;
126 }
127
128 // SA muons can also be saved in regions with 0 magnetic field, which can cause extrapolation crashes
129 float spectrometerFieldIntegral = 0.0;
130 muon->parameter(spectrometerFieldIntegral, xAOD::Muon::spectrometerFieldIntegral);
131 if (spectrometerFieldIntegral < 0.1) {
132 ATH_MSG_DEBUG("Skipping SA muon with spectrometerFieldIntegral " << muon->spectrometerFieldIntegral << " T*m!");
133 continue;
134 }
135
136 candidateSAmuons.push_back(muon);
137 }
138
139 // bail out if we don't have at least two valid SA muons
140 if (candidateSAmuons.size() < 2) {
141 ATH_MSG_VERBOSE("Not enough standalone muons to fit a vertex!");
142 return StatusCode::SUCCESS;
143 }
144
145 // extrapolate SA muons only if we have enough candidates to form a vertex
146 std::vector<std::shared_ptr<xAOD::TrackParticle>> extrapolatedMuSATracks;
147 std::map<const xAOD::TrackParticle*, const xAOD::Muon*> muonToExtrapolatedTrackMap;
148 for (const auto muon : candidateSAmuons) {
149 const xAOD::TrackParticle* MuSAMSTP = muon->trackParticle(xAOD::Muon::MuonSpectrometerTrackParticle);
150 auto extrapolatedMuSATrack = extrapolateMuSA(*MuSAMSTP, eventInfo, ctx);
151 if (extrapolatedMuSATrack->definingParameters()[Trk::d0] > 8000) {
152 ATH_MSG_DEBUG("Failed to extrapolate MuSA track, skipping!");
153 continue;
154 }
155 //Convert unique_ptr to shared pointer to allow ownership complications later.
156 extrapolatedMuSATracks.emplace_back(extrapolatedMuSATrack.release());
157 muonToExtrapolatedTrackMap[extrapolatedMuSATracks.back().get()] = muon;
158 ATH_MSG_VERBOSE("Extrapolated MuSA track! Total extrapolated so far: " << extrapolatedMuSATracks.size());
159 }
160
161 if (extrapolatedMuSATracks.size() < 2) {
162 ATH_MSG_VERBOSE("Not enough extrapolated tracks to fit a vertex!");
163 return StatusCode::SUCCESS;
164 }
165
166 std::unique_ptr<Trk::IVKalState> state = m_vertexFitter->makeState(ctx);
167 std::vector<const xAOD::TrackParticle*> tracksToFit(2);
168 std::vector<const xAOD::NeutralParticle*> dummyNeutrals;
169 // Loop over all unique pairs.
170 for (unsigned int i = 0; i < extrapolatedMuSATracks.size(); i++) {
171 for (unsigned int j = i+1; j < extrapolatedMuSATracks.size(); j++) {
172 MuSAVtxFitterTool::WrkVrt MuSACandidate;
173 tracksToFit[0] = extrapolatedMuSATracks[i].get();
174 tracksToFit[1] = extrapolatedMuSATracks[j].get();
175 StatusCode res = m_vertexFitter->VKalVrtFit(tracksToFit, dummyNeutrals,
176 MuSACandidate.pos, MuSACandidate.mom,
177 MuSACandidate.charge, MuSACandidate.cov,
178 MuSACandidate.chi2PerTrk, MuSACandidate.trkAtVrt,
179 MuSACandidate.chi2, *state);
180 if (res.isSuccess()) {
181 ATH_MSG_DEBUG("MuSA vertex fit successful!");
182 MuSACandidate.newExtrapolatedTracks.push_back(extrapolatedMuSATracks[i]);
183 MuSACandidate.newExtrapolatedTracks.push_back(extrapolatedMuSATracks[j]);
184 MuSACandidate.muonCandidates = {
185 muonToExtrapolatedTrackMap[tracksToFit[0]],
186 muonToExtrapolatedTrackMap[tracksToFit[1]]
187 };
188 MuSACandidate.minOpAng = muonToExtrapolatedTrackMap[tracksToFit[0]]->p4().DeltaR(
189 muonToExtrapolatedTrackMap[tracksToFit[1]]->p4());
190 workVerticesContainer.emplace_back(std::move(MuSACandidate));
191 } else {
192 ATH_MSG_DEBUG("MuSA vertex fit failed!");
193 }
194 }
195 }
196 // NOTE: Any remaining extrapolated tracks in the vector will be cleaned up automatically when they go out of scope
197 // 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
198 return StatusCode::SUCCESS;
199}
200
201StatusCode MuSAVtxFitterTool::doPostFitSelections(std::vector<MuSAVtxFitterTool::WrkVrt>& workVerticesContainer) const{
202 ATH_MSG_DEBUG("MuSAVtxFitterTool::doPostFitSelections");
203 //minimal method for post-fit selections
204 for (auto& workVertex : workVerticesContainer) {
205 if (workVertex.chi2 > m_baseChi2Cut) {
206 ATH_MSG_DEBUG("SA vertex chi2 too high, removing from consideration");
207 workVertex.isGood = false; // isGood flag has no specific utility here yet, but may be used in the future for more sophisticated recovery/reassembly
208 }
209 }
210
211 //remove any vertices marked as bad
212 workVerticesContainer.erase(std::remove_if(workVerticesContainer.begin(), workVerticesContainer.end(),
213 [](const MuSAVtxFitterTool::WrkVrt& vtx) { return !vtx.isGood; }), workVerticesContainer.end());
214
215 return StatusCode::SUCCESS;
216}
217
218StatusCode MuSAVtxFitterTool::selectBestVertices(std::vector<MuSAVtxFitterTool::WrkVrt>& workVerticesContainer) const
219{
220 ATH_MSG_DEBUG("MuSAVtxFitterTool::selectBestVertices");
221 //the default logic will create vertices with all possible combinations of SA muons
222 //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)
223 //if this occurs we can select based on the highest chi2 and remove the poorer-quality vertex from consideration
224
225 //first check if a muon is re-used in multiple vertices
226
227 if (workVerticesContainer.size() < 2) {
228 ATH_MSG_VERBOSE("Only one MuSA vertex, no need to select best!");
229 return StatusCode::SUCCESS;
230 }
231
232 //sort all the vertices by chi2 starting with the lowest
233 std::sort(workVerticesContainer.begin(), workVerticesContainer.end(), [](const MuSAVtxFitterTool::WrkVrt& v1, const MuSAVtxFitterTool::WrkVrt& v2) { return v1.chi2 < v2.chi2; });
234 ATH_MSG_VERBOSE("Sorted vertices by chi2! Lowest chi2: " << workVerticesContainer.front().chi2 << " Highest chi2: " << workVerticesContainer.back().chi2);
235
236 //starting with the lowest chi2 vertex, check its muons and remove any other vertices that use the same muons
237 for (unsigned int i = 0; i < workVerticesContainer.size(); i++) {
238 auto& vertex = workVerticesContainer.at(i);
239 if (!vertex.isGood) {
240 continue; // skip vertices we've already marked as bad
241 }
242 for (unsigned int j = i+1; j < workVerticesContainer.size(); j++) {
243 auto& otherVertex = workVerticesContainer.at(j);
244 for (const auto& muon : vertex.muonCandidates) {
245 if (std::find(otherVertex.muonCandidates.begin(), otherVertex.muonCandidates.end(), muon) != otherVertex.muonCandidates.end()) {
246 //remove the other vertex
247 ATH_MSG_VERBOSE("Found a vertex re-using a muon with chi2: " << otherVertex.chi2 << " removing from consideration!");
248 otherVertex.isGood = false;
249 }
250 }
251 }
252 }
253
254 //remove any vertices marked as bad
255 workVerticesContainer.erase(std::remove_if(workVerticesContainer.begin(), workVerticesContainer.end(),
256 [](const MuSAVtxFitterTool::WrkVrt& vtx) { return !vtx.isGood; }), workVerticesContainer.end());
257
258 return StatusCode::SUCCESS;
259
260
261}
262} // 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,...