ATLAS Offline Software
Loading...
Searching...
No Matches
HGTDTrackExtensionAlg.cxx
Go to the documentation of this file.
1
11
13#include "AsgTools/ToolStore.h"
19#include "ActsInterop/Logger.h"
27#include "Acts/TrackFinding/TrackStateCreator.hpp"
28#include "Acts/Surfaces/PlaneSurface.hpp"
29#include "Acts/Surfaces/RectangleBounds.hpp"
30#include "Acts/Utilities/VectorHelpers.hpp"
31#include <optional>
35#include "GaudiKernel/PhysicalConstants.h" // for Gaudi::Units::c_light
37
38
39namespace ActsTrk{
40
41// ---------------- Initialize ----------------
43{
44 ATH_MSG_DEBUG("Initializing " << name() << "...");
45
48
49 // Initialize all WriteDecorHandleKeys
53 ATH_CHECK(m_layerClusterTimeKey.initialize());
54 ATH_CHECK(m_extrapXKey.initialize());
55 ATH_CHECK(m_extrapYKey.initialize());
56 ATH_CHECK(m_numHGTDHitsKey.initialize());
57
58 ATH_CHECK(m_actsTrackLinkKey.initialize());
59
60 // Initialize HGTD-extension related keys
62
63 // Checks for logger
66
67 ATH_CHECK(m_monTool.retrieve(EnableTool{not m_monTool.empty()}));
68 ATH_CHECK(m_trackStatePrinter.retrieve(EnableTool{not m_trackStatePrinter.empty()}));
69 ATH_CHECK(m_pixelCalibTool.retrieve(EnableTool{not m_pixelCalibTool.empty()}));
70 ATH_CHECK(m_stripCalibTool.retrieve(EnableTool{not m_stripCalibTool.empty()}));
71 ATH_CHECK(m_hgtdCalibTool.retrieve(EnableTool{not m_hgtdCalibTool.empty()}));
72
73 //Initialize logger
74 m_logger = makeActsAthenaLogger(this, "Acts");
75
76 // Initialize surface accessor
78
79
80 auto magneticField = std::make_unique<ATLASMagneticFieldWrapper>();
81 std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry = m_trackingGeometryTool->trackingGeometry();
82
83 detail::Stepper stepper(std::move(magneticField));
84 detail::Navigator::Config cfg{trackingGeometry};
85 cfg.resolvePassive = true;
86 cfg.resolveMaterial = true;
87 cfg.resolveSensitive = true;
88 detail::Navigator navigator(cfg, logger().cloneWithSuffix("Navigator"));
89 detail::Propagator propagator(std::move(stepper), std::move(navigator), logger().cloneWithSuffix("Prop"));
90
91 // Using the CKF propagator as extrapolator
92 detail::Extrapolator extrapolator = propagator;
93
94 // Configure track selector for extension
95 Acts::TrackSelector::EtaBinnedConfig trackSelectorCfg(std::vector<double>({0, 4})); //abs value here
96 trackSelectorCfg.cutSets[0].ptMin = 500;
97 trackSelectorCfg.cutSets[0].ptMax = 1000000;
98 trackSelectorCfg.cutSets[0].minMeasurements = 1;
99 trackSelectorCfg.cutSets[0].maxHoles = 4;
100 trackSelectorCfg.cutSets[0].maxOutliers = 4;
101 trackSelectorCfg.cutSets[0].maxHolesAndOutliers = 4;
102 trackSelectorCfg.cutSets[0].maxSharedHits = 4;
103 trackSelectorCfg.cutSets[0].maxChi2 = 10000000.0;
104
105 ATH_MSG_DEBUG(trackSelectorCfg);
106
107 detail::CKF_config ckfConfig{
108 std::move(extrapolator),
109 detail::CKF{std::move(propagator), logger().cloneWithSuffix("CKF")},
110 {},
111 Acts::TrackSelector{trackSelectorCfg}};
112
113 m_trackFinder = std::make_unique<detail::CKF_config>(std::move(ckfConfig));
114
115 return StatusCode::SUCCESS;
116}
117
118// ---------------- Execute ----------------
119
120StatusCode HGTDTrackExtensionAlg::execute(const EventContext& ctx) const
121{
122 ATH_MSG_DEBUG("Executing " << name() << "...");
123
124 auto timer = Monitored::Timer<std::chrono::milliseconds>("TIME_execute");
125 auto mon_nTracks = Monitored::Scalar<int>("nTracks");
126 auto mon = Monitored::Group(m_monTool, timer, mon_nTracks);
127
128 // ================================================== //
129 // ========= RETRIEVE TRACK PARTICLES =============== //
130 // ================================================== //
131
132 const xAOD::TrackParticleContainer* trackParticles{nullptr};
133 ATH_CHECK(SG::get(trackParticles, m_trackParticleContainerName, ctx));
134
135 ATH_MSG_DEBUG("Size of trackParticles collection " << trackParticles->size());
136
137 // Create WriteDecorHandles for all decorations
145
147 ATH_CHECK( actsTrackLink.isValid() );
148 // ================================================== //
149 // ============ RETRIEVE MEASUREMENTS =============== //
150 // ================================================== //
151
152 ATH_MSG_DEBUG("Reading input collection with key " << m_uncalibratedMeasurementContainerKey_HGTD.key());
153
154 const xAOD::UncalibratedMeasurementContainer* uncalibratedMeasurementContainer{nullptr};
155 ATH_CHECK(SG::get(uncalibratedMeasurementContainer ,m_uncalibratedMeasurementContainerKey_HGTD, ctx));
156 ATH_MSG_DEBUG("Retrieved " << uncalibratedMeasurementContainer->size()
157 << " input elements from key " << m_uncalibratedMeasurementContainerKey_HGTD.key());
158
159 // Modern approach uses surface accessor instead of detector element map
160
161 ATH_MSG_DEBUG("Investigating HGTD geometry structure");
162
163 Acts::GeometryContext geoContext = m_trackingGeometryTool->getGeometryContext(ctx).context();
164 Acts::MagneticFieldContext mfContext = m_extrapolationTool->getMagneticFieldContext(ctx);
165
166
167 // Get a list of all identifiers associated with HGTD clusters
168 if (msgLvl(MSG::DEBUG)) { // DEBUG
169 const xAOD::HGTDClusterContainer* hgtdInitHandle{nullptr};
170 ATH_CHECK(SG::get(hgtdInitHandle, m_HGTDClusterContainerName, ctx));
171
172 std::unordered_set<uint32_t> hgtdVolumes {};
173 std::unordered_map<uint32_t, std::unordered_set<uint32_t>> hgtdLayers {}; // volume -> layers
174
175 for (const xAOD::HGTDCluster* cluster : *hgtdInitHandle) {
176 const Acts::Surface* surface = m_surfAcc.get(cluster);
177
178 if (surface) {
179 Acts::GeometryIdentifier geoID = surface->geometryId();
180 uint32_t vol = geoID.volume();
181 uint32_t layer = geoID.layer();
182
183 hgtdVolumes.insert(vol);
184 hgtdLayers[vol].insert(layer);
185
186 }
187 else {
188 ATH_MSG_ERROR("Failed to retrieve surface for cluster " << cluster->index());
189 return StatusCode::FAILURE;
190 }
191 }
192
193 ATH_MSG_DEBUG("Found " << hgtdVolumes.size() << " different HGTD volumes");
194 for (std::uint32_t vol : hgtdVolumes) {
195 ATH_MSG_DEBUG("HGTD Volume " << vol << " has " << hgtdLayers[vol].size() << " layers:");
196 for (std::uint32_t lyr : hgtdLayers[vol]) {
197 ATH_MSG_DEBUG(" - Layer " << lyr);
198 }
199 }
200 } // DEBUG
201
202 detail::TrackFindingMeasurements measurements(1ul); // only one measurement collection: HGTD clusters
203 ATH_CHECK( collectMeasurements(ctx, measurements) );
204
205 using DefaultTrackStateCreator = Acts::TrackStateCreator<ActsTrk::detail::UncalibSourceLinkAccessor::Iterator,detail::RecoTrackContainer>;
206
208 DefaultTrackStateCreator::SourceLinkAccessor slAccessorDelegate;
209 slAccessorDelegate.connect<&ActsTrk::detail::UncalibSourceLinkAccessor::range>(&slAccessor);
210
211 // acts_tracking_geometry is already declared in processTrackExtension function
212
213 ATH_MSG_DEBUG("Create " << uncalibratedMeasurementContainer->size()
214 << " source links from measurements in "
216
217 if (m_trackStatePrinter.isSet()) {
218 m_trackStatePrinter->printMeasurements(ctx,
219 {uncalibratedMeasurementContainer}, //wrap in a braced initializer list to make a vector
220 measurements.measurementOffsets());
221 }
222
223 Acts::PropagatorPlainOptions plainOptions(geoContext, mfContext);
224 plainOptions.direction = Acts::Direction::Forward();
225
226 HGTDTrackExtensionAlg::CKFOptions options(geoContext,
227 mfContext,
229 //slAccessorDelegate,
230 m_trackFinder->ckfExtensions,
231 plainOptions);
232
237
238 DefaultTrackStateCreator defaultTrackStateCreator{};
239 defaultTrackStateCreator.sourceLinkAccessor = slAccessorDelegate;
240 defaultTrackStateCreator.calibrator.template connect<&detail::OnTrackCalibrator<detail::RecoTrackStateContainer>::calibrate>(&calibrator);
241
242 options.extensions.createTrackStates.template connect<
243 &DefaultTrackStateCreator::createTrackStates>(&defaultTrackStateCreator);
244
245 // ================================================== //
246 // ============ LOOP OVER TRACKPARTICLES ============ //
247 // ================================================== //
248
249 // Loop over each track particle and decorate it with various information
250 for (const xAOD::TrackParticle* trackParticle : *trackParticles) {
251 // Default to empty track data
252 TrackExtensionData trackData;
253 // Check if the TrackParticle has a link to an ACTS track
254 ElementLink<ActsTrk::TrackContainer> link_to_track = actsTrackLink(*trackParticle);
255 if (!link_to_track.isValid()) {
256 ATH_MSG_ERROR("Invalid ACTS track link for TrackParticle " << trackParticle->index());
257 return StatusCode::FAILURE;
258 }
259
260 std::optional<ActsTrk::TrackContainer::ConstTrackProxy> optional_track = *link_to_track;
261 if (!optional_track.has_value()) {
262 ATH_MSG_ERROR("No valid ACTS track associated with TrackParticle " << trackParticle->index());
263 return StatusCode::FAILURE;
264 }
265
266 const ActsTrk::TrackContainer::ConstTrackProxy& track = optional_track.value();
267
268 Acts::VectorTrackContainer trackBackend;
269 Acts::VectorMultiTrajectory trackStateBackend;
270 detail::RecoTrackContainer tracksContainerTemp(trackBackend, trackStateBackend);
271
272 // Retrieve quantities for ACTS track
273 float trackEta = Acts::VectorHelpers::eta(track.momentum());
274
275 // Check eta coverage first
276 if (std::abs(trackEta) < m_minEtaAcceptance or
277 std::abs(trackEta) > m_maxEtaAcceptance) {
278 ATH_MSG_DEBUG("!!!!! ------ Track eta " << trackEta
279 << " outside eta range [" << m_minEtaAcceptance.value() << ", " << m_maxEtaAcceptance.value()
280 << "], skipping extension ------ !!!!!");
281
282 // set default values
283 layerHasExtensionHandle(*trackParticle) = trackData.hasClusterVec;
284 layerExtensionChi2Handle(*trackParticle) = trackData.chi2Vec;
285 layerClusterRawTimeHandle(*trackParticle) = trackData.rawTimeVec;
286 layerClusterTimeHandle(*trackParticle) = trackData.timeVec;
287 extrapXHandle(*trackParticle) = trackData.extrapX;
288 extrapYHandle(*trackParticle) = trackData.extrapY;
289 numHGTDHitsHandle(*trackParticle) = trackData.numHGTDHits;
290
291 continue;
292 }
293
294 float trackpT = track.transverseMomentum();
295 float trackPhi = track.phi();
296 float trackNmeasurements = track.nMeasurements();
297
298 ATH_MSG_DEBUG("TrackParticle " << trackParticle->index() << " has ACTS track with eta: " << trackEta << ", phi = " << trackPhi << " pT: " << trackpT << " and nMeasurements: " << trackNmeasurements);
299
300 // Parameters at last measurement state
301 const auto lastMeasurementState = Acts::findLastMeasurementState(track);
302 if (not lastMeasurementState.ok()) {
303 ATH_MSG_ERROR("Problem finding last measurement state for acts track");
304 return StatusCode::FAILURE;
305 }
306 const Acts::BoundTrackParameters lastMeasurementStateParameters = track.createParametersFromState(*lastMeasurementState);
307
308 // Parameters at reference state of track - not necessarily a measurement state!!!
309 const Acts::Surface& refSurface = track.referenceSurface();
310 const Acts::BoundTrackParameters parametersAtRefSurface(refSurface.getSharedPtr(),
311 track.parameters(),
312 track.covariance(),
313 track.particleHypothesis());
314
315 ATH_MSG_DEBUG("Initial track parameters for extension - lastMeasurementStateParameters:");
316 ATH_MSG_DEBUG(" - eta: " << -1 * log(tan(lastMeasurementStateParameters.theta() * 0.5)));
317 ATH_MSG_DEBUG(" - phi: " << lastMeasurementStateParameters.phi());
318 ATH_MSG_DEBUG(" - pT: " << std::abs(1./lastMeasurementStateParameters.qOverP() * std::sin(lastMeasurementStateParameters.theta())));
319 ATH_MSG_DEBUG(" - theta: " << lastMeasurementStateParameters.theta());
320 ATH_MSG_DEBUG(" - qOverP: " << lastMeasurementStateParameters.qOverP());
321 ATH_MSG_DEBUG(" - covariance exists: " << (lastMeasurementStateParameters.covariance().has_value() ? "yes" : "no"));
322
323 // Now use the *last measurement parameters* parameters for the CKF
324 auto result = m_trackFinder->ckf.findTracks(lastMeasurementStateParameters, options,tracksContainerTemp);
325
326 ATH_MSG_DEBUG("Built " << tracksContainerTemp.size() << " tracks from it");
327
328 for (const detail::RecoTrackContainer::TrackProxy trackProxy : tracksContainerTemp) {
329 trackData = processTrackExtension(ctx, trackParticle, trackProxy);
330 }
331
332 // Apply decorations from the track data
333 layerHasExtensionHandle(*trackParticle) = trackData.hasClusterVec;
334 layerExtensionChi2Handle(*trackParticle) = trackData.chi2Vec;
335 layerClusterRawTimeHandle(*trackParticle) = trackData.rawTimeVec;
336 layerClusterTimeHandle(*trackParticle) = trackData.timeVec;
337 extrapXHandle(*trackParticle) = trackData.extrapX;
338 extrapYHandle(*trackParticle) = trackData.extrapY;
339 numHGTDHitsHandle(*trackParticle) = trackData.numHGTDHits;
340 } // loop on tracks
341
342
343 return StatusCode::SUCCESS;
344}
345
346
347StatusCode
349 detail::TrackFindingMeasurements& measurements) const {
350
352 ATH_CHECK( HGTDClustersHandle.isValid() );
353 const xAOD::HGTDClusterContainer* HGTDClusters = HGTDClustersHandle.cptr();
354
355 ATH_MSG_DEBUG("Measurements (HGTD only) size: " << HGTDClustersHandle->size());
356
357 if (not m_monTool.empty()) {
358 {
359 auto mon_nclusters = Monitored::Scalar("n_hgtd_clusters", HGTDClusters->size());
360 auto mon = Monitored::Group(m_monTool, mon_nclusters);
361 }
362
363 // Add cluster position to monitoring tool
364 for (const xAOD::HGTDCluster* cluster : *HGTDClustersHandle) {
365 const Acts::Surface* surface = m_surfAcc.get(cluster);
366 if (not surface) continue;
367
368 Acts::Vector3 globalPos = surface->center(m_trackingGeometryTool->getGeometryContext(context).context());
369
370 // Get the time from local position (3rd coordinate)
371 auto localPosition = cluster->localPosition<3>(); // Get 3D local position
372 double clusterTime = localPosition[2]; // Time is in the third coordinate
373
374 ATH_MSG_DEBUG("HGTD Cluster: "<< Amg::toString(globalPos) );
375
376 auto mon_cluster_x = Monitored::Scalar("cluster_x", globalPos.x());
377 auto mon_cluster_y = Monitored::Scalar("cluster_y", globalPos.y());
378 auto mon_cluster_z = Monitored::Scalar("cluster_z", globalPos.z());
379 auto mon_cluster_t = Monitored::Scalar("cluster_t", clusterTime);
380 auto mon = Monitored::Group(m_monTool,
381 mon_cluster_x, mon_cluster_y, mon_cluster_z,
382 mon_cluster_t);
383 }
384 } // MONITORING
385
386 measurements.addMeasurements(0,
387 *HGTDClusters,
388 *m_trackingGeometryTool->surfaceIdMap());
389
390 return StatusCode::SUCCESS;
391}
392
394 const EventContext& ctx,
395 const xAOD::TrackParticle* trackParticle,
396 const detail::RecoTrackContainer::TrackProxy& trackProxy) const {
397
399
400 // Modern approach uses surface accessor instead of detector element map
401
402 // Apply track smoothing before trying to access chi2 values
403 Acts::GeometryContext geoContext = m_trackingGeometryTool->getGeometryContext(ctx).context();
404 const Acts::TrackingGeometry* acts_tracking_geometry = m_trackingGeometryTool->trackingGeometry().get();
405
406
407 // Count measurements, holes, and HGTD hits specifically
408 std::size_t nMeasurements = 0;
409 std::size_t nHoles = 0;
410 std::size_t nOutliers = 0;
411 std::size_t nHGTDHits = 0;
412
413 std::vector<bool> hasHitInLayer = {false, false, false, false};
414 std::vector<float> chi2PerLayer = {0.0, 0.0, 0.0, 0.0};
415 std::vector<float> timePerLayer = {0.0, 0.0, 0.0, 0.0};
416 std::vector<float> rawTimePerLayer = {0.0, 0.0, 0.0, 0.0};
417
418 std::vector<int> truthClassPerLayer = {-1, -1, -1, -1};
419 std::vector<bool> isShadowedPerLayer = {false, false, false, false};
420 std::vector<bool> isMergedPerLayer = {false, false, false, false};
421 std::vector<bool> primaryExpectedPerLayer = {false, false, false, false};
422
423 // Extrapolated position - get the position at the first HGTD surface encountered
424 float extrapX = 0.0;
425 float extrapY = 0.0;
426 float extrapZ = 0.0;
427 bool foundExtrapolation = false;
428
429 trackProxy.container().trackStateContainer().visitBackwards(
430 trackProxy.tipIndex(),
431 [&](const auto& state) {
432 auto flags = state.typeFlags();
433 if (flags.test(Acts::TrackStateFlag::HoleFlag)) {
434 nHoles++;
435 } else if (flags.test(Acts::TrackStateFlag::OutlierFlag)) {
436 nOutliers++;
437 } else if (flags.test(Acts::TrackStateFlag::MeasurementFlag)) {
438 nMeasurements++;
439
440 // Check if this is an HGTD hit
441 if (state.hasReferenceSurface() && flags.test(Acts::TrackStateFlag::MeasurementFlag)) {
442 const auto& surface = state.referenceSurface();
443 Acts::GeometryIdentifier geoID = surface.geometryId();
444
445 if (isHGTDSurface(geoID)) {
446 std::size_t layerIndex = getHGTDLayerIndex(geoID);
447
448 const auto& calibrated = state.template calibrated<3>(); //x,y,time
449 const auto& predicted = state.predicted(); //6D
450 const auto& calibCov = state.template calibratedCovariance<3>(); // Full 3D covariance
451
452
453
454 Eigen::Vector2d residual2d;
455 residual2d(0) = calibrated(0) - predicted(Acts::eBoundLoc0);
456 residual2d(1) = calibrated(1) - predicted(Acts::eBoundLoc1);
457
458 // Extract the top-left 2x2 from the 3x3 measurement covariance
459 AmgSymMatrix(2) cov_2d{calibCov.template block<2,2>(0,0)};
460
461 // Get the predicted covariance for residual calculation
462 const auto& predictedCov = state.predictedCovariance();
463 AmgSymMatrix(2) predicted_cov_2d{predictedCov.template block<2,2>(0,0)};
464
465 // Total residual covariance is measurement + predicted covariances
466 AmgSymMatrix(2) residual_cov = cov_2d + predicted_cov_2d;
467
468
469
470 double chi2=0.0;
471 double ndf = 2.0;
472 if (residual_cov.determinant() != 0)
473 {
474 chi2 = residual2d.transpose() * residual_cov.inverse() * residual2d;
475 }
476 else
477 {
478 chi2=-99.9;
479 }
480
481 if (layerIndex < 4) {
482 nHGTDHits++;
483 hasHitInLayer[layerIndex] = true;
484 chi2PerLayer[layerIndex] =chi2/ndf; //state.chi2();
485
486 // Get the measured time from the calibrated 3D measurement (local x, y, time)
487
488 float rawTime = 0.0f;
489 float calibratedTime = 0.0f;
490
491 if (state.hasCalibrated()) {
492 // Extract time from calibrated data
493 try {
494 const auto& calibrated = state.template calibrated<3>();
495 calibratedTime = static_cast<float>(calibrated(2));
496 ATH_MSG_DEBUG("Got time from calibrated<3>: " << calibratedTime);
497 } catch (const std::exception& e) {
498 ATH_MSG_WARNING("Failed to extract time from calibrated<3>: " << e.what());
499
500 }
501 }
502
503 // Extract raw time from HGTD clusters
504 const xAOD::HGTDCluster* cluster = getHGTDClusterFromState(state);
505
506 if (cluster) {
507 auto localPos = cluster->localPosition<3>();
508 rawTime = static_cast<float>(localPos[2]);
509 ATH_MSG_DEBUG("Got raw time from cluster local position: " << rawTime);
510 } else {
511 ATH_MSG_WARNING("Could not get cluster from state");
512 }
513
514 // Store the raw time
515 rawTimePerLayer[layerIndex] = rawTime;
516
517
518 if (cluster) {
519 auto [correctedTime, timeErr] = correctTOF(
520 trackParticle,
521 cluster,
522 calibratedTime,
523 0.0, // time error set to zero for now!
524 acts_tracking_geometry,
525 geoContext);
526 timePerLayer[layerIndex] = correctedTime;
527 ATH_MSG_DEBUG("Applied TOF correction: " << calibratedTime << " -> " << correctedTime);
528 } else {
529 // No cluster or time, use raw time
530 timePerLayer[layerIndex] = calibratedTime;
531 ATH_MSG_DEBUG("No cluster found for TOF correction, using calibrated time: " << calibratedTime);
532 }
533 }
534 else
535 {
536 ATH_MSG_DEBUG("State does not have calibrated data");
537 }
538
539 // For extrapolation: use the first HGTD hit's surface position.
540 if (!foundExtrapolation) {
541 foundExtrapolation = true;
542 if (state.hasPredicted()) {
543 // Get the local predicted position
544 const auto& predicted = state.predicted();
545 Acts::Vector2 localPos(predicted[Acts::eBoundLoc0], predicted[Acts::eBoundLoc1]);
546
547 // Transform to global coordinates
548 Acts::Vector3 globalPos = surface.localToGlobal(
549 geoContext,
550 localPos,
551 Acts::Vector3::Zero());
552
553 extrapX = globalPos.x();
554 extrapY = globalPos.y();
555 extrapZ = globalPos.z();
556
557 ATH_MSG_DEBUG("Extrapolated position (predicted) at HGTD: x=" << extrapX
558 << ", y=" << extrapY << ", z=" << extrapZ);
559 } else {
560 // Fallback to surface center
561 Acts::Vector3 globalPos = surface.center(geoContext);
562 extrapX = globalPos.x();
563 extrapY = globalPos.y();
564 extrapZ = globalPos.z();
565
566 ATH_MSG_DEBUG("Extrapolated position (surface center) at HGTD: x=" << extrapX
567 << ", y=" << extrapY << ", z=" << extrapZ);
568 }
569 }
570
571 ATH_MSG_DEBUG("Found HGTD hit on layer " << layerIndex
572 << ", chi2=" << chi2PerLayer[layerIndex]
573 << ", time=" << timePerLayer[layerIndex]);
574
575
576 }
577 }
578 }
579
580 });
581
582 // If we didn't find any HGTD hits but we still have a valid bestTrack,
583 // we should try to predict where the track would intersect HGTD
584 if (nHGTDHits == 0 && !foundExtrapolation) {
585
586 // Try to calculate the extrapolation position using your existing method
587 if (getExtrapolationPosition(ctx, trackProxy, extrapX, extrapY, extrapZ)) {
588 ATH_MSG_DEBUG("!!! Didn't find any HGTD hits but calculated extrapolation position: x=" << extrapX
589 << ", y=" << extrapY << ", z=" << extrapZ);
590 foundExtrapolation = true;
591 }
592 }
593
594 ATH_MSG_DEBUG("Track Statistics: "
595 << " nMeasurements=" << nMeasurements
596 << " nHGTDHits=" << nHGTDHits
597 << " nHoles=" << nHoles
598 << " nOutliers=" << nOutliers
599 << " extrapolation found: " << (foundExtrapolation ? "yes" : "no"));
600
601
602 // Fill the data structure with results
603 data.hasClusterVec = std::move(hasHitInLayer);
604 data.chi2Vec = std::move(chi2PerLayer);
605 data.timeVec = std::move(timePerLayer);
606 data.rawTimeVec = std::move(rawTimePerLayer);
607 data.extrapX = extrapX;
608 data.extrapY = extrapY;
609 data.extrapZ = extrapZ;
610 data.numHGTDHits = nHGTDHits;
611
612 return data;
613}
614
615std::size_t HGTDTrackExtensionAlg::getHGTDLayerIndex(const Acts::GeometryIdentifier& geoID) const {
616 // Get volume and layer ID
617 std::uint32_t volume = geoID.volume();
618 std::uint32_t layer = geoID.layer();
619
620 // Check if we're in the positive or negative endcap
621 bool isPositiveEndcap = (volume == 25);
622 bool isNegativeEndcap = (volume == 2);
623
624 // Different mapping for different sides to maintain consistent physical ordering
625 if (isPositiveEndcap) {
626 // Mapping for positive endcap
627 switch(layer) {
628 case 2: return 0; // First HGTD layer (closest to IP)
629 case 4: return 1; // Second HGTD layer
630 case 6: return 2; // Third HGTD layer
631 case 8: return 3; // Fourth HGTD layer (farthest from IP)
632 default: return 99; // Invalid layer
633 }
634 } else if (isNegativeEndcap) {
635 // Mapping for negative endcap - potentially different ordering
636 switch(layer) {
637 case 2: return 3;
638 case 4: return 2;
639 case 6: return 1;
640 case 8: return 0;
641 default: return 99; // Invalid layer
642 }
643 } else {
644 return 99; // Not an HGTD volume
645 }
646}
647
649 const EventContext& ctx,
650 const detail::RecoTrackContainer::TrackProxy& track,
651 float& x, float& y, float& z) const {
652
653 // Default values
654 x = 0.0;
655 y = 0.0;
656 z = 0.0;
657
658 // Get the position at the first HGTD layer
659 bool foundHGTDSurface = false;
660
661 // Explicitly capture ctx and geoContext in the lambda
662 Acts::GeometryContext geoContext = m_trackingGeometryTool->getGeometryContext(ctx).context();
663 track.container().trackStateContainer().visitBackwards(
664 track.tipIndex(),
665 [this, &foundHGTDSurface, &x, &y, &z, geoContext](const auto& state) {
666 // Skip if already found or no reference surface
667 if (foundHGTDSurface || !state.hasReferenceSurface()) {
668 return;
669 }
670
671 const auto& surface = state.referenceSurface();
672 Acts::GeometryIdentifier geoID = surface.geometryId();
673
674 // Check if this is an HGTD surface
675 if (isHGTDSurface(geoID)) {
676 // Instead of getting the surface center, use the predicted parameters
677 if (state.hasPredicted()) {
678 // Get the local predicted parameters
679 const auto& predicted = state.predicted();
680
681 // Get the local predicted position (first two components of the predicted vector)
682 // Local coordinates: [loc0, loc1, phi, theta, q/p, time]
683 Acts::Vector2 localPos(predicted[Acts::eBoundLoc0], predicted[Acts::eBoundLoc1]);
684
685 // Transform to global coordinates
686 Acts::Vector3 globalPos = surface.localToGlobal(
687 geoContext,
688 localPos,
689 Acts::Vector3::Zero()); // Direction doesn't matter for position
690
691 // Store coordinates
692 x = globalPos.x();
693 y = globalPos.y();
694 z = globalPos.z();
695
696 ATH_MSG_DEBUG("Predicted position on HGTD surface: (" << x << ", " << y << ", " << z << ")");
697 foundHGTDSurface = true;
698 } else {
699 // Fallback to surface center if predicted parameters not available
700 Acts::Vector3 globalPos = surface.center(geoContext);
701 x = globalPos.x();
702 y = globalPos.y();
703 z = globalPos.z();
704
705 ATH_MSG_DEBUG("Fallback to surface center for HGTD: (" << x << ", " << y << ", " << z << ")");
706 foundHGTDSurface = true;
707 }
708 }
709 });
710
711 return foundHGTDSurface;
712}
713
714// Helper function to check if a surface is in HGTD
715bool HGTDTrackExtensionAlg::isHGTDSurface(const Acts::GeometryIdentifier& geoID) const {
716 // Get volume and layer
717 std::uint32_t volume = geoID.volume();
718 std::uint32_t layer = geoID.layer();
719
720 // Check if it's one of the known HGTD volumes
721 if (volume == 2 || volume == 25) {
722 // Check if it's one of the HGTD layers
723 if (layer == 2 || layer == 4 || layer == 6 || layer == 8) {
724 return true;
725 }
726 }
727
728 return false;
729}
730
731
732
733std::pair<float, float> HGTDTrackExtensionAlg::correctTOF(
734 const xAOD::TrackParticle* trackParticle,
735 const xAOD::HGTDCluster* cluster,
736 float measuredTime,
737 float measuredTimeErr,
738 const Acts::TrackingGeometry* /*trackingGeometry*/,
739 const Acts::GeometryContext& geoContext) const {
740
741 ATH_MSG_DEBUG("Correcting input time: " << measuredTime);
742
743 if (!trackParticle || !cluster) {
744 ATH_MSG_WARNING("Null pointer provided to correctTOF");
745 return {measuredTime, measuredTimeErr}; // Return uncorrected values
746 }
747
748 // Get the surface for this HGTD cluster
749 const Acts::Surface* surface = nullptr;
750 try {
751 surface = m_surfAcc.get(cluster);
752 } catch (const std::exception& e) {
753 ATH_MSG_WARNING("Exception getting surface: " << e.what());
754 return {measuredTime, measuredTimeErr}; // Return uncorrected values
755 }
756
757 if (!surface) {
758 ATH_MSG_WARNING("Could not determine surface for HGTD cluster with id "
759 << cluster->identifier());
760 return {measuredTime, measuredTimeErr}; // Return uncorrected values
761 }
762
763 // Get the global position of the hit
764 Acts::Vector3 globalHitPos;
765 try {
766 // Try to get the cluster's local position
767 auto localPos = cluster->localPosition<3>();
768 // Transform to global coordinates
769 globalHitPos = surface->localToGlobal(
770 geoContext,
771 Acts::Vector2(localPos[0], localPos[1]),
772 Acts::Vector3::Zero());
773 } catch (const std::exception& e) {
774 ATH_MSG_WARNING("Failed to transform position: " << e.what());
775 // Fall back to surface center
776 globalHitPos = surface->center(geoContext);
777 }
778
779 // Get track origin (vertex position)
780 //option 1 - use beamspot
781 //Amg::Vector3D trackOrigin(trackParticle->vx(), trackParticle->vy(), trackParticle->vz());
782
783 //option 2 - use perigee - this is what is done in legacy code: https://gitlab.cern.ch/atlas/athena/-/blob/main/HighGranularityTimingDetector/HGTD_Reconstruction/HGTD_RecTools/src/StraightLineTOFcorrectionTool.cxx
784 // Get track origin from perigee parameters instead of vertex
785 // In ACTS, this is the d0 and z0 parameter with reference to the beamline
786
787 // Get the perigee position (the point of closest approach to the beamline)
788 double d0 = trackParticle->d0();
789 double z0 = trackParticle->z0();
790 double phi0 = trackParticle->phi0();
791
792 Amg::Vector3D trackOrigin(-d0 * std::sin(phi0), d0 * std::cos(phi0), z0);
793 ATH_MSG_DEBUG("Track perigee: d0=" << d0 << ", z0=" << z0 << ", phi0=" << phi0);
794 ATH_MSG_DEBUG("Track origin (perigee): (" << trackOrigin.x() << ", "
795 << trackOrigin.y() << ", " << trackOrigin.z() << ")");
796
797 // Calculate distance components
798 float dx = globalHitPos.x() - trackOrigin.x();
799 float dy = globalHitPos.y() - trackOrigin.y();
800 float dz = globalHitPos.z() - trackOrigin.z();
801
802 // Calculate distance and time of flight
803 float distance = std::sqrt(dx*dx + dy*dy + dz*dz);
804 float tof = distance / Gaudi::Units::c_light;
805
806 // Apply TOF correction
807 float correctedTime = measuredTime - tof;
808
809 ATH_MSG_DEBUG("Track origin: (" << trackOrigin.x() << ", "
810 << trackOrigin.y() << ", " << trackOrigin.z() << ")");
811 ATH_MSG_DEBUG("Hit position: (" << globalHitPos.x() << ", "
812 << globalHitPos.y() << ", " << globalHitPos.z() << ")");
813 ATH_MSG_DEBUG("Distance = " << distance << " mm, TOF = " << tof
814 << " ns, Corrected time = " << correctedTime);
815
816 return {correctedTime, measuredTimeErr};
817}
818
820 if (state.hasUncalibratedSourceLink()) {
821 auto sl = state.getUncalibratedSourceLink().template get<ATLASUncalibSourceLink>();
822 assert( sl != nullptr);
824 xAOD::UncalibMeasType clusterType = uncalib_cluster.type();
825
826 if (clusterType == xAOD::UncalibMeasType::HGTDClusterType) {
827 ATH_MSG_DEBUG("Found HGTD cluster in source link");
828 auto hgtdCluster = static_cast<const xAOD::HGTDCluster *>(&uncalib_cluster);
829 return hgtdCluster;
830 } else {
831 ATH_MSG_DEBUG("Source link contains non-HGTD measurement type: " << static_cast<int>(clusterType));
832 }
833
834 // If we have a reference surface, try to match by position
835 if (state.hasReferenceSurface()) {
836 const auto& surface = state.referenceSurface();
837 Acts::GeometryIdentifier geoID = surface.geometryId();
838
839 // Check if this is an HGTD surface
840 if (isHGTDSurface(geoID)) {
841 ATH_MSG_DEBUG("This is an HGTD surface with ID: " << geoID.volume() << ":" << geoID.layer());
842
843 // Get the current context
844 EventContext ctx = Gaudi::Hive::currentContext();
845
846 // Modern approach uses surface accessor instead of detector element map
847
848 // Get the HGTD clusters
850 if (!hgtdClusters.isValid()) {
851 ATH_MSG_WARNING("Failed to retrieve HGTD clusters");
852 return nullptr;
853 }
854
855 // Get global position of the state surface
856 const Acts::GeometryContext& geoContext = m_trackingGeometryTool->getGeometryContext(ctx).context();
857 Acts::Vector3 statePos = surface.center(geoContext);
858
859 // Find the closest cluster to this state position
860 const xAOD::HGTDCluster* closestCluster = nullptr;
861 double minDistance = 100.0; // Use a reasonable threshold (in mm)
862
863 for (const xAOD::HGTDCluster* cluster : *hgtdClusters) {
864 // Get the cluster's surface
865 const Acts::Surface* clusterSurface = m_surfAcc.get(cluster);
866
867 if (!clusterSurface) continue;
868
869 // Check if it's on the same surface by comparing geometry IDs
870 Acts::GeometryIdentifier clusterGeoID = clusterSurface->geometryId();
871 if (clusterGeoID.volume() == geoID.volume() && clusterGeoID.layer() == geoID.layer()) {
872 // Get cluster position
873 Acts::Vector3 clusterPos = clusterSurface->center(geoContext);
874
875 // Calculate 2D distance (x,y only, since z is fixed for a layer)
876 double dx = clusterPos.x() - statePos.x();
877 double dy = clusterPos.y() - statePos.y();
878 double distance = std::sqrt(dx*dx + dy*dy);
879
880 // Update closest if this is better
881 if (distance < minDistance) {
882 minDistance = distance;
883 closestCluster = cluster;
884 ATH_MSG_DEBUG("Found possible cluster match at distance " << distance << " mm");
885 }
886 }
887 }
888
889 if (closestCluster) {
890 ATH_MSG_DEBUG("Found closest cluster at distance " << minDistance << " mm");
891 return closestCluster;
892 } else {
893 ATH_MSG_DEBUG("No matching cluster found on this surface");
894 }
895 }
896 }
897 } else {
898 ATH_MSG_DEBUG("State doesn't have uncalibrated source link");
899 }
900
901 return nullptr;
902}
903
904} // End namespace ActsTrk
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration.
char data[hepevt_bytes_allocation_ATLAS]
Definition HepEvt.cxx:11
if(febId1==febId2)
Header file to be included by clients of the Monitored infrastructure.
Handle class for reading a decoration on an object.
Handle class for adding a decoration to an object.
std::unique_ptr< const Acts::Logger > makeActsAthenaLogger(IMessageSvc *svc, const std::string &name, int level, std::optional< std::string > parent_name)
#define y
#define x
#define z
bool isHGTDSurface(const Acts::GeometryIdentifier &geoID) const
SG::ReadHandleKey< xAOD::TrackParticleContainer > m_trackParticleContainerName
StatusCode collectMeasurements(const EventContext &context, detail::TrackFindingMeasurements &measurements) const
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_layerClusterRawTimeKey
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_numHGTDHitsKey
PublicToolHandle< ActsTrk::ITrackingGeometryTool > m_trackingGeometryTool
SG::ReadHandleKey< xAOD::UncalibratedMeasurementContainer > m_uncalibratedMeasurementContainerKey_HGTD
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_layerExtensionChi2Key
bool getExtrapolationPosition(const EventContext &ctx, const detail::RecoTrackContainer::TrackProxy &track, float &x, float &y, float &z) const
Gaudi::Property< float > m_minEtaAcceptance
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_layerHasExtensionKey
Acts::CombinatorialKalmanFilterOptions< detail::RecoTrackContainer > CKFOptions
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_extrapYKey
ToolHandle< ActsTrk::IExtrapolationTool > m_extrapolationTool
Acts::CalibrationContext m_calibrationContext
const Acts::Logger & logger() const
Private access to the logger.
std::pair< float, float > correctTOF(const xAOD::TrackParticle *trackParticle, const xAOD::HGTDCluster *cluster, float measuredTime, float measuredTimeErr, const Acts::TrackingGeometry *trackingGeometry, const Acts::GeometryContext &geoContext) const
std::size_t getHGTDLayerIndex(const Acts::GeometryIdentifier &geoID) const
SG::ReadDecorHandleKey< xAOD::TrackParticleContainer > m_actsTrackLinkKey
virtual StatusCode initialize() override
ToolHandle< ActsTrk::IOnTrackCalibratorTool< detail::RecoTrackStateContainer > > m_stripCalibTool
ToolHandle< ActsTrk::IOnTrackCalibratorTool< detail::RecoTrackStateContainer > > m_hgtdCalibTool
Gaudi::Property< float > m_maxEtaAcceptance
std::unique_ptr< detail::CKF_config > m_trackFinder
const xAOD::HGTDCluster * getHGTDClusterFromState(const ActsTrk::detail::RecoConstTrackStateContainerProxy &state) const
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_layerClusterTimeKey
std::unique_ptr< const Acts::Logger > m_logger
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_extrapXKey
ActsTrk::detail::xAODUncalibMeasSurfAcc m_surfAcc
ToolHandle< GenericMonitoringTool > m_monTool
SG::ReadHandleKey< xAOD::HGTDClusterContainer > m_HGTDClusterContainerName
ToolHandle< ActsTrk::IOnTrackCalibratorTool< detail::RecoTrackStateContainer > > m_pixelCalibTool
virtual StatusCode execute(const EventContext &) const override
ToolHandle< ActsTrk::TrackStatePrinterTool > m_trackStatePrinter
TrackExtensionData processTrackExtension(const EventContext &ctx, const xAOD::TrackParticle *trackParticle, const detail::RecoTrackContainer::TrackProxy &trackProxy) const
std::pair< Iterator, Iterator > range(const Acts::Surface &surface) const
Inner detector / ITk calibrator implementation used in the KalmanFilterTool.
const std::vector< std::size_t > & measurementOffsets() const
void addMeasurements(std::size_t typeIndex, const xAOD::UncalibratedMeasurementContainer &clusterContainer, const DetectorElementToActsGeometryIdMap &detectorElementToGeoid, const MeasurementIndex *measurementIndex=nullptr)
const MeasurementRangeList & measurementRanges() const
Helper class to access the Acts::surface associated with an Uncalibrated xAOD measurement.
bool msgLvl(const MSG::Level lvl) const
size_type size() const noexcept
Returns the number of elements in the collection.
Group of local monitoring quantities and retain correlation when filling histograms
Declare a monitored scalar variable.
A monitored timer.
Handle class for reading a decoration on an object.
virtual bool isValid() override final
Can the handle be successfully dereferenced?
const_pointer_type cptr()
Dereference the pointer.
Handle class for adding a decoration to an object.
float z0() const
Returns the parameter.
float d0() const
Returns the parameter.
float phi0() const
Returns the parameter, which has range to .
DetectorIdentType identifier() const
Returns the full Identifier of the measurement.
ConstVectorMap< N > localPosition() const
Returns the local position of the measurement.
virtual xAOD::UncalibMeasType type() const =0
Returns the type of the measurement type as a simple enumeration.
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:130
Acts::SympyStepper Stepper
Adapted from Acts Examples/Algorithms/TrackFinding/src/TrackFindingAlgorithmFunction....
GenUncalibSourceLinkAccessor< MeasurementRangeList > UncalibSourceLinkAccessor
Acts::TrackContainer< Acts::VectorTrackContainer, Acts::VectorMultiTrajectory > RecoTrackContainer
RecoTrackStateContainer::ConstTrackStateProxy RecoConstTrackStateContainerProxy
Acts::CombinatorialKalmanFilter< Propagator, RecoTrackContainer > CKF
The AlignStoreProviderAlg loads the rigid alignment corrections and pipes them through the readout ge...
const xAOD::UncalibratedMeasurement & getUncalibratedMeasurement(const ATLASUncalibSourceLink &source_link)
Acts::TrackStateCreator< ActsTrk::detail::UncalibSourceLinkAccessor::Iterator, detail::RecoTrackContainer > DefaultTrackStateCreator
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Matrix< double, 3, 1 > Vector3D
const T * get(const ReadCondHandleKey< T > &key, const EventContext &ctx)
Convenience function to retrieve an object given a ReadCondHandleKey.
SG::ReadCondHandle< T > makeHandle(const SG::ReadCondHandleKey< T > &key, const EventContext &ctx=Gaudi::Hive::currentContext())
HGTDClusterContainer_v1 HGTDClusterContainer
Define the version of the HGTD cluster container.
TrackParticle_v1 TrackParticle
Reference the current persistent version:
UncalibMeasType
Define the type of the uncalibrated measurement.
TrackParticleContainer_v1 TrackParticleContainer
Definition of the current "TrackParticle container version".
UncalibratedMeasurementContainer_v1 UncalibratedMeasurementContainer
Define the version of the uncalibrated measurement container.
HGTDCluster_v1 HGTDCluster
Define the version of the pixel cluster class.
Definition HGTDCluster.h:13
UncalibratedMeasurement_v1 UncalibratedMeasurement
Define the version of the uncalibrated measurement class.
Data structure to hold HGTD track extension results Contains information about hits,...
std::vector< float > chi2Vec
Chi2 contribution per HGTD layer.
std::vector< bool > hasClusterVec
Whether track has cluster in each HGTD layer.
std::vector< float > timeVec
TOF-corrected time per HGTD layer.
std::vector< float > rawTimeVec
Raw measured time per HGTD layer.
int numHGTDHits
Total number of HGTD hits on extended track.