ATLAS Offline Software
Loading...
Searching...
No Matches
FastMuonSABuilder.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
6
9
11#include "ActsInterop/Logger.h"
12
13namespace {
15 double calcRedChi2(const MuonR4::Segment& segment) {
16 return segment.nDoF() > 0ul ? segment.chi2() / segment.nDoF() : segment.chi2();
17 }
19 bool betterSegment(const MuonR4::Segment& newSegment, const MuonR4::Segment& oldSegment) {
20 if (newSegment.nDoF() == oldSegment.nDoF()) {
21 return newSegment.chi2() < oldSegment.chi2();
22 }
23 return newSegment.nDoF() > oldSegment.nDoF();
24 }
25 using namespace Muon::MuonStationIndex;
27 unsigned countNSWHits(const std::vector<const MuonR4::SpacePoint*>& hits,
28 const MuonR4::SpacePointBucket* parentBucket) {
29 if (toStationIndex(parentBucket->msSector()->chamberIndex()) != StIndex::EI) {
30 return 0;
31 }
32 return std::ranges::count_if(hits, [](const MuonR4::SpacePoint* hit) {
33 return xAOD::isNSW(hit->type()); });
34 }
36 auto layerRank = [](LayerIndex l) {
37 switch (l) {
38 case LayerIndex::Inner: return 0;
39 case LayerIndex::Middle: return 1;
40 case LayerIndex::Outer: return 2;
41 case LayerIndex::BarrelExtended: return 3;
42 case LayerIndex::Extended: return 4;
43 default: return 5;
44 }
45 };
46}
47
48namespace MuonR4::FastReco {
49using namespace Muon::MuonStationIndex;
50using namespace MuonR4::SegmentFit;
51using namespace Acts::UnitLiterals;
52
53constexpr auto phiIdx {Acts::toUnderlying(ParamDefs::phi)};
54constexpr auto x0Idx {Acts::toUnderlying(ParamDefs::x0)};
55constexpr auto thetaIdx {Acts::toUnderlying(ParamDefs::theta)};
56constexpr auto y0Idx {Acts::toUnderlying(ParamDefs::y0)};
57constexpr auto etaCovIdx {Acts::toUnderlying(SpacePoint::CovIdx::etaCov)};
58
59FastMuonSABuilder::FastMuonSABuilder(const std::string& name, Config&& config) :
60 AthMessaging{name}, m_cfg{std::move(config)} {
63 fitCfg.calibrator = m_cfg.calibrator;
64 fitCfg.visionTool = m_cfg.visionTool;
65 fitCfg.idHelperSvc = m_cfg.idHelperSvc;
66 fitCfg.fitT0 = false;
67 fitCfg.calcAlongStrip = false;
68 fitCfg.recalibrate = m_cfg.recalibInFit;
69 fitCfg.useFastFitter = m_cfg.useFastFitter;
70 fitCfg.fastPreFitter = m_cfg.fastPreFitter;
71 fitCfg.ignoreFailedPreFit = m_cfg.ignoreFailedPreFit;
72 fitCfg.useHessian = m_cfg.useHessianResidual;
73 fitCfg.doBeamSpot = false;
74 fitCfg.outlierRemovalCut = m_cfg.outlierRemovalCut;
75 fitCfg.recoveryPull = m_cfg.recoveryPull;
76 fitCfg.nPrecHitCut = m_cfg.precHitCut;
77 fitCfg.maxIter = m_cfg.maxIter;
78 fitCfg.parsToUse = {ParamDefs::y0, ParamDefs::theta};
79
81 SegmentLineFitter::Config nswFitCfg{fitCfg};
82 nswFitCfg.parsToUse = {ParamDefs::x0, ParamDefs::y0, ParamDefs::theta, ParamDefs::phi};
83
84 m_fitter = std::make_unique<LineFitter>(name, std::move(fitCfg));
85 m_nswFitter = std::make_unique<LineFitter>(name, std::move(nswFitCfg));
86
88 MdtSegmentSeeder::Config genCfg{};
89 genCfg.hitPullCut = m_cfg.seedHitChi2;
90 genCfg.busyLayerLimit = 3.;
91 genCfg.startWithPattern = false;
92 m_mdtSeeder = std::make_unique<MdtSegmentSeeder>(std::move(genCfg), makeActsAthenaLogger(this, name));
93
94 }
95
98 const ActsTrk::GeometryContext& gctx,
99 const GlobalPattern& pattern,
100 MuonCont_t& outMuons) const {
101 ATH_MSG_VERBOSE(__func__<<"() Start processing " << pattern);
102
103 std::vector<StIndex> stations {pattern.getStations()};
104 std::ranges::sort(stations, [](StIndex s1, StIndex s2) {
105 LayerIndex l1 {toLayerIndex(s1)}, l2 {toLayerIndex(s2)};
106 if (l1 == l2) {
107 return isBarrel(s1);
108 }
109 return layerRank(l1) < layerRank(l2);
110 });
111
112 std::vector<Segment_t> muonSegments{};
113 muonSegments.reserve(3u);
114 for (const StIndex st : stations) {
117 if (muonSegments.size() > 2 || (st == stations.back() && muonSegments.size() == 0.)) {
118 break;
119 }
120 // If we already have a segment in the layer, we don't try to fit another one
121 const LayerIndex layer {toLayerIndex(st)};
122 if (std::ranges::any_of(muonSegments, [&layer](const Segment_t& seg) {
123 return toLayerIndex(
124 seg->measurements().back()->spacePoint()->msSector()->chamberIndex()) == layer; })) {
125 ATH_MSG_VERBOSE(__func__<<"() Already found a segment in layer " << layer
126 << " - skip station " << st);
127 continue;
128 }
130 const HitVec_t& hits {pattern.hitsInStation(st)};
131 const std::vector<Bucket_t>& buckets {pattern.bucketsInStation(st)};
132
134 std::unordered_map<const MuonGMR4::SpectrometerSector*, HitVec_t> hitsPerSector{};
135 std::ranges::for_each(hits, [&hitsPerSector](Hit_t hit) {
136 hitsPerSector[hit->msSector()].push_back(hit);
137 });
140 std::vector<const MuonGMR4::SpectrometerSector*> sectorsInStation{};
141 sectorsInStation.reserve(hitsPerSector.size());
142 std::ranges::transform(hitsPerSector, std::back_inserter(sectorsInStation),
143 [](const auto& pair) { return pair.first; });
144 std::ranges::sort(sectorsInStation, std::ranges::greater{},
145 [&hitsPerSector](const MuonGMR4::SpectrometerSector* s) {
146 return hitsPerSector[s].size(); });
147
148 std::vector<Segment_t> stSegments{};
149 stSegments.reserve(sectorsInStation.size());
152 for (const MuonGMR4::SpectrometerSector* sector : sectorsInStation) {
153 ATH_MSG_VERBOSE(__func__<<"() Start segment fitting in sector " << sector->identString()
154 <<" station " << st << " with " << hitsPerSector[sector].size() << " hits.");
155 const HitVec_t& sectorHits {hitsPerSector[sector]};
156
159 std::vector<Bucket_t> bucketsInSector {};
160 std::ranges::copy_if(buckets, std::back_inserter(bucketsInSector),
161 [&sector](Bucket_t bucket) { return bucket->msSector() == sector;
162 });
163 if (bucketsInSector.empty()) {
164 throw std::runtime_error(std::format("No parent bucket found for sector {} in station {}", sector->identString(), stName(st)));
165 }
166 Bucket_t parentBucket {bucketsInSector.size() < 2u ? bucketsInSector.back()
167 : *std::ranges::max_element(bucketsInSector, std::ranges::less{}, [&sectorHits](const Bucket_t& b) {
168 return std::ranges::count_if(sectorHits, [&b](const Hit_t& hit) {
169 return std::ranges::any_of(*b, [&hit](const auto& h) {
170 return h.get() == hit; });
171 });
172 })};
173
174 Segment_t segment {fitSegment(ctx, sector->localToGlobalTransform(gctx), parentBucket, std::move(hitsPerSector[sector]))};
175 if (segment) {
176 //coverity[RW.NO_MATCHING_FUNCTION:FALSE]
177 ATH_MSG_VERBOSE(__func__<<"() Successfully fitted segment in station "<< st <<": Pos: "
178 << Amg::toString(segment->position())<< ", dir: "<< Amg::toString(segment->direction())
179 << ", chi2: "<< segment->chi2()<<", nDoF: "<<segment->nDoF()<<std::endl << print(segment->measurements()));
180 stSegments.push_back(std::move(segment));
181 // If we found a good segment, we don't try to fit segments in other sectors of the same station.
182 if (calcRedChi2(*stSegments.back()) <= m_cfg.goodSegmentCut) {
183 break;
184 }
185 continue;
186 }
187 ATH_MSG_VERBOSE(__func__<<"() No segment could be fitted. Try next sector, if any.");
188 }
189 if (stSegments.empty()) {
190 ATH_MSG_DEBUG("No segment could be fitted in station " << st << " for pattern" << pattern);
191 continue;
192 }
193 Segment_t& bestStSegment {stSegments.size() > 1
194 ? *std::ranges::max_element(stSegments, [](const Segment_t& s1, const Segment_t& s2) {
195 return betterSegment(*s2, *s1); })
196 : stSegments.back()};
197 ATH_MSG_VERBOSE(__func__<<"() Best segment in station " << st << ": Pos: " << Amg::toString(bestStSegment->position())
198 << ", dir: " << Amg::toString(bestStSegment->direction()) << ", chi2: " << bestStSegment->chi2() << ", nDoF: " << bestStSegment->nDoF());
199
200 muonSegments.push_back(std::move(bestStSegment));
201 }
202
203 ATH_MSG_VERBOSE(__func__<<"() Found "<< muonSegments.size()<<" muon segments to construct a candidate.");
204 if (muonSegments.size() < 2) {
205 ATH_MSG_DEBUG(__func__<<"() Not enough muon segments to construct a candidate - abort.");
206 return nullptr;
207 }
208 /* Sort station points by radial distance. Needed when we have segments in Extended layers */
209 std::ranges::sort(muonSegments, std::ranges::less{},
210 [](const Segment_t& seg) { return seg->position().perp(); });
211
212 const Amg::Vector3D planeNorm {Acts::makeDirectionFromPhiTheta(pattern.phi() + 90._degree, 90._degree)};
213
214 std::vector<ITrackSeedingTool::PosMomPair_t> circlePoints{};
215 for (const Segment_t& seg : muonSegments) {
216 if (!seg){
217 continue;
218 }
219 int sector {seg->measurements().back()->spacePoint()->msSector()->sector()};
220 Amg::Vector3D projDir {ExpandedSector{static_cast<unsigned>(sector),
222
223 Amg::Vector3D projPos {Acts::PlanarHelper::intersectPlane(seg->position(), projDir,
224 planeNorm, Amg::Vector3D::Zero()).position()};
225
226 circlePoints.emplace_back(std::move(projPos), Amg::projectDirOntoPlane(seg->direction(), planeNorm));
227 }
228 assert(muonSegments.size() <= 3);
229 const double qtimesP = m_cfg.trackSeeder->estimateQtimesP(ctx, planeNorm, circlePoints);
230
231 const double eta {muonSegments[0]->position().eta()};
232 const double pt {std::abs(qtimesP) / std::cosh(eta)};
233
234 xAOD::Muon* newMuon = outMuons->push_back(std::make_unique<xAOD::Muon>());
235 newMuon->setAuthor(xAOD::Muon::Author::MuidSA);
236 newMuon->setP4(pt, eta, pattern.phi());
237 newMuon->setCharge(qtimesP > 0. ? 1 : -1);
238 newMuon->setMuonType(xAOD::Muon::MuonType::MuonStandAlone);
239 return newMuon;
240}
242//coverity[routine_not_emitted:FALSE]
243FastMuonSABuilder::fitSegment(const EventContext& ctx,
244 const Amg::Transform3D& localToGlobal,
245 Bucket_t parentBucket,
246 std::vector<Hit_t>&& hits) const {
248 const unsigned firstLayer {m_spSorter.sectorLayerNum(*hits.front())};
249 if (hits.size() < 2 ||
250 std::ranges::none_of(hits, [&](Hit_t hit) {
251 return m_spSorter.sectorLayerNum(*hit) != firstLayer; })) {
252 ATH_MSG_DEBUG(__func__<<"() Not enough layers with hits to fit a segment, skipping!");
253 return nullptr;
254 }
255 Acts::CalibrationContext cctx {ActsTrk::getCalibrationContext(ctx)};
256
258 Parameters initialPars{};
259 initialPars[phiIdx] = 90._degree;
260 initialPars[x0Idx] = 0;
261
264 if (const unsigned nNSWhits {countNSWHits(hits, parentBucket)}; nNSWhits > 0) {
265
266 if (nNSWhits < hits.size()) {
267 ATH_MSG_DEBUG(__func__<<"() Mixed hit types: "<<nNSWhits<<" NSW over "
268 <<hits.size()<<" total hits in the segment seed.");
269
270 std::vector<Hit_t> selHits{};
271 const bool useNSW {nNSWhits > (hits.size() - nNSWhits)};
272 std::ranges::copy_if(hits, std::back_inserter(selHits), [useNSW](Hit_t hit) {
273 return xAOD::isNSW(hit->type()) == useNSW; });
274 std::swap(hits, selHits);
275 }
276 /* Estimate initial parameters in bending direction with a linear regression */
277 auto validHits = estimateBendingPars(std::move(hits), initialPars);
278 const auto [locPos, locDir] {makeLine(initialPars)};
279 ATH_MSG_VERBOSE(__func__<<"() Initial parameters: "<<toString(initialPars)
280 << ", hits: "<<print(validHits));
281
282 auto houghSeed {std::make_unique<SegmentSeed>(0., 0., 0., 0., 0., std::move(validHits), parentBucket)};
283
285 CalibSpacePointVec calibHits{m_cfg.calibrator->calibrate(ctx, houghSeed->getHitsInMax(), locPos, locDir, 0.)};
286 return m_nswFitter->fitSegment(ctx, houghSeed.get(), initialPars, localToGlobal, std::move(calibHits));
287 }
288
291 auto houghSeed {std::make_unique<SegmentSeed>(0., 0., 0., 0., 0., std::move(hits), parentBucket)};
292 std::vector<Segment_t> segments{};
293 MdtSegmentSeeder::State_t seedState{initialPars, houghSeed.get(), m_cfg.calibrator, m_cfg.recalibSeed};
294
295 ATH_MSG_VERBOSE(__func__<<"() Start segment seed search");
296 //coverity[routine_not_emitted:FALSE]
297 while (auto seed = m_mdtSeeder->nextSeed(cctx, seedState)) {
298 ATH_MSG_VERBOSE(__func__<<"() Found a seed. Try to fit the segment...");
299
300 Segment_t segment {m_fitter->fitSegment(ctx, houghSeed.get(), seed->parameters,
301 localToGlobal, std::move(seed->hits))};
302 if (segment) {
303 segments.push_back(std::move(segment));
304 }
305 }
306
307 if (!segments.empty()) {
308 ATH_MSG_VERBOSE(__func__<<"() In total "<<segments.size()<<" segment were constructed. Keep the best one.");
309 if (msgLvl(MSG::VERBOSE) && segments.size() > 1) {
310 for (const Segment_t& seg : segments) {
311 //coverity[RW.NO_MATCHING_FUNCTION:FALSE]
312 ATH_MSG_VERBOSE(__func__<<"() Segment: Pos: "<<Amg::toString(seg->position())
313 <<", dir: "<<Amg::toString(seg->direction())<<", chi2: "<<seg->chi2()
314 <<", nDoF: "<<seg->nDoF()<<std::endl<<print(seg->measurements()));
315 }
316 }
317 return std::move(*std::ranges::max_element(segments, [&](const Segment_t& s1, const Segment_t& s2) {
318 return betterSegment(*s2, *s1);
319 }));
320 }
321 ATH_MSG_VERBOSE(__func__<<"() No segment seeds could be fitted.");
322 return nullptr;
323}
326 Parameters& pars) const {
327 // Estimate line parameters in the bending plane using a weighted linear regression.
328 double S {0.}, Sy {0.}, Sz {0.}, Szz {0.}, Syz {0.};
329 HitVec_t validHits{};
330 for (const auto& hit : hits) {
331 const Amg::Vector3D& locPos {hit->localPosition()};
332 const double z {locPos.z()};
333 const double y {locPos.y()};
334 const double sigma2 {hit->covariance()[etaCovIdx]};
335 if (sigma2 < 1e-4) {
336 ATH_MSG_WARNING(__func__<<"() Hit"<< *hit<<" with very small eta covariance: " << sigma2 << ". Skipping the measurement.");
337 continue;
338 }
339 validHits.push_back(hit);
340 const double w {1./sigma2};
341
342 S += w;
343 Sy += w * y;
344 Sz += w * z;
345 Szz += w * z * z;
346 Syz += w * z * y;
347 }
348 const double det {S * Szz - Sz * Sz};
349
350 if (std::abs(det) < 1e-6) {
351 ATH_MSG_VERBOSE(__func__<<"() Degenerate weighted regression, using furthest hits...");
352 const auto [minZHit, maxZHit] = std::ranges::minmax_element(hits, std::ranges::less{},
353 [](const Hit_t& h) { return h->localPosition().z(); });
354 const Amg::Vector3D& minLocPos {(*minZHit)->localPosition()};
355 const Amg::Vector3D& maxLocPos {(*maxZHit)->localPosition()};
356 const double deltaZ {maxLocPos.z() - minLocPos.z()};
357 // This should not happen since we required at least 2 different layers.
358 assert(std::abs(deltaZ) > 1e-3);
359
360 pars[thetaIdx] = std::atan2(maxLocPos.y() - minLocPos.y(), deltaZ);
361 pars[y0Idx] = minLocPos.y() - std::tan(pars[thetaIdx]) * minLocPos.z();
362 return validHits;
363 }
364 //coverity[DIVIDE_BY_ZERO:FALSE]
365 const double slope { (S * Syz - Sz * Sy) / det };
366 //coverity[DIVIDE_BY_ZERO:FALSE]
367 const double intercept { (Szz * Sy - Sz * Syz) / det };
368
369 pars[thetaIdx] = std::atan(slope);
370 pars[y0Idx] = intercept;
371 return validHits;
372}
373
374}
Scalar eta() const
pseudorapidity method
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
std::unique_ptr< const Acts::Logger > makeActsAthenaLogger(IMessageSvc *svc, const std::string &name, int level, std::optional< std::string > parent_name)
#define y
#define z
bool msgLvl(const MSG::Level lvl) const
Test the output level.
AthMessaging(IMessageSvc *msgSvc, const std::string &name)
Constructor.
A spectrometer sector forms the envelope of all chambers that are placed in the same MS sector & laye...
Muon::MuonStationIndex::ChIndex chamberIndex() const
Returns the chamber index scheme.
Amg::Vector3D normalDir() const
Returns the vector that is normal to the plane spanned by the expanded sector.
@ center
Project the segment onto the overlap with the previous sector.
HitVec_t estimateBendingPars(HitVec_t &&hits, Parameters &pars) const
Estimate the bending parameters of a segment through a weighted linear regression.
std::unique_ptr< MdtSegmentSeeder > m_mdtSeeder
Pointer to the L-R segment seeder.
FastMuonSABuilder(const std::string &name, Config &&config)
Standard constructor.
Muon::MuonStationIndex::StIndex StIndex
Type alias for the station index.
std::unique_ptr< Segment > Segment_t
Type alias for the segment type.
SpacePointPerLayerSorter m_spSorter
Spacepoint sorter per logical measurement layer.
xAOD::Muon * buildMuonCandidate(const EventContext &ctx, const ActsTrk::GeometryContext &gctx, const GlobalPattern &pattern, MuonCont_t &outMuons) const
Main methods steering the muon candidate building.
std::unique_ptr< LineFitter > m_fitter
Pointer to the actual segment fitter.
const SpacePoint * Hit_t
Type alias for the hit type & associated vector.
const SpacePointBucket * Bucket_t
Type alias for the bucket type.
Segment_t fitSegment(const EventContext &ctx, const Amg::Transform3D &localToGlobal, Bucket_t parentBucket, std::vector< Hit_t > &&hits) const
Fit a segment using the provided seed.
std::unique_ptr< LineFitter > m_nswFitter
Pointer to the NSW segment fitter.
Config m_cfg
Global Pattern Recognition configuration.
xAOD::FillContainer< xAOD::MuonContainer, xAOD::MuonAuxContainerR4 > MuonCont_t
Define the muon container type.
SegmentFit::Parameters Parameters
Type alias for the segment fitting parameters.
Data class to represent an eta maximum in hough space.
std::vector< CalibSpacePointPtr > CalibSpacePointVec
SeedingState< HitVec_t, CalibCont_t, SeederStateBase > State_t
Define the state holder object.
Placeholder for what will later be the muon segment EDM representation.
unsigned int nDoF() const
Returns the number of degrees of freedom.
: The muon space point bucket represents a collection of points that will bre processed together in t...
const MuonGMR4::SpectrometerSector * msSector() const
returns th associated muonChamber
The muon space point is the combination of two uncalibrated measurements one of them measures the eta...
STL class.
void setAuthor(const Author auth)
set author
void setMuonType(MuonType type)
void setP4(double pt, double eta, double phi)
Set method for IParticle values.
Definition Muon_v1.cxx:71
void setCharge(float charge)
Set the charge (must be the same as primaryTrackParticle() ).
Acts::CalibrationContext getCalibrationContext(const EventContext &ctx)
The Acts::Calibration context is piped through the Acts fitters to (re)calibrate the Acts::SourceLink...
Amg::Vector3D projectDirOntoPlane(const Amg::Vector3D &direction, const Amg::Vector3D &planeNorm)
Project the direction vector onto the plane and renormalize to unity.
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Affine3d Transform3D
Eigen::Matrix< double, 3, 1 > Vector3D
constexpr auto etaCovIdx
constexpr auto thetaIdx
constexpr auto phiIdx
std::pair< Amg::Vector3D, Amg::Vector3D > makeLine(const Parameters &pars)
Returns the parsed parameters into an Eigen line parametrization.
std::string toString(const Parameters &pars)
Dumps the parameters into a string with labels in front of each number.
ISpacePointCalibrator::CalibSpacePointVec CalibSpacePointVec
std::string print(const cont_t &container)
Print a space point container to string.
StIndex toStationIndex(ChIndex index)
convert ChIndex into StIndex
bool isBarrel(const ChIndex index)
Returns true if the chamber index points to a barrel chamber.
LayerIndex
enum to classify the different layers in the muon spectrometer
const std::string & stName(StIndex index)
convert StIndex into a string
LayerIndex toLayerIndex(ChIndex index)
convert ChIndex into LayerIndex
l
Printing final latex table to .tex output file.
unsigned long ul
STL namespace.
void swap(ElementLinkVector< DOBJ > &lhs, ElementLinkVector< DOBJ > &rhs)
bool isNSW(const UncalibMeasType aodType)
Returns whether the measurement is a NSW measurement.
Muon_v1 Muon
Reference the current persistent version:
const ISpacePointCalibrator * calibrator
Pointer to the calibrator.
bool doBeamSpot
Switch to insert a beamspot constraint if possible.
const Muon::IMuonIdHelperSvc * idHelperSvc
Pointer to the idHelperSvc.
unsigned nPrecHitCut
Minimum number of precision hits.
double outlierRemovalCut
Cut on the segment chi2 / nDoF to launch the outlier removal.
const MuonValR4::IPatternVisualizationTool * visionTool
Pointer to the visualization tool.
double recoveryPull
Maximum pull on a measurement to add it back on the line.