ATLAS Offline Software
Loading...
Searching...
No Matches
TracccTrackConverterAlg.cxx
Go to the documentation of this file.
1/*
2Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
5
8
12#include <detray/geometry/tracking_surface.hpp>
13#include "Acts/EventData/TrackStateType.hpp"
14
15#include <stdexcept>
16
17namespace ActsTrk {
18
20{
21 ATH_MSG_DEBUG("Initializing.");
22
23 ATH_CHECK(m_hostMR.retrieve());
24 ATH_CHECK(m_copy.retrieve());
25
30
31 ATH_CHECK(m_inputTracksKey.initialize());
32 ATH_CHECK(m_outputTracksKey.initialize());
33
36
37 // Build the ACTS-surface <-> ACTS-id lookup map once,
38 // up front, rather than re-deriving them per event.
39 // Possibly this will be built during ACTS->Detray conversion
40 // Possibly this is an issue when the tracking geometry changes between events
41 if (!m_trackingGeometrySvc.empty()) {
43 m_trackingGeometry = m_trackingGeometrySvc->trackingGeometry();
44 m_trackingGeometry->visitSurfaces([&](const Acts::Surface* surface) {
45 if (!surface) return;
46 // m_allsurfaces.push_back(surface->getSharedPtr());
47 const auto *actsElement = getActsDetectorElement(surface);
48 if (!actsElement){
49 ATH_MSG_DEBUG("Could not find matching Acts detector element for surface with geometryId " << surface->geometryId());
50 return;
51 }
52 if(dynamic_cast<const InDetDD::HGTD_DetectorElement*>(actsElement->upstreamDetectorElement()) != nullptr) {
53 // skip HGTD surfaces for now
54 // currently no time info is being used in traccc
55 ATH_MSG_VERBOSE("Found HGTD surface with geometryId " << surface->geometryId());
56 return;
57 }
58 const auto *geoElement = actsElement->upstreamDetectorElement();
59 const auto *detElem = dynamic_cast<const InDetDD::SiDetectorElement*>(geoElement);
60 if (!geoElement || !detElem) {
61 ATH_MSG_DEBUG("Could not find matching Athena silicon detector element for surface with geometryId " << surface->geometryId());
62 return;
63 }
64
65 const Acts::GeometryIdentifier actsID = surface->geometryId();
66 const auto [it, inserted] = m_actsSurfaceMap.insert({actsID, surface});
67 if (!inserted) {
68 ATH_MSG_WARNING("ACTS id " << actsID
69 << " maps to two Acts surfaces: "
70 << it->second->geometryId() << " and "
71 << surface->geometryId());
72 }
73
74 });
75
76 }else {
77 ATH_MSG_FATAL("TrackingGeometrySvc is not configured, cannot translate Traccc tracks to ACTS tracks");
78 return StatusCode::FAILURE;
79 }
80
83 m_outputTracksKey.key())));
84
85 ATH_MSG_DEBUG("Successfully initialized");
86 return StatusCode::SUCCESS;
87}
88
89
91 const unsigned int& meas_index,
92 std::span<const unsigned int> pixelMap,
93 std::span<const unsigned int> stripMap,
94 const xAOD::PixelClusterContainer& pixel_clusters,
95 const xAOD::StripClusterContainer& strip_clusters) const
96{
97 const unsigned int pixel_index = (pixelMap)[meas_index];
98 if(pixel_index != std::numeric_limits<unsigned int>::max()){
99
100 return *pixel_clusters.at(pixel_index);
101 }
102
103 const unsigned int strip_index = (stripMap)[meas_index];
104 if(strip_index != std::numeric_limits<unsigned int>::max()){
105 return *strip_clusters.at(strip_index);
106 }
107
108 ATH_MSG_FATAL("Measurement index could not be found!!!");
109 throw std::domain_error("No measurement index match found in xAOD map");
110
111}
112
114 const Acts::GeometryIdentifier& actsID) const
115{
116 const auto surfaceIt = m_actsSurfaceMap.find(actsID);
117 if (surfaceIt == m_actsSurfaceMap.end()) {
118 ATH_MSG_ERROR("No Acts surface known for Acts geometry id "
119 << actsID);
120 throw std::domain_error("No Acts surface for this geometry id");
121 }
122 return *surfaceIt->second;
123}
124
125std::optional<Acts::BoundTrackParameters>
127 traccc::bound_track_parameters<traccc::default_algebra> const& trkParams)
128 const
129{
130 if (trkParams.bound_local()[0] == 0 && trkParams.bound_local()[1] == 0 &&
131 trkParams.phi() == 0 && trkParams.theta() == 0 &&
132 trkParams.qop() == 0 && trkParams.time() == 0) {
133 // traccc reports degenerate states like this; treat as "no
134 // parameters" rather than as a real (but zeroed-out) track.
135 return std::nullopt;
136 }
137
138 const auto& detrayDetector = m_hostDetector->as<traccc::itk_detector>();
139 const detray::tracking_surface detray_surface{detrayDetector, trkParams.surface_link()};
140 const auto geo_id = detray_surface.source();
141 const Acts::GeometryIdentifier acts_geom_id{geo_id};
142
143 const Acts::Surface& surface = findActsSurface(acts_geom_id);
144
145 Acts::BoundVector params;
146 params << trkParams.bound_local()[0], trkParams.bound_local()[1],
147 trkParams.phi(), trkParams.theta(), trkParams.qop(), trkParams.time();
148
149 // ---- sanity-check units ----
150 const double qop = trkParams.qop();
151 const double p = (qop != 0) ? 1.0 / std::abs(qop) : 0.0;
152 const double pT = p * std::sin(trkParams.theta());
153 ATH_MSG_DEBUG("Global params: d0=" << trkParams.bound_local()[0]
154 << " z0=" << trkParams.bound_local()[1]
155 << " phi=" << trkParams.phi()
156 << " theta=" << trkParams.theta()
157 << " qop=" << qop
158 << " -> |p|=" << p << " pT=" << pT
159 << " charge=" << (qop > 0 ? +1 : -1)
160 << " time=" << trkParams.time());
161
162 // Traccc covariance has no time uncertainty; Acts::BoundMatrix does
163 // (it's eBoundSize x eBoundSize == 6x6). For now leave the time row/column at the
164 // Identity() values set above and only fill the spatial+qop values.
165 static constexpr unsigned int kAtlasCovSize = 5; // d0, z0, phi, theta, qop (no time)
166
167 Acts::BoundMatrix cov = Acts::BoundMatrix::Identity();
168 const auto& atlasCov = trkParams.covariance();
169 for (unsigned i = 0; i < kAtlasCovSize; ++i) {
170 for (unsigned j = 0; j < kAtlasCovSize; ++j) {
171 cov(i, j) = atlasCov[i][j];
172 }
173 }
174
175 return Acts::BoundTrackParameters(surface.getSharedPtr(), params, cov,
176 Acts::ParticleHypothesis::pion());
177}
178
179template <typename state_t>
180std::optional<Acts::BoundTrackParameters>
182 traccc::edm::track_state<state_t> const& state) const
183{
184 const auto& atlasParam = state.smoothed_params();
185
186 if (atlasParam.bound_local()[0] == 0 && atlasParam.bound_local()[1] == 0 &&
187 atlasParam.phi() == 0 && atlasParam.theta() == 0 &&
188 atlasParam.qop() == 0 && atlasParam.time() == 0) {
189 // traccc reports degenerate states like this; treat as "no
190 // parameters" rather than as a real (but zeroed-out) state.
191 return std::nullopt;
192 }
193
194 const auto& detrayDetector = m_hostDetector->as<traccc::itk_detector>();
195 const detray::tracking_surface detray_surface{detrayDetector, atlasParam.surface_link()};
196 const auto geo_id = detray_surface.source();
197 const Acts::GeometryIdentifier acts_geom_id{geo_id};
198
199 const Acts::Surface& surface = findActsSurface(acts_geom_id);
200
201 // d0/z0 come from the measurement's local position; the remaining bound
202 // parameters come from the smoothed track state.
203 Acts::BoundVector params;
204 params << atlasParam.bound_local()[0], atlasParam.bound_local()[1],
205 atlasParam.phi(), atlasParam.theta(), atlasParam.qop(),
206 atlasParam.time();
207
208 // ---- per-state momentum check for sanity ----
209 const double qop = atlasParam.qop();
210 const double p = (qop != 0) ? 1.0 / std::abs(qop) : 0.0;
211 const double pT = p * std::sin(atlasParam.theta());
212 ATH_MSG_DEBUG("Smoothed state: d0=" << atlasParam.bound_local()[0]
213 << " z0=" << atlasParam.bound_local()[1]
214 << " phi=" << atlasParam.phi()
215 << " theta=" << atlasParam.theta()
216 << " qop=" << qop
217 << " -> |p|=" << p << " pT=" << pT);
218
219 // Traccc covariance has no time uncertainty; Acts::BoundMatrix does
220 // (it's eBoundSize x eBoundSize == 6x6). For now leave the time row/column at the
221 // Identity() values set above and only fill the spatial+qop values.
222 static constexpr unsigned int kAtlasCovSize = 5; // d0, z0, phi, theta, qop (no time)
223
224 Acts::BoundMatrix cov = Acts::BoundMatrix::Identity();
225 const auto& atlasCov = atlasParam.covariance();
226 for (unsigned i = 0; i < kAtlasCovSize; ++i) {
227 for (unsigned j = 0; j < kAtlasCovSize; ++j) {
228 cov(i, j) = atlasCov[i][j];
229 }
230 }
231
232 return Acts::BoundTrackParameters(surface.getSharedPtr(), params, cov,
233 Acts::ParticleHypothesis::pion());
234}
235
236StatusCode TracccTrackConverterAlg::execute(const EventContext& ctx) const
237{
238 // ---- Retrieve HOST RESIDENT clusters (always needed) ----
239 // These are made during the TracccMeasurementConverterAlg
240 auto pixel_clusters = SG::makeHandle(m_inputPixelClustersKey, ctx);
241 ATH_CHECK(pixel_clusters.isValid());
242
243 auto strip_clusters = SG::makeHandle(m_inputStripClustersKey, ctx);
244 ATH_CHECK(strip_clusters.isValid());
245
246 // ---- Retrieve mapping from traccc measurement index to pixel cluster/spacepoint index (always needed) ----
247 // Because the SPs were created from xAOD pixel clusters on the host, the same traccc measurement index
248 // points to the xAOD pixel cluster and xAOD spacepoint in their respective containers
249 auto pixelMeasMap = SG::makeHandle(m_inputMeasToPixelSPKey, ctx);
250 ATH_CHECK(pixelMeasMap.isValid());
251
252 auto stripMeasMap = SG::makeHandle(m_inputMeasToStripClKey, ctx);
253 ATH_CHECK(stripMeasMap.isValid());
254
255 if(stripMeasMap->size() == 0 || pixelMeasMap->size() == 0){
256 ATH_MSG_FATAL("Maps relating traccc measurements to xAOD cluster containers are empty!!");
257 return StatusCode::FAILURE;
258 }
259
260 // ---- Retrieve DEVICE resident traccc tracks and track states ----
261 auto tracks = SG::makeHandle(m_inputTracksKey, ctx);
262 ATH_CHECK(tracks.isValid());
263
264 auto copy = m_copy->copy(ctx);
265
266 traccc_track_container::buffer traccc_tracks_buffer;
267 traccc_tracks_buffer.tracks =
268 copy->to(tracks->tracks, m_hostMR->mr(),
269 nullptr, vecmem::copy::type::device_to_host);
270 traccc_tracks_buffer.states =
271 copy->to(tracks->states, m_hostMR->mr(),
272 nullptr, vecmem::copy::type::device_to_host);
273
274 traccc_track_container::const_device traccc_tracks(
275 traccc_tracks_buffer);
276
277 ATH_MSG_DEBUG("Read " << traccc_tracks.tracks.size() << " tracks from device.");
278 m_nTracksIn += traccc_tracks.tracks.size();
279
280 // -- Write HOST resident ACTS track container ----
281 Acts::VectorTrackContainer trackBackend;
282 Acts::VectorMultiTrajectory trackStateBackend;
283 ActsTrk::MutableTrackContainer trackContainer(std::move(trackBackend),
284 std::move(trackStateBackend));
285
286 // Bookkeeping for debug summary
287 unsigned nExcludedFitOutcome = 0;
288 unsigned nExcludedNoState = 0;
289 unsigned nExcludedBadNdf = 0;
290 unsigned nExcludedWeirdState = 0;
291 unsigned nExcludedWeirdGlobal = 0;
292
293 for (std::size_t i = 0; i < traccc_tracks.tracks.size(); ++i) {
294
295 const traccc::edm::track track = traccc_tracks.tracks.at(i);
296
297 const auto fitOutcome = track.fit_outcome();
298 if (fitOutcome == traccc::track_fit_outcome::FAILURE_NON_POSITIVE_NDF ||
299 fitOutcome == traccc::track_fit_outcome::FAILURE_NOT_ALL_SMOOTHED ||
300 fitOutcome == traccc::track_fit_outcome::UNKNOWN) {
301 ATH_MSG_DEBUG("Skipping track " << i << ": fit outcome "
302 << static_cast<int>(fitOutcome));
303 ++nExcludedFitOutcome;
304 continue;
305 }
306
307 if (track.constituent_links().empty()) {
308 ++nExcludedNoState;
309 continue;
310 }
311
312 if (track.ndf() >
313 static_cast<float>(std::numeric_limits<unsigned int>::max()) ||
314 track.ndf() <
315 static_cast<float>(std::numeric_limits<unsigned int>::min())) {
316 ++nExcludedBadNdf;
317 continue;
318 }
319
320 TrackValidity track_validity = kValid;
321
322 auto actsTrack = trackContainer.makeTrack();
323 actsTrack.chi2() = track.chi2();
324 actsTrack.nDoF() = track.ndf();
325
326 Acts::TrackStatePropMask const state_mask =
327 Acts::TrackStatePropMask::Smoothed;
328
329 bool firstState = true;
330
331 for (const auto [linkType, stateIdx] : track.constituent_links()) {
332
333 assert(linkType == traccc::edm::track_constituent_link::track_state);
334
335 const auto& state = traccc_tracks.states.at(stateIdx);
336 const auto& meas_index = state.measurement_index();
337
338 auto trackState = actsTrack.appendTrackState(state_mask);
339 trackState.typeFlags().setIsMeasurement();
340
341 const std::optional<Acts::BoundTrackParameters> smoothed =
343
344 if (!smoothed) {
345 ATH_MSG_DEBUG("Track " << i << ": degenerate smoothed state, "
346 "dropping track");
347 track_validity = TrackValidity::kInvalidState;
348 break;
349 }
350
351 const xAOD::UncalibratedMeasurement* sourceLink =
352 &makeSourceLink(meas_index, *pixelMeasMap, *stripMeasMap, *pixel_clusters, *strip_clusters);
353
354 trackState.setUncalibratedSourceLink(ActsTrk::detail::xAODUncalibMeasCalibrator::pack(sourceLink));
355
356 // traccc does not yet do backpropagation, so there is no true
357 // reference surface (perigee) for the track. We use the surface
358 // of the first measurement instead and rely on back-propagation
359 // during the later Acts -> xAOD conversion step to find the real
360 // perigee.
361 if (firstState) {
362 const std::optional<Acts::BoundTrackParameters> global =
363 convertGlobalToActsParameters(track.params());
364 if (!global) {
365 ATH_MSG_DEBUG("Track "
366 << i
367 << ": degenerate global parameters, "
368 "dropping track");
370 break;
371 }
372 actsTrack.parameters() = global->parameters();
373 actsTrack.covariance() = *global->covariance();
374 actsTrack.setReferenceSurface(
375 global->referenceSurface().getSharedPtr());
376 firstState = false;
377 }
378
379 try {
380 trackState.setReferenceSurface(
381 smoothed->referenceSurface().getSharedPtr());
382 trackState.smoothed() = smoothed->parameters();
383 trackState.smoothedCovariance() = *smoothed->covariance();
384 } catch (const std::exception& e) {
385 ATH_MSG_ERROR("Track " << i << ": failed to set track state ("
386 << e.what() << ")");
387 }
388
389 }
390
391 if (track_validity == TrackValidity::kInvalidState) {
392 ++nExcludedWeirdState;
393 trackContainer.removeTrack(actsTrack.index());
394 } else if (track_validity == TrackValidity::kInvalidGlobalParams) {
395 ++nExcludedWeirdGlobal;
396 trackContainer.removeTrack(actsTrack.index());
397 } else {
398 // sanity checks
399 const auto& pars = actsTrack.parameters();
400 const double qop = pars[Acts::eBoundQOverP];
401 const double p = (qop != 0) ? 1.0 / std::abs(qop) : 0.0;
402 const double pT = p * std::sin(pars[Acts::eBoundTheta]);
403 ATH_MSG_DEBUG("Track " << i << " ACCEPTED: nHits="
404 << track.constituent_links().size()
405 << " chi2=" << track.chi2()
406 << " ndf=" << track.ndf()
407 << " |p|=" << p << " GeV pT=" << pT << " GeV");
408 }
409
410 }
411
412 ATH_MSG_DEBUG("Converted " << trackContainer.size() << " tracks to " << m_outputTracksKey.key() );
413 ATH_MSG_DEBUG("excluded: "
414 << nExcludedFitOutcome << " (fit outcome), "
415 << nExcludedNoState << " (no state), "
416 << nExcludedBadNdf << " (bad ndf), "
417 << nExcludedWeirdState << " (weird state), "
418 << nExcludedWeirdGlobal << " (weird global params)");
419
420 m_nExcludedFitOutcome += nExcludedFitOutcome;
421 m_nExcludedNoState += nExcludedNoState;
422 m_nExcludedBadNdf += nExcludedBadNdf;
423 m_nExcludedWeirdState += nExcludedWeirdState;
424 m_nExcludedWeirdGlobal += nExcludedWeirdGlobal;
425
426 m_nTracksOut += trackContainer.size();
427
428 Acts::ConstVectorTrackContainer constTrackBackend(
429 std::move(trackContainer.container()));
430 Acts::ConstVectorMultiTrajectory constTrackStateBackend(
431 std::move(trackContainer.trackStateContainer()));
432 auto constTrackContainer = std::make_unique<ActsTrk::TrackContainer>(
433 std::move(constTrackBackend), std::move(constTrackStateBackend));
434
436 m_outputTracksKey, ctx);
437 ATH_CHECK(outputHandle.record(std::move(constTrackContainer)));
438
439 return StatusCode::SUCCESS;
440}
441
443{
444 ATH_MSG_DEBUG("Finalizing.");
445
446 ATH_MSG_DEBUG("Received " << m_nTracksIn << " tracks, wrote " << m_nTracksOut);
447 ATH_MSG_DEBUG("Excluded: "
448 << m_nExcludedFitOutcome << " (fit outcome), "
449 << m_nExcludedNoState << " (no state), "
450 << m_nExcludedBadNdf << " (bad ndf), "
451 << m_nExcludedWeirdState << " (weird state), "
452 << m_nExcludedWeirdGlobal << " (weird global params)");
453
454 ATH_MSG_DEBUG("Successfully finalized");
455 return StatusCode::SUCCESS;
456}
457
458} // namespace ActsTrk
const ActsDetectorElement * getActsDetectorElement(const Acts::Surface &surf)
Attempts to retrieve the ActsDetectorElement associated to the passed ActsSurface.
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_ERROR(x,...)
#define ATH_MSG_WARNING(x,...)
#define ATH_MSG_VERBOSE(x,...)
#define ATH_MSG_FATAL(x,...)
Handle class for reading from StoreGate.
Handle class for recording to StoreGate.
@ kInvalidGlobalParams
Gaudi::Property< std::string > m_hostDetectorObjectName
virtual StatusCode initialize() override
Function initializing the algorithm.
std::optional< Acts::BoundTrackParameters > convertGlobalToActsParameters(traccc::bound_track_parameters< traccc::default_algebra > const &trkParams) const
std::shared_ptr< const Acts::TrackingGeometry > m_trackingGeometry
std::map< Acts::GeometryIdentifier, const Acts::Surface * > m_actsSurfaceMap
virtual StatusCode execute(const EventContext &ctx) const override
Function executing the algorithm.
Gaudi::Property< std::string > m_geoIdMappingObjectName
virtual StatusCode finalize() override
Function finalizing the algorthm.
SG::ReadHandleKey< xAOD::StripClusterContainer > m_inputStripClustersKey
const xAOD::UncalibratedMeasurement & makeSourceLink(const unsigned int &meas_index, std::span< const unsigned int > pixelMap, std::span< const unsigned int > stripMap, const xAOD::PixelClusterContainer &pixel_clusters, const xAOD::StripClusterContainer &strip_clusters) const
ServiceHandle< ActsTrk::ITrackingGeometrySvc > m_trackingGeometrySvc
const Acts::Surface & findActsSurface(const Acts::GeometryIdentifier &actsID) const
ToolHandle< AthDevice::ICopyTool > m_copy
SG::ReadHandleKey< xAOD::PixelClusterContainer > m_inputPixelClustersKey
SG::ReadHandleKey< std::vector< unsigned int > > m_inputMeasToStripClKey
std::atomic< int > m_nTracksIn
The object counters for debug prints in finalize method {.
ActsTrk::MutableTrackContainerHandlesHelper m_tracksBackendHandlesHelper
SG::ReadHandleKey< traccc_track_container::buffer > m_inputTracksKey
SG::WriteHandleKey< ActsTrk::TrackContainer > m_outputTracksKey
const traccc::host_detector * m_hostDetector
const ActsTrk::GeometryIdMapping * m_idMapping
ToolHandle< AthDevice::IMemoryResourceTool > m_hostMR
SG::ReadHandleKey< std::vector< unsigned int > > m_inputMeasToPixelSPKey
std::optional< Acts::BoundTrackParameters > convertSmoothedToActsParameters(traccc::edm::track_state< state_t > const &state) const
static Acts::SourceLink pack(const Ptr_t &measurement)
Pack the measurement type pointer to an Acts::SourceLink including the intermediate conversion into a...
const ServiceHandle< StoreGateSvc > & detStore() const
const T * at(size_type n) const
Access an element, as an rvalue.
Class to hold geometrical description of an HGTD detector element.
Class to hold geometrical description of a silicon detector element.
StatusCode record(std::unique_ptr< T > data)
Record a const object to the store.
The AlignStoreProviderAlg loads the rigid alignment corrections and pipes them through the readout ge...
std::string prefixFromTrackContainerName(const std::string &tracks)
Parse TrackContainer name to get the prefix for backends The name has to contain XYZTracks,...
Acts::TrackContainer< MutableTrackBackend, MutableTrackStateBackend, Acts::detail::ValueHolder > MutableTrackContainer
SG::ReadCondHandle< T > makeHandle(const SG::ReadCondHandleKey< T > &key, const EventContext &ctx=Gaudi::Hive::currentContext())
PixelClusterContainer_v1 PixelClusterContainer
Define the version of the pixel cluster container.
UncalibratedMeasurement_v1 UncalibratedMeasurement
Define the version of the uncalibrated measurement class.
StripClusterContainer_v1 StripClusterContainer
Define the version of the strip cluster container.