ATLAS Offline Software
Loading...
Searching...
No Matches
SegmentLineFitter.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
5// Compile this file assuming that FP operations may trap.
6// Prevents spurious FPEs in the clang build.
9
12
16
17#include <ActsInterop/Logger.h>
20
21
22#include <format>
23
28
29
30namespace MuonR4::SegmentFit{
31 using namespace Acts;
32 using namespace Acts::UnitLiterals;
33
37
38 namespace {
39
40 constexpr double calcRedChi2(const Result_t& result) {
41 return result.nDoF > 0ul ? result.chi2 / result.nDoF : result.chi2;
42 }
45 inline unsigned countPrecHits(const HitVec_t& hits) {
46 return std::ranges::count_if(hits, [](const Hit_t& hit){
47 return isPrecisionHit(*hit);
48 });
49 }
51 inline unsigned countPhiHits(const HitVec_t& hits) {
52 return std::ranges::count_if(hits, [](const Hit_t& hit){
53 return isGoodHit(*hit) && hit->measuresPhi();
54 });
55 }
59 inline void removeBeamSpot(HitVec_t& hits){
60 hits.erase(std::remove_if(hits.begin(), hits.end(),
61 [](const Hit_t& a){
62 return a->type() == xAOD::UncalibMeasType::Other;
63 }), hits.end());
64 }
65 inline HitVec_t copyAndSort(HitVec_t hits) {
66 std::ranges::sort(hits, [](const Hit_t& a, const Hit_t& b){
67 return a->localPosition().z() < b->localPosition().z();
68 });
69 return hits;
70 }
71 }
72 SegmentLineFitter::Config::RangeArray
74 RangeArray rng{};
75 constexpr double spatRang = 10._m;
76 constexpr double timeTange = 50._ns;
77 using enum ParamDefs;
78 rng[toUnderlying(y0)] = std::array{-spatRang, spatRang};
79 rng[toUnderlying(x0)] = std::array{-spatRang, spatRang};
80 rng[toUnderlying(phi)] = std::array{-179._degree, 179._degree};
81 rng[toUnderlying(theta)] = std::array{0._degree, 175._degree};
82 rng[toUnderlying(t0)] = std::array{-timeTange, timeTange};
83 return rng;
84 }
85 SegmentLineFitter::SegmentLineFitter(const std::string& name, Config&& config):
86 AthMessaging{name},
87 m_fitter{config, makeActsAthenaLogger(this, name)},
88 m_cfg{config} {
89 m_goodHitSel.connect<isGoodHit>();
90 }
91 Result_t SegmentLineFitter::callLineFit(const Acts::CalibrationContext& cctx,
92 const Parameters& startPars,
93 const Amg::Transform3D& localToGlobal,
94 HitVec_t&& calibHits) const {
95
97 bool appendsBS = m_cfg.doBeamSpot && countPhiHits(calibHits) > 0;
98
99
100 Result_t result{};
101 //check the degrees of freedom before try the fit
102 if (const std::size_t nPars = m_fitter.config().parsToUse.size(); nPars > 0ul) {
103 auto dOF = m_fitter.countDoF(calibHits, m_goodHitSel);
104 if (dOF.bending + dOF.nonBending < nPars) {
105 return result;
106 }
107 // check that there are at least two crossing stereo measurements
108 if (dOF.nonBending == 0ul && nPars == 4ul){
109 bool foundU{false}, foundV{false};
110 for (const HitVec_t::value_type& hit : calibHits) {
111 if (hit->type() != xAOD::UncalibMeasType::MMClusterType || !isGoodHit(*hit)) {
112 continue;
113 }
114 const auto* mmClust = dynamic_cast<const xAOD::MMCluster*>(hit->spacePoint()->primaryMeasurement());
115 assert(mmClust != nullptr);
116 const auto& design = mmClust->readoutElement()->stripLayer(mmClust->layerHash()).design();
117 if (!design.hasStereoAngle()) {
118 continue;
119 }
120 if (design.stereoAngle() > 0.) {
121 foundU = true;
122 } else {
123 foundV = true;
124 }
125 if (foundU && foundV) {
126 break;
127 }
128 }
129 if (!foundU || !foundV) {
130 result.measurements = std::move(calibHits);
131 result.parameters = startPars;
132 return result;
133 }
134 if (m_cfg.doBeamSpot) {
135 appendsBS = true;
136 }
137 }
138 }
139 if (appendsBS) {
140 const Amg::Transform3D globToLoc{localToGlobal.inverse()};
141 Amg::Vector3D beamSpot{globToLoc.translation()};
142 Amg::Vector3D beamLine{globToLoc.linear().col(2)};
143 SpacePoint::Cov_t covariance{};
144 covariance[toUnderlying(AxisDefs::etaCov)] = square(m_cfg.beamSpotRadius);
145 covariance[toUnderlying(AxisDefs::phiCov)] = square(m_cfg.beamSpotLength);
147 auto beamSpotSP = std::make_unique<CalibratedSpacePoint>(nullptr, std::move(beamSpot));
148 beamSpotSP->setBeamDirection(std::move(beamLine));
149 beamSpotSP->setCovariance(std::move(covariance));
150 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Beam spot constraint "
151 <<Amg::toString(beamSpotSP->localPosition())<<", "<<beamSpotSP->covariance());
152 calibHits.emplace_back(std::move(beamSpotSP));
153 }
154 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__ <<": Start segment fit with parameters "
155 <<toString(startPars) <<", plane location: "<<Amg::toString(localToGlobal)
156 <<std::endl<<print(calibHits));
157
158 FitOpts_t fitOpts{};
159 fitOpts.calibContext = cctx;
160 fitOpts.calibrator = m_cfg.calibrator;
161 fitOpts.selector = m_goodHitSel;
162
163 fitOpts.measurements = std::move(calibHits);
164 fitOpts.localToGlobal = localToGlobal;
165 fitOpts.startParameters = startPars;
167 constexpr auto t0idx = toUnderlying(ParamDefs::t0);
168 fitOpts.startParameters[t0idx] = ActsTrk::timeToActs(fitOpts.startParameters[t0idx]);
170 result = m_fitter.fit(std::move(fitOpts));
172 if (m_fitter.config().fitT0) {
173 result.parameters[t0idx] = ActsTrk::timeToAthena(result.parameters[t0idx]);
174 result.covariance(t0idx, t0idx) = Acts::square(ActsTrk::timeToAthena(1.)) * result.covariance(t0idx, t0idx);
175 for (ParamDefs p : {ParamDefs::x0, ParamDefs::y0, ParamDefs::phi, ParamDefs::theta}) {
176 auto pidx = toUnderlying(p);
177 result.covariance(t0idx, pidx) = ActsTrk::timeToAthena(result.covariance(t0idx, pidx));
178 result.covariance(pidx, t0idx) = ActsTrk::timeToAthena(result.covariance(pidx, t0idx));
179 }
180 }
182 {
183 const auto[segPos, segDir] = makeLine(result.parameters);
184 for (Hit_t& hit : result.measurements) {
185 hit->setChi2Term(SeedingAux::chi2Term(segPos, segDir, *hit));
186 }
187 }
188 return result;
189 }
190 std::unique_ptr<Segment>
191 SegmentLineFitter::fitSegment(const EventContext& ctx,
192 const SegmentSeed* parent,
193 const Parameters& startPars,
194 const Amg::Transform3D& localToGlobal,
195 HitVec_t&& calibHits) const {
196
197 const Acts::CalibrationContext cctx = ActsTrk::getCalibrationContext(ctx);
198 if (!checkPrecHitCount(calibHits) ) {
199 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__ <<": Not enough degree of freedom available. What shall be fitted?!");
200 return nullptr;
201 }
202 if (m_cfg.visionTool) {
203 Result_t preFit{};
204 preFit.parameters = startPars;
205 preFit.measurements = calibHits;
206 auto seedCopy = convertToSegment(localToGlobal, parent, std::move(preFit));
207 m_cfg.visionTool->visualizeSegment(ctx, *seedCopy, "Prefit");
208 }
209 Result_t segFit = callLineFit(cctx, startPars, localToGlobal, std::move(calibHits));
210 if (m_cfg.visionTool && segFit.converged) {
211 auto seedCopy = convertToSegment(localToGlobal, parent, Result_t{segFit});
212 m_cfg.visionTool->visualizeSegment(ctx, *seedCopy, "Intermediate fit");
213 }
214 if (!removeOutliers(cctx, *parent, localToGlobal,
215 segFit.converged ? segFit.parameters : startPars,
216 segFit)) {
217 return nullptr;
218 }
219 if (!plugHoles(cctx, *parent, localToGlobal, segFit)) {
220 return nullptr;
221 }
222 auto finalSeg = convertToSegment(localToGlobal, parent, std::move(segFit));
223 if (m_cfg.visionTool) {
224 m_cfg.visionTool->visualizeSegment(ctx, *finalSeg, "Final fit");
225 }
226 return finalSeg;
227 }
228 std::unique_ptr<Segment>
230 const SegmentSeed* patternSeed,
231 Result_t&& data) const {
232 const auto [locPos, locDir] = makeLine(data.parameters);
233 Amg::Vector3D globPos = locToGlob * locPos;
234 Amg::Vector3D globDir = locToGlob.linear()* locDir;
235
236 std::ranges::sort(data.measurements, [](const Hit_t& a, const Hit_t& b){
237 return a->localPosition().z() < b->localPosition().z();
238 });
239 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__ <<": Create new segment "
240 <<toString(data.parameters)<<" in "<<patternSeed->msSector()->identString()
241 <<"built from:\n"<<print(data.measurements));
242
243 auto finalSeg = std::make_unique<Segment>(std::move(globPos), std::move(globDir),
244 patternSeed, std::move(data.measurements),
245 data.chi2, data.nDoF);
246 finalSeg->setCallsToConverge(data.nIter);
247 finalSeg->setParUncertainties(std::move(data.covariance));
248 if (m_fitter.config().fitT0) {
249 finalSeg->setSegmentT0(data.parameters[toUnderlying(ParamDefs::t0)]);
250 }
251 return finalSeg;
252 }
253
254 bool SegmentLineFitter::removeOutliers(const Acts::CalibrationContext& cctx,
255 const SegmentSeed& seed,
256 const Amg::Transform3D& localToGlobal,
257 const LinePar_t& startPars,
258 Result_t& fitResult) const {
259
260 if (!checkPrecHitCount(fitResult.measurements) ||
261 fitResult.nIter > m_fitter.config().maxIter) {
262 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__
263 <<": No degree of freedom available. What shall be removed?!. nDoF: "
264 <<fitResult.nDoF<<", n-meas: "<<countPrecHits(fitResult.measurements)
265 <<std::endl<<print(fitResult.measurements));
266 return false;
267 }
268 if (fitResult.converged && calcRedChi2(fitResult) < m_cfg.outlierRemovalCut) {
269 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__ <<": The segment "<<toString(fitResult.parameters)
270 <<" is already of good quality "<< calcRedChi2(fitResult)<<". Don't remove outliers");
271 return true;
272 }
273 if (fitResult.nDoF == 0u){
274 return false;
275 }
276 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__ <<": Segment "
277 <<toString(fitResult.parameters)<<", nIter: "<<fitResult.nIter
278 <<" is of badish quality. "<<print(fitResult.measurements)
279 <<std::endl<<"Remove worst hit");
280
283 if (m_cfg.doBeamSpot) {
284 removeBeamSpot(fitResult.measurements);
285 }
286
288 std::ranges::sort(fitResult.measurements,
289 [](const HitVec_t::value_type& a, const HitVec_t::value_type& b){
291 if (isGoodHit(*a) != isGoodHit(*b)) {
292 return !isGoodHit(*a);
293 }
294 return a->chi2Term() < b->chi2Term();
295 });
296 fitResult.measurements.back()->setFitState(HitState::Outlier);
297 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<" Mark "<<(*fitResult.measurements.back())<<" as outlier");
298
300 if (!checkPrecHitCount(fitResult.measurements)) {
301 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__
302 <<": No degree of freedom available after outlier removal. n-meas: "
303 <<countPrecHits(fitResult.measurements)<<std::endl<<print(fitResult.measurements));
304 return false;
305 }
306
308 Result_t newAttempt = callLineFit(cctx, startPars, localToGlobal,
309 std::move(fitResult.measurements));
310 if (newAttempt.converged) {
311 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<" The outlier removal converged.");
312 newAttempt.nIter+=fitResult.nIter;
313 fitResult = std::move(newAttempt);
314 if (m_cfg.visionTool) {
315 const EventContext& ctx{*cctx.get<const EventContext*>()};
316 auto seedCopy = convertToSegment(localToGlobal, &seed, Result_t{fitResult});
317 m_cfg.visionTool->visualizeSegment(ctx, *seedCopy, "Bad fit recovery");
318 }
319 } else {
320 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__
321 <<" Outlier removal fit did not converge. Needed iterations: "<<newAttempt.nIter);
322 if (newAttempt.nIter == 0ul) {
323 return false;
324 }
325 fitResult.nIter+=newAttempt.nIter;
326 fitResult.measurements = std::move(newAttempt.measurements);
327 }
328 return removeOutliers(cctx, seed, localToGlobal,
329 fitResult.converged ? fitResult.parameters : startPars,
330 fitResult);
331 }
332
334 auto [segPos, segDir] = makeLine(candidate.parameters);
335 cleanStripLayers(candidate.measurements);
336 candidate.measurements.erase(std::remove_if(candidate.measurements.begin(),
337 candidate.measurements.end(),
338 [&](const HitVec_t::value_type& hit){
339 if (hit->fitState() == HitState::Valid) {
340 return false;
341 } else if (hit->fitState() == HitState::Duplicate) {
342 return true;
343 }
346 const double dist = Amg::lineDistance(segPos, segDir,
347 hit->localPosition(),
348 hit->sensorDirection());
349 const auto* dc = static_cast<const xAOD::MdtDriftCircle*>(hit->spacePoint()->primaryMeasurement());
350 return dist >= dc->readoutElement()->innerTubeRadius();
351 }
352 return false;
353 }), candidate.measurements.end());
354 }
356 const SpacePointPerLayerSorter sorter{};
358 std::ranges::sort(hits, [&](const Hit_t&a ,const Hit_t& b){
359 // move the straws to the end of the vector
360 if (a->isStraw() || b->isStraw()) {
361 return !a->isStraw();
362 }
363 // move the beam spot to the end of the vector
364 if (a->type() == xAOD::UncalibMeasType::Other ||
365 b->type() == xAOD::UncalibMeasType::Other) {
366 return a->type() != xAOD::UncalibMeasType::Other;
367 }
368 // sort the strips by layer
369 const unsigned lay_a = sorter.sectorLayerNum(*a->spacePoint());
370 const unsigned lay_b = sorter.sectorLayerNum(*b->spacePoint());
371 if (lay_a != lay_b) {
372 return lay_a < lay_b;
373 }
374 if (a->fitState() != b->fitState()) {
375 return a->fitState() == HitState::Valid;
376 }
377 const double chi2a = a->chi2Term();
378 const double chi2b = b->chi2Term();
379 /* Do not accept pad hits even though they've smaller chi2
380 * than the neighbouring strip */
382 const auto* sTgcA = static_cast<const xAOD::sTgcMeasurement*>(a->spacePoint()->primaryMeasurement());
383 const auto* sTgcB = static_cast<const xAOD::sTgcMeasurement*>(b->spacePoint()->primaryMeasurement());
384 if (sTgcA->channelType() == xAOD::sTgcMeasurement::sTgcChannelTypes::Pad &&
385 sTgcB->channelType() == xAOD::sTgcMeasurement::sTgcChannelTypes::Strip) {
386 return std::sqrt(chi2b) > m_cfg.recoveryPull;
387 } else if (sTgcB->channelType() == xAOD::sTgcMeasurement::sTgcChannelTypes::Pad &&
388 sTgcA->channelType() == xAOD::sTgcMeasurement::sTgcChannelTypes::Strip) {
389 return std::sqrt(chi2a) < m_cfg.recoveryPull;
390 }
391 }
392 return chi2a < chi2b;
393 });
394
395 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__ <<": Check for duplicate strip hits");
397 for (HitVec_t::iterator itr = hits.begin(); itr != hits.end(); ++itr) {
398 const Hit_t& hit_a{*itr};
399 // Straws and the beamspot are after all the strips have been passed
400 if (hit_a->isStraw() || hit_a->type() == xAOD::UncalibMeasType::Other) {
401 break;
402 }
403 if(hit_a->fitState() == HitState::Duplicate) {
404 continue;
405 }
406 const unsigned lay_a = sorter.sectorLayerNum(*hit_a->spacePoint());
408 for (HitVec_t::iterator itr2 = itr + 1; itr2 != hits.end(); ++itr2) {
409 const Hit_t& hit_b{*itr2};
410 if (hit_b->type() == xAOD::UncalibMeasType::Other || hit_b->isStraw()) {
411 break;
412 }
413 if (hit_b->fitState() == HitState::Duplicate) {
414 continue;
415 }
416 if (lay_a != sorter.sectorLayerNum(*hit_b->spacePoint())) {
417 break;
418 }
420 if ((hit_a->measuresEta() && hit_b->measuresEta()) ||
421 (hit_a->measuresPhi() && hit_b->measuresPhi())) {
422 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__ <<": Duplicate hit on same layer"<<std::endl
423 <<" -- reject: "<<(*hit_b)<<std::endl
424 <<" -- accept: "<<(*hit_a));
425 hit_b->setFitState(HitState::Duplicate);
426 }
427 }
428 }
429 }
430
431 inline bool SegmentLineFitter::betterResult(const Result_t& newResult, const Result_t& oldResult) const {
432 if (!newResult.converged) {
433 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" The new result did not converge");
434 return false;
435 }
436 const double redChi2New = calcRedChi2(newResult);
437 const double redChi2Old = calcRedChi2(oldResult);
438 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Compare results -- old chi2: "<<redChi2Old<<", nDoF: "
439 <<oldResult.nDoF<<" vs. new chi2: "<<redChi2New<<", nDoF: "<<newResult.nDoF
440 <<" -- outlier removal: "<<m_cfg.outlierRemovalCut);
441 if (newResult.nDoF == oldResult.nDoF) {
442 //check the number of precision hits
443 const std::size_t newPrecisionHits = countPrecHits(newResult.measurements);
444 const std::size_t oldPrecisionHits = countPrecHits(oldResult.measurements);
445 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" Compare results -- old precHits: "<<oldPrecisionHits
446 <<" vs. new precHits: "<<newPrecisionHits);
447 return (newPrecisionHits > oldPrecisionHits && redChi2New < m_cfg.outlierRemovalCut) ||
448 redChi2New < redChi2Old;
449 }
450 return (redChi2New < m_cfg.outlierRemovalCut && newResult.nDoF > oldResult.nDoF) ||
451 (redChi2New > m_cfg.outlierRemovalCut && redChi2New < redChi2Old);
452 }
453 bool SegmentLineFitter::plugHoles(const Acts::CalibrationContext& cctx,
454 const SegmentSeed& seed,
455 const Amg::Transform3D& localToGlobal,
456 Result_t& toRecover) const {
458 ATH_MSG_DEBUG(__func__<<"() - "<<__LINE__ <<": segment "<<toString(toRecover.parameters)
459 <<", chi2: "<< calcRedChi2(toRecover) <<", nDoF: "<<toRecover.nDoF
460 <<std::endl<<print(copyAndSort(toRecover.measurements)));
462 std::vector<const SpacePoint*> usedSpacePoints{};
463 usedSpacePoints.reserve(toRecover.measurements.size());
464 for (auto& hit : toRecover.measurements) {
465 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__ <<": "<<(*hit)<<" is known");
466 usedSpacePoints.push_back(hit->spacePoint());
467 }
468
469 const EventContext& ctx{*cctx.get<const EventContext*>()};
470
471 const double timeOff = toRecover.parameters[toUnderlying(ParamDefs::t0)];
472 HitVec_t candidateHits{};
473 std::size_t recovCandidates{0};
474 const auto [locPos, locDir] = makeLine(toRecover.parameters);
475
477 for (const auto& hit : *seed.parentBucket()){
479 if (Acts::rangeContainsValue(usedSpacePoints, hit.get())) {
480 continue;
481 }
482 Hit_t calibHit{};
483 double pull{-1.};
484 if (hit->isStraw()) {
485 using namespace Acts::detail::LineHelper;
486 const double dist = signedDistance(locPos, locDir, hit->localPosition(), hit->sensorDirection());
487 const auto* dc = static_cast<const xAOD::MdtDriftCircle*>(hit->primaryMeasurement());
488 // Check whether the tube is crossed by the hit
489 if (std::abs(dist) >= dc->readoutElement()->innerTubeRadius()) {
490 continue;
491 }
492 } else {
494 if (!hit->measuresEta() &&
495 std::abs(hit->sensorDirection().dot(hit->localPosition() -
496 SeedingAux::extrapolateToPlane(locPos,locDir, *hit))) >
497 1.1*std::sqrt(hit->covariance()[toUnderlying(AxisDefs::etaCov)])){
498 continue;
499 }
502 pull = std::sqrt(SeedingAux::chi2Term(locPos, locDir, *hit));
503 if (pull > 1.1 * m_cfg.recoveryPull) {
504 continue;
505 }
506 }
507 calibHit = m_cfg.calibrator->calibrate(ctx, hit.get(), locPos, locDir, ActsTrk::timeToActs(timeOff));
508 calibHit->setChi2Term(SeedingAux::chi2Term(locPos, locDir, *calibHit));
509 if (calibHit->chi2Term() <= Acts::square(m_cfg.recoveryPull)) {
510 recovCandidates += calibHit->fitState() == HitState::Valid;
511 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Candidate hit for recovery "
512 <<(*calibHit));
513 } else {
514 calibHit->setFitState(HitState::Outlier);
515 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Outlier hit "
516 <<(*calibHit)<<" -> limit: "<<m_cfg.recoveryPull);
517 }
518 candidateHits.push_back(std::move(calibHit));
519 }
521 if (!recovCandidates) {
522 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": No space point candidates for recovery were found");
523 toRecover.measurements.insert(toRecover.measurements.end(),
524 std::make_move_iterator(candidateHits.begin()),
525 std::make_move_iterator(candidateHits.end()));
526 eraseWrongHits(toRecover);
527 return toRecover.nDoF > 0;
528 }
529 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Found "<<recovCandidates<<" space points for recovery. ");
530
531 HitVec_t hitsForRecovery = toRecover.measurements;
533 if (m_cfg.doBeamSpot) {
534 removeBeamSpot(hitsForRecovery);
535 }
536
537 hitsForRecovery.insert(hitsForRecovery.end(), candidateHits.begin(), candidateHits.end());
538
539 cleanStripLayers(hitsForRecovery);
540
541 Result_t recovered = callLineFit(cctx, toRecover.parameters, localToGlobal,
542 std::move(hitsForRecovery));
543
546 if (betterResult(recovered, toRecover)) {
547 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Accept segment with recovered "
548 <<(recovered.nDoF - toRecover.nDoF)<<" extra nDoF.");
549 recovered.nIter += toRecover.nIter;
550 toRecover = std::move(recovered);
551
552 std::vector<const CalibratedSpacePoint*> stripOutliers{};
553 stripOutliers.reserve(toRecover.measurements.size());
556 unsigned recovLoop{(candidateHits.size() == recovCandidates)*m_cfg.nRecoveryLoops};
557 while (++recovLoop <= m_cfg.nRecoveryLoops) {
558 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Enter recovery loop "<<recovLoop<<".");
559 hitsForRecovery = toRecover.measurements;
560 // Remove the beamspot
561 if (m_cfg.doBeamSpot) {
562 removeBeamSpot(hitsForRecovery);
563 }
564 // Check whether an outlier can be lifted to on-track
565 for (HitVec_t::value_type& hit : hitsForRecovery) {
566 if (hit->fitState() != HitState::Outlier) {
567 continue;
568 }
569 if (hit->chi2Term() < Acts::square(m_cfg.recoveryPull)) {
570 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Try to recover outlier "<<(*hit));
571 hit->setFitState(HitState::Valid);
572 stripOutliers.push_back(hit.get());
573 }
574 }
575 // Nothing to recover
576 if (stripOutliers.empty()) {
577 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": No additional measurement found");
578 break;
579 }
580 // Ensure that only one hit per layer is fit
581 cleanStripLayers(hitsForRecovery);
582 // Recovery turned out to be duplicates on the same layer
583 if (std::ranges::none_of(stripOutliers,[](const CalibratedSpacePoint* sp) {
584 return sp->fitState() == HitState::Valid;
585 })) {
586 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Outliers turned out to be all duplicates.");
587 break;
588 }
589 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Start fit without the outliers.");
590 stripOutliers.clear();
591 recovered = callLineFit(cctx, toRecover.parameters, localToGlobal, std::move(hitsForRecovery));
592 if (!betterResult(recovered, toRecover)) {
593 break;
594 }
595 recovered.nIter += toRecover.nIter;
596 toRecover = std::move(recovered);
597 }
598 } else{
599 for (HitVec_t::value_type& hit : candidateHits) {
600 hit->setFitState(HitState::Outlier);
601 toRecover.measurements.push_back(std::move(hit));
602 }
603 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Reject refitted segment. Append hits as outliers: "
604 <<std::endl<<print(copyAndSort(toRecover.measurements)));
605 }
606 eraseWrongHits(toRecover);
607 return true;
608 }
609 inline bool SegmentLineFitter::checkPrecHitCount(const HitVec_t& candidateHits) const {
610 using namespace Muon::MuonStationIndex;
611
612 const size_t nPrecHits = countPrecHits(candidateHits);
613 if (nPrecHits < m_cfg.nPrecHitCut) {
614 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": Not enough precision hits for segment fit. "
615 <<nPrecHits<<" < "<<m_cfg.nPrecHitCut);
616 return false;
617 }
618
619 const auto firstHit {std::ranges::find_if(candidateHits, [](const Hit_t& hit){
620 return hit->spacePoint() != nullptr;
621 })};
622 assert(firstHit != candidateHits.end());
623 if (toStationIndex((*firstHit)->spacePoint()->msSector()->chamberIndex()) == StIndex::EI &&
624 std::ranges::any_of(candidateHits, [](const Hit_t& hit){
625 return xAOD::isNSW(hit->type()); })) {
626
627 std::array<std::size_t, 3> nStrips{Acts::filledArray<std::size_t, 3>(0u)};
628 std::size_t nPhiHits {0u};
629 for (const Hit_t& hit : candidateHits) {
630 if (!isGoodHit(*hit)) {
631 continue;
632 }
633
634 if (hit->type() == xAOD::UncalibMeasType::sTgcStripType) {
635 nStrips[0] += isPrecisionHit(*hit);
636 nPhiHits += hit->measuresPhi();
637 continue;
638 } else if (hit->type() == xAOD::UncalibMeasType::MMClusterType) {
639 const auto* mmClust = dynamic_cast<const xAOD::MMCluster*>(hit->spacePoint()->primaryMeasurement());
640 assert(mmClust);
641 const auto& design = mmClust->readoutElement()->stripLayer(mmClust->measurementHash()).design();
642 if (!design.hasStereoAngle()) {
643 ++nStrips[0];
644 } else if (design.stereoAngle() > 0.) {
645 ++nStrips[1];
646 } else {
647 ++nStrips[2];
648 }
649 }
650 }
653
654 std::size_t nEtaOrientations =
655 std::ranges::count_if(nStrips, [](std::size_t n){ return n > 0; });
656 if (nEtaOrientations == 3u) {
657 nEtaOrientations += std::ranges::any_of( nStrips, [](std::size_t n){ return n > 1; });
658 }
659 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<": nHits: "<<candidateHits.size()
660 <<", nPhiHits: "<<nPhiHits<<", nEtaOrientations: "<<nEtaOrientations
661 <<", N X-strips: "<<nStrips[0]<<", U-strips: "<<nStrips[1]<<", V-strips: "<<nStrips[2]);
662
663 if ( nEtaOrientations == 4u ||
664 (nEtaOrientations == 3u && nPhiHits >= 1u) ||
665 (nEtaOrientations == 2u && nPhiHits >= 2u)||
666 (std::ranges::any_of(nStrips, [](std::size_t n){ return n >= 2u; }) && nPhiHits >= 2u)) {
667 return true;
668 }
669 return false;
670 }
671 return true;
672 }
673}
Scalar phi() const
phi method
Scalar theta() const
theta method
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_DEBUG(x)
static Double_t sp
static Double_t a
static Double_t t0
if(pathvar)
std::unique_ptr< const Acts::Logger > makeActsAthenaLogger(IMessageSvc *svc, const std::string &name, int level, std::optional< std::string > parent_name)
AthMessaging(IMessageSvc *msgSvc, const std::string &name)
Constructor.
std::string identString() const
Returns a string encoding the chamber index & the sector of the MS sector.
The calibrated Space point is created during the calibration process.
Segment::MeasType Hit_t
Abrivation of the space point type to use.
bool plugHoles(const Acts::CalibrationContext &cctx, const SegmentSeed &seed, const Amg::Transform3D &localToGlobal, Result_t &toRecover) const
Recovery of missed hits.
Selector_t m_goodHitSel
Selector to identify the valid hits.
Result_t callLineFit(const Acts::CalibrationContext &cctx, const Parameters &startPars, const Amg::Transform3D &localToGlobal, HitVec_t &&calibHits) const
Calls the underlying line fitter to determine the segment parameters.
ConfigSwitches m_cfg
Configuration switches of the ATLAS fitter implementation.
Fitter_t::FitResult< HitVec_t > Result_t
Abrivation of the fit result.
bool betterResult(const Result_t &newResult, const Result_t &oldResult) const
Returns whether the new fit result is better than the one from the previous iteration.
void eraseWrongHits(Result_t &candidate) const
Removes all hits from the segment which are obvious outliers.
void cleanStripLayers(HitVec_t &hits) const
Marks duplicate hits on a strip layer as outliers to avoid competing contributions from the same laye...
std::unique_ptr< Segment > convertToSegment(const Amg::Transform3D &locToGlobTrf, const SegmentSeed *parentSeed, Result_t &&toConvert) const
Converts the fit result into a segment object.
bool checkPrecHitCount(const HitVec_t &candidateHits) const
Checks if the candidate has enough precision hits to fit a segment.
Fitter_t::FitOptions< HitVec_t, ISpacePointCalibrator > FitOpts_t
Abrivation of the fit options.
std::unique_ptr< Segment > fitSegment(const EventContext &ctx, const SegmentSeed *parent, const LinePar_t &startPars, const Amg::Transform3D &localToGlobal, HitVec_t &&calibHits) const
Fit a set of measurements to a straight segment line.
Fitter_t m_fitter
Actual implementation of the straight line fit.
Fitter_t::ParamVec_t LinePar_t
Abrivation of the fitted line parameters.
std::vector< Hit_t > HitVec_t
Collection of space points.
SegmentLineFitter(const std::string &name, Config &&config)
Standard constructor.
bool removeOutliers(const Acts::CalibrationContext &cctx, const SegmentSeed &seed, const Amg::Transform3D &localToGlobal, const LinePar_t &startPars, Result_t &fitResult) const
Cleans the fitted segment from the most outlier hit and then attempts to refit the segment.
Representation of a segment seed (a fully processed hough maximum) produced by the hough transform.
Definition SegmentSeed.h:14
const MuonGMR4::SpectrometerSector * msSector() const
Returns the associated chamber.
The SpacePointPerLayerSorter sort two given space points by their layer Identifier.
std::array< double, 3 > Cov_t
Abrivation of the covariance type.
constexpr double timeToAthena(T actsT)
Converts a time unit from Acts to Athena units.
Acts::CalibrationContext getCalibrationContext(const EventContext &ctx)
The Acts::Calibration context is piped through the Acts fitters to (re)calibrate the Acts::SourceLink...
constexpr auto timeToActs(T athenaT)
Converts a time unit from Athena to Acts units.
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Affine3d Transform3D
Eigen::Matrix< double, 3, 1 > Vector3D
SegmentLineFitter::Result_t Result_t
SegmentLineFitter::HitVec_t HitVec_t
SeedingAux::FitParIndex ParamDefs
Use the same parameter indices as used by the CompSpacePointAuxiliaries.
SegmentLineFitter::Hit_t Hit_t
std::pair< Amg::Vector3D, Amg::Vector3D > makeLine(const Parameters &pars)
Returns the parsed parameters into an Eigen line parametrization.
Acts::Experimental::CompositeSpacePointLineFitter::ParamVec_t Parameters
std::string toString(const Parameters &pars)
Dumps the parameters into a string with labels in front of each number.
bool isPrecisionHit(const SpacePoint &hit)
Returns whether the uncalibrated spacepoint is a precision hit (Mdt, micromegas, stgc strips).
std::string print(const cont_t &container)
Print a space point container to string.
bool isGoodHit(const CalibratedSpacePoint &hit)
Returns whether the calibrated spacepoint is valid and therefore suitable to be used in the segment f...
StIndex toStationIndex(ChIndex index)
convert ChIndex into StIndex
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.
MdtDriftCircle_v1 MdtDriftCircle
MMCluster_v1 MMCluster
sTgcMeasurement_v1 sTgcMeasurement
static RangeArray defaultRanges()
Function that returns a set of predefined ranges for testing.
Tell the compiler to optimize assuming that FP may trap.
#define CXXUTILS_TRAPPING_FP
Definition trapping_fp.h:24