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