From c5024de8a692cc646ba3d6635f8b9ec16965dbed Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Tue, 5 May 2026 16:27:47 +0200 Subject: [PATCH 01/11] ITS: add 11-layer GPU tracking support --- .../GPU/ITStrackingGPU/TimeFrameGPU.h | 4 +- .../ITS/tracking/GPU/cuda/TimeFrameGPU.cu | 1 + .../tracking/GPU/cuda/TrackerTraitsGPU.cxx | 3 + .../ITS/tracking/GPU/cuda/TrackingKernels.cu | 181 ++++++++++++++++++ 4 files changed, 187 insertions(+), 2 deletions(-) diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h index c87b3d36b9a6a..4518396959f18 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h @@ -25,7 +25,7 @@ namespace o2::its::gpu { template -class TimeFrameGPU final : public TimeFrame +class TimeFrameGPU : public TimeFrame { using typename TimeFrame::IndexTableUtilsN; using typename TimeFrame::ROFOverlapTableN; @@ -35,7 +35,7 @@ class TimeFrameGPU final : public TimeFrame public: TimeFrameGPU() = default; - ~TimeFrameGPU() final = default; + ~TimeFrameGPU() override = default; /// Most relevant operations void pushMemoryStack(const int); diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu index b9091eebde377..235c7c30dc719 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu @@ -694,4 +694,5 @@ void TimeFrameGPU::wipe() } template class TimeFrameGPU<7>; +template class TimeFrameGPU<11>; } // namespace o2::its::gpu diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx index 0359f2cfb0d03..6c135896f0a82 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx @@ -380,4 +380,7 @@ void TrackerTraitsGPU::setBz(float bz) } template class TrackerTraitsGPU<7>; +#ifdef ENABLE_UPGRADES +template class TrackerTraitsGPU<11>; +#endif } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu index 49b8f19d68ea6..6d778f17dc932 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu @@ -1258,4 +1258,185 @@ template void computeTrackSeedHandler(TrackSeed<7>* trackSeeds, const o2::base::PropagatorF::MatCorrType matCorrType, o2::its::ExternalAllocator* alloc); +/// Explicit instantiation of ALICE3 handlers +#ifdef ENABLE_UPGRADES +template void countTrackletsInROFsHandler<11>(const IndexTableUtils<11>* utils, + const ROFMaskTable<11>::View& rofMask, + const int layer, + const ROFOverlapTable<11>::View& rofOverlaps, + const ROFVertexLookupTable<11>::View& vertexLUT, + const int vertexId, + const Vertex* vertices, + const int* rofPV, + const Cluster** clusters, + std::vector nClusters, + const int** ROFClusters, + const unsigned char** usedClusters, + const int** clustersIndexTables, + int** trackletsLUTs, + gsl::span trackletsLUTsHost, + const int iteration, + const float NSigmaCut, + bounded_vector& phiCuts, + const float resolutionPV, + std::array& minRs, + std::array& maxRs, + bounded_vector& resolutions, + std::vector& radii, + bounded_vector& mulScatAng, + o2::its::ExternalAllocator* alloc, + gpu::Streams& streams); + +template void computeTrackletsInROFsHandler<11>(const IndexTableUtils<11>* utils, + const ROFMaskTable<11>::View& rofMask, + const int layer, + const ROFOverlapTable<11>::View& rofOverlaps, + const ROFVertexLookupTable<11>::View& vertexLUT, + const int vertexId, + const Vertex* vertices, + const int* rofPV, + const Cluster** clusters, + std::vector nClusters, + const int** ROFClusters, + const unsigned char** usedClusters, + const int** clustersIndexTables, + Tracklet** tracklets, + gsl::span spanTracklets, + gsl::span nTracklets, + int** trackletsLUTs, + gsl::span trackletsLUTsHost, + const int iteration, + const float NSigmaCut, + bounded_vector& phiCuts, + const float resolutionPV, + std::array& minRs, + std::array& maxRs, + bounded_vector& resolutions, + std::vector& radii, + bounded_vector& mulScatAng, + o2::its::ExternalAllocator* alloc, + gpu::Streams& streams); + +template void countCellsHandler<11>(const Cluster** sortedClusters, + const Cluster** unsortedClusters, + const TrackingFrameInfo** tfInfo, + Tracklet** tracklets, + int** trackletsLUT, + const int nTracklets, + const int layer, + CellSeed* cells, + int** cellsLUTsArrayDevice, + int* cellsLUTsHost, + const float bz, + const float maxChi2ClusterAttachment, + const float cellDeltaTanLambdaSigma, + const float nSigmaCut, + const std::vector& layerxX0Host, + o2::its::ExternalAllocator* alloc, + gpu::Streams& streams); + +template void computeCellsHandler<11>(const Cluster** sortedClusters, + const Cluster** unsortedClusters, + const TrackingFrameInfo** tfInfo, + Tracklet** tracklets, + int** trackletsLUT, + const int nTracklets, + const int layer, + CellSeed* cells, + int** cellsLUTsArrayDevice, + int* cellsLUTsHost, + const float bz, + const float maxChi2ClusterAttachment, + const float cellDeltaTanLambdaSigma, + const float nSigmaCut, + const std::vector& layerxX0Host, + gpu::Streams& streams); + +template void countCellNeighboursHandler<11>(CellSeed** cellsLayersDevice, + int* neighboursLUT, + int** cellsLUTs, + gpuPair* cellNeighbours, + int* neighboursIndexTable, + const Tracklet** tracklets, + const float maxChi2ClusterAttachment, + const float bz, + const int layerIndex, + const unsigned int nCells, + const unsigned int nCellsNext, + const int maxCellNeighbours, + o2::its::ExternalAllocator* alloc, + gpu::Stream& stream); + +template void computeCellNeighboursHandler<11>(CellSeed** cellsLayersDevice, + int* neighboursLUT, + int** cellsLUTs, + gpuPair* cellNeighbours, + int* neighboursIndexTable, + const Tracklet** tracklets, + const float maxChi2ClusterAttachment, + const float bz, + const int layerIndex, + const unsigned int nCells, + const unsigned int nCellsNext, + const int maxCellNeighbours, + gpu::Stream& stream); + +template void processNeighboursHandler<11>(const int startLayer, + const int startLevel, + CellSeed** allCellSeeds, + CellSeed* currentCellSeeds, + std::array& nCells, + const unsigned char** usedClusters, + std::array& neighbours, + gsl::span neighboursDeviceLUTs, + const TrackingFrameInfo** foundTrackingFrameInfo, + bounded_vector>& seedsHost, + const float bz, + const float maxChi2ClusterAttachment, + const float maxChi2NDF, + const std::vector& layerxX0Host, + const o2::base::Propagator* propagator, + const o2::base::PropagatorF::MatCorrType matCorrType, + o2::its::ExternalAllocator* alloc); + +template void countTrackSeedHandler(TrackSeed<11>* trackSeeds, + const TrackingFrameInfo** foundTrackingFrameInfo, + const Cluster** unsortedClusters, + int* seedLUT, + const std::vector& layerRadiiHost, + const std::vector& minPtsHost, + const std::vector& layerxX0Host, + const unsigned int nSeeds, + const float bz, + const int startLevel, + const float maxChi2ClusterAttachment, + const float maxChi2NDF, + const int reseedIfShorter, + const bool repeatRefitOut, + const bool shiftRefToCluster, + const o2::base::Propagator* propagator, + const o2::base::PropagatorF::MatCorrType matCorrType, + o2::its::ExternalAllocator* alloc); + +template void computeTrackSeedHandler(TrackSeed<11>* trackSeeds, + const TrackingFrameInfo** foundTrackingFrameInfo, + const Cluster** unsortedClusters, + o2::its::TrackITSExt* tracks, + const int* seedLUT, + const std::vector& layerRadiiHost, + const std::vector& minPtsHost, + const std::vector& layerxX0Host, + const unsigned int nSeeds, + const unsigned int nTracks, + const float bz, + const int startLevel, + const float maxChi2ClusterAttachment, + const float maxChi2NDF, + const int reseedIfShorter, + const bool repeatRefitOut, + const bool shiftRefToCluster, + const o2::base::Propagator* propagator, + const o2::base::PropagatorF::MatCorrType matCorrType, + o2::its::ExternalAllocator* alloc); +#endif } // namespace o2::its From c1d0915c16e5a8f54b7315736662033a25b2ad8c Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Wed, 29 Apr 2026 10:42:43 +0200 Subject: [PATCH 02/11] ALICE3: move tracking to GlobalReconstruction --- Detectors/Upgrades/ALICE3/CMakeLists.txt | 1 + .../GlobalReconstruction/CMakeLists.txt | 13 ++ .../reconstruction/CMakeLists.txt | 42 ++++++ .../ALICE3GlobalReconstruction}/TimeFrame.h | 0 .../ALICE3GlobalReconstruction}/TrackerACTS.h | 0 .../reconstruction/src/TimeFrame.cxx | 2 +- .../reconstruction/src/TrackerACTS.cxx | 2 +- .../workflow/CMakeLists.txt | 35 +++++ .../GlobalReconstruction/workflow/README.md | 130 ++++++++++++++++++ .../RecoWorkflow.h | 29 ++++ .../TrackWriterSpec.h | 0 .../TrackerSpec.h | 0 .../workflow/src/RecoWorkflow.cxx | 38 +++++ .../workflow/src/TrackWriterSpec.cxx | 2 +- .../workflow/src/TrackerSpec.cxx | 6 +- .../alice3-global-reconstruction-workflow.cxx | 58 ++++++++ .../ALICE3/TRK/reconstruction/CMakeLists.txt | 18 +-- .../ALICE3/TRK/workflow/CMakeLists.txt | 5 +- .../Upgrades/ALICE3/TRK/workflow/README.md | 127 +---------------- .../include/TRKWorkflow/RecoWorkflow.h | 7 +- .../ALICE3/TRK/workflow/src/RecoWorkflow.cxx | 15 +- .../TRK/workflow/src/trk-reco-workflow.cxx | 13 +- 22 files changed, 364 insertions(+), 179 deletions(-) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt rename Detectors/Upgrades/ALICE3/{TRK/reconstruction/include/TRKReconstruction => GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction}/TimeFrame.h (100%) rename Detectors/Upgrades/ALICE3/{TRK/reconstruction/include/TRKReconstruction => GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction}/TrackerACTS.h (100%) rename Detectors/Upgrades/ALICE3/{TRK => GlobalReconstruction}/reconstruction/src/TimeFrame.cxx (99%) rename Detectors/Upgrades/ALICE3/{TRK => GlobalReconstruction}/reconstruction/src/TrackerACTS.cxx (99%) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h rename Detectors/Upgrades/ALICE3/{TRK/workflow/include/TRKWorkflow => GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow}/TrackWriterSpec.h (100%) rename Detectors/Upgrades/ALICE3/{TRK/workflow/include/TRKWorkflow => GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow}/TrackerSpec.h (100%) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx rename Detectors/Upgrades/ALICE3/{TRK => GlobalReconstruction}/workflow/src/TrackWriterSpec.cxx (97%) rename Detectors/Upgrades/ALICE3/{TRK => GlobalReconstruction}/workflow/src/TrackerSpec.cxx (99%) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx diff --git a/Detectors/Upgrades/ALICE3/CMakeLists.txt b/Detectors/Upgrades/ALICE3/CMakeLists.txt index 0335e85007c01..334bb13064783 100644 --- a/Detectors/Upgrades/ALICE3/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(Passive) add_subdirectory(TRK) +add_subdirectory(GlobalReconstruction) add_subdirectory(ECal) add_subdirectory(FD3) add_subdirectory(FT3) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt new file mode 100644 index 0000000000000..208c0a60b364e --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +add_subdirectory(reconstruction) +add_subdirectory(workflow) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt new file mode 100644 index 0000000000000..206ff485fcf9b --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt @@ -0,0 +1,42 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +if(Acts_FOUND) + set(actsTarget Acts::Core) +endif() + +o2_add_library(ALICE3GlobalReconstruction + TARGETVARNAME targetName + SOURCES src/TimeFrame.cxx + $<$:src/TrackerACTS.cxx> + PUBLIC_LINK_LIBRARIES + O2::ITStracking + O2::GPUCommon + Microsoft.GSL::GSL + O2::CommonConstants + O2::DataFormatsITSMFT + O2::DataFormatsTRK + O2::SimulationDataFormat + O2::ITSBase + O2::ITSReconstruction + O2::ITSMFTReconstruction + O2::DataFormatsITS + O2::TRKBase + O2::TRKSimulation + nlohmann_json::nlohmann_json + ${actsTarget} + PRIVATE_LINK_LIBRARIES + O2::Steer + TBB::tbb) + +if(Acts_FOUND) + target_compile_definitions(${targetName} PUBLIC O2_WITH_ACTS) +endif() diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TimeFrame.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TimeFrame.h rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TrackerACTS.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TrackerACTS.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TrackerACTS.h rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TrackerACTS.h diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TimeFrame.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx similarity index 99% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TimeFrame.cxx rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx index 957560aea8cae..2b4aea8d654d4 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TimeFrame.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx @@ -13,7 +13,7 @@ /// \brief TRK TimeFrame implementation /// -#include "TRKReconstruction/TimeFrame.h" +#include "ALICE3GlobalReconstruction/TimeFrame.h" #include "TRKSimulation/Hit.h" #include "TRKBase/GeometryTGeo.h" #include "Framework/Logger.h" diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TrackerACTS.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx similarity index 99% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TrackerACTS.cxx rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx index 732a0acc14b66..8d5947aeca9bd 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TrackerACTS.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx @@ -16,7 +16,7 @@ /// \since 2026-04-01 /// -#include "TRKReconstruction/TrackerACTS.h" +#include "ALICE3GlobalReconstruction/TrackerACTS.h" #include #include diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt new file mode 100644 index 0000000000000..be6add9c03483 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +o2_add_library(ALICE3GlobalReconstructionWorkflow + TARGETVARNAME targetName + SOURCES src/TrackerSpec.cxx + src/TrackWriterSpec.cxx + src/RecoWorkflow.cxx + PUBLIC_LINK_LIBRARIES O2::Framework + O2::GPUWorkflow + O2::SimConfig + O2::DataFormatsITSMFT + O2::DataFormatsTRK + O2::SimulationDataFormat + O2::DPLUtils + O2::TRKBase + O2::TRKSimulation + O2::ALICE3GlobalReconstruction + nlohmann_json::nlohmann_json) + +o2_add_executable(reco-workflow + SOURCES src/alice3-global-reconstruction-workflow.cxx + COMPONENT_NAME alice3-global-reconstruction + PUBLIC_LINK_LIBRARIES O2::ALICE3GlobalReconstructionWorkflow + O2::TRKSimulation + O2::ALICE3GlobalReconstruction + O2::ITStracking) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md new file mode 100644 index 0000000000000..c3984ed5e7b50 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md @@ -0,0 +1,130 @@ +# ALICE 3 Global Reconstruction Workflow + +This document describes how to run the ALICE 3 global reconstruction workflow and provides examples of configuration files. + +## Overview + +The global reconstruction workflow performs track reconstruction from simulated hits, producing reconstructed tracks with MC truth labels. The workflow currently supports the track reconstruction from hits using the Cellular Automaton (CA) algorithm. The output is stored to a ROOT file for offline analysis (example of QA macro provided in `TRK/macros/test/CheckTracksCA.C`). + +## Quick Start + +### Basic Command + +```bash +o2-alice3-global-reconstruction-reco-workflow --tracking-from-hits-config config_tracker.json -b +``` + +### Command Line Options + +- `--tracking-from-hits-config `: Path to tracking configuration JSON file (required) +- `-b`: Batch mode (no GUI) +- `--disable-root-output`: Skip writing tracks to ROOT file +- `--help`: Show all available options + +## Configuration File + +The tracking configuration is provided via a JSON file that specifies: +1. Input file paths +2. Geometry parameters (magnetic field, detector pitch) +3. Tracking algorithm parameters (can specify multiple iterations) + +### Example Configuration (`config_tracker.json`) + +```json +{ + "inputfiles": { + "hits": "o2sim_HitsTRK.root", + "geometry": "o2sim_geometry.root", + "mcHeader": "o2sim_MCHeader.root", + "kinematics": "o2sim_Kine.root" + }, + "geometry": { + "bz": 5.0, + "pitch": [0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004] + }, + "trackingparams": [{ + "NLayers": 11, + "DeltaROF": 0, + "LayerZ": [25.1, 25.1, 25.1, 64.2, 64.2, 64.2, 64.2, 64.2, 128.5, 128.5, 128.5], + "LayerRadii": [0.5, 1.2, 2.5, 7.05, 9.05, 12.05, 20.05, 30.05, 45.05, 60.5, 80.05], + "LayerxX0": [0.001, 0.001, 0.001, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], + "LayerResolution": [0.0003, 0.0003, 0.0003, 0.0003, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012], + "SystErrorY2": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + "SystErrorZ2": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + "ZBins": 256, + "PhiBins": 128, + "nROFsPerIterations": -1, + "UseDiamond": false, + "Diamond": [0.0, 0.0, 0.0], + "AllowSharingFirstCluster": false, + "ClusterSharing": 0, + "MinTrackLength": 7, + "NSigmaCut": 10, + "PVres": 0.01, + "TrackletMinPt": 0.1, + "TrackletsPerClusterLimit": 2.0, + "CellDeltaTanLambdaSigma": 0.007, + "CellsPerClusterLimit": 2.0, + "MaxChi2ClusterAttachment": 60.0, + "MaxChi2NDF": 30.0, + "ReseedIfShorter": 6, + "MinPt": [0.0, 0.0, 0.0, 0.0], + "StartLayerMask": 4095, + "RepeatRefitOut": false, + "ShiftRefToCluster": true, + "FindShortTracks": false, + "PerPrimaryVertexProcessing": false, + "SaveTimeBenchmarks": false, + "DoUPCIteration": false, + "FataliseUponFailure": true, + "UseTrackFollower": true, + "UseTrackFollowerTop": false, + "UseTrackFollowerBot": false, + "UseTrackFollowerMix": true, + "TrackFollowerNSigmaCutZ": 1.0, + "TrackFollowerNSigmaCutPhi": 1.0, + "createArtefactLabels": false, + "PrintMemory": false, + "DropTFUponFailure": false + }] +} +``` +Note that the `trackingparams` field can contain multiple sets of parameters for different iterations of the tracking algorithm. The example above shows a single iteration with 11 layers and it is **not** optimized. + +## Complete Workflow Example + +### 1. Run Simulation + +First, generate simulation data: + +```bash +o2-sim-serial-run5 -n 200 -g pythia8hi -m TRK --configKeyValues "Diamond.width[0]=0.01;Diamond.width[1]=0.01;Diamond.width[2]=5;TRKBase.layoutML=kTurboStaves;TRKBase.layoutOT=kStaggered;" +``` + +This produces, among other files: +- `o2sim_HitsTRK.root` +- `o2sim_geometry.root` +- `o2sim_MCHeader.root` +- `o2sim_Kine.root` +That will be used by the reconstruction as currently we do not have clusters. + +### 2. Run Reconstruction + +Execute the tracking workflow: + +```bash +o2-alice3-global-reconstruction-reco-workflow --tracking-from-hits-config config_tracker.json -b +``` + +This produces: +- `o2trac_trk.root`: Reconstructed tracks with MC labels + +### 3. Run Quality Assurance + +Analyze the tracking performance: + +```bash +root -l +.L CheckTracksCA.C+ +CheckTracksCA("o2trac_trk.root", "o2sim_Kine.root", "o2sim_HitsTRK.root", "trk_qa_output.root") +``` diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h new file mode 100644 index 0000000000000..17c183dc1cb4c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h @@ -0,0 +1,29 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef O2_ALICE3_GLOBALRECONSTRUCTION_RECOWORKFLOW_H +#define O2_ALICE3_GLOBALRECONSTRUCTION_RECOWORKFLOW_H + +#include "Framework/WorkflowSpec.h" +#include "GPUDataTypesConfig.h" +#include + +namespace o2::trk::global_reco_workflow +{ + +o2::framework::WorkflowSpec getWorkflow(bool useMC, + const std::string& hitRecoConfig, + bool disableRootOutput = false, + o2::gpu::gpudatatypes::DeviceType dType = o2::gpu::gpudatatypes::DeviceType::CPU); + +} // namespace o2::trk::global_reco_workflow + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackWriterSpec.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackWriterSpec.h rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackerSpec.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackerSpec.h rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx new file mode 100644 index 0000000000000..ce745ff1ddc6c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx @@ -0,0 +1,38 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h" +#include "Framework/Logger.h" + +namespace o2::trk::global_reco_workflow +{ + +framework::WorkflowSpec getWorkflow(bool useMC, + const std::string& hitRecoConfig, + bool disableRootOutput, + o2::gpu::gpudatatypes::DeviceType dtype) +{ + framework::WorkflowSpec specs; + + if (!hitRecoConfig.empty()) { + LOGP(info, "Using hit reco config from file {}", hitRecoConfig); + specs.emplace_back(o2::trk::getTrackerSpec(useMC, hitRecoConfig, dtype)); + if (!disableRootOutput) { + specs.emplace_back(o2::trk::getTrackWriterSpec(useMC)); + } + } + + return specs; +} + +} // namespace o2::trk::global_reco_workflow diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackWriterSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackWriterSpec.cxx similarity index 97% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackWriterSpec.cxx rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackWriterSpec.cxx index 1606c32a0ea78..9827c2fc2469d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackWriterSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackWriterSpec.cxx @@ -13,7 +13,7 @@ #include -#include "TRKWorkflow/TrackWriterSpec.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" #include "DataFormatsITS/TrackITS.h" #include "SimulationDataFormat/MCCompLabel.h" diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx similarity index 99% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index c9d793a3ec78f..7373622892525 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -25,12 +25,12 @@ #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" #include "TRKSimulation/Hit.h" -#include "TRKReconstruction/TimeFrame.h" -#include "TRKWorkflow/TrackerSpec.h" +#include "ALICE3GlobalReconstruction/TimeFrame.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" #include #ifdef O2_WITH_ACTS -#include "TRKReconstruction/TrackerACTS.h" +#include "ALICE3GlobalReconstruction/TrackerACTS.h" #endif #include diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx new file mode 100644 index 0000000000000..0549e827e2f5d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h" +#include "CommonUtils/ConfigurableParam.h" + +#include "Framework/CallbacksPolicy.h" +#include "Framework/ConfigContext.h" +#include "Framework/CompletionPolicyHelpers.h" + +#include + +using namespace o2::framework; + +void customize(std::vector& policies) +{ + // o2::raw::HBFUtilsInitializer::addNewTimeSliceCallback(policies); +} + +void customize(std::vector& policies) +{ + policies.push_back(CompletionPolicyHelpers::consumeWhenAllOrdered(".*(?:TRK|trk).*[W,w]riter.*")); +} + +void customize(std::vector& workflowOptions) +{ + std::vector options{ + {"disable-root-output", VariantType::Bool, false, {"do not write output root files"}}, + {"disable-mc", VariantType::Bool, false, {"disable MC propagation even if available"}}, + {"tracking-from-hits-config", VariantType::String, "", {"JSON file with tracking from hits configuration"}}, + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, + {"gpu-device", VariantType::Int, 1, {"use gpu device: CPU=1,CUDA=2,HIP=3 (default: CPU)"}}}; + std::swap(workflowOptions, options); +} + +#include "Framework/runDataProcessing.h" +#include "Framework/Logger.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) +{ + auto useMC = !configcontext.options().get("disable-mc"); + auto hitRecoConfig = configcontext.options().get("tracking-from-hits-config"); + auto gpuDevice = static_cast(configcontext.options().get("gpu-device")); + auto disableRootOutput = configcontext.options().get("disable-root-output"); + o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); + + o2::conf::ConfigurableParam::writeINI("o2alice3globalrecoflow_configuration.ini"); + + return o2::trk::global_reco_workflow::getWorkflow(useMC, hitRecoConfig, disableRootOutput, gpuDevice); +} diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt index b8cb6a88f7163..45ce53ba7c3a3 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt @@ -15,28 +15,16 @@ endif() o2_add_library(TRKReconstruction TARGETVARNAME targetName - SOURCES src/TimeFrame.cxx - src/Clusterer.cxx + SOURCES src/Clusterer.cxx $<$:src/ClustererACTS.cxx> - $<$:src/TrackerACTS.cxx> PUBLIC_LINK_LIBRARIES - O2::ITStracking - O2::GPUCommon Microsoft.GSL::GSL - O2::CommonConstants O2::DataFormatsITSMFT O2::DataFormatsTRK O2::SimulationDataFormat - O2::ITSBase - O2::ITSReconstruction - O2::ITSMFTReconstruction - O2::DataFormatsITS - O2::TRKSimulation + O2::TRKBase nlohmann_json::nlohmann_json - ${actsTarget} - PRIVATE_LINK_LIBRARIES - O2::Steer - TBB::tbb) + ${actsTarget}) if(Acts_FOUND) target_compile_definitions(${targetName} PUBLIC O2_WITH_ACTS) diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt index 42402fe6b62dc..e3309d78f47ea 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt @@ -15,8 +15,6 @@ o2_add_library(TRKWorkflow src/DigitWriterSpec.cxx src/ClustererSpec.cxx src/ClusterWriterSpec.cxx - src/TrackerSpec.cxx - src/TrackWriterSpec.cxx src/RecoWorkflow.cxx PUBLIC_LINK_LIBRARIES O2::Framework O2::GPUWorkflow @@ -35,5 +33,4 @@ o2_add_executable(reco-workflow COMPONENT_NAME alice3-trk PUBLIC_LINK_LIBRARIES O2::TRKWorkflow O2::TRKSimulation - O2::TRKReconstruction - O2::ITStracking) + O2::TRKReconstruction) diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/README.md b/Detectors/Upgrades/ALICE3/TRK/workflow/README.md index 1cdce15b72726..2afb599319217 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/README.md +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/README.md @@ -1,130 +1,11 @@ # TRK Reconstruction Workflow -This document describes how to run the TRK (ALICE 3 Tracker) reconstruction workflow and provides examples of configuration files. +This workflow handles TRK-local reconstruction devices such as digit reading and clusterization. -## Overview - -The TRK reconstruction workflow performs track reconstruction from simulated hits, producing reconstructed tracks with MC truth labels. The workflow currently supports the track reconstruction from hits using the Cellular Automaton (CA) algorithm. The ouput is stored to a ROOT file for offline analysis (example of QA macro provided in `macros/test/CheckTracksCA.C`). - -## Quick Start - -### Basic Command +## Basic Command ```bash -o2-alice3-trk-reco-workflow --tracking-from-hits-config config_tracker.json -b -``` - -### Command Line Options - -- `--tracking-from-hits-config `: Path to tracking configuration JSON file (required) -- `-b`: Batch mode (no GUI) -- `--disable-root-output`: Skip writing tracks to ROOT file -- `--help`: Show all available options - -## Configuration File - -The tracking configuration is provided via a JSON file that specifies: -1. Input file paths -2. Geometry parameters (magnetic field, detector pitch) -3. Tracking algorithm parameters (can specify multiple iterations) - -### Example Configuration (`config_tracker.json`) - -```json -{ - "inputfiles": { - "hits": "o2sim_HitsTRK.root", - "geometry": "o2sim_geometry.root", - "mcHeader": "o2sim_MCHeader.root", - "kinematics": "o2sim_Kine.root" - }, - "geometry": { - "bz": 5.0, - "pitch": [0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004] - }, - "trackingparams": [{ - "NLayers": 11, - "DeltaROF": 0, - "LayerZ": [25.1, 25.1, 25.1, 64.2, 64.2, 64.2, 64.2, 64.2, 128.5, 128.5, 128.5], - "LayerRadii": [0.5, 1.2, 2.5, 7.05, 9.05, 12.05, 20.05, 30.05, 45.05, 60.5, 80.05], - "LayerxX0": [0.001, 0.001, 0.001, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], - "LayerResolution": [0.0003, 0.0003, 0.0003, 0.0003, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012], - "SystErrorY2": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - "SystErrorZ2": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - "ZBins": 256, - "PhiBins": 128, - "nROFsPerIterations": -1, - "UseDiamond": false, - "Diamond": [0.0, 0.0, 0.0], - "AllowSharingFirstCluster": false, - "ClusterSharing": 0, - "MinTrackLength": 7, - "NSigmaCut": 10, - "PVres": 0.01, - "TrackletMinPt": 0.1, - "TrackletsPerClusterLimit": 2.0, - "CellDeltaTanLambdaSigma": 0.007, - "CellsPerClusterLimit": 2.0, - "MaxChi2ClusterAttachment": 60.0, - "MaxChi2NDF": 30.0, - "ReseedIfShorter": 6, - "MinPt": [0.0, 0.0, 0.0, 0.0], - "StartLayerMask": 4095, - "RepeatRefitOut": false, - "ShiftRefToCluster": true, - "FindShortTracks": false, - "PerPrimaryVertexProcessing": false, - "SaveTimeBenchmarks": false, - "DoUPCIteration": false, - "FataliseUponFailure": true, - "UseTrackFollower": true, - "UseTrackFollowerTop": false, - "UseTrackFollowerBot": false, - "UseTrackFollowerMix": true, - "TrackFollowerNSigmaCutZ": 1.0, - "TrackFollowerNSigmaCutPhi": 1.0, - "createArtefactLabels": false, - "PrintMemory": false, - "DropTFUponFailure": false - }] -} +o2-alice3-trk-reco-workflow -b ``` -Note that the `trackingparams` field can contain multiple sets of parameters for different iterations of the tracking algorithm. The example above shows a single iteration with 11 layers and it is **not** optimized. - -## Complete Workflow Example -### 1. Run Simulation - -First, generate simulation data: - -```bash -o2-sim-serial-run5 -n 200 -g pythia8hi -m TRK --configKeyValues "Diamond.width[0]=0.01;Diamond.width[1]=0.01;Diamond.width[2]=5;TRKBase.layoutML=kTurboStaves;TRKBase.layoutOT=kStaggered;" -``` - -This produces, among other files: -- `o2sim_HitsTRK.root` -- `o2sim_geometry.root` -- `o2sim_MCHeader.root` -- `o2sim_Kine.root` -That will be used by the reconstruction as currently we do not have clusters. - -### 2. Run Reconstruction - -Execute the tracking workflow: - -```bash -o2-alice3-trk-reco-workflow --tracking-from-hits-config config_tracker.json -b -``` - -This produces: -- `o2trac_trk.root`: Reconstructed tracks with MC labels - -### 3. Run Quality Assurance - -Analyze the tracking performance: - -```bash -root -l -.L CheckTracksCA.C+ -CheckTracksCA("o2trac_trk.root", "o2sim_Kine.root", "o2sim_HitsTRK.root", "trk_qa_output.root") -``` +Use `o2-alice3-global-reconstruction-reco-workflow` for ALICE 3 tracking from hits. diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h index 7046955a20c2e..863c5deae7241 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h @@ -13,8 +13,6 @@ #define O2_TRK_RECOWORKFLOW_H #include "Framework/WorkflowSpec.h" -#include "GPUDataTypesConfig.h" -#include namespace o2::trk { @@ -22,12 +20,9 @@ namespace reco_workflow { o2::framework::WorkflowSpec getWorkflow(bool useMC, - const std::string& hitRecoConfig, bool upstreamDigits = false, bool upstreamClusters = false, - bool disableRootOutput = false, - bool useGPUWF = false, - o2::gpu::gpudatatypes::DeviceType dType = o2::gpu::gpudatatypes::DeviceType::CPU); + bool disableRootOutput = false); } } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx index d10feb4214f38..02895f42ac094 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx @@ -13,8 +13,6 @@ #include "TRKWorkflow/ClustererSpec.h" #include "TRKWorkflow/ClusterWriterSpec.h" #include "TRKWorkflow/DigitReaderSpec.h" -#include "TRKWorkflow/TrackerSpec.h" -#include "TRKWorkflow/TrackWriterSpec.h" #include "Framework/CCDBParamSpec.h" #include @@ -23,12 +21,9 @@ namespace o2::trk::reco_workflow { framework::WorkflowSpec getWorkflow(bool useMC, - const std::string& hitRecoConfig, bool upstreamDigits, bool upstreamClusters, - bool disableRootOutput, - bool useGPUWF, - o2::gpu::gpudatatypes::DeviceType dtype) + bool disableRootOutput) { framework::WorkflowSpec specs; @@ -43,14 +38,6 @@ framework::WorkflowSpec getWorkflow(bool useMC, specs.emplace_back(o2::trk::getClusterWriterSpec(useMC)); } - if (!hitRecoConfig.empty()) { - LOGP(info, "Using hit reco config from file {}", hitRecoConfig); - specs.emplace_back(o2::trk::getTrackerSpec(useMC, hitRecoConfig, dtype)); - if (!disableRootOutput) { - specs.emplace_back(o2::trk::getTrackWriterSpec(useMC)); - } - } - return specs; } diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx index 166e6f65b4b2b..e9584095cbb4c 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx @@ -22,8 +22,6 @@ #include "TRKWorkflow/RecoWorkflow.h" #include "CommonUtils/ConfigurableParam.h" -#include "ITStracking/TrackingConfigParam.h" -#include "ITStracking/Configuration.h" #include "Framework/CallbacksPolicy.h" #include "Framework/ConfigContext.h" @@ -52,11 +50,7 @@ void customize(std::vector& workflowOptions) {"clusters-from-upstream", VariantType::Bool, false, {"clusters will be provided from upstream, skip clusterizer"}}, {"disable-root-output", VariantType::Bool, false, {"do not write output root files"}}, {"disable-mc", VariantType::Bool, false, {"disable MC propagation even if available"}}, - {"tracking-from-hits-config", VariantType::String, "", {"JSON file with tracking from hits configuration"}}, - {"disable-tracking", VariantType::Bool, false, {"disable tracking step"}}, - {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, - {"use-gpu-workflow", VariantType::Bool, false, {"use GPU workflow (default: false)"}}, - {"gpu-device", VariantType::Int, 1, {"use gpu device: CPU=1,CUDA=2,HIP=3 (default: CPU)"}}}; + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; std::swap(workflowOptions, options); } @@ -67,9 +61,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { // Update the (declared) parameters if changed from the command line auto useMC = !configcontext.options().get("disable-mc"); - auto hitRecoConfig = configcontext.options().get("tracking-from-hits-config"); - auto useGpuWF = configcontext.options().get("use-gpu-workflow"); - auto gpuDevice = static_cast(configcontext.options().get("gpu-device")); auto extDigits = configcontext.options().get("digits-from-upstream"); auto extClusters = configcontext.options().get("clusters-from-upstream"); auto disableRootOutput = configcontext.options().get("disable-root-output"); @@ -78,5 +69,5 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) // write the configuration used for the reco workflow o2::conf::ConfigurableParam::writeINI("o2itsrecoflow_configuration.ini"); - return o2::trk::reco_workflow::getWorkflow(useMC, hitRecoConfig, extDigits, extClusters, disableRootOutput, useGpuWF, gpuDevice); + return o2::trk::reco_workflow::getWorkflow(useMC, extDigits, extClusters, disableRootOutput); } From 608e3721ca7063827e4f03fe7308bb8c9f7ed5ad Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Thu, 30 Apr 2026 21:08:13 +0200 Subject: [PATCH 03/11] ALICE3: add cluster and GPU tracking paths --- .../reconstruction/CMakeLists.txt | 34 +- .../GPUExternalAllocator.h | 64 ++ .../ALICE3GlobalReconstruction/TimeFrame.h | 32 +- .../ALICE3GlobalReconstruction/TimeFrameGPU.h | 54 ++ .../ALICE3GlobalReconstruction/TrackerACTS.h | 6 +- .../src/GPUExternalAllocator.cxx | 210 +++++++ .../reconstruction/src/TimeFrame.cxx | 354 +++++++++-- .../reconstruction/src/TimeFrameGPU.cxx | 442 +++++++++++++ .../reconstruction/src/TrackerACTS.cxx | 4 +- .../GlobalReconstruction/workflow/README.md | 6 +- .../RecoWorkflow.h | 1 + .../TrackerSpec.h | 8 +- .../workflow/src/RecoWorkflow.cxx | 8 +- .../workflow/src/TrackerSpec.cxx | 589 +++++++++++------- .../alice3-global-reconstruction-workflow.cxx | 9 +- .../include/TRKReconstruction/Clusterer.h | 9 +- .../include/TRKReconstruction/ClustererACTS.h | 4 +- .../TRK/reconstruction/src/Clusterer.cxx | 57 +- .../TRK/reconstruction/src/ClustererACTS.cxx | 4 +- 19 files changed, 1596 insertions(+), 299 deletions(-) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cxx create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt index 206ff485fcf9b..8805c1885b079 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt @@ -13,9 +13,24 @@ if(Acts_FOUND) set(actsTarget Acts::Core) endif() +set(alice3GlobalRecoGpuSources "") +set(alice3GlobalRecoGpuTargets "") +set(alice3GlobalRecoGpuPrivateTargets "") +if(CUDA_ENABLED) + find_package(CUDAToolkit REQUIRED) + list(APPEND alice3GlobalRecoGpuSources src/TimeFrameGPU.cxx src/GPUExternalAllocator.cxx) + list(APPEND alice3GlobalRecoGpuTargets O2::ITStrackingCUDA) + list(APPEND alice3GlobalRecoGpuPrivateTargets CUDA::cudart) +elseif(HIP_ENABLED) + list(APPEND alice3GlobalRecoGpuSources src/TimeFrameGPU.cxx src/GPUExternalAllocator.cxx) + list(APPEND alice3GlobalRecoGpuTargets O2::ITStrackingHIP) + list(APPEND alice3GlobalRecoGpuPrivateTargets hip::host) +endif() + o2_add_library(ALICE3GlobalReconstruction TARGETVARNAME targetName SOURCES src/TimeFrame.cxx + ${alice3GlobalRecoGpuSources} $<$:src/TrackerACTS.cxx> PUBLIC_LINK_LIBRARIES O2::ITStracking @@ -30,12 +45,29 @@ o2_add_library(ALICE3GlobalReconstruction O2::ITSMFTReconstruction O2::DataFormatsITS O2::TRKBase + O2::TRKReconstruction O2::TRKSimulation nlohmann_json::nlohmann_json + ${alice3GlobalRecoGpuTargets} ${actsTarget} PRIVATE_LINK_LIBRARIES O2::Steer - TBB::tbb) + TBB::tbb + ${alice3GlobalRecoGpuPrivateTargets}) + +if(alice3GlobalRecoGpuTargets) + target_compile_definitions(${targetName} PUBLIC TRK_HAS_GPU_TRACKING) +endif() + +if(CUDA_ENABLED) + target_include_directories(${targetName} PRIVATE ${CUDAToolkit_INCLUDE_DIRS}) +endif() + +if(CUDA_ENABLED) + target_compile_definitions(${targetName} PUBLIC TRK_HAS_CUDA_TRACKING) +elseif(HIP_ENABLED) + target_compile_definitions(${targetName} PUBLIC TRK_HAS_HIP_TRACKING) +endif() if(Acts_FOUND) target_compile_definitions(${targetName} PUBLIC O2_WITH_ACTS) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h new file mode 100644 index 0000000000000..62683b8185943 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_GPUEXTERNALALLOCATOR_H +#define ALICEO2_ALICE3GLOBALRECONSTRUCTION_GPUEXTERNALALLOCATOR_H + +#include "ITStracking/ExternalAllocator.h" + +#include +#include +#include +#include +#include +#include + +namespace o2::trk +{ + +class GPUExternalAllocator final : public o2::its::ExternalAllocator +{ + public: + GPUExternalAllocator() = default; + ~GPUExternalAllocator(); + + void* allocate(size_t size) override; + void deallocate(char* ptr, size_t size) override; + void pushTagOnStack(uint64_t tag) override; + void popTagOffStack(uint64_t tag) override; + + void releaseAll(); + + private: + enum class AllocationSpace { Host, Device }; + + struct AllocationMeta { + AllocationSpace space; + uint64_t tag; + bool stacked; + }; + + using MemoryType = std::underlying_type_t; + + void* allocateHost(size_t size); + void* allocateDevice(size_t size); + void freeAllocation(void* ptr, AllocationSpace space); + void removeFromTagLocked(uint64_t tag, void* ptr); + + std::mutex mMutex; + std::vector mTagStack; + std::unordered_map> mTaggedAllocations; + std::unordered_map mAllocations; +}; + +} // namespace o2::trk + +#endif diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h index 005237fe28839..f75e5da0be3b5 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h @@ -13,13 +13,17 @@ /// \brief TRK TimeFrame class derived from ITS TimeFrame /// -#ifndef ALICEO2_TRK_TIMEFRAME_H -#define ALICEO2_TRK_TIMEFRAME_H +#ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H +#define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H #include "ITStracking/TimeFrame.h" #include "ITStracking/Constants.h" #include "ITStracking/Configuration.h" #include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" +#include #include #include #include @@ -37,8 +41,8 @@ class GeometryTGeo; /// TRK TimeFrame class that extends ITS TimeFrame functionality /// This allows for customization of tracking algorithms specific to the TRK detector -template -class TimeFrame : public o2::its::TimeFrame +template +class TimeFrame : public o2::its::TimeFrame { public: TimeFrame() = default; @@ -49,20 +53,34 @@ class TimeFrame : public o2::its::TimeFrame /// Process hits from TTree to initialize ROFs /// \param hitsTree Tree containing TRK hits + /// \param mcHeaderTree Tree containing MC event headers + /// \param nEvents Number of events to process /// \param gman TRK geometry manager instance /// \param config Configuration parameters for hit reconstruction int loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config); + /// Load ROF data from TRK clustered inputs (without topology dictionary for the time being). + /// Patterns are expected as [rowSpan, colSpan, bitmap...] for each cluster. + int loadROFrameData(gsl::span rofs, + gsl::span clusters, + gsl::span patterns, + const dataformats::MCTruthContainer* mcLabels = nullptr, + float yPlaneMLOT = 0.f); + /// Add primary vertices from MC headers for each ROF /// \param mcHeaderTree Tree containing MC event headers /// \param nRofs Number of ROFs (Read-Out Frames) /// \param nEvents Number of events to process /// \param inROFpileup Number of events per ROF - /// \param rofLength ROF length in BCs (must match what was used in loadROFsFromHitTree) - void getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup, uint32_t rofLength = 198); + void getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup); + + /// Add primary vertices using truth seeding from the DigitizationContext (collisioncontext.root). + /// Maps each MC collision to its ROF via the ROF BCData timestamps (TRK digitising timing). + /// \param rofs Span of TRK ROF records used to determine which ROF each collision falls into + void addTruthSeedingVertices(gsl::span rofs); }; } // namespace trk } // namespace o2 -#endif // ALICEO2_TRK_TIMEFRAME_H +#endif // ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h new file mode 100644 index 0000000000000..7743eed305994 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H +#define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H + +#include "ITStrackingGPU/TimeFrameGPU.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" + +#include +#include + +class TTree; + +namespace o2 +{ +namespace trk +{ +class GeometryTGeo; + +template +class TimeFrameGPU : public o2::its::gpu::TimeFrameGPU +{ + public: + TimeFrameGPU() = default; + ~TimeFrameGPU() override = default; + + int loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config); + + int loadROFrameData(gsl::span rofs, + gsl::span clusters, + gsl::span patterns, + const dataformats::MCTruthContainer* mcLabels = nullptr, + float yPlaneMLOT = 0.f); + + void getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup); + void addTruthSeedingVertices(gsl::span rofs); +}; + +} // namespace trk +} // namespace o2 + +#endif diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TrackerACTS.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TrackerACTS.h index 2910abf480961..ee69b32a23895 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TrackerACTS.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TrackerACTS.h @@ -16,8 +16,8 @@ /// \since 2026-04-01 /// -#ifndef ALICE3_INCLUDE_TRACKERACTS_H_ -#define ALICE3_INCLUDE_TRACKERACTS_H_ +#ifndef ALICE3_GLOBALRECONSTRUCTION_INCLUDE_TRACKERACTS_H_ +#define ALICE3_GLOBALRECONSTRUCTION_INCLUDE_TRACKERACTS_H_ #include "Acts/Definitions/Units.hpp" #include "Framework/Logger.h" @@ -186,4 +186,4 @@ float TrackerACTS::evaluateTask(Func&& task, std::string_view taskName) } // namespace o2::trk -#endif /* ALICE3_INCLUDE_TRACKERACTS_H_ */ +#endif /* ALICE3_GLOBALRECONSTRUCTION_INCLUDE_TRACKERACTS_H_ */ diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cxx new file mode 100644 index 0000000000000..df2a2c30b037a --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cxx @@ -0,0 +1,210 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#if defined(TRK_HAS_CUDA_TRACKING) +#include +#elif defined(TRK_HAS_HIP_TRACKING) +#include +#endif + +#include "ALICE3GlobalReconstruction/GPUExternalAllocator.h" + +#include +#include +#include + +namespace +{ +#if defined(TRK_HAS_CUDA_TRACKING) +void checkGpuError(cudaError_t error, const char* call) +{ + if (error != cudaSuccess) { + throw std::runtime_error(std::string(call) + ": " + cudaGetErrorString(error)); + } +} +#elif defined(TRK_HAS_HIP_TRACKING) +void checkGpuError(hipError_t error, const char* call) +{ + if (error != hipSuccess) { + throw std::runtime_error(std::string(call) + ": " + hipGetErrorString(error)); + } +} +#endif +} // namespace + +namespace o2::trk +{ + +GPUExternalAllocator::~GPUExternalAllocator() +{ + releaseAll(); +} + +void* GPUExternalAllocator::allocate(size_t size) +{ + const auto type = static_cast(getType()); + const bool useHost = (type & static_cast(o2::gpu::GPUMemoryResource::MEMORY_HOST)) != 0; + const bool useStack = (type & static_cast(o2::gpu::GPUMemoryResource::MEMORY_STACK)) != 0; + + void* ptr = useHost ? allocateHost(size) : allocateDevice(size); + + std::lock_guard guard(mMutex); + const uint64_t tag = (useStack && !mTagStack.empty()) ? mTagStack.back() : 0; + mAllocations.emplace(ptr, AllocationMeta{useHost ? AllocationSpace::Host : AllocationSpace::Device, tag, useStack}); + if (useStack) { + mTaggedAllocations[tag].push_back(ptr); + } + + return ptr; +} + +void GPUExternalAllocator::deallocate(char* ptr, size_t) +{ + if (!ptr) { + return; + } + + AllocationMeta meta; + { + std::lock_guard guard(mMutex); + const auto found = mAllocations.find(ptr); + if (found == mAllocations.end()) { + return; + } + meta = found->second; + mAllocations.erase(found); + if (meta.stacked) { + removeFromTagLocked(meta.tag, ptr); + } + } + + freeAllocation(ptr, meta.space); +} + +void GPUExternalAllocator::pushTagOnStack(uint64_t tag) +{ + std::lock_guard guard(mMutex); + mTagStack.push_back(tag); +} + +void GPUExternalAllocator::popTagOffStack(uint64_t tag) +{ + std::vector> toFree; + { + std::lock_guard guard(mMutex); + if (mTagStack.empty() || mTagStack.back() != tag) { + throw std::runtime_error("GPUExternalAllocator tag stack mismatch"); + } + + const auto tagged = mTaggedAllocations.find(tag); + if (tagged != mTaggedAllocations.end()) { + toFree.reserve(tagged->second.size()); + for (void* ptr : tagged->second) { + const auto found = mAllocations.find(ptr); + if (found != mAllocations.end()) { + toFree.emplace_back(ptr, found->second.space); + mAllocations.erase(found); + } + } + mTaggedAllocations.erase(tagged); + } + + mTagStack.pop_back(); + } + + for (const auto& [ptr, space] : toFree) { + freeAllocation(ptr, space); + } +} + +void GPUExternalAllocator::releaseAll() +{ + std::vector> toFree; + { + std::lock_guard guard(mMutex); + toFree.reserve(mAllocations.size()); + for (const auto& [ptr, meta] : mAllocations) { + toFree.emplace_back(ptr, meta.space); + } + mAllocations.clear(); + mTaggedAllocations.clear(); + mTagStack.clear(); + } + + for (const auto& [ptr, space] : toFree) { + freeAllocation(ptr, space); + } +} + +void* GPUExternalAllocator::allocateHost(size_t size) +{ + void* ptr = nullptr; +#if defined(TRK_HAS_CUDA_TRACKING) + checkGpuError(cudaHostAlloc(&ptr, size, cudaHostAllocPortable), "cudaHostAlloc"); +#elif defined(TRK_HAS_HIP_TRACKING) + checkGpuError(hipHostMalloc(&ptr, size, hipHostMallocPortable), "hipHostMalloc"); +#else + throw std::runtime_error("GPUExternalAllocator built without a GPU backend"); +#endif + return ptr; +} + +void* GPUExternalAllocator::allocateDevice(size_t size) +{ + void* ptr = nullptr; +#if defined(TRK_HAS_CUDA_TRACKING) + checkGpuError(cudaMalloc(&ptr, size), "cudaMalloc"); +#elif defined(TRK_HAS_HIP_TRACKING) + checkGpuError(hipMalloc(&ptr, size), "hipMalloc"); +#else + throw std::runtime_error("GPUExternalAllocator built without a GPU backend"); +#endif + return ptr; +} + +void GPUExternalAllocator::freeAllocation(void* ptr, AllocationSpace space) +{ + if (!ptr) { + return; + } + +#if defined(TRK_HAS_CUDA_TRACKING) + if (space == AllocationSpace::Host) { + checkGpuError(cudaFreeHost(ptr), "cudaFreeHost"); + } else { + checkGpuError(cudaFree(ptr), "cudaFree"); + } +#elif defined(TRK_HAS_HIP_TRACKING) + if (space == AllocationSpace::Host) { + checkGpuError(hipHostFree(ptr), "hipHostFree"); + } else { + checkGpuError(hipFree(ptr), "hipFree"); + } +#else + (void)space; +#endif +} + +void GPUExternalAllocator::removeFromTagLocked(uint64_t tag, void* ptr) +{ + const auto tagged = mTaggedAllocations.find(tag); + if (tagged == mTaggedAllocations.end()) { + return; + } + + auto& entries = tagged->second; + entries.erase(std::remove(entries.begin(), entries.end(), ptr), entries.end()); + if (entries.empty()) { + mTaggedAllocations.erase(tagged); + } +} + +} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx index 2b4aea8d654d4..ee48e7a70f8e8 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx @@ -14,22 +14,30 @@ /// #include "ALICE3GlobalReconstruction/TimeFrame.h" +#include "TRKReconstruction/Clusterer.h" #include "TRKSimulation/Hit.h" #include "TRKBase/GeometryTGeo.h" +#include "TRKBase/SegmentationChip.h" #include "Framework/Logger.h" #include "SimulationDataFormat/MCEventHeader.h" +#include "SimulationDataFormat/DigitizationContext.h" +#include "Steer/MCKinematicsReader.h" #include #include +#include +#include +#include +#include +#include +#include #include #include -using o2::its::clearResizeBoundedVector; - namespace o2::trk { -template -int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config) +template +int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config) { constexpr std::array startLayer{0, 3}; const Long64_t nEvents = hitsTree->GetEntries(); @@ -41,28 +49,26 @@ int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const int inROFpileup{config.contains("inROFpileup") ? config["inROFpileup"].get() : 1}; - // Calculate number of ROFs + // Calculate number of ROFs and initialize data structures const int nRofs = (nEvents + inROFpileup - 1) / inROFpileup; - - // Set up ROF timing for all layers (no staggering in TRK simulation, all layers read out together) - constexpr uint32_t rofLength = 198; // ROF length in BC - o2::its::ROFOverlapTable overlapTable; - for (int iLayer = 0; iLayer < NLayers; ++iLayer) { - overlapTable.defineLayer(iLayer, nRofs, rofLength, 0, 0, 0); - } - overlapTable.init(); - this->setROFOverlapTable(overlapTable); - - // Set up the vertex lookup table timing (pre-allocate, vertices will be filled later) - o2::its::ROFVertexLookupTable vtxLookupTable; - for (int iLayer = 0; iLayer < NLayers; ++iLayer) { - vtxLookupTable.defineLayer(iLayer, nRofs, rofLength, 0, 0, 0); + typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; + typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; + typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + rofOverlapTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + rofVertexLookupTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + rofMaskTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); } - vtxLookupTable.init(); // pre-allocate without vertices - this->setROFVertexLookupTable(vtxLookupTable); + rofOverlapTable.init(); + rofVertexLookupTable.init(); + rofMaskTable.init(); + this->setROFOverlapTable(std::move(rofOverlapTable)); + this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); + this->setMultiplicityCutMask(std::move(rofMaskTable)); + this->useMultiplictyMask(); // Reset and prepare ROF data structures - for (int iLayer{0}; iLayer < NLayers; ++iLayer) { + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { this->mMinR[iLayer] = std::numeric_limits::max(); this->mMaxR[iLayer] = std::numeric_limits::lowest(); this->mROFramesClusters[iLayer].clear(); @@ -70,10 +76,11 @@ int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, this->mUnsortedClusters[iLayer].clear(); this->mTrackingFrameInfo[iLayer].clear(); this->mClusterExternalIndices[iLayer].clear(); + this->mClusterSize[iLayer].clear(); } // Pre-count hits to reserve memory efficiently - std::array clusterCountPerLayer{}; + std::array clusterCountPerLayer{}; for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { hitsTree->GetEntry(iEvent); for (const auto& hit : *trkHit) { @@ -82,24 +89,24 @@ int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, } int subDetID = gman->getSubDetID(hit.GetDetectorID()); const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); - if (layer >= NLayers) { + if (layer >= nLayers) { continue; } ++clusterCountPerLayer[layer]; } } - // Reserve memory for all layers (mClusterSize is now per-layer) - for (int iLayer{0}; iLayer < NLayers; ++iLayer) { + // Reserve memory for all layers + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); - clearResizeBoundedVector(this->mClusterSize[iLayer], clusterCountPerLayer[iLayer], this->mMemoryPool.get()); + this->mClusterSize[iLayer].reserve(clusterCountPerLayer[iLayer]); } std::array resolution{0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004}; - if (config["geometry"]["pitch"].size() == static_cast(NLayers)) { - for (size_t iLayer{0}; iLayer < config["geometry"]["pitch"].size(); ++iLayer) { + if (config["geometry"]["pitch"].size() == nLayers) { + for (int iLayer{0}; iLayer < config["geometry"]["pitch"].size(); ++iLayer) { LOGP(info, "Setting resolution for layer {} from config", iLayer); LOGP(info, "Layer {} pitch {} cm", iLayer, config["geometry"]["pitch"][iLayer].get()); resolution[iLayer] = config["geometry"]["pitch"][iLayer].get() / std::sqrt(12.f); @@ -107,10 +114,9 @@ int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, } LOGP(info, "Number of active parts in VD: {}", gman->getNumberOfActivePartsVD()); - // One shared MC label container for all layers - auto* labels = new dataformats::MCTruthContainer(); - int hitCounter{0}; + auto labels = new dataformats::MCTruthContainer(); + int iRof{0}; // Current ROF index for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { hitsTree->GetEntry(iEvent); @@ -126,7 +132,7 @@ int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, o2::math_utils::Point3D gloXYZ; o2::math_utils::Point3D trkXYZ; float r{0.f}; - if (layer >= NLayers) { + if (layer >= nLayers) { continue; } if (layer >= 3) { @@ -157,12 +163,11 @@ int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, std::array{trkXYZ.y(), trkXYZ.z()}, std::array{resolution[layer] * resolution[layer], 0., resolution[layer] * resolution[layer]}); /// Rotate to the global frame - const int clusterIdxInLayer = this->mUnsortedClusters[layer].size(); - this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), clusterIdxInLayer); + this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); this->addClusterExternalIndexToLayer(layer, hitCounter); + this->mClusterSize[layer].push_back(1); // For compatibility with cluster-based tracking, set cluster size to 1 for hits MCCompLabel label{hit.GetTrackID(), static_cast(iEvent), 0}; labels->addElement(hitCounter, label); - this->mClusterSize[layer][clusterIdxInLayer] = 1; hitCounter++; } trkHit->clear(); @@ -173,53 +178,296 @@ int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, for (unsigned int iLayer{0}; iLayer < this->mUnsortedClusters.size(); ++iLayer) { this->mROFramesClusters[iLayer][iRof] = this->mUnsortedClusters[iLayer].size(); // effectively calculating an exclusive sum } + // Update primary vertices ROF structure + } + this->mClusterLabels[0] = labels; + } + return nRofs; +} + +template +int TimeFrame::loadROFrameData(gsl::span rofs, + gsl::span clusters, + gsl::span patterns, + const dataformats::MCTruthContainer* mcLabels, + float yPlaneMLOT) +{ + constexpr std::array startLayer{0, 3}; + GeometryTGeo* geom = GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + + const int nRofs = rofs.size(); + typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; + typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; + typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + rofOverlapTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + rofVertexLookupTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + rofMaskTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + } + rofOverlapTable.init(); + rofVertexLookupTable.init(); + rofMaskTable.init(); + this->setROFOverlapTable(std::move(rofOverlapTable)); + this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); + this->setMultiplicityCutMask(std::move(rofMaskTable)); + this->useMultiplictyMask(); + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mMinR[iLayer] = std::numeric_limits::max(); + this->mMaxR[iLayer] = std::numeric_limits::lowest(); + this->mROFramesClusters[iLayer].clear(); + this->mROFramesClusters[iLayer].resize(nRofs + 1, 0); + this->mUnsortedClusters[iLayer].clear(); + this->mTrackingFrameInfo[iLayer].clear(); + this->mClusterExternalIndices[iLayer].clear(); + this->mClusterSize[iLayer].clear(); + } + + std::array clusterCountPerLayer{}; + for (const auto& c : clusters) { + if (c.subDetID < 0 || c.subDetID > 1) { + continue; + } + if (c.disk != -1) { + continue; // skip non-barrel clusters for now + } + const int layer = startLayer[c.subDetID] + c.layer; + if (layer < 0 || layer >= nLayers) { + continue; } + ++clusterCountPerLayer[layer]; } - // Set the shared labels container for all layers - for (int iLayer = 0; iLayer < NLayers; ++iLayer) { - this->mClusterLabels[iLayer] = labels; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mClusterSize[iLayer].reserve(clusterCountPerLayer[iLayer]); + } + + const uint8_t* pattPtr = patterns.data(); + const uint8_t* pattEnd = pattPtr + patterns.size(); + + for (size_t iRof{0}; iRof < rofs.size(); ++iRof) { + const auto& rof = rofs[iRof]; + const int first = rof.getFirstEntry(); + const int last = first + rof.getNEntries(); + + for (int clusterId{first}; clusterId < last; ++clusterId) { + // Parse the pattern header up-front so we always know how many bytes + // this cluster occupies in the pattern stream. The stream is keyed + // per-cluster: every `continue` below MUST advance pattPtr by + // pattAdvance, otherwise the next cluster decodes from a stale + // offset and the whole layer's geometry is corrupted. + if (pattPtr + 2 > pattEnd) { + LOGP(error, "Pattern stream exhausted while decoding cluster {}", clusterId); + break; + } + const uint8_t* pattForCluster = pattPtr; + const int nBytes = (pattForCluster[0] * pattForCluster[1] + 7) / 8; + if (pattPtr + 2 + nBytes > pattEnd) { + LOGP(error, "Pattern stream truncated for cluster {}", clusterId); + break; + } + const int pattAdvance = 2 + nBytes; + + if (clusterId < 0 || clusterId >= static_cast(clusters.size())) { + LOGP(warning, "Skipping out-of-range cluster id {} for ROF {}", clusterId, iRof); + pattPtr += pattAdvance; + continue; + } + + const auto& c = clusters[clusterId]; + if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { + pattPtr += pattAdvance; + continue; + } + + const int layer = startLayer[c.subDetID] + c.layer; + if (layer < 0 || layer >= nLayers) { + LOGP(error, "Skipping cluster with invalid layer {} (subDetID {}, layer {})", layer, c.subDetID, c.layer); + pattPtr += pattAdvance; + continue; + } + + auto locXYZ = Clusterer::getClusterLocalCoordinates(c, pattForCluster, yPlaneMLOT); + pattPtr += pattAdvance; + + const auto gloXYZ = geom->getMatrixL2G(c.chipID) * locXYZ; + + float alpha{0.f}; + o2::math_utils::Point3D trkXYZ; + if (c.subDetID == 1) { + alpha = geom->getSensorRefAlphaMLOT(c.chipID); + trkXYZ = geom->getMatrixT2L(c.chipID - geom->getNumberOfActivePartsVD()) ^ locXYZ; + } else { + const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + alpha = std::atan2(gloXYZ.Y(), gloXYZ.X()); + o2::math_utils::bringTo02Pi(alpha); + trkXYZ.SetX(r); + trkXYZ.SetY(0.f); + trkXYZ.SetZ(gloXYZ.Z()); + } + + const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + this->mMinR[layer] = std::min(this->mMinR[layer], r); + this->mMaxR[layer] = std::max(this->mMaxR[layer], r); + + const float sigmaY2 = (c.subDetID == 0) + ? 0.25f * SegmentationChip::PitchRowVD * SegmentationChip::PitchRowVD + : 0.25f * SegmentationChip::PitchRowMLOT * SegmentationChip::PitchRowMLOT; + const float sigmaZ2 = (c.subDetID == 0) + ? 0.25f * SegmentationChip::PitchColVD * SegmentationChip::PitchColVD + : 0.25f * SegmentationChip::PitchColMLOT * SegmentationChip::PitchColMLOT; + + this->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, + std::array{trkXYZ.y(), trkXYZ.z()}, + std::array{sigmaY2, 0.f, sigmaZ2}); + this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); + this->addClusterExternalIndexToLayer(layer, clusterId); + this->mClusterSize[layer].push_back(std::clamp(static_cast(c.size), 0u, 255u)); + } + + for (unsigned int iL{0}; iL < this->mUnsortedClusters.size(); ++iL) { + this->mROFramesClusters[iL][iRof + 1] = this->mUnsortedClusters[iL].size(); + } + } + + for (auto i = 0; i < this->mNTrackletsPerCluster.size(); ++i) { + this->mNTrackletsPerCluster[i].resize(this->mUnsortedClusters[1].size()); + this->mNTrackletsPerClusterSum[i].resize(this->mUnsortedClusters[1].size() + 1); + } + + if (mcLabels != nullptr) { + this->mClusterLabels[0] = mcLabels; } return nRofs; } -template -void TimeFrame::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup, uint32_t rofLength) +template +void TimeFrame::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup) { auto mcheader = new o2::dataformats::MCEventHeader; mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); this->mPrimaryVertices.clear(); + this->mPrimaryVerticesLabels.clear(); int iRof{0}; for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { mcHeaderTree->GetEntry(iEvent); o2::its::Vertex vertex; + vertex.setTimeStamp(o2::its::TimeEstBC{static_cast(iRof), 1}); vertex.setXYZ(mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); vertex.setNContributors(30); vertex.setChi2(0.f); - - // Set proper BC timestamp for vertex-ROF compatibility - // The vertex timestamp is set to the center of its ROF with half-ROF as error - const uint32_t rofCenter = static_cast(rofLength * iRof + rofLength / 2); - const uint16_t rofHalf = static_cast(rofLength / 2); - vertex.setTimeStamp({rofCenter, rofHalf}); - - LOGP(debug, "ROF {}: Added primary vertex at ({}, {}, {}) with BC timestamp [{}, +/-{}]", - iRof, mcheader->GetX(), mcheader->GetY(), mcheader->GetZ(), rofCenter, rofHalf); + LOGP(debug, "ROF {}: Added primary vertex at ({}, {}, {})", iRof, mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); this->addPrimaryVertex(vertex); + this->addPrimaryVertexLabel({o2::MCCompLabel{o2::MCCompLabel::maxTrackID(), static_cast(iEvent), 0, false}, 1.f}); if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { iRof++; } } - this->mMultiplicityCutMask.resetMask(1u); /// all ROFs are valid with MC primary vertices. - - // Update the vertex lookup table with the newly added vertices this->updateROFVertexLookupTable(); } + + // Explicit template instantiation for TRK with 11 layers + +template +void TimeFrame::addTruthSeedingVertices(gsl::span rofs) +{ + LOGP(info, "TRK: using truth seeds as vertices from DigitizationContext"); + this->mPrimaryVertices.clear(); + this->mPrimaryVerticesLabels.clear(); + + const auto dc = o2::steer::DigitizationContext::loadFromFile("collisioncontext.root"); + const auto irs = dc->getEventRecords(); + o2::steer::MCKinematicsReader mcReader(dc); + + // Pre-compute ROF start BC (as absolute long) for binary search + std::vector rofStartBC(rofs.size()); + for (size_t i = 0; i < rofs.size(); ++i) { + rofStartBC[i] = rofs[i].getBCData().toLong(); + } + + using Vertex = o2::its::Vertex; + struct VertInfo { + std::pmr::vector vertices; + std::pmr::vector srcs; + std::pmr::vector events; + }; + std::map vertMap; + + const int iSrc = 0; // primary collision generator source + auto eveId2colId = dc->getCollisionIndicesForSource(iSrc); + for (int iEve{0}; iEve < mcReader.getNEvents(iSrc); ++iEve) { + const auto& ir = irs[eveId2colId[iEve]]; + if (!ir.isDummy()) { + const auto& eve = mcReader.getMCEventHeader(iSrc, iEve); + const int64_t evBC = ir.toLong(); + // Find ROF: last ROF whose start BC <= evBC + auto it = std::upper_bound(rofStartBC.begin(), rofStartBC.end(), evBC); + if (it != rofStartBC.begin()) { + --it; + int rofId = static_cast(std::distance(rofStartBC.begin(), it)); + auto* mr = this->mMemoryPool.get(); + if (!vertMap.contains(rofId)) { + vertMap[rofId] = { + .vertices = std::pmr::vector(mr), + .srcs = std::pmr::vector(mr), + .events = std::pmr::vector(mr), + }; + } + Vertex vert; + vert.setTimeStamp(o2::its::TimeEstBC{static_cast(rofId), 1}); + vert.setNContributors(std::max(1L, std::ranges::count_if( + mcReader.getTracks(iSrc, iEve), + [](const auto& trk) { + return trk.isPrimary() && trk.GetPt() > 0.05 && std::abs(trk.GetEta()) < 1.1; + }))); + vert.setXYZ((float)eve.GetX(), (float)eve.GetY(), (float)eve.GetZ()); + vert.setChi2(1); + constexpr float cov = 50e-9f; + vert.setCov(cov, cov, cov, cov, cov, cov); + vertMap[rofId].vertices.push_back(vert); + vertMap[rofId].srcs.push_back(iSrc); + vertMap[rofId].events.push_back(iEve); + } + } + mcReader.releaseTracksForSourceAndEvent(iSrc, iEve); + } + + size_t nVerts{0}; + auto* mr = this->mMemoryPool.get(); + for (int iROF{0}; iROF < static_cast(rofs.size()); ++iROF) { + std::pmr::vector verts(mr); + std::pmr::vector> polls(mr); + if (vertMap.contains(iROF)) { + const auto& info = vertMap[iROF]; + verts = info.vertices; + nVerts += verts.size(); + for (size_t i{0}; i < verts.size(); ++i) { + o2::MCCompLabel lbl(o2::MCCompLabel::maxTrackID(), info.events[i], info.srcs[i], false); + polls.emplace_back(lbl, 1.f); + } + } + for (const auto& vert : verts) { + this->addPrimaryVertex(vert); + } + for (const auto& label : polls) { + this->addPrimaryVertexLabel(label); + } + } + this->updateROFVertexLookupTable(); + LOGP(info, "TRK truth seeding: {}/{} ROFs with {} vertices -> ={:.2f}", + vertMap.size(), rofs.size(), nVerts, + vertMap.size() > 0 ? (float)nVerts / (float)vertMap.size() : 0.f); +} + template class TimeFrame<11>; } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx new file mode 100644 index 0000000000000..26e2b69dbdfc6 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx @@ -0,0 +1,442 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "ALICE3GlobalReconstruction/TimeFrameGPU.h" + +#include "TRKReconstruction/Clusterer.h" +#include "TRKSimulation/Hit.h" +#include "TRKBase/GeometryTGeo.h" +#include "TRKBase/SegmentationChip.h" +#include "Framework/Logger.h" +#include "SimulationDataFormat/MCEventHeader.h" +#include "SimulationDataFormat/DigitizationContext.h" +#include "Steer/MCKinematicsReader.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::trk +{ + +template +int TimeFrameGPU::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config) +{ + constexpr std::array startLayer{0, 3}; + const Long64_t nEvents = hitsTree->GetEntries(); + + gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + + std::vector* trkHit = nullptr; + hitsTree->SetBranchAddress("TRKHit", &trkHit); + + const int inROFpileup{config.contains("inROFpileup") ? config["inROFpileup"].get() : 1}; + const int nRofs = (nEvents + inROFpileup - 1) / inROFpileup; + typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; + typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; + typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + rofOverlapTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + rofVertexLookupTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + rofMaskTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + } + rofOverlapTable.init(); + rofVertexLookupTable.init(); + rofMaskTable.init(); + this->setROFOverlapTable(std::move(rofOverlapTable)); + this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); + this->setMultiplicityCutMask(std::move(rofMaskTable)); + this->useMultiplictyMask(); + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mMinR[iLayer] = std::numeric_limits::max(); + this->mMaxR[iLayer] = std::numeric_limits::lowest(); + this->mROFramesClusters[iLayer].clear(); + this->mROFramesClusters[iLayer].resize(nRofs + 1, 0); + this->mUnsortedClusters[iLayer].clear(); + this->mTrackingFrameInfo[iLayer].clear(); + this->mClusterExternalIndices[iLayer].clear(); + this->mClusterSize[iLayer].clear(); + } + + std::array clusterCountPerLayer{}; + for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { + hitsTree->GetEntry(iEvent); + for (const auto& hit : *trkHit) { + if (gman->getDisk(hit.GetDetectorID()) != -1) { + continue; + } + int subDetID = gman->getSubDetID(hit.GetDetectorID()); + const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); + if (layer >= nLayers) { + continue; + } + ++clusterCountPerLayer[layer]; + } + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mClusterSize[iLayer].reserve(clusterCountPerLayer[iLayer]); + } + + std::array resolution{0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004}; + if (config["geometry"]["pitch"].size() == nLayers) { + for (int iLayer{0}; iLayer < config["geometry"]["pitch"].size(); ++iLayer) { + resolution[iLayer] = config["geometry"]["pitch"][iLayer].get() / std::sqrt(12.f); + } + } + + int hitCounter{0}; + auto labels = new dataformats::MCTruthContainer(); + + int iRof{0}; + for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { + hitsTree->GetEntry(iEvent); + + for (auto& hit : *trkHit) { + if (gman->getDisk(hit.GetDetectorID()) != -1) { + continue; + } + int subDetID = gman->getSubDetID(hit.GetDetectorID()); + const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); + + float alpha{0.f}; + o2::math_utils::Point3D gloXYZ; + o2::math_utils::Point3D trkXYZ; + float r{0.f}; + if (layer >= nLayers) { + continue; + } + if (layer >= 3) { + int chipID = hit.GetDetectorID(); + alpha = gman->getSensorRefAlphaMLOT(chipID); + const o2::math_utils::Transform3D& l2g = gman->getMatrixL2G(chipID); + auto locXYZ = l2g ^ (hit.GetPos()); + locXYZ.SetX(locXYZ.X() + gRandom->Gaus(0.0, resolution[layer])); + locXYZ.SetZ(locXYZ.Z() + gRandom->Gaus(0.0, resolution[layer])); + gloXYZ = gman->getMatrixL2G(chipID) * locXYZ; + trkXYZ = gman->getMatrixT2L(chipID - gman->getNumberOfActivePartsVD()) ^ locXYZ; + r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + } else { + const auto& hitPos = hit.GetPos(); + r = std::hypot(hitPos.X(), hitPos.Y()); + alpha = std::atan2(hitPos.Y(), hitPos.X()) + gRandom->Gaus(0.0, resolution[layer] / r); + o2::math_utils::bringTo02Pi(alpha); + gloXYZ.SetX(r * std::cos(alpha)); + gloXYZ.SetY(r * std::sin(alpha)); + gloXYZ.SetZ(hitPos.Z() + gRandom->Gaus(0.0, resolution[layer])); + trkXYZ.SetX(r); + trkXYZ.SetY(0.f); + trkXYZ.SetZ(gloXYZ.Z()); + } + this->mMinR[layer] = std::min(this->mMinR[layer], r); + this->mMaxR[layer] = std::max(this->mMaxR[layer], r); + this->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, + std::array{trkXYZ.y(), trkXYZ.z()}, + std::array{resolution[layer] * resolution[layer], 0., resolution[layer] * resolution[layer]}); + this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); + this->addClusterExternalIndexToLayer(layer, hitCounter); + this->mClusterSize[layer].push_back(1); + MCCompLabel label{hit.GetTrackID(), static_cast(iEvent), 0}; + labels->addElement(hitCounter, label); + ++hitCounter; + } + trkHit->clear(); + + if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { + ++iRof; + for (unsigned int iLayer{0}; iLayer < this->mUnsortedClusters.size(); ++iLayer) { + this->mROFramesClusters[iLayer][iRof] = this->mUnsortedClusters[iLayer].size(); + } + } + this->mClusterLabels[0] = labels; + } + return nRofs; +} + +template +int TimeFrameGPU::loadROFrameData(gsl::span rofs, + gsl::span clusters, + gsl::span patterns, + const dataformats::MCTruthContainer* mcLabels, + float yPlaneMLOT) +{ + constexpr std::array startLayer{0, 3}; + GeometryTGeo* geom = GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + + const int nRofs = rofs.size(); + typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; + typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; + typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + rofOverlapTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + rofVertexLookupTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + rofMaskTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + } + rofOverlapTable.init(); + rofVertexLookupTable.init(); + rofMaskTable.init(); + this->setROFOverlapTable(std::move(rofOverlapTable)); + this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); + this->setMultiplicityCutMask(std::move(rofMaskTable)); + this->useMultiplictyMask(); + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mMinR[iLayer] = std::numeric_limits::max(); + this->mMaxR[iLayer] = std::numeric_limits::lowest(); + this->mROFramesClusters[iLayer].clear(); + this->mROFramesClusters[iLayer].resize(nRofs + 1, 0); + this->mUnsortedClusters[iLayer].clear(); + this->mTrackingFrameInfo[iLayer].clear(); + this->mClusterExternalIndices[iLayer].clear(); + this->mClusterSize[iLayer].clear(); + } + + std::array clusterCountPerLayer{}; + for (const auto& c : clusters) { + if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { + continue; + } + const int layer = startLayer[c.subDetID] + c.layer; + if (layer < 0 || layer >= nLayers) { + continue; + } + ++clusterCountPerLayer[layer]; + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mClusterSize[iLayer].reserve(clusterCountPerLayer[iLayer]); + } + + const uint8_t* pattPtr = patterns.data(); + const uint8_t* pattEnd = pattPtr + patterns.size(); + + for (size_t iRof{0}; iRof < rofs.size(); ++iRof) { + const auto& rof = rofs[iRof]; + const int first = rof.getFirstEntry(); + const int last = first + rof.getNEntries(); + + for (int clusterId{first}; clusterId < last; ++clusterId) { + if (clusterId < 0 || clusterId >= static_cast(clusters.size())) { + LOGP(warning, "Skipping out-of-range cluster id {} for ROF {}", clusterId, iRof); + continue; + } + + const auto& c = clusters[clusterId]; + if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { + continue; + } + + if (pattPtr + 2 > pattEnd) { + LOGP(error, "Pattern stream exhausted while decoding cluster {}", clusterId); + break; + } + const uint8_t* pattForCluster = pattPtr; + const int nBytes = (pattForCluster[0] * pattForCluster[1] + 7) / 8; + if (pattPtr + 2 + nBytes > pattEnd) { + LOGP(error, "Pattern stream truncated for cluster {}", clusterId); + break; + } + + const int layer = startLayer[c.subDetID] + c.layer; + if (layer < 0 || layer >= nLayers) { + LOGP(error, "Skipping cluster with invalid layer {} (subDetID {}, layer {})", layer, c.subDetID, c.layer); + pattPtr += 2 + nBytes; + continue; + } + + auto locXYZ = Clusterer::getClusterLocalCoordinates(c, pattForCluster, yPlaneMLOT); + pattPtr += 2 + nBytes; + + const auto gloXYZ = geom->getMatrixL2G(c.chipID) * locXYZ; + + float alpha{0.f}; + o2::math_utils::Point3D trkXYZ; + if (c.subDetID == 1) { + alpha = geom->getSensorRefAlphaMLOT(c.chipID); + trkXYZ = geom->getMatrixT2L(c.chipID - geom->getNumberOfActivePartsVD()) ^ locXYZ; + } else { + const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + alpha = std::atan2(gloXYZ.Y(), gloXYZ.X()); + o2::math_utils::bringTo02Pi(alpha); + trkXYZ.SetX(r); + trkXYZ.SetY(0.f); + trkXYZ.SetZ(gloXYZ.Z()); + } + + const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + this->mMinR[layer] = std::min(this->mMinR[layer], r); + this->mMaxR[layer] = std::max(this->mMaxR[layer], r); + + const float sigmaY2 = (c.subDetID == 0) + ? 0.25f * SegmentationChip::PitchRowVD * SegmentationChip::PitchRowVD + : 0.25f * SegmentationChip::PitchRowMLOT * SegmentationChip::PitchRowMLOT; + const float sigmaZ2 = (c.subDetID == 0) + ? 0.25f * SegmentationChip::PitchColVD * SegmentationChip::PitchColVD + : 0.25f * SegmentationChip::PitchColMLOT * SegmentationChip::PitchColMLOT; + + this->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, + std::array{trkXYZ.y(), trkXYZ.z()}, + std::array{sigmaY2, 0.f, sigmaZ2}); + this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); + this->addClusterExternalIndexToLayer(layer, clusterId); + this->mClusterSize[layer].push_back(std::clamp(static_cast(c.size), 0u, 255u)); + } + + for (unsigned int iL{0}; iL < this->mUnsortedClusters.size(); ++iL) { + this->mROFramesClusters[iL][iRof + 1] = this->mUnsortedClusters[iL].size(); + } + } + + for (auto i = 0; i < this->mNTrackletsPerCluster.size(); ++i) { + this->mNTrackletsPerCluster[i].resize(this->mUnsortedClusters[1].size()); + this->mNTrackletsPerClusterSum[i].resize(this->mUnsortedClusters[1].size() + 1); + } + + if (mcLabels != nullptr) { + this->mClusterLabels[0] = mcLabels; + } + + return nRofs; +} + +template +void TimeFrameGPU::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup) +{ + auto mcheader = new o2::dataformats::MCEventHeader; + mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); + + this->mPrimaryVertices.clear(); + this->mPrimaryVerticesLabels.clear(); + + int iRof{0}; + for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { + mcHeaderTree->GetEntry(iEvent); + o2::its::Vertex vertex; + vertex.setTimeStamp(o2::its::TimeEstBC{static_cast(iRof), 1}); + vertex.setXYZ(mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); + vertex.setNContributors(30); + vertex.setChi2(0.f); + this->addPrimaryVertex(vertex); + this->addPrimaryVertexLabel({o2::MCCompLabel{o2::MCCompLabel::maxTrackID(), static_cast(iEvent), 0, false}, 1.f}); + if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { + ++iRof; + } + } + this->updateROFVertexLookupTable(); +} + +template +void TimeFrameGPU::addTruthSeedingVertices(gsl::span rofs) +{ + LOGP(info, "TRK: using truth seeds as vertices from DigitizationContext"); + this->mPrimaryVertices.clear(); + this->mPrimaryVerticesLabels.clear(); + + const auto dc = o2::steer::DigitizationContext::loadFromFile("collisioncontext.root"); + const auto irs = dc->getEventRecords(); + o2::steer::MCKinematicsReader mcReader(dc); + + std::vector rofStartBC(rofs.size()); + for (size_t i = 0; i < rofs.size(); ++i) { + rofStartBC[i] = rofs[i].getBCData().toLong(); + } + + using Vertex = o2::its::Vertex; + struct VertInfo { + std::pmr::vector vertices; + std::pmr::vector srcs; + std::pmr::vector events; + }; + std::map vertMap; + + const int iSrc = 0; + auto eveId2colId = dc->getCollisionIndicesForSource(iSrc); + for (int iEve{0}; iEve < mcReader.getNEvents(iSrc); ++iEve) { + const auto& ir = irs[eveId2colId[iEve]]; + if (!ir.isDummy()) { + const auto& eve = mcReader.getMCEventHeader(iSrc, iEve); + const int64_t evBC = ir.toLong(); + auto it = std::upper_bound(rofStartBC.begin(), rofStartBC.end(), evBC); + if (it != rofStartBC.begin()) { + --it; + int rofId = static_cast(std::distance(rofStartBC.begin(), it)); + auto* mr = this->mMemoryPool.get(); + if (!vertMap.contains(rofId)) { + vertMap[rofId] = { + .vertices = std::pmr::vector(mr), + .srcs = std::pmr::vector(mr), + .events = std::pmr::vector(mr), + }; + } + Vertex vert; + vert.setTimeStamp(o2::its::TimeEstBC{static_cast(rofId), 1}); + vert.setNContributors(std::max(1L, std::ranges::count_if( + mcReader.getTracks(iSrc, iEve), + [](const auto& trk) { + return trk.isPrimary() && trk.GetPt() > 0.05 && std::abs(trk.GetEta()) < 1.1; + }))); + vert.setXYZ((float)eve.GetX(), (float)eve.GetY(), (float)eve.GetZ()); + vert.setChi2(1); + constexpr float cov = 50e-9f; + vert.setCov(cov, cov, cov, cov, cov, cov); + vertMap[rofId].vertices.push_back(vert); + vertMap[rofId].srcs.push_back(iSrc); + vertMap[rofId].events.push_back(iEve); + } + } + mcReader.releaseTracksForSourceAndEvent(iSrc, iEve); + } + + size_t nVerts{0}; + auto* mr = this->mMemoryPool.get(); + for (int iROF{0}; iROF < static_cast(rofs.size()); ++iROF) { + std::pmr::vector verts(mr); + std::pmr::vector> polls(mr); + if (vertMap.contains(iROF)) { + const auto& info = vertMap[iROF]; + verts = info.vertices; + nVerts += verts.size(); + for (size_t i{0}; i < verts.size(); ++i) { + o2::MCCompLabel lbl(o2::MCCompLabel::maxTrackID(), info.events[i], info.srcs[i], false); + polls.emplace_back(lbl, 1.f); + } + } + for (const auto& vert : verts) { + this->addPrimaryVertex(vert); + } + for (const auto& label : polls) { + this->addPrimaryVertexLabel(label); + } + } + this->updateROFVertexLookupTable(); + LOGP(info, "TRK truth seeding: {}/{} ROFs with {} vertices -> ={:.2f}", + vertMap.size(), rofs.size(), nVerts, + vertMap.empty() ? 0.f : static_cast(nVerts) / static_cast(vertMap.size())); +} + +template class TimeFrameGPU<11>; + +} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx index 8d5947aeca9bd..e870ee934816f 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx @@ -261,10 +261,10 @@ void TrackerACTS::clustersToTracks() double totalTime = 0.; LOG(info) << "==== TRK ACTS Tracking ===="; - LOG(info) << "Processing " << mTimeFrame->getNrof(0) << " ROFs with B = " << mBz << " T"; + LOG(info) << "Processing " << mTimeFrame->getNrof() << " ROFs with B = " << mBz << " T"; // Process each ROF - for (int iROF = 0; iROF < mTimeFrame->getNrof(0); ++iROF) { + for (int iROF = 0; iROF < mTimeFrame->getNrof(); ++iROF) { LOG(info) << "Processing ROF " << iROF; // Build space points mCurState = SpacePointBuilding; diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md index c3984ed5e7b50..c25266c7d1286 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md @@ -4,7 +4,7 @@ This document describes how to run the ALICE 3 global reconstruction workflow an ## Overview -The global reconstruction workflow performs track reconstruction from simulated hits, producing reconstructed tracks with MC truth labels. The workflow currently supports the track reconstruction from hits using the Cellular Automaton (CA) algorithm. The output is stored to a ROOT file for offline analysis (example of QA macro provided in `TRK/macros/test/CheckTracksCA.C`). +The global reconstruction workflow performs track reconstruction from simulated hits or TRK clusters, producing reconstructed tracks with MC truth labels. The workflow currently supports tracking using the Cellular Automaton (CA) algorithm. The output is stored to a ROOT file for offline analysis (example of QA macro provided in `TRK/macros/test/CheckTracksCA.C`). ## Quick Start @@ -16,7 +16,9 @@ o2-alice3-global-reconstruction-reco-workflow --tracking-from-hits-config config ### Command Line Options -- `--tracking-from-hits-config `: Path to tracking configuration JSON file (required) +- `--tracking-from-hits-config `: Path to tracking-from-hits configuration JSON file +- `--tracking-from-clusters-config `: Path to tracking-from-clusters configuration JSON file +- `--gpu-device `: Tracking device type (`1` CPU, `2` CUDA, `3` HIP) - `-b`: Batch mode (no GUI) - `--disable-root-output`: Skip writing tracks to ROOT file - `--help`: Show all available options diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h index 17c183dc1cb4c..98a5176d5db44 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h @@ -21,6 +21,7 @@ namespace o2::trk::global_reco_workflow o2::framework::WorkflowSpec getWorkflow(bool useMC, const std::string& hitRecoConfig, + const std::string& clusterRecoConfig, bool disableRootOutput = false, o2::gpu::gpudatatypes::DeviceType dType = o2::gpu::gpudatatypes::DeviceType::CPU); diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h index 304b32041c2dc..006bb4cbf5260 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h @@ -22,6 +22,7 @@ #include #include "ITStracking/BoundedAllocator.h" +#include "ITStracking/ExternalAllocator.h" #include "ITStracking/TrackingInterface.h" #include "GPUDataTypesConfig.h" @@ -39,6 +40,7 @@ class TrackerDPL : public framework::Task TrackerDPL(std::shared_ptr gr, bool isMC, const std::string& hitRecoConfig, + const std::string& clusterRecoConfig, gpu::gpudatatypes::DeviceType dType = gpu::gpudatatypes::DeviceType::CPU); ~TrackerDPL() override = default; void init(framework::InitContext& ic) final; @@ -54,16 +56,20 @@ class TrackerDPL : public framework::Task // std::unique_ptr mChainITS = nullptr; // std::shared_ptr mGGCCDBRequest; // ITSTrackingInterface mITSTrackingInterface; + bool mIsMC{true}; + gpu::gpudatatypes::DeviceType mDeviceType{gpu::gpudatatypes::DeviceType::CPU}; std::shared_ptr mMemoryPool; + std::shared_ptr mGPUAllocator; std::shared_ptr mTaskArena; nlohmann::json mHitRecoConfig; + nlohmann::json mClusterRecoConfig; TStopwatch mTimer; #ifdef O2_WITH_ACTS bool mUseACTS = false; #endif }; -framework::DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, gpu::gpudatatypes::DeviceType dType = gpu::gpudatatypes::DeviceType::CPU); +framework::DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, const std::string& clusterRecoConfig, gpu::gpudatatypes::DeviceType dType = gpu::gpudatatypes::DeviceType::CPU); } // namespace o2::trk #endif /* O2_TRK_TRACKERDPL */ diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx index ce745ff1ddc6c..024bd3b4425f8 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx @@ -19,14 +19,16 @@ namespace o2::trk::global_reco_workflow framework::WorkflowSpec getWorkflow(bool useMC, const std::string& hitRecoConfig, + const std::string& clusterRecoConfig, bool disableRootOutput, o2::gpu::gpudatatypes::DeviceType dtype) { framework::WorkflowSpec specs; - if (!hitRecoConfig.empty()) { - LOGP(info, "Using hit reco config from file {}", hitRecoConfig); - specs.emplace_back(o2::trk::getTrackerSpec(useMC, hitRecoConfig, dtype)); + if (!hitRecoConfig.empty() || !clusterRecoConfig.empty()) { + LOG_IF(info, !hitRecoConfig.empty()) << "Using hit reco config from file " << hitRecoConfig; + LOG_IF(info, !clusterRecoConfig.empty()) << "Using cluster reco config from file " << clusterRecoConfig; + specs.emplace_back(o2::trk::getTrackerSpec(useMC, hitRecoConfig, clusterRecoConfig, dtype)); if (!disableRootOutput) { specs.emplace_back(o2::trk::getTrackWriterSpec(useMC)); } diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index 7373622892525..cadb65ec413d8 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -10,8 +10,14 @@ // or submit itself to any jurisdiction. #include +#include +#include #include +#include +#include +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" #include "DetectorsBase/GeometryManager.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/Configuration.h" @@ -20,12 +26,19 @@ #include "Framework/ControlService.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" +#include "ITStracking/TrackingConfigParam.h" #include "SimulationDataFormat/MCEventHeader.h" #include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" #include "TRKSimulation/Hit.h" #include "ALICE3GlobalReconstruction/TimeFrame.h" +#ifdef TRK_HAS_GPU_TRACKING +#include "ALICE3GlobalReconstruction/TimeFrameGPU.h" +#include "ALICE3GlobalReconstruction/GPUExternalAllocator.h" +#include "ITStrackingGPU/TrackerTraitsGPU.h" +#endif #include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" #include @@ -41,17 +54,24 @@ namespace o2 using namespace framework; namespace trk { +using Vertex = o2::dataformats::Vertex>; TrackerDPL::TrackerDPL(std::shared_ptr gr, bool isMC, const std::string& hitRecoConfigFileName, + const std::string& clusterRecoConfigFileName, o2::gpu::gpudatatypes::DeviceType dType) { if (!hitRecoConfigFileName.empty()) { std::ifstream configFile(hitRecoConfigFileName); mHitRecoConfig = nlohmann::json::parse(configFile); } - + if (!clusterRecoConfigFileName.empty()) { + std::ifstream configFile(clusterRecoConfigFileName); + mClusterRecoConfig = nlohmann::json::parse(configFile); + } + mIsMC = isMC; + mDeviceType = dType; // mITSTrackingInterface.setTrackingMode(trMode); } @@ -78,156 +98,142 @@ void TrackerDPL::stop() std::vector TrackerDPL::createTrackingParamsFromConfig() { std::vector trackingParams; + auto loadTrackingParamsFromJson = [](std::vector& trackingParams, const nlohmann::json& paramConfigJson) { + for (const auto& paramConfig : paramConfigJson) { + o2::its::TrackingParameters params; - if (!mHitRecoConfig.contains("trackingparams") || !mHitRecoConfig["trackingparams"].is_array()) { - LOGP(fatal, "No trackingparams field found in configuration or it is not an array. Returning empty vector."); - return trackingParams; - } + // Parse integer parameters + if (paramConfig.contains("NLayers")) { + params.NLayers = paramConfig["NLayers"].get(); + } + if (paramConfig.contains("ZBins")) { + params.ZBins = paramConfig["ZBins"].get(); + } + if (paramConfig.contains("PhiBins")) { + params.PhiBins = paramConfig["PhiBins"].get(); + } + if (paramConfig.contains("ClusterSharing")) { + params.ClusterSharing = paramConfig["ClusterSharing"].get(); + } + if (paramConfig.contains("MinTrackLength")) { + params.MinTrackLength = paramConfig["MinTrackLength"].get(); + } + if (paramConfig.contains("ReseedIfShorter")) { + params.ReseedIfShorter = paramConfig["ReseedIfShorter"].get(); + } + if (paramConfig.contains("StartLayerMask")) { + params.StartLayerMask = paramConfig["StartLayerMask"].get(); + } - for (const auto& paramConfig : mHitRecoConfig["trackingparams"]) { - o2::its::TrackingParameters params; + // Parse float parameters + if (paramConfig.contains("NSigmaCut")) { + params.NSigmaCut = paramConfig["NSigmaCut"].get(); + } + if (paramConfig.contains("PVres")) { + params.PVres = paramConfig["PVres"].get(); + } + if (paramConfig.contains("TrackletMinPt")) { + params.TrackletMinPt = paramConfig["TrackletMinPt"].get(); + } + if (paramConfig.contains("CellDeltaTanLambdaSigma")) { + params.CellDeltaTanLambdaSigma = paramConfig["CellDeltaTanLambdaSigma"].get(); + } + if (paramConfig.contains("MaxChi2ClusterAttachment")) { + params.MaxChi2ClusterAttachment = paramConfig["MaxChi2ClusterAttachment"].get(); + } + if (paramConfig.contains("MaxChi2NDF")) { + params.MaxChi2NDF = paramConfig["MaxChi2NDF"].get(); + } - // Parse integer parameters - if (paramConfig.contains("NLayers")) { - params.NLayers = paramConfig["NLayers"].get(); - } - if (paramConfig.contains("ZBins")) { - params.ZBins = paramConfig["ZBins"].get(); - } - if (paramConfig.contains("PhiBins")) { - params.PhiBins = paramConfig["PhiBins"].get(); - } - if (paramConfig.contains("ClusterSharing")) { - params.ClusterSharing = paramConfig["ClusterSharing"].get(); - } - if (paramConfig.contains("MinTrackLength")) { - params.MinTrackLength = paramConfig["MinTrackLength"].get(); - } - if (paramConfig.contains("ReseedIfShorter")) { - params.ReseedIfShorter = paramConfig["ReseedIfShorter"].get(); - } - if (paramConfig.contains("StartLayerMask")) { - params.StartLayerMask = paramConfig["StartLayerMask"].get(); - } + // Parse boolean parameters + if (paramConfig.contains("UseDiamond")) { + params.UseDiamond = paramConfig["UseDiamond"].get(); + } + if (paramConfig.contains("AllowSharingFirstCluster")) { + params.AllowSharingFirstCluster = paramConfig["AllowSharingFirstCluster"].get(); + } + if (paramConfig.contains("RepeatRefitOut")) { + params.RepeatRefitOut = paramConfig["RepeatRefitOut"].get(); + } + if (paramConfig.contains("ShiftRefToCluster")) { + params.ShiftRefToCluster = paramConfig["ShiftRefToCluster"].get(); + } + if (paramConfig.contains("PerPrimaryVertexProcessing")) { + params.PerPrimaryVertexProcessing = paramConfig["PerPrimaryVertexProcessing"].get(); + } + if (paramConfig.contains("SaveTimeBenchmarks")) { + params.SaveTimeBenchmarks = paramConfig["SaveTimeBenchmarks"].get(); + } + if (paramConfig.contains("DoUPCIteration")) { + params.DoUPCIteration = paramConfig["DoUPCIteration"].get(); + } + if (paramConfig.contains("FataliseUponFailure")) { + params.FataliseUponFailure = paramConfig["FataliseUponFailure"].get(); + } + if (paramConfig.contains("createArtefactLabels")) { + params.createArtefactLabels = paramConfig["createArtefactLabels"].get(); + } + if (paramConfig.contains("PrintMemory")) { + params.PrintMemory = paramConfig["PrintMemory"].get(); + } + if (paramConfig.contains("DropTFUponFailure")) { + params.DropTFUponFailure = paramConfig["DropTFUponFailure"].get(); + } - // Parse float parameters - if (paramConfig.contains("NSigmaCut")) { - params.NSigmaCut = paramConfig["NSigmaCut"].get(); - } - if (paramConfig.contains("PVres")) { - params.PVres = paramConfig["PVres"].get(); - } - if (paramConfig.contains("TrackletMinPt")) { - params.TrackletMinPt = paramConfig["TrackletMinPt"].get(); - } - if (paramConfig.contains("CellDeltaTanLambdaSigma")) { - params.CellDeltaTanLambdaSigma = paramConfig["CellDeltaTanLambdaSigma"].get(); - } - if (paramConfig.contains("MaxChi2ClusterAttachment")) { - params.MaxChi2ClusterAttachment = paramConfig["MaxChi2ClusterAttachment"].get(); - } - if (paramConfig.contains("MaxChi2NDF")) { - params.MaxChi2NDF = paramConfig["MaxChi2NDF"].get(); - } - // if (paramConfig.contains("TrackFollowerNSigmaCutZ")) { - // params.TrackFollowerNSigmaCutZ = paramConfig["TrackFollowerNSigmaCutZ"].get(); - // } - // if (paramConfig.contains("TrackFollowerNSigmaCutPhi")) { - // params.TrackFollowerNSigmaCutPhi = paramConfig["TrackFollowerNSigmaCutPhi"].get(); - // } - - // Parse boolean parameters - if (paramConfig.contains("UseDiamond")) { - params.UseDiamond = paramConfig["UseDiamond"].get(); - } - if (paramConfig.contains("AllowSharingFirstCluster")) { - params.AllowSharingFirstCluster = paramConfig["AllowSharingFirstCluster"].get(); - } - if (paramConfig.contains("RepeatRefitOut")) { - params.RepeatRefitOut = paramConfig["RepeatRefitOut"].get(); - } - if (paramConfig.contains("ShiftRefToCluster")) { - params.ShiftRefToCluster = paramConfig["ShiftRefToCluster"].get(); - } - // if (paramConfig.contains("FindShortTracks")) { - // params.FindShortTracks = paramConfig["FindShortTracks"].get(); - // } - if (paramConfig.contains("PerPrimaryVertexProcessing")) { - params.PerPrimaryVertexProcessing = paramConfig["PerPrimaryVertexProcessing"].get(); - } - if (paramConfig.contains("SaveTimeBenchmarks")) { - params.SaveTimeBenchmarks = paramConfig["SaveTimeBenchmarks"].get(); - } - if (paramConfig.contains("DoUPCIteration")) { - params.DoUPCIteration = paramConfig["DoUPCIteration"].get(); - } - if (paramConfig.contains("FataliseUponFailure")) { - params.FataliseUponFailure = paramConfig["FataliseUponFailure"].get(); - } - // if (paramConfig.contains("UseTrackFollower")) { - // params.UseTrackFollower = paramConfig["UseTrackFollower"].get(); - // } - // if (paramConfig.contains("UseTrackFollowerTop")) { - // params.UseTrackFollowerTop = paramConfig["UseTrackFollowerTop"].get(); - // } - // if (paramConfig.contains("UseTrackFollowerBot")) { - // params.UseTrackFollowerBot = paramConfig["UseTrackFollowerBot"].get(); - // } - // if (paramConfig.contains("UseTrackFollowerMix")) { - // params.UseTrackFollowerMix = paramConfig["UseTrackFollowerMix"].get(); - // } - if (paramConfig.contains("createArtefactLabels")) { - params.createArtefactLabels = paramConfig["createArtefactLabels"].get(); - } - if (paramConfig.contains("PrintMemory")) { - params.PrintMemory = paramConfig["PrintMemory"].get(); - } - if (paramConfig.contains("DropTFUponFailure")) { - params.DropTFUponFailure = paramConfig["DropTFUponFailure"].get(); - } + // Parse vector parameters + if (paramConfig.contains("LayerZ")) { + params.LayerZ = paramConfig["LayerZ"].get>(); + } + if (paramConfig.contains("LayerRadii")) { + params.LayerRadii = paramConfig["LayerRadii"].get>(); + } + if (paramConfig.contains("LayerxX0")) { + params.LayerxX0 = paramConfig["LayerxX0"].get>(); + } + if (paramConfig.contains("LayerResolution")) { + params.LayerResolution = paramConfig["LayerResolution"].get>(); + } + if (paramConfig.contains("SystErrorY2")) { + params.SystErrorY2 = paramConfig["SystErrorY2"].get>(); + } + if (paramConfig.contains("SystErrorZ2")) { + params.SystErrorZ2 = paramConfig["SystErrorZ2"].get>(); + } + if (paramConfig.contains("MinPt")) { + params.MinPt = paramConfig["MinPt"].get>(); + } - // Parse vector parameters - if (paramConfig.contains("LayerZ")) { - params.LayerZ = paramConfig["LayerZ"].get>(); - } - if (paramConfig.contains("LayerRadii")) { - params.LayerRadii = paramConfig["LayerRadii"].get>(); - } - if (paramConfig.contains("LayerxX0")) { - params.LayerxX0 = paramConfig["LayerxX0"].get>(); - } - if (paramConfig.contains("LayerResolution")) { - params.LayerResolution = paramConfig["LayerResolution"].get>(); - } - if (paramConfig.contains("SystErrorY2")) { - params.SystErrorY2 = paramConfig["SystErrorY2"].get>(); - } - if (paramConfig.contains("SystErrorZ2")) { - params.SystErrorZ2 = paramConfig["SystErrorZ2"].get>(); - } - if (paramConfig.contains("MinPt")) { - params.MinPt = paramConfig["MinPt"].get>(); - } + // Parse Diamond array + if (paramConfig.contains("Diamond") && paramConfig["Diamond"].is_array() && paramConfig["Diamond"].size() == 3) { + params.Diamond[0] = paramConfig["Diamond"][0].get(); + params.Diamond[1] = paramConfig["Diamond"][1].get(); + params.Diamond[2] = paramConfig["Diamond"][2].get(); + } - // Parse Diamond array - if (paramConfig.contains("Diamond") && paramConfig["Diamond"].is_array() && paramConfig["Diamond"].size() == 3) { - params.Diamond[0] = paramConfig["Diamond"][0].get(); - params.Diamond[1] = paramConfig["Diamond"][1].get(); - params.Diamond[2] = paramConfig["Diamond"][2].get(); - } + // Parse size_t parameter + if (paramConfig.contains("MaxMemory")) { + params.MaxMemory = paramConfig["MaxMemory"].get(); + } - // Parse size_t parameter - if (paramConfig.contains("MaxMemory")) { - params.MaxMemory = paramConfig["MaxMemory"].get(); - } + // Parse CorrType enum + if (paramConfig.contains("CorrType")) { + int corrTypeInt = paramConfig["CorrType"].get(); + params.CorrType = static_cast::MatCorrType>(corrTypeInt); + } - // Parse CorrType enum - if (paramConfig.contains("CorrType")) { - int corrTypeInt = paramConfig["CorrType"].get(); - params.CorrType = static_cast::MatCorrType>(corrTypeInt); + trackingParams.push_back(params); } + }; + + - trackingParams.push_back(params); + if (mHitRecoConfig.contains("trackingparams") && mHitRecoConfig["trackingparams"].is_array()) { + loadTrackingParamsFromJson(trackingParams, mHitRecoConfig["trackingparams"]); + } else if (mClusterRecoConfig.contains("trackingparams") && mClusterRecoConfig["trackingparams"].is_array()) { + loadTrackingParamsFromJson(trackingParams, mClusterRecoConfig["trackingparams"]); + } else { + LOGP(fatal, "No trackingparams field found in configuration or it is not an array. Returning empty vector."); + return trackingParams; } LOGP(info, "Loaded {} tracking parameter sets from configuration", trackingParams.size()); @@ -236,102 +242,215 @@ std::vector TrackerDPL::createTrackingParamsFromCon void TrackerDPL::run(ProcessingContext& pc) { + if (mMemoryPool.get() == nullptr) { + mMemoryPool = std::make_shared(); + } + if (mTaskArena.get() == nullptr) { + mTaskArena = std::make_shared(1); /// TODO: make it configurable + } + + // Create tracking parameters from config and set them in the time frame + auto trackingParams = createTrackingParamsFromConfig(); + auto cput = mTimer.CpuTime(); auto realt = mTimer.RealTime(); mTimer.Start(false); - if (!mHitRecoConfig.empty()) { - TFile hitsFile(mHitRecoConfig["inputfiles"]["hits"].get().c_str(), "READ"); - TFile mcHeaderFile(mHitRecoConfig["inputfiles"]["mcHeader"].get().c_str(), "READ"); - TTree* hitsTree = hitsFile.Get("o2sim"); - std::vector* trkHit = nullptr; - hitsTree->SetBranchAddress("TRKHit", &trkHit); - - TTree* mcHeaderTree = mcHeaderFile.Get("o2sim"); - auto mcheader = new o2::dataformats::MCEventHeader; - mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); - - o2::base::GeometryManager::loadGeometry(mHitRecoConfig["inputfiles"]["geometry"].get().c_str(), false, true); - auto* gman = o2::trk::GeometryTGeo::Instance(); - - const Long64_t nEvents{hitsTree->GetEntries()}; - LOGP(info, "Starting reconstruction from hits for {} events", nEvents); - - if (mMemoryPool.get() == nullptr) { - mMemoryPool = std::make_shared(); - } - if (mTaskArena.get() == nullptr) { - mTaskArena = std::make_shared(1); /// TODO: make it configurable - } + const bool useGPU = mDeviceType != o2::gpu::gpudatatypes::DeviceType::CPU; +#ifndef TRK_HAS_GPU_TRACKING + if (useGPU) { + LOGP(fatal, "TRK GPU tracking was requested but this build has no TRK GPU tracking backend"); + } +#else +#ifdef TRK_HAS_CUDA_TRACKING + if (useGPU && mDeviceType != o2::gpu::gpudatatypes::DeviceType::CUDA) { + LOGP(fatal, "This build provides the CUDA TRK tracking backend only, but device type {} was requested", static_cast(mDeviceType)); + } +#elif defined(TRK_HAS_HIP_TRACKING) + if (useGPU && mDeviceType != o2::gpu::gpudatatypes::DeviceType::HIP) { + LOGP(fatal, "This build provides the HIP TRK tracking backend only, but device type {} was requested", static_cast(mDeviceType)); + } +#endif +#endif - o2::trk::TimeFrame<11> timeFrame; - o2::its::TrackerTraits<11> itsTrackerTraits; - o2::its::Tracker<11> itsTracker(&itsTrackerTraits); + auto runTracking = [&](auto& timeFrame, auto& trackerTraits) { + o2::its::Tracker<11> itsTracker(&trackerTraits); timeFrame.setMemoryPool(mMemoryPool); - itsTrackerTraits.setMemoryPool(mMemoryPool); - itsTrackerTraits.setNThreads(mTaskArena->max_concurrency(), mTaskArena); - itsTrackerTraits.adoptTimeFrame(static_cast*>(&timeFrame)); - itsTrackerTraits.setBz(mHitRecoConfig["geometry"]["bz"].get()); - auto field = new field::MagneticField("ALICE3Mag", "ALICE 3 Magnetic Field", mHitRecoConfig["geometry"]["bz"].get() / 5.f, 0.0, o2::field::MagFieldParam::k5kGUniform); - TGeoGlobalMagField::Instance()->SetField(field); - TGeoGlobalMagField::Instance()->Lock(); + trackerTraits.setMemoryPool(mMemoryPool); + trackerTraits.setNThreads(mTaskArena->max_concurrency(), mTaskArena); + trackerTraits.adoptTimeFrame(static_cast*>(&timeFrame)); itsTracker.adoptTimeFrame(timeFrame); + trackerTraits.updateTrackingParameters(trackingParams); + + int nRofs{0}; + if (!mHitRecoConfig.empty()) { + TFile hitsFile(mHitRecoConfig["inputfiles"]["hits"].get().c_str(), "READ"); + TFile mcHeaderFile(mHitRecoConfig["inputfiles"]["mcHeader"].get().c_str(), "READ"); + TTree* hitsTree = hitsFile.Get("o2sim"); + std::vector* trkHit = nullptr; + hitsTree->SetBranchAddress("TRKHit", &trkHit); + + TTree* mcHeaderTree = mcHeaderFile.Get("o2sim"); + auto mcheader = new o2::dataformats::MCEventHeader; + mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); + + o2::base::GeometryManager::loadGeometry(mHitRecoConfig["inputfiles"]["geometry"].get().c_str(), false, true); + auto* gman = o2::trk::GeometryTGeo::Instance(); + + const Long64_t nEvents{hitsTree->GetEntries()}; + LOGP(info, "Starting {} reconstruction from hits for {} events", trackerTraits.getName(), nEvents); + + trackerTraits.setBz(mHitRecoConfig["geometry"]["bz"].get()); + auto field = new field::MagneticField("ALICE3Mag", "ALICE 3 Magnetic Field", mHitRecoConfig["geometry"]["bz"].get() / 5.f, 0.0, o2::field::MagFieldParam::k5kGUniform); + TGeoGlobalMagField::Instance()->SetField(field); + TGeoGlobalMagField::Instance()->Lock(); + + nRofs = timeFrame.loadROFsFromHitTree(hitsTree, gman, mHitRecoConfig); + const int inROFpileup{mHitRecoConfig.contains("inROFpileup") ? mHitRecoConfig["inROFpileup"].get() : 1}; + timeFrame.getPrimaryVerticesFromMC(mcHeaderTree, nRofs, nEvents, inROFpileup); + } else if (!mClusterRecoConfig.empty()) { + LOGP(info, "Starting {} reconstruction from clusters", trackerTraits.getName()); + + o2::base::GeometryManager::loadGeometry(mClusterRecoConfig["inputfiles"]["geometry"].get().c_str(), false, true); + o2::trk::GeometryTGeo::Instance(); + + trackerTraits.setBz(mClusterRecoConfig["geometry"]["bz"].get()); + auto field = new field::MagneticField("ALICE3Mag", "ALICE 3 Magnetic Field", mClusterRecoConfig["geometry"]["bz"].get() / 5.f, 0.0, o2::field::MagFieldParam::k5kGUniform); + TGeoGlobalMagField::Instance()->SetField(field); + TGeoGlobalMagField::Instance()->Lock(); + + constexpr int nLayers{11}; + std::array, nLayers> layerClusters; + std::array, nLayers> layerPatterns; + std::array, nLayers> layerROFs; + std::array*, nLayers> layerLabels{}; + + size_t nInputRofs{0}; + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + layerClusters[iLayer] = pc.inputs().get>(std::format("compClusters_{}", iLayer)); + layerPatterns[iLayer] = pc.inputs().get>(std::format("patterns_{}", iLayer)); + layerROFs[iLayer] = pc.inputs().get>(std::format("ROframes_{}", iLayer)); + nInputRofs = std::max(nInputRofs, layerROFs[iLayer].size()); + if (mIsMC) { + layerLabels[iLayer] = pc.inputs().get*>(std::format("trkmclabels_{}", iLayer)).release(); + } + } - const int nRofs = timeFrame.loadROFsFromHitTree(hitsTree, gman, mHitRecoConfig); - const int inROFpileup{mHitRecoConfig.contains("inROFpileup") ? mHitRecoConfig["inROFpileup"].get() : 1}; - - // Add primary vertices from MC headers for each ROF - timeFrame.getPrimaryVerticesFromMC(mcHeaderTree, nRofs, nEvents, inROFpileup); - // Create tracking parameters from config and set them in the time frame - auto trackingParams = createTrackingParamsFromConfig(); + std::vector rofRecords; + std::vector compClusters; + std::vector patterns; + dataformats::MCTruthContainer labels; + std::array, nLayers> patternOffsets; + rofRecords.reserve(nInputRofs); + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + compClusters.reserve(compClusters.size() + layerClusters[iLayer].size()); + patterns.reserve(patterns.size() + layerPatterns[iLayer].size()); + + auto& offsets = patternOffsets[iLayer]; + offsets.reserve(layerClusters[iLayer].size() + 1); + offsets.push_back(0); + size_t patternOffset{0}; + for (size_t iCluster = 0; iCluster < layerClusters[iLayer].size(); ++iCluster) { + if (patternOffset + 2 > layerPatterns[iLayer].size()) { + LOGP(error, "Pattern stream exhausted while indexing TRK layer {} cluster {}", iLayer, iCluster); + offsets.push_back(layerPatterns[iLayer].size()); + continue; + } + const auto* pattForCluster = layerPatterns[iLayer].data() + patternOffset; + const size_t nBytes = (pattForCluster[0] * pattForCluster[1] + 7) / 8; + patternOffset = std::min(patternOffset + 2 + nBytes, layerPatterns[iLayer].size()); + offsets.push_back(patternOffset); + } + } - itsTrackerTraits.updateTrackingParameters(trackingParams); + for (size_t iRof = 0; iRof < nInputRofs; ++iRof) { + o2::trk::ROFRecord mergedROF; + mergedROF.setFirstEntry(compClusters.size()); + mergedROF.setROFrame(iRof); + + bool hasBCData{false}; + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + if (iRof >= layerROFs[iLayer].size()) { + continue; + } + const auto& layerROF = layerROFs[iLayer][iRof]; + if (!hasBCData) { + mergedROF.setBCData(layerROF.getBCData()); + hasBCData = true; + } + + const auto first = layerROF.getFirstEntry(); + const auto last = first + layerROF.getNEntries(); + if (first < 0 || last < first || last >= static_cast(patternOffsets[iLayer].size())) { + LOGP(warning, "Skipping TRK clusters for invalid range [{}, {}) on layer {}", first, last, iLayer); + continue; + } + const auto patternFirst = patternOffsets[iLayer][first]; + const auto patternLast = patternOffsets[iLayer][last]; + for (int iCluster = first; iCluster < last; ++iCluster) { + if (iCluster < 0 || iCluster >= static_cast(layerClusters[iLayer].size())) { + LOGP(warning, "Skipping out-of-range TRK cluster {} on layer {}", iCluster, iLayer); + continue; + } + const auto mergedClusterIndex = compClusters.size(); + compClusters.push_back(layerClusters[iLayer][iCluster]); + if (mIsMC) { + if (layerLabels[iLayer] != nullptr) { + labels.addElements(mergedClusterIndex, layerLabels[iLayer]->getLabels(iCluster)); + } else { + labels.addNoLabelIndex(mergedClusterIndex); + } + } + } + patterns.insert(patterns.end(), layerPatterns[iLayer].begin() + patternFirst, layerPatterns[iLayer].begin() + patternLast); + } + + mergedROF.setNEntries(compClusters.size() - mergedROF.getFirstEntry()); + rofRecords.push_back(mergedROF); + } -#ifdef O2_WITH_ACTS - if (mUseACTS) { - LOG(info) << "Running the tracking with ACTS"; - o2::trk::TrackerACTS<11> actsTracker; - actsTracker.setBz(mHitRecoConfig["geometry"]["bz"].get()); - actsTracker.adoptTimeFrame(timeFrame); - actsTracker.clustersToTracks(); + const float yPlaneMLOT = 0.0010f; + nRofs = timeFrame.loadROFrameData(rofRecords, compClusters, patterns, mIsMC ? &labels : nullptr, yPlaneMLOT); + timeFrame.addTruthSeedingVertices(rofRecords); } -#endif const auto trackingLoopStart = std::chrono::steady_clock::now(); for (size_t iter{0}; iter < trackingParams.size(); ++iter) { LOGP(info, "{}", trackingParams[iter].asString()); - timeFrame.initialise(iter, trackingParams[iter], 11, false); - itsTrackerTraits.computeLayerTracklets(iter, -1); + trackerTraits.initialiseTimeFrame(iter); + trackerTraits.computeLayerTracklets(iter, -1); LOGP(info, "Number of tracklets in iteration {}: {}", iter, timeFrame.getNumberOfTracklets()); - itsTrackerTraits.computeLayerCells(iter); + trackerTraits.computeLayerCells(iter); LOGP(info, "Number of cells in iteration {}: {}", iter, timeFrame.getNumberOfCells()); - itsTrackerTraits.findCellsNeighbours(iter); + trackerTraits.findCellsNeighbours(iter); LOGP(info, "Number of cell neighbours in iteration {}: {}", iter, timeFrame.getNumberOfNeighbours()); - itsTrackerTraits.findRoads(iter); - LOGP(info, "Number of tracks in iteration {}: {}", iter, timeFrame.getNumberOfTracks()); + trackerTraits.findRoads(iter); + LOGP(info, "Number of roads in iteration {}: {}", iter, timeFrame.getNumberOfTracks()); } const auto trackingLoopElapsedMs = std::chrono::duration_cast(std::chrono::steady_clock::now() - trackingLoopStart).count(); LOGP(info, "Tracking iterations block took {} ms", trackingLoopElapsedMs); - itsTracker.computeTracksMClabels(); + if (mIsMC) { + itsTracker.computeTracksMClabels(); + } - // Collect tracks and labels (flat vectors in the new interface) const auto& tracks = timeFrame.getTracks(); const auto& labels = timeFrame.getTracksLabel(); - - // Copy to output vectors (TrackITSExt -> TrackITS slicing for output compatibility) std::vector allTracks(tracks.begin(), tracks.end()); - std::vector allLabels(labels.begin(), labels.end()); + std::vector allLabels; int totalTracks = allTracks.size(); int goodTracks = 0; int fakeTracks = 0; - for (const auto& label : allLabels) { - if (label.isFake()) { - fakeTracks++; - } else { - goodTracks++; + if (mIsMC) { + allLabels.assign(labels.begin(), labels.end()); + for (const auto& label : allLabels) { + if (label.isFake()) { + ++fakeTracks; + } else { + ++goodTracks; + } } } @@ -340,16 +459,34 @@ void TrackerDPL::run(ProcessingContext& pc) LOGP(info, "Good tracks: {} ({:.1f}%)", goodTracks, totalTracks > 0 ? 100.0 * goodTracks / totalTracks : 0); LOGP(info, "Fake tracks: {} ({:.1f}%)", fakeTracks, totalTracks > 0 ? 100.0 * fakeTracks / totalTracks : 0); - // Stream tracks and labels to DPL output pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKS", 0}, allTracks); - pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKSMCTR", 0}, allLabels); + if (mIsMC) { + pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKSMCTR", 0}, allLabels); + } - LOGP(info, "Tracks and MC labels streamed to output"); + LOGP(info, "Tracks{} streamed to output", mIsMC ? " and MC labels" : ""); + }; - pc.services().get().endOfStream(); - pc.services().get().readyToQuit(framework::QuitRequest::Me); +#ifdef TRK_HAS_GPU_TRACKING + if (useGPU) { + o2::trk::TimeFrameGPU<11> timeFrame; + o2::its::TrackerTraitsGPU<11> itsTrackerTraits; + if (!mGPUAllocator) { + mGPUAllocator = std::make_shared(); + } + timeFrame.setFrameworkAllocator(mGPUAllocator.get()); + runTracking(timeFrame, itsTrackerTraits); + } else +#endif + { + o2::trk::TimeFrame<11> timeFrame; + o2::its::TrackerTraits<11> itsTrackerTraits; + runTracking(timeFrame, itsTrackerTraits); } + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(framework::QuitRequest::Me); + mTimer.Stop(); LOGP(info, "CPU Reconstruction time for this TF {} s (cpu), {} s (wall)", mTimer.CpuTime() - cput, mTimer.RealTime() - realt); } @@ -364,7 +501,7 @@ void TrackerDPL::endOfStream(EndOfStreamContext& ec) LOGF(info, "TRK CA-Tracker total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, o2::gpu::gpudatatypes::DeviceType dType) +DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, const std::string& clusterRecoConfig, o2::gpu::gpudatatypes::DeviceType dType) { std::vector inputs; std::vector outputs; @@ -379,7 +516,9 @@ DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, o true); if (!hitRecoConfig.empty()) { - outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); + if (useMC) { + outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); + } return DataProcessorSpec{ "trk-hits-tracker", {}, @@ -387,6 +526,7 @@ DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, o AlgorithmSpec{adaptFromTask(ggRequest, useMC, hitRecoConfig, + clusterRecoConfig, dType)}, Options{ConfigParamSpec{"max-loops", VariantType::Int, 1, {"max number of loops"}} #ifdef O2_WITH_ACTS @@ -398,16 +538,22 @@ DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, o inputs.emplace_back("dummy", "TRK", "DUMMY", 0, Lifetime::Timeframe); - constexpr bool expectClusterInputs = false; - if (expectClusterInputs) { + /// Keep momentarily both the hit-based and cluster-based reconstruction in the code, but if a cluster reco config is provided, we assume that the input will be clusters and not hits, and we set the inputs accordingly. This is to avoid having to change the workflow too much for the moment, but ideally we should drop the hit-based reconstruction asap. + if (!clusterRecoConfig.empty()) { inputs.pop_back(); - inputs.emplace_back("compClusters", "TRK", "COMPCLUSTERS", 0, Lifetime::Timeframe); - inputs.emplace_back("patterns", "TRK", "PATTERNS", 0, Lifetime::Timeframe); - inputs.emplace_back("ROframes", "TRK", "CLUSTERSROF", 0, Lifetime::Timeframe); + constexpr int nLayers{11}; + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + inputs.emplace_back(std::format("compClusters_{}", iLayer), "TRK", "COMPCLUSTERS", iLayer, Lifetime::Timeframe); + inputs.emplace_back(std::format("patterns_{}", iLayer), "TRK", "PATTERNS", iLayer, Lifetime::Timeframe); + inputs.emplace_back(std::format("ROframes_{}", iLayer), "TRK", "CLUSTERSROF", iLayer, Lifetime::Timeframe); + if (useMC) { + inputs.emplace_back(std::format("trkmclabels_{}", iLayer), "TRK", "CLUSTERSMCTR", iLayer, Lifetime::Timeframe); + } + } } // inputs.emplace_back("itscldict", "TRK", "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec("ITS/Calib/ClusterDictionary")); - // inputs.emplace_back("TRK_almiraparam", "TRK", "ALMIRAPARAM", 0, Lifetime::Condition, ccdbParamSpec("TRK/Config/AlmiraParam")); + // inputs.emplace_back("itsalppar", "TRK", "ALPIDEPARAM", 0, Lifetime::Condition, ccdbParamSpec("ITS/Config/AlpideParam")); // outputs.emplace_back("TRK", "TRACKCLSID", 0, Lifetime::Timeframe); // outputs.emplace_back("TRK", "TRKTrackROF", 0, Lifetime::Timeframe); @@ -416,11 +562,9 @@ DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, o // outputs.emplace_back("TRK", "IRFRAMES", 0, Lifetime::Timeframe); if (useMC) { - // inputs.emplace_back("trkmclabels", "TRK", "CLUSTERSMCTR", 0, Lifetime::Timeframe); - // inputs.emplace_back("TRKMC2ROframes", "TRK", "CLUSTERSMC2ROF", 0, Lifetime::Timeframe); // outputs.emplace_back("TRK", "VERTICESMCTR", 0, Lifetime::Timeframe); // outputs.emplace_back("TRK", "VERTICESMCPUR", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); + outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); // outputs.emplace_back("TRK", "TRKTrackMC2ROF", 0, Lifetime::Timeframe); } @@ -431,6 +575,7 @@ DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, o AlgorithmSpec{adaptFromTask(ggRequest, useMC, hitRecoConfig, + clusterRecoConfig, dType)}, Options{}}; } diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx index 0549e827e2f5d..7e9950f4def2e 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx @@ -16,6 +16,7 @@ #include "Framework/ConfigContext.h" #include "Framework/CompletionPolicyHelpers.h" +#include #include using namespace o2::framework; @@ -36,6 +37,7 @@ void customize(std::vector& workflowOptions) {"disable-root-output", VariantType::Bool, false, {"do not write output root files"}}, {"disable-mc", VariantType::Bool, false, {"disable MC propagation even if available"}}, {"tracking-from-hits-config", VariantType::String, "", {"JSON file with tracking from hits configuration"}}, + {"tracking-from-clusters-config", VariantType::String, "", {"JSON file with tracking from clusters configuration"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, {"gpu-device", VariantType::Int, 1, {"use gpu device: CPU=1,CUDA=2,HIP=3 (default: CPU)"}}}; std::swap(workflowOptions, options); @@ -48,11 +50,16 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { auto useMC = !configcontext.options().get("disable-mc"); auto hitRecoConfig = configcontext.options().get("tracking-from-hits-config"); + auto clusterRecoConfig = configcontext.options().get("tracking-from-clusters-config"); auto gpuDevice = static_cast(configcontext.options().get("gpu-device")); auto disableRootOutput = configcontext.options().get("disable-root-output"); o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); + if (hitRecoConfig.empty() && clusterRecoConfig.empty()) { + throw std::invalid_argument("no reconstruction input configured: provide either --tracking-from-hits-config or --tracking-from-clusters-config "); + } + o2::conf::ConfigurableParam::writeINI("o2alice3globalrecoflow_configuration.ini"); - return o2::trk::global_reco_workflow::getWorkflow(useMC, hitRecoConfig, disableRootOutput, gpuDevice); + return o2::trk::global_reco_workflow::getWorkflow(useMC, hitRecoConfig, clusterRecoConfig, disableRootOutput, gpuDevice); } diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h index bcd95155f533f..d96b2ecc5af00 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h @@ -28,6 +28,7 @@ #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" #include "TRKBase/Specs.h" +#include "MathUtils/Cartesian.h" #include #include #include @@ -48,6 +49,7 @@ class Clusterer using Digit = o2::itsmft::Digit; using DigROFRecord = o2::itsmft::ROFRecord; + using DigMC2ROFRecord = o2::itsmft::MC2ROFRecord; using ClusterTruth = o2::dataformats::MCTruthContainer; using ConstDigitTruth = o2::dataformats::ConstMCTruthContainerView; using Label = o2::MCCompLabel; @@ -166,7 +168,12 @@ class Clusterer std::vector& patterns, std::vector& clusterROFs, const ConstDigitTruth* digitLabels = nullptr, - ClusterTruth* clusterLabels = nullptr); + ClusterTruth* clusterLabels = nullptr, + gsl::span digMC2ROFs = {}, + std::vector* clusterMC2ROFs = nullptr); + + static o2::math_utils::Point3D getClusterLocalCoordinates(const Cluster& cluster, const uint8_t* patt, + float yPlaneMLOT = 0.f) noexcept; protected: int mNHugeClus = 0; diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h index 5d68193e5e375..37a148aa78afb 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h @@ -35,7 +35,9 @@ class ClustererACTS : public Clusterer std::vector& patterns, std::vector& clusterROFs, const ConstDigitTruth* digitLabels = nullptr, - ClusterTruth* clusterLabels = nullptr) override; + ClusterTruth* clusterLabels = nullptr, + gsl::span digMC2ROFs = {}, + std::vector* clusterMC2ROFs = nullptr) override; private: }; diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx index e0d689e4db5ed..d60d6900657ba 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx @@ -14,6 +14,7 @@ #include "TRKReconstruction/Clusterer.h" #include "TRKBase/GeometryTGeo.h" +#include "TRKBase/SegmentationChip.h" #include #include @@ -21,6 +22,51 @@ namespace o2::trk { +//__________________________________________________ +o2::math_utils::Point3D Clusterer::getClusterLocalCoordinates(const Cluster& cluster, const uint8_t* patt, + float yPlaneMLOT) noexcept +{ + const uint8_t rowSpan = *patt++; + const uint8_t colSpan = *patt++; + const int nBytes = (rowSpan * colSpan + 7) / 8; + + float cogDr{0.f}, cogDc{0.f}; + int nPix{0}, pixIdx{0}; + for (int ib = 0; ib < nBytes; ib++) { + const uint8_t byte = *patt++; + for (int bit = 7; bit >= 0 && pixIdx < rowSpan * colSpan; bit--, pixIdx++) { + if (byte & (1 << bit)) { + cogDr += pixIdx / colSpan; + cogDc += pixIdx % colSpan; + nPix++; + } + } + } + if (nPix > 1) { + cogDr /= nPix; + cogDc /= nPix; + } + + float x{0.f}, y{0.f}, z{0.f}; + SegmentationChip::detectorToLocalUnchecked(cluster.row, cluster.col, x, z, + cluster.subDetID, cluster.layer, cluster.disk); + + const float pitchRow = (cluster.subDetID == 0) ? SegmentationChip::PitchRowVD : SegmentationChip::PitchRowMLOT; + const float pitchCol = (cluster.subDetID == 0) ? SegmentationChip::PitchColVD : SegmentationChip::PitchColMLOT; + x -= cogDr * pitchRow; + z += cogDc * pitchCol; + + if (cluster.subDetID == 0) { + auto cv = SegmentationChip::flatToCurved(cluster.layer, x, 0.f); + x = cv.X(); + y = cv.Y(); + } else { + y = yPlaneMLOT; + } + + return {x, y, z}; +} + //__________________________________________________ void Clusterer::process(gsl::span digits, gsl::span digitROFs, @@ -28,7 +74,9 @@ void Clusterer::process(gsl::span digits, std::vector& patterns, std::vector& clusterROFs, const ConstDigitTruth* digitLabels, - ClusterTruth* clusterLabels) + ClusterTruth* clusterLabels, + gsl::span digMC2ROFs, + std::vector* clusterMC2ROFs) { if (!mThread) { mThread = std::make_unique(this); @@ -79,6 +127,13 @@ void Clusterer::process(gsl::span digits, clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), outFirst, static_cast(clusters.size()) - outFirst); } + + if (clusterMC2ROFs && !digMC2ROFs.empty()) { + clusterMC2ROFs->reserve(clusterMC2ROFs->size() + digMC2ROFs.size()); + for (const auto& in : digMC2ROFs) { + clusterMC2ROFs->emplace_back(in.eventRecordID, in.rofRecordID, in.minROF, in.maxROF); + } + } } //__________________________________________________ diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx index b764fcdd1cd79..2dbf56ae610e3 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx @@ -162,7 +162,9 @@ void ClustererACTS::process(gsl::span digits, std::vector& patterns, std::vector& clusterROFs, const ConstDigitTruth* digitLabels, - ClusterTruth* clusterLabels) + ClusterTruth* clusterLabels, + gsl::span digMC2ROFs, + std::vector* clusterMC2ROFs) { if (!mThread) { mThread = std::make_unique(this); From 7a1d14faa8e4ebca9899f59f329a797e3c34eb0c Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Fri, 1 May 2026 10:08:59 +0200 Subject: [PATCH 04/11] ALICE3: derive ROF timing for TRK inputs --- .../ALICE3GlobalReconstruction/TimeFrame.h | 29 +++ .../ALICE3GlobalReconstruction/TimeFrameGPU.h | 15 ++ .../reconstruction/src/TimeFrame.cxx | 169 +++++++++++++--- .../reconstruction/src/TimeFrameGPU.cxx | 187 ++++++++++++++---- .../workflow/src/TrackerSpec.cxx | 5 + 5 files changed, 335 insertions(+), 70 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h index f75e5da0be3b5..eebda7f5902b8 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h @@ -16,6 +16,7 @@ #ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H #define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H +#include "CommonDataFormat/InteractionRecord.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/Constants.h" #include "ITStracking/Configuration.h" @@ -23,6 +24,7 @@ #include "SimulationDataFormat/MCTruthContainer.h" #include "DataFormatsTRK/Cluster.h" #include "DataFormatsTRK/ROFRecord.h" +#include #include #include #include @@ -78,6 +80,33 @@ class TimeFrame : public o2::its::TimeFrame /// Maps each MC collision to its ROF via the ROF BCData timestamps (TRK digitising timing). /// \param rofs Span of TRK ROF records used to determine which ROF each collision falls into void addTruthSeedingVertices(gsl::span rofs); + + /// Derive the per-layer LayerTiming from the per-layer ROF spans and initialise + /// the ROF lookup tables. Each layer can have its own mROFLength and mROFBias, + /// so staggered TRK readouts are handled naturally as long as the input + /// ROFRecords carry the right BCData. The TF anchor (used to keep timing + /// values bounded when expressed as BC offsets) is set to the earliest + /// rofs[0].BCData across layers; consumers can read it back via getTFAnchorIR(). + /// Idempotent — must be called before loadROFrameData() in the cluster path. + /// \param layerROFs One ROFRecord span per layer. + void deriveAndInitTiming(const std::array, nLayers>& layerROFs); + + /// TF anchor IR: the earliest first-ROF BCData seen across all layers when + /// deriveAndInitTiming() was called. All LayerTiming BC values (and any BC + /// the tracker emits via clockLayer.getROFStartInBC) are offsets from this + /// anchor — add anchor.toLong() to convert back to absolute BC. + const o2::InteractionRecord& getTFAnchorIR() const noexcept { return mTFAnchorIR; } + + private: + /// One-shot setup of the per-layer LayerTiming and the three ROF lookup tables + /// (overlap, vertex lookup, multiplicity mask). Idempotent: subsequent calls are + /// no-ops, so the data-loading entry points may invoke it on every TF without + /// rebuilding the tables. Mirrors the initOnceDone gate in + /// ITSTrackingInterface::updateTimeDependentParams. + void initTimingTables(const std::array& timings); + + bool mTimingTablesInitialised{false}; + o2::InteractionRecord mTFAnchorIR{0, 0}; }; } // namespace trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h index 7743eed305994..90bb83969bc22 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h @@ -12,12 +12,14 @@ #ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H #define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H +#include "CommonDataFormat/InteractionRecord.h" #include "ITStrackingGPU/TimeFrameGPU.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" #include "DataFormatsTRK/Cluster.h" #include "DataFormatsTRK/ROFRecord.h" +#include #include #include @@ -46,6 +48,19 @@ class TimeFrameGPU : public o2::its::gpu::TimeFrameGPU void getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup); void addTruthSeedingVertices(gsl::span rofs); + + /// Mirror of o2::trk::TimeFrame::deriveAndInitTiming for the GPU backend. + /// See the CPU version for the design notes; the two implementations are + /// kept in sync by hand until the dedup follow-up lands. + void deriveAndInitTiming(const std::array, nLayers>& layerROFs); + + const o2::InteractionRecord& getTFAnchorIR() const noexcept { return mTFAnchorIR; } + + private: + void initTimingTables(const std::array& timings); + + bool mTimingTablesInitialised{false}; + o2::InteractionRecord mTFAnchorIR{0, 0}; }; } // namespace trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx index ee48e7a70f8e8..60a6026be6a9e 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx @@ -36,6 +36,103 @@ namespace o2::trk { +template +void TimeFrame::initTimingTables(const std::array& timings) +{ + if (mTimingTablesInitialised) { + return; + } + typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; + typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; + typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + rofOverlapTable.defineLayer(iLayer, timings[iLayer]); + rofVertexLookupTable.defineLayer(iLayer, timings[iLayer]); + rofMaskTable.defineLayer(iLayer, timings[iLayer]); + } + rofOverlapTable.init(); + rofVertexLookupTable.init(); + rofMaskTable.init(); + // ALICE3 TRK currently runs without per-ROF selection — no FastMultEst, + // no PhysTrigger, no UPC iteration on this detector yet. Enable every ROF + // so useMultiplictyMask() acts as an effective no-op for the tracker until + // a real selector is ported (see ITS TrackingInterface.cxx for the + // reference implementation). + rofMaskTable.resetMask(1u); + this->setROFOverlapTable(std::move(rofOverlapTable)); + this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); + this->setMultiplicityCutMask(std::move(rofMaskTable)); + this->useMultiplictyMask(); + mTimingTablesInitialised = true; + + // One-shot log of resolved per-layer timing and mask state. + const auto maskView = this->getROFMaskView(); + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + LOGP(info, "TRK timing initialised: layer {}: {}", iLayer, timings[iLayer].asString()); + LOGP(info, "TRK ROF mask: {}", maskView.asString(iLayer)); + } +} + +template +void TimeFrame::deriveAndInitTiming(const std::array, nLayers>& layerROFs) +{ + if (mTimingTablesInitialised) { + return; + } + + // Anchor the TF at the earliest first-ROF BCData seen across layers, so + // every layer's mROFBias is non-negative and intra-anchor BC values stay + // bounded (LayerTiming uses uint32 BC counters). For non-staggered TRK + // every layer is anchored at BC=0 (mROFBias=0); per-layer biases only + // diverge once the digitiser actually staggers the readout. + o2::InteractionRecord anchor{0, 0}; + bool haveAnchor = false; + for (const auto& span : layerROFs) { + if (span.empty()) { + continue; + } + const auto& first = span.front().getBCData(); + if (!haveAnchor || first.toLong() < anchor.toLong()) { + anchor = first; + haveAnchor = true; + } + } + mTFAnchorIR = anchor; + const int64_t anchorBC = anchor.toLong(); + + std::array timings{}; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + const auto& span = layerROFs[iLayer]; + auto& t = timings[iLayer]; + t.mNROFsTF = static_cast(span.size()); + + if (span.size() >= 2) { + const int64_t delta = span[1].getBCData().toLong() - span[0].getBCData().toLong(); + if (delta > 0) { + t.mROFLength = static_cast(delta); + } else { + LOGP(warning, "TRK layer {}: non-positive BC delta between rofs[0] and rofs[1] ({}); falling back to mROFLength=1", iLayer, delta); + t.mROFLength = 1; + } + } else { + if (span.size() == 1) { + LOGP(warning, "TRK layer {}: only one input ROF — cannot derive mROFLength; falling back to mROFLength=1", iLayer); + } + t.mROFLength = 1; + } + + if (!span.empty()) { + const int64_t bias = span.front().getBCData().toLong() - anchorBC; + // bias is guaranteed >= 0 by the anchor choice above + t.mROFBias = static_cast(bias); + } + t.mROFDelay = 0; + t.mROFAddTimeErr = 0; + } + + initTimingTables(timings); +} + template int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config) { @@ -51,21 +148,19 @@ int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, // Calculate number of ROFs and initialize data structures const int nRofs = (nEvents + inROFpileup - 1) / inROFpileup; - typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; - typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; - typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; + // Hit-tree path has no real BCData, so the timing stays at the placeholder + // (mROFLength = 1 BC, no delay/bias). For timing-sensitive work use the + // cluster path, which derives mROFLength from the input ROF BC stamps. + std::array timings{}; for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - rofOverlapTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); - rofVertexLookupTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); - rofMaskTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + timings[iLayer].mNROFsTF = static_cast(nRofs); + timings[iLayer].mROFLength = 1; + } + this->initTimingTables(timings); + const auto& timing = this->getROFOverlapTableView().getLayer(0); + if (timing.mNROFsTF != static_cast(nRofs)) { + LOGP(fatal, "TRK: inconsistent number of ROFs across TFs: timing has {}, hit-tree path produced {}", timing.mNROFsTF, nRofs); } - rofOverlapTable.init(); - rofVertexLookupTable.init(); - rofMaskTable.init(); - this->setROFOverlapTable(std::move(rofOverlapTable)); - this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); - this->setMultiplicityCutMask(std::move(rofMaskTable)); - this->useMultiplictyMask(); // Reset and prepare ROF data structures for (int iLayer{0}; iLayer < nLayers; ++iLayer) { @@ -197,21 +292,18 @@ int TimeFrame::loadROFrameData(gsl::span rofs geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); const int nRofs = rofs.size(); - typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; - typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; - typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - rofOverlapTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); - rofVertexLookupTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); - rofMaskTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + // Per-layer LayerTiming (and mROFLength / mROFBias) must already be in place; + // the cluster path requires the caller (TrackerSpec) to invoke + // deriveAndInitTiming() with the per-layer ROF spans first. We deliberately + // do not derive timing from the merged `rofs` span here — that would lose + // per-layer information needed for staggered readouts. + if (!mTimingTablesInitialised) { + LOGP(fatal, "TRK::loadROFrameData: timing tables not initialised — call deriveAndInitTiming() first"); + } + const auto& timing = this->getROFOverlapTableView().getLayer(0); + if (timing.mNROFsTF != static_cast(nRofs)) { + LOGP(fatal, "TRK: inconsistent number of ROFs across TFs: timing has {}, cluster path received {}", timing.mNROFsTF, nRofs); } - rofOverlapTable.init(); - rofVertexLookupTable.init(); - rofMaskTable.init(); - this->setROFOverlapTable(std::move(rofOverlapTable)); - this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); - this->setMultiplicityCutMask(std::move(rofMaskTable)); - this->useMultiplictyMask(); for (int iLayer{0}; iLayer < nLayers; ++iLayer) { this->mMinR[iLayer] = std::numeric_limits::max(); @@ -355,11 +447,21 @@ void TimeFrame::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs this->mPrimaryVertices.clear(); this->mPrimaryVerticesLabels.clear(); + // Vertex timestamps live in the clock layer's intra-anchor BC frame + // (anchor = mTFAnchorIR set by deriveAndInitTiming, or {0,0} for the + // hit-tree placeholder). TimeEstBC interval [t0, t0+tE] covers one full + // clock-layer ROF so that clockLayer.getROF(vertex.getTimeStamp().lower()) + // round-trips back to iRof. + const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); + const auto rofLength = clockLayer.mROFLength; + int iRof{0}; for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { mcHeaderTree->GetEntry(iEvent); o2::its::Vertex vertex; - vertex.setTimeStamp(o2::its::TimeEstBC{static_cast(iRof), 1}); + vertex.setTimeStamp(o2::its::TimeEstBC{ + clockLayer.getROFStartInBC(iRof), + static_cast(rofLength)}); vertex.setXYZ(mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); vertex.setNContributors(30); vertex.setChi2(0.f); @@ -394,6 +496,13 @@ void TimeFrame::addTruthSeedingVertices(gsl::spangetROFOverlapTableView().getClockLayer(); + const auto rofLength = clockLayer.mROFLength; + using Vertex = o2::its::Vertex; struct VertInfo { std::pmr::vector vertices; @@ -423,7 +532,9 @@ void TimeFrame::addTruthSeedingVertices(gsl::span(rofId), 1}); + vert.setTimeStamp(o2::its::TimeEstBC{ + clockLayer.getROFStartInBC(rofId), + static_cast(rofLength)}); vert.setNContributors(std::max(1L, std::ranges::count_if( mcReader.getTracks(iSrc, iEve), [](const auto& trk) { diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx index 26e2b69dbdfc6..c59f59b111831 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx @@ -35,6 +35,96 @@ namespace o2::trk { +template +void TimeFrameGPU::initTimingTables(const std::array& timings) +{ + if (mTimingTablesInitialised) { + return; + } + typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; + typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; + typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + rofOverlapTable.defineLayer(iLayer, timings[iLayer]); + rofVertexLookupTable.defineLayer(iLayer, timings[iLayer]); + rofMaskTable.defineLayer(iLayer, timings[iLayer]); + } + rofOverlapTable.init(); + rofVertexLookupTable.init(); + rofMaskTable.init(); + // ALICE3 TRK currently runs without per-ROF selection — see CPU mirror + // for the design note. All-pass policy until a real selector is ported. + rofMaskTable.resetMask(1u); + this->setROFOverlapTable(std::move(rofOverlapTable)); + this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); + this->setMultiplicityCutMask(std::move(rofMaskTable)); + this->useMultiplictyMask(); + mTimingTablesInitialised = true; + + const auto maskView = this->getROFMaskView(); + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + LOGP(info, "TRK timing initialised: layer {}: {}", iLayer, timings[iLayer].asString()); + LOGP(info, "TRK ROF mask: {}", maskView.asString(iLayer)); + } +} + +template +void TimeFrameGPU::deriveAndInitTiming(const std::array, nLayers>& layerROFs) +{ + if (mTimingTablesInitialised) { + return; + } + + // Mirror of o2::trk::TimeFrame::deriveAndInitTiming. Anchor at the earliest + // first-ROF BCData seen across layers; per-layer mROFLength comes from the + // BC delta between consecutive ROFs of that layer. + o2::InteractionRecord anchor{0, 0}; + bool haveAnchor = false; + for (const auto& span : layerROFs) { + if (span.empty()) { + continue; + } + const auto& first = span.front().getBCData(); + if (!haveAnchor || first.toLong() < anchor.toLong()) { + anchor = first; + haveAnchor = true; + } + } + mTFAnchorIR = anchor; + const int64_t anchorBC = anchor.toLong(); + + std::array timings{}; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + const auto& span = layerROFs[iLayer]; + auto& t = timings[iLayer]; + t.mNROFsTF = static_cast(span.size()); + + if (span.size() >= 2) { + const int64_t delta = span[1].getBCData().toLong() - span[0].getBCData().toLong(); + if (delta > 0) { + t.mROFLength = static_cast(delta); + } else { + LOGP(warning, "TRK layer {}: non-positive BC delta between rofs[0] and rofs[1] ({}); falling back to mROFLength=1", iLayer, delta); + t.mROFLength = 1; + } + } else { + if (span.size() == 1) { + LOGP(warning, "TRK layer {}: only one input ROF — cannot derive mROFLength; falling back to mROFLength=1", iLayer); + } + t.mROFLength = 1; + } + + if (!span.empty()) { + const int64_t bias = span.front().getBCData().toLong() - anchorBC; + t.mROFBias = static_cast(bias); + } + t.mROFDelay = 0; + t.mROFAddTimeErr = 0; + } + + initTimingTables(timings); +} + template int TimeFrameGPU::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config) { @@ -48,21 +138,18 @@ int TimeFrameGPU::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gm const int inROFpileup{config.contains("inROFpileup") ? config["inROFpileup"].get() : 1}; const int nRofs = (nEvents + inROFpileup - 1) / inROFpileup; - typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; - typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; - typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; + // Hit-tree path has no real BCData; keep placeholder timing + // (mROFLength = 1 BC). Use the cluster path for timing-sensitive work. + std::array timings{}; for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - rofOverlapTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); - rofVertexLookupTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); - rofMaskTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + timings[iLayer].mNROFsTF = static_cast(nRofs); + timings[iLayer].mROFLength = 1; + } + this->initTimingTables(timings); + const auto& timing = this->getROFOverlapTableView().getLayer(0); + if (timing.mNROFsTF != static_cast(nRofs)) { + LOGP(fatal, "TRK: inconsistent number of ROFs across TFs: timing has {}, hit-tree path produced {}", timing.mNROFsTF, nRofs); } - rofOverlapTable.init(); - rofVertexLookupTable.init(); - rofMaskTable.init(); - this->setROFOverlapTable(std::move(rofOverlapTable)); - this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); - this->setMultiplicityCutMask(std::move(rofMaskTable)); - this->useMultiplictyMask(); for (int iLayer{0}; iLayer < nLayers; ++iLayer) { this->mMinR[iLayer] = std::numeric_limits::max(); @@ -185,21 +272,16 @@ int TimeFrameGPU::loadROFrameData(gsl::span r geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); const int nRofs = rofs.size(); - typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; - typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; - typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - rofOverlapTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); - rofVertexLookupTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); - rofMaskTable.defineLayer(iLayer, nRofs, 1, 0, 0, 0); + // Per-layer LayerTiming must already be in place; the cluster path requires + // the caller (TrackerSpec) to invoke deriveAndInitTiming() with the per-layer + // ROF spans first. See the CPU mirror for design notes. + if (!mTimingTablesInitialised) { + LOGP(fatal, "TRK::loadROFrameData (GPU): timing tables not initialised — call deriveAndInitTiming() first"); + } + const auto& timing = this->getROFOverlapTableView().getLayer(0); + if (timing.mNROFsTF != static_cast(nRofs)) { + LOGP(fatal, "TRK: inconsistent number of ROFs across TFs: timing has {}, cluster path received {}", timing.mNROFsTF, nRofs); } - rofOverlapTable.init(); - rofVertexLookupTable.init(); - rofMaskTable.init(); - this->setROFOverlapTable(std::move(rofOverlapTable)); - this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); - this->setMultiplicityCutMask(std::move(rofMaskTable)); - this->useMultiplictyMask(); for (int iLayer{0}; iLayer < nLayers; ++iLayer) { this->mMinR[iLayer] = std::numeric_limits::max(); this->mMaxR[iLayer] = std::numeric_limits::lowest(); @@ -239,16 +321,11 @@ int TimeFrameGPU::loadROFrameData(gsl::span r const int last = first + rof.getNEntries(); for (int clusterId{first}; clusterId < last; ++clusterId) { - if (clusterId < 0 || clusterId >= static_cast(clusters.size())) { - LOGP(warning, "Skipping out-of-range cluster id {} for ROF {}", clusterId, iRof); - continue; - } - - const auto& c = clusters[clusterId]; - if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { - continue; - } - + // Parse the pattern header up-front so we always know how many bytes + // this cluster occupies in the pattern stream. The stream is keyed + // per-cluster: every `continue` below MUST advance pattPtr by + // pattAdvance, otherwise the next cluster decodes from a stale + // offset and the whole layer's geometry is corrupted. if (pattPtr + 2 > pattEnd) { LOGP(error, "Pattern stream exhausted while decoding cluster {}", clusterId); break; @@ -259,16 +336,29 @@ int TimeFrameGPU::loadROFrameData(gsl::span r LOGP(error, "Pattern stream truncated for cluster {}", clusterId); break; } + const int pattAdvance = 2 + nBytes; + + if (clusterId < 0 || clusterId >= static_cast(clusters.size())) { + LOGP(warning, "Skipping out-of-range cluster id {} for ROF {}", clusterId, iRof); + pattPtr += pattAdvance; + continue; + } + + const auto& c = clusters[clusterId]; + if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { + pattPtr += pattAdvance; + continue; + } const int layer = startLayer[c.subDetID] + c.layer; if (layer < 0 || layer >= nLayers) { LOGP(error, "Skipping cluster with invalid layer {} (subDetID {}, layer {})", layer, c.subDetID, c.layer); - pattPtr += 2 + nBytes; + pattPtr += pattAdvance; continue; } auto locXYZ = Clusterer::getClusterLocalCoordinates(c, pattForCluster, yPlaneMLOT); - pattPtr += 2 + nBytes; + pattPtr += pattAdvance; const auto gloXYZ = geom->getMatrixL2G(c.chipID) * locXYZ; @@ -331,11 +421,19 @@ void TimeFrameGPU::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nR this->mPrimaryVertices.clear(); this->mPrimaryVerticesLabels.clear(); + // Vertex timestamps live in the clock layer's intra-anchor BC frame + // (anchor = mTFAnchorIR set by deriveAndInitTiming, or {0,0} for the + // hit-tree placeholder). See the CPU mirror for design notes. + const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); + const auto rofLength = clockLayer.mROFLength; + int iRof{0}; for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { mcHeaderTree->GetEntry(iEvent); o2::its::Vertex vertex; - vertex.setTimeStamp(o2::its::TimeEstBC{static_cast(iRof), 1}); + vertex.setTimeStamp(o2::its::TimeEstBC{ + clockLayer.getROFStartInBC(iRof), + static_cast(rofLength)}); vertex.setXYZ(mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); vertex.setNContributors(30); vertex.setChi2(0.f); @@ -364,6 +462,11 @@ void TimeFrameGPU::addTruthSeedingVertices(gsl::spangetROFOverlapTableView().getClockLayer(); + const auto rofLength = clockLayer.mROFLength; + using Vertex = o2::its::Vertex; struct VertInfo { std::pmr::vector vertices; @@ -392,7 +495,9 @@ void TimeFrameGPU::addTruthSeedingVertices(gsl::span(rofId), 1}); + vert.setTimeStamp(o2::its::TimeEstBC{ + clockLayer.getROFStartInBC(rofId), + static_cast(rofLength)}); vert.setNContributors(std::max(1L, std::ranges::count_if( mcReader.getTracks(iSrc, iEve), [](const auto& trk) { diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index cadb65ec413d8..e3dcddc152ce2 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -336,6 +336,11 @@ void TrackerDPL::run(ProcessingContext& pc) } } + // Build per-layer LayerTiming from each layer's own ROF BCData. This is + // a precondition for loadROFrameData(): timing must be in place before + // clusters are loaded. Idempotent across TFs. + timeFrame.deriveAndInitTiming(layerROFs); + std::vector rofRecords; std::vector compClusters; std::vector patterns; From 9d752d0db996e69fd00c9211806d8b6ca481eb78 Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Sat, 2 May 2026 13:53:21 +0200 Subject: [PATCH 05/11] ALICE3: write track ROFs and IR frames --- .../workflow/src/TrackerSpec.cxx | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index e3dcddc152ce2..8b91cd4fdd13e 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -15,7 +15,9 @@ #include #include #include +#include +#include "CommonDataFormat/IRFrame.h" #include "DataFormatsTRK/Cluster.h" #include "DataFormatsTRK/ROFRecord.h" #include "DetectorsBase/GeometryManager.h" @@ -464,12 +466,69 @@ void TrackerDPL::run(ProcessingContext& pc) LOGP(info, "Good tracks: {} ({:.1f}%)", goodTracks, totalTracks > 0 ? 100.0 * goodTracks / totalTracks : 0); LOGP(info, "Fake tracks: {} ({:.1f}%)", fakeTracks, totalTracks > 0 ? 100.0 * fakeTracks / totalTracks : 0); + // Build per-ROF track records and IR frames in the clock-layer's frame. + // The clock layer is the fastest TRK layer (max mNROFsTF) — see + // ROFOverlapTableView::getClock(). Each track's TimeStamp deterministically + // maps to a clock-layer ROF; track ordering from the tracker is by + // increasing time, so the per-ROF firstEntry/nEntries pair indexes a + // contiguous range of allTracks. Mirrors ITS TrackingInterface.cxx. + const auto& rofView = timeFrame.getROFOverlapTableView(); + const auto& clockLayer = rofView.getClockLayer(); + const int clockLayerId = rofView.getClock(); + const int64_t anchorBC = timeFrame.getTFAnchorIR().toLong(); + + int highestROF = static_cast(clockLayer.mNROFsTF); + for (const auto& trc : allTracks) { + highestROF = std::max(highestROF, static_cast(clockLayer.getROF(trc.getTimeStamp()))); + } + for (const auto& vtx : timeFrame.getPrimaryVertices()) { + highestROF = std::max(highestROF, static_cast(clockLayer.getROF(vtx.getTimeStamp().lower()))); + } + + std::vector allTrackROFs(highestROF); + for (size_t iROF = 0; iROF < allTrackROFs.size(); ++iROF) { + auto& rof = allTrackROFs[iROF]; + o2::InteractionRecord ir; + ir.setFromLong(anchorBC + static_cast(clockLayer.getROFStartInBC(iROF))); + rof.setBCData(ir); + rof.setROFrame(iROF); + rof.setFirstEntry(0); + rof.setNEntries(0); + } + + std::vector rofEntries(highestROF + 1, 0); + for (const auto& trc : allTracks) { + const int rof = static_cast(clockLayer.getROF(trc.getTimeStamp())); + if (rof >= 0 && rof < highestROF) { + ++rofEntries[rof]; + } + } + std::exclusive_scan(rofEntries.begin(), rofEntries.end(), rofEntries.begin(), 0); + + std::vector irFrames; + irFrames.reserve(allTrackROFs.size()); + const auto& maskView = timeFrame.getROFMaskView(); + const auto rofLenMinus1 = clockLayer.mROFLength > 0 ? clockLayer.mROFLength - 1 : 0; + for (size_t iROF = 0; iROF < allTrackROFs.size(); ++iROF) { + allTrackROFs[iROF].setFirstEntry(rofEntries[iROF]); + allTrackROFs[iROF].setNEntries(rofEntries[iROF + 1] - rofEntries[iROF]); + if (maskView.isROFEnabled(clockLayerId, static_cast(iROF))) { + const auto& bcStart = allTrackROFs[iROF].getBCData(); + auto& irFrame = irFrames.emplace_back(bcStart, bcStart + rofLenMinus1); + irFrame.info = allTrackROFs[iROF].getNEntries(); + } + } + pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKS", 0}, allTracks); + pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKSROF", 0}, allTrackROFs); + pc.outputs().snapshot(o2::framework::Output{"TRK", "IRFRAMES", 0}, irFrames); if (mIsMC) { pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKSMCTR", 0}, allLabels); } - LOGP(info, "Tracks{} streamed to output", mIsMC ? " and MC labels" : ""); + LOGP(info, "TRK pushed {} tracks in {} ROFs and {} IR frames{}", + allTracks.size(), allTrackROFs.size(), irFrames.size(), + mIsMC ? " (with MC labels)" : ""); }; #ifdef TRK_HAS_GPU_TRACKING @@ -511,6 +570,8 @@ DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, c std::vector inputs; std::vector outputs; outputs.emplace_back("TRK", "TRACKS", 0, Lifetime::Timeframe); + outputs.emplace_back("TRK", "TRACKSROF", 0, Lifetime::Timeframe); + outputs.emplace_back("TRK", "IRFRAMES", 0, Lifetime::Timeframe); auto ggRequest = std::make_shared(false, // orbitResetTime false, // GRPECS=true false, // GRPLHCIF From e0fc09a9858c399a1390a657cc2acb7e027a877d Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Sat, 2 May 2026 13:59:46 +0200 Subject: [PATCH 06/11] ALICE3: assign cluster MC labels in TimeFrame loading --- .../reconstruction/src/TimeFrame.cxx | 4 ++- .../reconstruction/src/TimeFrameGPU.cxx | 4 ++- .../workflow/src/TrackerSpec.cxx | 32 ++++--------------- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx index 60a6026be6a9e..5cd652c32f600 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx @@ -275,8 +275,10 @@ int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, } // Update primary vertices ROF structure } - this->mClusterLabels[0] = labels; } + // The MC labels container is the same pointer for every event — assign once + // after the loop instead of redundantly on each iteration. + this->mClusterLabels[0] = labels; return nRofs; } diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx index c59f59b111831..36e95386b70b6 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx @@ -255,8 +255,10 @@ int TimeFrameGPU::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gm this->mROFramesClusters[iLayer][iRof] = this->mUnsortedClusters[iLayer].size(); } } - this->mClusterLabels[0] = labels; } + // The MC labels container is the same pointer for every event — assign once + // after the loop instead of redundantly on each iteration. + this->mClusterLabels[0] = labels; return nRofs; } diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index 8b91cd4fdd13e..c7b7dc161fedf 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -74,19 +74,10 @@ TrackerDPL::TrackerDPL(std::shared_ptr gr, } mIsMC = isMC; mDeviceType = dType; - // mITSTrackingInterface.setTrackingMode(trMode); } void TrackerDPL::init(InitContext& ic) { - // mTimer.Stop(); - // mTimer.Reset(); - // o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); - // mChainITS.reset(mRecChain->AddChain()); - // mITSTrackingInterface.setTraitsFromProvider(mChainITS->GetITSVertexerTraits(), - // mChainITS->GetITSTrackerTraits(), - // mChainITS->GetITSTimeframe()); - #ifdef O2_WITH_ACTS mUseACTS = ic.options().get("useACTS"); #endif @@ -529,6 +520,12 @@ void TrackerDPL::run(ProcessingContext& pc) LOGP(info, "TRK pushed {} tracks in {} ROFs and {} IR frames{}", allTracks.size(), allTrackROFs.size(), irFrames.size(), mIsMC ? " (with MC labels)" : ""); + + // Clear tracking state so the TimeFrame is ready for the next TF. + // Today timeFrame is a stack local and is destroyed at end of this + // lambda anyway, but wipe() matches the ITS lifecycle pattern and stays + // correct if/when timeFrame is hoisted to a member for cross-TF reuse. + timeFrame.wipe(); }; #ifdef TRK_HAS_GPU_TRACKING @@ -555,11 +552,6 @@ void TrackerDPL::run(ProcessingContext& pc) LOGP(info, "CPU Reconstruction time for this TF {} s (cpu), {} s (wall)", mTimer.CpuTime() - cput, mTimer.RealTime() - realt); } -// void TrackerDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) -// { -// // mITSTrackingInterface.finaliseCCDB(matcher, obj); -// } - void TrackerDPL::endOfStream(EndOfStreamContext& ec) { LOGF(info, "TRK CA-Tracker total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); @@ -618,20 +610,8 @@ DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, c } } - // inputs.emplace_back("itscldict", "TRK", "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec("ITS/Calib/ClusterDictionary")); - // inputs.emplace_back("itsalppar", "TRK", "ALPIDEPARAM", 0, Lifetime::Condition, ccdbParamSpec("ITS/Config/AlpideParam")); - - // outputs.emplace_back("TRK", "TRACKCLSID", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "TRKTrackROF", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "VERTICES", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "VERTICESROF", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "IRFRAMES", 0, Lifetime::Timeframe); - if (useMC) { - // outputs.emplace_back("TRK", "VERTICESMCTR", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "VERTICESMCPUR", 0, Lifetime::Timeframe); outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "TRKTrackMC2ROF", 0, Lifetime::Timeframe); } return DataProcessorSpec{ From ec8e8c00a844e00dedcfcc30f863d999601ba1d0 Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Sun, 3 May 2026 10:32:11 +0200 Subject: [PATCH 07/11] ALICE3: share TimeFrame code between CPU and GPU --- .../ALICE3GlobalReconstruction/TimeFrame.h | 90 +-- .../ALICE3GlobalReconstruction/TimeFrameGPU.h | 52 +- .../TimeFrameMixin.h | 581 ++++++++++++++++++ .../reconstruction/src/TimeFrame.cxx | 567 +---------------- .../reconstruction/src/TimeFrameGPU.cxx | 536 +--------------- .../workflow/src/TrackerSpec.cxx | 98 +-- .../TRK/workflow/src/trk-reco-workflow.cxx | 11 - 7 files changed, 611 insertions(+), 1324 deletions(-) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h index eebda7f5902b8..6daefb2346e2c 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h @@ -10,106 +10,26 @@ // or submit itself to any jurisdiction. /// /// \file TimeFrame.h -/// \brief TRK TimeFrame class derived from ITS TimeFrame +/// \brief CPU TRK TimeFrame wrapper. /// #ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H #define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H -#include "CommonDataFormat/InteractionRecord.h" +#include "ALICE3GlobalReconstruction/TimeFrameMixin.h" #include "ITStracking/TimeFrame.h" -#include "ITStracking/Constants.h" -#include "ITStracking/Configuration.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/MCTruthContainer.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" -#include -#include -#include -#include -#include -#include -#include - -class TTree; - -namespace o2 -{ -namespace trk +namespace o2::trk { -class GeometryTGeo; -/// TRK TimeFrame class that extends ITS TimeFrame functionality -/// This allows for customization of tracking algorithms specific to the TRK detector template -class TimeFrame : public o2::its::TimeFrame +class TimeFrame : public TimeFrameMixin> { public: TimeFrame() = default; ~TimeFrame() override = default; - - /// Override methods if needed for TRK-specific behavior - /// For now, we inherit all functionality from ITS TimeFrame - - /// Process hits from TTree to initialize ROFs - /// \param hitsTree Tree containing TRK hits - /// \param mcHeaderTree Tree containing MC event headers - /// \param nEvents Number of events to process - /// \param gman TRK geometry manager instance - /// \param config Configuration parameters for hit reconstruction - int loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config); - - /// Load ROF data from TRK clustered inputs (without topology dictionary for the time being). - /// Patterns are expected as [rowSpan, colSpan, bitmap...] for each cluster. - int loadROFrameData(gsl::span rofs, - gsl::span clusters, - gsl::span patterns, - const dataformats::MCTruthContainer* mcLabels = nullptr, - float yPlaneMLOT = 0.f); - - /// Add primary vertices from MC headers for each ROF - /// \param mcHeaderTree Tree containing MC event headers - /// \param nRofs Number of ROFs (Read-Out Frames) - /// \param nEvents Number of events to process - /// \param inROFpileup Number of events per ROF - void getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup); - - /// Add primary vertices using truth seeding from the DigitizationContext (collisioncontext.root). - /// Maps each MC collision to its ROF via the ROF BCData timestamps (TRK digitising timing). - /// \param rofs Span of TRK ROF records used to determine which ROF each collision falls into - void addTruthSeedingVertices(gsl::span rofs); - - /// Derive the per-layer LayerTiming from the per-layer ROF spans and initialise - /// the ROF lookup tables. Each layer can have its own mROFLength and mROFBias, - /// so staggered TRK readouts are handled naturally as long as the input - /// ROFRecords carry the right BCData. The TF anchor (used to keep timing - /// values bounded when expressed as BC offsets) is set to the earliest - /// rofs[0].BCData across layers; consumers can read it back via getTFAnchorIR(). - /// Idempotent — must be called before loadROFrameData() in the cluster path. - /// \param layerROFs One ROFRecord span per layer. - void deriveAndInitTiming(const std::array, nLayers>& layerROFs); - - /// TF anchor IR: the earliest first-ROF BCData seen across all layers when - /// deriveAndInitTiming() was called. All LayerTiming BC values (and any BC - /// the tracker emits via clockLayer.getROFStartInBC) are offsets from this - /// anchor — add anchor.toLong() to convert back to absolute BC. - const o2::InteractionRecord& getTFAnchorIR() const noexcept { return mTFAnchorIR; } - - private: - /// One-shot setup of the per-layer LayerTiming and the three ROF lookup tables - /// (overlap, vertex lookup, multiplicity mask). Idempotent: subsequent calls are - /// no-ops, so the data-loading entry points may invoke it on every TF without - /// rebuilding the tables. Mirrors the initOnceDone gate in - /// ITSTrackingInterface::updateTimeDependentParams. - void initTimingTables(const std::array& timings); - - bool mTimingTablesInitialised{false}; - o2::InteractionRecord mTFAnchorIR{0, 0}; }; -} // namespace trk -} // namespace o2 +} // namespace o2::trk #endif // ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h index 90bb83969bc22..744fca166489f 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h @@ -8,62 +8,28 @@ // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +/// +/// \file TimeFrameGPU.h +/// \brief GPU TRK TimeFrame wrapper. +/// #ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H #define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H -#include "CommonDataFormat/InteractionRecord.h" +#include "ALICE3GlobalReconstruction/TimeFrameMixin.h" #include "ITStrackingGPU/TimeFrameGPU.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/MCTruthContainer.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" -#include -#include -#include - -class TTree; - -namespace o2 -{ -namespace trk +namespace o2::trk { -class GeometryTGeo; template -class TimeFrameGPU : public o2::its::gpu::TimeFrameGPU +class TimeFrameGPU : public TimeFrameMixin> { public: TimeFrameGPU() = default; ~TimeFrameGPU() override = default; - - int loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config); - - int loadROFrameData(gsl::span rofs, - gsl::span clusters, - gsl::span patterns, - const dataformats::MCTruthContainer* mcLabels = nullptr, - float yPlaneMLOT = 0.f); - - void getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup); - void addTruthSeedingVertices(gsl::span rofs); - - /// Mirror of o2::trk::TimeFrame::deriveAndInitTiming for the GPU backend. - /// See the CPU version for the design notes; the two implementations are - /// kept in sync by hand until the dedup follow-up lands. - void deriveAndInitTiming(const std::array, nLayers>& layerROFs); - - const o2::InteractionRecord& getTFAnchorIR() const noexcept { return mTFAnchorIR; } - - private: - void initTimingTables(const std::array& timings); - - bool mTimingTablesInitialised{false}; - o2::InteractionRecord mTFAnchorIR{0, 0}; }; -} // namespace trk -} // namespace o2 +} // namespace o2::trk -#endif +#endif // ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h new file mode 100644 index 0000000000000..fb589874b24d9 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h @@ -0,0 +1,581 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +/// +/// \file TimeFrameMixin.h +/// \brief Shared TRK TimeFrame helpers for CPU and GPU backends. +/// + +#ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEMIXIN_H +#define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEMIXIN_H + +#include "CommonDataFormat/InteractionRecord.h" +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" +#include "ITStracking/ROFLookupTables.h" +#include "ITStracking/TimeFrame.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCEventHeader.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "SimulationDataFormat/DigitizationContext.h" +#include "Steer/MCKinematicsReader.h" +#include "TRKReconstruction/Clusterer.h" +#include "TRKSimulation/Hit.h" +#include "TRKBase/GeometryTGeo.h" +#include "TRKBase/SegmentationChip.h" +#include "Framework/Logger.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace o2::trk +{ + +template +class TimeFrameMixin : public Base +{ + public: + TimeFrameMixin() = default; + ~TimeFrameMixin() override = default; + + int loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config); + + int loadROFrameData(const std::array, nLayers>& layerROFs, + const std::array, nLayers>& layerClusters, + const std::array, nLayers>& layerPatterns, + const std::array*, nLayers>* mcLabels = nullptr, + float yPlaneMLOT = 0.f); + + void getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup); + + void addTruthSeedingVertices(gsl::span rofs); + + void deriveAndInitTiming(const std::array, nLayers>& layerROFs); + + const o2::InteractionRecord& getTFAnchorIR() const noexcept { return mTFAnchorIR; } + + protected: + void initTimingTables(const std::array& timings); + void updateHostROFVertexLookupTable(); + + bool mTimingTablesInitialised{false}; + o2::InteractionRecord mTFAnchorIR{0, 0}; +}; + +template +void TimeFrameMixin::updateHostROFVertexLookupTable() +{ + static_cast*>(this)->updateROFVertexLookupTable(); +} + +template +void TimeFrameMixin::initTimingTables(const std::array& timings) +{ + if (mTimingTablesInitialised) { + return; + } + typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; + typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; + typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + rofOverlapTable.defineLayer(iLayer, timings[iLayer]); + rofVertexLookupTable.defineLayer(iLayer, timings[iLayer]); + rofMaskTable.defineLayer(iLayer, timings[iLayer]); + } + rofOverlapTable.init(); + rofVertexLookupTable.init(); + rofMaskTable.init(); + rofMaskTable.resetMask(1u); + this->setROFOverlapTable(std::move(rofOverlapTable)); + this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); + this->setMultiplicityCutMask(std::move(rofMaskTable)); + this->useMultiplictyMask(); + mTimingTablesInitialised = true; + + const auto maskView = this->getROFMaskView(); + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + LOGP(info, "TRK timing initialised: layer {}: {}", iLayer, timings[iLayer].asString()); + LOGP(info, "TRK ROF mask: {}", maskView.asString(iLayer)); + } +} + +template +void TimeFrameMixin::deriveAndInitTiming(const std::array, nLayers>& layerROFs) +{ + if (mTimingTablesInitialised) { + return; + } + + o2::InteractionRecord anchor{0, 0}; + bool haveAnchor = false; + for (const auto& span : layerROFs) { + if (span.empty()) { + continue; + } + const auto& first = span.front().getBCData(); + if (!haveAnchor || first.toLong() < anchor.toLong()) { + anchor = first; + haveAnchor = true; + } + } + mTFAnchorIR = anchor; + const int64_t anchorBC = anchor.toLong(); + + std::array timings{}; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + const auto& span = layerROFs[iLayer]; + auto& t = timings[iLayer]; + t.mNROFsTF = static_cast(span.size()); + + if (span.size() >= 2) { + const int64_t delta = span[1].getBCData().toLong() - span[0].getBCData().toLong(); + if (delta > 0) { + t.mROFLength = static_cast(delta); + } else { + LOGP(warning, "TRK layer {}: non-positive BC delta between rofs[0] and rofs[1] ({}); falling back to mROFLength=1", iLayer, delta); + t.mROFLength = 1; + } + } else { + if (span.size() == 1) { + LOGP(warning, "TRK layer {}: only one input ROF — cannot derive mROFLength; falling back to mROFLength=1", iLayer); + } + t.mROFLength = 1; + } + + if (!span.empty()) { + const int64_t bias = span.front().getBCData().toLong() - anchorBC; + t.mROFBias = static_cast(bias); + } + t.mROFDelay = 0; + t.mROFAddTimeErr = 0; + } + + initTimingTables(timings); +} + +template +int TimeFrameMixin::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config) +{ + constexpr std::array startLayer{0, 3}; + const Long64_t nEvents = hitsTree->GetEntries(); + this->setIsStaggered(true); + + gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + + std::vector* trkHit = nullptr; + hitsTree->SetBranchAddress("TRKHit", &trkHit); + + const int inROFpileup{config.contains("inROFpileup") ? config["inROFpileup"].get() : 1}; + + const int nRofs = (nEvents + inROFpileup - 1) / inROFpileup; + std::array timings{}; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + timings[iLayer].mNROFsTF = static_cast(nRofs); + timings[iLayer].mROFLength = 1; + } + this->initTimingTables(timings); + const auto& timing = this->getROFOverlapTableView().getLayer(0); + if (timing.mNROFsTF != static_cast(nRofs)) { + LOGP(fatal, "TRK: inconsistent number of ROFs across TFs: timing has {}, hit-tree path produced {}", timing.mNROFsTF, nRofs); + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mMinR[iLayer] = std::numeric_limits::max(); + this->mMaxR[iLayer] = std::numeric_limits::lowest(); + this->mROFramesClusters[iLayer].clear(); + this->mROFramesClusters[iLayer].resize(nRofs + 1, 0); + this->mUnsortedClusters[iLayer].clear(); + this->mTrackingFrameInfo[iLayer].clear(); + this->mClusterExternalIndices[iLayer].clear(); + this->mClusterSize[iLayer].clear(); + } + + std::array clusterCountPerLayer{}; + for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { + hitsTree->GetEntry(iEvent); + for (const auto& hit : *trkHit) { + if (gman->getDisk(hit.GetDetectorID()) != -1) { + continue; + } + int subDetID = gman->getSubDetID(hit.GetDetectorID()); + const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); + if (layer >= nLayers) { + continue; + } + ++clusterCountPerLayer[layer]; + } + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mClusterSize[iLayer].reserve(clusterCountPerLayer[iLayer]); + } + + std::array resolution{0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004}; + if (config["geometry"]["pitch"].size() == nLayers) { + for (int iLayer{0}; iLayer < config["geometry"]["pitch"].size(); ++iLayer) { + LOGP(info, "Setting resolution for layer {} from config", iLayer); + LOGP(info, "Layer {} pitch {} cm", iLayer, config["geometry"]["pitch"][iLayer].get()); + resolution[iLayer] = config["geometry"]["pitch"][iLayer].get() / std::sqrt(12.f); + } + } + LOGP(info, "Number of active parts in VD: {}", gman->getNumberOfActivePartsVD()); + + std::array hitCounterPerLayer{}; + std::array*, nLayers> labels{}; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + labels[iLayer] = new dataformats::MCTruthContainer(); + this->mClusterLabels[iLayer] = labels[iLayer]; + } + + int iRof{0}; + for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { + hitsTree->GetEntry(iEvent); + + for (auto& hit : *trkHit) { + if (gman->getDisk(hit.GetDetectorID()) != -1) { + continue; + } + int subDetID = gman->getSubDetID(hit.GetDetectorID()); + const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); + + float alpha{0.f}; + o2::math_utils::Point3D gloXYZ; + o2::math_utils::Point3D trkXYZ; + float r{0.f}; + if (layer >= nLayers) { + continue; + } + if (layer >= 3) { + int chipID = hit.GetDetectorID(); + alpha = gman->getSensorRefAlphaMLOT(chipID); + const o2::math_utils::Transform3D& l2g = gman->getMatrixL2G(chipID); + auto locXYZ = l2g ^ (hit.GetPos()); + locXYZ.SetX(locXYZ.X() + gRandom->Gaus(0.0, resolution[layer])); + locXYZ.SetZ(locXYZ.Z() + gRandom->Gaus(0.0, resolution[layer])); + gloXYZ = gman->getMatrixL2G(chipID) * locXYZ; + trkXYZ = gman->getMatrixT2L(chipID - gman->getNumberOfActivePartsVD()) ^ locXYZ; + r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + } else { + const auto& hitPos = hit.GetPos(); + r = std::hypot(hitPos.X(), hitPos.Y()); + alpha = std::atan2(hitPos.Y(), hitPos.X()) + gRandom->Gaus(0.0, resolution[layer] / r); + o2::math_utils::bringTo02Pi(alpha); + gloXYZ.SetX(r * std::cos(alpha)); + gloXYZ.SetY(r * std::sin(alpha)); + gloXYZ.SetZ(hitPos.Z() + gRandom->Gaus(0.0, resolution[layer])); + trkXYZ.SetX(r); + trkXYZ.SetY(0.f); + trkXYZ.SetZ(gloXYZ.Z()); + } + this->mMinR[layer] = std::min(this->mMinR[layer], r); + this->mMaxR[layer] = std::max(this->mMaxR[layer], r); + this->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, + std::array{trkXYZ.y(), trkXYZ.z()}, + std::array{resolution[layer] * resolution[layer], 0., resolution[layer] * resolution[layer]}); + this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); + const int layerHitCounter = hitCounterPerLayer[layer]++; + this->addClusterExternalIndexToLayer(layer, layerHitCounter); + this->mClusterSize[layer].push_back(1); + MCCompLabel label{hit.GetTrackID(), static_cast(iEvent), 0}; + labels[layer]->addElement(layerHitCounter, label); + } + trkHit->clear(); + + if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { + iRof++; + for (unsigned int iLayer{0}; iLayer < this->mUnsortedClusters.size(); ++iLayer) { + this->mROFramesClusters[iLayer][iRof] = this->mUnsortedClusters[iLayer].size(); + } + } + } + return nRofs; +} + +template +int TimeFrameMixin::loadROFrameData(const std::array, nLayers>& layerROFs, + const std::array, nLayers>& layerClusters, + const std::array, nLayers>& layerPatterns, + const std::array*, nLayers>* mcLabels, + float yPlaneMLOT) +{ + constexpr std::array startLayer{0, 3}; + this->setIsStaggered(true); + GeometryTGeo* geom = GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + + if (!mTimingTablesInitialised) { + LOGP(fatal, "TRK::loadROFrameData: timing tables not initialised — call deriveAndInitTiming() first"); + } + int nRofs{0}; + for (const auto& rofs : layerROFs) { + nRofs = std::max(nRofs, static_cast(rofs.size())); + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + const auto& timing = this->getROFOverlapTableView().getLayer(iLayer); + if (timing.mNROFsTF != static_cast(layerROFs[iLayer].size())) { + LOGP(fatal, "TRK: inconsistent number of ROFs on layer {}: timing has {}, cluster path received {}", iLayer, timing.mNROFsTF, layerROFs[iLayer].size()); + } + this->mMinR[iLayer] = std::numeric_limits::max(); + this->mMaxR[iLayer] = std::numeric_limits::lowest(); + this->mROFramesClusters[iLayer].clear(); + this->mROFramesClusters[iLayer].resize(layerROFs[iLayer].size() + 1, 0); + this->mUnsortedClusters[iLayer].clear(); + this->mTrackingFrameInfo[iLayer].clear(); + this->mClusterExternalIndices[iLayer].clear(); + this->mClusterSize[iLayer].clear(); + this->mUnsortedClusters[iLayer].reserve(layerClusters[iLayer].size()); + this->mTrackingFrameInfo[iLayer].reserve(layerClusters[iLayer].size()); + this->mClusterExternalIndices[iLayer].reserve(layerClusters[iLayer].size()); + this->mClusterSize[iLayer].reserve(layerClusters[iLayer].size()); + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + const uint8_t* pattPtr = layerPatterns[iLayer].data(); + const uint8_t* pattEnd = pattPtr + layerPatterns[iLayer].size(); + + for (size_t iRof{0}; iRof < layerROFs[iLayer].size(); ++iRof) { + const auto& rof = layerROFs[iLayer][iRof]; + const int first = rof.getFirstEntry(); + const int last = first + rof.getNEntries(); + + for (int clusterId{first}; clusterId < last; ++clusterId) { + if (pattPtr + 2 > pattEnd) { + LOGP(error, "Pattern stream exhausted while decoding layer {} cluster {}", iLayer, clusterId); + break; + } + const uint8_t* pattForCluster = pattPtr; + const int nBytes = (pattForCluster[0] * pattForCluster[1] + 7) / 8; + if (pattPtr + 2 + nBytes > pattEnd) { + LOGP(error, "Pattern stream truncated for layer {} cluster {}", iLayer, clusterId); + break; + } + const int pattAdvance = 2 + nBytes; + + if (clusterId < 0 || clusterId >= static_cast(layerClusters[iLayer].size())) { + LOGP(warning, "Skipping out-of-range TRK cluster {} on layer {}", clusterId, iLayer); + pattPtr += pattAdvance; + continue; + } + + const auto& c = layerClusters[iLayer][clusterId]; + if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { + pattPtr += pattAdvance; + continue; + } + + const int clusterLayer = startLayer[c.subDetID] + c.layer; + if (clusterLayer != iLayer) { + LOGP(error, "Skipping cluster from layer {} found in TRK layer stream {}", clusterLayer, iLayer); + pattPtr += pattAdvance; + continue; + } + + auto locXYZ = Clusterer::getClusterLocalCoordinates(c, pattForCluster, yPlaneMLOT); + pattPtr += pattAdvance; + + const auto gloXYZ = geom->getMatrixL2G(c.chipID) * locXYZ; + + float alpha{0.f}; + o2::math_utils::Point3D trkXYZ; + if (c.subDetID == 1) { + alpha = geom->getSensorRefAlphaMLOT(c.chipID); + trkXYZ = geom->getMatrixT2L(c.chipID - geom->getNumberOfActivePartsVD()) ^ locXYZ; + } else { + const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + alpha = std::atan2(gloXYZ.Y(), gloXYZ.X()); + o2::math_utils::bringTo02Pi(alpha); + trkXYZ.SetX(r); + trkXYZ.SetY(0.f); + trkXYZ.SetZ(gloXYZ.Z()); + } + + const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + this->mMinR[iLayer] = std::min(this->mMinR[iLayer], r); + this->mMaxR[iLayer] = std::max(this->mMaxR[iLayer], r); + + const float sigmaY2 = (c.subDetID == 0) + ? 0.25f * SegmentationChip::PitchRowVD * SegmentationChip::PitchRowVD + : 0.25f * SegmentationChip::PitchRowMLOT * SegmentationChip::PitchRowMLOT; + const float sigmaZ2 = (c.subDetID == 0) + ? 0.25f * SegmentationChip::PitchColVD * SegmentationChip::PitchColVD + : 0.25f * SegmentationChip::PitchColMLOT * SegmentationChip::PitchColMLOT; + + this->addTrackingFrameInfoToLayer(iLayer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, + std::array{trkXYZ.y(), trkXYZ.z()}, + std::array{sigmaY2, 0.f, sigmaZ2}); + this->addClusterToLayer(iLayer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[iLayer].size()); + this->addClusterExternalIndexToLayer(iLayer, clusterId); + this->mClusterSize[iLayer].push_back(std::clamp(static_cast(c.size), 0u, 255u)); + } + + this->mROFramesClusters[iLayer][iRof + 1] = this->mUnsortedClusters[iLayer].size(); + } + } + + for (auto i = 0; i < this->mNTrackletsPerCluster.size(); ++i) { + this->mNTrackletsPerCluster[i].resize(this->mUnsortedClusters[1].size()); + this->mNTrackletsPerClusterSum[i].resize(this->mUnsortedClusters[1].size() + 1); + } + + if (mcLabels != nullptr) { + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mClusterLabels[iLayer] = (*mcLabels)[iLayer]; + } + } + + return nRofs; +} + +template +void TimeFrameMixin::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup) +{ + auto mcheader = new o2::dataformats::MCEventHeader; + mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); + + this->mPrimaryVertices.clear(); + this->mPrimaryVerticesLabels.clear(); + + const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); + const auto rofLength = clockLayer.mROFLength; + + int iRof{0}; + for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { + mcHeaderTree->GetEntry(iEvent); + o2::its::Vertex vertex; + vertex.setTimeStamp(o2::its::TimeEstBC{ + clockLayer.getROFStartInBC(iRof), + static_cast(rofLength)}); + vertex.setXYZ(mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); + vertex.setNContributors(30); + vertex.setChi2(0.f); + LOGP(debug, "ROF {}: Added primary vertex at ({}, {}, {})", iRof, mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); + this->addPrimaryVertex(vertex); + this->addPrimaryVertexLabel({o2::MCCompLabel{o2::MCCompLabel::maxTrackID(), static_cast(iEvent), 0, false}, 1.f}); + if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { + iRof++; + } + } + updateHostROFVertexLookupTable(); +} + +template +void TimeFrameMixin::addTruthSeedingVertices(gsl::span rofs) +{ + LOGP(info, "TRK: using truth seeds as vertices from DigitizationContext"); + this->mPrimaryVertices.clear(); + this->mPrimaryVerticesLabels.clear(); + + const auto dc = o2::steer::DigitizationContext::loadFromFile("collisioncontext.root"); + const auto irs = dc->getEventRecords(); + o2::steer::MCKinematicsReader mcReader(dc); + + std::vector rofStartBC(rofs.size()); + for (size_t i = 0; i < rofs.size(); ++i) { + rofStartBC[i] = rofs[i].getBCData().toLong(); + } + + const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); + const auto rofLength = clockLayer.mROFLength; + + using Vertex = o2::its::Vertex; + struct VertInfo { + std::pmr::vector vertices; + std::pmr::vector srcs; + std::pmr::vector events; + }; + std::map vertMap; + + const int iSrc = 0; + auto eveId2colId = dc->getCollisionIndicesForSource(iSrc); + for (int iEve{0}; iEve < mcReader.getNEvents(iSrc); ++iEve) { + const auto& ir = irs[eveId2colId[iEve]]; + if (!ir.isDummy()) { + const auto& eve = mcReader.getMCEventHeader(iSrc, iEve); + const int64_t evBC = ir.toLong(); + auto it = std::upper_bound(rofStartBC.begin(), rofStartBC.end(), evBC); + if (it != rofStartBC.begin()) { + --it; + int rofId = static_cast(std::distance(rofStartBC.begin(), it)); + auto* mr = this->mMemoryPool.get(); + if (!vertMap.contains(rofId)) { + vertMap[rofId] = { + .vertices = std::pmr::vector(mr), + .srcs = std::pmr::vector(mr), + .events = std::pmr::vector(mr), + }; + } + Vertex vert; + vert.setTimeStamp(o2::its::TimeEstBC{ + clockLayer.getROFStartInBC(rofId), + static_cast(rofLength)}); + vert.setNContributors(std::max(1L, std::ranges::count_if( + mcReader.getTracks(iSrc, iEve), + [](const auto& trk) { + return trk.isPrimary() && trk.GetPt() > 0.05 && std::abs(trk.GetEta()) < 1.1; + }))); + vert.setXYZ((float)eve.GetX(), (float)eve.GetY(), (float)eve.GetZ()); + vert.setChi2(1); + constexpr float cov = 50e-9f; + vert.setCov(cov, cov, cov, cov, cov, cov); + vertMap[rofId].vertices.push_back(vert); + vertMap[rofId].srcs.push_back(iSrc); + vertMap[rofId].events.push_back(iEve); + } + } + mcReader.releaseTracksForSourceAndEvent(iSrc, iEve); + } + + size_t nVerts{0}; + auto* mr = this->mMemoryPool.get(); + for (int iROF{0}; iROF < static_cast(rofs.size()); ++iROF) { + std::pmr::vector verts(mr); + std::pmr::vector> polls(mr); + if (vertMap.contains(iROF)) { + const auto& info = vertMap[iROF]; + verts = info.vertices; + nVerts += verts.size(); + for (size_t i{0}; i < verts.size(); ++i) { + o2::MCCompLabel lbl(o2::MCCompLabel::maxTrackID(), info.events[i], info.srcs[i], false); + polls.emplace_back(lbl, 1.f); + } + } + for (const auto& vert : verts) { + this->addPrimaryVertex(vert); + } + for (const auto& label : polls) { + this->addPrimaryVertexLabel(label); + } + } + updateHostROFVertexLookupTable(); + LOGP(info, "TRK truth seeding: {}/{} ROFs with {} vertices -> ={:.2f}", + vertMap.size(), rofs.size(), nVerts, + vertMap.size() > 0 ? (float)nVerts / (float)vertMap.size() : 0.f); +} + +} // namespace o2::trk + +#endif // ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEMIXIN_H diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx index 5cd652c32f600..1f7997b2e3968 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx @@ -10,577 +10,16 @@ // or submit itself to any jurisdiction. /// /// \file TimeFrame.cxx -/// \brief TRK TimeFrame implementation +/// \brief Explicit instantiation of TimeFrameMixin and TimeFrame for the +/// ITS CPU base. Shared method bodies live in TimeFrameMixin.h. /// #include "ALICE3GlobalReconstruction/TimeFrame.h" -#include "TRKReconstruction/Clusterer.h" -#include "TRKSimulation/Hit.h" -#include "TRKBase/GeometryTGeo.h" -#include "TRKBase/SegmentationChip.h" -#include "Framework/Logger.h" -#include "SimulationDataFormat/MCEventHeader.h" -#include "SimulationDataFormat/DigitizationContext.h" -#include "Steer/MCKinematicsReader.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include namespace o2::trk { -template -void TimeFrame::initTimingTables(const std::array& timings) -{ - if (mTimingTablesInitialised) { - return; - } - typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; - typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; - typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - rofOverlapTable.defineLayer(iLayer, timings[iLayer]); - rofVertexLookupTable.defineLayer(iLayer, timings[iLayer]); - rofMaskTable.defineLayer(iLayer, timings[iLayer]); - } - rofOverlapTable.init(); - rofVertexLookupTable.init(); - rofMaskTable.init(); - // ALICE3 TRK currently runs without per-ROF selection — no FastMultEst, - // no PhysTrigger, no UPC iteration on this detector yet. Enable every ROF - // so useMultiplictyMask() acts as an effective no-op for the tracker until - // a real selector is ported (see ITS TrackingInterface.cxx for the - // reference implementation). - rofMaskTable.resetMask(1u); - this->setROFOverlapTable(std::move(rofOverlapTable)); - this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); - this->setMultiplicityCutMask(std::move(rofMaskTable)); - this->useMultiplictyMask(); - mTimingTablesInitialised = true; - - // One-shot log of resolved per-layer timing and mask state. - const auto maskView = this->getROFMaskView(); - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - LOGP(info, "TRK timing initialised: layer {}: {}", iLayer, timings[iLayer].asString()); - LOGP(info, "TRK ROF mask: {}", maskView.asString(iLayer)); - } -} - -template -void TimeFrame::deriveAndInitTiming(const std::array, nLayers>& layerROFs) -{ - if (mTimingTablesInitialised) { - return; - } - - // Anchor the TF at the earliest first-ROF BCData seen across layers, so - // every layer's mROFBias is non-negative and intra-anchor BC values stay - // bounded (LayerTiming uses uint32 BC counters). For non-staggered TRK - // every layer is anchored at BC=0 (mROFBias=0); per-layer biases only - // diverge once the digitiser actually staggers the readout. - o2::InteractionRecord anchor{0, 0}; - bool haveAnchor = false; - for (const auto& span : layerROFs) { - if (span.empty()) { - continue; - } - const auto& first = span.front().getBCData(); - if (!haveAnchor || first.toLong() < anchor.toLong()) { - anchor = first; - haveAnchor = true; - } - } - mTFAnchorIR = anchor; - const int64_t anchorBC = anchor.toLong(); - - std::array timings{}; - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - const auto& span = layerROFs[iLayer]; - auto& t = timings[iLayer]; - t.mNROFsTF = static_cast(span.size()); - - if (span.size() >= 2) { - const int64_t delta = span[1].getBCData().toLong() - span[0].getBCData().toLong(); - if (delta > 0) { - t.mROFLength = static_cast(delta); - } else { - LOGP(warning, "TRK layer {}: non-positive BC delta between rofs[0] and rofs[1] ({}); falling back to mROFLength=1", iLayer, delta); - t.mROFLength = 1; - } - } else { - if (span.size() == 1) { - LOGP(warning, "TRK layer {}: only one input ROF — cannot derive mROFLength; falling back to mROFLength=1", iLayer); - } - t.mROFLength = 1; - } - - if (!span.empty()) { - const int64_t bias = span.front().getBCData().toLong() - anchorBC; - // bias is guaranteed >= 0 by the anchor choice above - t.mROFBias = static_cast(bias); - } - t.mROFDelay = 0; - t.mROFAddTimeErr = 0; - } - - initTimingTables(timings); -} - -template -int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config) -{ - constexpr std::array startLayer{0, 3}; - const Long64_t nEvents = hitsTree->GetEntries(); - - gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); - - std::vector* trkHit = nullptr; - hitsTree->SetBranchAddress("TRKHit", &trkHit); - - const int inROFpileup{config.contains("inROFpileup") ? config["inROFpileup"].get() : 1}; - - // Calculate number of ROFs and initialize data structures - const int nRofs = (nEvents + inROFpileup - 1) / inROFpileup; - // Hit-tree path has no real BCData, so the timing stays at the placeholder - // (mROFLength = 1 BC, no delay/bias). For timing-sensitive work use the - // cluster path, which derives mROFLength from the input ROF BC stamps. - std::array timings{}; - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - timings[iLayer].mNROFsTF = static_cast(nRofs); - timings[iLayer].mROFLength = 1; - } - this->initTimingTables(timings); - const auto& timing = this->getROFOverlapTableView().getLayer(0); - if (timing.mNROFsTF != static_cast(nRofs)) { - LOGP(fatal, "TRK: inconsistent number of ROFs across TFs: timing has {}, hit-tree path produced {}", timing.mNROFsTF, nRofs); - } - - // Reset and prepare ROF data structures - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - this->mMinR[iLayer] = std::numeric_limits::max(); - this->mMaxR[iLayer] = std::numeric_limits::lowest(); - this->mROFramesClusters[iLayer].clear(); - this->mROFramesClusters[iLayer].resize(nRofs + 1, 0); - this->mUnsortedClusters[iLayer].clear(); - this->mTrackingFrameInfo[iLayer].clear(); - this->mClusterExternalIndices[iLayer].clear(); - this->mClusterSize[iLayer].clear(); - } - - // Pre-count hits to reserve memory efficiently - std::array clusterCountPerLayer{}; - for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { - hitsTree->GetEntry(iEvent); - for (const auto& hit : *trkHit) { - if (gman->getDisk(hit.GetDetectorID()) != -1) { - continue; // skip non-barrel hits - } - int subDetID = gman->getSubDetID(hit.GetDetectorID()); - const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); - if (layer >= nLayers) { - continue; - } - ++clusterCountPerLayer[layer]; - } - } - - // Reserve memory for all layers - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mClusterSize[iLayer].reserve(clusterCountPerLayer[iLayer]); - } - - std::array resolution{0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004}; - if (config["geometry"]["pitch"].size() == nLayers) { - for (int iLayer{0}; iLayer < config["geometry"]["pitch"].size(); ++iLayer) { - LOGP(info, "Setting resolution for layer {} from config", iLayer); - LOGP(info, "Layer {} pitch {} cm", iLayer, config["geometry"]["pitch"][iLayer].get()); - resolution[iLayer] = config["geometry"]["pitch"][iLayer].get() / std::sqrt(12.f); - } - } - LOGP(info, "Number of active parts in VD: {}", gman->getNumberOfActivePartsVD()); - - int hitCounter{0}; - auto labels = new dataformats::MCTruthContainer(); - - int iRof{0}; // Current ROF index - for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { - hitsTree->GetEntry(iEvent); - - for (auto& hit : *trkHit) { - if (gman->getDisk(hit.GetDetectorID()) != -1) { - continue; // skip non-barrel hits for this test - } - int subDetID = gman->getSubDetID(hit.GetDetectorID()); - const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); - - float alpha{0.f}; - o2::math_utils::Point3D gloXYZ; - o2::math_utils::Point3D trkXYZ; - float r{0.f}; - if (layer >= nLayers) { - continue; - } - if (layer >= 3) { - int chipID = hit.GetDetectorID(); - alpha = gman->getSensorRefAlphaMLOT(chipID); - const o2::math_utils::Transform3D& l2g = gman->getMatrixL2G(chipID); - auto locXYZ = l2g ^ (hit.GetPos()); - locXYZ.SetX(locXYZ.X() + gRandom->Gaus(0.0, resolution[layer])); - locXYZ.SetZ(locXYZ.Z() + gRandom->Gaus(0.0, resolution[layer])); - gloXYZ = gman->getMatrixL2G(chipID) * locXYZ; - trkXYZ = gman->getMatrixT2L(chipID - gman->getNumberOfActivePartsVD()) ^ locXYZ; - r = std::hypot(gloXYZ.X(), gloXYZ.Y()); - } else { - const auto& hitPos = hit.GetPos(); - r = std::hypot(hitPos.X(), hitPos.Y()); - alpha = std::atan2(hitPos.Y(), hitPos.X()) + gRandom->Gaus(0.0, resolution[layer] / r); - o2::math_utils::bringTo02Pi(alpha); - gloXYZ.SetX(r * std::cos(alpha)); - gloXYZ.SetY(r * std::sin(alpha)); - gloXYZ.SetZ(hitPos.Z() + gRandom->Gaus(0.0, resolution[layer])); - trkXYZ.SetX(r); - trkXYZ.SetY(0.f); - trkXYZ.SetZ(gloXYZ.Z()); - } - this->mMinR[layer] = std::min(this->mMinR[layer], r); - this->mMaxR[layer] = std::max(this->mMaxR[layer], r); - this->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, - std::array{trkXYZ.y(), trkXYZ.z()}, - std::array{resolution[layer] * resolution[layer], 0., resolution[layer] * resolution[layer]}); - /// Rotate to the global frame - this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); - this->addClusterExternalIndexToLayer(layer, hitCounter); - this->mClusterSize[layer].push_back(1); // For compatibility with cluster-based tracking, set cluster size to 1 for hits - MCCompLabel label{hit.GetTrackID(), static_cast(iEvent), 0}; - labels->addElement(hitCounter, label); - hitCounter++; - } - trkHit->clear(); - - // Update ROF structure when we complete an ROF or reach the last event - if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { - iRof++; - for (unsigned int iLayer{0}; iLayer < this->mUnsortedClusters.size(); ++iLayer) { - this->mROFramesClusters[iLayer][iRof] = this->mUnsortedClusters[iLayer].size(); // effectively calculating an exclusive sum - } - // Update primary vertices ROF structure - } - } - // The MC labels container is the same pointer for every event — assign once - // after the loop instead of redundantly on each iteration. - this->mClusterLabels[0] = labels; - return nRofs; -} - -template -int TimeFrame::loadROFrameData(gsl::span rofs, - gsl::span clusters, - gsl::span patterns, - const dataformats::MCTruthContainer* mcLabels, - float yPlaneMLOT) -{ - constexpr std::array startLayer{0, 3}; - GeometryTGeo* geom = GeometryTGeo::Instance(); - geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); - - const int nRofs = rofs.size(); - // Per-layer LayerTiming (and mROFLength / mROFBias) must already be in place; - // the cluster path requires the caller (TrackerSpec) to invoke - // deriveAndInitTiming() with the per-layer ROF spans first. We deliberately - // do not derive timing from the merged `rofs` span here — that would lose - // per-layer information needed for staggered readouts. - if (!mTimingTablesInitialised) { - LOGP(fatal, "TRK::loadROFrameData: timing tables not initialised — call deriveAndInitTiming() first"); - } - const auto& timing = this->getROFOverlapTableView().getLayer(0); - if (timing.mNROFsTF != static_cast(nRofs)) { - LOGP(fatal, "TRK: inconsistent number of ROFs across TFs: timing has {}, cluster path received {}", timing.mNROFsTF, nRofs); - } - - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - this->mMinR[iLayer] = std::numeric_limits::max(); - this->mMaxR[iLayer] = std::numeric_limits::lowest(); - this->mROFramesClusters[iLayer].clear(); - this->mROFramesClusters[iLayer].resize(nRofs + 1, 0); - this->mUnsortedClusters[iLayer].clear(); - this->mTrackingFrameInfo[iLayer].clear(); - this->mClusterExternalIndices[iLayer].clear(); - this->mClusterSize[iLayer].clear(); - } - - std::array clusterCountPerLayer{}; - for (const auto& c : clusters) { - if (c.subDetID < 0 || c.subDetID > 1) { - continue; - } - if (c.disk != -1) { - continue; // skip non-barrel clusters for now - } - const int layer = startLayer[c.subDetID] + c.layer; - if (layer < 0 || layer >= nLayers) { - continue; - } - ++clusterCountPerLayer[layer]; - } - - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mClusterSize[iLayer].reserve(clusterCountPerLayer[iLayer]); - } - - const uint8_t* pattPtr = patterns.data(); - const uint8_t* pattEnd = pattPtr + patterns.size(); - - for (size_t iRof{0}; iRof < rofs.size(); ++iRof) { - const auto& rof = rofs[iRof]; - const int first = rof.getFirstEntry(); - const int last = first + rof.getNEntries(); - - for (int clusterId{first}; clusterId < last; ++clusterId) { - // Parse the pattern header up-front so we always know how many bytes - // this cluster occupies in the pattern stream. The stream is keyed - // per-cluster: every `continue` below MUST advance pattPtr by - // pattAdvance, otherwise the next cluster decodes from a stale - // offset and the whole layer's geometry is corrupted. - if (pattPtr + 2 > pattEnd) { - LOGP(error, "Pattern stream exhausted while decoding cluster {}", clusterId); - break; - } - const uint8_t* pattForCluster = pattPtr; - const int nBytes = (pattForCluster[0] * pattForCluster[1] + 7) / 8; - if (pattPtr + 2 + nBytes > pattEnd) { - LOGP(error, "Pattern stream truncated for cluster {}", clusterId); - break; - } - const int pattAdvance = 2 + nBytes; - - if (clusterId < 0 || clusterId >= static_cast(clusters.size())) { - LOGP(warning, "Skipping out-of-range cluster id {} for ROF {}", clusterId, iRof); - pattPtr += pattAdvance; - continue; - } - - const auto& c = clusters[clusterId]; - if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { - pattPtr += pattAdvance; - continue; - } - - const int layer = startLayer[c.subDetID] + c.layer; - if (layer < 0 || layer >= nLayers) { - LOGP(error, "Skipping cluster with invalid layer {} (subDetID {}, layer {})", layer, c.subDetID, c.layer); - pattPtr += pattAdvance; - continue; - } - - auto locXYZ = Clusterer::getClusterLocalCoordinates(c, pattForCluster, yPlaneMLOT); - pattPtr += pattAdvance; - - const auto gloXYZ = geom->getMatrixL2G(c.chipID) * locXYZ; - - float alpha{0.f}; - o2::math_utils::Point3D trkXYZ; - if (c.subDetID == 1) { - alpha = geom->getSensorRefAlphaMLOT(c.chipID); - trkXYZ = geom->getMatrixT2L(c.chipID - geom->getNumberOfActivePartsVD()) ^ locXYZ; - } else { - const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); - alpha = std::atan2(gloXYZ.Y(), gloXYZ.X()); - o2::math_utils::bringTo02Pi(alpha); - trkXYZ.SetX(r); - trkXYZ.SetY(0.f); - trkXYZ.SetZ(gloXYZ.Z()); - } - - const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); - this->mMinR[layer] = std::min(this->mMinR[layer], r); - this->mMaxR[layer] = std::max(this->mMaxR[layer], r); - - const float sigmaY2 = (c.subDetID == 0) - ? 0.25f * SegmentationChip::PitchRowVD * SegmentationChip::PitchRowVD - : 0.25f * SegmentationChip::PitchRowMLOT * SegmentationChip::PitchRowMLOT; - const float sigmaZ2 = (c.subDetID == 0) - ? 0.25f * SegmentationChip::PitchColVD * SegmentationChip::PitchColVD - : 0.25f * SegmentationChip::PitchColMLOT * SegmentationChip::PitchColMLOT; - - this->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, - std::array{trkXYZ.y(), trkXYZ.z()}, - std::array{sigmaY2, 0.f, sigmaZ2}); - this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); - this->addClusterExternalIndexToLayer(layer, clusterId); - this->mClusterSize[layer].push_back(std::clamp(static_cast(c.size), 0u, 255u)); - } - - for (unsigned int iL{0}; iL < this->mUnsortedClusters.size(); ++iL) { - this->mROFramesClusters[iL][iRof + 1] = this->mUnsortedClusters[iL].size(); - } - } - - for (auto i = 0; i < this->mNTrackletsPerCluster.size(); ++i) { - this->mNTrackletsPerCluster[i].resize(this->mUnsortedClusters[1].size()); - this->mNTrackletsPerClusterSum[i].resize(this->mUnsortedClusters[1].size() + 1); - } - - if (mcLabels != nullptr) { - this->mClusterLabels[0] = mcLabels; - } - - return nRofs; -} - -template -void TimeFrame::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup) -{ - auto mcheader = new o2::dataformats::MCEventHeader; - mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); - - this->mPrimaryVertices.clear(); - this->mPrimaryVerticesLabels.clear(); - - // Vertex timestamps live in the clock layer's intra-anchor BC frame - // (anchor = mTFAnchorIR set by deriveAndInitTiming, or {0,0} for the - // hit-tree placeholder). TimeEstBC interval [t0, t0+tE] covers one full - // clock-layer ROF so that clockLayer.getROF(vertex.getTimeStamp().lower()) - // round-trips back to iRof. - const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); - const auto rofLength = clockLayer.mROFLength; - - int iRof{0}; - for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { - mcHeaderTree->GetEntry(iEvent); - o2::its::Vertex vertex; - vertex.setTimeStamp(o2::its::TimeEstBC{ - clockLayer.getROFStartInBC(iRof), - static_cast(rofLength)}); - vertex.setXYZ(mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); - vertex.setNContributors(30); - vertex.setChi2(0.f); - LOGP(debug, "ROF {}: Added primary vertex at ({}, {}, {})", iRof, mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); - this->addPrimaryVertex(vertex); - this->addPrimaryVertexLabel({o2::MCCompLabel{o2::MCCompLabel::maxTrackID(), static_cast(iEvent), 0, false}, 1.f}); - if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { - iRof++; - } - } - this->updateROFVertexLookupTable(); -} - - - -// Explicit template instantiation for TRK with 11 layers - -template -void TimeFrame::addTruthSeedingVertices(gsl::span rofs) -{ - LOGP(info, "TRK: using truth seeds as vertices from DigitizationContext"); - this->mPrimaryVertices.clear(); - this->mPrimaryVerticesLabels.clear(); - - const auto dc = o2::steer::DigitizationContext::loadFromFile("collisioncontext.root"); - const auto irs = dc->getEventRecords(); - o2::steer::MCKinematicsReader mcReader(dc); - - // Pre-compute ROF start BC (as absolute long) for binary search - std::vector rofStartBC(rofs.size()); - for (size_t i = 0; i < rofs.size(); ++i) { - rofStartBC[i] = rofs[i].getBCData().toLong(); - } - - // Vertex timestamps live in the clock layer's intra-anchor BC frame - // (anchor = mTFAnchorIR set by deriveAndInitTiming). TimeEstBC interval - // covers one full clock-layer ROF, so - // clockLayer.getROF(vertex.getTimeStamp().lower()) maps back to rofId. - const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); - const auto rofLength = clockLayer.mROFLength; - - using Vertex = o2::its::Vertex; - struct VertInfo { - std::pmr::vector vertices; - std::pmr::vector srcs; - std::pmr::vector events; - }; - std::map vertMap; - - const int iSrc = 0; // primary collision generator source - auto eveId2colId = dc->getCollisionIndicesForSource(iSrc); - for (int iEve{0}; iEve < mcReader.getNEvents(iSrc); ++iEve) { - const auto& ir = irs[eveId2colId[iEve]]; - if (!ir.isDummy()) { - const auto& eve = mcReader.getMCEventHeader(iSrc, iEve); - const int64_t evBC = ir.toLong(); - // Find ROF: last ROF whose start BC <= evBC - auto it = std::upper_bound(rofStartBC.begin(), rofStartBC.end(), evBC); - if (it != rofStartBC.begin()) { - --it; - int rofId = static_cast(std::distance(rofStartBC.begin(), it)); - auto* mr = this->mMemoryPool.get(); - if (!vertMap.contains(rofId)) { - vertMap[rofId] = { - .vertices = std::pmr::vector(mr), - .srcs = std::pmr::vector(mr), - .events = std::pmr::vector(mr), - }; - } - Vertex vert; - vert.setTimeStamp(o2::its::TimeEstBC{ - clockLayer.getROFStartInBC(rofId), - static_cast(rofLength)}); - vert.setNContributors(std::max(1L, std::ranges::count_if( - mcReader.getTracks(iSrc, iEve), - [](const auto& trk) { - return trk.isPrimary() && trk.GetPt() > 0.05 && std::abs(trk.GetEta()) < 1.1; - }))); - vert.setXYZ((float)eve.GetX(), (float)eve.GetY(), (float)eve.GetZ()); - vert.setChi2(1); - constexpr float cov = 50e-9f; - vert.setCov(cov, cov, cov, cov, cov, cov); - vertMap[rofId].vertices.push_back(vert); - vertMap[rofId].srcs.push_back(iSrc); - vertMap[rofId].events.push_back(iEve); - } - } - mcReader.releaseTracksForSourceAndEvent(iSrc, iEve); - } - - size_t nVerts{0}; - auto* mr = this->mMemoryPool.get(); - for (int iROF{0}; iROF < static_cast(rofs.size()); ++iROF) { - std::pmr::vector verts(mr); - std::pmr::vector> polls(mr); - if (vertMap.contains(iROF)) { - const auto& info = vertMap[iROF]; - verts = info.vertices; - nVerts += verts.size(); - for (size_t i{0}; i < verts.size(); ++i) { - o2::MCCompLabel lbl(o2::MCCompLabel::maxTrackID(), info.events[i], info.srcs[i], false); - polls.emplace_back(lbl, 1.f); - } - } - for (const auto& vert : verts) { - this->addPrimaryVertex(vert); - } - for (const auto& label : polls) { - this->addPrimaryVertexLabel(label); - } - } - this->updateROFVertexLookupTable(); - LOGP(info, "TRK truth seeding: {}/{} ROFs with {} vertices -> ={:.2f}", - vertMap.size(), rofs.size(), nVerts, - vertMap.size() > 0 ? (float)nVerts / (float)vertMap.size() : 0.f); -} - +template class TimeFrameMixin<11, o2::its::TimeFrame<11>>; template class TimeFrame<11>; } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx index 36e95386b70b6..714ead765b005 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx @@ -8,542 +8,18 @@ // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +/// +/// \file TimeFrameGPU.cxx +/// \brief Explicit instantiation of TimeFrameMixin and TimeFrameGPU for the +/// ITS GPU base. Shared method bodies live in TimeFrameMixin.h. +/// #include "ALICE3GlobalReconstruction/TimeFrameGPU.h" -#include "TRKReconstruction/Clusterer.h" -#include "TRKSimulation/Hit.h" -#include "TRKBase/GeometryTGeo.h" -#include "TRKBase/SegmentationChip.h" -#include "Framework/Logger.h" -#include "SimulationDataFormat/MCEventHeader.h" -#include "SimulationDataFormat/DigitizationContext.h" -#include "Steer/MCKinematicsReader.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - namespace o2::trk { -template -void TimeFrameGPU::initTimingTables(const std::array& timings) -{ - if (mTimingTablesInitialised) { - return; - } - typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; - typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; - typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - rofOverlapTable.defineLayer(iLayer, timings[iLayer]); - rofVertexLookupTable.defineLayer(iLayer, timings[iLayer]); - rofMaskTable.defineLayer(iLayer, timings[iLayer]); - } - rofOverlapTable.init(); - rofVertexLookupTable.init(); - rofMaskTable.init(); - // ALICE3 TRK currently runs without per-ROF selection — see CPU mirror - // for the design note. All-pass policy until a real selector is ported. - rofMaskTable.resetMask(1u); - this->setROFOverlapTable(std::move(rofOverlapTable)); - this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); - this->setMultiplicityCutMask(std::move(rofMaskTable)); - this->useMultiplictyMask(); - mTimingTablesInitialised = true; - - const auto maskView = this->getROFMaskView(); - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - LOGP(info, "TRK timing initialised: layer {}: {}", iLayer, timings[iLayer].asString()); - LOGP(info, "TRK ROF mask: {}", maskView.asString(iLayer)); - } -} - -template -void TimeFrameGPU::deriveAndInitTiming(const std::array, nLayers>& layerROFs) -{ - if (mTimingTablesInitialised) { - return; - } - - // Mirror of o2::trk::TimeFrame::deriveAndInitTiming. Anchor at the earliest - // first-ROF BCData seen across layers; per-layer mROFLength comes from the - // BC delta between consecutive ROFs of that layer. - o2::InteractionRecord anchor{0, 0}; - bool haveAnchor = false; - for (const auto& span : layerROFs) { - if (span.empty()) { - continue; - } - const auto& first = span.front().getBCData(); - if (!haveAnchor || first.toLong() < anchor.toLong()) { - anchor = first; - haveAnchor = true; - } - } - mTFAnchorIR = anchor; - const int64_t anchorBC = anchor.toLong(); - - std::array timings{}; - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - const auto& span = layerROFs[iLayer]; - auto& t = timings[iLayer]; - t.mNROFsTF = static_cast(span.size()); - - if (span.size() >= 2) { - const int64_t delta = span[1].getBCData().toLong() - span[0].getBCData().toLong(); - if (delta > 0) { - t.mROFLength = static_cast(delta); - } else { - LOGP(warning, "TRK layer {}: non-positive BC delta between rofs[0] and rofs[1] ({}); falling back to mROFLength=1", iLayer, delta); - t.mROFLength = 1; - } - } else { - if (span.size() == 1) { - LOGP(warning, "TRK layer {}: only one input ROF — cannot derive mROFLength; falling back to mROFLength=1", iLayer); - } - t.mROFLength = 1; - } - - if (!span.empty()) { - const int64_t bias = span.front().getBCData().toLong() - anchorBC; - t.mROFBias = static_cast(bias); - } - t.mROFDelay = 0; - t.mROFAddTimeErr = 0; - } - - initTimingTables(timings); -} - -template -int TimeFrameGPU::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config) -{ - constexpr std::array startLayer{0, 3}; - const Long64_t nEvents = hitsTree->GetEntries(); - - gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); - - std::vector* trkHit = nullptr; - hitsTree->SetBranchAddress("TRKHit", &trkHit); - - const int inROFpileup{config.contains("inROFpileup") ? config["inROFpileup"].get() : 1}; - const int nRofs = (nEvents + inROFpileup - 1) / inROFpileup; - // Hit-tree path has no real BCData; keep placeholder timing - // (mROFLength = 1 BC). Use the cluster path for timing-sensitive work. - std::array timings{}; - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - timings[iLayer].mNROFsTF = static_cast(nRofs); - timings[iLayer].mROFLength = 1; - } - this->initTimingTables(timings); - const auto& timing = this->getROFOverlapTableView().getLayer(0); - if (timing.mNROFsTF != static_cast(nRofs)) { - LOGP(fatal, "TRK: inconsistent number of ROFs across TFs: timing has {}, hit-tree path produced {}", timing.mNROFsTF, nRofs); - } - - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - this->mMinR[iLayer] = std::numeric_limits::max(); - this->mMaxR[iLayer] = std::numeric_limits::lowest(); - this->mROFramesClusters[iLayer].clear(); - this->mROFramesClusters[iLayer].resize(nRofs + 1, 0); - this->mUnsortedClusters[iLayer].clear(); - this->mTrackingFrameInfo[iLayer].clear(); - this->mClusterExternalIndices[iLayer].clear(); - this->mClusterSize[iLayer].clear(); - } - - std::array clusterCountPerLayer{}; - for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { - hitsTree->GetEntry(iEvent); - for (const auto& hit : *trkHit) { - if (gman->getDisk(hit.GetDetectorID()) != -1) { - continue; - } - int subDetID = gman->getSubDetID(hit.GetDetectorID()); - const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); - if (layer >= nLayers) { - continue; - } - ++clusterCountPerLayer[layer]; - } - } - - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mClusterSize[iLayer].reserve(clusterCountPerLayer[iLayer]); - } - - std::array resolution{0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004}; - if (config["geometry"]["pitch"].size() == nLayers) { - for (int iLayer{0}; iLayer < config["geometry"]["pitch"].size(); ++iLayer) { - resolution[iLayer] = config["geometry"]["pitch"][iLayer].get() / std::sqrt(12.f); - } - } - - int hitCounter{0}; - auto labels = new dataformats::MCTruthContainer(); - - int iRof{0}; - for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { - hitsTree->GetEntry(iEvent); - - for (auto& hit : *trkHit) { - if (gman->getDisk(hit.GetDetectorID()) != -1) { - continue; - } - int subDetID = gman->getSubDetID(hit.GetDetectorID()); - const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); - - float alpha{0.f}; - o2::math_utils::Point3D gloXYZ; - o2::math_utils::Point3D trkXYZ; - float r{0.f}; - if (layer >= nLayers) { - continue; - } - if (layer >= 3) { - int chipID = hit.GetDetectorID(); - alpha = gman->getSensorRefAlphaMLOT(chipID); - const o2::math_utils::Transform3D& l2g = gman->getMatrixL2G(chipID); - auto locXYZ = l2g ^ (hit.GetPos()); - locXYZ.SetX(locXYZ.X() + gRandom->Gaus(0.0, resolution[layer])); - locXYZ.SetZ(locXYZ.Z() + gRandom->Gaus(0.0, resolution[layer])); - gloXYZ = gman->getMatrixL2G(chipID) * locXYZ; - trkXYZ = gman->getMatrixT2L(chipID - gman->getNumberOfActivePartsVD()) ^ locXYZ; - r = std::hypot(gloXYZ.X(), gloXYZ.Y()); - } else { - const auto& hitPos = hit.GetPos(); - r = std::hypot(hitPos.X(), hitPos.Y()); - alpha = std::atan2(hitPos.Y(), hitPos.X()) + gRandom->Gaus(0.0, resolution[layer] / r); - o2::math_utils::bringTo02Pi(alpha); - gloXYZ.SetX(r * std::cos(alpha)); - gloXYZ.SetY(r * std::sin(alpha)); - gloXYZ.SetZ(hitPos.Z() + gRandom->Gaus(0.0, resolution[layer])); - trkXYZ.SetX(r); - trkXYZ.SetY(0.f); - trkXYZ.SetZ(gloXYZ.Z()); - } - this->mMinR[layer] = std::min(this->mMinR[layer], r); - this->mMaxR[layer] = std::max(this->mMaxR[layer], r); - this->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, - std::array{trkXYZ.y(), trkXYZ.z()}, - std::array{resolution[layer] * resolution[layer], 0., resolution[layer] * resolution[layer]}); - this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); - this->addClusterExternalIndexToLayer(layer, hitCounter); - this->mClusterSize[layer].push_back(1); - MCCompLabel label{hit.GetTrackID(), static_cast(iEvent), 0}; - labels->addElement(hitCounter, label); - ++hitCounter; - } - trkHit->clear(); - - if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { - ++iRof; - for (unsigned int iLayer{0}; iLayer < this->mUnsortedClusters.size(); ++iLayer) { - this->mROFramesClusters[iLayer][iRof] = this->mUnsortedClusters[iLayer].size(); - } - } - } - // The MC labels container is the same pointer for every event — assign once - // after the loop instead of redundantly on each iteration. - this->mClusterLabels[0] = labels; - return nRofs; -} - -template -int TimeFrameGPU::loadROFrameData(gsl::span rofs, - gsl::span clusters, - gsl::span patterns, - const dataformats::MCTruthContainer* mcLabels, - float yPlaneMLOT) -{ - constexpr std::array startLayer{0, 3}; - GeometryTGeo* geom = GeometryTGeo::Instance(); - geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); - - const int nRofs = rofs.size(); - // Per-layer LayerTiming must already be in place; the cluster path requires - // the caller (TrackerSpec) to invoke deriveAndInitTiming() with the per-layer - // ROF spans first. See the CPU mirror for design notes. - if (!mTimingTablesInitialised) { - LOGP(fatal, "TRK::loadROFrameData (GPU): timing tables not initialised — call deriveAndInitTiming() first"); - } - const auto& timing = this->getROFOverlapTableView().getLayer(0); - if (timing.mNROFsTF != static_cast(nRofs)) { - LOGP(fatal, "TRK: inconsistent number of ROFs across TFs: timing has {}, cluster path received {}", timing.mNROFsTF, nRofs); - } - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - this->mMinR[iLayer] = std::numeric_limits::max(); - this->mMaxR[iLayer] = std::numeric_limits::lowest(); - this->mROFramesClusters[iLayer].clear(); - this->mROFramesClusters[iLayer].resize(nRofs + 1, 0); - this->mUnsortedClusters[iLayer].clear(); - this->mTrackingFrameInfo[iLayer].clear(); - this->mClusterExternalIndices[iLayer].clear(); - this->mClusterSize[iLayer].clear(); - } - - std::array clusterCountPerLayer{}; - for (const auto& c : clusters) { - if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { - continue; - } - const int layer = startLayer[c.subDetID] + c.layer; - if (layer < 0 || layer >= nLayers) { - continue; - } - ++clusterCountPerLayer[layer]; - } - - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mClusterSize[iLayer].reserve(clusterCountPerLayer[iLayer]); - } - - const uint8_t* pattPtr = patterns.data(); - const uint8_t* pattEnd = pattPtr + patterns.size(); - - for (size_t iRof{0}; iRof < rofs.size(); ++iRof) { - const auto& rof = rofs[iRof]; - const int first = rof.getFirstEntry(); - const int last = first + rof.getNEntries(); - - for (int clusterId{first}; clusterId < last; ++clusterId) { - // Parse the pattern header up-front so we always know how many bytes - // this cluster occupies in the pattern stream. The stream is keyed - // per-cluster: every `continue` below MUST advance pattPtr by - // pattAdvance, otherwise the next cluster decodes from a stale - // offset and the whole layer's geometry is corrupted. - if (pattPtr + 2 > pattEnd) { - LOGP(error, "Pattern stream exhausted while decoding cluster {}", clusterId); - break; - } - const uint8_t* pattForCluster = pattPtr; - const int nBytes = (pattForCluster[0] * pattForCluster[1] + 7) / 8; - if (pattPtr + 2 + nBytes > pattEnd) { - LOGP(error, "Pattern stream truncated for cluster {}", clusterId); - break; - } - const int pattAdvance = 2 + nBytes; - - if (clusterId < 0 || clusterId >= static_cast(clusters.size())) { - LOGP(warning, "Skipping out-of-range cluster id {} for ROF {}", clusterId, iRof); - pattPtr += pattAdvance; - continue; - } - - const auto& c = clusters[clusterId]; - if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { - pattPtr += pattAdvance; - continue; - } - - const int layer = startLayer[c.subDetID] + c.layer; - if (layer < 0 || layer >= nLayers) { - LOGP(error, "Skipping cluster with invalid layer {} (subDetID {}, layer {})", layer, c.subDetID, c.layer); - pattPtr += pattAdvance; - continue; - } - - auto locXYZ = Clusterer::getClusterLocalCoordinates(c, pattForCluster, yPlaneMLOT); - pattPtr += pattAdvance; - - const auto gloXYZ = geom->getMatrixL2G(c.chipID) * locXYZ; - - float alpha{0.f}; - o2::math_utils::Point3D trkXYZ; - if (c.subDetID == 1) { - alpha = geom->getSensorRefAlphaMLOT(c.chipID); - trkXYZ = geom->getMatrixT2L(c.chipID - geom->getNumberOfActivePartsVD()) ^ locXYZ; - } else { - const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); - alpha = std::atan2(gloXYZ.Y(), gloXYZ.X()); - o2::math_utils::bringTo02Pi(alpha); - trkXYZ.SetX(r); - trkXYZ.SetY(0.f); - trkXYZ.SetZ(gloXYZ.Z()); - } - - const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); - this->mMinR[layer] = std::min(this->mMinR[layer], r); - this->mMaxR[layer] = std::max(this->mMaxR[layer], r); - - const float sigmaY2 = (c.subDetID == 0) - ? 0.25f * SegmentationChip::PitchRowVD * SegmentationChip::PitchRowVD - : 0.25f * SegmentationChip::PitchRowMLOT * SegmentationChip::PitchRowMLOT; - const float sigmaZ2 = (c.subDetID == 0) - ? 0.25f * SegmentationChip::PitchColVD * SegmentationChip::PitchColVD - : 0.25f * SegmentationChip::PitchColMLOT * SegmentationChip::PitchColMLOT; - - this->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, - std::array{trkXYZ.y(), trkXYZ.z()}, - std::array{sigmaY2, 0.f, sigmaZ2}); - this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); - this->addClusterExternalIndexToLayer(layer, clusterId); - this->mClusterSize[layer].push_back(std::clamp(static_cast(c.size), 0u, 255u)); - } - - for (unsigned int iL{0}; iL < this->mUnsortedClusters.size(); ++iL) { - this->mROFramesClusters[iL][iRof + 1] = this->mUnsortedClusters[iL].size(); - } - } - - for (auto i = 0; i < this->mNTrackletsPerCluster.size(); ++i) { - this->mNTrackletsPerCluster[i].resize(this->mUnsortedClusters[1].size()); - this->mNTrackletsPerClusterSum[i].resize(this->mUnsortedClusters[1].size() + 1); - } - - if (mcLabels != nullptr) { - this->mClusterLabels[0] = mcLabels; - } - - return nRofs; -} - -template -void TimeFrameGPU::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup) -{ - auto mcheader = new o2::dataformats::MCEventHeader; - mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); - - this->mPrimaryVertices.clear(); - this->mPrimaryVerticesLabels.clear(); - - // Vertex timestamps live in the clock layer's intra-anchor BC frame - // (anchor = mTFAnchorIR set by deriveAndInitTiming, or {0,0} for the - // hit-tree placeholder). See the CPU mirror for design notes. - const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); - const auto rofLength = clockLayer.mROFLength; - - int iRof{0}; - for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { - mcHeaderTree->GetEntry(iEvent); - o2::its::Vertex vertex; - vertex.setTimeStamp(o2::its::TimeEstBC{ - clockLayer.getROFStartInBC(iRof), - static_cast(rofLength)}); - vertex.setXYZ(mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); - vertex.setNContributors(30); - vertex.setChi2(0.f); - this->addPrimaryVertex(vertex); - this->addPrimaryVertexLabel({o2::MCCompLabel{o2::MCCompLabel::maxTrackID(), static_cast(iEvent), 0, false}, 1.f}); - if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { - ++iRof; - } - } - this->updateROFVertexLookupTable(); -} - -template -void TimeFrameGPU::addTruthSeedingVertices(gsl::span rofs) -{ - LOGP(info, "TRK: using truth seeds as vertices from DigitizationContext"); - this->mPrimaryVertices.clear(); - this->mPrimaryVerticesLabels.clear(); - - const auto dc = o2::steer::DigitizationContext::loadFromFile("collisioncontext.root"); - const auto irs = dc->getEventRecords(); - o2::steer::MCKinematicsReader mcReader(dc); - - std::vector rofStartBC(rofs.size()); - for (size_t i = 0; i < rofs.size(); ++i) { - rofStartBC[i] = rofs[i].getBCData().toLong(); - } - - // Vertex timestamps live in the clock layer's intra-anchor BC frame - // (anchor = mTFAnchorIR set by deriveAndInitTiming). - const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); - const auto rofLength = clockLayer.mROFLength; - - using Vertex = o2::its::Vertex; - struct VertInfo { - std::pmr::vector vertices; - std::pmr::vector srcs; - std::pmr::vector events; - }; - std::map vertMap; - - const int iSrc = 0; - auto eveId2colId = dc->getCollisionIndicesForSource(iSrc); - for (int iEve{0}; iEve < mcReader.getNEvents(iSrc); ++iEve) { - const auto& ir = irs[eveId2colId[iEve]]; - if (!ir.isDummy()) { - const auto& eve = mcReader.getMCEventHeader(iSrc, iEve); - const int64_t evBC = ir.toLong(); - auto it = std::upper_bound(rofStartBC.begin(), rofStartBC.end(), evBC); - if (it != rofStartBC.begin()) { - --it; - int rofId = static_cast(std::distance(rofStartBC.begin(), it)); - auto* mr = this->mMemoryPool.get(); - if (!vertMap.contains(rofId)) { - vertMap[rofId] = { - .vertices = std::pmr::vector(mr), - .srcs = std::pmr::vector(mr), - .events = std::pmr::vector(mr), - }; - } - Vertex vert; - vert.setTimeStamp(o2::its::TimeEstBC{ - clockLayer.getROFStartInBC(rofId), - static_cast(rofLength)}); - vert.setNContributors(std::max(1L, std::ranges::count_if( - mcReader.getTracks(iSrc, iEve), - [](const auto& trk) { - return trk.isPrimary() && trk.GetPt() > 0.05 && std::abs(trk.GetEta()) < 1.1; - }))); - vert.setXYZ((float)eve.GetX(), (float)eve.GetY(), (float)eve.GetZ()); - vert.setChi2(1); - constexpr float cov = 50e-9f; - vert.setCov(cov, cov, cov, cov, cov, cov); - vertMap[rofId].vertices.push_back(vert); - vertMap[rofId].srcs.push_back(iSrc); - vertMap[rofId].events.push_back(iEve); - } - } - mcReader.releaseTracksForSourceAndEvent(iSrc, iEve); - } - - size_t nVerts{0}; - auto* mr = this->mMemoryPool.get(); - for (int iROF{0}; iROF < static_cast(rofs.size()); ++iROF) { - std::pmr::vector verts(mr); - std::pmr::vector> polls(mr); - if (vertMap.contains(iROF)) { - const auto& info = vertMap[iROF]; - verts = info.vertices; - nVerts += verts.size(); - for (size_t i{0}; i < verts.size(); ++i) { - o2::MCCompLabel lbl(o2::MCCompLabel::maxTrackID(), info.events[i], info.srcs[i], false); - polls.emplace_back(lbl, 1.f); - } - } - for (const auto& vert : verts) { - this->addPrimaryVertex(vert); - } - for (const auto& label : polls) { - this->addPrimaryVertexLabel(label); - } - } - this->updateROFVertexLookupTable(); - LOGP(info, "TRK truth seeding: {}/{} ROFs with {} vertices -> ={:.2f}", - vertMap.size(), rofs.size(), nVerts, - vertMap.empty() ? 0.f : static_cast(nVerts) / static_cast(vertMap.size())); -} - +template class TimeFrameMixin<11, o2::its::gpu::TimeFrameGPU<11>>; template class TimeFrameGPU<11>; } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index c7b7dc161fedf..ba3d1977bf1d0 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -95,7 +95,6 @@ std::vector TrackerDPL::createTrackingParamsFromCon for (const auto& paramConfig : paramConfigJson) { o2::its::TrackingParameters params; - // Parse integer parameters if (paramConfig.contains("NLayers")) { params.NLayers = paramConfig["NLayers"].get(); } @@ -118,7 +117,6 @@ std::vector TrackerDPL::createTrackingParamsFromCon params.StartLayerMask = paramConfig["StartLayerMask"].get(); } - // Parse float parameters if (paramConfig.contains("NSigmaCut")) { params.NSigmaCut = paramConfig["NSigmaCut"].get(); } @@ -138,7 +136,6 @@ std::vector TrackerDPL::createTrackingParamsFromCon params.MaxChi2NDF = paramConfig["MaxChi2NDF"].get(); } - // Parse boolean parameters if (paramConfig.contains("UseDiamond")) { params.UseDiamond = paramConfig["UseDiamond"].get(); } @@ -173,7 +170,6 @@ std::vector TrackerDPL::createTrackingParamsFromCon params.DropTFUponFailure = paramConfig["DropTFUponFailure"].get(); } - // Parse vector parameters if (paramConfig.contains("LayerZ")) { params.LayerZ = paramConfig["LayerZ"].get>(); } @@ -196,19 +192,16 @@ std::vector TrackerDPL::createTrackingParamsFromCon params.MinPt = paramConfig["MinPt"].get>(); } - // Parse Diamond array if (paramConfig.contains("Diamond") && paramConfig["Diamond"].is_array() && paramConfig["Diamond"].size() == 3) { params.Diamond[0] = paramConfig["Diamond"][0].get(); params.Diamond[1] = paramConfig["Diamond"][1].get(); params.Diamond[2] = paramConfig["Diamond"][2].get(); } - // Parse size_t parameter if (paramConfig.contains("MaxMemory")) { params.MaxMemory = paramConfig["MaxMemory"].get(); } - // Parse CorrType enum if (paramConfig.contains("CorrType")) { int corrTypeInt = paramConfig["CorrType"].get(); params.CorrType = static_cast::MatCorrType>(corrTypeInt); @@ -242,7 +235,6 @@ void TrackerDPL::run(ProcessingContext& pc) mTaskArena = std::make_shared(1); /// TODO: make it configurable } - // Create tracking parameters from config and set them in the time frame auto trackingParams = createTrackingParamsFromConfig(); auto cput = mTimer.CpuTime(); @@ -329,87 +321,22 @@ void TrackerDPL::run(ProcessingContext& pc) } } - // Build per-layer LayerTiming from each layer's own ROF BCData. This is - // a precondition for loadROFrameData(): timing must be in place before - // clusters are loaded. Idempotent across TFs. timeFrame.deriveAndInitTiming(layerROFs); - std::vector rofRecords; - std::vector compClusters; - std::vector patterns; - dataformats::MCTruthContainer labels; - std::array, nLayers> patternOffsets; - rofRecords.reserve(nInputRofs); - for (int iLayer = 0; iLayer < nLayers; ++iLayer) { - compClusters.reserve(compClusters.size() + layerClusters[iLayer].size()); - patterns.reserve(patterns.size() + layerPatterns[iLayer].size()); - - auto& offsets = patternOffsets[iLayer]; - offsets.reserve(layerClusters[iLayer].size() + 1); - offsets.push_back(0); - size_t patternOffset{0}; - for (size_t iCluster = 0; iCluster < layerClusters[iLayer].size(); ++iCluster) { - if (patternOffset + 2 > layerPatterns[iLayer].size()) { - LOGP(error, "Pattern stream exhausted while indexing TRK layer {} cluster {}", iLayer, iCluster); - offsets.push_back(layerPatterns[iLayer].size()); - continue; - } - const auto* pattForCluster = layerPatterns[iLayer].data() + patternOffset; - const size_t nBytes = (pattForCluster[0] * pattForCluster[1] + 7) / 8; - patternOffset = std::min(patternOffset + 2 + nBytes, layerPatterns[iLayer].size()); - offsets.push_back(patternOffset); - } - } - + std::vector truthSeedROFs; + truthSeedROFs.reserve(nInputRofs); for (size_t iRof = 0; iRof < nInputRofs; ++iRof) { - o2::trk::ROFRecord mergedROF; - mergedROF.setFirstEntry(compClusters.size()); - mergedROF.setROFrame(iRof); - - bool hasBCData{false}; for (int iLayer = 0; iLayer < nLayers; ++iLayer) { - if (iRof >= layerROFs[iLayer].size()) { - continue; + if (iRof < layerROFs[iLayer].size()) { + truthSeedROFs.push_back(layerROFs[iLayer][iRof]); + break; } - const auto& layerROF = layerROFs[iLayer][iRof]; - if (!hasBCData) { - mergedROF.setBCData(layerROF.getBCData()); - hasBCData = true; - } - - const auto first = layerROF.getFirstEntry(); - const auto last = first + layerROF.getNEntries(); - if (first < 0 || last < first || last >= static_cast(patternOffsets[iLayer].size())) { - LOGP(warning, "Skipping TRK clusters for invalid range [{}, {}) on layer {}", first, last, iLayer); - continue; - } - const auto patternFirst = patternOffsets[iLayer][first]; - const auto patternLast = patternOffsets[iLayer][last]; - for (int iCluster = first; iCluster < last; ++iCluster) { - if (iCluster < 0 || iCluster >= static_cast(layerClusters[iLayer].size())) { - LOGP(warning, "Skipping out-of-range TRK cluster {} on layer {}", iCluster, iLayer); - continue; - } - const auto mergedClusterIndex = compClusters.size(); - compClusters.push_back(layerClusters[iLayer][iCluster]); - if (mIsMC) { - if (layerLabels[iLayer] != nullptr) { - labels.addElements(mergedClusterIndex, layerLabels[iLayer]->getLabels(iCluster)); - } else { - labels.addNoLabelIndex(mergedClusterIndex); - } - } - } - patterns.insert(patterns.end(), layerPatterns[iLayer].begin() + patternFirst, layerPatterns[iLayer].begin() + patternLast); } - - mergedROF.setNEntries(compClusters.size() - mergedROF.getFirstEntry()); - rofRecords.push_back(mergedROF); } const float yPlaneMLOT = 0.0010f; - nRofs = timeFrame.loadROFrameData(rofRecords, compClusters, patterns, mIsMC ? &labels : nullptr, yPlaneMLOT); - timeFrame.addTruthSeedingVertices(rofRecords); + nRofs = timeFrame.loadROFrameData(layerROFs, layerClusters, layerPatterns, mIsMC ? &layerLabels : nullptr, yPlaneMLOT); + timeFrame.addTruthSeedingVertices(truthSeedROFs); } const auto trackingLoopStart = std::chrono::steady_clock::now(); @@ -457,12 +384,6 @@ void TrackerDPL::run(ProcessingContext& pc) LOGP(info, "Good tracks: {} ({:.1f}%)", goodTracks, totalTracks > 0 ? 100.0 * goodTracks / totalTracks : 0); LOGP(info, "Fake tracks: {} ({:.1f}%)", fakeTracks, totalTracks > 0 ? 100.0 * fakeTracks / totalTracks : 0); - // Build per-ROF track records and IR frames in the clock-layer's frame. - // The clock layer is the fastest TRK layer (max mNROFsTF) — see - // ROFOverlapTableView::getClock(). Each track's TimeStamp deterministically - // maps to a clock-layer ROF; track ordering from the tracker is by - // increasing time, so the per-ROF firstEntry/nEntries pair indexes a - // contiguous range of allTracks. Mirrors ITS TrackingInterface.cxx. const auto& rofView = timeFrame.getROFOverlapTableView(); const auto& clockLayer = rofView.getClockLayer(); const int clockLayerId = rofView.getClock(); @@ -521,10 +442,6 @@ void TrackerDPL::run(ProcessingContext& pc) allTracks.size(), allTrackROFs.size(), irFrames.size(), mIsMC ? " (with MC labels)" : ""); - // Clear tracking state so the TimeFrame is ready for the next TF. - // Today timeFrame is a stack local and is destroyed at end of this - // lambda anyway, but wipe() matches the ITS lifecycle pattern and stays - // correct if/when timeFrame is hoisted to a member for cross-TF reuse. timeFrame.wipe(); }; @@ -596,7 +513,6 @@ DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, c inputs.emplace_back("dummy", "TRK", "DUMMY", 0, Lifetime::Timeframe); - /// Keep momentarily both the hit-based and cluster-based reconstruction in the code, but if a cluster reco config is provided, we assume that the input will be clusters and not hits, and we set the inputs accordingly. This is to avoid having to change the workflow too much for the moment, but ideally we should drop the hit-based reconstruction asap. if (!clusterRecoConfig.empty()) { inputs.pop_back(); constexpr int nLayers{11}; diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx index e9584095cbb4c..bd1d5acc9b9a7 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx @@ -9,17 +9,6 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. -// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -// All rights not expressly granted are reserved. -// -// This software is distributed under the terms of the GNU General Public -// License v3 (GPL Version 3), copied verbatim in the file "COPYING". -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - #include "TRKWorkflow/RecoWorkflow.h" #include "CommonUtils/ConfigurableParam.h" From 6bf67731412e8e2e16f3156d9476b10d88611870 Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Mon, 4 May 2026 09:39:46 +0200 Subject: [PATCH 08/11] ALICE3: validate configuration before running --- .../GlobalReconstruction/workflow/README.md | 3 ++- .../workflow/src/TrackerSpec.cxx | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md index c25266c7d1286..f22e95d6971db 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md @@ -53,6 +53,7 @@ The tracking configuration is provided via a JSON file that specifies: "LayerResolution": [0.0003, 0.0003, 0.0003, 0.0003, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012], "SystErrorY2": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], "SystErrorZ2": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + "AddTimeError": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "ZBins": 256, "PhiBins": 128, "nROFsPerIterations": -1, @@ -70,7 +71,7 @@ The tracking configuration is provided via a JSON file that specifies: "MaxChi2ClusterAttachment": 60.0, "MaxChi2NDF": 30.0, "ReseedIfShorter": 6, - "MinPt": [0.0, 0.0, 0.0, 0.0], + "MinPt": [0.0, 0.0, 0.0, 0.0, 0.0], "StartLayerMask": 4095, "RepeatRefitOut": false, "ShiftRefToCluster": true, diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index ba3d1977bf1d0..2ee181eabec31 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -191,6 +191,9 @@ std::vector TrackerDPL::createTrackingParamsFromCon if (paramConfig.contains("MinPt")) { params.MinPt = paramConfig["MinPt"].get>(); } + if (paramConfig.contains("AddTimeError")) { + params.AddTimeError = paramConfig["AddTimeError"].get>(); + } if (paramConfig.contains("Diamond") && paramConfig["Diamond"].is_array() && paramConfig["Diamond"].size() == 3) { params.Diamond[0] = paramConfig["Diamond"][0].get(); @@ -207,6 +210,19 @@ std::vector TrackerDPL::createTrackingParamsFromCon params.CorrType = static_cast::MatCorrType>(corrTypeInt); } + const auto nLayers = static_cast(params.NLayers); + LOG_IF(fatal, params.LayerZ.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter LayerZ: expected " << nLayers << " entries, got " << params.LayerZ.size(); + LOG_IF(fatal, params.LayerRadii.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter LayerRadii: expected " << nLayers << " entries, got " << params.LayerRadii.size(); + LOG_IF(fatal, params.LayerxX0.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter LayerxX0: expected " << nLayers << " entries, got " << params.LayerxX0.size(); + LOG_IF(fatal, params.LayerResolution.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter LayerResolution: expected " << nLayers << " entries, got " << params.LayerResolution.size(); + LOG_IF(fatal, params.SystErrorY2.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter SystErrorY2: expected " << nLayers << " entries, got " << params.SystErrorY2.size(); + LOG_IF(fatal, params.SystErrorZ2.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter SystErrorZ2: expected " << nLayers << " entries, got " << params.SystErrorZ2.size(); + LOG_IF(fatal, params.AddTimeError.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter AddTimeError: expected " << nLayers << " entries, got " << params.AddTimeError.size(); + + LOG_IF(fatal, params.MinTrackLength > params.NLayers) << "Invalid ALICE3 TRK tracking parameter MinTrackLength: expected <= NLayers (" << params.NLayers << "), got " << params.MinTrackLength; + const auto minPtSize = static_cast(params.NLayers - params.MinTrackLength + 1); + LOG_IF(fatal, params.MinPt.size() != minPtSize) << "Invalid ALICE3 TRK tracking parameter MinPt: expected " << minPtSize << " entries, got " << params.MinPt.size(); + trackingParams.push_back(params); } }; From 428f7ab252cd717b37126db10866cff5fa2c735c Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Mon, 4 May 2026 09:40:24 +0200 Subject: [PATCH 09/11] ALICE3: clang-format --- .../ALICE3GlobalReconstruction/GPUExternalAllocator.h | 3 ++- .../include/ALICE3GlobalReconstruction/TimeFrameMixin.h | 8 ++++---- .../GlobalReconstruction/workflow/src/TrackerSpec.cxx | 2 -- .../reconstruction/include/TRKReconstruction/Clusterer.h | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h index 62683b8185943..e873931a5a46c 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h @@ -38,7 +38,8 @@ class GPUExternalAllocator final : public o2::its::ExternalAllocator void releaseAll(); private: - enum class AllocationSpace { Host, Device }; + enum class AllocationSpace { Host, + Device }; struct AllocationMeta { AllocationSpace space; diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h index fb589874b24d9..1d45ec812ab8f 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h @@ -533,10 +533,10 @@ void TimeFrameMixin::addTruthSeedingVertices(gsl::span(rofLength)}); vert.setNContributors(std::max(1L, std::ranges::count_if( - mcReader.getTracks(iSrc, iEve), - [](const auto& trk) { - return trk.isPrimary() && trk.GetPt() > 0.05 && std::abs(trk.GetEta()) < 1.1; - }))); + mcReader.getTracks(iSrc, iEve), + [](const auto& trk) { + return trk.isPrimary() && trk.GetPt() > 0.05 && std::abs(trk.GetEta()) < 1.1; + }))); vert.setXYZ((float)eve.GetX(), (float)eve.GetY(), (float)eve.GetZ()); vert.setChi2(1); constexpr float cov = 50e-9f; diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index 2ee181eabec31..455b733c96fbe 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -227,8 +227,6 @@ std::vector TrackerDPL::createTrackingParamsFromCon } }; - - if (mHitRecoConfig.contains("trackingparams") && mHitRecoConfig["trackingparams"].is_array()) { loadTrackingParamsFromJson(trackingParams, mHitRecoConfig["trackingparams"]); } else if (mClusterRecoConfig.contains("trackingparams") && mClusterRecoConfig["trackingparams"].is_array()) { diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h index d96b2ecc5af00..3d30eb5068efe 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h @@ -173,7 +173,7 @@ class Clusterer std::vector* clusterMC2ROFs = nullptr); static o2::math_utils::Point3D getClusterLocalCoordinates(const Cluster& cluster, const uint8_t* patt, - float yPlaneMLOT = 0.f) noexcept; + float yPlaneMLOT = 0.f) noexcept; protected: int mNHugeClus = 0; From a5c8b7503a2f4456f0ae530aaa3108635361599e Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Tue, 5 May 2026 16:23:27 +0200 Subject: [PATCH 10/11] ALICE3: fix initialisation with MC vertices Co-authored-by: Copilot --- .../TimeFrameMixin.h | 74 +++++-------------- .../workflow/src/TrackerSpec.cxx | 13 +--- 2 files changed, 21 insertions(+), 66 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h index 1d45ec812ab8f..42bfa13900d23 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h @@ -41,8 +41,6 @@ #include #include #include -#include -#include #include #include @@ -68,7 +66,7 @@ class TimeFrameMixin : public Base void getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup); - void addTruthSeedingVertices(gsl::span rofs); + void addTruthSeedingVertices(); void deriveAndInitTiming(const std::array, nLayers>& layerROFs); @@ -483,7 +481,7 @@ void TimeFrameMixin::getPrimaryVerticesFromMC(TTree* mcHeaderTree } template -void TimeFrameMixin::addTruthSeedingVertices(gsl::span rofs) +void TimeFrameMixin::addTruthSeedingVertices() { LOGP(info, "TRK: using truth seeds as vertices from DigitizationContext"); this->mPrimaryVertices.clear(); @@ -493,21 +491,17 @@ void TimeFrameMixin::addTruthSeedingVertices(gsl::spangetEventRecords(); o2::steer::MCKinematicsReader mcReader(dc); - std::vector rofStartBC(rofs.size()); - for (size_t i = 0; i < rofs.size(); ++i) { - rofStartBC[i] = rofs[i].getBCData().toLong(); - } - + const int64_t anchorBC = mTFAnchorIR.toLong(); const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); const auto rofLength = clockLayer.mROFLength; using Vertex = o2::its::Vertex; - struct VertInfo { - std::pmr::vector vertices; - std::pmr::vector srcs; - std::pmr::vector events; + struct VertEntry { + int64_t bc; + Vertex vertex; + int event; }; - std::map vertMap; + std::vector entries; const int iSrc = 0; auto eveId2colId = dc->getCollisionIndicesForSource(iSrc); @@ -515,22 +509,11 @@ void TimeFrameMixin::addTruthSeedingVertices(gsl::span(std::distance(rofStartBC.begin(), it)); - auto* mr = this->mMemoryPool.get(); - if (!vertMap.contains(rofId)) { - vertMap[rofId] = { - .vertices = std::pmr::vector(mr), - .srcs = std::pmr::vector(mr), - .events = std::pmr::vector(mr), - }; - } + const int64_t evBC = ir.toLong() - anchorBC; + if (evBC >= 0) { Vertex vert; vert.setTimeStamp(o2::its::TimeEstBC{ - clockLayer.getROFStartInBC(rofId), + static_cast(evBC), static_cast(rofLength)}); vert.setNContributors(std::max(1L, std::ranges::count_if( mcReader.getTracks(iSrc, iEve), @@ -541,39 +524,22 @@ void TimeFrameMixin::addTruthSeedingVertices(gsl::spanmMemoryPool.get(); - for (int iROF{0}; iROF < static_cast(rofs.size()); ++iROF) { - std::pmr::vector verts(mr); - std::pmr::vector> polls(mr); - if (vertMap.contains(iROF)) { - const auto& info = vertMap[iROF]; - verts = info.vertices; - nVerts += verts.size(); - for (size_t i{0}; i < verts.size(); ++i) { - o2::MCCompLabel lbl(o2::MCCompLabel::maxTrackID(), info.events[i], info.srcs[i], false); - polls.emplace_back(lbl, 1.f); - } - } - for (const auto& vert : verts) { - this->addPrimaryVertex(vert); - } - for (const auto& label : polls) { - this->addPrimaryVertexLabel(label); - } + // Sort by BC so the lookup table binary search works correctly + std::ranges::sort(entries, {}, &VertEntry::bc); + + for (const auto& e : entries) { + this->addPrimaryVertex(e.vertex); + o2::MCCompLabel lbl(o2::MCCompLabel::maxTrackID(), e.event, iSrc, false); + this->addPrimaryVertexLabel({lbl, 1.f}); } updateHostROFVertexLookupTable(); - LOGP(info, "TRK truth seeding: {}/{} ROFs with {} vertices -> ={:.2f}", - vertMap.size(), rofs.size(), nVerts, - vertMap.size() > 0 ? (float)nVerts / (float)vertMap.size() : 0.f); + LOGP(info, "TRK truth seeding: added {} vertices", entries.size()); } } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index 455b733c96fbe..b597cd5a7e7bf 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -337,20 +337,9 @@ void TrackerDPL::run(ProcessingContext& pc) timeFrame.deriveAndInitTiming(layerROFs); - std::vector truthSeedROFs; - truthSeedROFs.reserve(nInputRofs); - for (size_t iRof = 0; iRof < nInputRofs; ++iRof) { - for (int iLayer = 0; iLayer < nLayers; ++iLayer) { - if (iRof < layerROFs[iLayer].size()) { - truthSeedROFs.push_back(layerROFs[iLayer][iRof]); - break; - } - } - } - const float yPlaneMLOT = 0.0010f; nRofs = timeFrame.loadROFrameData(layerROFs, layerClusters, layerPatterns, mIsMC ? &layerLabels : nullptr, yPlaneMLOT); - timeFrame.addTruthSeedingVertices(truthSeedROFs); + timeFrame.addTruthSeedingVertices(); } const auto trackingLoopStart = std::chrono::steady_clock::now(); From c6ef3e7ad89154468e3278af2db0883fcc80a635 Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Tue, 5 May 2026 16:25:13 +0200 Subject: [PATCH 11/11] ALICE3: add new performance macro --- .../GlobalReconstruction/CMakeLists.txt | 1 + .../macros/CMakeLists.txt | 21 + .../macros/CheckTracksALICE3.C | 619 ++++++++++++++++++ 3 files changed, 641 insertions(+) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt index 208c0a60b364e..6b859412a0ff5 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt @@ -11,3 +11,4 @@ add_subdirectory(reconstruction) add_subdirectory(workflow) +add_subdirectory(macros) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt new file mode 100644 index 0000000000000..8295e490f4d7d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +o2_add_test_root_macro(CheckTracksALICE3.C + PUBLIC_LINK_LIBRARIES O2::DataFormatsITS + O2::DataFormatsTRK + O2::ITStracking + O2::SimulationDataFormat + O2::DetectorsBase + O2::TRKBase + O2::TRKSimulation + O2::Steer + LABELS trk COMPILE_ONLY) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C new file mode 100644 index 0000000000000..836327507018c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C @@ -0,0 +1,619 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file CheckTracksALICE3.C +/// \brief Quality assurance macro for TRK tracking + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DataFormatsITS/TrackITS.h" +#include "DataFormatsTRK/Cluster.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTrack.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "SimulationDataFormat/O2DatabasePDG.h" +#include "Steer/MCKinematicsReader.h" + +#endif + +using namespace std; +using namespace o2; + +struct ParticleClusterInfo { + std::bitset<11> layerClusters; + int nClusters = 0; + float pt = 0.0f; + + void addCluster(int layer) + { + if (!layerClusters[layer]) { + layerClusters[layer] = true; + nClusters++; + } + } + + bool hasConsecutiveLayers(int nConsecutive) const + { + for (int startLayer = 0; startLayer <= 11 - nConsecutive; ++startLayer) { + bool allSet = true; + for (int i = 0; i < nConsecutive; ++i) { + if (!layerClusters[startLayer + i]) { + allSet = false; + break; + } + } + if (allSet) { + return true; + } + } + return false; + } +}; + +void CheckTracksALICE3(std::string tracfile = "o2trac_trk.root", + std::string simprefix = "o2sim", + std::string clusfile = "o2clus_trk.root", + std::string outputfile = "trk_qa_output.root") +{ + gStyle->SetOptStat(0); + + std::cout << "=== Starting TRK Track Quality Assurance ===" << std::endl; + std::cout << "Input files:" << std::endl; + std::cout << " Tracks: " << tracfile << std::endl; + std::cout << " Sim prefix: " << simprefix << std::endl; + std::cout << " Clusters: " << clusfile << std::endl; + std::cout << " Output: " << outputfile << std::endl; + std::cout << std::endl; + + // MC kinematics reader + o2::steer::MCKinematicsReader kineReader(simprefix, o2::steer::MCKinematicsReader::Mode::kMCKine); + const int nEvents = kineReader.getNEvents(0); + std::cout << "Number of MC events: " << nEvents << std::endl; + + // Open clusters file to count cluster-associated layers per particle + TFile* clustersFile = TFile::Open(clusfile.c_str(), "READ"); + if (!clustersFile || clustersFile->IsZombie()) { + std::cerr << "ERROR: Cannot open clusters file: " << clusfile << std::endl; + return; + } + TTree* clusTree = clustersFile->Get("o2sim"); + if (!clusTree) { + std::cerr << "ERROR: Cannot find o2sim tree in clusters file" << std::endl; + return; + } + + // Open reconstructed tracks file + TFile* tracFile = TFile::Open(tracfile.c_str(), "READ"); + if (!tracFile || tracFile->IsZombie()) { + std::cerr << "ERROR: Cannot open tracks file: " << tracfile << std::endl; + return; + } + TTree* recTree = tracFile->Get("o2sim"); + if (!recTree) { + std::cerr << "ERROR: Cannot find o2sim tree in tracks file" << std::endl; + return; + } + + // Reconstructed tracks and labels + std::vector* recTracks = nullptr; + std::vector* trkLabels = nullptr; + recTree->SetBranchAddress("TRKTrack", &recTracks); + recTree->SetBranchAddress("TRKTrackMCTruth", &trkLabels); + + std::cout << "Reading tracks from tree..." << std::endl; + + // Analyze cluster tree to count cluster-associated layers per particle + std::cout << "Analyzing clusters from tree..." << std::endl; + std::unordered_map particleClusterMap; + + static constexpr int nTRKLayers = 11; + std::array*, nTRKLayers> clustersPerLayer{}; + std::array*, nTRKLayers> clusterLabelsPerLayer{}; + + for (int iLayer = 0; iLayer < nTRKLayers; ++iLayer) { + const std::string clusBranch = std::string("TRKClusterComp_") + std::to_string(iLayer); + const std::string truthBranch = std::string("TRKClusterMCTruth_") + std::to_string(iLayer); + if (!clusTree->GetBranch(clusBranch.c_str())) { + std::cerr << "WARNING: Missing cluster branch for layer " << iLayer << " (expected " << clusBranch << ")" << std::endl; + continue; + } + if (!clusTree->GetBranch(truthBranch.c_str())) { + std::cerr << "WARNING: Missing cluster MC-truth branch for layer " << iLayer << " (expected " << truthBranch << ")" << std::endl; + continue; + } + clusTree->SetBranchAddress(clusBranch.c_str(), &clustersPerLayer[iLayer]); + clusTree->SetBranchAddress(truthBranch.c_str(), &clusterLabelsPerLayer[iLayer]); + } + + Long64_t nClusEntries = clusTree->GetEntries(); + std::cout << "Processing " << nClusEntries << " cluster entries..." << std::endl; + + for (Long64_t iEntry = 0; iEntry < nClusEntries; ++iEntry) { + clusTree->GetEntry(iEntry); + for (int iLayer = 0; iLayer < nTRKLayers; ++iLayer) { + const auto* clusArr = clustersPerLayer[iLayer]; + const auto* clusLabArr = clusterLabelsPerLayer[iLayer]; + if (!clusArr || !clusLabArr) { + continue; + } + for (size_t iClus = 0; iClus < clusArr->size(); ++iClus) { + const auto labels = clusLabArr->getLabels(iClus); + if (labels.empty()) { + continue; + } + const auto& lab = labels[0]; + if (!lab.isValid() || lab.getSourceID() != 0 || !lab.isCorrect()) { + continue; + } + int trackID = -1, evID = -1, srcID = -1; + bool fake = false; + lab.get(trackID, evID, srcID, fake); + if (trackID < 0 || evID < 0) { + continue; + } + particleClusterMap[o2::MCCompLabel(trackID, evID, 0)].addCluster(iLayer); + } + } + } + + std::cout << "Found " << particleClusterMap.size() << " unique particles with clusters" << std::endl; + + // Store particle info and fill generated histograms + std::unordered_map particlePtMap; + + // Create histograms + constexpr int nb = 100; + double xbins[nb + 1], ptcutl = 0.05, ptcuth = 10.; + double a = std::log(ptcuth / ptcutl) / nb; + for (int i = 0; i <= nb; i++) + xbins[i] = ptcutl * std::exp(i * a); + + TH1D genParticlePtHist("genParticlePt", "Generated Particle p_{T} (All Layers); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + TH1D genParticlePt7LayersHist("genParticlePt7Layers", "Generated Particle p_{T} with clusters in at least 7 consecutive layers; #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + TH1D chargedPrimaryPtHist("chargedPrimaryPt", + "Charged primary particles |#eta| < 2; #it{p}_{T} (GeV/#it{c}); Counts", + nb, xbins); + TH1D goodTracks("goodTracks", "Good Tracks; p_{T} (GeV/c); Counts", nb, xbins); + TH1D fakeTracks("fakeTracks", "Fake Tracks; p_{T} (GeV/c); Counts", nb, xbins); + + std::array goodTracksMatching, fakeTracksMatching; + for (int i = 0; i < 5; ++i) { + goodTracksMatching[i] = TH1D(Form("goodTracksMatching_%dLayers", i + 7), + Form("Good Tracks with %d cluster layers; p_{T} (GeV/c); Counts", i + 7), + nb, xbins); + fakeTracksMatching[i] = TH1D(Form("fakeTracksMatching_%dLayers", i + 7), + Form("Fake Tracks with %d cluster layers; p_{T} (GeV/c); Counts", i + 7), + nb, xbins); + } + + TH1D numberOfClustersPerTrack("numberOfClustersPerTrack", + "Number of clusters per track; N_{clusters}; Counts", + 12, -0.5, 11.5); + TH1D cloneTracks("cloneTracks", "Clone Tracks; #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + + std::array duplicateTracksMatching; + for (int i = 0; i < 5; ++i) { + duplicateTracksMatching[i] = TH1D(Form("duplicateTracksMatching_%dLayers", i + 7), + Form("Duplicate Tracks with %d cluster layers; p_{T} (GeV/c); Counts", i + 7), + nb, xbins); + } + + TH1D genParticleEtaHist("genParticleEta", + "Generated Particle #eta (11 consec. layers, p_{T} > 1 GeV/c); #eta; Counts", + 100, -2.5, 2.5); + std::array goodTracksMatchingEta; + for (int i = 0; i < 5; ++i) { + goodTracksMatchingEta[i] = TH1D(Form("goodTracksMatchingEta_%dLayers", i + 7), + Form("Good Tracks #eta with %d cluster layers (p_{T} > 1 GeV/c); #eta; Counts", i + 7), + 100, -2.5, 2.5); + } + + // Numerators for summary efficiency/fake/duplicate vs 7-layer reference + TH1D goodTracks7("goodTracks7Layers", "Good Tracks (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + TH1D fakeTracks7("fakeTracks7Layers", "Fake Tracks (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + TH1D cloneTracks7("cloneTracks7Layers", "Clone Tracks (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + + // Deduplicated fake/clone numerators for 11-layer reference summary + TH1D fakeTracks11("fakeTracks11Layers", "Fake Tracks (11 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + TH1D cloneTracks11("cloneTracks11Layers", "Clone Tracks (11 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + + // First pass: identify particles with full hit coverage from kinematics + std::cout << "Analyzing MC particles..." << std::endl; + for (int iEvent = 0; iEvent < nEvents; ++iEvent) { + const auto& mcTracks = kineReader.getTracks(iEvent); + for (size_t iTrack = 0; iTrack < mcTracks.size(); ++iTrack) { + const auto& mcTrack = mcTracks[iTrack]; + if (!mcTrack.isPrimary()) { + continue; + } + + // Create label for this particle + o2::MCCompLabel label(iTrack, iEvent, 0); + float pt = mcTrack.GetPt(); + + // Charged primary in |eta| < 2 + if (std::abs(mcTrack.GetEta()) < 2.f) { + auto* pdgPart = o2::O2DatabasePDG::Instance()->GetParticle(mcTrack.GetPdgCode()); + if (pdgPart != nullptr && pdgPart->Charge() != 0.) { + chargedPrimaryPtHist.Fill(pt); + } + } + + // Store particle info + particlePtMap[label] = pt; + + auto clusIt = particleClusterMap.find(label); + if (clusIt != particleClusterMap.end()) { + clusIt->second.pt = pt; + + if (clusIt->second.hasConsecutiveLayers(11)) { + genParticlePtHist.Fill(pt); + if (pt > 1.f) { + genParticleEtaHist.Fill(mcTrack.GetEta()); + } + } + + if (clusIt->second.hasConsecutiveLayers(7)) { + genParticlePt7LayersHist.Fill(pt); + } + } + } + } + + std::cout << "Generated particles with 11 cluster layers: " << genParticlePtHist.GetEntries() << std::endl; + std::cout << "Generated particles with 7+ consecutive cluster layers: " << genParticlePt7LayersHist.GetEntries() << std::endl; + + // Count how many reconstructed tracks point to each MC label (clone detection) + std::unordered_map labelRecoCount; + { + int nROFsTmp = recTree->GetEntries(); + for (int iROF = 0; iROF < nROFsTmp; ++iROF) { + recTree->GetEntry(iROF); + if (!trkLabels) { + continue; + } + for (const auto& lab : *trkLabels) { + if (!lab.isSet() || !lab.isValid() || lab.isFake()) { + continue; + } + int eventID = lab.getEventID(); + int trackID = lab.getTrackID(); + if (eventID < 0 || eventID >= nEvents) { + continue; + } + const auto& mcTracks = kineReader.getTracks(eventID); + if (trackID < 0 || trackID >= (int)mcTracks.size()) { + continue; + } + if (!mcTracks[trackID].isPrimary()) { + continue; + } + labelRecoCount[o2::MCCompLabel(lab.getTrackID(), lab.getEventID(), 0)]++; + } + } + } + + // Second pass: analyze reconstructed tracks + std::cout << "Analyzing reconstructed tracks..." << std::endl; + int nROFs = recTree->GetEntries(); + int totalTracks = 0; + int goodTracksCount = 0; + int fakeTracksCount = 0; + int cloneTracksCount = 0; + // Track which MC labels have already been filled per matching bin to avoid double-counting clones + std::array, 5> filledGoodLabels; + std::unordered_set filledGoodLabelsAny; + std::unordered_set filledGoodLabelsAny7; + std::unordered_set filledFakeLabelsAny11; + std::unordered_set filledCloneLabelsAny11; + + for (int iROF = 0; iROF < nROFs; ++iROF) { + recTree->GetEntry(iROF); + + if (!recTracks || !trkLabels) { + continue; + } + + totalTracks += recTracks->size(); + + for (size_t iTrack = 0; iTrack < recTracks->size(); ++iTrack) { + const auto& track = recTracks->at(iTrack); + const auto& label = trkLabels->at(iTrack); + + if (!label.isSet() || !label.isValid()) { + continue; + } + + int eventID = label.getEventID(); + int trackID = label.getTrackID(); + int nClusters = track.getNumberOfClusters(); + + // Get MC track info + if (eventID < 0 || eventID >= nEvents) { + continue; + } + + const auto& mcTracks = kineReader.getTracks(eventID); + if (trackID < 0 || trackID >= (int)mcTracks.size()) { + continue; + } + if (!mcTracks[trackID].isPrimary()) { + continue; + } + + float pt = mcTracks[trackID].GetPt(); + float eta = mcTracks[trackID].GetEta(); + + // Fill histograms + numberOfClustersPerTrack.Fill(nClusters); + + auto key = o2::MCCompLabel(trackID, eventID, 0); + if (particleClusterMap.find(key) != particleClusterMap.end() && particleClusterMap[key].hasConsecutiveLayers(11)) { + if (label.isFake()) { + fakeTracks.Fill(pt); + fakeTracksCount++; + if (nClusters >= 7 && nClusters <= 11) { + fakeTracksMatching[nClusters - 7].Fill(pt); + } + filledFakeLabelsAny11.insert(key); + } else { + if (filledGoodLabelsAny.insert(key).second) { + goodTracks.Fill(pt); + goodTracksCount++; + } + if (nClusters >= 7 && nClusters <= 11) { + int bin = nClusters - 7; + if (filledGoodLabels[bin].insert(key).second) { + goodTracksMatching[bin].Fill(pt); + if (pt > 1.f) { + goodTracksMatchingEta[bin].Fill(eta); + } + } else { + duplicateTracksMatching[bin].Fill(pt); + } + } + if (labelRecoCount[key] > 1) { + cloneTracks.Fill(pt); + cloneTracksCount++; + filledCloneLabelsAny11.insert(key); + } + } + } + + // Fill summary histograms vs 7-layer reference + auto clusIt7 = particleClusterMap.find(key); + if (clusIt7 != particleClusterMap.end() && clusIt7->second.hasConsecutiveLayers(7)) { + if (label.isFake()) { + fakeTracks7.Fill(pt); + } else { + if (filledGoodLabelsAny7.insert(key).second) { + goodTracks7.Fill(pt); + } + if (labelRecoCount[key] > 1) { + cloneTracks7.Fill(pt); + } + } + } + } + } + + // Create efficiency histograms + std::cout << "Total tracks: " << totalTracks << ". Out of those matching particles with 11 clusters, good: " << goodTracksCount + << ", fake: " << fakeTracksCount << ", clones: " << cloneTracksCount << std::endl; + + std::cout << "Computing efficiencies..." << std::endl; + + std::array efficiencyHistograms; + THStack* efficiencyStack = new THStack("efficiencyStack", + "Tracking Efficiency; #it{p}_{T} (GeV/#it{c}); Efficiency"); + + std::array efficiencyEtaHistograms; + THStack* efficiencyEtaStack = new THStack("efficiencyEtaStack", + "Tracking Efficiency vs #eta (p_{T} > 1 GeV/c); #eta; Efficiency"); + + int colors[5] = {kRed, kBlue, kGreen + 2, kMagenta, kOrange}; + for (int i = 0; i < 5; ++i) { + int nClusters = i + 7; + efficiencyHistograms[i] = TH1D(Form("efficiency_%dClusters", nClusters), + Form("Efficiency for %d cluster tracks; #it{p}_{T} (GeV/#it{c}); Efficiency", nClusters), + nb, xbins); + + efficiencyHistograms[i].Divide(&goodTracksMatching[i], &genParticlePtHist, 1, 1, "B"); + + efficiencyHistograms[i].SetLineColor(colors[i]); + efficiencyHistograms[i].SetFillColor(colors[i]); + efficiencyHistograms[i].SetLineWidth(2); + efficiencyHistograms[i].SetMarkerColor(colors[i]); + efficiencyHistograms[i].SetMarkerStyle(20 + i); + efficiencyStack->Add(&efficiencyHistograms[i]); + + efficiencyEtaHistograms[i] = TH1D(Form("efficiencyEta_%dClusters", nClusters), + Form("Efficiency vs #eta for %d cluster tracks (p_{T} > 1 GeV/c); #eta; Efficiency", nClusters), + 100, -2.5, 2.5); + efficiencyEtaHistograms[i].Divide(&goodTracksMatchingEta[i], &genParticleEtaHist, 1, 1, "B"); + efficiencyEtaHistograms[i].SetLineColor(colors[i]); + efficiencyEtaHistograms[i].SetFillColor(colors[i]); + efficiencyEtaHistograms[i].SetLineWidth(2); + efficiencyEtaHistograms[i].SetMarkerColor(colors[i]); + efficiencyEtaHistograms[i].SetMarkerStyle(20 + i); + efficiencyEtaStack->Add(&efficiencyEtaHistograms[i]); + } + + // Build summary efficiency/fake/duplicate vs 7-layer reference + TH1D effVs7("efficiencyVs7Layers", + "Tracking Efficiency (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + effVs7.Divide(&goodTracks7, &genParticlePt7LayersHist, 1, 1, "B"); + effVs7.SetLineColor(kBlue); + effVs7.SetLineWidth(2); + effVs7.SetMarkerColor(kBlue); + effVs7.SetMarkerStyle(20); + + TH1D fakeVs7("fakeRateVs7Layers", + "Fake Rate (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + fakeVs7.Divide(&fakeTracks7, &genParticlePt7LayersHist, 1, 1, "B"); + fakeVs7.SetLineColor(kRed); + fakeVs7.SetLineWidth(2); + fakeVs7.SetMarkerColor(kRed); + fakeVs7.SetMarkerStyle(21); + + TH1D dupVs7("duplicateRateVs7Layers", + "Duplicate Rate (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + dupVs7.Divide(&cloneTracks7, &genParticlePt7LayersHist, 1, 1, "B"); + dupVs7.SetLineColor(kGreen + 2); + dupVs7.SetLineWidth(2); + dupVs7.SetMarkerColor(kGreen + 2); + dupVs7.SetMarkerStyle(22); + + // Build summary efficiency/fake/duplicate vs 11-layer reference + // Fill deduplicated fake/clone histograms from the sets collected during the reco loop + for (const auto& [lbl, info] : particleClusterMap) { + if (!info.hasConsecutiveLayers(11)) { + continue; + } + auto ptIt = particlePtMap.find(lbl); + if (ptIt == particlePtMap.end()) { + continue; + } + float ptLbl = ptIt->second; + if (filledFakeLabelsAny11.count(lbl)) { + fakeTracks11.Fill(ptLbl); + } + if (filledCloneLabelsAny11.count(lbl)) { + cloneTracks11.Fill(ptLbl); + } + } + + TH1D effVs11("efficiencyVs11Layers", + "Tracking Efficiency (11 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + effVs11.Divide(&goodTracks, &genParticlePtHist, 1, 1, "B"); + effVs11.SetLineColor(kBlue); + effVs11.SetLineWidth(2); + effVs11.SetMarkerColor(kBlue); + effVs11.SetMarkerStyle(20); + + TH1D fakeVs11("fakeRateVs11Layers", + "Fake Rate (11 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + fakeVs11.Divide(&fakeTracks11, &genParticlePtHist, 1, 1, "B"); + fakeVs11.SetLineColor(kRed); + fakeVs11.SetLineWidth(2); + fakeVs11.SetMarkerColor(kRed); + fakeVs11.SetMarkerStyle(21); + + TH1D dupVs11("duplicateRateVs11Layers", + "Duplicate Rate (11 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + dupVs11.Divide(&cloneTracks11, &genParticlePtHist, 1, 1, "B"); + dupVs11.SetLineColor(kGreen + 2); + dupVs11.SetLineWidth(2); + dupVs11.SetMarkerColor(kGreen + 2); + dupVs11.SetMarkerStyle(22); + + // Summary canvas — 7-layer reference + TCanvas summaryCanvas("summaryCanvas7Layers", "TRK Tracking QA Summary (7 layers ref.)", 800, 600); + summaryCanvas.SetLogx(); + double ymax = std::max({effVs7.GetMaximum(), fakeVs7.GetMaximum(), dupVs7.GetMaximum()}); + effVs7.GetYaxis()->SetRangeUser(0., 1.1 * ymax + 0.05); + effVs7.Draw("E"); + fakeVs7.Draw("E SAME"); + dupVs7.Draw("E SAME"); + TLegend leg(0.65, 0.70, 0.88, 0.88); + leg.SetBorderSize(0); + leg.AddEntry(&effVs7, "Efficiency", "lp"); + leg.AddEntry(&fakeVs7, "Fake rate", "lp"); + leg.AddEntry(&dupVs7, "Duplicate rate", "lp"); + leg.Draw(); + + // Summary canvas — 11-layer reference + TCanvas summaryCanvas11("summaryCanvas11Layers", "TRK Tracking QA Summary (11 layers ref.)", 800, 600); + summaryCanvas11.SetLogx(); + double ymax11 = std::max({effVs11.GetMaximum(), fakeVs11.GetMaximum(), dupVs11.GetMaximum()}); + effVs11.GetYaxis()->SetRangeUser(0., 1.1 * ymax11 + 0.05); + effVs11.Draw("E"); + fakeVs11.Draw("E SAME"); + dupVs11.Draw("E SAME"); + TLegend leg11(0.65, 0.70, 0.88, 0.88); + leg11.SetBorderSize(0); + leg11.AddEntry(&effVs11, "Efficiency", "lp"); + leg11.AddEntry(&fakeVs11, "Fake rate", "lp"); + leg11.AddEntry(&dupVs11, "Duplicate rate", "lp"); + leg11.Draw(); + + // Write output + std::cout << "Writing output to " << outputfile << std::endl; + TFile outFile(outputfile.c_str(), "RECREATE"); + + // Top-level: summary plots + summaryCanvas.Write(); + effVs7.Write(); + fakeVs7.Write(); + dupVs7.Write(); + summaryCanvas11.Write(); + effVs11.Write(); + fakeVs11.Write(); + dupVs11.Write(); + + // Details directory: per-cluster-count breakdowns and raw counts + TDirectory* detDir = outFile.mkdir("details"); + detDir->cd(); + genParticlePtHist.Write(); + genParticlePt7LayersHist.Write(); + genParticleEtaHist.Write(); + chargedPrimaryPtHist.Write(); + goodTracks.Write(); + fakeTracks.Write(); + cloneTracks.Write(); + goodTracks7.Write(); + fakeTracks7.Write(); + cloneTracks7.Write(); + fakeTracks11.Write(); + cloneTracks11.Write(); + numberOfClustersPerTrack.Write(); + for (int i = 0; i < 5; ++i) { + goodTracksMatching[i].Write(); + fakeTracksMatching[i].Write(); + duplicateTracksMatching[i].Write(); + efficiencyHistograms[i].Write(); + goodTracksMatchingEta[i].Write(); + efficiencyEtaHistograms[i].Write(); + } + efficiencyStack->Write(); + efficiencyEtaStack->Write(); + + outFile.Close(); + + // Clean up + clustersFile->Close(); + tracFile->Close(); + delete efficiencyStack; + delete efficiencyEtaStack; + delete clustersFile; + delete tracFile; +}