86 ACTS_DEBUG(
"TrackFindingGNNAlg::initialize() - begin");
105 ActsPlugins::ModuleMapCuda::Config gcCfg;
106 gcCfg.rScale = 1000.f;
107 gcCfg.zScale = 1000.f;
108 gcCfg.phiScale = std::numbers::pi_v<float>;
110 gcCfg.gpuBlocks = 512;
111 std::shared_ptr<ActsPlugins::GraphConstructionBase> gc =
112 std::make_shared<ActsPlugins::ModuleMapCuda>(
113 gcCfg,
m_logger->cloneWithSuffix(
"ModuleMap"));
115 std::shared_ptr<ActsPlugins::EdgeClassificationBase> gnn;
116 if (
m_gnnPath.value().find(
".onnx") != std::string::npos) {
117#ifdef ACTS_GNN_ONNX_BACKEND
118 ActsPlugins::OnnxEdgeClassifier::Config gnnCfg;
121 gnn = std::make_shared<ActsPlugins::OnnxEdgeClassifier>(
122 gnnCfg,
m_logger->cloneWithSuffix(
"GNN"));
124 ATH_MSG_ERROR(
"GNN .onnx selected but build lacks ONNX backend");
125 return StatusCode::FAILURE;
127 }
else if (
m_gnnPath.value().find(
".pt") != std::string::npos) {
128#ifdef ACTS_GNN_TORCH_BACKEND
129 ActsPlugins::TorchEdgeClassifier::Config gnnCfg;
132 gnnCfg.useEdgeFeatures =
true;
133 gnn = std::make_shared<ActsPlugins::TorchEdgeClassifier>(
134 gnnCfg,
m_logger->cloneWithSuffix(
"GNN"));
136 ATH_MSG_ERROR(
"GNN .pt selected but build lacks libtorch backend");
137 return StatusCode::FAILURE;
139 }
else if (
m_gnnPath.value().find(
".engine") != std::string::npos) {
140#ifdef ACTS_GNN_WITH_TENSORRT
141 ActsPlugins::TensorRTEdgeClassifier::Config gnnCfg;
145 gnn = std::make_shared<ActsPlugins::TensorRTEdgeClassifier>(
146 gnnCfg,
m_logger->cloneWithSuffix(
"GNN"));
148 ATH_MSG_ERROR(
"GNN .engine selected but build lacks TensorRT backend");
149 return StatusCode::FAILURE;
153 return StatusCode::FAILURE;
156 ActsPlugins::CudaTrackBuilding::Config tbCfg;
157 tbCfg.doJunctionRemoval =
true;
158 std::shared_ptr<ActsPlugins::TrackBuildingBase> tb =
159 std::make_shared<ActsPlugins::CudaTrackBuilding>(
160 tbCfg,
m_logger->cloneWithSuffix(
"GraphSeg"));
163 gc, std::vector{gnn}, tb,
m_logger->cloneWithSuffix(
"Pipeline"));
171 ACTS_INFO(
"Use phi overlap spacepoints: " << std::boolalpha
174 using TSC = Acts::TrackSelector::Config;
177 auto commonConfig = [&](TSC &config) {
178 config.requireReferenceSurface =
true;
186 commonConfig(config);
189 config.ptMin = 900_MeV;
190 config.loc0Max = 2_mm;
191 config.loc0Min = -2_mm;
195 commonConfig(config);
198 config.ptMin = 400_MeV;
199 config.loc0Max = 2_mm;
200 config.loc0Min = -2_mm;
202 .addCuts([&](TSC &config) {
203 commonConfig(config);
205 config.minMeasurements = 7;
206 config.ptMin = 400_MeV;
207 config.loc0Max = 10_mm;
208 config.loc0Min = -10_mm;
213 ACTS_DEBUG(
"TrackFindingGNNAlg::initialize() - end");
214 return StatusCode::SUCCESS;
220 ACTS_DEBUG(
"TrackFindingGNNAlg::execute() - begin");
222 std::optional<Athena::Chrono> timer;
223 timer.emplace(
"GNN get spacepoint handles",
m_chronoSvc.get());
225 const Acts::GeometryContext gctx =
m_ctxProvider.getGeometryContext(ctx);
226 const Acts::MagneticFieldContext mctx =
m_ctxProvider.getMagneticFieldContext(ctx);
227 const Acts::CalibrationContext cctx =
m_ctxProvider.getCalibrationContext(ctx);
234 const auto &pixelSPContainer = *pixelSPHandle.cptr();
238 const auto &stripSPContainer = *stripSPHandle.cptr();
240 auto stripSPOVHandle =
243 const auto &stripSPOVContainer = *stripSPOVHandle.cptr();
245 constexpr std::size_t nFeatures = 12;
246 std::size_t nSP = pixelSPContainer.size() + stripSPContainer.size() +
247 stripSPOVContainer.size();
249 ACTS_DEBUG(
"Number spacepoints: "
250 << nSP <<
" (" <<
"pixel: " << pixelSPContainer.size() <<
", "
251 <<
"strip: " << stripSPContainer.size() <<
", "
252 <<
"strip overlap: " << stripSPOVContainer.size() <<
")");
255 timer.emplace(
"GNN extract data",
m_chronoSvc.get());
257 std::vector<std::uint64_t> moduleIds;
258 moduleIds.reserve(nSP);
259 std::vector<const xAOD::SpacePoint *> allSPPtrs;
260 allSPPtrs.reserve(nSP);
261 std::vector<Acts::GeometryIdentifier> geoIds, sortedGeoIds(nSP);
264 std::size_t skipped = 0;
265 for (
const auto &spc :
266 {pixelSPContainer, stripSPContainer, stripSPOVContainer}) {
267 for (
auto sp : spc) {
268 auto cl1 =
sp->measurements().front();
271 Identifier atlasIdCl1(
static_cast<Identifier::value_type
>(cl1->identifier()));
273 if (
sp->measurements().size() == 2) {
274 auto cl2 =
sp->measurements().at(1);
275 Identifier atlasIdCl2(
static_cast<Identifier::value_type
>(cl2->identifier()));
283 if (overlapFlag == 2 || overlapFlag == 3) {
285 ACTS_VERBOSE(
"Skip phi overlap spacepoint (flag=" << overlapFlag
291 geoIds.push_back(geoIdCl1);
293 allSPPtrs.push_back(
sp);
297 ACTS_DEBUG(
"Skipped " << skipped <<
" SPs because of phi overlap");
298 nSP = allSPPtrs.size();
299 ACTS_DEBUG(
"Keep " << nSP <<
" SPs for feature creation");
301 timer.emplace(
"GNN build input tensor",
m_chronoSvc.get());
303 std::vector<std::size_t> idxs(nSP);
304 std::iota(idxs.begin(), idxs.end(), 0);
307 idxs, [&](
auto a,
auto b) {
return moduleIds.at(
a) < moduleIds.at(b); });
308 std::ranges::sort(moduleIds);
310 std::vector<float> features(nFeatures * nSP);
311 std::vector<boost::container::static_vector<Acts::SourceLink, 2>> sourceLinks(
313 std::vector<int> id(nSP);
315 for (
auto k = 0ul; k < nSP; k++) {
319 std::span<float> f(features.data() + k * nFeatures, nFeatures);
320 const auto &
sp = *allSPPtrs.at(i);
322 using namespace Acts::VectorHelpers;
323 using namespace Acts::AngleHelpers;
325 Acts::Vector3 spp{
sp.x(),
sp.y(),
sp.z()};
327 if (
sp.measurements().size() == 1) {
328 for (
auto j = 0ul; j < nFeatures; j += 4) {
329 f[j + 0] =
perp(spp) / 1000.f;
330 f[j + 1] =
phi(spp) / std::numbers::pi_v<float>;
331 f[j + 2] =
sp.z() / 1000.f;
336 f[j + 0] =
perp(spp) / 1000.f;
337 f[j + 1] =
phi(spp) / std::numbers::pi_v<float>;
338 f[j + 2] =
sp.z() / 1000.f;
341 for (
auto m :
sp.measurements()) {
343 auto gp = cl->globalPosition();
345 f[j + 0] =
perp(gp) / 1000.f;
346 f[j + 1] =
phi(gp) / std::numbers::pi_v<float>;
347 f[j + 2] = gp.z() / 1000.f;
356 sortedGeoIds.at(k) = geoIds.at(i);
362 m_gpuInstanceCount->acquire();
365 m_gpuInstanceCount->release();
367 ACTS_DEBUG(
"Have " << candidates.size() <<
" candidates after GNN");
370 auto candidateSelector = [&](
const std::vector<int> &c) {
371 bool tooFewMeasurements = std::accumulate(c.begin(), c.end(), 0ul, [&](
auto sum,
auto spi) {
372 return sum + allSPPtrs.at(spi)->measurements().size();
374 bool noPixelHits = !std::ranges::any_of(c, [&](
auto spi) {
return allSPPtrs.at(spi)->measurements().size() == 1; });
375 return tooFewMeasurements || noPixelHits;
378 candidates.erase(
std::remove_if(candidates.begin(), candidates.end(), candidateSelector),
381 <<
" measurements: " << candidates.size());
384 timer.emplace(
"GNN parameter estimation + fit",
m_chronoSvc.get());
386 Acts::VectorTrackContainer trackBackend;
387 Acts::VectorMultiTrajectory trackStateBackend;
388 constexpr std::size_t nTracksExpected = 3000;
389 trackBackend.reserve(nTracksExpected);
390 trackStateBackend.reserve(nTracksExpected * 30);
396 auto makeSeedFromCandidate = [&](
const std::vector<int> &cand) -> std::optional<boost::container::small_vector<const xAOD::SpacePoint*, 3>> {
398 boost::container::small_vector<const xAOD::SpacePoint*, 3> picked;
399 if (cand.empty())
return std::nullopt;
401 Acts::Vector3 v{
sp->x(),
sp->y(),
sp->z()};
405 picked.push_back(last);
406 for (std::size_t i = 1; i < cand.size() && picked.size() < 3; ++i) {
409 picked.push_back(
sp);
413 if (picked.size() < 3)
return std::nullopt;
417 auto retrieveSurface = [&](
const ActsTrk::Seed& seed,
bool useTopSp) ->
const Acts::Surface& {
422 throw std::runtime_error(
"retrieveSurface: no Acts surface for GeometryIdentifier " + std::to_string(geoId.value()));
428 return Acts::fastHypot(
sp->x(),
sp->y(),
sp->z());
431 for (
const auto &cand : candidates) {
432 auto pickedOpt = makeSeedFromCandidate(cand);
433 if (!pickedOpt.has_value())
continue;
435 auto picked = *pickedOpt;
438 return R_of(a) < R_of(b);
444 seed,
true, gctx, mctx, retrieveSurface);
445 if (!initialParamsOpt.has_value())
continue;
447 boost::container::small_vector<const xAOD::SpacePoint*, 16> sortedSP;
448 sortedSP.reserve(cand.size());
449 for (
int spi : cand) sortedSP.push_back(allSPPtrs.at(spi));
450 std::sort(sortedSP.begin(), sortedSP.end(),
452 return R_of(a) < R_of(b);
455 std::vector<const xAOD::UncalibratedMeasurement*> measList;
456 measList.reserve(sortedSP.size() * 2);
459 measList.push_back(m);
463 auto fitted =
m_fitterTool->fit(measList, *initialParamsOpt, gctx, mctx, cctx);
465 for (
auto track : *fitted) {
466 auto newTrack = tracks.makeTrack();
467 newTrack.copyFrom(track);
472 ACTS_DEBUG(
"After track fit: " << tracks.size() <<
" / " << candidates.size()
476 if (candidates.size() == 1 && tracks.size() == 1) {
477 const auto &t = *tracks.begin();
478 ACTS_DEBUG(
"Single particle case: " << candidates.front().size() <<
" -> "
484 timer.emplace(
"Track selection & conversion",
m_chronoSvc.get());
486 Acts::VectorTrackContainer selTrackBackend;
487 selTrackBackend.reserve(trackBackend.size());
491 for (
auto track : tracks) {
492 if (selector.isValidTrack(track)) {
493 auto newTrack = selectedTracks.makeTrack();
496 newTrack.copyFrom(track);
500 ACTS_DEBUG(
"GNN cand: " << candidates.size() <<
", fitted: " << tracks.size()
501 <<
", selected: " << selectedTracks.size());
504 Acts::ConstVectorTrackContainer constTrackBackend(std::move(selTrackBackend));
505 Acts::ConstVectorMultiTrajectory constTrackStateBackend(std::move(trackStateBackend));
506 std::unique_ptr<ActsTrk::TrackContainer> constTracksContainer
507 = std::make_unique<ActsTrk::TrackContainer>(std::move(constTrackBackend), std::move(constTrackStateBackend) );
511 ATH_CHECK(trackContainerHandle.
record(std::move(constTracksContainer)));
513 return StatusCode::SUCCESS;