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