ATLAS Offline Software
Loading...
Searching...
No Matches
MsTrackSeederTool.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4#include "MsTrackSeederTool.h"
5
6
7
10
11#include "Acts/Utilities/Helpers.hpp"
12#include "Acts/Definitions/Tolerance.hpp"
13#include "Acts/Definitions/Units.hpp"
14#include "Acts/Geometry/TrackingGeometry.hpp"
15
16#include "Acts/Surfaces/LineBounds.hpp"
17#include "Acts/Surfaces/PlaneSurface.hpp"
18
23#include "GaudiKernel/PhysicalConstants.h"
24
25namespace {
26 using namespace Acts::UnitLiterals;
28 constexpr bool chargeAgree(double PtimesQ1, double PtimesQ2) {
29 return PtimesQ1 * PtimesQ2 > 0;
30 };
32 inline double momentumDev(double PtimesQ1, double PtimesQ2) {
33 const double denom {std::max(std::abs(PtimesQ1) + std::abs(PtimesQ2), Acts::s_epsilon)};
34 return std::abs(PtimesQ1 - PtimesQ2) / denom;
35 };
36 float reducedChi2(const xAOD::MuonSegment& seg) {
37 // Tell clang to optimize assuming that FP operations may trap.
39 return seg.chiSquared() / std::max(1.f, seg.numberDoF());
40 }
41 std::string print(const xAOD::MuonSegment& seg) {
42 return std::format("{:}, nPrecHits: {:}, nPhiHits: {:}", MuonR4::printID(seg),
43 seg.nPrecisionHits(), seg.nPhiLayers());
44 }
45
46 bool isNswSegment(const xAOD::MuonSegment& seg) {
47 using namespace Muon::MuonStationIndex;
48 return seg.technology() == TechnologyIndex::STGC ||
50 toStationIndex(seg.chamberIndex()) == StIndex::EE;
51 }
52
53}
54
55namespace MuonR4{
57
59 ATH_CHECK(m_ctxProvider.initialize());
60 ATH_CHECK(m_segSelector.retrieve());
62 ATH_CHECK(m_segmentKey.initialize(!m_segmentKey.empty()));
63 ATH_CHECK(detStore()->retrieve(m_detMgr));
64
65 if (m_nFieldSteps == 0) {
66 ATH_MSG_ERROR("The number of field steps must not be zero "<<m_nFieldSteps);
67 return StatusCode::FAILURE;
68 }
70 const double stepSize {1. / m_nFieldSteps};
71 for (std::size_t i = 0; i < m_nFieldSteps; ++i) {
72 m_fieldExtpSteps.push_back((static_cast<double>(i) + 0.5) * stepSize);
73 }
74 return StatusCode::SUCCESS;
75 }
76
77 Acts::Result<Acts::BoundTrackParameters>
79 const MsTrackSeed& seed) const {
80 const Acts::GeometryContext tgContext = m_ctxProvider.getGeometryContext(ctx);
81 const Acts::MagneticFieldContext mfContext = m_ctxProvider.getMagneticFieldContext(ctx);
83 mfContext.get<const AtlasFieldCacheCondObj*>()->getInitializedCache(magField);
84
85 const xAOD::MuonSegment* refSeg{nullptr};
86 Acts::BoundMatrix cov{Acts::BoundMatrix::Zero()};
87 for (const xAOD::MuonSegment* segment : seed.segments()) {
94 if (!refSeg && !isNswSegment(*segment) &&
95 m_segSelector->passSeedingQuality(ctx, *segment)) {
96 refSeg = segment;
97 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Set reference segment to "<<::print(*segment));
98 }
99 Acts::BoundTrackParameters boundPars = SegmentFit::boundSegmentPars(tgContext, *m_detMgr, *segment);
100 if (!boundPars.covariance()) {
101 continue;
102 }
103 for (int i =0 ; i < cov.cols(); ++i) {
104 cov(i,i) += (*boundPars.covariance())(i,i);
105 }
106
107 }
108 //if we did not find a reference segment let's try the NSW one before we give up on the track
109 if(!refSeg){
110 for (const xAOD::MuonSegment* segment : seed.segments()) {
111 if (isNswSegment(*segment) &&
112 m_segSelector->passSeedingQuality(ctx, *segment)) {
113 refSeg = segment;
114 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - NSW is the best what we have apparently....");
115 break;
116 }
117 }
118 }
119
120 if (!refSeg) {
121 ATH_MSG_WARNING(__func__<<"() "<<__LINE__
122 <<" - No reference segment passing seeding quality was found.");
123 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
124 }
125 Amg::Vector3D seedPos{atFirstSurface(tgContext, *refSeg)};
126 Amg::Vector3D seedDir{refSeg->direction()};
127 ATH_MSG_DEBUG(__func__<<"() "<<__LINE__<<" - Initial seed pos: "<<Amg::toString(seedPos)
128 <<", dir: "<<Amg::toString(seedDir) << " eta " << seedDir.eta() << " phi "
129 << (seedDir.phi() /Gaudi::Units::degree) );
130
131 const xAOD::MuonSegment* frontSegment = seed.segments().front();
132
133 const Acts::Surface& firstSurf = xAOD::muonSurface(firstMeasurement(*frontSegment));
134 const Acts::GeometryIdentifier volId = volumeId(firstSurf);
135
136 // Find the first measurement
137 const Acts::TrackingVolume* volume{MuonGMR4::highestAlignable(m_trackingGeometrySvc->trackingGeometry()->findVolume(volId))};
138
139 if (!volume) {
140 ATH_MSG_WARNING(__func__<<"() "<<__LINE__
141 <<" - Failed to find tracking volume for seed measurement "<<volId);
142 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
143 }
144 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__
145 <<" - Bounding volume "<<volume->volumeName()
146 <<", trf: "<<Amg::toString(volume->localToGlobalTransform(tgContext))
147 <<", bounds: "<<volume->volumeBounds());
151 if (frontSegment != refSeg) {
152 const Amg::Vector3D frontSegPos = atFirstSurface(tgContext, *frontSegment);
153 const Amg::Transform3D toFirstTrf = firstSurf.localToGlobalTransform(tgContext).inverse();
154 const Amg::Vector3D locFrontSegPos = toFirstTrf * frontSegPos;
155 if (!volume->inside(tgContext, frontSegPos)) {
156 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Segment "<<::print(*frontSegment)
157 <<" not inside mother volume: "<<volume->volumeName()<<", "
158 <<Amg::toString(volume->globalToLocalTransform(tgContext)*frontSegPos)
159 <<", bounds: "<<volume->volumeBounds()<<", "
160 <<SegmentFit::localSegmentPars(*frontSegment));
161 }
163 {
164 const Amg::Transform3D& toLoc{volume->globalToLocalTransform(tgContext)};
165 const Amg::Vector3D locSeedDir = toLoc.linear() * seedDir;
166 const Amg::Vector3D frontSeedDir = toLoc.linear() * frontSegment->direction();
167 seedDir = volume->localToGlobalTransform(tgContext).linear() *
168 Acts::makeDirectionFromAxisTangents(houghTanAlpha(locSeedDir),
169 houghTanBeta(frontSeedDir));
170 }
174 const Acts::MultiIntersection firstIsect = firstSurf.intersect(tgContext, seedPos, seedDir,
175 Acts::BoundaryTolerance::Infinite());
176 const Amg::Vector3D locAtFirst = toFirstTrf * firstIsect.at(0).position();
177 if (firstSurf.type() == Acts::Surface::SurfaceType::Straw) {
178 const auto& bounds = static_cast<const Acts::LineBounds&>(firstSurf.bounds());
179 using enum Acts::LineBounds::BoundValues;
182 const Amg::Vector3D locStartPos{locFrontSegPos.x(), locFrontSegPos.y(),
183 std::clamp(locAtFirst.z(), -bounds.get(eHalfLengthZ), bounds.get(eHalfLengthZ))};
184 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - The first surface is a straw "
185 <<bounds<<", track seed @first: "<<Amg::toString(locAtFirst)<<" vs. segment @first: "
186 <<Amg::toString(locFrontSegPos));
187 seedPos = firstSurf.localToGlobalTransform(tgContext) * locStartPos;
188 } else if (firstSurf.type() == Acts::Surface::SurfaceType::Plane) {
189 if (isNswSegment(*frontSegment)) {
190 seedPos = frontSegPos;
191 }
192 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - The first surface is a straw "
193 <<firstSurf.bounds()<<", "<<Amg::toString(locAtFirst)<<" vs. "<<Amg::toString(locFrontSegPos));
194 } else {
195 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Unexpected surface type "<<firstSurf.type());
196 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
197 }
198 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Updated seed position: "<<Amg::toString(seedPos));
199 }
200
201
202 auto boundSurf = MuonGMR4::bottomBoundary(*volume);
203 if (!boundSurf) {
204 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Failed to find boundary surface for tracking volume");
205 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
206 }
207 std::shared_ptr<const Acts::Surface> targetSurf{};
212 auto propagateToBoundary = [&](const Acts::Surface& volBoundary) -> Acts::Result<Amg::Vector3D> {
213
214 const Amg::Transform3D& trf{volBoundary.localToGlobalTransform(tgContext)};
215 using namespace Acts::PlanarHelper;
216 auto pIsect = intersectPlane(seedPos, seedDir, trf.linear().col(Amg::z), trf.translation());
218 if (pIsect.pathLength() > Acts::s_epsilon || !pIsect.isValid()) {
219 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Intersection @"<<Amg::toString(pIsect.position())
220 <<" is forward "<<pIsect.pathLength()<<" or invalid "<<(!pIsect.isValid())
221 <<" within volume "<<volume->inside(tgContext, pIsect.position()));
222 return Acts::Result<Amg::Vector3D>::failure(std::make_error_code(std::errc::invalid_argument));
223 }
224 Acts::Result<Amg::Vector2D> locPos = volBoundary.globalToLocal(tgContext, pIsect.position(),
225 Amg::Vector3D::Zero());
226 if (!locPos.ok()){
227 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Intersection is not on surface "<<
228 Amg::toString(trf.inverse()*pIsect.position()));
229 return Acts::Result<Amg::Vector3D>::failure(std::make_error_code(std::errc::invalid_argument));
230 }
231 if (!volBoundary.insideBounds(*locPos)) {
232 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Intersection is outside the boundaries: "<<
233 Amg::toString(*locPos)<<", bounds: "<<volBoundary.bounds());
234 return Acts::Result<Amg::Vector3D>::failure(std::make_error_code(std::errc::invalid_argument));
235 }
236 targetSurf = volBoundary.getSharedPtr();
237 return Acts::Result<Amg::Vector3D>::success(pIsect.position());
238 };
240 auto pIsect = propagateToBoundary(*boundSurf);
244 if (!pIsect.ok() && volume->isAlignable()) {
245 const Acts::VolumePlacementBase* placement = volume->volumePlacement();
246 for (std::size_t portal = 0; !pIsect.ok() && portal< placement->nPortalPlacements(); ++portal) {
247 pIsect = propagateToBoundary(placement->portalPlacement(portal)->surface());
248 }
249 }
250 if (!pIsect.ok()) {
251 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" Cannot create valid start parameters from seed "<<seed<<".");
252 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
253 }
255 auto fourPos = ActsTrk::convertPosToActs(*pIsect, (*pIsect).mag() / Gaudi::Units::c_light);
256 const double qOverP = 1./ ActsTrk::energyToActs(estimateQtimesP(tgContext, seed, magField));
257
258 cov (Acts::eBoundQOverP, Acts::eBoundQOverP) = Acts::square(0.8 * qOverP);
259 return Acts::BoundTrackParameters::create(tgContext, targetSurf,
260 fourPos, seedDir, qOverP, cov,
261 Acts::ParticleHypothesis::muon());
262 }
263 Amg::Vector3D MsTrackSeederTool::segPosOntoPhiPlane(const Acts::GeometryContext& tgContext,
264 const Amg::Vector3D& planeNormal,
265 const xAOD::MuonSegment& segment) const{
266 const std::size_t nMeas = nMeasurements(segment);
267 Amg::Vector3D wireDir{Amg::Vector3D::Zero()};
268 for (std::size_t meas = 0; meas < nMeas; ++ meas) {
269 if (isOutlierMeasurement(segment, meas)) {
270 continue;
271 }
272 const xAOD::UncalibratedMeasurement* measPtr = getMeasurement(segment, meas);
274 wireDir = xAOD::muonSurface(measPtr).localToGlobalTransform(tgContext).linear().col(Amg::z);
275 break;
276 } else if (xAOD::isNSW(measPtr->type())) {
277 wireDir = m_trackingGeometrySvc->trackingGeometry()->findVolume(volumeId(xAOD::muonSurface(measPtr)))->
278 localToGlobalTransform(tgContext).linear().col(Amg::x);
279 break;
280 }
281 }
283 if (nMeas == 0ul) {
284 wireDir = envelope(segment)->surface().localToGlobalTransform(tgContext).linear().col(Amg::x);
285 }
286
287 return Acts::PlanarHelper::intersectPlane(segment.position(), wireDir,
288 planeNormal, Amg::Vector3D::Zero()).position();
289 }
290 Amg::Vector2D MsTrackSeederTool::expressOnCylinder(const Acts::GeometryContext& tgContext,
291 const xAOD::MuonSegment& segment,
292 const Location loc,
293 const ExpandedSector sector) const {
295 const Amg::Vector3D pos{segPosOntoPhiPlane(tgContext, sector.normalDir(), segment)};
296 const Amg::Vector3D dir{segment.direction()};
297
298 const Amg::Vector2D projPos{pos.perp(), pos.z()};
299 const Amg::Vector2D projDir{dir.perp(), dir.z()};
300
301 ATH_MSG_VERBOSE( "segment position:" << Amg::toString(segment.position())
302 << ", direction: " << Amg::toString(segment.direction()) );
303 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Express segment in @"<<Amg::toString(pos)
304 <<", direction: "<<Amg::toString(dir)<< " sector projector: " << sector
305 << " location: " << Acts::toUnderlying(loc));
306 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Projected position onto sector: "<<Amg::toString(projPos)
307 <<", projected direction: "<<Amg::toString(projDir));
308
309 double lambda{0.};
310 if (Location::Barrel == loc) {
311 lambda = Amg::intersect<2>(projPos, projDir, Amg::Vector2D::UnitX(),
312 m_barrelRadius).value_or(10. * Gaudi::Units::km);
313 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Intersect with barrel at radius: "<<m_barrelRadius<<" --> "<<Amg::toString(projPos + lambda * projDir));
314 } else {
315 lambda = Amg::intersect<2>(projPos, projDir, Amg::Vector2D::UnitY(),
316 Acts::copySign(1.*m_endcapDiscZ, projPos[1])).value_or(10. * Gaudi::Units::km);
317 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Intersect with endcap at z: "<<Acts::copySign(1.*m_endcapDiscZ, projPos[1])
318 <<" --> "<<Amg::toString(projPos + lambda * projDir));
319 }
320 return projPos + lambda * projDir;
321 }
323 const Location loc) const {
324 using enum Location;
325 if (loc == Barrel && std::abs(projPos[1]) > std::min(m_endcapDiscZ, m_barrelLength)) {
326 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Position "<<Amg::toString(projPos)<<
327 " exceeds cylinder boundaries ("<<(1.*m_barrelRadius)<<", "
328 <<std::min(m_endcapDiscZ, m_barrelLength)<<")");
329 return false;
330 } else if (loc == Endcap && (0 > projPos[0] || projPos[0] > m_endcapDiscRadius)) {
331 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Position "<<Amg::toString(projPos)<<
332 " exceeds endcap boundaries ("<<(1.*m_endcapDiscRadius)<<", "<<Acts::copySign(1.*m_endcapDiscZ, projPos[1])<<")");
333 return false;
334 }
335 return true;
336 }
337 double MsTrackSeederTool::estimateQtimesP(const EventContext& ctx,
338 const Amg::Vector3D& planeNorm,
339 std::span<const PosMomPair_t> circlePoints) const {
340 const Acts::MagneticFieldContext mfContext = m_ctxProvider.getMagneticFieldContext(ctx);
341 MagField::AtlasFieldCache magField{};
342 mfContext.get<const AtlasFieldCacheCondObj*>()->getInitializedCache(magField);
343 if (circlePoints.size() < 2 || circlePoints.size() > 3){
344 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Invalid number of circle points passed: "<<circlePoints.size());
345 constexpr double straightLine = 10._TeV;
346 return straightLine;
347 }
348 return circlePoints.size() == 3 ? estimateQtimesP(planeNorm, circlePoints[0], circlePoints[1], circlePoints[2], magField)
349 : estimateQtimesP(planeNorm, circlePoints[0], circlePoints[1], magField);
350 }
352 const PosMomPair_t& p1,
353 const PosMomPair_t& p2,
354 const PosMomPair_t& p3,
355 MagField::AtlasFieldCache& fieldCache) const {
356 // When 3 points are available, we can use each pair of segments to estimate
357 // the momentum and charge, and then combine the estimates. To make the
358 // combination, we define a struct to hold each PtimesQ estimate
359 struct Estimate {
360 double PtimesQ{0.};
361 // Weighting factor based on the magnitude of the integrated force.
362 double weight{0.};
363 // Scores as a combination of charge agreement and momentum deviation.
364 double score{0.};
365 };
366
367 const Amg::Vector3D force12 {forceIntegration(p1, p2, planeNorm, fieldCache)};
368 const Amg::Vector3D force23 {forceIntegration(p2, p3, planeNorm, fieldCache)};
369 const Amg::Vector3D force13 {force12 + force23};
370
371 std::vector<Estimate> estimates{};
372 const double weightNorm {force12.mag() + force23.mag()};
373 // Pairwise momentum estimates: 12 and 23
374 estimates.emplace_back(getPtimesQ(force12, p2.second - p1.second), force12.mag()/weightNorm, 0.);
375 estimates.emplace_back(getPtimesQ(force23, p3.second - p2.second), force23.mag()/weightNorm, 0.);
376 // Two estimates for pair 13: using segment directions and using position differences
377 const double weight13 {force13.mag()/weightNorm};
378 estimates.emplace_back(getPtimesQ(force13, p3.second - p1.second), weight13, 0.);
379 const Amg::Vector3D t12 {(p2.first - p1.first).unit()};
380 const Amg::Vector3D t23 {(p3.first - p2.first).unit()};
381 estimates.emplace_back(getPtimesQ(force13, t23 - t12), weight13, 0.);
382
383 for (std::size_t i {0}; i < estimates.size(); ++i) {
384 Estimate& est1 {estimates[i]};
385 for (std::size_t j {i+1}; j < estimates.size(); ++j) {
386 Estimate& est2 {estimates[j]};
387 // Compute the charge agreement score & momentum deviation
388 double w {std::min(est1.weight, est2.weight)};
389 double chargeScore {chargeAgree(est1.PtimesQ, est2.PtimesQ) ? 1. : -1.};
390 double pDevPenalty {momentumDev(est1.PtimesQ, est2.PtimesQ)};
391 // Update the scores
392 est1.score += w * (chargeScore - pDevPenalty);
393 est2.score += w * (chargeScore - pDevPenalty);
394 }
395 }
396 if (msgLvl(MSG::VERBOSE)) {
397 std::vector<std::string> names {"Pair01", "Pair12", "Pair02Seg", "Pair02Pos"};
398 for (const auto& [i, est] : Acts::enumerate(estimates)) {
399 ATH_MSG_VERBOSE(__func__<<"() Estimate "<<names[i]<<": PtimesQ: "<<est.PtimesQ*1e-3
400 <<", weight: "<<est.weight<<", score: "<<est.score);
401 }
402 }
403 // Find the best charge estimate and estimate the final momentum as a weighted average of the
404 // estimates that agree with the best charge
405 const Estimate& bestEstimate {*std::ranges::max_element(estimates,
406 std::ranges::less{}, &Estimate::score)};
407 const double charge {std::copysign(1., bestEstimate.PtimesQ)};
408
409 double totalSum {0.}, totalWeight {0.};
410 for (const Estimate& est : estimates) {
411 if (chargeAgree(est.PtimesQ, charge)) {
412 totalSum += est.PtimesQ * est.weight;
413 totalWeight += est.weight;
414 }
415 }
416 assert(totalWeight > Acts::s_epsilon);
417 return totalSum / totalWeight;
418 }
420 const PosMomPair_t& p1,
421 const PosMomPair_t& p2,
422 MagField::AtlasFieldCache& fieldCache) const {
423
424 return getPtimesQ(forceIntegration(p1, p2, planeNorm, fieldCache),
425 p2.second - p1.second);
426 }
428 const PosMomPair_t& point2,
429 const Amg::Vector3D& planeNorm,
430 MagField::AtlasFieldCache& fieldCache) const {
431 const auto& [pos1, dir1] = point1;
432 const auto& [pos2, dir2] = point2;
433 ATH_MSG_DEBUG(__func__<<"() "<<__LINE__<<" - Integrate field from "<<Amg::toString(pos1)<<", "<<Amg::toString(dir1)
434 <<" to "<<Amg::toString(pos2)<<", "<<Amg::toString(dir2));
435
436 Amg::Vector3D locField{Amg::Vector3D::Zero()};
437 Amg::Vector3D accumForce{Amg::Vector3D::Zero()};
438 for (double fieldStep : m_fieldExtpSteps) {
439 const Amg::Vector3D extPos {(1. - fieldStep) * pos1 + fieldStep * pos2};
440 const Amg::Vector3D extDir {((1. - fieldStep) * dir1 + fieldStep * dir2).unit()};
441
442 fieldCache.getField(extPos.data(), locField.data());
443 const Amg::Vector3D locForce {locField.dot(planeNorm) * extDir.cross(planeNorm)};
444 accumForce += locForce;
445
446 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - step: "<<fieldStep
447 <<", pos: "<<Amg::toString(extPos)<<", dir: "<<Amg::toString(extDir)
448 <<" --> local |B|: "<<locField.mag()*1e3 << " [T]"<<", |Bnorm|: "<<locField.dot(planeNorm)*1e3
449 <<" [T], local |v x Bnorm|: "<<locForce.mag()*1e3<<" [T].");
450 }
451 const double dS {(pos2 - pos1).mag() / static_cast<double>(m_fieldExtpSteps.size())};
452 ATH_MSG_DEBUG(__func__<<"() "<<__LINE__<<" - Integrated force: "<<Amg::toString(accumForce)<<", dS: "<<dS);
453 return accumForce * dS;
454 }
455 double MsTrackSeederTool::getPtimesQ(const Amg::Vector3D& forceIntegral,
456 const Amg::Vector3D& deltaDir) const {
457 const double PtimesQ {0.3 * Gaudi::Units::GeV * forceIntegral.mag2() / deltaDir.dot(forceIntegral)};
458
459 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - estimateQtimesP() force integral: "<<forceIntegral.mag()<<" [T*m], deltaDir: "
460 <<deltaDir.mag()<<", cos: "<<deltaDir.dot(forceIntegral)/ (deltaDir.mag() * forceIntegral.mag())
461 <<", PtimesQ: "<<PtimesQ/Gaudi::Units::GeV <<" [GeV].");
462 return PtimesQ;
463 }
464 double MsTrackSeederTool::estimateQtimesP(const Acts::GeometryContext& tgContext,
465 const MsTrackSeed& seed,
466 MagField::AtlasFieldCache& magField) const {
467 using namespace Muon::MuonStationIndex;
468
470 double deltaPhiAcc {0.};
471 std::optional<double> centralPhi {};
472 unsigned nSegsWithPhi{0};
473 for (const xAOD::MuonSegment* segment : seed.segments()) {
474 if (segment->nPhiLayers() > 0) {
475 if (!centralPhi) centralPhi = segment->position().phi();
476 deltaPhiAcc += P4Helpers::deltaPhi(*centralPhi, segment->position().phi());
477 ++nSegsWithPhi;
478 }
479 }
480 const double circPhi {nSegsWithPhi > 0
481 ? P4Helpers::deltaPhi(*centralPhi + deltaPhiAcc / nSegsWithPhi, 0.)
482 : seed.sector().phi()};
483
484 std::array<const xAOD::MuonSegment*, 3> segmentsToUse{};
485 // Try first to find segments in the inner, middle and outer layers. If both a barrel
486 // and endcap segments are present in the same layer, the barrel segment is preferred.
487 for (const xAOD::MuonSegment* segment : seed.segments()) {
488 ChIndex chIndex {segment->chamberIndex()};
489 switch(toLayerIndex(chIndex)) {
490 using enum LayerIndex;
491 case Inner:
492 if (!segmentsToUse[0] || isBarrel(chIndex)) {
493 segmentsToUse[0] = segment;
494 }
495 break;
496 case Middle:
497 if (!segmentsToUse[1] || isBarrel(chIndex)) {
498 segmentsToUse[1] = segment;
499 }
500 break;
501 case Outer:
502 if (!segmentsToUse[2] || isBarrel(chIndex)) {
503 segmentsToUse[2] = segment;
504 }
505 break;
506 default:
507 break;
508 }
509 }
510 unsigned nSegments = std::ranges::count_if(segmentsToUse,
511 [](const xAOD::MuonSegment* seg) { return seg != nullptr; });
512
515 if (nSegments < 3) {
516 auto missingSeg = std::ranges::find(segmentsToUse, nullptr);
517 for (const xAOD::MuonSegment* segment : seed.segments()) {
518 LayerIndex layIndex {toLayerIndex(segment->chamberIndex())};
519 if (layIndex != LayerIndex::Extended && layIndex != LayerIndex::BarrelExtended) {
520 continue;
521 }
522 assert(missingSeg != segmentsToUse.end());
523 *missingSeg = segment;
524 ++nSegments;
525 if (nSegments == 3) {
526 break;
527 }
528 missingSeg = std::ranges::find(segmentsToUse, nullptr);
529 }
530 std::ranges::sort(segmentsToUse, [](const xAOD::MuonSegment* seg1, const xAOD::MuonSegment* seg2) {
531 if (!seg1 || !seg2) {
532 return seg1 != nullptr;
533 }
534 return seg1->position().perp() < seg2->position().perp();
535 });
536 }
537 const Amg::Vector3D planeNorm {Acts::makeDirectionFromPhiTheta(circPhi + 90._degree, 90._degree)};
538 auto point = [&](const xAOD::MuonSegment* seg) {
539 return std::make_pair(segPosOntoPhiPlane(tgContext, planeNorm, *seg),
540 Amg::projectDirOntoPlane(seg->direction(), planeNorm));
541 };
542 return nSegments == 3
543 ? estimateQtimesP(planeNorm, point(segmentsToUse[0]), point(segmentsToUse[1]), point(segmentsToUse[2]), magField)
544 : estimateQtimesP(planeNorm, point(segmentsToUse[0]), point(segmentsToUse[1]), magField);
545 }
546 void MsTrackSeederTool::appendSegment(const Acts::GeometryContext& tgContext,
547 const xAOD::MuonSegment* segment,
548 const Location loc,
549 TreeRawVec_t& outContainer) const {
550
551 const unsigned segSector = segment->sector();
552 for (const auto proj : {SectorProjector::leftOverlap,
553 SectorProjector::center,
554 SectorProjector::rightOverlap}) {
556 const ExpandedSector projSector{segSector, proj};
557 if (segment->nPhiLayers() > 0 && projSector != ExpandedSector{segment->position().phi()}) {
558 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Segment @"<<Amg::toString(segment->position())
559 <<" is not in sector "<<projSector);
560 continue;
561 }
562 const Amg::Vector2D refPoint{expressOnCylinder(tgContext, *segment, loc, projSector)};
563 if (!withinBounds(refPoint, loc)) {
564 continue;
565 }
566 using enum SeedCoords;
567 std::array<double, 3> coords{Acts::filledArray<double, 3>(0.)};
569 coords[Acts::toUnderlying(eSector)] = projSector.sector();
572 coords[Acts::toUnderlying(eDetSection)] = Acts::copySign(Acts::toUnderlying(loc), refPoint[1]);
574 coords[Acts::toUnderlying(ePosOnCylinder)] = refPoint[Location::Barrel == loc];
575 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Add segment "<<::print(*segment)
576 <<" with "<<coords<<" to the search tree");
577 outContainer.emplace_back(std::move(coords), segment);
578 }
579 }
580 SearchTree_t MsTrackSeederTool::constructTree(const Acts::GeometryContext& tgContext,
581 const xAOD::MuonSegmentContainer& segments) const{
582 TreeRawVec_t rawData{};
583 rawData.reserve(3*segments.size());
584 for (const xAOD::MuonSegment* segment : segments){
585 appendSegment(tgContext, segment, Location::Barrel, rawData);
586 appendSegment(tgContext, segment, Location::Endcap, rawData);
587 }
588 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Create a new tree with "<<rawData.size()<<" entries. ");
589 return SearchTree_t{std::move(rawData)};
590 }
591 StatusCode MsTrackSeederTool::findTrackSeeds(const EventContext& ctx,
592 std::vector<MsTrackSeed>& outputSeeds) const {
593
594 const xAOD::MuonSegmentContainer* segments{nullptr};
595 ATH_CHECK(SG::get(segments, m_segmentKey , ctx));
596 const Acts::GeometryContext tgContext = m_ctxProvider.getGeometryContext(ctx);
597 SearchTree_t orderedSegs{constructTree(tgContext, *segments)};
598 MsTrackSeedContainer trackSeeds{};
599 using enum SeedCoords;
600 for (const auto& [coords, seedCandidate] : orderedSegs) {
603 if (!m_segSelector->passSeedingQuality(ctx, *seedCandidate)){
604 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Segment "<<::print(*seedCandidate)<<" does not pass the seeding quality.");
605 continue;
606 }
608 SearchTree_t::range_t selectRange{};
611 selectRange[Acts::toUnderlying(eDetSection)].shrink(coords[Acts::toUnderlying(eDetSection)] - 0.1,
612 coords[Acts::toUnderlying(eDetSection)] + 0.1);
614 selectRange[Acts::toUnderlying(ePosOnCylinder)].shrink(coords[Acts::toUnderlying(ePosOnCylinder)] - m_seedHalfLength,
615 coords[Acts::toUnderlying(ePosOnCylinder)] + m_seedHalfLength);
617 selectRange[Acts::toUnderlying(eSector)].shrink(coords[Acts::toUnderlying(eSector)] -0.25,
618 coords[Acts::toUnderlying(eSector)] +0.25);
619
620 MsTrackSeed newSeed{static_cast<Location>(std::abs(coords[Acts::toUnderlying(eDetSection)])),
621 ExpandedSector{static_cast<std::int8_t>(coords[Acts::toUnderlying(eSector)])}};
623 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Search for compatible segments to "<<::print(*seedCandidate)<<".");
624 orderedSegs.rangeSearchMapDiscard(selectRange, [&](
625 const SearchTree_t::coordinate_t& /*coords*/,
626 const xAOD::MuonSegment* extendWithMe) {
628 if (!m_segSelector->compatibleForTrack(ctx, *seedCandidate, *extendWithMe)) {
629 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Segment "<<::print(*extendWithMe)<<" is not compatible.");
630 return;
631 }
632 auto itr = std::ranges::find_if(newSeed.segments(), [extendWithMe](const xAOD::MuonSegment* onSeed){
633 return extendWithMe->chamberIndex() == onSeed->chamberIndex();
634 });
635 if (itr == newSeed.segments().end()){
636 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Add segment "<<::print(*extendWithMe)<<" to seed.");
637 newSeed.addSegment(extendWithMe);
638 }
639 else if (reducedChi2(**itr) > reducedChi2(*extendWithMe) &&
640 (*itr)->nPhiLayers() <= extendWithMe->nPhiLayers()) {
641
642 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Replace segment "<<::print(**itr)<<" with "
643 <<::print(*extendWithMe)<<" on seed due to better chi2.");
644 newSeed.replaceSegment(*itr, extendWithMe);
645 }
646 });
648 if (newSeed.segments().empty()) {
649 continue;
650 }
651
652 newSeed.addSegment(seedCandidate);
653
654 // Let's check if we build a single station seed and if yes reject it.
655 using namespace Muon::MuonStationIndex;
656 if(toLayerIndex(newSeed.segments().front()->chamberIndex()) == toLayerIndex(newSeed.segments().back()->chamberIndex())){
657 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Reject seed with segments in the same station.");
658 continue;
659 }
660
661 //Check if we have multiple segments from the same station, if so split the seed and create duplicate seeds
662
664 const double r = newSeed.location() == Location::Barrel ? 1.*m_barrelRadius
665 : coords[Acts::toUnderlying(ePosOnCylinder)];
666 const double z = newSeed.location() == Location::Barrel ? coords[Acts::toUnderlying(ePosOnCylinder)]
667 : coords[Acts::toUnderlying(eDetSection)]* m_endcapDiscZ;
668
669 Amg::Vector3D pos = r * newSeed.sector().radialDir()
670 + z * Amg::Vector3D::UnitZ();
671
672 newSeed.setPosition(std::move(pos));
673 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Add new seed "<<newSeed);
674 trackSeeds.emplace_back(std::move(newSeed));
675 }
676 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Found in total "<<trackSeeds.size()<<" before overlap removal");
677 // outputSeeds
678 trackSeeds = resolveOverlaps(std::move(trackSeeds));
679 outputSeeds.insert(outputSeeds.end(), std::make_move_iterator(trackSeeds.begin()),
680 std::make_move_iterator(trackSeeds.end()));
681 return StatusCode::SUCCESS;
682 }
685
687 std::ranges::sort(unresolved, [](const MsTrackSeed& a, const MsTrackSeed&b) {
688 return a.segments().size() > b.segments().size();
689 });
690 MsTrackSeedContainer outputSeeds{};
691 outputSeeds.reserve(unresolved.size());
692 std::ranges::copy_if(std::move(unresolved), std::back_inserter(outputSeeds),
693 [&outputSeeds](const MsTrackSeed& testMe) {
694 for (const MsTrackSeed& good : outputSeeds){
695 if (!testMe.sector().isNeighbour(good.sector())) {
696 continue;
697 }
698 const std::size_t sharedSegs = std::ranges::count_if(testMe.segments(),
699 [&good](const xAOD::MuonSegment* segInTest){
700 return Acts::rangeContainsValue(good.segments(), segInTest);
701 });
702 if (sharedSegs == testMe.segments().size()) {
703 return false;
704 }
705 }
706 return true;
707 });
708
709 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Found in total "<<outputSeeds.size()<<" after overlap removal");
710 return outputSeeds;
711 }
714 return m_detMgr->getSectorEnvelope(segment.chamberIndex(),
715 segment.sector(),
716 segment.etaIndex());
717 }
718}
Scalar mag() const
mag method
const PlainObject unit() const
This is a plugin that makes Eigen look like CLHEP & defines some convenience methods.
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
double charge(const T &p)
Definition AtlasPID.h:1003
static Double_t a
void print(char *figname, TCanvas *c1)
#define z
size_type size() const noexcept
Returns the number of elements in the collection.
Local cache for magnetic field (based on MagFieldServices/AtlasFieldSvcTLS.h).
void getField(const double *ATH_RESTRICT xyz, double *ATH_RESTRICT bxyz, double *ATH_RESTRICT deriv=nullptr)
get B field value at given position xyz[3] is in mm, bxyz[3] is in kT if deriv[9] is given,...
A spectrometer sector forms the envelope of all chambers that are placed in the same MS sector & laye...
const Acts::PlaneSurface & surface() const
Returns the associated surface.
Amg::Vector3D normalDir() const
Returns the vector that is normal to the plane spanned by the expanded sector.
std::int8_t sector() const
Returns the expanded sector number.
bool isNeighbour(const ExpandedSector &other) const
Amg::Vector3D radialDir() const
Returns the vector pointing radially along the sector plane.
void addSegment(const xAOD::MuonSegment *seg)
Append a segment to the seed.
Location location() const
Returns the location of the seed.
ExpandedSector sector() const
Returns the seed's sector.
Definition MsTrackSeed.h:61
std::span< const xAOD::MuonSegment *const > segments() const
Returns the vector of associated segments.
void replaceSegment(const xAOD::MuonSegment *exist, const xAOD::MuonSegment *updated)
Replaces an already added segment in the seed with a better suited one.
void setPosition(Amg::Vector3D &&pos)
set the seed's position
void appendSegment(const Acts::GeometryContext &tgContext, const xAOD::MuonSegment *segment, const Location loc, TreeRawVec_t &outContainer) const
Append the segment to the raw data container.
SG::ReadHandleKey< xAOD::MuonSegmentContainer > m_segmentKey
Declare the data dependency on the standard Mdt+Rpc+Tgc segment container & on the NSW segment contai...
virtual Acts::Result< Acts::BoundTrackParameters > estimateStartParameters(const EventContext &ctx, const MsTrackSeed &seed) const override final
virtual bool withinBounds(const Amg::Vector2D &projPos, const Location loc) const override final
MsTrackSeed::Location Location
Enum toggling whether the segment is in the endcap or barrel.
ToolHandle< ISegmentSelectionTool > m_segSelector
Pointer to the segement selection tool which compares two segments for their compatibilitiy.
Gaudi::Property< double > m_barrelRadius
The radius of he barrel cylinder.
SearchTree_t::vector_t TreeRawVec_t
Abbrivation of the KDTree raw data vector.
Gaudi::Property< double > m_endcapDiscRadius
Radius of the endcap discs.
Gaudi::Property< double > m_barrelLength
The maximum length of the barrel cylinder, if not capped by the placement of the endcap discs.
ServiceHandle< ActsTrk::ITrackingGeometrySvc > m_trackingGeometrySvc
Tracking geometry tool.
SearchTree_t constructTree(const Acts::GeometryContext &tgContext, const xAOD::MuonSegmentContainer &segments) const
Construct a complete search tree from a MuonSegment container.
Acts::KDTree< 3, const xAOD::MuonSegment *, double, std::array, 6 > SearchTree_t
Definition of the search tree class.
ActsTrk::ContextUtility m_ctxProvider
Utility to fetch the geometry, magnetic field and calibration context in the event.
const MuonGMR4::SpectrometerSector * envelope(const xAOD::MuonSegment &segment) const
Returns the spectrometer envelope associated to the segment (Coord system where the parameter are exp...
std::vector< double > m_fieldExtpSteps
The list of field steps in the force field integration.
double getPtimesQ(const Amg::Vector3D &forceIntegral, const Amg::Vector3D &deltaDir) const
Compute the charge times momentum from the integral of lorentz force and the total change in directio...
virtual StatusCode initialize() override final
Gaudi::Property< double > m_endcapDiscZ
Position of the endcap discs.
virtual StatusCode findTrackSeeds(const EventContext &ctx, std::vector< MsTrackSeed > &outSeeds) const override final
Retrieves the segment container from StoreGate and constructs TrackSeeds from them.
Gaudi::Property< unsigned > m_nFieldSteps
number of steps between two segments to integrate the magnetic field
Amg::Vector3D segPosOntoPhiPlane(const Acts::GeometryContext &tgContext, const Amg::Vector3D &planeNormal, const xAOD::MuonSegment &segment) const
Projects the segment position onto the plane with global phi = x The local coordinate system is arran...
SeedCoords
Abrivation of the seed coordinates.
@ eSector
Sector of the associated spectrometer sector.
@ ePosOnCylinder
Extrapolation position along the cylinder surface.
@ eDetSection
Encode the seed location (-1,1 -> endcaps, 0 -> barrel.
virtual Amg::Vector2D expressOnCylinder(const Acts::GeometryContext &tgContext, const xAOD::MuonSegment &segment, const Location loc, const ExpandedSector sector) const override final
Expresses the passed segment on the virtual cylinder constructed by the track seeder.
const MuonGMR4::MuonDetectorManager * m_detMgr
Gaudi::Property< double > m_seedHalfLength
Maximum separation of point on the cylinder to be picked up onto a seed.
MsTrackSeedContainer resolveOverlaps(MsTrackSeedContainer &&unresolved) const
Removes exact duplciates or partial subsets of the MsTrackSeeds.
virtual double estimateQtimesP(const EventContext &ctx, const Amg::Vector3D &planeNorm, std::span< const PosMomPair_t > circlePoints) const override final
Amg::Vector3D forceIntegration(const PosMomPair_t &point1, const PosMomPair_t &point2, const Amg::Vector3D &planeNorm, MagField::AtlasFieldCache &fieldCache) const
Compute the integral of magnetic force (v x B ) dS along a trajectory, given the initial and final po...
float numberDoF() const
Returns the numberDoF.
Amg::Vector3D direction() const
Returns the direction as Amg::Vector.
float chiSquared() const
std::uint8_t nPrecisionHits() const
Returns the number of precision hits.
::Muon::MuonStationIndex::TechnologyIndex technology() const
Returns the main technology of the segment.
::Muon::MuonStationIndex::ChIndex chamberIndex() const
Returns the chamber index.
Amg::Vector3D position() const
Returns the position as Amg::Vector.
int etaIndex() const
Returns the eta index, which corresponds to stationEta in the offline identifiers (and the ).
std::uint8_t nPhiLayers() const
Returns the number of trigger phi hits.
virtual xAOD::UncalibMeasType type() const =0
Returns the type of the measurement type as a simple enumeration.
int r
Definition globals.cxx:22
constexpr double energyToActs(const double athenaE)
Converts an energy scalar from Athena to Acts units.
Acts::Vector4 convertPosToActs(const Amg::Vector3D &athenaPos, const double athenaTime=0.)
Converts a position vector & time from Athena units into Acts units.
Amg::Vector3D projectDirOntoPlane(const Amg::Vector3D &direction, const Amg::Vector3D &planeNorm)
Project the direction vector onto the plane and renormalize to unity.
std::optional< double > intersect(const AmgVector(N)&posA, const AmgVector(N)&dirA, const AmgVector(N)&posB, const AmgVector(N)&dirB)
Calculates the point B' along the line B that's closest to a second line A.
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Affine3d Transform3D
Eigen::Matrix< double, 2, 1 > Vector2D
Eigen::Matrix< double, 3, 1 > Vector3D
const Acts::Surface * bottomBoundary(const Acts::TrackingVolume &volume)
Returns the boundary surface parallel to the x-y plane at negative local z.
const Acts::TrackingVolume * highestAlignable(const Acts::TrackingVolume *volume)
Returns the highest parent volume that is alignable.
Acts::BoundTrackParameters boundSegmentPars(const ActsTrk::GeometryContext &gctx, const MuonGMR4::MuonDetectorManager &detMgr, const xAOD::MuonSegment &segment, const Acts::ParticleHypothesis hypot=Acts::ParticleHypothesis::muon())
Returns the segment parameters as boundTrackParameters.
Parameters localSegmentPars(const xAOD::MuonSegment &seg)
Returns the localSegPars decoration from a xAODMuon::Segment.
This header ties the generic definitions in this package.
std::vector< MsTrackSeed > MsTrackSeedContainer
Definition MsTrackSeed.h:69
std::string printID(const xAOD::MuonSegment &seg)
Print the chamber ID of a segment, e.g.
const xAOD::UncalibratedMeasurement * getMeasurement(const xAOD::MuonSegment &segment, const std::size_t n)
Returns the n-th uncalibrated measurement.
double houghTanBeta(const Amg::Vector3D &v)
Returns the hough tanBeta [y] / [z].
std::size_t nMeasurements(const xAOD::MuonSegment &segment)
Returns the number of associated Uncalibrated measurements.
Acts::GeometryIdentifier volumeId(const Acts::Surface &surface)
Returns the identifier of the volume in which the surface is embedded.
bool isOutlierMeasurement(const xAOD::MuonSegment &segment, const std::size_t n)
Returns whether the n-the uncalibrated measurement is an outlier.
const xAOD::UncalibratedMeasurement * firstMeasurement(const xAOD::MuonSegment &segment, const bool skipOutlier=true)
Retrieves the first measurement associated with the segment.
Amg::Vector3D atFirstSurface(const Acts::GeometryContext &gctx, const xAOD::MuonSegment &segment, const bool skipOutlier=true)
Expresses the segment position on the surface of the first measurement.
std::string print(const cont_t &container)
Print a space point container to string.
double houghTanAlpha(const Amg::Vector3D &v)
: Returns the hough tanAlpha [x] / [z]
MsTrackSeederTool::SearchTree_t SearchTree_t
ChIndex chIndex(const std::string &index)
convert ChIndex name string to enum
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
LayerIndex toLayerIndex(ChIndex index)
convert ChIndex into LayerIndex
ChIndex
enum to classify the different chamber layers in the muon spectrometer
double deltaPhi(double phiA, double phiB)
delta Phi in range [-pi,pi[
Definition P4Helpers.h:34
const T * get(const ReadCondHandleKey< T > &key, const EventContext &ctx)
Convenience function to retrieve an object given a ReadCondHandleKey.
bool isNSW(const UncalibMeasType aodType)
Returns whether the measurement is a NSW measurement.
UncalibratedMeasurement_v1 UncalibratedMeasurement
Define the version of the uncalibrated measurement class.
MuonSegmentContainer_v1 MuonSegmentContainer
Definition of the current "MuonSegment container version".
MuonSegment_v1 MuonSegment
Reference the current persistent version:
const Acts::Surface & muonSurface(const UncalibratedMeasurement *meas)
Returns the associated Acts surface to the measurement.
Tell the compiler to optimize assuming that FP may trap.
#define CXXUTILS_TRAPPING_FP
Definition trapping_fp.h:24