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 = config.calibrator;
64 fitCfg.visionTool = config.visionTool;
65 fitCfg.idHelperSvc = config.idHelperSvc;
66 fitCfg.fitT0 = false;
67 fitCfg.calcAlongStrip = false;
68 fitCfg.recalibrate = config.recalibInFit;
69 fitCfg.useFastFitter = config.useFastFitter;
70 fitCfg.fastPreFitter = config.fastPreFitter;
71 fitCfg.ignoreFailedPreFit = config.ignoreFailedPreFit;
72 fitCfg.useHessian = config.useHessianResidual;
73 fitCfg.doBeamSpot = false;
74 fitCfg.outlierRemovalCut = config.outlierRemovalCut;
75 fitCfg.recoveryPull = config.recoveryPull;
76 fitCfg.nPrecHitCut = config.precHitCut;
77 fitCfg.maxIter = config.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 = config.seedHitChi2;
90 genCfg.busyLayerLimit = 3.;
91 genCfg.startWithPattern = false;
92 m_mdtSeeder = std::make_unique<MdtSegmentSeeder>(std::move(genCfg), makeActsAthenaLogger(this, name));
93
95 MsTrackSeeder::Config trackCfg{};
96 trackCfg.nFieldSteps = config.nFieldSteps;
97 m_trackSeeder = std::make_unique<MsTrackSeeder>(name, std::move(trackCfg));
98
99 }
102 const ActsTrk::GeometryContext& gctx,
103 const AtlasFieldCacheCondObj& magField,
104 const GlobalPattern& pattern,
105 MuonCont_t& outMuons) const {
106 ATH_MSG_VERBOSE(__func__<<"() Start processing " << pattern);
107
108 std::vector<StIndex> stations {pattern.getStations()};
109 std::ranges::sort(stations, [](StIndex s1, StIndex s2) {
110 LayerIndex l1 {toLayerIndex(s1)}, l2 {toLayerIndex(s2)};
111 if (l1 == l2) {
112 return isBarrel(s1);
113 }
114 return layerRank(l1) < layerRank(l2);
115 });
116
117 std::vector<Segment_t> muonSegments{};
118 for (const StIndex st : stations) {
120 if (muonSegments.size() > 2) {
121 break;
122 }
123 // If we already have a segment in the layer, we don't try to fit another one
124 const LayerIndex layer {toLayerIndex(st)};
125 if (std::ranges::any_of(muonSegments, [&layer](const Segment_t& seg) {
126 return toLayerIndex(
127 seg->measurements().back()->spacePoint()->msSector()->chamberIndex()) == layer; })) {
128 ATH_MSG_VERBOSE(__func__<<"() Already found a segment in layer " << layer
129 << " - skip station " << st);
130 continue;
131 }
133 const HitVec_t& hits {pattern.hitsInStation(st)};
134 const std::vector<Bucket_t>& buckets {pattern.bucketsInStation(st)};
135
137 std::unordered_map<const MuonGMR4::SpectrometerSector*, HitVec_t> hitsPerSector{};
138 std::ranges::for_each(hits, [&hitsPerSector](Hit_t hit) {
139 hitsPerSector[hit->msSector()].push_back(hit);
140 });
143 std::vector<const MuonGMR4::SpectrometerSector*> sectorsInStation{};
144 sectorsInStation.reserve(hitsPerSector.size());
145 std::ranges::transform(hitsPerSector, std::back_inserter(sectorsInStation),
146 [](const auto& pair) { return pair.first; });
147 std::ranges::sort(sectorsInStation, std::ranges::greater{},
148 [&hitsPerSector](const MuonGMR4::SpectrometerSector* s) {
149 return hitsPerSector[s].size(); });
150
151 std::vector<Segment_t> stSegments{};
154 for (const MuonGMR4::SpectrometerSector* sector : sectorsInStation) {
155 ATH_MSG_VERBOSE(__func__<<"() Start segment fitting in sector " << sector->identString()
156 <<" station " << st << " with " << hitsPerSector[sector].size() << " hits.");
157 const HitVec_t& sectorHits {hitsPerSector[sector]};
158
161 std::vector<Bucket_t> bucketsInSector {};
162 std::ranges::copy_if(buckets, std::back_inserter(bucketsInSector),
163 [&sector](Bucket_t bucket) { return bucket->msSector() == sector;
164 });
165 if (bucketsInSector.empty()) {
166 throw std::runtime_error(std::format("No parent bucket found for sector {} in station {}", sector->identString(), stName(st)));
167 }
168 Bucket_t parentBucket {bucketsInSector.size() < 2u ? bucketsInSector.back()
169 : *std::ranges::max_element(bucketsInSector, std::ranges::less{}, [&sectorHits](const Bucket_t& b) {
170 return std::ranges::count_if(sectorHits, [&b](const Hit_t& hit) {
171 return std::ranges::any_of(*b, [&hit](const auto& h) {
172 return h.get() == hit; });
173 });
174 })};
175
176 Segment_t segment {fitSegment(ctx, sector->localToGlobalTransform(gctx), parentBucket, std::move(hitsPerSector[sector]))};
177 if (segment) {
178 ATH_MSG_VERBOSE(__func__<<"() Successfully fitted segment in station "<< st <<": Pos: "
179 << Amg::toString(segment->position())<< ", dir: "<< Amg::toString(segment->direction())
180 << ", chi2: "<< segment->chi2()<<", nDoF: "<<segment->nDoF()<<std::endl << print(segment->measurements()));
181 stSegments.push_back(std::move(segment));
182 // If we found a good segment, we don't try to fit segments in other sectors of the same station.
183 if (calcRedChi2(*stSegments.back()) <= m_cfg.goodSegmentCut) {
184 break;
185 }
186 continue;
187 }
188 ATH_MSG_VERBOSE(__func__<<"() No segment could be fitted. Try next sector, if any.");
189 }
190 if (stSegments.empty()) {
191 ATH_MSG_DEBUG("No segment could be fitted in station " << st << " for pattern" << pattern);
192 continue;
193 }
194 Segment_t& bestStSegment {stSegments.size() > 1
195 ? *std::ranges::max_element(stSegments, [](const Segment_t& s1, const Segment_t& s2) {
196 return betterSegment(*s2, *s1); })
197 : stSegments.back()};
198 ATH_MSG_VERBOSE(__func__<<"() Best segment in station " << st << ": Pos: " << Amg::toString(bestStSegment->position())
199 << ", dir: " << Amg::toString(bestStSegment->direction()) << ", chi2: " << bestStSegment->chi2() << ", nDoF: " << bestStSegment->nDoF());
200
201 muonSegments.push_back(std::move(bestStSegment));
202 }
203
204 ATH_MSG_VERBOSE(__func__<<"() Found "<< muonSegments.size()<<" muon segments to construct a candidate.");
205 if (muonSegments.size() < 2) {
206 ATH_MSG_DEBUG(__func__<<"() Not enough muon segments to construct a candidate - abort.");
207 return nullptr;
208 }
209 /* Sort station points by radial distance. Needed when we have segments in Extended layers */
210 std::ranges::sort(muonSegments, std::ranges::less{},
211 [](const Segment_t& seg) { return seg->position().perp(); });
212
213 const Amg::Vector3D planeNorm {Acts::makeDirectionFromPhiTheta(pattern.phi() + 90._degree, 90._degree)};
214 auto point = [&planeNorm, this](const Segment_t& seg) {
215 int sector {seg->measurements().back()->spacePoint()->msSector()->sector()};
216 return std::make_pair(m_trackSeeder->segPosOntoPhiPlane(planeNorm, sector, seg->position()),
217 m_trackSeeder->segDirOntoPhiPlane(planeNorm, seg->direction()));
218 };
219
220 assert(muonSegments.size() <= 3);
221 const double qtimesP {muonSegments.size() == 3
222 ? m_trackSeeder->estimateQtimesP(magField, planeNorm, point(muonSegments[0]), point(muonSegments[1]), point(muonSegments[2]))
223 : m_trackSeeder->estimateQtimesP(magField, planeNorm, point(muonSegments[0]), point(muonSegments[1]))};
224
225 const double theta {muonSegments[0]->position().theta()};
226 const double eta {-std::log(std::tan(theta/2.))};
227 const double pt {std::abs(qtimesP) * std::sin(theta)};
228
229 xAOD::Muon* newMuon = outMuons->push_back(std::make_unique<xAOD::Muon>());
230 newMuon->setAuthor(xAOD::Muon::Author::MuidSA);
231 newMuon->setP4(pt, eta, pattern.phi());
232 newMuon->setCharge(qtimesP > 0. ? 1 : -1);
233 newMuon->setMuonType(xAOD::Muon::MuonType::MuonStandAlone);
234 return newMuon;
235}
237FastMuonSABuilder::fitSegment(const EventContext& ctx,
238 const Amg::Transform3D& localToGlobal,
239 Bucket_t parentBucket,
240 std::vector<Hit_t>&& hits) const {
242 const unsigned firstLayer {m_spSorter.sectorLayerNum(*hits.front())};
243 if (hits.size() < 2 ||
244 std::ranges::none_of(hits, [&](Hit_t hit) {
245 return m_spSorter.sectorLayerNum(*hit) != firstLayer; })) {
246 ATH_MSG_DEBUG(__func__<<"() Not enough layers with hits to fit a segment, skipping!");
247 return nullptr;
248 }
249 Acts::CalibrationContext cctx {ActsTrk::getCalibrationContext(ctx)};
250
252 Parameters initialPars{};
253 initialPars[phiIdx] = 90._degree;
254 initialPars[x0Idx] = 0;
255
258 if (const unsigned nNSWhits {countNSWHits(hits, parentBucket)}; nNSWhits > 0) {
259
260 if (nNSWhits < hits.size()) {
261 ATH_MSG_DEBUG(__func__<<"() Mixed hit types: "<<nNSWhits<<" NSW over "
262 <<hits.size()<<" total hits in the segment seed.");
263
264 std::vector<Hit_t> selHits{};
265 const bool useNSW {nNSWhits > (hits.size() - nNSWhits)};
266 std::ranges::copy_if(hits, std::back_inserter(selHits), [useNSW](Hit_t hit) {
267 return xAOD::isNSW(hit->type()) == useNSW; });
268 std::swap(hits, selHits);
269 }
270 /* Estimate initial parameters in bending direction with a linear regression */
271 auto validHits = estimateBendingPars(std::move(hits), initialPars);
272 const auto [locPos, locDir] {makeLine(initialPars)};
273 ATH_MSG_VERBOSE(__func__<<"() Initial parameters: "<<toString(initialPars)
274 << ", hits: "<<print(validHits));
275
276 auto houghSeed {std::make_unique<SegmentSeed>(0., 0., 0., 0., 0., std::move(validHits), parentBucket)};
277
279 CalibSpacePointVec calibHits{m_cfg.calibrator->calibrate(ctx, houghSeed->getHitsInMax(), locPos, locDir, 0.)};
280 return m_nswFitter->fitSegment(ctx, houghSeed.get(), initialPars, localToGlobal, std::move(calibHits));
281 }
282
285 auto houghSeed {std::make_unique<SegmentSeed>(0., 0., 0., 0., 0., std::move(hits), parentBucket)};
286 std::vector<Segment_t> segments{};
287 MdtSegmentSeeder::State_t seedState{initialPars, houghSeed.get(), m_cfg.calibrator, m_cfg.recalibSeed};
288
289 ATH_MSG_VERBOSE(__func__<<"() Start segment seed search");
290 while (auto seed = m_mdtSeeder->nextSeed(cctx, seedState)) {
291 ATH_MSG_VERBOSE(__func__<<"() Found a seed. Try to fit the segment...");
292
293 Segment_t segment {m_fitter->fitSegment(ctx, houghSeed.get(), seed->parameters,
294 localToGlobal, std::move(seed->hits))};
295 if (segment) {
296 segments.push_back(std::move(segment));
297 }
298 }
299
300 if (!segments.empty()) {
301 ATH_MSG_VERBOSE(__func__<<"() In total "<<segments.size()<<" segment were constructed. Keep the best one.");
302 if (msgLvl(MSG::VERBOSE) && segments.size() > 1) {
303 for (const Segment_t& seg : segments) {
304 ATH_MSG_VERBOSE(__func__<<"() Segment: Pos: "<<Amg::toString(seg->position())
305 <<", dir: "<<Amg::toString(seg->direction())<<", chi2: "<<seg->chi2()
306 <<", nDoF: "<<seg->nDoF()<<std::endl<<print(seg->measurements()));
307 }
308 }
309 return std::move(*std::ranges::max_element(segments, [&](const Segment_t& s1, const Segment_t& s2) {
310 return betterSegment(*s2, *s1);
311 }));
312 }
313 ATH_MSG_VERBOSE(__func__<<"() No segment seeds could be fitted.");
314 return nullptr;
315}
318 Parameters& pars) const {
319 // Estimate line parameters in the bending plane using a weighted linear regression.
320 double S {0.}, Sy {0.}, Sz {0.}, Szz {0.}, Syz {0.};
321 HitVec_t validHits{};
322 for (const auto& hit : hits) {
323 const Amg::Vector3D& locPos {hit->localPosition()};
324 const double z {locPos.z()};
325 const double y {locPos.y()};
326 const double sigma2 {hit->covariance()[etaCovIdx]};
327 if (sigma2 < 1e-4) {
328 ATH_MSG_WARNING(__func__<<"() Hit"<< *hit<<" with very small eta covariance: " << sigma2 << ". Skipping the measurement.");
329 continue;
330 }
331 validHits.push_back(hit);
332 const double w {1./sigma2};
333
334 S += w;
335 Sy += w * y;
336 Sz += w * z;
337 Szz += w * z * z;
338 Syz += w * z * y;
339 }
340 const double det {S * Szz - Sz * Sz};
341
342 if (std::abs(det) < 1e-6) {
343 ATH_MSG_VERBOSE(__func__<<"() Degenerate weigthed regression, using furthest hits...");
344 const auto [minZHit, maxZHit] = std::ranges::minmax_element(hits, std::ranges::less{},
345 [](const Hit_t& h) { return h->localPosition().z(); });
346 const Amg::Vector3D& minLocPos {(*minZHit)->localPosition()};
347 const Amg::Vector3D& maxLocPos {(*maxZHit)->localPosition()};
348 const double deltaZ {maxLocPos.z() - minLocPos.z()};
349 // This should not happen since we required at least 2 different layers.
350 assert(std::abs(deltaZ) > 1e-3);
351
352 pars[thetaIdx] = std::atan2(maxLocPos.y() - minLocPos.y(), deltaZ);
353 pars[y0Idx] = minLocPos.y() - std::tan(pars[thetaIdx]) * minLocPos.z();
354 return validHits;
355 }
356 const double slope { (S * Syz - Sz * Sy) / det };
357 const double intercept { (Szz * Sy - Sz * Syz) / det };
358
359 pars[thetaIdx] = std::atan(slope);
360 pars[y0Idx] = intercept;
361 return validHits;
362}
363
364}
Scalar eta() const
pseudorapidity method
Scalar theta() const
theta 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.
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.
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.
std::unique_ptr< MsTrackSeeder > m_trackSeeder
Pointer to the track seeder for momentum estimate.
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.
xAOD::Muon * buildMuonCandidate(const EventContext &ctx, const ActsTrk::GeometryContext &gctx, const AtlasFieldCacheCondObj &magField, const GlobalPattern &pattern, MuonCont_t &outMuons) const
Main methods steering the muon candidate building.
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...
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:
Configuration object.
unsigned nFieldSteps
number of steps between two segments to integrate the magnetic field
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.