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
22#include "GaudiKernel/PhysicalConstants.h"
23
24namespace {
25 using namespace Acts::UnitLiterals;
27 constexpr bool chargeAgree(double PtimesQ1, double PtimesQ2) {
28 return PtimesQ1 * PtimesQ2 > 0;
29 };
31 inline double momentumDev(double PtimesQ1, double PtimesQ2) {
32 const double denom {std::max(std::abs(PtimesQ1) + std::abs(PtimesQ2), Acts::s_epsilon)};
33 return std::abs(PtimesQ1 - PtimesQ2) / denom;
34 };
35 float reducedChi2(const xAOD::MuonSegment& seg) {
36 return seg.chiSquared() / std::max(1.f, seg.numberDoF());
37 }
38 std::string print(const xAOD::MuonSegment& seg) {
39 return std::format("{:}, nPrecHits: {:}, nPhiHits: {:}", MuonR4::printID(seg),
40 seg.nPrecisionHits(), seg.nPhiLayers());
41 }
42
43 bool isNswSegment(const xAOD::MuonSegment& seg) {
44 using namespace Muon::MuonStationIndex;
45 return seg.technology() == TechnologyIndex::STGC ||
47 toStationIndex(seg.chamberIndex()) == StIndex::EE;
48 }
49
50}
51
52namespace MuonR4{
54
56 ATH_CHECK(m_segSelector.retrieve());
59 ATH_CHECK(m_segmentKey.initialize(!m_segmentKey.empty()));
60 ATH_CHECK(detStore()->retrieve(m_detMgr));
61
62 if (m_nFieldSteps == 0) {
63 ATH_MSG_ERROR("The number of field steps must not be zero "<<m_nFieldSteps);
64 return StatusCode::FAILURE;
65 }
67 const double stepSize {1. / m_nFieldSteps};
68 for (std::size_t i = 0; i < m_nFieldSteps; ++i) {
69 m_fieldExtpSteps.push_back((static_cast<double>(i) + 0.5) * stepSize);
70 }
71 return StatusCode::SUCCESS;
72 }
73
74 Acts::Result<Acts::BoundTrackParameters>
76 const MsTrackSeed& seed) const {
77 const Acts::GeometryContext tgContext = m_trackingGeometryTool->getGeometryContext(ctx).context();
78 const Acts::MagneticFieldContext mfContext = m_extrapolationTool->getMagneticFieldContext(ctx);
80 mfContext.get<const AtlasFieldCacheCondObj*>()->getInitializedCache(magField);
81
82 const xAOD::MuonSegment* refSeg{nullptr};
83 for (const xAOD::MuonSegment* segment : seed.segments()) {
90 if (!isNswSegment(*segment) &&
91 m_segSelector->passSeedingQuality(ctx, *segment)) {
92 refSeg = segment;
93 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Set reference segment to "<<::print(*segment));
94 break;
95 }
96 }
97 //if we did not find a reference segment let's try the NSW one before we give up on the track
98 if(!refSeg){
99 for (const xAOD::MuonSegment* segment : seed.segments()) {
100 if (isNswSegment(*segment) &&
101 m_segSelector->passSeedingQuality(ctx, *segment)) {
102 refSeg = segment;
103 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - NSW is the best what we have apparently....");
104 break;
105 }
106 }
107 }
108
109 if (!refSeg) {
110 ATH_MSG_WARNING(__func__<<"() "<<__LINE__
111 <<" - No reference segment passing seeding quality was found.");
112 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
113 }
114 Amg::Vector3D seedPos{atFirstSurface(tgContext, *refSeg)};
115 Amg::Vector3D seedDir{refSeg->direction()};
116 ATH_MSG_DEBUG(__func__<<"() "<<__LINE__<<" - Initial seed pos: "<<Amg::toString(seedPos)
117 <<", dir: "<<Amg::toString(seedDir) << " eta " << seedDir.eta() << " phi "
118 << (seedDir.phi() /Gaudi::Units::degree) );
119
120 const xAOD::MuonSegment* frontSegment = seed.segments().front();
121
122 const Acts::Surface& firstSurf = xAOD::muonSurface(firstMeasurement(*frontSegment));
123 const Acts::GeometryIdentifier volId = volumeId(firstSurf);
124
125 // Find the first measurement
126 const Acts::TrackingVolume* volume{m_trackingGeometryTool->trackingGeometry()->findVolume(volId)};
127
128 if (!volume) {
129 ATH_MSG_WARNING(__func__<<"() "<<__LINE__
130 <<" - Failed to find tracking volume for seed measurement "<<volId);
131 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
132 }
133 if (volume->motherVolume() && volume->motherVolume()->isAlignable()) {
134 volume = volume->motherVolume();
135 }
136 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__
137 <<" - Bounding volume "<<volume->volumeName()
138 <<", trf: "<<Amg::toString(volume->localToGlobalTransform(tgContext))
139 <<", bounds: "<<volume->volumeBounds());
143 if (frontSegment != refSeg) {
144 const Amg::Vector3D frontSegPos = atFirstSurface(tgContext, *frontSegment);
145 const Amg::Transform3D toFirstTrf = firstSurf.localToGlobalTransform(tgContext).inverse();
146 const Amg::Vector3D locFrontSegPos = toFirstTrf * frontSegPos;
147 if (!volume->inside(tgContext, frontSegPos)) {
148 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Segment "<<::print(*frontSegment)
149 <<" not inside mother volume: "<<volume->volumeName()<<", "
150 <<Amg::toString(volume->globalToLocalTransform(tgContext)*frontSegPos)
151 <<", bounds: "<<volume->volumeBounds()<<", "
152 <<SegmentFit::localSegmentPars(*frontSegment));
153 }
154
158 const Acts::MultiIntersection firstIsect = firstSurf.intersect(tgContext, seedPos, seedDir,
159 Acts::BoundaryTolerance::Infinite());
160 const Amg::Vector3D locAtFirst = toFirstTrf * firstIsect.at(0).position();
161 if (firstSurf.type() == Acts::Surface::SurfaceType::Straw) {
162 const auto& bounds = static_cast<const Acts::LineBounds&>(firstSurf.bounds());
163 using enum Acts::LineBounds::BoundValues;
166 const Amg::Vector3D locStartPos{locFrontSegPos.x(), locFrontSegPos.y(),
167 std::clamp(locAtFirst.z(), -bounds.get(eHalfLengthZ), bounds.get(eHalfLengthZ))};
168 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - The first surface is a straw "
169 <<bounds<<", track seed @first: "<<Amg::toString(locAtFirst)<<" vs. segment @first: "
170 <<Amg::toString(locFrontSegPos));
171 seedPos = firstSurf.localToGlobalTransform(tgContext) * locStartPos;
172 } else if (firstSurf.type() == Acts::Surface::SurfaceType::Plane) {
173 if (isNswSegment(*frontSegment)) {
174 seedPos = frontSegPos;
175 }
176 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - The first surface is a straw "
177 <<firstSurf.bounds()<<", "<<Amg::toString(locAtFirst)<<" vs. "<<Amg::toString(locFrontSegPos));
178 } else {
179 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Unexpected surface type "<<firstSurf.type());
180 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
181 }
182 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Updated seed position: "<<Amg::toString(seedPos));
183 }
184
185
186 auto boundSurf = MuonGMR4::bottomBoundary(*volume);
187 if (!boundSurf) {
188 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Failed to find boundary surface for tracking volume");
189 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
190 }
191 std::shared_ptr<const Acts::Surface> targetSurf{};
196 auto propagateToBoundary = [&](const Acts::Surface& volBoundary) -> Acts::Result<Amg::Vector3D> {
197
198 const Amg::Transform3D& trf{volBoundary.localToGlobalTransform(tgContext)};
199 using namespace Acts::PlanarHelper;
200 auto pIsect = intersectPlane(seedPos, seedDir, trf.linear().col(Amg::z), trf.translation());
202 if (pIsect.pathLength() > Acts::s_epsilon || !pIsect.isValid()) {
203 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Intersection @"<<Amg::toString(pIsect.position())
204 <<" is forward "<<pIsect.pathLength()<<" or invalid "<<(!pIsect.isValid())
205 <<" within volume "<<volume->inside(tgContext, pIsect.position()));
206 return Acts::Result<Amg::Vector3D>::failure(std::make_error_code(std::errc::invalid_argument));
207 }
208 Acts::Result<Amg::Vector2D> locPos = volBoundary.globalToLocal(tgContext, pIsect.position(),
209 Amg::Vector3D::Zero());
210 if (!locPos.ok()){
211 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Intersection is not on surface "<<
212 Amg::toString(trf.inverse()*pIsect.position()));
213 return Acts::Result<Amg::Vector3D>::failure(std::make_error_code(std::errc::invalid_argument));
214 }
215 if (!volBoundary.insideBounds(*locPos)) {
216 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Intersection is outside the boundaries: "<<
217 Amg::toString(*locPos)<<", bounds: "<<volBoundary.bounds());
218 return Acts::Result<Amg::Vector3D>::failure(std::make_error_code(std::errc::invalid_argument));
219 }
220 targetSurf = volBoundary.getSharedPtr();
221 return Acts::Result<Amg::Vector3D>::success(pIsect.position());
222 };
224 auto pIsect = propagateToBoundary(*boundSurf);
228 if (!pIsect.ok() && volume->isAlignable()) {
229 const Acts::VolumePlacementBase* placement = volume->volumePlacement();
230 for (std::size_t portal = 0; !pIsect.ok() && portal< placement->nPortalPlacements(); ++portal) {
231 pIsect = propagateToBoundary(placement->portalPlacement(portal)->surface());
232 }
233 }
234 if (!pIsect.ok()) {
235 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" Cannot create valid start parameters from seed "<<seed<<".");
236 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
237 }
238 /* Calcul*/
239 auto fourPos = ActsTrk::convertPosToActs(*pIsect, (*pIsect).mag() / Gaudi::Units::c_light);
240 const double qOverP = 1./ ActsTrk::energyToActs(estimateQtimesP(tgContext, seed, magField));
241 return Acts::BoundTrackParameters::create(tgContext, targetSurf,
242 fourPos, seedDir, qOverP,
243 Acts::BoundMatrix::Identity(),
244 Acts::ParticleHypothesis::muon());
245 }
246 Amg::Vector3D MsTrackSeederTool::segPosOntoPhiPlane(const Acts::GeometryContext& tgContext,
247 const Amg::Vector3D& planeNormal,
248 const xAOD::MuonSegment& segment) const{
249 const std::size_t nMeas = nMeasurements(segment);
250 Amg::Vector3D wireDir{Amg::Vector3D::Zero()};
251 for (std::size_t meas = 0; meas < nMeas; ++ meas) {
252 if (isOutlierMeasurement(segment, meas)) {
253 continue;
254 }
255 const xAOD::UncalibratedMeasurement* measPtr = getMeasurement(segment, meas);
257 wireDir = xAOD::muonSurface(measPtr).localToGlobalTransform(tgContext).linear().col(Amg::z);
258 break;
259 } else if (xAOD::isNSW(measPtr->type())) {
260 wireDir = m_trackingGeometryTool->trackingGeometry()->findVolume(volumeId(xAOD::muonSurface(measPtr)))->
261 localToGlobalTransform(tgContext).linear().col(Amg::x);
262 break;
263 }
264 }
266 if (nMeas == 0ul) {
267 wireDir = envelope(segment)->surface().localToGlobalTransform(tgContext).linear().col(Amg::x);
268 }
269
270 return Acts::PlanarHelper::intersectPlane(segment.position(), wireDir,
271 planeNormal, Amg::Vector3D::Zero()).position();
272 }
273 Amg::Vector2D MsTrackSeederTool::expressOnCylinder(const Acts::GeometryContext& tgContext,
274 const xAOD::MuonSegment& segment,
275 const Location loc,
276 const ExpandedSector sector) const {
278 const Amg::Vector3D pos{segPosOntoPhiPlane(tgContext, sector.normalDir(), segment)};
279 const Amg::Vector3D dir{segment.direction()};
280
281 const Amg::Vector2D projPos{pos.perp(), pos.z()};
282 const Amg::Vector2D projDir{dir.perp(), dir.z()};
283
284 ATH_MSG_VERBOSE( "segment position:" << Amg::toString(segment.position())
285 << ", direction: " << Amg::toString(segment.direction()) );
286 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Express segment in @"<<Amg::toString(pos)
287 <<", direction: "<<Amg::toString(dir)<< " sector projector: " << sector
288 << " location: " << Acts::toUnderlying(loc));
289 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Projected position onto sector: "<<Amg::toString(projPos)
290 <<", projected direction: "<<Amg::toString(projDir));
291
292 double lambda{0.};
293 if (Location::Barrel == loc) {
294 lambda = Amg::intersect<2>(projPos, projDir, Amg::Vector2D::UnitX(),
295 m_barrelRadius).value_or(10. * Gaudi::Units::km);
296 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Intersect with barrel at radius: "<<m_barrelRadius<<" --> "<<Amg::toString(projPos + lambda * projDir));
297 } else {
298 lambda = Amg::intersect<2>(projPos, projDir, Amg::Vector2D::UnitY(),
299 Acts::copySign(1.*m_endcapDiscZ, projPos[1])).value_or(10. * Gaudi::Units::km);
300 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Intersect with endcap at z: "<<Acts::copySign(1.*m_endcapDiscZ, projPos[1])
301 <<" --> "<<Amg::toString(projPos + lambda * projDir));
302 }
303 return projPos + lambda * projDir;
304 }
306 const Location loc) const {
307 using enum Location;
308 if (loc == Barrel && std::abs(projPos[1]) > std::min(m_endcapDiscZ, m_barrelLength)) {
309 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Position "<<Amg::toString(projPos)<<
310 " exceeds cylinder boundaries ("<<(1.*m_barrelRadius)<<", "
311 <<std::min(m_endcapDiscZ, m_barrelLength)<<")");
312 return false;
313 } else if (loc == Endcap && (0 > projPos[0] || projPos[0] > m_endcapDiscRadius)) {
314 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Position "<<Amg::toString(projPos)<<
315 " exceeds endcap boundaries ("<<(1.*m_endcapDiscRadius)<<", "<<Acts::copySign(1.*m_endcapDiscZ, projPos[1])<<")");
316 return false;
317 }
318 return true;
319 }
320 double MsTrackSeederTool::estimateQtimesP(const EventContext& ctx,
321 const Amg::Vector3D& planeNorm,
322 std::span<const PosMomPair_t> circlePoints) const {
323 const Acts::MagneticFieldContext mfContext = m_extrapolationTool->getMagneticFieldContext(ctx);
324 MagField::AtlasFieldCache magField{};
325 mfContext.get<const AtlasFieldCacheCondObj*>()->getInitializedCache(magField);
326 if (circlePoints.size() < 2 || circlePoints.size() > 3){
327 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Invalid number of circle points passed: "<<circlePoints.size());
328 constexpr double straightLine = 10._TeV;
329 return straightLine;
330 }
331 return circlePoints.size() == 3 ? estimateQtimesP(planeNorm, circlePoints[0], circlePoints[1], circlePoints[2], magField)
332 : estimateQtimesP(planeNorm, circlePoints[0], circlePoints[1], magField);
333 }
335 const PosMomPair_t& p1,
336 const PosMomPair_t& p2,
337 const PosMomPair_t& p3,
338 MagField::AtlasFieldCache& fieldCache) const {
339 // When 3 points are available, we can use each pair of segments to estimate
340 // the momentum and charge, and then combine the estimates. To make the
341 // combination, we define a struct to hold each PtimesQ estimate
342 struct Estimate {
343 double PtimesQ{0.};
344 // Weighting factor based on the magnitude of the integrated force.
345 double weight{0.};
346 // Scores as a combination of charge agreement and momentum deviation.
347 double score{0.};
348 };
349
350 const Amg::Vector3D force12 {forceIntegration(p1, p2, planeNorm, fieldCache)};
351 const Amg::Vector3D force23 {forceIntegration(p2, p3, planeNorm, fieldCache)};
352 const Amg::Vector3D force13 {force12 + force23};
353
354 std::vector<Estimate> estimates{};
355 const double weightNorm {force12.mag() + force23.mag()};
356 // Pairwise momentum estimates: 12 and 23
357 estimates.emplace_back(getPtimesQ(force12, p2.second - p1.second), force12.mag()/weightNorm, 0.);
358 estimates.emplace_back(getPtimesQ(force23, p3.second - p2.second), force23.mag()/weightNorm, 0.);
359 // Two estimates for pair 13: using segment directions and using position differences
360 const double weight13 {force13.mag()/weightNorm};
361 estimates.emplace_back(getPtimesQ(force13, p3.second - p1.second), weight13, 0.);
362 const Amg::Vector3D t12 {(p2.first - p1.first).unit()};
363 const Amg::Vector3D t23 {(p3.first - p2.first).unit()};
364 estimates.emplace_back(getPtimesQ(force13, t23 - t12), weight13, 0.);
365
366 for (std::size_t i {0}; i < estimates.size(); ++i) {
367 Estimate& est1 {estimates[i]};
368 for (std::size_t j {i+1}; j < estimates.size(); ++j) {
369 Estimate& est2 {estimates[j]};
370 // Compute the charge agreement score & momentum deviation
371 double w {std::min(est1.weight, est2.weight)};
372 double chargeScore {chargeAgree(est1.PtimesQ, est2.PtimesQ) ? 1. : -1.};
373 double pDevPenalty {momentumDev(est1.PtimesQ, est2.PtimesQ)};
374 // Update the scores
375 est1.score += w * (chargeScore - pDevPenalty);
376 est2.score += w * (chargeScore - pDevPenalty);
377 }
378 }
379 if (msgLvl(MSG::VERBOSE)) {
380 std::vector<std::string> names {"Pair01", "Pair12", "Pair02Seg", "Pair02Pos"};
381 for (const auto& [i, est] : Acts::enumerate(estimates)) {
382 ATH_MSG_VERBOSE(__func__<<"() Estimate "<<names[i]<<": PtimesQ: "<<est.PtimesQ*1e-3
383 <<", weight: "<<est.weight<<", score: "<<est.score);
384 }
385 }
386 // Find the best charge estimate and estimate the final momentum as a weighted average of the
387 // estimates that agree with the best charge
388 const Estimate& bestEstimate {*std::ranges::max_element(estimates,
389 std::ranges::less{}, &Estimate::score)};
390 const double charge {std::copysign(1., bestEstimate.PtimesQ)};
391
392 double totalSum {0.}, totalWeight {0.};
393 for (const Estimate& est : estimates) {
394 if (chargeAgree(est.PtimesQ, charge)) {
395 totalSum += est.PtimesQ * est.weight;
396 totalWeight += est.weight;
397 }
398 }
399 assert(totalWeight > Acts::s_epsilon);
400 return totalSum / totalWeight;
401 }
403 const PosMomPair_t& p1,
404 const PosMomPair_t& p2,
405 MagField::AtlasFieldCache& fieldCache) const {
406
407 return getPtimesQ(forceIntegration(p1, p2, planeNorm, fieldCache),
408 p2.second - p1.second);
409 }
411 const PosMomPair_t& point2,
412 const Amg::Vector3D& planeNorm,
413 MagField::AtlasFieldCache& fieldCache) const {
414 const auto& [pos1, dir1] = point1;
415 const auto& [pos2, dir2] = point2;
416 ATH_MSG_DEBUG(__func__<<"() "<<__LINE__<<" - Integrate field from "<<Amg::toString(pos1)<<", "<<Amg::toString(dir1)
417 <<" to "<<Amg::toString(pos2)<<", "<<Amg::toString(dir2));
418
419 Amg::Vector3D locField{Amg::Vector3D::Zero()};
420 Amg::Vector3D accumForce{Amg::Vector3D::Zero()};
421 for (double fieldStep : m_fieldExtpSteps) {
422 const Amg::Vector3D extPos {(1. - fieldStep) * pos1 + fieldStep * pos2};
423 const Amg::Vector3D extDir {((1. - fieldStep) * dir1 + fieldStep * dir2).unit()};
424
425 fieldCache.getField(extPos.data(), locField.data());
426 const Amg::Vector3D locForce {locField.dot(planeNorm) * extDir.cross(planeNorm)};
427 accumForce += locForce;
428
429 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - step: "<<fieldStep
430 <<", pos: "<<Amg::toString(extPos)<<", dir: "<<Amg::toString(extDir)
431 <<" --> local |B|: "<<locField.mag()*1e3 << " [T]"<<", |Bnorm|: "<<locField.dot(planeNorm)*1e3
432 <<" [T], local |v x Bnorm|: "<<locForce.mag()*1e3<<" [T].");
433 }
434 const double dS {(pos2 - pos1).mag() / static_cast<double>(m_fieldExtpSteps.size())};
435 ATH_MSG_DEBUG(__func__<<"() "<<__LINE__<<" - Integrated force: "<<Amg::toString(accumForce)<<", dS: "<<dS);
436 return accumForce * dS;
437 }
438 double MsTrackSeederTool::getPtimesQ(const Amg::Vector3D& forceIntegral,
439 const Amg::Vector3D& deltaDir) const {
440 const double PtimesQ {0.3 * Gaudi::Units::GeV * forceIntegral.mag2() / deltaDir.dot(forceIntegral)};
441
442 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - estimateQtimesP() force integral: "<<forceIntegral.mag()<<" [T*m], deltaDir: "
443 <<deltaDir.mag()<<", cos: "<<deltaDir.dot(forceIntegral)/ (deltaDir.mag() * forceIntegral.mag())
444 <<", PtimesQ: "<<PtimesQ/Gaudi::Units::GeV <<" [GeV].");
445 return PtimesQ;
446 }
447 double MsTrackSeederTool::estimateQtimesP(const Acts::GeometryContext& tgContext,
448 const MsTrackSeed& seed,
449 MagField::AtlasFieldCache& magField) const {
450 using namespace Muon::MuonStationIndex;
451
453 double deltaPhiAcc {0.};
454 std::optional<double> centralPhi {};
455 unsigned nSegsWithPhi{0};
456 for (const xAOD::MuonSegment* segment : seed.segments()) {
457 if (segment->nPhiLayers() > 0) {
458 if (!centralPhi) centralPhi = segment->position().phi();
459 deltaPhiAcc += P4Helpers::deltaPhi(*centralPhi, segment->position().phi());
460 ++nSegsWithPhi;
461 }
462 }
463 const double circPhi {nSegsWithPhi > 0
464 ? P4Helpers::deltaPhi(*centralPhi + deltaPhiAcc / nSegsWithPhi, 0.)
465 : seed.sector().phi()};
466
467 std::array<const xAOD::MuonSegment*, 3> segmentsToUse{};
468 // Try first to find segments in the inner, middle and outer layers. If both a barrel
469 // and endcap segments are present in the same layer, the barrel segment is preferred.
470 for (const xAOD::MuonSegment* segment : seed.segments()) {
471 ChIndex chIndex {segment->chamberIndex()};
472 switch(toLayerIndex(chIndex)) {
473 using enum LayerIndex;
474 case Inner:
475 if (!segmentsToUse[0] || isBarrel(chIndex)) {
476 segmentsToUse[0] = segment;
477 }
478 break;
479 case Middle:
480 if (!segmentsToUse[1] || isBarrel(chIndex)) {
481 segmentsToUse[1] = segment;
482 }
483 break;
484 case Outer:
485 if (!segmentsToUse[2] || isBarrel(chIndex)) {
486 segmentsToUse[2] = segment;
487 }
488 break;
489 default:
490 break;
491 }
492 }
493 unsigned nSegments = std::ranges::count_if(segmentsToUse,
494 [](const xAOD::MuonSegment* seg) { return seg != nullptr; });
495
498 if (nSegments < 3) {
499 auto missingSeg = std::ranges::find(segmentsToUse, nullptr);
500 for (const xAOD::MuonSegment* segment : seed.segments()) {
501 LayerIndex layIndex {toLayerIndex(segment->chamberIndex())};
502 if (layIndex != LayerIndex::Extended && layIndex != LayerIndex::BarrelExtended) {
503 continue;
504 }
505 assert(missingSeg != segmentsToUse.end());
506 *missingSeg = segment;
507 ++nSegments;
508 if (nSegments == 3) {
509 break;
510 }
511 missingSeg = std::ranges::find(segmentsToUse, nullptr);
512 }
513 std::ranges::sort(segmentsToUse, [](const xAOD::MuonSegment* seg1, const xAOD::MuonSegment* seg2) {
514 if (!seg1 || !seg2) {
515 return seg1 != nullptr;
516 }
517 return seg1->position().perp() < seg2->position().perp();
518 });
519 }
520 const Amg::Vector3D planeNorm {Acts::makeDirectionFromPhiTheta(circPhi + 90._degree, 90._degree)};
521 auto point = [&](const xAOD::MuonSegment* seg) {
522 return std::make_pair(segPosOntoPhiPlane(tgContext, planeNorm, *seg),
523 Amg::projectDirOntoPlane(seg->direction(), planeNorm));
524 };
525 return nSegments == 3
526 ? estimateQtimesP(planeNorm, point(segmentsToUse[0]), point(segmentsToUse[1]), point(segmentsToUse[2]), magField)
527 : estimateQtimesP(planeNorm, point(segmentsToUse[0]), point(segmentsToUse[1]), magField);
528 }
529 void MsTrackSeederTool::appendSegment(const Acts::GeometryContext& tgContext,
530 const xAOD::MuonSegment* segment,
531 const Location loc,
532 TreeRawVec_t& outContainer) const {
533
534 const unsigned segSector = segment->sector();
535 for (const auto proj : {SectorProjector::leftOverlap,
536 SectorProjector::center,
537 SectorProjector::rightOverlap}) {
539 const ExpandedSector projSector{segSector, proj};
540 if (segment->nPhiLayers() > 0 && projSector != ExpandedSector{segment->position().phi()}) {
541 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Segment @"<<Amg::toString(segment->position())
542 <<" is not in sector "<<projSector);
543 continue;
544 }
545 const Amg::Vector2D refPoint{expressOnCylinder(tgContext, *segment, loc, projSector)};
546 if (!withinBounds(refPoint, loc)) {
547 continue;
548 }
549 using enum SeedCoords;
550 std::array<double, 3> coords{Acts::filledArray<double, 3>(0.)};
552 coords[Acts::toUnderlying(eSector)] = projSector.sector();
555 coords[Acts::toUnderlying(eDetSection)] = Acts::copySign(Acts::toUnderlying(loc), refPoint[1]);
557 coords[Acts::toUnderlying(ePosOnCylinder)] = refPoint[Location::Barrel == loc];
558 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Add segment "<<::print(*segment)
559 <<" with "<<coords<<" to the search tree");
560 outContainer.emplace_back(std::move(coords), segment);
561 }
562 }
563 SearchTree_t MsTrackSeederTool::constructTree(const Acts::GeometryContext& tgContext,
564 const xAOD::MuonSegmentContainer& segments) const{
565 TreeRawVec_t rawData{};
566 rawData.reserve(3*segments.size());
567 for (const xAOD::MuonSegment* segment : segments){
568 appendSegment(tgContext, segment, Location::Barrel, rawData);
569 appendSegment(tgContext, segment, Location::Endcap, rawData);
570 }
571 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Create a new tree with "<<rawData.size()<<" entries. ");
572 return SearchTree_t{std::move(rawData)};
573 }
574 StatusCode MsTrackSeederTool::findTrackSeeds(const EventContext& ctx,
575 std::vector<MsTrackSeed>& outputSeeds) const {
576
577 const xAOD::MuonSegmentContainer* segments{nullptr};
578 ATH_CHECK(SG::get(segments, m_segmentKey , ctx));
579 const Acts::GeometryContext tgContext = m_trackingGeometryTool->getGeometryContext(ctx).context();
580 SearchTree_t orderedSegs{constructTree(tgContext, *segments)};
581 MsTrackSeedContainer trackSeeds{};
582 using enum SeedCoords;
583 for (const auto& [coords, seedCandidate] : orderedSegs) {
586 if (!m_segSelector->passSeedingQuality(ctx, *seedCandidate)){
587 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Segment "<<::print(*seedCandidate)<<" does not pass the seeding quality.");
588 continue;
589 }
591 SearchTree_t::range_t selectRange{};
594 selectRange[Acts::toUnderlying(eDetSection)].shrink(coords[Acts::toUnderlying(eDetSection)] - 0.1,
595 coords[Acts::toUnderlying(eDetSection)] + 0.1);
597 selectRange[Acts::toUnderlying(ePosOnCylinder)].shrink(coords[Acts::toUnderlying(ePosOnCylinder)] - m_seedHalfLength,
598 coords[Acts::toUnderlying(ePosOnCylinder)] + m_seedHalfLength);
600 selectRange[Acts::toUnderlying(eSector)].shrink(coords[Acts::toUnderlying(eSector)] -0.25,
601 coords[Acts::toUnderlying(eSector)] +0.25);
602
603 MsTrackSeed newSeed{static_cast<Location>(std::abs(coords[Acts::toUnderlying(eDetSection)])),
604 ExpandedSector{static_cast<std::int8_t>(coords[Acts::toUnderlying(eSector)])}};
606 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Search for compatible segments to "<<::print(*seedCandidate)<<".");
607 orderedSegs.rangeSearchMapDiscard(selectRange, [&](
608 const SearchTree_t::coordinate_t& /*coords*/,
609 const xAOD::MuonSegment* extendWithMe) {
611 if (!m_segSelector->compatibleForTrack(ctx, *seedCandidate, *extendWithMe)) {
612 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Segment "<<::print(*extendWithMe)<<" is not compatible.");
613 return;
614 }
615 auto itr = std::ranges::find_if(newSeed.segments(), [extendWithMe](const xAOD::MuonSegment* onSeed){
616 return extendWithMe->chamberIndex() == onSeed->chamberIndex();
617 });
618 if (itr == newSeed.segments().end()){
619 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Add segment "<<::print(*extendWithMe)<<" to seed.");
620 newSeed.addSegment(extendWithMe);
621 }
622 else if (reducedChi2(**itr) > reducedChi2(*extendWithMe) &&
623 (*itr)->nPhiLayers() <= extendWithMe->nPhiLayers()) {
624
625 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Replace segment "<<::print(**itr)<<" with "
626 <<::print(*extendWithMe)<<" on seed due to better chi2.");
627 newSeed.replaceSegment(*itr, extendWithMe);
628 }
629 });
631 if (newSeed.segments().empty()) {
632 continue;
633 }
634
635 newSeed.addSegment(seedCandidate);
636
637 // Let's check if we build a single station seed and if yes reject it.
638 using namespace Muon::MuonStationIndex;
639 if(toLayerIndex(newSeed.segments().front()->chamberIndex()) == toLayerIndex(newSeed.segments().back()->chamberIndex())){
640 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Reject seed with segments in the same station.");
641 continue;
642 }
643
644 //Check if we have multiple segments from the same station, if so split the seed and create duplicate seeds
645
647 const double r = newSeed.location() == Location::Barrel ? 1.*m_barrelRadius
648 : coords[Acts::toUnderlying(ePosOnCylinder)];
649 const double z = newSeed.location() == Location::Barrel ? coords[Acts::toUnderlying(ePosOnCylinder)]
650 : coords[Acts::toUnderlying(eDetSection)]* m_endcapDiscZ;
651
652 Amg::Vector3D pos = r * newSeed.sector().radialDir()
653 + z * Amg::Vector3D::UnitZ();
654
655 newSeed.setPosition(std::move(pos));
656 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Add new seed "<<newSeed);
657 trackSeeds.emplace_back(std::move(newSeed));
658 }
659 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Found in total "<<trackSeeds.size()<<" before overlap removal");
660 // outputSeeds
661 trackSeeds = resolveOverlaps(std::move(trackSeeds));
662 outputSeeds.insert(outputSeeds.end(), std::make_move_iterator(trackSeeds.begin()),
663 std::make_move_iterator(trackSeeds.end()));
664 return StatusCode::SUCCESS;
665 }
668
670 std::ranges::sort(unresolved, [](const MsTrackSeed& a, const MsTrackSeed&b) {
671 return a.segments().size() > b.segments().size();
672 });
673 MsTrackSeedContainer outputSeeds{};
674 outputSeeds.reserve(unresolved.size());
675 std::ranges::copy_if(std::move(unresolved), std::back_inserter(outputSeeds),
676 [&outputSeeds](const MsTrackSeed& testMe) {
677 for (const MsTrackSeed& good : outputSeeds){
678 if (!testMe.sector().isNeighbour(good.sector())) {
679 continue;
680 }
681 const std::size_t sharedSegs = std::ranges::count_if(testMe.segments(),
682 [&good](const xAOD::MuonSegment* segInTest){
683 return Acts::rangeContainsValue(good.segments(), segInTest);
684 });
685 if (sharedSegs == testMe.segments().size()) {
686 return false;
687 }
688 }
689 return true;
690 });
691
692 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" - Found in total "<<outputSeeds.size()<<" after overlap removal");
693 return outputSeeds;
694 }
697 return m_detMgr->getSectorEnvelope(segment.chamberIndex(),
698 segment.sector(),
699 segment.etaIndex());
700 }
701}
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:997
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:63
const std::vector< const xAOD::MuonSegment * > & 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
ToolHandle< ActsTrk::IExtrapolationTool > m_extrapolationTool
Track extrapolation tool.
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.
PublicToolHandle< ActsTrk::ITrackingGeometryTool > m_trackingGeometryTool
Tracking geometry tool.
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.
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.
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.
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:71
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.
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.
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.