From 56ddbe25f81b149aeb69495a843ee04e19dc6a2d Mon Sep 17 00:00:00 2001 From: shahoian Date: Thu, 23 Apr 2026 17:40:17 +0200 Subject: [PATCH 001/102] Fixes in processCosmics, allow pipelining Only cosmic tracks with at least one TRD or TOF contribution will be accepted in the tpc-refitter. Added options for cosmics processing: --ignore-legs-wo-outer-det will skip cosmic legs w/o TRD or TOF constraint even if other leg is well constrained --use-cosmic-leg-timing will prefer the timestamp of each leg to (if available) for the refit, otherwise use cosmic track timestamp. When the workflow is run with >1 lane, the streamer output XX.root of each lane will appear with suffix: XX_.root --- Detectors/TPC/workflow/src/TPCRefitter.cxx | 67 +++++++++++-------- .../workflow/src/tpc-refitter-workflow.cxx | 7 +- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/Detectors/TPC/workflow/src/TPCRefitter.cxx b/Detectors/TPC/workflow/src/TPCRefitter.cxx index fbab4500fde5c..4b877f56c90fd 100644 --- a/Detectors/TPC/workflow/src/TPCRefitter.cxx +++ b/Detectors/TPC/workflow/src/TPCRefitter.cxx @@ -22,6 +22,7 @@ #include "DetectorsBase/Propagator.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/ControlService.h" +#include "Framework/DeviceSpec.h" #include "Framework/Task.h" #include "MathUtils/Tsallis.h" #include "DetectorsCommonDataFormats/DetID.h" @@ -96,6 +97,8 @@ class TPCRefitterSpec final : public Task int mWriteTrackClusters = 0; ///< bitmask of which cluster information to dump to the tree: 0x1 = cluster native, 0x2 = corrected cluster positions, 0x4 = uncorrected cluster positions, 0x8 occupancy info bool mDoSampling{false}; ///< perform sampling of unbinned data bool mDoRefit{true}; ///< perform refit of TPC track + bool mIgnorLegsWOGoodTime{false}; ///< ignore cosmic legs w/o TRD or TOF constraint instead of using the time of other constraned leg + bool mUseCosmicLegTiming{false}; ///< use the timestamp from the cosmic track leg instead of using cosmic track timestamp std::vector mClusterOccupancy; ///< binned occupancy of all clusters std::vector mITSTPCTrackOccupanyTPCTime; ///< binned occupancy for ITS-TPC matched tracks using the TPC track time std::vector mITSTPCTrackOccupanyCombinedTime; ///< binned occupancy for ITS-TPC matched tracks using the combined track time @@ -152,6 +155,9 @@ void TPCRefitterSpec::init(InitContext& ic) mStudyType = ic.options().get("study-type"); mWriterType = ic.options().get("writer-type"); mWriteTrackClusters = ic.options().get("write-track-clusters"); + mIgnorLegsWOGoodTime = ic.options().get("ignore-legs-wo-outer-det"); + mUseCosmicLegTiming = ic.options().get("use-cosmic-leg-timing"); + const auto occBinsPerDrift = ic.options().get("occupancy-bins-per-drift"); mTimeBinsPerTF = (o2::raw::HBFUtils::Instance().nHBFPerTF * o2::constants::lhc::LHCMaxBunches) / 8 + 2 * mTimeBinsPerDrift; // add one drift before and after the TF mOccupancyBinsPerTF = static_cast(std::ceil(float(mTimeBinsPerTF * occBinsPerDrift) / mTimeBinsPerDrift)); @@ -160,19 +166,23 @@ void TPCRefitterSpec::init(InitContext& ic) mITSTPCTrackOccupanyCombinedTime.resize(mOccupancyBinsPerTF); LOGP(info, "Using {} bins for the occupancy per TF", mOccupancyBinsPerTF); + int lane = ic.services().get().inputTimesliceId; + int maxLanes = ic.services().get().maxInputTimeslices; + auto composeName = [maxLanes, lane](const std::string& seed) { return maxLanes > 1 ? fmt::format("{}_{}.root", seed, lane) : fmt::format("{}.root", seed); }; + if ((mWriterType & WriterType::Streamer) == WriterType::Streamer) { if ((mStudyType & StudyType::TPC) == StudyType::TPC) { - mDBGOutTPC = std::make_unique("tpctracks-study-streamer.root", "recreate"); + mDBGOutTPC = std::make_unique(composeName("tpctracks-study-streamer").c_str(), "recreate"); } if ((mStudyType & StudyType::ITSTPC) == StudyType::ITSTPC) { - mDBGOutITSTPC = std::make_unique("itstpctracks-study-streamer.root", "recreate"); + mDBGOutITSTPC = std::make_unique(composeName("itstpctracks-study-streamer").c_str(), "recreate"); } if ((mStudyType & StudyType::Cosmics) == StudyType::Cosmics) { - mDBGOutCosmics = std::make_unique("cosmics-study-streamer.root", "recreate"); + mDBGOutCosmics = std::make_unique(composeName("cosmics-study-streamer").c_str(), "recreate"); } } if (ic.options().get("dump-clusters")) { - mDBGOutCl = std::make_unique("tpc-trackStudy-cl.root", "recreate"); + mDBGOutCl = std::make_unique(composeName("tpc-trackStudy-cl").c_str(), "recreate"); } if (mXRef < 0.) { @@ -677,35 +687,36 @@ bool TPCRefitterSpec::processTPCTrack(o2::tpc::TrackTPC tr, o2::MCCompLabel lbl, void TPCRefitterSpec::processCosmics(o2::globaltracking::RecoContainer& recoData) { - auto tof = recoData.getTOFClusters(); const auto& par = o2::tpc::ParameterElectronics::Instance(); const auto invBinWidth = 1.f / par.ZbinWidth; for (const auto& cosmic : mCosmics) { // - const auto& gidtop = cosmic.getRefTop(); - const auto& gidbot = cosmic.getRefBottom(); - - // LOGP(info, "Sources: {} - {}", o2::dataformats::GlobalTrackID::getSourceName(gidtop.getSource()), o2::dataformats::GlobalTrackID::getSourceName(gidbot.getSource())); - - std::array contributorsGID[2] = {recoData.getSingleDetectorRefs(cosmic.getRefTop()), recoData.getSingleDetectorRefs(cosmic.getRefBottom())}; - const auto trackTime = cosmic.getTimeMUS().getTimeStamp() * invBinWidth; - - // check if track has TPC & TOF for top and bottom part - // loop over both parts - for (const auto& comsmicInfo : contributorsGID) { - auto& tpcGlobal = comsmicInfo[GTrackID::TPC]; - auto& tofGlobal = comsmicInfo[GTrackID::TOF]; - if (tpcGlobal.isIndexSet() && tofGlobal.isIndexSet()) { - const auto itrTPC = tpcGlobal.getIndex(); - const auto itrTOF = tofGlobal.getIndex(); - const auto& tofCl = tof[itrTOF]; - const auto tofTime = tofCl.getTime() * 1e-6 * invBinWidth; // ps -> us -> time bins - const auto tofTimeRaw = tofCl.getTimeRaw() * 1e-6 * invBinWidth; // ps -> us -> time bins - const auto& trackTPC = mTPCTracksArray[itrTPC]; - // LOGP(info, "Cosmic time: {}, TOF time: {}, TOF time raw: {}, TPC time: {}", trackTime, tofTime, tofTimeRaw, trackTPC.getTime0()); - processTPCTrack(trackTPC, mUseMC ? mTPCTrkLabels[itrTPC] : o2::MCCompLabel{}, mDBGOutCosmics.get(), nullptr, nullptr, false, tofTime); + const GTrackID gidTopBot[] = {cosmic.getRefTop(), cosmic.getRefBottom()}; + // LOGP(info, "Sources: {} - {}", o2::dataformats::GlobalTrackID::getSourceName(gidTopBot[0].getSource()), o2::dataformats::GlobalTrackID::getSourceName(gidTopBot[1].getSource())); + // Wequire at least one TRD of TOF contribution to constrain the timestamp + bool hasGoodTime[2] = {false, false}; + std::array contributorsGID[2]; + for (int i = 0; i < 2; i++) { + contributorsGID[i] = recoData.getSingleDetectorRefs(gidTopBot[i]); + hasGoodTime[i] = gidTopBot[i].includesDet(DetID::TOF) || gidTopBot[i].includesDet(DetID::TRD); + } + if (!hasGoodTime[0] && !hasGoodTime[1]) { + continue; + } + float trackTime = cosmic.getTimeMUS().getTimeStamp() * invBinWidth; // this time corresponds to the center of top/bottom legs time-brackers intersection, i.e. should be the most precise one + + for (int i = 0; i < 2; i++) { + if (!contributorsGID[i][GTrackID::TPC].isSourceSet() || (mIgnorLegsWOGoodTime && !hasGoodTime[i])) { + continue; + } + const auto& trackTPC = mTPCTracksArray[contributorsGID[i][GTrackID::TPC]]; + float useTrackTime = trackTime, dummyError = 0.f; + if (mUseCosmicLegTiming && hasGoodTime[i]) { // track out time was requested (if available) + recoData.getTrackTime(gidTopBot[i], useTrackTime, dummyError); + useTrackTime *= invBinWidth; } + processTPCTrack(trackTPC, mUseMC ? mTPCTrkLabels[contributorsGID[i][GTrackID::TPC]] : o2::MCCompLabel{}, mDBGOutCosmics.get(), nullptr, nullptr, false, useTrackTime); } } } @@ -731,6 +742,8 @@ DataProcessorSpec getTPCRefitterSpec(GTrackID::mask_t srcTracks, GTrackID::mask_ {"study-type", VariantType::Int, 1, {"Bitmask of study type: 0x1 = TPC only, 0x2 = TPC + ITS, 0x4 = Cosmics"}}, {"writer-type", VariantType::Int, 1, {"Bitmask of writer type: 0x1 = per track streamer, 0x2 = per TF vectors"}}, {"occupancy-bins-per-drift", VariantType::UInt32, 31u, {"number of bin for occupancy histogram per drift time (500tb)"}}, + {"ignore-legs-wo-outer-det", VariantType::Bool, false, {"Ignore cosmic legs w/o TRD or TOF constraint even if other leg is well constrained"}}, + {"use-cosmic-leg-timing", VariantType::Bool, false, {"Use leg-specific timestamp instead of cosmic track final timestamp"}}, }; auto dataRequest = std::make_shared(); diff --git a/Detectors/TPC/workflow/src/tpc-refitter-workflow.cxx b/Detectors/TPC/workflow/src/tpc-refitter-workflow.cxx index 61c589512d1ce..567d9caf14bc6 100644 --- a/Detectors/TPC/workflow/src/tpc-refitter-workflow.cxx +++ b/Detectors/TPC/workflow/src/tpc-refitter-workflow.cxx @@ -66,11 +66,8 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); const auto enableCosmics = configcontext.options().get("enable-cosmics"); - GID::mask_t allowedSourcesTrc = GID::getSourcesMask("ITS,TPC,ITS-TPC,TPC-TOF"); - GID::mask_t allowedSourcesClus = GID::getSourcesMask("TPC,TOF"); - if (enableCosmics) { - allowedSourcesTrc = allowedSourcesTrc | GID::getSourcesMask("ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); - } + GID::mask_t allowedSourcesTrc = GID::getSourcesMask("TPC,ITS-TPC,TPC-TOF,TPC-TRD,ITS-TPC-TRD,TPC-TRD-TOF,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); + GID::mask_t allowedSourcesClus = GID::getSourcesMask("TPC"); GID::mask_t srcTrc = allowedSourcesTrc & GID::getSourcesMask(configcontext.options().get("track-sources")); GID::mask_t srcCls = allowedSourcesClus & GID::getSourcesMask(configcontext.options().get("cluster-sources")); From bda26127c37b2257861a768f1cc0e4851e3f6083 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 24 Apr 2026 15:56:26 +0200 Subject: [PATCH 002/102] Put back AMD origin for the metadata message (#15322) --- Framework/Core/include/Framework/AnalysisSupportHelpers.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Framework/Core/include/Framework/AnalysisSupportHelpers.h b/Framework/Core/include/Framework/AnalysisSupportHelpers.h index 803d8cf9d4685..1c1f2dcb86a1e 100644 --- a/Framework/Core/include/Framework/AnalysisSupportHelpers.h +++ b/Framework/Core/include/Framework/AnalysisSupportHelpers.h @@ -20,9 +20,7 @@ namespace o2::framework { -static constexpr std::array AODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}}; -// static constexpr std::array extendedAODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}}; -// static constexpr std::array writableAODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}}; +static constexpr std::array AODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}, header::DataOrigin{"AMD"}}; class DataOutputDirector; struct ConfigContext; From 40430f9321f76363deb68d9105057e7930974b80 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Fri, 24 Apr 2026 23:08:58 +0200 Subject: [PATCH 003/102] Revert "Put back AMD origin for the metadata message (#15322)" (#15325) This reverts commit bda26127c37b2257861a768f1cc0e4851e3f6083. --- Framework/Core/include/Framework/AnalysisSupportHelpers.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Framework/Core/include/Framework/AnalysisSupportHelpers.h b/Framework/Core/include/Framework/AnalysisSupportHelpers.h index 1c1f2dcb86a1e..803d8cf9d4685 100644 --- a/Framework/Core/include/Framework/AnalysisSupportHelpers.h +++ b/Framework/Core/include/Framework/AnalysisSupportHelpers.h @@ -20,7 +20,9 @@ namespace o2::framework { -static constexpr std::array AODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}, header::DataOrigin{"AMD"}}; +static constexpr std::array AODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}}; +// static constexpr std::array extendedAODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}}; +// static constexpr std::array writableAODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}}; class DataOutputDirector; struct ConfigContext; From 0f6d7bc1612ec326ef2c94fce1b99f75ec31b014 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Fri, 24 Apr 2026 23:39:27 +0200 Subject: [PATCH 004/102] DPL Analysis: fix metadata writing (#15326) --- Framework/AnalysisSupport/src/AODWriterHelpers.cxx | 2 +- Framework/Core/include/Framework/AnalysisSupportHelpers.h | 5 ++--- Framework/Core/src/AnalysisSupportHelpers.cxx | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Framework/AnalysisSupport/src/AODWriterHelpers.cxx b/Framework/AnalysisSupport/src/AODWriterHelpers.cxx index 19cf7a1ebff7b..5b5829d96a1de 100644 --- a/Framework/AnalysisSupport/src/AODWriterHelpers.cxx +++ b/Framework/AnalysisSupport/src/AODWriterHelpers.cxx @@ -154,7 +154,7 @@ AlgorithmSpec AODWriterHelpers::getOutputTTreeWriter(ConfigContext const& ctx) } // skip non-AOD refs - if (!DataSpecUtils::partialMatch(*ref.spec, AODOrigins)) { + if (!DataSpecUtils::partialMatch(*ref.spec, writableAODOrigins)) { continue; } startTime = DataRefUtils::getHeader(ref)->startTime; diff --git a/Framework/Core/include/Framework/AnalysisSupportHelpers.h b/Framework/Core/include/Framework/AnalysisSupportHelpers.h index 803d8cf9d4685..c1968123e765d 100644 --- a/Framework/Core/include/Framework/AnalysisSupportHelpers.h +++ b/Framework/Core/include/Framework/AnalysisSupportHelpers.h @@ -20,9 +20,8 @@ namespace o2::framework { -static constexpr std::array AODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}}; -// static constexpr std::array extendedAODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}}; -// static constexpr std::array writableAODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}}; +static constexpr std::array AODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}, header::DataOrigin{"AMD"}}; +static constexpr std::array writableAODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}}; class DataOutputDirector; struct ConfigContext; diff --git a/Framework/Core/src/AnalysisSupportHelpers.cxx b/Framework/Core/src/AnalysisSupportHelpers.cxx index 4dab3b364e04d..35228bba531b0 100644 --- a/Framework/Core/src/AnalysisSupportHelpers.cxx +++ b/Framework/Core/src/AnalysisSupportHelpers.cxx @@ -102,7 +102,7 @@ std::shared_ptr AnalysisSupportHelpers::getDataOutputDirecto // use the dangling outputs std::vector danglingOutputs; for (auto ii = 0u; ii < OutputsInputs.size(); ii++) { - if (DataSpecUtils::partialMatch(OutputsInputs[ii], AODOrigins) && isDangling[ii]) { + if (DataSpecUtils::partialMatch(OutputsInputs[ii], writableAODOrigins) && isDangling[ii]) { danglingOutputs.emplace_back(OutputsInputs[ii]); } } From abdd7f12e61bcc676ec5cd13bf1d7f967458d32e Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Sun, 26 Apr 2026 08:40:44 +0200 Subject: [PATCH 005/102] Make sure metadata exists in the generated test AO2D (#15323) --- prodtests/full_system_test.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/prodtests/full_system_test.sh b/prodtests/full_system_test.sh index 8496a31d577bc..ca83911d5fab8 100755 --- a/prodtests/full_system_test.sh +++ b/prodtests/full_system_test.sh @@ -340,6 +340,12 @@ for STAGE in $STAGES; do if [[ $aod_size -gt 0 ]]; then echo "AO2D file produced: AO2D.root (size: ${aod_size} bytes)" echo "aod_size_${STAGE},${TAG} value=${aod_size}" >> ${METRICFILE} + # Check that the metadata TMap is present + if ! root -b -l -q -e 'auto* f = TFile::Open("AO2D.root"); if (!f || f->IsZombie()) { exit(1); } if (!dynamic_cast(f->Get("metaData"))) { std::cerr << "ERROR: metaData TMap missing from AO2D.root" << std::endl; exit(1); }' 2>&1; then + echo "ERROR: metaData TMap missing from AO2D.root" + exit 1 + fi + echo "AO2D metaData TMap present" else echo "ERROR: AO2D file (AO2D.root) exists but is empty" echo "aod_size_${STAGE},${TAG} value=0" >> ${METRICFILE} From d91bd52f914c9a12fe4d29ccb0526f7fb7150d57 Mon Sep 17 00:00:00 2001 From: Gabriele Cimador Date: Fri, 24 Apr 2026 21:19:14 +0200 Subject: [PATCH 006/102] GPU TPC: Slighly augment dynamic buffer for TPC Decoding --- GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx | 9 +++++---- GPU/GPUTracking/Definitions/GPUSettingsList.h | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx b/GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx index e28718e6b6755..b44cdb420d74b 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx @@ -118,9 +118,10 @@ void GPUTPCDecompression::RegisterMemoryAllocation() void GPUTPCDecompression::SetMaxData(const GPUTrackingInOutPointers& io) { uint32_t maxAttachedClsMargin1 = *std::max_element(mInputGPU.nSliceRowClusters, mInputGPU.nSliceRowClusters + mInputGPU.nSliceRows); - float clsRatio1 = (mInputGPU.nUnattachedClusters > 0 ? float(mInputGPU.nAttachedClusters) / float(mInputGPU.nUnattachedClusters) : 1.0f) * 1.5f; + float clsRatio1 = (mInputGPU.nUnattachedClusters > 0 ? float(mInputGPU.nAttachedClusters) / float(mInputGPU.nUnattachedClusters) : 1.0f) * mRec->MemoryScalers()->tpcDecodingClusterRatioFactor1; maxAttachedClsMargin1 *= clsRatio1; - uint32_t maxAttachedClsMargin2 = mInputGPU.nSliceRows > 0 ? (mInputGPU.nAttachedClusters / mInputGPU.nSliceRows * 3.5) : 0; // mean #attached cls per SectorRow multiplied by 3.5 (tuned) - mMaxNativeClustersPerBuffer = std::max({maxAttachedClsMargin1, maxAttachedClsMargin2, 1000u}); // take biggest margin, 1000 clusters minimum - mMaxNativeClustersPerBuffer = std::min(mMaxNativeClustersPerBuffer, mRec->GetProcessingSettings().tpcMaxAttachedClustersPerSectorRow); // upperbound given by configurable param + uint32_t maxAttachedClsMargin2 = mInputGPU.nSliceRows > 0 ? (mInputGPU.nAttachedClusters / mInputGPU.nSliceRows * mRec->MemoryScalers()->tpcDecodingClusterRatioFactor2) : 0; // mean #attached cls per SectorRow multiplied by 3.5 (tuned) + mMaxNativeClustersPerBuffer = std::max({maxAttachedClsMargin1, maxAttachedClsMargin2, 1000u}); // take biggest margin, 1000 clusters minimum + mMaxNativeClustersPerBuffer = std::min(mMaxNativeClustersPerBuffer, mRec->GetProcessingSettings().tpcMaxAttachedClustersPerSectorRow); // upperbound given by configurable param + mMaxNativeClustersPerBuffer += mRec->MemoryScalers()->tpcDecodingSafetyBuffer; } diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index bbf8dbb508b4a..43a5f4f79abdc 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -297,7 +297,7 @@ AddOption(nnCCDBInteractionRate, std::string, "500", "", 0, "Distinguishes betwe AddHelp("help", 'h') EndConfig() -// Settings steering the processing of NN Clusterization +// Scaling factors for gpu buffer size estimation BeginSubConfig(GPUSettingsProcessingScaling, scaling, configStandalone.proc, "SCALING", 0, "Processing settings for neural network clusterizer", proc_scaling) AddOption(offset, float, 1000., "", 0, "Scaling Factor: offset") AddOption(hitOffset, float, 20000, "", 0, "Scaling Factor: hitOffset") @@ -315,6 +315,9 @@ AddOption(tpcMergedTrackPerSectorTrack, float, 1.0, "", 0, "Scaling Factor: tpcM AddOption(tpcMergedTrackHitPerSectorHit, float, 1.1, "", 0, "Scaling Factor: tpcMergedTrackHitPerSectorHit") AddOptionArray(tpcCompressedUnattachedHitsBase1024, int32_t, 3, (900, 900, 500), "", 0, "Scaling Factor: tpcCompressedUnattachedHitsBase1024") AddOption(conservativeMemoryEstimate, bool, false, "", 0, "Use some more conservative defaults for larger buffers during TPC processing") +AddOption(tpcDecodingClusterRatioFactor1, float, 1.5, "", 0, "Scaling Factor: for first margin of dynamic buffer allocation for attached clusters in TPC Decoding") +AddOption(tpcDecodingClusterRatioFactor2, float, 3.5, "", 0, "Scaling Factor: for second margin of dynamic buffer allocation for attached clusters in TPC Decoding") +AddOption(tpcDecodingSafetyBuffer, uint16_t, 1000, "", 0, "Scaling Factor: safety cluster buffer to add to dynamic buffer allocation for attached clusters in TPC Decoding") AddHelp("help", 'h') EndConfig() From 5bfa7ec76c43de8f5e46f8d9e880d04a8f3afe4e Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Mon, 27 Apr 2026 11:17:13 +0200 Subject: [PATCH 007/102] ITS: fix ITSTrackROF outpu (#15328) * ITS: fix ITSTrackROF outpu Signed-off-by: Felix Schlepper * TPCITS: make matcher internally consistent Signed-off-by: Felix Schlepper --------- Signed-off-by: Felix Schlepper --- Detectors/GlobalTracking/src/MatchTPCITS.cxx | 2 +- .../ITS/tracking/src/TrackingInterface.cxx | 157 +++++++++--------- 2 files changed, 81 insertions(+), 78 deletions(-) diff --git a/Detectors/GlobalTracking/src/MatchTPCITS.cxx b/Detectors/GlobalTracking/src/MatchTPCITS.cxx index 29c45edd6eb96..1457790c7c531 100644 --- a/Detectors/GlobalTracking/src/MatchTPCITS.cxx +++ b/Detectors/GlobalTracking/src/MatchTPCITS.cxx @@ -709,7 +709,7 @@ bool MatchTPCITS::prepareITSData() mITSWork.reserve(mITSTracksArray.size()); // total N ITS clusters in TF - const auto& lastClROF = mITSClusterROFRec[nROFs - 1]; + const auto& lastClROF = mITSClusterROFRec.back(); int nITSClus = lastClROF.getFirstEntry() + lastClROF.getNEntries(); mABClusterLinkIndex.resize(nITSClus, MinusOne); for (int sec = o2::constants::math::NSectors; sec--;) { diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx index cc8731c8b6912..d469fa4246ef5 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx @@ -160,8 +160,27 @@ void ITSTrackingInterface::run(framework::ProcessingContext& pc) auto& allVerticesLabels = mIsMC ? pc.outputs().make>(Output{"ITS", "VERTICESMCTR", 0}) : dummyMCLabVerts; auto& allVerticesPurities = mIsMC ? pc.outputs().make>(Output{"ITS", "VERTICESMCPUR", 0}) : dummyMCPurVerts; + const auto clock = mTimeFrame->getROFOverlapTableView().getClock(); + const auto& clockLayer = mTimeFrame->getROFOverlapTableView().getClockLayer(); + auto setBCData = [&](auto& rofs) { + for (size_t iROF{0}; iROF < rofs.size(); ++iROF) { // set BC data + auto& rof = rofs[iROF]; + int orb = (iROF * par.getROFLengthInBC(clock) / o2::constants::lhc::LHCMaxBunches) + tfInfo.firstTForbit; + int bc = (iROF * par.getROFLengthInBC(clock) % o2::constants::lhc::LHCMaxBunches) + par.getROFDelayInBC(clock); + o2::InteractionRecord ir(bc, orb); + rof.setBCData(ir); + rof.setROFrame(iROF); + rof.setNEntries(0); + rof.setFirstEntry(-1); + } + }; + if (!hasClusters) { // skip processing if no data is received entirely but still create empty output so consumers do not wait + allTrackROFs.resize(clockLayer.mNROFsTF); + vertROFvec.resize(clockLayer.mNROFsTF); + setBCData(allTrackROFs); + setBCData(vertROFvec); return; } @@ -269,86 +288,70 @@ void ITSTrackingInterface::run(framework::ProcessingContext& pc) if (mTimeFrame->hasBogusClusters()) { LOG(warning) << fmt::format(" + The processed timeframe had {} clusters with wild z coordinates, check the dictionaries", mTimeFrame->hasBogusClusters()); } + } - auto& tracks = mTimeFrame->getTracks(); - allTrackLabels.reserve(mTimeFrame->getTracksLabel().size()); // should be 0 if not MC - std::copy(mTimeFrame->getTracksLabel().begin(), mTimeFrame->getTracksLabel().end(), std::back_inserter(allTrackLabels)); - { - // create the track to clock ROF association here - // the clock ROF is just the fastest ROF - // the number of ROFs does not necessarily reflect the actual ROFs - // due to possible delay of other layers, however it is guaranteed to be >=0 - // tracks are guaranteed to be sorted here by their lower edge - const auto& clock = mTimeFrame->getROFOverlapTableView().getClock(); - const auto& clockLayer = mTimeFrame->getROFOverlapTableView().getClockLayer(); - auto setBCData = [&](auto& rofs) { - for (size_t iROF{0}; iROF < rofs.size(); ++iROF) { // set BC data - auto& rof = rofs[iROF]; - int orb = (iROF * par.getROFLengthInBC(clock) / o2::constants::lhc::LHCMaxBunches) + tfInfo.firstTForbit; - int bc = (iROF * par.getROFLengthInBC(clock) % o2::constants::lhc::LHCMaxBunches) + par.getROFDelayInBC(clock); - o2::InteractionRecord ir(bc, orb); - rof.setBCData(ir); - rof.setROFrame(iROF); - rof.setNEntries(0); - rof.setFirstEntry(-1); - } - }; - // we pick whatever is the largest possible number of rofs since there might be tracks/vertices which are beyond - // the clock layer - int highestROF{0}; - for (const auto& trc : tracks) { - highestROF = std::max(highestROF, (int)clockLayer.getROF(trc.getTimeStamp())); - } - for (const auto& vtx : vertices) { - highestROF = std::max(highestROF, (int)clockLayer.getROF(vtx.getTimeStamp().lower())); - } - highestROF = std::max(highestROF, (int)clockLayer.mNROFsTF); - allTrackROFs.resize(highestROF); - vertROFvec.resize(highestROF); - setBCData(allTrackROFs); - setBCData(vertROFvec); - - mTimeFrame->useMultiplictyMask(); // use multiplicty selection for IR frames - - std::vector rofEntries(highestROF + 1, 0); - for (unsigned int iTrk{0}; iTrk < tracks.size(); ++iTrk) { - auto& trc{tracks[iTrk]}; - trc.setFirstClusterEntry((int)allClusIdx.size()); // before adding tracks, create final cluster indices - int ncl = trc.getNumberOfClusters(), nclf = 0; - for (int ic = TrackITSExt::MaxClusters; ic--;) { // track internally keeps in->out cluster indices, but we want to store the references as out->in!!! - auto clid = trc.getClusterIndex(ic); - if (clid >= 0) { - trc.setClusterSize(ic, mTimeFrame->getClusterSize((mDoStaggering) ? ic : 0, clid)); - allClusIdx.push_back(clid); - nclf++; - } - } - assert(ncl == nclf); - allTracks.emplace_back(trc); - auto rof = clockLayer.getROF(trc.getTimeStamp()); - ++rofEntries[rof]; - } - std::exclusive_scan(rofEntries.begin(), rofEntries.end(), rofEntries.begin(), 0); - for (size_t iROF{0}; iROF < allTrackROFs.size(); ++iROF) { - allTrackROFs[iROF].setFirstEntry(rofEntries[iROF]); - allTrackROFs[iROF].setNEntries(rofEntries[iROF + 1] - rofEntries[iROF]); - if (mTimeFrame->getROFMaskView().isROFEnabled(clockLayerId, (int)iROF)) { - auto& irFrame = irFrames.emplace_back(allTrackROFs[iROF].getBCData(), allTrackROFs[iROF].getBCData() + clockLayer.mROFLength - 1); - irFrame.info = allTrackROFs[iROF].getNEntries(); - } - } - // same thing for vertices rofs - std::fill(rofEntries.begin(), rofEntries.end(), 0); - for (const auto& vtx : vertices) { - auto rof = clockLayer.getROF(vtx.getTimeStamp().lower()); - ++rofEntries[rof]; - } - std::exclusive_scan(rofEntries.begin(), rofEntries.end(), rofEntries.begin(), 0); - for (size_t iROF{0}; iROF < vertROFvec.size(); ++iROF) { - vertROFvec[iROF].setFirstEntry(rofEntries[iROF]); - vertROFvec[iROF].setNEntries(rofEntries[iROF + 1] - rofEntries[iROF]); + auto& tracks = mTimeFrame->getTracks(); + allTrackLabels.reserve(mTimeFrame->getTracksLabel().size()); // should be 0 if not MC + std::copy(mTimeFrame->getTracksLabel().begin(), mTimeFrame->getTracksLabel().end(), std::back_inserter(allTrackLabels)); + // create the track to clock ROF association here + // the clock ROF is just the fastest ROF + // the number of ROFs does not necessarily reflect the actual ROFs + // due to possible delay of other layers, however it is guaranteed to be >=0 + // tracks are guaranteed to be sorted here by their lower edge + // we pick whatever is the largest possible number of rofs since there might be tracks/vertices which are beyond + // the clock layer + int highestROF{0}; + for (const auto& trc : tracks) { + highestROF = std::max(highestROF, (int)clockLayer.getROF(trc.getTimeStamp())); + } + for (const auto& vtx : vertices) { + highestROF = std::max(highestROF, (int)clockLayer.getROF(vtx.getTimeStamp().lower())); + } + highestROF = std::max(highestROF, (int)clockLayer.mNROFsTF); + allTrackROFs.resize(highestROF); + vertROFvec.resize(highestROF); + setBCData(allTrackROFs); + setBCData(vertROFvec); + + mTimeFrame->useMultiplictyMask(); // use multiplicty selection for IR frames + + std::vector rofEntries(highestROF + 1, 0); + for (unsigned int iTrk{0}; iTrk < tracks.size(); ++iTrk) { + auto& trc{tracks[iTrk]}; + trc.setFirstClusterEntry((int)allClusIdx.size()); // before adding tracks, create final cluster indices + int ncl = trc.getNumberOfClusters(), nclf = 0; + for (int ic = TrackITSExt::MaxClusters; ic--;) { // track internally keeps in->out cluster indices, but we want to store the references as out->in!!! + auto clid = trc.getClusterIndex(ic); + if (clid >= 0) { + trc.setClusterSize(ic, mTimeFrame->getClusterSize((mDoStaggering) ? ic : 0, clid)); + allClusIdx.push_back(clid); + nclf++; } } + assert(ncl == nclf); + allTracks.emplace_back(trc); + auto rof = clockLayer.getROF(trc.getTimeStamp()); + ++rofEntries[rof]; + } + std::exclusive_scan(rofEntries.begin(), rofEntries.end(), rofEntries.begin(), 0); + for (size_t iROF{0}; iROF < allTrackROFs.size(); ++iROF) { + allTrackROFs[iROF].setFirstEntry(rofEntries[iROF]); + allTrackROFs[iROF].setNEntries(rofEntries[iROF + 1] - rofEntries[iROF]); + if (mTimeFrame->getROFMaskView().isROFEnabled(clockLayerId, (int)iROF)) { + auto& irFrame = irFrames.emplace_back(allTrackROFs[iROF].getBCData(), allTrackROFs[iROF].getBCData() + clockLayer.mROFLength - 1); + irFrame.info = allTrackROFs[iROF].getNEntries(); + } + } + // same thing for vertices rofs + std::fill(rofEntries.begin(), rofEntries.end(), 0); + for (const auto& vtx : vertices) { + auto rof = clockLayer.getROF(vtx.getTimeStamp().lower()); + ++rofEntries[rof]; + } + std::exclusive_scan(rofEntries.begin(), rofEntries.end(), rofEntries.begin(), 0); + for (size_t iROF{0}; iROF < vertROFvec.size(); ++iROF) { + vertROFvec[iROF].setFirstEntry(rofEntries[iROF]); + vertROFvec[iROF].setNEntries(rofEntries[iROF + 1] - rofEntries[iROF]); } LOGP(info, "ITSTracker pushed {} tracks in {} rofs and {} vertices {}", allTracks.size(), allTrackROFs.size(), vertices.size(), ((mDoStaggering) ? "in staggered-readout mode" : "")); From 2cc88ed03e8ece02aa38da697508837ea465f894 Mon Sep 17 00:00:00 2001 From: shahoian Date: Mon, 27 Apr 2026 10:59:42 +0200 Subject: [PATCH 008/102] Fix typo in GPUTPCGMMerger::MergedTrackStreamer --- GPU/GPUTracking/Merger/GPUTPCGMMergerDump.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GPUTracking/Merger/GPUTPCGMMergerDump.cxx b/GPU/GPUTracking/Merger/GPUTPCGMMergerDump.cxx index 9ee2bcf4eb6b5..9f6858df4d3f2 100644 --- a/GPU/GPUTracking/Merger/GPUTPCGMMergerDump.cxx +++ b/GPU/GPUTracking/Merger/GPUTPCGMMergerDump.cxx @@ -289,7 +289,7 @@ inline void GPUTPCGMMerger::MergedTrackStreamerInternal(const GPUTPCGMBorderTrac void GPUTPCGMMerger::MergedTrackStreamer(const GPUTPCGMBorderTrack& b1, const GPUTPCGMBorderTrack& b2, const char* name, int32_t sector1, int32_t sector2, uint8_t mergeMode, float weight, float frac) const { #ifdef DEBUG_STREAMER - if (!(mergeMode & mergeModes::mergeAcrossCE0)) { + if (!(mergeMode & mergeModes::mergeAcrossCE)) { MergedTrackStreamerInternal<0>(b1, b2, name, sector1, sector2, mergeMode, weight, frac); } #endif From 5dfe60ee3b74a7cebe3233e7445e9ffdff98da53 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Mon, 27 Apr 2026 23:45:18 +0200 Subject: [PATCH 009/102] DPL: boost::property_tree::json_parser::write_json no longer adds EoL (#15333) --- Framework/Core/src/DataProcessingDevice.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/Framework/Core/src/DataProcessingDevice.cxx b/Framework/Core/src/DataProcessingDevice.cxx index b062f2bf68a75..2bc01df7c0260 100644 --- a/Framework/Core/src/DataProcessingDevice.cxx +++ b/Framework/Core/src/DataProcessingDevice.cxx @@ -402,7 +402,6 @@ void DataProcessingDevice::Init() if (entry.second.empty() == false) { boost::property_tree::json_parser::write_json(ss, entry.second, false); str = ss.str(); - str.pop_back(); // remove EoL } else { str = entry.second.get_value(); } From 75f2eef3bf0325d7945d912f77dd0df13d1fa81f Mon Sep 17 00:00:00 2001 From: shahoian Date: Mon, 27 Apr 2026 18:16:01 +0200 Subject: [PATCH 010/102] Fix sign of the tgL in ITS seeding --- .../ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h index 584d28a3cd9a8..2224495607ee1 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h @@ -80,8 +80,7 @@ GPUdi() o2::track::TrackParCov buildTrackSeed(const Cluster& cluster1, q2pt = sign * crv / (bz * o2::constants::math::B2C); q2pt2 = crv * crv; } - const float tgl = 0.5f * (math_utils::computeTanDipAngle(x1, y1, x2, y2, cluster1.zCoordinate, cluster2.zCoordinate) + - math_utils::computeTanDipAngle(x2, y2, x3, y3, cluster2.zCoordinate, tf3.positionTrackingFrame[1])); + const float tgl = -0.5f * sign * (math_utils::computeTanDipAngle(x1, y1, x2, y2, cluster1.zCoordinate, cluster2.zCoordinate) + math_utils::computeTanDipAngle(x2, y2, x3, y3, cluster2.zCoordinate, tf3.positionTrackingFrame[1])); const float sg2q2pt = o2::track::kC1Pt2max * o2::gpu::CAMath::Clamp(q2pt2, 0.0005f, 1.0f); return {x3, tf3.alphaTrackingFrame, {y3, tf3.positionTrackingFrame[1], snp, tgl, q2pt}, {tf3.covarianceTrackingFrame[0], tf3.covarianceTrackingFrame[1], tf3.covarianceTrackingFrame[2], 0.f, 0.f, o2::track::kCSnp2max, 0.f, 0.f, 0.f, o2::track::kCTgl2max, 0.f, 0.f, 0.f, 0.f, sg2q2pt}}; } From f3a09eadecd35b9e01e4b5632d1aa047f9fc2fa1 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:21:04 +0200 Subject: [PATCH 011/102] DPL: improve messages about missing resources (#15330) --- .../Framework/ComputingQuotaEvaluator.h | 4 ++- .../Core/include/Framework/ResourcePolicy.h | 3 ++ .../Core/src/ComputingQuotaEvaluator.cxx | 15 +++++--- Framework/Core/src/DataProcessingDevice.cxx | 36 ++++++++++++++++--- Framework/Core/src/ResourcePolicyHelpers.cxx | 13 ++++--- 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/Framework/Core/include/Framework/ComputingQuotaEvaluator.h b/Framework/Core/include/Framework/ComputingQuotaEvaluator.h index 17ce9c2ba3e65..b25bc1611d79f 100644 --- a/Framework/Core/include/Framework/ComputingQuotaEvaluator.h +++ b/Framework/Core/include/Framework/ComputingQuotaEvaluator.h @@ -37,7 +37,9 @@ class ComputingQuotaEvaluator /// @a task the task which needs some quota /// @a request the resource request the @a task needs /// @a now the time (e.g. uv_now) when invoked. - bool selectOffer(int task, ComputingQuotaRequest const& request, uint64_t now); + /// @a accumulated if non-null, filled with the resources accumulated from + /// selected offers (useful for diagnosing shortfalls on failure). + bool selectOffer(int task, ComputingQuotaRequest const& request, uint64_t now, ComputingQuotaOffer* accumulated = nullptr); /// Consume offers for a given taskId /// @a reportConsumedOffer callback which reports back that an offer has been consumed. void consume(int taskId, diff --git a/Framework/Core/include/Framework/ResourcePolicy.h b/Framework/Core/include/Framework/ResourcePolicy.h index eb8d77b209a8f..1062c223b07f6 100644 --- a/Framework/Core/include/Framework/ResourcePolicy.h +++ b/Framework/Core/include/Framework/ResourcePolicy.h @@ -31,6 +31,9 @@ struct ResourcePolicy { std::string name; Matcher matcher; ComputingQuotaRequest request; + /// Minimum resources required to run. Used to report which resources + /// are missing when scheduling fails. + ComputingQuotaOffer minRequired; }; } // namespace o2::framework diff --git a/Framework/Core/src/ComputingQuotaEvaluator.cxx b/Framework/Core/src/ComputingQuotaEvaluator.cxx index 3f5bff2b53fab..5dd4249cab519 100644 --- a/Framework/Core/src/ComputingQuotaEvaluator.cxx +++ b/Framework/Core/src/ComputingQuotaEvaluator.cxx @@ -62,7 +62,7 @@ struct QuotaEvaluatorStats { std::vector expired; }; -bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& selector, uint64_t now) +bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& selector, uint64_t now, ComputingQuotaOffer* outAccumulated) { O2_SIGNPOST_ID_GENERATE(qid, quota); @@ -102,10 +102,13 @@ bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& } dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCES_SATISFACTORY), DataProcessingStats::Op::Add, 1}); } else { - O2_SIGNPOST_START(quota, sid, "summary", "Not enough resources to select offers."); - dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCES_MISSING), DataProcessingStats::Op::Add, 1}); if (result.size()) { + O2_SIGNPOST_START(quota, sid, "summary", "Not enough resources: accumulated %zu partial offers providing cpu=%d, memory=%lld MB, shared memory=%lld MB, timeslices=%lld, but still insufficient.", + result.size(), totalOffer.cpu, totalOffer.memory / 1000000, totalOffer.sharedMemory / 1000000, totalOffer.timeslices); dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCES_INSUFFICIENT), DataProcessingStats::Op::Add, 1}); + } else { + O2_SIGNPOST_START(quota, sid, "summary", "Not enough resources: no suitable offers found (all offers were invalid, expired, or owned by other tasks)."); + dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCES_MISSING), DataProcessingStats::Op::Add, 1}); } } if (stats.invalidOffers.size()) { @@ -205,7 +208,11 @@ bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& O2_SIGNPOST_EVENT_EMIT(quota, tid, "select", "Offer should be expired by now, checking again."); }, minValidity + 100, 0); } // If we get here it means we never got enough offers, so we return false. - return summarizeWhatHappended(enough, stats.selectedOffers, accumulated, stats); + bool result = summarizeWhatHappended(enough, stats.selectedOffers, accumulated, stats); + if (outAccumulated) { + *outAccumulated = accumulated; + } + return result; } void ComputingQuotaEvaluator::consume(int id, ComputingQuotaConsumer& consumer, std::function& reportConsumedOffer) diff --git a/Framework/Core/src/DataProcessingDevice.cxx b/Framework/Core/src/DataProcessingDevice.cxx index 2bc01df7c0260..8677aaa42a363 100644 --- a/Framework/Core/src/DataProcessingDevice.cxx +++ b/Framework/Core/src/DataProcessingDevice.cxx @@ -1370,7 +1370,8 @@ void DataProcessingDevice::Run() // the evaluator. In this case, the request is always satisfied and // we run on whatever resource is available. auto& spec = ref.get(); - bool enough = ref.get().selectOffer(streamRef.index, spec.resourcePolicy.request, uv_now(state.loop)); + ComputingQuotaOffer accumulated; + bool enough = ref.get().selectOffer(streamRef.index, spec.resourcePolicy.request, uv_now(state.loop), &accumulated); struct SchedulingStats { std::atomic lastScheduled = 0; @@ -1398,17 +1399,42 @@ void DataProcessingDevice::Run() run_completion(&handle, 0); } } else { + auto const lastSched = schedulingStats.lastScheduled.load(); + auto const schedInfo = lastSched ? fmt::format(", last scheduled {} ms ago", uv_now(state.loop) - lastSched) : std::string(", never successfully scheduled"); + auto const buildMissingInfo = [&]() { + auto const& required = spec.resourcePolicy.minRequired; + std::string missingInfo; + if (required.sharedMemory > 0 && accumulated.sharedMemory < required.sharedMemory) { + missingInfo += fmt::format(" shared memory (have {} MB, need {} MB)", accumulated.sharedMemory / 1000000, required.sharedMemory / 1000000); + } + if (required.timeslices > 0 && accumulated.timeslices < required.timeslices) { + missingInfo += fmt::format(" timeslices (have {}, need {})", accumulated.timeslices, required.timeslices); + } + if (required.cpu > 0 && accumulated.cpu < required.cpu) { + missingInfo += fmt::format(" CPU cores (have {}, need {})", accumulated.cpu, required.cpu); + } + if (required.memory > 0 && accumulated.memory < required.memory) { + missingInfo += fmt::format(" memory (have {} MB, need {} MB)", accumulated.memory / 1000000, required.memory / 1000000); + } + return missingInfo.empty() ? std::string(" (policy: ") + spec.resourcePolicy.name + ")" : " -" + missingInfo; + }; if (schedulingStats.numberOfUnscheduledSinceLastScheduled >= schedulingStats.nextWarnAt) { + auto const missingStr = buildMissingInfo(); O2_SIGNPOST_EVENT_EMIT_WARN(scheduling, sid, "Run", - "Not enough resources to schedule computation. %zu skipped so far. Last scheduled at %zu. Data is not lost and it will be scheduled again.", + "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.", + streamRef.index, schedulingStats.numberOfUnscheduledSinceLastScheduled.load(), - schedulingStats.lastScheduled.load()); + schedInfo.c_str(), + missingStr.c_str()); schedulingStats.nextWarnAt = schedulingStats.nextWarnAt * 2; } else { + auto const missingStr = buildMissingInfo(); O2_SIGNPOST_EVENT_EMIT(scheduling, sid, "Run", - "Not enough resources to schedule computation. %zu skipped so far. Last scheduled at %zu. Data is not lost and it will be scheduled again.", + "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.", + streamRef.index, schedulingStats.numberOfUnscheduledSinceLastScheduled.load(), - schedulingStats.lastScheduled.load()); + schedInfo.c_str(), + missingStr.c_str()); } schedulingStats.numberOfUnscheduled++; schedulingStats.numberOfUnscheduledSinceLastScheduled++; diff --git a/Framework/Core/src/ResourcePolicyHelpers.cxx b/Framework/Core/src/ResourcePolicyHelpers.cxx index 2c5c4f54dd9b5..650beec3ac599 100644 --- a/Framework/Core/src/ResourcePolicyHelpers.cxx +++ b/Framework/Core/src/ResourcePolicyHelpers.cxx @@ -36,7 +36,8 @@ ResourcePolicy ResourcePolicyHelpers::cpuBoundTask(char const* s, int requestedC [matcher = std::regex(s)](DeviceSpec const& spec) -> bool { return std::regex_match(spec.name, matcher); }, - [requestedCPUs](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { return accumulated.cpu >= requestedCPUs ? OfferScore::Enough : OfferScore::More; }}; + [requestedCPUs](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { return accumulated.cpu >= requestedCPUs ? OfferScore::Enough : OfferScore::More; }, + ComputingQuotaOffer{.cpu = requestedCPUs}}; } ResourcePolicy ResourcePolicyHelpers::rateLimitedSharedMemoryBoundTask(char const* s, int requestedSharedMemory, int requestedTimeslices) @@ -46,7 +47,7 @@ ResourcePolicy ResourcePolicyHelpers::rateLimitedSharedMemoryBoundTask(char cons [matcher = std::regex(s)](DeviceSpec const& spec) -> bool { return std::regex_match(spec.name, matcher); }, - [requestedSharedMemory, requestedTimeslices](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { + [requestedSharedMemory, requestedTimeslices](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { // If we have enough memory and not enough timeslices, // ignore further shared memory. if (accumulated.sharedMemory >= requestedSharedMemory && offer.timeslices == 0) { @@ -66,7 +67,8 @@ ResourcePolicy ResourcePolicyHelpers::rateLimitedSharedMemoryBoundTask(char cons return OfferScore::Enough; } // We need more resources - return OfferScore::More; }}; + return OfferScore::More; }, + ComputingQuotaOffer{.sharedMemory = requestedSharedMemory, .timeslices = requestedTimeslices}}; } ResourcePolicy ResourcePolicyHelpers::sharedMemoryBoundTask(char const* s, int requestedSharedMemory) @@ -76,11 +78,12 @@ ResourcePolicy ResourcePolicyHelpers::sharedMemoryBoundTask(char const* s, int r [matcher = std::regex(s)](DeviceSpec const& spec) -> bool { return std::regex_match(spec.name, matcher); }, - [requestedSharedMemory](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { + [requestedSharedMemory](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { if (offer.sharedMemory == 0) { return OfferScore::Unneeded; } - return (accumulated.sharedMemory + offer.sharedMemory)>= requestedSharedMemory ? OfferScore::Enough : OfferScore::More; }}; + return (accumulated.sharedMemory + offer.sharedMemory) >= requestedSharedMemory ? OfferScore::Enough : OfferScore::More; }, + ComputingQuotaOffer{.sharedMemory = requestedSharedMemory}}; } } // namespace o2::framework From 4bd0da6880f3495451cca41ea7fbdad0e831b1d2 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Mon, 27 Apr 2026 15:18:01 +0200 Subject: [PATCH 012/102] CollisionContextTool: Ability to use o2::ft0::EventsPerBC calib object --- .../FIT/FT0/include/DataFormatsFT0/EventsPerBc.h | 13 +++++++++++++ Steer/CMakeLists.txt | 2 +- Steer/src/CollisionContextTool.cxx | 11 ++++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/EventsPerBc.h b/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/EventsPerBc.h index 9fcd1318914bd..632eac342fdc9 100644 --- a/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/EventsPerBc.h +++ b/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/EventsPerBc.h @@ -14,11 +14,24 @@ #include "CommonConstants/LHCConstants.h" #include +#include +#include namespace o2::ft0 { struct EventsPerBc { std::array histogram; + + std::unique_ptr toTH1F(const char* name = "eventsPerBc") const + { + constexpr int N = o2::constants::lhc::LHCMaxBunches; + auto h = std::make_unique(name, name, N, 0, N); + for (int i = 0; i < N; ++i) { + h->SetBinContent(i + 1, histogram[i]); + } + return h; + } + ClassDefNV(EventsPerBc, 1); }; } // namespace o2::ft0 diff --git a/Steer/CMakeLists.txt b/Steer/CMakeLists.txt index 8e2706d31bb0a..0d7f300c5513e 100644 --- a/Steer/CMakeLists.txt +++ b/Steer/CMakeLists.txt @@ -23,7 +23,7 @@ o2_add_library(Steer o2_add_executable(colcontexttool COMPONENT_NAME steer SOURCES src/CollisionContextTool.cxx - PUBLIC_LINK_LIBRARIES Boost::program_options O2::Algorithm O2::Steer O2::SimulationDataFormat) + PUBLIC_LINK_LIBRARIES Boost::program_options O2::Algorithm O2::Steer O2::SimulationDataFormat O2::DataFormatsFT0) o2_target_root_dictionary(Steer HEADERS include/Steer/HitProcessingManager.h diff --git a/Steer/src/CollisionContextTool.cxx b/Steer/src/CollisionContextTool.cxx index 6bee407c01264..e97eeada3fd0c 100644 --- a/Steer/src/CollisionContextTool.cxx +++ b/Steer/src/CollisionContextTool.cxx @@ -19,6 +19,7 @@ #include "DataFormatsCalibration/MeanVertexObject.h" #include "SimulationDataFormat/DigitizationContext.h" #include "SimConfig/InteractionDiamondParam.h" +#include "DataFormatsFT0/EventsPerBc.h" #include #include #include @@ -424,7 +425,7 @@ int main(int argc, char* argv[]) auto mode = ispecs[id].syncmode; if (mode == InteractionLockMode::NOLOCK) { auto sampler = std::make_unique(); - TH1F* mu_hist = nullptr; + std::unique_ptr mu_hist; // we check if there is a realistic bunch crossing distribution available const auto& mu_distr_source = options.nontrivial_mu_distribution; @@ -441,7 +442,11 @@ int main(int argc, char* argv[]) ccdb_inst.setFatalWhenNull(false); auto local_hist = ccdb_inst.getForTimeStamp(ccdb_info.fullPath, options.timestamp); if (local_hist) { - mu_hist = (TH1F*)(local_hist->Clone("h2")); // we need to clone since ownership of local_hist is with TFile + // case in which CCDB object contains directly a ROOT histogram + mu_hist.reset((TH1F*)local_hist->Clone("h2")); // we need to clone since ownership of local_hist is with TFile + } else if (auto events_per_bc = ccdb_inst.getForTimeStamp(ccdb_info.fullPath, options.timestamp)) { + // case in which CCDB object is from FT0 EventsPerBC calib (will be default) + mu_hist = events_per_bc->toTH1F(); } else { LOG(warn) << "No mu(bc) distribution found on CCDB. Using uniform one"; } @@ -451,7 +456,7 @@ int main(int argc, char* argv[]) auto mudistr_file = TFile::Open(mu_distr_source.c_str(), "OPEN"); if (mudistr_file && !mudistr_file->IsZombie()) { auto local_hist = mudistr_file->Get("hBcTVX"); - mu_hist = (TH1F*)(local_hist->Clone("h2")); // we need to clone since ownership of local_hist is with TFile + mu_hist.reset((TH1F*)local_hist->Clone("h2")); // we need to clone since ownership of local_hist is with TFile mudistr_file->Close(); } } From 2ab49d25cbff7f5980f4e974b636737489addc8f Mon Sep 17 00:00:00 2001 From: pillot Date: Thu, 27 Nov 2025 10:24:58 +0100 Subject: [PATCH 013/102] filter trackable ROFs at clustering level --- .../MUON/MCH/Base/include/MCHBase/Trackable.h | 12 ++++++- Detectors/MUON/MCH/Base/src/Trackable.cxx | 22 ++++++++++++ .../include/MCHClustering/ClusterizerParam.h | 2 ++ .../include/MCHROFFiltering/TrackableFilter.h | 28 ++++++++++++++- Detectors/MUON/MCH/Workflow/CMakeLists.txt | 1 + .../src/ClusterFinderOriginalSpec.cxx | 35 ++++++++++++++++--- 6 files changed, 94 insertions(+), 6 deletions(-) diff --git a/Detectors/MUON/MCH/Base/include/MCHBase/Trackable.h b/Detectors/MUON/MCH/Base/include/MCHBase/Trackable.h index a862be411cb35..ef556d49a9201 100644 --- a/Detectors/MUON/MCH/Base/include/MCHBase/Trackable.h +++ b/Detectors/MUON/MCH/Base/include/MCHBase/Trackable.h @@ -38,11 +38,21 @@ bool isTrackable(std::array itemsPerChamber, /** Return the number of items per chamber. * * @tparam T the type of items : implementation exists so far - * only for mch::Digit (clusters and pre-clusters to come next) + * for deIds (int) and mch::Digit */ template std::array perChamber(gsl::span items); +/** Return the number of items per chamber. + * + * @tparam T1 the type of items : implementation exists so far + * for mch::PreCluster + * @tparam T2 the type of subitems pointed to by items, + * e.g. mch::Digit attached to mch::PreCluster + */ +template +std::array perChamber(gsl::span items, gsl::span subitems); + /** Return the number of items per station (1 station==2 chambers). */ template std::array perStation(gsl::span items) diff --git a/Detectors/MUON/MCH/Base/src/Trackable.cxx b/Detectors/MUON/MCH/Base/src/Trackable.cxx index c25b12945cb90..0545f7cb1eac5 100644 --- a/Detectors/MUON/MCH/Base/src/Trackable.cxx +++ b/Detectors/MUON/MCH/Base/src/Trackable.cxx @@ -10,7 +10,9 @@ // or submit itself to any jurisdiction. #include "MCHBase/Trackable.h" + #include "DataFormatsMCH/Digit.h" +#include "MCHBase/PreCluster.h" namespace o2::mch { @@ -59,7 +61,27 @@ std::array perChamber(gsl::span digits) for (const auto& digit : digits) { nofDigits[digit.getDetID() / 100 - 1]++; } + // do not count isolated digits (at least 2 are required for a cluster) + for (auto i = 0; i < 10; ++i) { + if (nofDigits[i] == 1) { + nofDigits[i] = 0; + } + } return nofDigits; } +/** Specialization of perChamber for PreClusters */ +template <> +std::array perChamber(gsl::span preclusters, gsl::span digits) +{ + std::array nofPreclusters{}; + for (const auto& precluster : preclusters) { + // only consider preclusters made of at least 2 digits + if (precluster.nDigits > 1) { + nofPreclusters[digits[precluster.firstDigit].getDetID() / 100 - 1]++; + } + } + return nofPreclusters; +} + } // namespace o2::mch diff --git a/Detectors/MUON/MCH/Clustering/include/MCHClustering/ClusterizerParam.h b/Detectors/MUON/MCH/Clustering/include/MCHClustering/ClusterizerParam.h index a24a8543af2cb..8f6f28a1f45d7 100644 --- a/Detectors/MUON/MCH/Clustering/include/MCHClustering/ClusterizerParam.h +++ b/Detectors/MUON/MCH/Clustering/include/MCHClustering/ClusterizerParam.h @@ -37,6 +37,8 @@ struct ClusterizerParam : public o2::conf::ConfigurableParamHelper ROFFilter createTrackableFilter(gsl::span items, @@ -46,6 +45,33 @@ ROFFilter }; } +/** Returns a ROFRecord filter that selects ROFs that are trackable. + * + * The returned filter is a function that takes a ROFRecord and returns + * a boolean. + * + * @param items : the items "pointed to" by the ROFRecords (preclusters, ...) + * @param subitems : the subitems "pointed to" by the items (digits, ...) + * + * @param requestStation : @ref isTrackable + * @param moreCandidates : @ref isTrackable + * + * @tparam T1 : the type of the items pointed to by the ROFRecords + * @tparam T2 : the type of the subitems pointed to by the items + */ +template +ROFFilter + createTrackableFilter(gsl::span items, + gsl::span subitems, + std::array requestStation = {true, true, true, true, true}, + bool moreCandidates = false) +{ + return [items, subitems, requestStation, moreCandidates](const ROFRecord& rof) { + std::array nofItemsPerChamber = perChamber(items.subspan(rof.getFirstIdx(), rof.getNEntries()), subitems); + return isTrackable(nofItemsPerChamber, requestStation, moreCandidates); + }; +} + } // namespace o2::mch #endif diff --git a/Detectors/MUON/MCH/Workflow/CMakeLists.txt b/Detectors/MUON/MCH/Workflow/CMakeLists.txt index e0fce7d103df7..f97c78526f21e 100644 --- a/Detectors/MUON/MCH/Workflow/CMakeLists.txt +++ b/Detectors/MUON/MCH/Workflow/CMakeLists.txt @@ -30,6 +30,7 @@ o2_add_library(MCHWorkflow O2::MCHPreClustering O2::MCHRawCommon O2::MCHRawDecoder + O2::MCHROFFiltering ROOT::TreePlayer ) diff --git a/Detectors/MUON/MCH/Workflow/src/ClusterFinderOriginalSpec.cxx b/Detectors/MUON/MCH/Workflow/src/ClusterFinderOriginalSpec.cxx index 8344d2837b814..e369e514b0f2e 100644 --- a/Detectors/MUON/MCH/Workflow/src/ClusterFinderOriginalSpec.cxx +++ b/Detectors/MUON/MCH/Workflow/src/ClusterFinderOriginalSpec.cxx @@ -35,13 +35,16 @@ #include "Framework/Logger.h" #include "CommonUtils/ConfigurableParam.h" -#include "DataFormatsMCH/ROFRecord.h" +#include "DataFormatsMCH/Cluster.h" #include "DataFormatsMCH/Digit.h" +#include "DataFormatsMCH/ROFRecord.h" #include "MCHBase/Error.h" #include "MCHBase/ErrorMap.h" #include "MCHBase/PreCluster.h" -#include "DataFormatsMCH/Cluster.h" +#include "MCHBase/TrackerParam.h" #include "MCHClustering/ClusterFinderOriginal.h" +#include "MCHClustering/ClusterizerParam.h" +#include "MCHROFFiltering/TrackableFilter.h" namespace o2 { @@ -94,11 +97,35 @@ class ClusterFinderOriginalTask auto& clusters = pc.outputs().make>(OutputRef{"clusters"}); auto& usedDigits = pc.outputs().make>(OutputRef{"clusterdigits"}); + // create the trackable ROF filtering if needed + ROFFilter trackable{}; + if (ClusterizerParam::Instance().onlyTrackable) { + const auto& trackerParam = TrackerParam::Instance(); + std::array requestStation{ + trackerParam.requestStation[0], + trackerParam.requestStation[1], + trackerParam.requestStation[2], + trackerParam.requestStation[3], + trackerParam.requestStation[4]}; + trackable = createTrackableFilter(preClusters, digits, requestStation, trackerParam.moreCandidates); + } + clusterROFs.reserve(preClusterROFs.size()); auto& errorMap = mClusterFinder.getErrorMap(); errorMap.clear(); + int nFilteredRofs = 0; + int nFilteredPreClusters = 0; for (const auto& preClusterROF : preClusterROFs) { + // filter out non-trackable ROFs if requested + if (ClusterizerParam::Instance().onlyTrackable && !trackable(preClusterROF)) { + // create an empty cluster ROF + clusterROFs.emplace_back(preClusterROF.getBCData(), clusters.size(), 0, preClusterROF.getBCWidth()); + continue; + } + ++nFilteredRofs; + nFilteredPreClusters += preClusterROF.getNEntries(); + // prepare to clusterize the current ROF auto clusterOffset = clusters.size(); mClusterFinder.reset(); @@ -137,8 +164,8 @@ class ClusterFinderOriginalTask }); mErrorMap.add(errorMap); - LOGP(info, "Found {:4d} clusters from {:4d} preclusters in {:2d} ROFs", - clusters.size(), preClusters.size(), preClusterROFs.size()); + LOGP(info, "Found {:4d} clusters from {:4d} preclusters (out of {:4d}) in {:2d} filtered ROFs (out of {:2d})", + clusters.size(), nFilteredPreClusters, preClusters.size(), nFilteredRofs, preClusterROFs.size()); } private: From 7b7c9fa9a21921d35ccc5b8066e6dd3d27fc9599 Mon Sep 17 00:00:00 2001 From: Anton Alkin Date: Tue, 28 Apr 2026 11:33:56 +0200 Subject: [PATCH 014/102] DPL Analysis: fix analysis CCDB fetcher still using origin --- .../CCDBSupport/src/AnalysisCCDBHelpers.cxx | 3 +- Framework/Core/include/Framework/ASoA.h | 68 +++++++++++-------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx index 3892f200645f6..c65ef2903db59 100644 --- a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx +++ b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx @@ -48,8 +48,7 @@ namespace void fillValidRoutes(CCDBFetcherHelper& helper, std::vector const& outputRoutes, std::unordered_map& bindings) { for (auto& route : outputRoutes) { - auto originMatcher = DataSpecUtils::asConcreteDataMatcher(route.matcher); - if (originMatcher.origin != header::DataOrigin{"ATIM"}) { + if (std::ranges::none_of(route.matcher.metadata, [](auto const& m) { return m.name.starts_with("ccdb:"); })) { continue; } auto specStr = DataSpecUtils::describe(route.matcher); diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 1628ec2039483..fc17fa139875c 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -3407,38 +3407,48 @@ consteval auto getIndexTargets() // // The columns of this table have to be CCDB_COLUMNS so that for each timestamp, we get a row // which points to the specified CCDB objectes described by those columns. -#define DECLARE_SOA_TIMESTAMPED_TABLE_FULL(_Name_, _Label_, _TimestampSource_, _TimestampColumn_, _Origin_, _Version_, _Desc_, ...) \ - O2HASH(_Desc_ "/" #_Version_); \ - template \ - using _Name_##TimestampFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ - using _Name_##Timestamp = _Name_##TimestampFrom>; \ - struct _Name_##TimestampMetadata : TableMetadata, __VA_ARGS__> { \ - template > \ - using base_table_t = _TimestampSource_##From; \ - template > \ - using extension_table_t = _Name_##TimestampFrom; \ - static constexpr const auto ccdb_urls = [](framework::pack) { \ - return std::array{Cs::query...}; \ - }(framework::pack<__VA_ARGS__>{}); \ - static constexpr const auto ccdb_bindings = [](framework::pack) { \ - return std::array{Cs::mLabel...}; \ - }(framework::pack<__VA_ARGS__>{}); \ - template > \ - static constexpr auto sources = _TimestampSource_##From::originals; \ - static constexpr auto timestamp_column_label = _TimestampColumn_::mLabel; \ - /*static constexpr auto timestampColumn = _TimestampColumn_;*/ \ - }; \ - template <> \ - struct MetadataTrait> { \ - using metadata = _Name_##TimestampMetadata; \ - }; \ - template \ - using _Name_##From = o2::soa::Join<_TimestampSource_, _Name_##TimestampFrom>; \ - using _Name_ = _Name_##From>; +#define DECLARE_SOA_TIMESTAMPED_TABLE_FULL(_Name_, _Label_, _TimestampSource_, _TimestampColumn_, _Version_, _Desc_, ...) \ + O2HASH(_Desc_ "/" #_Version_); \ + template \ + using _Name_##TimestampFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ + using _Name_##Timestamp = _Name_##TimestampFrom>; \ + struct _Name_##TimestampMetadata : TableMetadata, __VA_ARGS__> { \ + template > \ + using base_table_t = _TimestampSource_##From; \ + template > \ + using extension_table_t = _Name_##TimestampFrom; \ + static constexpr const auto ccdb_urls = [](framework::pack) { \ + return std::array{Cs::query...}; \ + }(framework::pack<__VA_ARGS__>{}); \ + static constexpr const auto ccdb_bindings = [](framework::pack) { \ + return std::array{Cs::mLabel...}; \ + }(framework::pack<__VA_ARGS__>{}); \ + static constexpr auto N = _TimestampSource_::originals.size(); \ + template > \ + static consteval auto generateSources() \ + { \ + return _TimestampSource_##From::originals; \ + } \ + static constexpr auto timestamp_column_label = _TimestampColumn_::mLabel; \ + /*static constexpr auto timestampColumn = _TimestampColumn_;*/ \ + }; \ + template <> \ + struct MetadataTrait> { \ + using metadata = _Name_##TimestampMetadata; \ + }; \ + template \ + using _Name_##From = o2::soa::Join<_TimestampSource_, _Name_##TimestampFrom>; \ + using _Name_ = _Name_##From>; #define DECLARE_SOA_TIMESTAMPED_TABLE(_Name_, _TimestampSource_, _TimestampColumn_, _Version_, _Desc_, ...) \ O2HASH(#_Name_ "Timestamped"); \ - DECLARE_SOA_TIMESTAMPED_TABLE_FULL(_Name_, #_Name_ "Timestamped", _TimestampSource_, _TimestampColumn_, "AOD", _Version_, _Desc_, __VA_ARGS__) + DECLARE_SOA_TIMESTAMPED_TABLE_FULL(_Name_, #_Name_ "Timestamped", _TimestampSource_, _TimestampColumn_, _Version_, _Desc_, __VA_ARGS__) namespace o2::soa { From 0528e00de1ddddffff010224362bbd494aae7d6d Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Wed, 29 Apr 2026 15:41:49 +0200 Subject: [PATCH 015/102] ITS: fix rare valid lookup at the edge of acceptance (#15327) * ITS: fix rare valid lookup at the edge of acceptance Technically, bin (0,0,0,0) is a valid result for the phi-z cluster query. Note though, that this if at all a super rare case and in local tests this did only showed up in the number of tracklets. So more a consistency fix than anything. Signed-off-by: Felix Schlepper * ITS: speedup line selection Signed-off-by: Felix Schlepper * ITS: improve logging Signed-off-by: Felix Schlepper * ITS: cleanup tracklet class Signed-off-by: Felix Schlepper * ITS: let staggering macro also run over non-staggered data Signed-off-by: Felix Schlepper * ITS: fix time assignment Signed-off-by: Felix Schlepper --------- Signed-off-by: Felix Schlepper --- .../ITSMFT/ITS/macros/test/CheckStaggering.C | 18 +++-- .../ITS/tracking/GPU/cuda/TrackingKernels.cu | 20 +----- .../ITS/tracking/include/ITStracking/Cell.h | 11 ++- .../tracking/include/ITStracking/Constants.h | 17 +++-- .../include/ITStracking/Definitions.h | 15 +++- .../include/ITStracking/IndexTableUtils.h | 2 +- .../tracking/include/ITStracking/MathUtils.h | 6 ++ .../tracking/include/ITStracking/TimeFrame.h | 6 +- .../tracking/include/ITStracking/Tracker.h | 17 +++-- .../tracking/include/ITStracking/Tracklet.h | 69 ++++++------------ .../tracking/include/ITStracking/Vertexer.h | 26 ++++--- .../include/ITStracking/VertexerTraits.h | 34 --------- Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx | 33 +++++++-- .../ITSMFT/ITS/tracking/src/TrackerTraits.cxx | 49 ++++++------- .../ITS/tracking/src/TrackingInterface.cxx | 1 + .../ITSMFT/ITS/tracking/src/Vertexer.cxx | 66 ++++++++++++----- .../ITS/tracking/src/VertexerTraits.cxx | 66 +++++++++-------- .../ITS/tracking/test/testROFLookupTables.cxx | 72 +++++++++++++++++++ .../ITSMFT/ITS/workflow/src/TrackerSpec.cxx | 7 +- 19 files changed, 313 insertions(+), 222 deletions(-) diff --git a/Detectors/ITSMFT/ITS/macros/test/CheckStaggering.C b/Detectors/ITSMFT/ITS/macros/test/CheckStaggering.C index e3a79779a5fb1..11ebcbbb1b1f1 100644 --- a/Detectors/ITSMFT/ITS/macros/test/CheckStaggering.C +++ b/Detectors/ITSMFT/ITS/macros/test/CheckStaggering.C @@ -80,9 +80,9 @@ void CheckStaggering(int runNumber, int max = -1, const std::string& dir = "") auto& ccdbmgr = o2::ccdb::BasicCCDBManager::instance(); ccdbmgr.setURL("https://alice-ccdb.cern.ch"); auto runDuration = ccdbmgr.getRunDuration(runNumber); - auto tRun = runDuration.first + (runDuration.second - runDuration.first) / 2; // time stamp for the middle of the run duration + auto tRun = runDuration.first + ((runDuration.second - runDuration.first) / 2); // time stamp for the middle of the run duration ccdbmgr.setTimestamp(tRun); - printf("Run %d has TS %lld", runNumber, tRun); + printf("Run %d has TS %ld", runNumber, tRun); auto geoAligned = ccdbmgr.get("GLO/Config/GeometryAligned"); auto magField = ccdbmgr.get("GLO/Config/GRPMagField"); auto grpLHC = ccdbmgr.get("GLO/Config/GRPLHCIF"); @@ -134,7 +134,7 @@ void CheckStaggering(int runNumber, int max = -1, const std::string& dir = "") auto hVtxZ = new TH1F("hVtxZ", "seeding vertices Z", 200, -16, 16); auto hVtxNCont = new TH1F("hVtxNCont", "seeding vertices contributors", 100, 0, 100); auto hVtxZNCont = new TProfile("hVtxZNCont", "seeding vertices z-contributors", 200, -16, 16); - auto hVtxCls = new TProfile("hVtxCls", ";Cls/TF;Cls/Vtx", 400, 20000, 60000); + auto hVtxCls = new TProfile("hVtxCls", ";Cls/TF;Cls/Vtx", 2000, 600000, 900000); auto hVtxTS = new TH1D("hVtxTS", "vtx time t0;t0 (BC)", o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches); const float minVtxWeight{5}; @@ -183,8 +183,12 @@ void CheckStaggering(int runNumber, int max = -1, const std::string& dir = "") tTrks->SetBranchAddress("ITSTrack", &trkArrPtr); tTrks->SetBranchAddress("Vertices", &vtxArrPtr); - for (int i{0}; i < 7; ++i) { - tCls->SetBranchAddress(Form("ITSClusterComp_%d", i), &clsArr[i]); + if (tCls->GetBranchStatus("ITSClusterComp")) { + tCls->SetBranchAddress("ITSClusterComp", &clsArr[0]); + } else { + for (int i{0}; i < 7; ++i) { + tCls->SetBranchAddress(Form("ITSClusterComp_%d", i), &clsArr[i]); + } } for (int iTF{0}; tTrks->LoadTree(iTF) >= 0; ++iTF) { @@ -193,7 +197,9 @@ void CheckStaggering(int runNumber, int max = -1, const std::string& dir = "") size_t ncls = 0; for (int i{0}; i < 7; ++i) { - ncls += clsArr[i]->size(); + if (clsArr[i]) { + ncls += clsArr[i]->size(); + } } // for each TF built pool of positive and negaitve tracks diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu index 4b12583d99c00..49b8f19d68ea6 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu @@ -47,20 +47,6 @@ namespace o2::its namespace gpu { -struct sort_tracklets { - GPUhd() bool operator()(const Tracklet& a, const Tracklet& b) - { - if (a.firstClusterIndex != b.firstClusterIndex) { - return a.firstClusterIndex < b.firstClusterIndex; - } - return a.secondClusterIndex < b.secondClusterIndex; - } -}; - -struct equal_tracklets { - GPUhd() bool operator()(const Tracklet& a, const Tracklet& b) { return a.firstClusterIndex == b.firstClusterIndex && a.secondClusterIndex == b.secondClusterIndex; } -}; - template struct sort_by_second { GPUhd() bool operator()(const gpuPair& a, const gpuPair& b) const { return a.second < b.second; } @@ -408,7 +394,7 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( const float sqInverseDeltaZ0{1.f / (math_utils::Sq(currentCluster.zCoordinate - primaryVertex.getZ()) + constants::Tolerance)}; /// protecting from overflows adding the detector resolution const float sigmaZ{o2::gpu::CAMath::Sqrt(math_utils::Sq(resolution) * math_utils::Sq(tanLambda) * ((math_utils::Sq(inverseR0) + sqInverseDeltaZ0) * math_utils::Sq(meanDeltaR) + 1.f) + math_utils::Sq(meanDeltaR * MSAngle))}; const int4 selectedBinsRect{o2::its::getBinsRect(currentCluster, layerIndex + 1, zAtRmin, zAtRmax, sigmaZ * NSigmaCut, phiCut, *utils)}; - if (selectedBinsRect.x == 0 && selectedBinsRect.y == 0 && selectedBinsRect.z == 0 && selectedBinsRect.w == 0) { + if (selectedBinsRect.x < 0) { continue; } int phiBinsNum{selectedBinsRect.w - selectedBinsRect.y + 1}; @@ -687,8 +673,8 @@ void computeTrackletsInROFsHandler(const IndexTableUtils* utils, mulScatAng[layer]); thrust::device_ptr tracklets_ptr(spanTracklets[layer]); auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[layer].get()); - thrust::sort(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[layer], gpu::sort_tracklets()); - auto unique_end = thrust::unique(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[layer], gpu::equal_tracklets()); + thrust::sort(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[layer]); + auto unique_end = thrust::unique(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[layer]); nTracklets[layer] = unique_end - tracklets_ptr; if (layer) { GPUChkErrS(cudaMemsetAsync(trackletsLUTsHost[layer], 0, (nClusters[layer] + 1) * sizeof(int), streams[layer].get())); diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h index dce66bdf99415..c7718ee666311 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h @@ -56,18 +56,17 @@ class SeedBase : public o2::track::TrackParCovF GPUhd() const auto& clustersRaw() const { return mClusters; } private: - float mChi2 = -999.f; - int mLevel = constants::UnusedIndex; + float mChi2{constants::UnsetValue}; + int mLevel{constants::UnusedIndex}; std::array mTracklets = constants::helpers::initArray(); std::array mClusters = constants::helpers::initArray(); TimeEstBC mTime; }; /// CellSeed: connections of three clusters -class CellSeed final : public SeedBase<3> +class CellSeed final : public SeedBase { - static constexpr int NStoredClusters = 3; - using Base = SeedBase; + using Base = SeedBase; public: GPUhdDefault() CellSeed() = default; @@ -98,7 +97,7 @@ class CellSeed final : public SeedBase<3> GPUhd() int getCluster(int layer) const { const int rel = layer - getInnerLayer(); - return (rel >= 0 && rel < NStoredClusters) ? this->clustersRaw()[rel] : constants::UnusedIndex; + return (rel >= 0 && rel < constants::ClustersPerCell) ? this->clustersRaw()[rel] : constants::UnusedIndex; } }; diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Constants.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Constants.h index d48e8fb7c5856..34fa819b178eb 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Constants.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Constants.h @@ -19,9 +19,6 @@ #include #include -#include "GPUCommonDef.h" -#include "GPUCommonDefAPI.h" - namespace o2::its::constants { @@ -30,12 +27,14 @@ constexpr float MB = KB * KB; constexpr float GB = MB * KB; constexpr bool DoTimeBenchmarks = true; constexpr bool SaveTimeBenchmarks = false; -constexpr float Tolerance = 1e-12; // numerical tolerance -constexpr int ClustersPerCell = 3; -constexpr int UnusedIndex = -1; -constexpr float Radl = 9.36f; // Radiation length of Si [cm] -constexpr float Rho = 2.33f; // Density of Si [g/cm^3] -constexpr int MaxIter = 4; // Max. supported iterations +constexpr float Tolerance = 1e-12; // numerical tolerance +constexpr int ClustersPerCell = 3; // number of clusters for a cell +constexpr int UnusedIndex = -1; // global unused flag +constexpr float UnsetValue = -999.f; // global unset value +constexpr float Radl = 9.36f; // Radiation length of Si [cm] +constexpr float Rho = 2.33f; // Density of Si [g/cm^3] +constexpr int MaxIter = 4; // Max. supported iterations +constexpr int MaxSelectedTrackletsPerCluster = 100; // vertexer: max lines per cluster namespace helpers { diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Definitions.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Definitions.h index 8dadf826aa80a..d79ea8c8bece8 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Definitions.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Definitions.h @@ -50,6 +50,19 @@ struct LogLogThrottler { return false; } }; + +struct TimingStats { + std::uint64_t calls = 0; + double totalTimeMs = 0.; + + void add(double timeMs) + { + ++calls; + totalTimeMs += timeMs; + } + double averageTimeMs() const { return calls ? totalTimeMs / static_cast(calls) : 0.; } +}; + } // namespace o2::its -#endif \ No newline at end of file +#endif diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/IndexTableUtils.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/IndexTableUtils.h index e2487208e9453..4e8d5bcfea42a 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/IndexTableUtils.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/IndexTableUtils.h @@ -124,7 +124,7 @@ GPUhdi() int4 getBinsRect(const Cluster& currentCluster, const int layerIndex, if (zRangeMax < -utils.getLayerZ(layerIndex) || zRangeMin > utils.getLayerZ(layerIndex) || zRangeMin > zRangeMax) { - return int4{0, 0, 0, 0}; + return int4{-1, -1, -1, -1}; } return int4{o2::gpu::GPUCommonMath::Max(0, utils.getZBinIndex(layerIndex, zRangeMin)), diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h index ab3c7d5d29873..d276e27638dbd 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h @@ -89,6 +89,12 @@ GPUhdi() float smallestAngleDifference(float a, float b) return o2::gpu::CAMath::Remainderf(b - a, o2::constants::math::TwoPI); } +GPUhdi() bool isPhiDifferenceBelow(const float phiA, const float phiB, const float phiCut) +{ + const float deltaPhi = o2::gpu::CAMath::Abs(phiA - phiB); + return deltaPhi < phiCut || deltaPhi > o2::constants::math::TwoPI - phiCut; +} + GPUhdi() constexpr float Sq(float v) { return v * v; diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h index f2506694755c5..300abb2a3b10d 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h @@ -26,9 +26,7 @@ #include "ITStracking/Cell.h" #include "ITStracking/Cluster.h" #include "ITStracking/Configuration.h" -#include "ITStracking/Constants.h" #include "ITStracking/ClusterLines.h" -#include "ITStracking/Definitions.h" #include "ITStracking/Tracklet.h" #include "ITStracking/IndexTableUtils.h" #include "ITStracking/ExternalAllocator.h" @@ -103,7 +101,7 @@ struct TimeFrame { void setBeamPosition(const float x, const float y, const float s2, const float base = 50.f, const float systematic = 0.f) { isBeamPositionOverridden = true; - resetBeamXY(x, y, s2 / o2::gpu::CAMath::Sqrt(base * base + systematic)); + resetBeamXY(x, y, s2 / o2::gpu::CAMath::Sqrt((base * base) + systematic)); } float getBeamX() const { return mBeamPos[0]; } @@ -249,7 +247,7 @@ struct TimeFrame { // Propagator const o2::base::PropagatorImpl* getDevicePropagator() const { return mPropagatorDevice; } - virtual void setDevicePropagator(const o2::base::PropagatorImpl*) {}; + virtual void setDevicePropagator(const o2::base::PropagatorImpl* /*unused*/) {}; template void addClusterToLayer(int layer, T&&... args); diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracker.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracker.h index ad8ea5b3b56af..240b0eb1e2f63 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracker.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracker.h @@ -25,10 +25,12 @@ #include #include #include +#include #include #include "ITStracking/Configuration.h" +#include "ITStracking/Definitions.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/TrackerTraits.h" #include "ITStracking/BoundedAllocator.h" @@ -91,16 +93,18 @@ class Tracker double mTotalTime{0}; std::shared_ptr mMemoryPool; - enum State { + enum Steps { TFInit = 0, Trackleting, Celling, Neighbouring, Roading, - NStates, + NSteps, }; - State mCurState{TFInit}; - static constexpr std::array StateNames{"TimeFrame initialisation", "Tracklet finding", "Cell finding", "Neighbour finding", "Road finding"}; + Steps mCurStep{TFInit}; + static constexpr std::array StateNames{"TimeFrame initialisation", "Tracklet finding", "Cell finding", "Neighbour finding", "Road finding"}; + std::vector> mTimingStats; + void addTimingStatCurStep(int iteration, double timeMs); }; template @@ -125,7 +129,7 @@ float Tracker::evaluateTask(void (Tracker::*task)(T...), std:: } logger(sstream.str()); - if (mTrkParams[0].SaveTimeBenchmarks) { + if (mTrkParams[iteration].SaveTimeBenchmarks) { std::string taskNameStr(taskName); std::transform(taskNameStr.begin(), taskNameStr.end(), taskNameStr.begin(), [](unsigned char c) { return std::tolower(c); }); @@ -133,6 +137,7 @@ float Tracker::evaluateTask(void (Tracker::*task)(T...), std:: if (std::ofstream file{"its_time_benchmarks.txt", std::ios::app}) { file << "trk:" << iteration << '\t' << taskNameStr << '\t' << diff << '\n'; } + addTimingStatCurStep(iteration, diff); } } else { @@ -140,7 +145,7 @@ float Tracker::evaluateTask(void (Tracker::*task)(T...), std:: } if (mTrkParams[iteration].PrintMemory) { - LOGP(info, "iter:{}:{}: {}", iteration, StateNames[mCurState], mMemoryPool->asString()); + LOGP(info, "iter:{}:{}: {}", iteration, StateNames[mCurStep], mMemoryPool->asString()); } return diff; diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracklet.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracklet.h index d93a5e1c7d70e..829fe9fa984e4 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracklet.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracklet.h @@ -24,27 +24,35 @@ #include "GPUCommonDef.h" #include "GPUCommonLogger.h" -#ifndef GPUCA_GPUCODE_DEVICE -#ifndef GPU_NO_FMT -#include -#include -#endif -#endif - namespace o2::its { +// tracklets are entirely determined by their two cluster idx struct Tracklet final { GPUhdDefault() Tracklet() = default; - GPUhdi() Tracklet(const int, const int, const Cluster&, const Cluster&, const TimeEstBC& t); - GPUhdi() Tracklet(const int, const int, float tanL, float phi, const TimeEstBC& t); - GPUhdDefault() bool operator==(const Tracklet&) const = default; - GPUhdi() unsigned char isEmpty() const + GPUhdi() Tracklet(const int firstClusterOrderingIndex, const int secondClusterOrderingIndex, + const Cluster& firstCluster, const Cluster& secondCluster, const TimeEstBC& t) + : firstClusterIndex(firstClusterOrderingIndex), + secondClusterIndex(secondClusterOrderingIndex), + tanLambda((firstCluster.zCoordinate - secondCluster.zCoordinate) / (firstCluster.radius - secondCluster.radius)), + phi(o2::gpu::GPUCommonMath::ATan2(firstCluster.yCoordinate - secondCluster.yCoordinate, firstCluster.xCoordinate - secondCluster.xCoordinate)), + mTime(t) {} + + GPUhdi() Tracklet(const int idx0, const int idx1, float tanL, float phi, const TimeEstBC& t) + : firstClusterIndex(idx0), + secondClusterIndex(idx1), + tanLambda(tanL), + phi(phi), + mTime(t) {} + GPUhdi() bool operator<(const Tracklet& o) const noexcept + { + return (firstClusterIndex != o.firstClusterIndex) ? firstClusterIndex < o.firstClusterIndex : secondClusterIndex < o.secondClusterIndex; + } + GPUhdi() bool operator==(const Tracklet& o) const noexcept { - return firstClusterIndex < 0 || secondClusterIndex < 0; + return firstClusterIndex == o.firstClusterIndex && secondClusterIndex == o.secondClusterIndex; } GPUhdi() bool isCompatible(const Tracklet& o) const { return mTime.isCompatible(o.mTime); } - GPUhdi() unsigned char operator<(const Tracklet&) const; GPUhd() void print() const { LOGP(info, "TRKLT: fClIdx:{} sClIdx:{} ts:{}+/-{} TgL={} Phi={}", firstClusterIndex, secondClusterIndex, mTime.getTimeStamp(), mTime.getTimeStampError(), tanLambda, phi); @@ -54,44 +62,13 @@ struct Tracklet final { int firstClusterIndex{constants::UnusedIndex}; int secondClusterIndex{constants::UnusedIndex}; - float tanLambda{-999}; - float phi{-999}; + float tanLambda{constants::UnsetValue}; + float phi{constants::UnsetValue}; TimeEstBC mTime; ClassDefNV(Tracklet, 1); }; -GPUhdi() Tracklet::Tracklet(const int firstClusterOrderingIndex, const int secondClusterOrderingIndex, - const Cluster& firstCluster, const Cluster& secondCluster, const TimeEstBC& t) - : firstClusterIndex(firstClusterOrderingIndex), - secondClusterIndex(secondClusterOrderingIndex), - tanLambda((firstCluster.zCoordinate - secondCluster.zCoordinate) / - (firstCluster.radius - secondCluster.radius)), - phi(o2::gpu::GPUCommonMath::ATan2(firstCluster.yCoordinate - secondCluster.yCoordinate, - firstCluster.xCoordinate - secondCluster.xCoordinate)), - mTime(t) -{ - // Nothing to do -} - -GPUhdi() Tracklet::Tracklet(const int idx0, const int idx1, float tanL, float phi, const TimeEstBC& t) - : firstClusterIndex(idx0), - secondClusterIndex(idx1), - tanLambda(tanL), - phi(phi), - mTime(t) -{ - // Nothing to do -} - -GPUhdi() unsigned char Tracklet::operator<(const Tracklet& t) const -{ - if (isEmpty()) { - return false; - } - return true; -} - } // namespace o2::its #endif /* TRACKINGITS_INCLUDE_TRACKLET_H_ */ diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Vertexer.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Vertexer.h index f1cf081473264..eff91e820c56d 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Vertexer.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Vertexer.h @@ -22,11 +22,11 @@ #include #include #include +#include #include #include "ITStracking/Constants.h" -#include "ITStracking/Definitions.h" #include "ITStracking/Configuration.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/VertexerTraits.h" @@ -56,6 +56,7 @@ class Vertexer float clustersToVertices(LogFunc = [](const std::string& s) { std::cout << s << '\n'; }); void filterMCTracklets(); + void printSummary() const; template void findTracklets(T&&... args) @@ -90,9 +91,9 @@ class Vertexer float evaluateTask(void (Vertexer::*task)(T...), std::string_view taskName, int iteration, LogFunc& logger, T&&... args); void printEpilog(LogFunc& logger, - const unsigned int trackletN01, const unsigned int trackletN12, - const unsigned selectedN, const unsigned int vertexN, const unsigned int totalVertexN, - const float trackletT, const float selecT, const float vertexT); + unsigned int trackletN01, unsigned int trackletN12, + unsigned selectedN, unsigned int vertexN, unsigned int totalVertexN, + float initT, float trackletT, float selecT, float vertexT); void setNThreads(int n, std::shared_ptr& arena) { mTraits->setNThreads(n, arena); } @@ -105,16 +106,18 @@ class Vertexer std::vector mVertParams; std::shared_ptr mMemoryPool; - enum State { + enum Steps { Init = 0, Trackleting, - Validating, + Selection, Finding, TruthSeeding, - NStates, + NSteps, }; - State mCurState{Init}; - static constexpr std::array StateNames{"Initialisation", "Tracklet finding", "Tracklet validation", "Vertex finding", "Truth seeding"}; + Steps mCurStep{Init}; + static constexpr std::array StateNames{"Initialisation", "Tracklet finding", "Tracklet selection", "Vertex finding", "Truth seeding"}; + std::vector> mTimingStats; + void addTimingStatCurStep(int iteration, double timeMs); }; template @@ -139,7 +142,7 @@ float Vertexer::evaluateTask(void (Vertexer::*task)(T...), std } logger(sstream.str()); - if (mVertParams[0].SaveTimeBenchmarks) { + if (mVertParams[iteration].SaveTimeBenchmarks) { std::string taskNameStr(taskName); std::transform(taskNameStr.begin(), taskNameStr.end(), taskNameStr.begin(), [](unsigned char c) { return std::tolower(c); }); @@ -147,13 +150,14 @@ float Vertexer::evaluateTask(void (Vertexer::*task)(T...), std if (std::ofstream file{"its_time_benchmarks.txt", std::ios::app}) { file << "vtx:" << iteration << '\t' << taskNameStr << '\t' << diff << '\n'; } + addTimingStatCurStep(iteration, diff); } } else { (this->*task)(std::forward(args)...); } if (mVertParams[iteration].PrintMemory) { - LOGP(info, "iter:{}:{}: {}", iteration, StateNames[mCurState], mMemoryPool->asString()); + LOGP(info, "iter:{}:{}: {}", iteration, StateNames[mCurStep], mMemoryPool->asString()); } return diff; diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h index 1adb09551e326..3230737a0f87c 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h @@ -53,12 +53,6 @@ class VertexerTraits VertexerTraits() = default; virtual ~VertexerTraits() = default; - GPUhdi() static consteval int4 getEmptyBinsRect() - { - return int4{0, 0, 0, 0}; - } - GPUhd() const int4 getBinsRect(const Cluster&, const int, const float, float maxdeltaz, float maxdeltaphi); - GPUhd() static const int4 getBinsRect(const Cluster&, const int, const float, float maxdeltaz, float maxdeltaphi, const IndexTableUtilsN&); GPUhd() static const int2 getPhiBins(float phi, float deltaPhi, const IndexTableUtilsN&); GPUhd() const int2 getPhiBins(float phi, float deltaPhi) { return getPhiBins(phi, deltaPhi, mIndexTableUtils); } @@ -134,34 +128,6 @@ GPUhdi() const int2 VertexerTraits::getPhiBins(float phi, float dPhi, c utils.getPhiBinIndex(math_utils::getNormalizedPhi(phi + dPhi))}; } -template -GPUhdi() const int4 VertexerTraits::getBinsRect(const Cluster& currentCluster, const int layerIndex, - const float directionZIntersection, float maxdeltaz, float maxdeltaphi, - const IndexTableUtilsN& utils) -{ - const float zRangeMin = directionZIntersection - 2 * maxdeltaz; - const float phiRangeMin = currentCluster.phi - maxdeltaphi; - const float zRangeMax = directionZIntersection + 2 * maxdeltaz; - const float phiRangeMax = currentCluster.phi + maxdeltaphi; - - if (zRangeMax < -utils.getLayerZ(layerIndex + 1) || - zRangeMin > utils.getLayerZ(layerIndex + 1) || zRangeMin > zRangeMax) { - return getEmptyBinsRect(); - } - - return int4{o2::gpu::GPUCommonMath::Max(0, utils.getZBinIndex(layerIndex + 1, zRangeMin)), - utils.getPhiBinIndex(math_utils::getNormalizedPhi(phiRangeMin)), - o2::gpu::GPUCommonMath::Min(utils.getNzBins() - 1, utils.getZBinIndex(layerIndex + 1, zRangeMax)), - utils.getPhiBinIndex(math_utils::getNormalizedPhi(phiRangeMax))}; -} - -template -GPUhdi() const int4 VertexerTraits::getBinsRect(const Cluster& currentCluster, const int layerIndex, - const float directionZIntersection, float maxdeltaz, float maxdeltaphi) -{ - return VertexerTraits::getBinsRect(currentCluster, layerIndex, directionZIntersection, maxdeltaz, maxdeltaphi, mIndexTableUtils); -} - } // namespace its } // namespace o2 #endif diff --git a/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx b/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx index 3e91788c9881c..382f2314b2e6a 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx @@ -49,7 +49,7 @@ float Tracker::clustersToTracks(const LogFunc& logger, const LogFunc& e int iteration{0}, iVertex{0}; auto handleException = [&](const auto& err) { LOGP(error, "Too much memory in {} in iteration {} iVtx={}: {:.2f} GB. Current limit is {:.2f} GB, check the detector status and/or the selections.", - StateNames[mCurState], iteration, iVertex, + StateNames[mCurStep], iteration, iVertex, (double)mTimeFrame->getArtefactsMemory() / GB, (double)mTrkParams[iteration].MaxMemory / GB); if (typeid(err) != typeid(std::bad_alloc)) { // only print if the exceptions is different from what is expected @@ -76,16 +76,16 @@ float Tracker::clustersToTracks(const LogFunc& logger, const LogFunc& e int nTracks{-static_cast(mTimeFrame->getNumberOfTracks())}; iVertex = std::min(maxNvertices, 0); logger(std::format("==== ITS {} Tracking iteration {} summary ====", mTraits->getName(), iteration)); - total += timeFrame = evaluateTask(&Tracker::initialiseTimeFrame, StateNames[mCurState = TFInit], iteration, evalLog, iteration); + total += timeFrame = evaluateTask(&Tracker::initialiseTimeFrame, StateNames[mCurStep = TFInit], iteration, evalLog, iteration); logger(std::format(" - TimeFrame initialisation completed in {:.2f} ms", timeFrame)); do { - timeTracklets += evaluateTask(&Tracker::computeTracklets, StateNames[mCurState = Trackleting], iteration, evalLog, iteration, iVertex); + timeTracklets += evaluateTask(&Tracker::computeTracklets, StateNames[mCurStep = Trackleting], iteration, evalLog, iteration, iVertex); nTracklets += mTraits->getTFNumberOfTracklets(); - timeCells += evaluateTask(&Tracker::computeCells, StateNames[mCurState = Celling], iteration, evalLog, iteration); + timeCells += evaluateTask(&Tracker::computeCells, StateNames[mCurStep = Celling], iteration, evalLog, iteration); nCells += mTraits->getTFNumberOfCells(); - timeNeighbours += evaluateTask(&Tracker::findCellsNeighbours, StateNames[mCurState = Neighbouring], iteration, evalLog, iteration); + timeNeighbours += evaluateTask(&Tracker::findCellsNeighbours, StateNames[mCurStep = Neighbouring], iteration, evalLog, iteration); nNeighbours += mTimeFrame->getNumberOfNeighbours(); - timeRoads += evaluateTask(&Tracker::findRoads, StateNames[mCurState = Roading], iteration, evalLog, iteration); + timeRoads += evaluateTask(&Tracker::findRoads, StateNames[mCurStep = Roading], iteration, evalLog, iteration); } while (++iVertex < maxNvertices); logger(std::format(" - Tracklet finding: {} tracklets found in {:.2f} ms", nTracklets, timeTracklets)); logger(std::format(" - Cell finding: {} cells found in {:.2f} ms", nCells, timeCells)); @@ -229,12 +229,33 @@ void Tracker::adoptTimeFrame(TimeFrame& tf) mTraits->adoptTimeFrame(&tf); } +template +void Tracker::addTimingStatCurStep(int iteration, double timeMs) +{ + if (iteration < 0) { + return; + } + if (mTimingStats.size() < (iteration + 1)) { + mTimingStats.resize(iteration + 1); + } + mTimingStats[iteration][mCurStep].add(timeMs); +} + template void Tracker::printSummary() const { auto avgTF = mTotalTime * 1.e-3 / ((mTimeFrameCounter > 0) ? (double)mTimeFrameCounter : -1.0); auto avgTFwithDropped = mTotalTime * 1.e-3 / (((mTimeFrameCounter + mNumberOfDroppedTFs) > 0) ? (double)(mTimeFrameCounter + mNumberOfDroppedTFs) : -1.0); LOGP(info, "Tracker summary: Processed {} TFs (dropped {}) in TOT={:.2f} s, AVG/TF={:.2f} ({:.2f}) s", mTimeFrameCounter, mNumberOfDroppedTFs, mTotalTime * 1.e-3, avgTF, avgTFwithDropped); + for (size_t iteration = 0; iteration < mTimingStats.size(); ++iteration) { + for (size_t state = 0; state < NSteps; ++state) { + const auto& stats = mTimingStats[iteration][state]; + if (!stats.calls) { + continue; + } + LOGP(info, " - iter {} {}: calls={} total={:.2f} ms avg={:.2f} ms", iteration, StateNames[state], stats.calls, stats.totalTimeMs, stats.averageTimeMs()); + } + } } template class Tracker<7>; diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx index 69ccd8228ad88..dc2d6e8889973 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx @@ -114,7 +114,7 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer const auto bins = o2::its::getBinsRect(currentCluster, iLayer + 1, zAtRmin, zAtRmax, sigmaZ * mTrkParams[iteration].NSigmaCut, mTimeFrame->getPhiCut(iLayer), mTimeFrame->getIndexTableUtils()); - if (bins.x == 0 && bins.y == 0 && bins.z == 0 && bins.w == 0) { + if (bins.x < 0) { continue; } int phiBinsNum = bins.w - bins.y + 1; @@ -150,11 +150,10 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer if (mTimeFrame->isClusterUsed(iLayer + 1, nextCluster.clusterId)) { continue; } - const float deltaPhi = o2::gpu::CAMath::Abs(o2::math_utils::toPMPi(currentCluster.phi - nextCluster.phi)); const float deltaZ = o2::gpu::CAMath::Abs((tanLambda * (nextCluster.radius - currentCluster.radius)) + currentCluster.zCoordinate - nextCluster.zCoordinate); if (deltaZ / sigmaZ < mTrkParams[iteration].NSigmaCut && - ((deltaPhi < mTimeFrame->getPhiCut(iLayer) || o2::gpu::GPUCommonMath::Abs(deltaPhi - o2::constants::math::TwoPI) < mTimeFrame->getPhiCut(iLayer)))) { + math_utils::isPhiDifferenceBelow(currentCluster.phi, nextCluster.phi, mTimeFrame->getPhiCut(iLayer))) { const float phi{o2::gpu::CAMath::ATan2(currentCluster.yCoordinate - nextCluster.yCoordinate, currentCluster.xCoordinate - nextCluster.xCoordinate)}; const float tanL = (currentCluster.zCoordinate - nextCluster.zCoordinate) / (currentCluster.radius - nextCluster.radius); if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { @@ -207,19 +206,11 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer } tbb::parallel_for(0, mTrkParams[iteration].TrackletsPerRoad(), [&](const int iLayer) { - /// Sort tracklets + /// Sort tracklets & remove duplicates + // duplicates can exist simply since we evaluate per vertex auto& trkl{mTimeFrame->getTracklets()[iLayer]}; - std::sort(trkl.begin(), trkl.end(), [](const Tracklet& a, const Tracklet& b) -> bool { - if (a.firstClusterIndex != b.firstClusterIndex) { - return a.firstClusterIndex < b.firstClusterIndex; - } - return a.secondClusterIndex < b.secondClusterIndex; - }); - /// Remove duplicates - trkl.erase(std::unique(trkl.begin(), trkl.end(), [](const Tracklet& a, const Tracklet& b) -> bool { - return a.firstClusterIndex == b.firstClusterIndex && a.secondClusterIndex == b.secondClusterIndex; - }), - trkl.end()); + std::sort(trkl.begin(), trkl.end()); + trkl.erase(std::unique(trkl.begin(), trkl.end()), trkl.end()); trkl.shrink_to_fit(); if (iLayer > 0) { /// recalculate lut auto& lut{mTimeFrame->getTrackletsLookupTable()[iLayer - 1]}; @@ -810,30 +801,40 @@ void TrackerTraits::acceptTracks(int iteration, bounded_vectormarkUsedCluster(iLayer, track.getClusterIndex(iLayer)); int currentROF = mTimeFrame->getClusterROF(iLayer, track.getClusterIndex(iLayer)); - auto rofTS = mTimeFrame->getROFOverlapTableView().getLayer(iLayer).getROFTimeBounds(currentROF, true); + const auto nominalROFTS = mTimeFrame->getROFOverlapTableView().getLayer(iLayer).getROFTimeBounds(currentROF); + const auto expandedROFTS = mTimeFrame->getROFOverlapTableView().getLayer(iLayer).getROFTimeBounds(currentROF, true); if (firstCls) { firstCls = false; - ts = rofTS; + nominalTS = nominalROFTS; + expandedTS = expandedROFTS; } else { - if (!ts.isCompatible(rofTS)) { - LOGP(fatal, "TS {}+/-{} are incompatible with {}+/-{}, this should not happen!", rofTS.getTimeStamp(), rofTS.getTimeStampError(), ts.getTimeStamp(), ts.getTimeStampError()); + if (nominalCompatible) { + if (nominalTS.isCompatible(nominalROFTS)) { + nominalTS += nominalROFTS; + } else { + nominalCompatible = false; + } + } + if (!expandedTS.isCompatible(expandedROFTS)) { + LOGP(fatal, "TS {}+/-{} are incompatible with {}+/-{}, this should not happen!", expandedROFTS.getTimeStamp(), expandedROFTS.getTimeStampError(), expandedTS.getTimeStamp(), expandedTS.getTimeStampError()); } - ts += rofTS; + expandedTS += expandedROFTS; } } - track.getTimeStamp() = ts.makeSymmetrical(); + track.getTimeStamp() = (nominalCompatible ? nominalTS : expandedTS).makeSymmetrical(); + // this is a sanity clamp + // we cannot be worse than the clock so we clamp to this if (track.getTimeStamp().getTimeStampError() > smallestROFHalf) { track.getTimeStamp().setTimeStampError(smallestROFHalf); } - track.setUserField(0); track.getParamOut().setUserField(0); mTimeFrame->getTracks().emplace_back(track); diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx index d469fa4246ef5..a48f23c5eb8f1 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx @@ -460,6 +460,7 @@ void ITSTrackingInterface::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) void ITSTrackingInterface::printSummary() const { + mVertexer->printSummary(); mTracker->printSummary(); } diff --git a/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx b/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx index 556302cb2854f..2acbec7fe8bc0 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx @@ -15,11 +15,6 @@ #include "ITStracking/Vertexer.h" #include "ITStracking/BoundedAllocator.h" -#include "ITStracking/Cluster.h" - -#include "ITStracking/ClusterLines.h" -#include "ITStracking/Tracklet.h" -#include "ITStracking/IndexTableUtils.h" #include "ITStracking/VertexerTraits.h" #include "ITStracking/TrackingConfigParam.h" @@ -41,8 +36,9 @@ float Vertexer::clustersToVertices(LogFunc logger) LogFunc evalLog = [](const std::string&) {}; if (mTimeFrame->hasMCinformation() && mVertParams[0].useTruthSeeding) { - float t = evaluateTask(&Vertexer::addTruthSeeds, StateNames[mCurState = TruthSeeding], 0, evalLog); + float t = evaluateTask(&Vertexer::addTruthSeeds, StateNames[mCurStep = TruthSeeding], 0, evalLog); sortVertices(); + ++mTimeFrameCounter; return t; } @@ -50,7 +46,7 @@ float Vertexer::clustersToVertices(LogFunc logger) mTraits->updateVertexingParameters(mVertParams); auto handleException = [&](const auto& err) { - LOGP(error, "Encountered critical error in step {}, stopping further processing of this TF: {}", StateNames[mCurState], err.what()); + LOGP(error, "Encountered critical error in step {}, stopping further processing of this TF: {}", StateNames[mCurStep], err.what()); if (!mVertParams[0].DropTFUponFailure) { throw err; } else { @@ -59,6 +55,7 @@ float Vertexer::clustersToVertices(LogFunc logger) }; float timeTracklet{0.f}, timeSelection{0.f}, timeVertexing{0.f}, timeInit{0.f}; + bool completed = false; try { for (int iteration = 0; iteration < (int)mVertParams.size(); ++iteration) { mMemoryPool->setMaxMemory(mVertParams[iteration].MaxMemory); @@ -66,15 +63,15 @@ float Vertexer::clustersToVertices(LogFunc logger) logger(fmt::format("=== ITS {} Seeding vertexer iteration {} summary:", mTraits->getName(), iteration)); trkPars.PhiBins = mTraits->getVertexingParameters()[0].PhiBins; trkPars.ZBins = mTraits->getVertexingParameters()[0].ZBins; - auto timeInitIteration = evaluateTask(&Vertexer::initialiseVertexer, StateNames[mCurState = Init], iteration, evalLog, trkPars, iteration); - auto timeTrackletIteration = evaluateTask(&Vertexer::findTracklets, StateNames[mCurState = Trackleting], iteration, evalLog, iteration); + auto timeInitIteration = evaluateTask(&Vertexer::initialiseVertexer, StateNames[mCurStep = Init], iteration, evalLog, trkPars, iteration); + auto timeTrackletIteration = evaluateTask(&Vertexer::findTracklets, StateNames[mCurStep = Trackleting], iteration, evalLog, iteration); nTracklets01 = mTimeFrame->getTotalTrackletsTF(0); nTracklets12 = mTimeFrame->getTotalTrackletsTF(1); - auto timeSelectionIteration = evaluateTask(&Vertexer::validateTracklets, StateNames[mCurState = Validating], iteration, evalLog, iteration); + auto timeSelectionIteration = evaluateTask(&Vertexer::validateTracklets, StateNames[mCurStep = Selection], iteration, evalLog, iteration); const auto nVerticesBefore = mTimeFrame->getPrimaryVertices().size(); - auto timeVertexingIteration = evaluateTask(&Vertexer::findVertices, StateNames[mCurState = Finding], iteration, evalLog, iteration); + auto timeVertexingIteration = evaluateTask(&Vertexer::findVertices, StateNames[mCurStep = Finding], iteration, evalLog, iteration); const auto nVerticesAfter = mTimeFrame->getPrimaryVertices().size(); - printEpilog(logger, nTracklets01, nTracklets12, mTimeFrame->getNLinesTotal(), nVerticesAfter - nVerticesBefore, nVerticesAfter, timeTrackletIteration, timeSelectionIteration, timeVertexingIteration); + printEpilog(logger, nTracklets01, nTracklets12, mTimeFrame->getNLinesTotal(), nVerticesAfter - nVerticesBefore, nVerticesAfter, timeInitIteration, timeTrackletIteration, timeSelectionIteration, timeVertexingIteration); timeInit += timeInitIteration; timeTracklet += timeTrackletIteration; timeSelection += timeSelectionIteration; @@ -84,6 +81,7 @@ float Vertexer::clustersToVertices(LogFunc logger) sortVertices(); mTimeFrame->updateROFVertexLookupTable(); } + completed = true; } catch (const BoundedMemoryResource::MemoryLimitExceeded& err) { handleException(err); } catch (const std::bad_alloc& err) { @@ -92,6 +90,10 @@ float Vertexer::clustersToVertices(LogFunc logger) LOGP(fatal, "Uncaught exception!"); } + if (completed) { + ++mTimeFrameCounter; + } + return timeInit + timeTracklet + timeSelection + timeVertexing; } @@ -135,15 +137,43 @@ void Vertexer::adoptTimeFrame(TimeFrameN& tf) mTraits->adoptTimeFrame(&tf); } +template +void Vertexer::addTimingStatCurStep(int iteration, double timeMs) +{ + if (iteration < 0) { + return; + } + if (mTimingStats.size() < (iteration + 1)) { + mTimingStats.resize(iteration + 1); + } + mTimingStats[iteration][mCurStep].add(timeMs); +} + +template +void Vertexer::printSummary() const +{ + LOGP(info, "Vertexer summary: Processed {} TFs", mTimeFrameCounter); + for (size_t iteration = 0; iteration < mTimingStats.size(); ++iteration) { + for (size_t state = 0; state < NSteps; ++state) { + const auto& stats = mTimingStats[iteration][state]; + if (!stats.calls) { + continue; + } + LOGP(info, " - iter {} {}: calls={} total={:.2f} ms avg={:.2f} ms", iteration, StateNames[state], stats.calls, stats.totalTimeMs, stats.averageTimeMs()); + } + } +} + template void Vertexer::printEpilog(LogFunc& logger, - const unsigned int trackletN01, const unsigned int trackletN12, - const unsigned selectedN, const unsigned int vertexN, const unsigned int totalVertexN, - const float trackletT, const float selecT, const float vertexT) + unsigned int trackletN01, unsigned int trackletN12, + unsigned selectedN, unsigned int vertexN, unsigned int totalVertexN, + float initT, float trackletT, float selecT, float vertexT) { - logger(fmt::format(" - {} Vertexer: found {} | {} tracklets in: {:.2f} ms", mTraits->getName(), trackletN01, trackletN12, trackletT)); - logger(fmt::format(" - {} Vertexer: selected {} tracklets in: {:.2f} ms", mTraits->getName(), selectedN, selecT)); - logger(fmt::format(" - {} Vertexer: found {} vertices (total: {}) in: {:.2f} ms", mTraits->getName(), vertexN, totalVertexN, vertexT)); + logger(fmt::format(" - {}: completed in {:.2f} ms", StateNames[Init], initT)); + logger(fmt::format(" - {}: found {} | {} tracklets in {:.2f} ms", StateNames[Trackleting], trackletN01, trackletN12, trackletT)); + logger(fmt::format(" - {}: selected {} tracklets in {:.2f} ms", StateNames[Selection], selectedN, selecT)); + logger(fmt::format(" - {}: found {} vertices (total {}) in {:.2f} ms", StateNames[Finding], vertexN, totalVertexN, vertexT)); } template class Vertexer<7>; diff --git a/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx b/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx index d0baa65c49147..00674b715b97d 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx @@ -36,6 +36,7 @@ namespace o2::its { namespace { + template void trackleterKernelHost( const gsl::span& clustersNextLayer, // 0 2 @@ -48,9 +49,9 @@ void trackleterKernelHost( const IndexTableUtils& utils, const TimeEstBC& timErr, gsl::span rofFoundTrackletsOffsets, - const int globalOffsetNextLayer = 0, - const int globalOffsetCurrentLayer = 0, - const int maxTrackletsPerCluster = static_cast(2e3)) + const int globalOffsetNextLayer, + const int globalOffsetCurrentLayer, + const int maxTrackletsPerCluster) { const int PhiBins{utils.getNphiBins()}; const int ZBins{utils.getNzBins()}; @@ -58,24 +59,24 @@ void trackleterKernelHost( for (int iCurrentLayerClusterIndex = 0; iCurrentLayerClusterIndex < clustersCurrentLayer.size(); ++iCurrentLayerClusterIndex) { int storedTracklets{0}; const Cluster& currentCluster{clustersCurrentLayer[iCurrentLayerClusterIndex]}; - const int4 selectedBinsRect{VertexerTraits::getBinsRect(currentCluster, (int)Mode, 0.f, 50.f, phiCut / 2, utils)}; - if (selectedBinsRect.x != 0 || selectedBinsRect.y != 0 || selectedBinsRect.z != 0 || selectedBinsRect.w != 0) { + const int4 selectedBinsRect{o2::its::getBinsRect(currentCluster, (int)Mode + 1, 0.f, 0.f, 100.f, phiCut / 2, utils)}; + if (selectedBinsRect.x >= 0) { int phiBinsNum{selectedBinsRect.w - selectedBinsRect.y + 1}; if (phiBinsNum < 0) { phiBinsNum += PhiBins; } // loop on phi bins next layer - for (int iPhiBin{selectedBinsRect.y}, iPhiCount{0}; iPhiCount < phiBinsNum; iPhiBin = ++iPhiBin == PhiBins ? 0 : iPhiBin, iPhiCount++) { + for (int iPhiBin{selectedBinsRect.y}, iPhiCount{0}; iPhiCount < phiBinsNum && storedTracklets < maxTrackletsPerCluster; iPhiBin = ++iPhiBin == PhiBins ? 0 : iPhiBin, iPhiCount++) { const int firstBinIndex{utils.getBinIndex(selectedBinsRect.x, iPhiBin)}; const int firstRowClusterIndex{indexTableNext[firstBinIndex]}; const int maxRowClusterIndex{indexTableNext[firstBinIndex + ZBins]}; // loop on clusters next layer - for (int iNextLayerClusterIndex{firstRowClusterIndex}; iNextLayerClusterIndex < maxRowClusterIndex && iNextLayerClusterIndex < static_cast(clustersNextLayer.size()); ++iNextLayerClusterIndex) { + for (int iNextLayerClusterIndex{firstRowClusterIndex}; iNextLayerClusterIndex < maxRowClusterIndex && iNextLayerClusterIndex < static_cast(clustersNextLayer.size()) && storedTracklets < maxTrackletsPerCluster; ++iNextLayerClusterIndex) { if (usedClustersNextLayer[iNextLayerClusterIndex]) { continue; } const Cluster& nextCluster{clustersNextLayer[iNextLayerClusterIndex]}; - if (o2::gpu::GPUCommonMath::Abs(math_utils::smallestAngleDifference(currentCluster.phi, nextCluster.phi)) < phiCut) { + if (math_utils::isPhiDifferenceBelow(currentCluster.phi, nextCluster.phi, phiCut)) { if (storedTracklets < maxTrackletsPerCluster) { if constexpr (!EvalRun) { if constexpr (Mode == TrackletMode::Layer0Layer1) { @@ -105,35 +106,39 @@ void trackletSelectionKernelHost( gsl::span usedClusters2, // global layer 2 used clusters const gsl::span& tracklets01, const gsl::span& tracklets12, - bounded_vector& usedTracklets, + bounded_vector& usedTracklets, const gsl::span foundTracklets01, const gsl::span foundTracklets12, bounded_vector& lines, const gsl::span& trackletLabels, bounded_vector& linesLabels, const int nLayer1Clusters, - const float tanLambdaCut = 0.025f, - const float phiCut = 0.005f, - const int maxTracklets = 100) + const float tanLambdaCut, + const float phiCut, + const int maxTracklets) { int offset01{0}, offset12{0}; for (int iCurrentLayerClusterIndex{0}; iCurrentLayerClusterIndex < nLayer1Clusters; ++iCurrentLayerClusterIndex) { int validTracklets{0}; - for (int iTracklet12{offset12}; iTracklet12 < offset12 + foundTracklets12[iCurrentLayerClusterIndex]; ++iTracklet12) { - for (int iTracklet01{offset01}; iTracklet01 < offset01 + foundTracklets01[iCurrentLayerClusterIndex]; ++iTracklet01) { + const int endTracklet01 = offset01 + foundTracklets01[iCurrentLayerClusterIndex]; + const int endTracklet12 = offset12 + foundTracklets12[iCurrentLayerClusterIndex]; + for (int iTracklet12{offset12}; iTracklet12 < endTracklet12 && validTracklets != maxTracklets; ++iTracklet12) { + const auto& tracklet12{tracklets12[iTracklet12]}; + for (int iTracklet01{offset01}; iTracklet01 < endTracklet01 && validTracklets != maxTracklets; ++iTracklet01) { if (usedTracklets[iTracklet01]) { continue; } const auto& tracklet01{tracklets01[iTracklet01]}; - const auto& tracklet12{tracklets12[iTracklet12]}; if (!tracklet01.getTimeStamp().isCompatible(tracklet12.getTimeStamp())) { continue; } const float deltaTanLambda{o2::gpu::GPUCommonMath::Abs(tracklet01.tanLambda - tracklet12.tanLambda)}; - const float deltaPhi{o2::gpu::GPUCommonMath::Abs(math_utils::smallestAngleDifference(tracklet01.phi, tracklet12.phi))}; - if (deltaTanLambda < tanLambdaCut && deltaPhi < phiCut && validTracklets != maxTracklets) { + if (deltaTanLambda >= tanLambdaCut) { + continue; + } + if (math_utils::isPhiDifferenceBelow(tracklet01.phi, tracklet12.phi, phiCut) && validTracklets != maxTracklets) { usedClusters0[tracklet01.firstClusterIndex] = 1; usedClusters2[tracklet12.secondClusterIndex] = 1; usedTracklets[iTracklet01] = true; @@ -264,20 +269,18 @@ void VertexerTraits::computeTracklets(const int iteration) if (mTimeFrame->hasMCinformation()) { for (const auto& trk : mTimeFrame->getTracklets()[0]) { o2::MCCompLabel label; - if (!trk.isEmpty()) { - int sortedId0{trk.firstClusterIndex}; - int sortedId1{trk.secondClusterIndex}; - for (const auto& lab0 : mTimeFrame->getClusterLabels(0, mTimeFrame->getClusters()[0][sortedId0].clusterId)) { - for (const auto& lab1 : mTimeFrame->getClusterLabels(1, mTimeFrame->getClusters()[1][sortedId1].clusterId)) { - if (lab0 == lab1 && lab0.isValid()) { - label = lab0; - break; - } - } - if (label.isValid()) { + int sortedId0{trk.firstClusterIndex}; + int sortedId1{trk.secondClusterIndex}; + for (const auto& lab0 : mTimeFrame->getClusterLabels(0, mTimeFrame->getClusters()[0][sortedId0].clusterId)) { + for (const auto& lab1 : mTimeFrame->getClusterLabels(1, mTimeFrame->getClusters()[1][sortedId1].clusterId)) { + if (lab0 == lab1 && lab0.isValid()) { + label = lab0; break; } } + if (label.isValid()) { + break; + } } mTimeFrame->getTrackletsLabel(0).emplace_back(label); } @@ -296,8 +299,8 @@ void VertexerTraits::computeTrackletMatching(const int iteration) if (mTimeFrame->getFoundTracklets(pivotRofId, 0).empty() || skipROF(iteration, pivotRofId)) { continue; } - mTimeFrame->getLines(pivotRofId).reserve(mTimeFrame->getNTrackletsCluster(pivotRofId, 0).size()); - bounded_vector usedTracklets(mTimeFrame->getFoundTracklets(pivotRofId, 0).size(), false, mMemoryPool.get()); + mTimeFrame->getLines(pivotRofId).reserve(std::min(mTimeFrame->getFoundTracklets(pivotRofId, 0).size(), mTimeFrame->getNTrackletsCluster(pivotRofId, 0).size() * constants::MaxSelectedTrackletsPerCluster)); + bounded_vector usedTracklets(mTimeFrame->getFoundTracklets(pivotRofId, 0).size(), 0, mMemoryPool.get()); trackletSelectionKernelHost( mTimeFrame->getClusters()[0].data(), mTimeFrame->getClusters()[1].data(), @@ -313,7 +316,8 @@ void VertexerTraits::computeTrackletMatching(const int iteration) mTimeFrame->getLinesLabel(pivotRofId), static_cast(mTimeFrame->getClustersOnLayer(pivotRofId, 1).size()), mVrtParams[iteration].tanLambdaCut, - mVrtParams[iteration].phiCut); + mVrtParams[iteration].phiCut, + constants::MaxSelectedTrackletsPerCluster); totalLines.local() += mTimeFrame->getLines(pivotRofId).size(); } }); diff --git a/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx b/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx index 8594e59149444..dd98a75efca7c 100644 --- a/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx +++ b/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx @@ -135,6 +135,78 @@ BOOST_AUTO_TEST_CASE(rofoverlap_staggered_pp) view.printAll(); } +BOOST_AUTO_TEST_CASE(rofoverlap_staggered_track_time_ignores_added_error) +{ + const uint32_t rofLen{198}; + const uint32_t rofDelay{33}; + const uint32_t addTimeErr{100}; + + o2::its::ROFOverlapTable<7> tableNoError; + o2::its::ROFOverlapTable<7> tableWithError; + for (uint32_t lay{0}; lay < 7; ++lay) { + const auto delay = (lay == 6) ? 0 : lay * rofDelay; + tableNoError.defineLayer(lay, 2, rofLen, delay, 0, 0); + tableWithError.defineLayer(lay, 2, rofLen, delay, 0, addTimeErr); + } + + auto getCommonTrackTime = [](const auto& table) { + auto ts = table.getLayer(0).getROFTimeBounds(0); + for (uint32_t lay{1}; lay < 7; ++lay) { + ts += table.getLayer(lay).getROFTimeBounds(0); + } + return ts.makeSymmetrical(); + }; + + const auto tsNoError = getCommonTrackTime(tableNoError); + BOOST_CHECK_EQUAL(tsNoError.getTimeStamp(), 181.5f); + BOOST_CHECK_EQUAL(tsNoError.getTimeStampError(), 16.5f); + + const auto tsWithError = getCommonTrackTime(tableWithError); + BOOST_CHECK_EQUAL(tsWithError.getTimeStamp(), 181.5f); + BOOST_CHECK_EQUAL(tsWithError.getTimeStampError(), 16.5f); +} + +BOOST_AUTO_TEST_CASE(rofoverlap_track_time_boundary_migration_fallback) +{ + const uint32_t rofLen{198}; + const uint32_t addTimeErr{30}; + + o2::its::ROFOverlapTable<7> table; + for (uint32_t lay{0}; lay < 7; ++lay) { + table.defineLayer(lay, 4, rofLen, 0, 0, addTimeErr); + } + + auto getCommonTrackTime = [](const auto& table) { + bool firstCls{true}, nominalCompatible{true}; + o2::its::TimeEstBC nominalTS, expandedTS; + for (uint32_t lay{0}; lay < 7; ++lay) { + const auto rof = lay < 3 ? 0 : 1; + const auto nominalROFTS = table.getLayer(lay).getROFTimeBounds(rof); + const auto expandedROFTS = table.getLayer(lay).getROFTimeBounds(rof, true); + if (firstCls) { + firstCls = false; + nominalTS = nominalROFTS; + expandedTS = expandedROFTS; + } else { + if (nominalCompatible) { + if (nominalTS.isCompatible(nominalROFTS)) { + nominalTS += nominalROFTS; + } else { + nominalCompatible = false; + } + } + BOOST_REQUIRE(expandedTS.isCompatible(expandedROFTS)); + expandedTS += expandedROFTS; + } + } + return (nominalCompatible ? nominalTS : expandedTS).makeSymmetrical(); + }; + + const auto tsWithError = getCommonTrackTime(table); + BOOST_CHECK_EQUAL(tsWithError.getTimeStamp(), 198.f); + BOOST_CHECK_EQUAL(tsWithError.getTimeStampError(), 30.f); +} + BOOST_AUTO_TEST_CASE(rofoverlap_staggered_alllayers) { // test staggered layers with ROF delay diff --git a/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx index 932c82c2d1ca4..bbafc48e931ed 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx @@ -85,8 +85,11 @@ void TrackerDPL::endOfStream(EndOfStreamContext& ec) void TrackerDPL::end() { - mITSTrackingInterface.printSummary(); - LOGF(info, "ITS CA-Tracker total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); + if (static bool printOnce{false}; !printOnce) { + printOnce = true; + mITSTrackingInterface.printSummary(); + LOGF(info, "ITS CA-Tracker total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); + } } DataProcessorSpec getTrackerSpec(bool useMC, bool doStag, bool useGeom, int trgType, TrackingMode::Type trMode, const bool overrBeamEst, o2::gpu::gpudatatypes::DeviceType dType) From 011d107ba2e63b679fea977b237919498a470aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Jacazio?= Date: Wed, 29 Apr 2026 20:42:31 +0200 Subject: [PATCH 016/102] [ALICE3] TF3: add proto-digitizer (#15340) --- .../Upgrades/ALICE3/IOTOF/CMakeLists.txt | 1 + .../IOTOF/DataFormatsIOTOF/CMakeLists.txt | 22 +++ .../include/DataFormatsIOTOF/Digit.h | 46 +++++ .../src/DataFormatsIOTOFLinkDef.h | 20 +++ .../IOTOF/DataFormatsIOTOF/src/Digit.cxx | 21 +++ .../ALICE3/IOTOF/simulation/CMakeLists.txt | 5 +- .../include/IOTOFSimulation/Digitizer.h | 111 ++++++++++++ .../ALICE3/IOTOF/simulation/src/Digitizer.cxx | 162 ++++++++++++++++++ 8 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/include/DataFormatsIOTOF/Digit.h create mode 100644 Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h create mode 100644 Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/Digit.cxx create mode 100644 Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Digitizer.h create mode 100644 Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx diff --git a/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt index 808320bf66404..04288f205d8f4 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt @@ -11,4 +11,5 @@ add_subdirectory(base) add_subdirectory(simulation) +add_subdirectory(DataFormatsIOTOF) add_subdirectory(macros) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt new file mode 100644 index 0000000000000..534e6217807c5 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt @@ -0,0 +1,22 @@ +# 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(DataFormatsIOTOF + SOURCES src/Digit.cxx + # SOURCES src/MCLabel.cxx + # SOURCES src/Cluster.cxx + PUBLIC_LINK_LIBRARIES O2::DataFormatsITSMFT) + +o2_target_root_dictionary(DataFormatsIOTOF + HEADERS include/DataFormatsIOTOF/Digit.h + # HEADERS include/DataFormatsIOTOF/MCLabel.h + # HEADERS include/DataFormatsIOTOF/Cluster.h + ) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/include/DataFormatsIOTOF/Digit.h b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/include/DataFormatsIOTOF/Digit.h new file mode 100644 index 0000000000000..19b5dc3bcd72b --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/include/DataFormatsIOTOF/Digit.h @@ -0,0 +1,46 @@ +// 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 Digit.h +/// \brief Definition of IOTOF digit class +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-03-17 +/// + +#ifndef ALICEO2_IOTOF_DIGIT_H +#define ALICEO2_IOTOF_DIGIT_H + +#include "DataFormatsITSMFT/Digit.h" + +namespace o2::iotof +{ +class Digit : public o2::itsmft::Digit +{ + public: + Digit() = default; + ~Digit() = default; + Digit(UShort_t chipindex = 0, UShort_t row = 0, UShort_t col = 0, Int_t charge = 0, double time = 0.) + : o2::itsmft::Digit(chipindex, row, col, charge), mTime(time) {}; + + // Setters + void setTime(double time) { mTime = time; } + + // Getters + double getTime() const { return mTime; } + + private: + double mTime = 0.; ///< Measured time (ns) + ClassDefNV(Digit, 1); +}; + +} // namespace o2::iotof +#endif // ALICEO2_IOTOF_DIGIT_H diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h new file mode 100644 index 0000000000000..8a167df4d6c7b --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h @@ -0,0 +1,20 @@ +// 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. + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::iotof::Digit + ; +// #pragma link C++ class std::vector < o2::iotof::Digit> + ; +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/Digit.cxx b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/Digit.cxx new file mode 100644 index 0000000000000..b15ecd94cd9af --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/Digit.cxx @@ -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. + +/// +/// \file Digit.cxx +/// \brief Implementation of IOTOF digit class +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-03-17 +/// + +#include "DataFormatsIOTOF/Digit.h" + +using namespace o2::iotof; diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/simulation/CMakeLists.txt index 5e7cbd87bfd35..f3418d9065fcb 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/CMakeLists.txt @@ -12,11 +12,14 @@ o2_add_library(IOTOFSimulation SOURCES src/Layer.cxx src/Detector.cxx + src/Digitizer.cxx # src/IOTOFServices.cxx PUBLIC_LINK_LIBRARIES O2::IOTOFBase + O2::DataFormatsIOTOF O2::ITSMFTSimulation) o2_target_root_dictionary(IOTOFSimulation HEADERS include/IOTOFSimulation/Detector.h - include/IOTOFSimulation/Layer.h) + include/IOTOFSimulation/Layer.h + include/IOTOFSimulation/Digitizer.h) # include/IOTOFSimulation/IOTOFServices.h) \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Digitizer.h b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Digitizer.h new file mode 100644 index 0000000000000..8964e33f8a1b6 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Digitizer.h @@ -0,0 +1,111 @@ +// 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 Digitizer.h +/// \brief Definition of the ALICE3 TOF digitizer +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-03-17 +/// + +#ifndef ALICEO2_IOTOF_DIGITIZER_H +#define ALICEO2_IOTOF_DIGITIZER_H + +#include "ITSMFTSimulation/Hit.h" +#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsIOTOF/Digit.h" +#include "DataFormatsITSMFT/ROFRecord.h" +#include "CommonDataFormat/InteractionRecord.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "IOTOFBase/GeometryTGeo.h" + +namespace o2::iotof +{ + +/// \class Digitizer +/// \brief Digitizer for the ALICE3 Inner/Outer TOF detector +/// +/// Converts MC hits into detector digits by: +/// - Applying time smearing according to detector resolution +/// - Converting energy loss to charge +/// - Applying charge threshold +/// - Managing readout frames (ROF) +class Digitizer +{ + public: + void setDigits(std::vector* dig) { mDigits = dig; } + void setMCLabels(o2::dataformats::MCTruthContainer* mclb) { mMCLabels = mclb; } + void setROFRecords(std::vector* rec) { mROFRecords = rec; } + + /// Initialize the digitizer + void init(); + + /// Steer conversion of hits to digits + void process(const std::vector* hits, int evID, int srcID); + + /// Set the event time + void setEventTime(const o2::InteractionTimeRecord& irt) { mEventTime = irt; } + + /// Set continuous readout mode + void setContinuous(bool v) { mContinuous = v; } + bool isContinuous() const { return mContinuous; } + + /// Flush the output container + void fillOutputContainer(); + + // Provide the common iotof::GeometryTGeo to access matrices and segmentation + void setGeometry(const o2::iotof::GeometryTGeo* gm) { mGeometry = gm; } + + // Setters for digitization parameters + void setChargeThreshold(float thr) { mChargeThreshold = thr; } + void setTimeResolution(float res) { mTimeResolution = res; } + void setEfficiency(float eff) { mEfficiency = eff; } + void setEnergyToCharge(float e2c) { mEnergyToCharge = e2c; } + + // Getters + float getChargeThreshold() const { return mChargeThreshold; } + float getTimeResolution() const { return mTimeResolution; } + float getEfficiency() const { return mEfficiency; } + + private: + /// Process a single hit + void processHit(const o2::itsmft::Hit& hit, int evID, int srcID); + + /// Apply time smearing to simulate detector resolution + double smearTime(double time) const; + + /// Convert energy loss to charge + int energyToCharge(float energyLoss) const; + + /// Check if the hit passes efficiency cut + bool isEfficient() const; + + static constexpr float sec2ns = 1e9f; ///< seconds to nanoseconds conversion + + const o2::iotof::GeometryTGeo* mGeometry = nullptr; ///< IOTOF geometry + + std::vector* mDigits = nullptr; //! output digits + std::vector* mROFRecords = nullptr; //! output ROF records + o2::dataformats::MCTruthContainer* mMCLabels = nullptr; //! output labels + + o2::InteractionTimeRecord mEventTime; ///< global event time and interaction record + bool mContinuous = true; ///< continuous readout mode + + // Digitization parameters + float mChargeThreshold = 100.f; ///< charge threshold for digit creation (electrons) + float mTimeResolution = 0.020f; ///< time resolution sigma in ns (20 ps default) + float mEfficiency = 0.98f; ///< detection efficiency + float mEnergyToCharge = 3.6e-9f; ///< energy loss to electrons conversion (3.6 eV per e-h pair in Si) +}; +} // namespace o2::iotof + +#endif \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx new file mode 100644 index 0000000000000..b865d6958ecfd --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx @@ -0,0 +1,162 @@ +// 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 Digitizer.cxx +/// \brief Implementation of the ALICE3 TOF digitizer +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-03-17 +/// + +#include "IOTOFSimulation/Digitizer.h" +#include "DetectorsRaw/HBFUtils.h" + +#include +#include +#include +#include +#include +#include + +namespace o2::iotof +{ + +//_______________________________________________________________________ +void Digitizer::init() +{ + LOG(info) << "Initializing IOTOF digitizer"; + LOG(info) << " Time resolution: " << mTimeResolution * 1e3 << " ps"; + LOG(info) << " Charge threshold: " << mChargeThreshold << " electrons"; + LOG(info) << " Detection efficiency: " << mEfficiency * 100 << " %"; + LOG(info) << " Continuous mode: " << (mContinuous ? "ON" : "OFF"); +} + +//_______________________________________________________________________ +void Digitizer::process(const std::vector* hits, int evID, int srcID) +{ + // Digitize hits from a single event + LOG(debug) << "Digitizing IOTOF hits: " << hits->size() << " hits from event " << evID << " source " << srcID; + + if (!hits || hits->empty()) { + return; + } + + // Sort hits by detector ID for better cache locality + std::vector hitIdx(hits->size()); + std::iota(hitIdx.begin(), hitIdx.end(), 0); + std::sort(hitIdx.begin(), hitIdx.end(), + [hits](int lhs, int rhs) { + return (*hits)[lhs].GetDetectorID() < (*hits)[rhs].GetDetectorID(); + }); + + // Process each hit + for (int i : hitIdx) { + processHit((*hits)[i], evID, srcID); + } + + // In triggered mode, flush output after each event + if (!mContinuous) { + fillOutputContainer(); + } +} + +//_______________________________________________________________________ +void Digitizer::processHit(const o2::itsmft::Hit& hit, int evID, int srcID) +{ + // Process a single hit and create a digit if it passes all cuts + + // Apply efficiency cut + if (!isEfficient()) { + LOG(debug) << "Hit rejected by efficiency cut"; + return; + } + + // Get detector element ID + int detID = hit.GetDetectorID(); + + // Convert energy loss to charge (number of electrons) + float energyLoss = hit.GetEnergyLoss(); // in GeV + int charge = energyToCharge(energyLoss); + + // Apply charge threshold + if (charge < mChargeThreshold) { + LOG(debug) << "Hit rejected by charge threshold: " << charge << " < " << mChargeThreshold; + return; + } + + // Get hit time and apply smearing + // Hit time is in seconds, convert to ns and add event time + double hitTime = hit.GetTime() * sec2ns; // convert to ns + double eventTimeNS = mEventTime.getTimeNS(); // event time since orbit 0 + double absoluteTime = hitTime + eventTimeNS; // absolute time + double smearedTime = smearTime(absoluteTime); // apply detector resolution + + // For now, use simple row/col mapping from detector ID + // TODO: Implement proper segmentation when geometry is finalized + uint16_t chipIndex = static_cast(detID); + uint16_t row = 0; // Will be determined from hit position + uint16_t col = 0; // Will be determined from hit position + + // Create the digit with time information + int digID = mDigits->size(); + mDigits->emplace_back(chipIndex, row, col, charge, smearedTime); + + LOG(debug) << "Created digit #" << digID << " chip=" << chipIndex + << " charge=" << charge << " time=" << smearedTime << " ns"; + + // Add MC truth label + if (mMCLabels) { + o2::MCCompLabel lbl(hit.GetTrackID(), evID, srcID, false); + mMCLabels->addElement(digID, lbl); + } +} + +//_______________________________________________________________________ +double Digitizer::smearTime(double time) const +{ + // Apply Gaussian smearing to simulate detector time resolution + if (mTimeResolution > 0) { + return time + gRandom->Gaus(0, mTimeResolution); + } + return time; +} + +//_______________________________________________________________________ +int Digitizer::energyToCharge(float energyLoss) const +{ + // Convert energy loss (GeV) to number of electrons + // Typical value: 3.6 eV per electron-hole pair in silicon + // energyLoss is in GeV, mEnergyToCharge is GeV per electron + return static_cast(energyLoss / mEnergyToCharge); +} + +//_______________________________________________________________________ +bool Digitizer::isEfficient() const +{ + // Apply efficiency cut using random number + return gRandom->Uniform() < mEfficiency; +} + +//_______________________________________________________________________ +void Digitizer::fillOutputContainer() +{ + // Create ROF record for the current event + if (mROFRecords && mDigits && !mDigits->empty()) { + o2::itsmft::ROFRecord rof; + rof.setFirstEntry(0); + rof.setNEntries(mDigits->size()); + rof.setBCData(mEventTime); + mROFRecords->push_back(rof); + LOG(debug) << "Created ROF record with " << mDigits->size() << " digits"; + } +} + +} // namespace o2::iotof From 38239f7417707ff1a9b91774a39006d15535fe01 Mon Sep 17 00:00:00 2001 From: shahoian Date: Thu, 30 Apr 2026 01:40:44 +0200 Subject: [PATCH 017/102] Avoid duplication of enable-M-shape-correction option --- prodtests/full-system-test/dpl-workflow.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prodtests/full-system-test/dpl-workflow.sh b/prodtests/full-system-test/dpl-workflow.sh index 92e93e3977c65..5ab54f9cf4b43 100755 --- a/prodtests/full-system-test/dpl-workflow.sh +++ b/prodtests/full-system-test/dpl-workflow.sh @@ -325,7 +325,7 @@ while [[ $# -gt 0 ]]; do case "$1" in --lumi-type=*) TPC_CORR_OPT+=" --lumi-type ${1#*=}"; [[ ${1#*=} == "2" ]] && { IGNOREIDC=0; }; shift 1;; --lumi-type) TPC_CORR_OPT+=" --lumi-type ${2}"; [[ ${2} == "2" ]] && { IGNOREIDC=0; }; shift 2;; - --enable-M-shape-correction) TPC_CORR_OPT+=" --enable-M-shape-correction"; TPC_SCALERS_CONF+=" --enable-M-shape-correction" ; shift 1;; + --enable-M-shape-correction) TPC_CORR_OPT+=" --enable-M-shape-correction"; shift 1;; --corrmap-lumi-mode=*) TPC_CORR_OPT+=" --corrmap-lumi-mode ${1#*=}"; shift 1;; --corrmap-lumi-mode) TPC_CORR_OPT+=" --corrmap-lumi-mode ${2}"; shift 2;; --disable-ctp-lumi-request) TPC_CORR_OPT+=" --disable-ctp-lumi-request"; CTPLUMY_DISABLED=1; shift 1;; From 499233f8c45ccab85a53e184744556805f96ea2d Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Thu, 30 Apr 2026 12:28:25 +0200 Subject: [PATCH 018/102] [ALICE3] TRK: introduce the staggering of ROFs in the TRK (#15335) * ALICE3: Add method to get the TRK layer id * ALICE3: introduce staggering logic in the digitizer * ALICE3: Adapt test macros to new staggering structure * ALICE3: adapt cluster finding to separated streams per layer Fix CheckCluster macro * Change macro name in CMakeLists.txt * Fix Specs.h and use that in geometry building --- .../TRK/base/include/TRKBase/AlmiraParam.h | 29 +- .../TRK/base/include/TRKBase/GeometryTGeo.h | 3 +- .../ALICE3/TRK/base/include/TRKBase/Specs.h | 2 + .../ALICE3/TRK/base/src/GeometryTGeo.cxx | 16 +- .../ALICE3/TRK/macros/test/CMakeLists.txt | 2 +- .../ALICE3/TRK/macros/test/CheckBandwidth.C | 205 +++++----- .../ALICE3/TRK/macros/test/CheckClusters.C | 380 +++++++++--------- .../test/{CheckDigits.C => CheckDigitsTRK.C} | 354 ++++++++-------- .../include/TRKReconstruction/Clusterer.h | 5 +- .../include/TRKReconstruction/ClustererACTS.h | 4 +- .../TRK/reconstruction/src/Clusterer.cxx | 11 +- .../TRK/reconstruction/src/ClustererACTS.cxx | 4 +- .../include/TRKSimulation/DigiParams.h | 42 +- .../include/TRKSimulation/Digitizer.h | 24 +- .../ALICE3/TRK/simulation/src/Detector.cxx | 13 +- .../ALICE3/TRK/simulation/src/DigiParams.cxx | 12 +- .../ALICE3/TRK/simulation/src/Digitizer.cxx | 93 ++--- .../include/TRKWorkflow/ClustererSpec.h | 2 + .../include/TRKWorkflow/DigitReaderSpec.h | 16 +- .../TRK/workflow/src/ClusterWriterSpec.cxx | 86 +++- .../ALICE3/TRK/workflow/src/ClustererSpec.cxx | 126 +++--- .../TRK/workflow/src/DigitReaderSpec.cxx | 90 +++-- .../TRK/workflow/src/DigitWriterSpec.cxx | 105 +++-- .../src/TRKDigitizerSpec.cxx | 302 +++++++------- 24 files changed, 1018 insertions(+), 908 deletions(-) rename Detectors/Upgrades/ALICE3/TRK/macros/test/{CheckDigits.C => CheckDigitsTRK.C} (52%) diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h index 2048666e21c00..9929a14c4e39c 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h @@ -12,21 +12,40 @@ #ifndef O2_TRK_ALMIRAPARAM_H #define O2_TRK_ALMIRAPARAM_H +#include + #include "CommonConstants/LHCConstants.h" #include "CommonUtils/ConfigurableParam.h" #include "CommonUtils/ConfigurableParamHelper.h" +#include "TRKBase/Specs.h" namespace o2 { namespace trk { -constexpr float DEFAlmiraStrobeDelay = 0.f; ///< default strobe delay in ns wrt ROF start, to be tuned with the real chip response struct AlmiraParam : public o2::conf::ConfigurableParamHelper { - int roFrameLengthInBC = o2::constants::lhc::LHCMaxBunches / 198; ///< ROF length in BC for continuous mode - float strobeDelay = DEFAlmiraStrobeDelay; ///< strobe start in ns wrt ROF start - float strobeLengthCont = -1.; ///< if < 0, full ROF length minus delay - int roFrameBiasInBC = 0; ///< ROF start bias in BC wrt orbit start + static constexpr size_t kNLayers = constants::VD::petal::nLayers + constants::ML::nLayers + constants::OT::nLayers; + static constexpr size_t getNLayers() { return kNLayers; } + + int roFrameLengthInBCPerLayer[kNLayers] = {0}; ///< ROF length in BC per layer + float strobeDelayPerLayer[kNLayers] = {0}; ///< strobe delay in ns per layer + float strobeLengthContPerLayer[kNLayers] = {0}; ///< strobe length in ns per layer + int roFrameBiasInBCPerLayer[kNLayers] = {0}; ///< ROF start bias in BC per layer + int roFrameDelayInBCPerLayer[kNLayers] = {0}; ///< extra ROF delay in BC per layer + + int getROFLengthInBC(int layer) const + { + if (roFrameLengthInBCPerLayer[layer] > 0) { + return roFrameLengthInBCPerLayer[layer]; + } else { + return o2::constants::lhc::LHCMaxBunches / 198; + } + } + float getStrobeDelay(int layer) const { return strobeDelayPerLayer[layer]; } + float getStrobeLengthCont(int layer) const { return strobeLengthContPerLayer[layer]; } + int getROFBiasInBC(int layer) const { return roFrameBiasInBCPerLayer[layer]; } + int getROFDelayInBC(int layer) const { return roFrameDelayInBCPerLayer[layer]; } O2ParamDef(AlmiraParam, "TRKAlmiraParam"); }; diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h index 576dbf434f757..53ad7662cbfcd 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h @@ -89,7 +89,8 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache int getSubDetID(int index) const; int getPetalCase(int index) const; int getDisk(int index) const; - int getLayer(int index) const; + int getLayer(int index) const; ///< local layer index within the sub-detector (0-based per VD/MLOT) + int getLayerTRK(int index) const; ///< global layer index across the full TRK (VD layers 0..nVD-1, MLOT layers nVD..nTotal-1) int getStave(int index) const; int getHalfStave(int index) const; int getModule(int index) const; diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h index 91d6f5669ef33..0ed7ca6a8a8d4 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h @@ -100,6 +100,7 @@ constexpr int nCols{static_cast(length / chip::pitchZ)}; namespace ML { +constexpr int nLayers{5}; // number of layers in the ML constexpr double width{constants::moduleMLOT::width * 1}; // width of the stave // constexpr double length{constants::moduleMLOT::length * 10}; // length of the stave constexpr double length{124 * cm}; // length of the stave, hardcoded to fit the implemented geometry @@ -117,6 +118,7 @@ constexpr double length{258 * cm}; // len constexpr int nRows{static_cast(width / moduleMLOT::chip::pitchX)}; // number of rows in the halfstave constexpr int nCols{static_cast(length / moduleMLOT::chip::pitchZ)}; // number of columns in the halfstave } // namespace halfstave +constexpr int nLayers{3}; // number of layers in the OT constexpr double width{halfstave::width * 2}; // width of the stave constexpr double length{halfstave::length}; // length of the stave constexpr int nRows{static_cast(width / moduleMLOT::chip::pitchX)}; // number of rows in the stave diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx index 10c1c63615d35..ddfc844cc964d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx @@ -12,6 +12,7 @@ #include #include #include "TRKBase/SegmentationChip.h" +#include "TRKBase/Specs.h" #include #include @@ -64,7 +65,7 @@ GeometryTGeo::GeometryTGeo(bool build, int loadTrans) : DetMatrixCache(detectors void GeometryTGeo::Build(int loadTrans) { ///// current geometry organization: - ///// total elements = x staves (*2 half staves if staggered geometry) * 8 layers ML+OT + 4 petal cases * (3 layers + 6 disks) + ///// total elements = x staves (*2 half staves if staggered geometry) * ML+OT layers + 4 petal cases * (3 layers + 6 disks) ///// indexing from 0 to 35: VD petals -> layers -> disks ///// indexing from 36 to y: MLOT staves @@ -203,6 +204,15 @@ int GeometryTGeo::getLayer(int index) const return -1; /// -1 if not found } //__________________________________________________________________________ +int GeometryTGeo::getLayerTRK(int index) const +{ + if (getDisk(index) != -1) { + return -1; /// disks do not have a global layer index + } + int subDetID = getSubDetID(index); + return subDetID * o2::trk::constants::VD::petal::nLayers + getLayer(index); // MLOT: offset by number of VD layers +} +//__________________________________________________________________________ int GeometryTGeo::getStave(int index) const { int subDetID = getSubDetID(index); @@ -1121,7 +1131,7 @@ void GeometryTGeo::Print(Option_t*) const std::cout << "Detector ID: " << sInstance.get()->getDetID() << std::endl; LOGF(info, "Summary of GeometryTGeo: %s", getName()); - LOGF(info, "Number of layers ML + OL: %d", mNumberOfLayersMLOT); + LOGF(info, "Number of layers ML + OT: %d", mNumberOfLayersMLOT); LOGF(info, "Number of active parts VD: %d", mNumberOfActivePartsVD); LOGF(info, "Number of layers VD: %d", mNumberOfLayersVD); LOGF(info, "Number of petals VD: %d", mNumberOfPetalsVD); @@ -1133,7 +1143,7 @@ void GeometryTGeo::Print(Option_t*) const LOGF(info, "Number of staves and half staves per layer MLOT: "); for (int i = 0; i < mNumberOfLayersMLOT; i++) { std::string mlot = ""; - mlot = (i < 4) ? "ML" : "OT"; + mlot = (i < constants::ML::nLayers) ? "ML" : "OT"; LOGF(info, "Layer: %d, %s, %d staves, %d half staves per stave", i, mlot.c_str(), mNumberOfStaves[i], mNumberOfHalfStaves[i]); } LOGF(info, "Number of modules per stave (half stave) in each ML(OT) layer: "); diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt index 33d1b4a5afdc6..cdae7c9c379fd 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt @@ -21,7 +21,7 @@ o2_add_test_root_macro(CheckBandwidth.C O2::Steer LABELS trk COMPILE_ONLY) -o2_add_test_root_macro(CheckDigits.C +o2_add_test_root_macro(CheckDigitsTRK.C PUBLIC_LINK_LIBRARIES O2::ITSMFTBase O2::ITSMFTSimulation O2::TRKBase diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C index 06d24361c7721..c071a06516d30 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C @@ -41,13 +41,9 @@ namespace { constexpr double DigitBits = 24.; -constexpr double BunchCrossingNS = 25.; -constexpr int ReadoutCycleBC = 18; -constexpr int ReadoutCycleSimBC = 18; -constexpr double ReadoutCycleSeconds = ReadoutCycleBC * BunchCrossingNS * 1.e-9; } // namespace -void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGeom = "o2sim_geometry.root", std::string collContextFile = "collisioncontext.root") +void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGeom = "sgn_geometry.root", std::string collContextFile = "collisioncontext.root") { gStyle->SetPalette(55); gStyle->SetOptStat(0); @@ -73,15 +69,14 @@ void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGe latex.DrawLatex(0.04, 0.06, Form("avg non-empty: %.3f collisions/ROF", nonEmptyAverageValue)); }; - auto drawCollisionInfoBox = [](double averageValue) { - const double effectiveIRRateHz = ReadoutCycleSeconds > 0. ? averageValue / ReadoutCycleSeconds : 0.; + auto drawCollisionInfoBox = [](double effectiveIRRateHz, double rofLengthBC) { TPaveText infoBox(0.55, 0.79, 0.88, 0.9, "NDC"); infoBox.SetFillColor(0); infoBox.SetBorderSize(1); infoBox.SetTextAlign(12); infoBox.SetTextSize(0.028); infoBox.AddText(Form("effective IR: %.3f MHz", effectiveIRRateHz * 1.e-6)); - infoBox.AddText(Form("ROF length: %d BC", ReadoutCycleBC)); + infoBox.AddText(Form("ROF length: %d BC", rofLengthBC)); infoBox.DrawClone(); }; @@ -168,13 +163,42 @@ void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGe } } + // --- Digits --- + + TFile* digFile = TFile::Open(digifile.data()); + TTree* digTree = (TTree*)digFile->Get("o2sim"); + const int nDigitTreeEntries = digTree->GetEntries(); + + std::vector*> digArr(nTotalLayers, nullptr); + std::vector*> rofRecords(nTotalLayers, nullptr); + for (int nDigitsLayer{0}; nDigitsLayer < nTotalLayers; ++nDigitsLayer) { + if (!digTree->GetBranch(Form("TRKDigit_%i", nDigitsLayer))) { + break; + } + digTree->SetBranchAddress(Form("TRKDigit_%i", nDigitsLayer), &digArr[nDigitsLayer]); + digTree->SetBranchAddress(Form("TRKDigitROF_%i", nDigitsLayer), &rofRecords[nDigitsLayer]); + } + + digTree->GetEntry(0); + if (nDigitTreeEntries > 1) { + LOG(warning) << "Digit tree has " << nDigitTreeEntries << " entries, but this macro processes entry 0 only."; + } + + std::vector rofLengthBC(nTotalLayers, 0u); + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + if (rofRecords[iLayer]->size() < 2) { + LOG(fatal) << "ROF record tree for layer " << iLayer << " has " << rofRecords[iLayer]->size() + << " entries, but at least 2 are expected (one per ROF + one empty at the end). Check input files."; + } + rofLengthBC[iLayer] = (*rofRecords[iLayer])[1].getBCData().bc - (*rofRecords[iLayer])[0].getBCData().bc; + } + // --- Collision context --- TFile* ccFile = TFile::Open(collContextFile.data()); auto* digiContext = (o2::steer::DigitizationContext*)ccFile->Get("DigitizationContext"); const o2::InteractionRecord firstSampledIR{0, digiContext->getFirstOrbitForSampling()}; - std::vector collisionsPerROF; - + std::vector> collisionsPerROF(nTotalLayers); for (const auto& record : digiContext->getEventRecords()) { auto nbc = record.differenceInBC(firstSampledIR); if (record.getTimeOffsetWrtBC() < 0. && nbc > 0) { @@ -183,63 +207,47 @@ void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGe if (nbc < 0) { continue; } - const size_t rofID = nbc / ReadoutCycleSimBC; - if (rofID >= collisionsPerROF.size()) { - collisionsPerROF.resize(rofID + 1, 0u); + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + if (rofLengthBC[iLayer] == 0) { + LOG(fatal) << "ROF length in BC for layer " << iLayer << " is zero. Check input files."; + } + const size_t rofID = nbc / rofLengthBC[iLayer]; + if (rofID >= collisionsPerROF[iLayer].size()) { + collisionsPerROF[iLayer].resize(rofID + 1, 0u); + } + ++collisionsPerROF[iLayer][rofID]; } - ++collisionsPerROF[rofID]; } - // --- Digits --- - - TFile* digFile = TFile::Open(digifile.data()); - TTree* digTree = (TTree*)digFile->Get("o2sim"); - const int nDigitTreeEntries = digTree->GetEntries(); - - std::vector* digArr = nullptr; - std::vector* rofRecords = nullptr; - digTree->SetBranchAddress("TRKDigit", &digArr); - digTree->SetBranchAddress("TRKDigitROF", &rofRecords); - - digTree->GetEntry(0); - if (nDigitTreeEntries > 1) { - LOG(warning) << "Digit tree has " << nDigitTreeEntries << " entries, but this macro processes entry 0 only."; - } - - const int nROFRec = (int)rofRecords->size(); - if (nROFRec != (int)collisionsPerROF.size()) { + const int nROFRec = (int)rofRecords[0]->size(); + if (nROFRec != (int)collisionsPerROF[0].size()) { LOG(fatal) << "Mismatch between number of ROF records in digit tree (" << nROFRec - << ") and number of ROFs computed from collisioncontext.root (" << collisionsPerROF.size() + << ") and number of ROFs computed from collisioncontext.root (" << collisionsPerROF[0].size() << "). Check input files."; } // --- Accumulate per-chip digit counts across all ROFs --- - const double rofNorm = nROFRec > 0 ? 1. / nROFRec : 0.; - const double bitsToGbps = ReadoutCycleSeconds > 0. ? DigitBits / ReadoutCycleSeconds / 1.e9 : 0.; - std::vector digitsPerChip(nChips, 0ull); std::vector maxDigitsPerROFPerChip(nChips, 0u); std::vector digitsInCurrentROFPerChip(nChips, 0u); - for (unsigned int iROF = 0; iROF < rofRecords->size(); ++iROF) { + for (unsigned int iROF = 0; iROF < (unsigned int)nROFRec; ++iROF) { std::vector touchedChips; - const unsigned int rofStart = (*rofRecords)[iROF].getFirstEntry(); - const unsigned int rofEnd = rofStart + (*rofRecords)[iROF].getNEntries(); - - for (unsigned int iDigit = rofStart; iDigit < rofEnd; ++iDigit) { - if (iDigit % 1000 == 0) { - std::cout << "Reading digit " << iDigit << " / " << digArr->size() << "\r" << std::flush; - } - const int chipID = (*digArr)[iDigit].getChipIndex(); - if (chipGeom[chipID].disk != -1) { - continue; - } - if (digitsInCurrentROFPerChip[chipID] == 0) { - touchedChips.push_back(chipID); + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + const unsigned int rofStart = (*rofRecords[iLayer])[iROF].getFirstEntry(); + const unsigned int rofEnd = rofStart + (*rofRecords[iLayer])[iROF].getNEntries(); + for (unsigned int iDigit = rofStart; iDigit < rofEnd; ++iDigit) { + const int chipID = (*digArr[iLayer])[iDigit].getChipIndex(); + if (chipGeom[chipID].disk != -1) { + continue; + } + if (digitsInCurrentROFPerChip[chipID] == 0) { + touchedChips.push_back(chipID); + } + ++digitsPerChip[chipID]; + ++digitsInCurrentROFPerChip[chipID]; } - ++digitsPerChip[chipID]; - ++digitsInCurrentROFPerChip[chipID]; } for (const int chipID : touchedChips) { @@ -273,19 +281,21 @@ void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGe // digitsInCurrentROFPerChip is all zeros after the first scan — reuse it here. { std::vector touchedChips; - for (unsigned int iROF = 0; iROF < rofRecords->size(); ++iROF) { + for (unsigned int iROF = 0; iROF < (unsigned int)nROFRec; ++iROF) { touchedChips.clear(); - const unsigned int rofStart = (*rofRecords)[iROF].getFirstEntry(); - const unsigned int rofEnd = rofStart + (*rofRecords)[iROF].getNEntries(); - for (unsigned int iDigit = rofStart; iDigit < rofEnd; ++iDigit) { - const int chipID = (*digArr)[iDigit].getChipIndex(); - if (chipGeom[chipID].disk != -1) { - continue; + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + const unsigned int rofStart = (*rofRecords[iLayer])[iROF].getFirstEntry(); + const unsigned int rofEnd = rofStart + (*rofRecords[iLayer])[iROF].getNEntries(); + for (unsigned int iDigit = rofStart; iDigit < rofEnd; ++iDigit) { + const int chipID = (*digArr[iLayer])[iDigit].getChipIndex(); + if (chipGeom[chipID].disk != -1) { + continue; + } + if (digitsInCurrentROFPerChip[chipID] == 0) { + touchedChips.push_back(chipID); + } + ++digitsInCurrentROFPerChip[chipID]; } - if (digitsInCurrentROFPerChip[chipID] == 0) { - touchedChips.push_back(chipID); - } - ++digitsInCurrentROFPerChip[chipID]; } for (const int chipID : touchedChips) { const int l = chipGeom[chipID].globalLayer; @@ -321,7 +331,7 @@ void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGe continue; } const int l = g.globalLayer; - const double avgDigits = digitsPerChip[chipID] * rofNorm; + const double avgDigits = digitsPerChip[chipID] / collisionsPerROF[l].size(); const double maxDigits = (double)maxDigitsPerROFPerChip[chipID]; layerStats[l].avgDigitsPerROF += avgDigits; layerStats[l].avgMaxDigitsPerROF += maxDigits; @@ -334,37 +344,50 @@ void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGe layerStats[l].avgDigitsPerROF *= norm; layerStats[l].avgMaxDigitsPerROF *= norm; } - layerStats[l].avgBandwidthGbps = layerStats[l].avgDigitsPerROF * bitsToGbps; - layerStats[l].peakBandwidthGbps = layerStats[l].peakAvgDigitsPerROF * bitsToGbps; + layerStats[l].avgBandwidthGbps = layerStats[l].avgDigitsPerROF * DigitBits / rofLengthBC[l] / o2::constants::lhc::LHCBunchSpacingNS * 1.e9; + layerStats[l].peakBandwidthGbps = layerStats[l].peakAvgDigitsPerROF * DigitBits / rofLengthBC[l] / o2::constants::lhc::LHCBunchSpacingNS * 1.e9; } // --- Collision plots --- if (nROFRec > 0) { - auto* hCollisionsPerROF = new TH1D("h_collisions_per_rof", "Collisions per ROF;ROF id;N collisions", - nROFRec, -0.5, nROFRec - 0.5); - double totalCollisionsPerROF = 0.; - double peakCollisionsPerROF = 0.; - int nNonEmptyROFs = 0; - - for (int rofID = 0; rofID < nROFRec; ++rofID) { - const double nColl = collisionsPerROF[rofID]; - hCollisionsPerROF->SetBinContent(rofID + 1, nColl); - totalCollisionsPerROF += nColl; - peakCollisionsPerROF = std::max(peakCollisionsPerROF, nColl); - if (nColl > 0.) { - ++nNonEmptyROFs; + std::vector totalCollisionsPerROF(nTotalLayers, 0.); + std::vector peakCollisionsPerROF(nTotalLayers, 0.); + std::vector nNonEmptyROFs(nTotalLayers, 0); + std::vector hCollisionsPerROFPerLayer(nTotalLayers, nullptr); + + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + hCollisionsPerROFPerLayer[iLayer] = new TH1D(Form("h_collisions_per_rof_layer%d", iLayer), + Form("Layer %d;ROF id;N collisions", iLayer), + nROFRec, -0.5, nROFRec - 0.5); + for (int rofID = 0; rofID < nROFRec; ++rofID) { + const double nColl = collisionsPerROF[iLayer][rofID]; + hCollisionsPerROFPerLayer[iLayer]->SetBinContent(rofID + 1, nColl); + totalCollisionsPerROF[iLayer] += nColl; + peakCollisionsPerROF[iLayer] = std::max(peakCollisionsPerROF[iLayer], nColl); + if (nColl > 0.) { + ++nNonEmptyROFs[iLayer]; + } } } - const double avgCollisionsPerROF = totalCollisionsPerROF / nROFRec; - auto* canvCollisionsPerROF = new TCanvas("canvCollisionsPerROF", "Collisions per ROF", 1050, 1050); - canvCollisionsPerROF->SetTopMargin(0.08); - hCollisionsPerROF->Draw("hist"); - drawCollisionSummary(avgCollisionsPerROF, - nNonEmptyROFs > 0 ? totalCollisionsPerROF / nNonEmptyROFs : 0., - peakCollisionsPerROF); - drawCollisionInfoBox(avgCollisionsPerROF); + const int nCols = std::max(1, (int)std::ceil(std::sqrt((double)nTotalLayers))); + const int nRows = (nTotalLayers + nCols - 1) / nCols; + auto* canvCollisionsPerROF = new TCanvas("canvCollisionsPerROF", "Collisions per ROF", 350 * nCols, 300 * nRows); + canvCollisionsPerROF->Divide(nCols, nRows); + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + canvCollisionsPerROF->cd(iLayer + 1); + gPad->SetTopMargin(0.10); + gPad->SetBottomMargin(0.14); + gPad->SetLeftMargin(0.14); + hCollisionsPerROFPerLayer[iLayer]->Draw("hist"); + const double avgCollisionsPerROF = totalCollisionsPerROF[iLayer] / collisionsPerROF[iLayer].size(); + drawCollisionSummary(avgCollisionsPerROF, + nNonEmptyROFs[iLayer] > 0 ? totalCollisionsPerROF[iLayer] / nNonEmptyROFs[iLayer] : 0., + peakCollisionsPerROF[iLayer]); + const double effectiveIRRateHz = avgCollisionsPerROF / rofLengthBC[iLayer] / o2::constants::lhc::LHCBunchSpacingNS * 1.e9; + drawCollisionInfoBox(effectiveIRRateHz, rofLengthBC[iLayer]); + } appendCanvasToPdf(canvCollisionsPerROF); } @@ -405,9 +428,9 @@ void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGe if (g.petal < 0 || g.petal >= nVDPetals) { continue; } - const double avgDigits = digitsPerChip[chipID] * rofNorm; + const double avgDigits = double(digitsPerChip[chipID]) / collisionsPerROF[g.globalLayer].size(); const double maxDigits = (double)maxDigitsPerROFPerChip[chipID]; - const double bandwidth = avgDigits * bitsToGbps; + const double bandwidth = avgDigits * DigitBits / rofLengthBC[g.globalLayer] / o2::constants::lhc::LHCBunchSpacingNS * 1.e9; hVDDigitsPerROF->SetBinContent(g.petal + 1, g.localLayer + 1, avgDigits); hVDMaxDigitsPerROF->SetBinContent(g.petal + 1, g.localLayer + 1, maxDigits); @@ -483,12 +506,12 @@ void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGe continue; } const double staveBinX = g.stave + (g.halfStave + 0.5) / nHalfStaves - 0.5; - const double avgDigits = digitsPerChip[chipID] * rofNorm; + const double avgDigits = double(digitsPerChip[chipID]) / collisionsPerROF[g.globalLayer].size(); const double maxDigits = (double)maxDigitsPerROFPerChip[chipID]; hDigitsPerROF->Fill(staveBinX, sensorID, avgDigits); hMaxDigitsPerROF->Fill(staveBinX, sensorID, maxDigits); - hBandwidth->Fill(staveBinX, sensorID, avgDigits * bitsToGbps); + hBandwidth->Fill(staveBinX, sensorID, avgDigits * DigitBits / rofLengthBC[g.globalLayer] / o2::constants::lhc::LHCBunchSpacingNS * 1.e9); } const auto& ls = layerStats[outputLayer]; diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C index 327577102d86e..28dc61aed9c8b 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C @@ -26,6 +26,7 @@ #include "DataFormatsTRK/Cluster.h" #include "DataFormatsTRK/ROFRecord.h" +#include "TRKBase/AlmiraParam.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" #include "TRKSimulation/Hit.h" @@ -142,47 +143,63 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", return; } - std::vector* clusArr = nullptr; - std::vector* rofRecVecP = nullptr; - std::vector* patternsPtr = nullptr; - clusTree->SetBranchAddress("TRKClusterComp", &clusArr); - clusTree->SetBranchAddress("TRKClustersROF", &rofRecVecP); - if (clusTree->GetBranch("TRKClusterPatt") != nullptr) { - clusTree->SetBranchAddress("TRKClusterPatt", &patternsPtr); + // Read per-layer cluster branches and accumulate + static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; + std::vector*> clusArrPerLayer(nLayers, nullptr); + std::vector*> rofRecVecPerLayer(nLayers, nullptr); + std::vector*> patternsPerLayer(nLayers, nullptr); + std::vector*> clusLabArrPerLayer(nLayers, nullptr); + + bool hasMC = true; + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + std::string brClus = std::string("TRKClusterComp_") + std::to_string(iLayer); + std::string brROF = std::string("TRKClustersROF_") + std::to_string(iLayer); + std::string brPatt = std::string("TRKClusterPatt_") + std::to_string(iLayer); + std::string brMCTruth = std::string("TRKClusterMCTruth_") + std::to_string(iLayer); + + if (clusTree->GetBranch(brClus.c_str()) == nullptr) { + LOGP(warning, "Branch {} not found, skipping layer {}", brClus, iLayer); + continue; + } + clusTree->SetBranchAddress(brClus.c_str(), &clusArrPerLayer[iLayer]); + clusTree->SetBranchAddress(brROF.c_str(), &rofRecVecPerLayer[iLayer]); + if (clusTree->GetBranch(brPatt.c_str()) != nullptr) { + clusTree->SetBranchAddress(brPatt.c_str(), &patternsPerLayer[iLayer]); + } + if (clusTree->GetBranch(brMCTruth.c_str()) != nullptr) { + clusTree->SetBranchAddress(brMCTruth.c_str(), &clusLabArrPerLayer[iLayer]); + } else { + hasMC = false; + } } - o2::dataformats::MCTruthContainer* clusLabArr = nullptr; - std::vector mc2rofVec, *mc2rofVecP = &mc2rofVec; - bool hasMC = (clusTree->GetBranch("TRKClusterMCTruth") != nullptr); - if (hasMC) { - clusTree->SetBranchAddress("TRKClusterMCTruth", &clusLabArr); - clusTree->SetBranchAddress("TRKClustersMC2ROF", &mc2rofVecP); + // Read entry and accumulate all layers + clusTree->GetEntry(0); + // Print total clusters per layer + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + LOGP(info, "Layer {}: {} clusters", iLayer, clusArrPerLayer[iLayer]->size()); } - clusTree->GetEntry(0); - const unsigned int nROFRec = rofRecVecP ? (unsigned int)rofRecVecP->size() : 0u; + // Accumulate max ROF count across all layers + unsigned int nROFRec = 0; + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + nROFRec = std::max(nROFRec, (unsigned int)rofRecVecPerLayer[iLayer]->size()); + } LOGP(info, "Number of ROF records: {}", nROFRec); - auto pattIt = patternsPtr ? patternsPtr->cbegin() : std::vector::const_iterator{}; - // ── Build per-ROF MC event range ─────────────────────────────────────────── - std::vector mcEvMin(nROFRec, (int)hitTree->GetEntries()); - std::vector mcEvMax(nROFRec, -1); + // ── Load all MC hit events upfront (TRK has no MC2ROF mapping) ────────────── if (hasMC) { - for (int imc = (int)mc2rofVec.size(); imc--;) { - const auto& mc2rof = mc2rofVec[imc]; - if (mc2rof.rofRecordID < 0) { - continue; - } - for (unsigned int irfd = mc2rof.maxROF - mc2rof.minROF + 1; irfd--;) { - unsigned int irof = mc2rof.rofRecordID + irfd; - if (irof >= nROFRec) { - continue; - } - if (mcEvMin[irof] > imc) { - mcEvMin[irof] = imc; - } - if (mcEvMax[irof] < imc) { - mcEvMax[irof] = imc; + LOGP(info, "Pre-loading {} MC events", hitTree->GetEntries()); + for (int im = 0; im < (int)hitTree->GetEntries(); im++) { + if (hitVecPool[im] == nullptr) { + hitTree->SetBranchAddress("TRKHit", &hitVecPool[im]); + hitTree->GetEntry(im); + auto& mc2hit = mc2hitVec[im]; + const auto* hv = hitVecPool[im]; + for (int ih = (int)hv->size(); ih--;) { + const auto& hit = (*hv)[ih]; + uint64_t key = (uint64_t(hit.GetTrackID()) << 32) + hit.GetDetectorID(); + mc2hit.emplace(key, ih); } } } @@ -203,170 +220,165 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", // ── Main loop ────────────────────────────────────────────────────────────── for (unsigned int irof = 0; irof < nROFRec; irof++) { - const auto& rofRec = (*rofRecVecP)[irof]; - - // Cache MC hit events for this ROF - if (hasMC) { - for (int im = mcEvMin[irof]; im <= mcEvMax[irof]; im++) { - if (hitVecPool[im] == nullptr) { - hitTree->SetBranchAddress("TRKHit", &hitVecPool[im]); - hitTree->GetEntry(im); - auto& mc2hit = mc2hitVec[im]; - const auto* hv = hitVecPool[im]; - for (int ih = (int)hv->size(); ih--;) { - const auto& hit = (*hv)[ih]; - uint64_t key = (uint64_t(hit.GetTrackID()) << 32) + hit.GetDetectorID(); - mc2hit.emplace(key, ih); - } - } + // Process each layer + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + if (rofRecVecPerLayer[iLayer]->empty() || irof >= rofRecVecPerLayer[iLayer]->size()) { + continue; } - } - - for (int icl = 0; icl < rofRec.getNEntries(); icl++) { - const int clEntry = rofRec.getFirstEntry() + icl; - const auto& cluster = (*clusArr)[clEntry]; - nTot++; - - // ── Parse pattern → center-of-gravity within bounding box ────────── - // The cluster stores the bounding-box top-left pixel (row, col). - // The pattern stream encodes [rowSpan, colSpan, bitmap...] for each cluster. - // We accumulate pixel row/col offsets to obtain a sub-pixel CoG correction. - float cogDr{0.f}, cogDc{0.f}; // mean offsets from bbox origin (pixels) - if (patternsPtr) { - const uint8_t rowSpan = *pattIt++; - const uint8_t colSpan = *pattIt++; - const int nBytes = (rowSpan * colSpan + 7) / 8; - int nPix{0}, pixIdx{0}; - for (int ib = 0; ib < nBytes; ib++) { - const uint8_t byte = *pattIt++; - for (int bit = 7; bit >= 0 && pixIdx < rowSpan * colSpan; bit--, pixIdx++) { - if (byte & (1 << bit)) { - cogDr += pixIdx / colSpan; - cogDc += pixIdx % colSpan; - nPix++; + const auto& rofRec = (*rofRecVecPerLayer[iLayer])[irof]; + const auto& clusArr = *clusArrPerLayer[iLayer]; + const auto& patternsPtr = (patternsPerLayer[iLayer] == nullptr) ? nullptr : patternsPerLayer[iLayer]; + const auto& clusLabArr = clusLabArrPerLayer[iLayer]; + + // Create per-layer pattern iterator + auto pattIt = patternsPtr ? patternsPtr->cbegin() : std::vector::const_iterator{}; + + for (int icl = 0; icl < rofRec.getNEntries(); icl++) { + const int clEntry = rofRec.getFirstEntry() + icl; + const auto& cluster = clusArr[clEntry]; + nTot++; + + // ── Parse pattern → center-of-gravity within bounding box ────────── + // The cluster stores the bounding-box top-left pixel (row, col). + // The pattern stream encodes [rowSpan, colSpan, bitmap...] for each cluster. + // We accumulate pixel row/col offsets to obtain a sub-pixel CoG correction. + float cogDr{0.f}, cogDc{0.f}; // mean offsets from bbox origin (pixels) + if (patternsPtr) { + const uint8_t rowSpan = *pattIt++; + const uint8_t colSpan = *pattIt++; + const int nBytes = (rowSpan * colSpan + 7) / 8; + int nPix{0}, pixIdx{0}; + for (int ib = 0; ib < nBytes; ib++) { + const uint8_t byte = *pattIt++; + 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; + } } - if (nPix > 1) { - cogDr /= nPix; - cogDc /= nPix; + + // ── Cluster local → global (CoG position) ───────────────────────────── + // Get local coords of the bounding-box corner pixel, then apply the + // fractional CoG displacement using the pixel pitch. + // Formula from detectorToLocalUnchecked: + // VD : xRow = 0.5*(width[lay]-pitchRow) - row*pitchRow → row↑ xRow↓ + // zCol = col*pitchCol + 0.5*(pitchCol-length) → col↑ zCol↑ + // MLOT: same structure with MLOT pitches + float clLocX{0.f}, clLocZ{0.f}; + o2::trk::SegmentationChip::detectorToLocalUnchecked( + cluster.row, cluster.col, clLocX, clLocZ, + cluster.subDetID, cluster.layer, cluster.disk); + const float pitchRow = (cluster.subDetID == 0) + ? o2::trk::SegmentationChip::PitchRowVD + : o2::trk::SegmentationChip::PitchRowMLOT; + const float pitchCol = (cluster.subDetID == 0) + ? o2::trk::SegmentationChip::PitchColVD + : o2::trk::SegmentationChip::PitchColMLOT; + clLocX -= cogDr * pitchRow; // increasing row → decreasing xRow + clLocZ += cogDc * pitchCol; // increasing col → increasing zCol + const float yResponse = (cluster.subDetID == 0) ? yPlaneVD : yPlaneMLOT; + // For VD the L2G matrix is built in the *curved* local frame (quasi-Cartesian, + // origin at the beam axis). Convert flat (clLocX, 0) → curved (xC, yC) first. + // For MLOT (flat sensors) the local frame is already Cartesian: pass directly. + // clLocX is already in the flat frame from detectorToLocalUnchecked + CoG and + // does NOT need any further transformation for the residual comparison. + o2::math_utils::Point3D locC; + if (cluster.subDetID == 0) { + auto cv = o2::trk::SegmentationChip::flatToCurved(cluster.layer, clLocX, 0.f); + locC = {cv.X(), cv.Y(), clLocZ}; + } else { + locC = {clLocX, yResponse, clLocZ}; + } + auto gloC = gman->getMatrixL2G(cluster.chipID)(locC); + + if (!hasMC || clusLabArr == nullptr) { + // No MC info: just fill geometry columns, leave residuals as 0 + std::array data = { + -1.f, -1.f, + 0.f, 0.f, 0.f, 0.f, 0.f, + (float)gloC.X(), (float)gloC.Y(), (float)gloC.Z(), + clLocX, clLocZ, + (float)rofRec.getROFrame(), (float)cluster.size, (float)cluster.chipID, + (float)cluster.layer, (float)cluster.disk, (float)cluster.subDetID, + (float)cluster.row, (float)cluster.col, -1.f}; + nt.Fill(data.data()); + continue; } - } - // ── Cluster local → global (CoG position) ───────────────────────────── - // Get local coords of the bounding-box corner pixel, then apply the - // fractional CoG displacement using the pixel pitch. - // Formula from detectorToLocalUnchecked: - // VD : xRow = 0.5*(width[lay]-pitchRow) - row*pitchRow → row↑ xRow↓ - // zCol = col*pitchCol + 0.5*(pitchCol-length) → col↑ zCol↑ - // MLOT: same structure with MLOT pitches - float clLocX{0.f}, clLocZ{0.f}; - o2::trk::SegmentationChip::detectorToLocalUnchecked( - cluster.row, cluster.col, clLocX, clLocZ, - cluster.subDetID, cluster.layer, cluster.disk); - const float pitchRow = (cluster.subDetID == 0) - ? o2::trk::SegmentationChip::PitchRowVD - : o2::trk::SegmentationChip::PitchRowMLOT; - const float pitchCol = (cluster.subDetID == 0) - ? o2::trk::SegmentationChip::PitchColVD - : o2::trk::SegmentationChip::PitchColMLOT; - clLocX -= cogDr * pitchRow; // increasing row → decreasing xRow - clLocZ += cogDc * pitchCol; // increasing col → increasing zCol - const float yResponse = (cluster.subDetID == 0) ? yPlaneVD : yPlaneMLOT; - // For VD the L2G matrix is built in the *curved* local frame (quasi-Cartesian, - // origin at the beam axis). Convert flat (clLocX, 0) → curved (xC, yC) first. - // For MLOT (flat sensors) the local frame is already Cartesian: pass directly. - // clLocX is already in the flat frame from detectorToLocalUnchecked + CoG and - // does NOT need any further transformation for the residual comparison. - o2::math_utils::Point3D locC; - if (cluster.subDetID == 0) { - auto cv = o2::trk::SegmentationChip::flatToCurved(cluster.layer, clLocX, 0.f); - locC = {cv.X(), cv.Y(), clLocZ}; - } else { - locC = {clLocX, yResponse, clLocZ}; - } - auto gloC = gman->getMatrixL2G(cluster.chipID)(locC); + // ── MC label ─────────────────────────────────────────────────────── + const auto& labels = clusLabArr->getLabels(clEntry); + if (labels.empty() || !labels[0].isValid()) { + nInvalidLabel++; + continue; + } + const auto& lab = labels[0]; + const int trID = lab.getTrackID(); + const int evID = lab.getEventID(); + + // ── Find matching MC hit ──────────────────────────────────────────── + const auto& mc2hit = mc2hitVec[evID]; + uint64_t key = (uint64_t(trID) << 32) + cluster.chipID; + auto hitEntry = mc2hit.find(key); + if (hitEntry == mc2hit.end()) { + nNoMCHit++; + continue; + } + const auto& hit = (*hitVecPool[evID])[hitEntry->second]; + const float pt = TMath::Hypot(hit.GetPx(), hit.GetPy()); + + // ── Hit global midpoint ──────────────────────────────────────────── + const auto& gloHend = hit.GetPos(); + const auto& gloHsta = hit.GetPosStart(); + o2::math_utils::Point3D gloHmid( + 0.5f * (gloHend.X() + gloHsta.X()), + 0.5f * (gloHend.Y() + gloHsta.Y()), + 0.5f * (gloHend.Z() + gloHsta.Z())); + + // ── Hit global → local ───────────────────────────── + o2::math_utils::Point3D locHsta = gman->getMatrixL2G(cluster.chipID) ^ (gloHsta); // inverse L2G + o2::math_utils::Point3D locHend = gman->getMatrixL2G(cluster.chipID) ^ (gloHend); // inverse L2G + + // ── Propagate hit segment to the sensor response surface ─────────────── + // Rather than the geometric midpoint, find where the track segment crosses + // the response plane (y = responseYShift in the flat local frame). + // For VD (curved): convert both endpoints to flat frame first. + // For ML/OT (flat): use local coordinates directly. + float hitLocX{0.f}, hitLocZ{0.f}; + if (cluster.subDetID == 0) { // VD – curved sensor + auto flatSta = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHsta.X(), locHsta.Y()); + auto flatEnd = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHend.X(), locHend.Y()); + float x0 = flatSta.X(), y0 = flatSta.Y(), z0 = locHsta.Z(); + float dltx = flatEnd.X() - x0, dlty = flatEnd.Y() - y0, dltz = locHend.Z() - z0; + float r = (std::abs(dlty) > 1e-9f) ? (yPlaneVD - y0) / dlty : 0.5f; + hitLocX = x0 + r * dltx; + hitLocZ = z0 + r * dltz; + } else { // ML/OT – flat sensor + float x0 = locHsta.X(), y0 = locHsta.Y(), z0 = locHsta.Z(); + float dltx = locHend.X() - x0, dlty = locHend.Y() - y0, dltz = locHend.Z() - z0; + float r = (std::abs(dlty) > 1e-9f) ? (yPlaneMLOT - y0) / dlty : 0.5f; + hitLocX = x0 + r * dltx; + hitLocZ = z0 + r * dltz; + } - if (!hasMC || clusLabArr == nullptr) { - // No MC info: just fill geometry columns, leave residuals as 0 + nValid++; std::array data = { - -1.f, -1.f, - 0.f, 0.f, 0.f, 0.f, 0.f, + (float)evID, (float)trID, + hitLocX, hitLocZ, + (float)gloHmid.X(), (float)gloHmid.Y(), (float)gloHmid.Z(), (float)gloC.X(), (float)gloC.Y(), (float)gloC.Z(), clLocX, clLocZ, (float)rofRec.getROFrame(), (float)cluster.size, (float)cluster.chipID, (float)cluster.layer, (float)cluster.disk, (float)cluster.subDetID, - (float)cluster.row, (float)cluster.col, -1.f}; + (float)cluster.row, (float)cluster.col, pt}; nt.Fill(data.data()); - continue; - } - - // ── MC label ─────────────────────────────────────────────────────── - const auto& labels = clusLabArr->getLabels(clEntry); - if (labels.empty() || !labels[0].isValid()) { - nInvalidLabel++; - continue; } - const auto& lab = labels[0]; - const int trID = lab.getTrackID(); - const int evID = lab.getEventID(); - - // ── Find matching MC hit ──────────────────────────────────────────── - const auto& mc2hit = mc2hitVec[evID]; - uint64_t key = (uint64_t(trID) << 32) + cluster.chipID; - auto hitEntry = mc2hit.find(key); - if (hitEntry == mc2hit.end()) { - nNoMCHit++; - continue; - } - const auto& hit = (*hitVecPool[evID])[hitEntry->second]; - const float pt = TMath::Hypot(hit.GetPx(), hit.GetPy()); - - // ── Hit global midpoint ──────────────────────────────────────────── - const auto& gloHend = hit.GetPos(); - const auto& gloHsta = hit.GetPosStart(); - o2::math_utils::Point3D gloHmid( - 0.5f * (gloHend.X() + gloHsta.X()), - 0.5f * (gloHend.Y() + gloHsta.Y()), - 0.5f * (gloHend.Z() + gloHsta.Z())); - - // ── Hit global → local ───────────────────────────── - o2::math_utils::Point3D locHsta = gman->getMatrixL2G(cluster.chipID) ^ (gloHsta); // inverse L2G - o2::math_utils::Point3D locHend = gman->getMatrixL2G(cluster.chipID) ^ (gloHend); // inverse L2G - - // ── Propagate hit segment to the sensor response surface ─────────────── - // Rather than the geometric midpoint, find where the track segment crosses - // the response plane (y = responseYShift in the flat local frame). - // For VD (curved): convert both endpoints to flat frame first. - // For ML/OT (flat): use local coordinates directly. - float hitLocX{0.f}, hitLocZ{0.f}; - if (cluster.subDetID == 0) { // VD – curved sensor - auto flatSta = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHsta.X(), locHsta.Y()); - auto flatEnd = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHend.X(), locHend.Y()); - float x0 = flatSta.X(), y0 = flatSta.Y(), z0 = locHsta.Z(); - float dltx = flatEnd.X() - x0, dlty = flatEnd.Y() - y0, dltz = locHend.Z() - z0; - float r = (std::abs(dlty) > 1e-9f) ? (yPlaneVD - y0) / dlty : 0.5f; - hitLocX = x0 + r * dltx; - hitLocZ = z0 + r * dltz; - } else { // ML/OT – flat sensor - float x0 = locHsta.X(), y0 = locHsta.Y(), z0 = locHsta.Z(); - float dltx = locHend.X() - x0, dlty = locHend.Y() - y0, dltz = locHend.Z() - z0; - float r = (std::abs(dlty) > 1e-9f) ? (yPlaneMLOT - y0) / dlty : 0.5f; - hitLocX = x0 + r * dltx; - hitLocZ = z0 + r * dltz; - } - - nValid++; - std::array data = { - (float)evID, (float)trID, - hitLocX, hitLocZ, - (float)gloHmid.X(), (float)gloHmid.Y(), (float)gloHmid.Z(), - (float)gloC.X(), (float)gloC.Y(), (float)gloC.Z(), - clLocX, clLocZ, - (float)rofRec.getROFrame(), (float)cluster.size, (float)cluster.chipID, - (float)cluster.layer, (float)cluster.disk, (float)cluster.subDetID, - (float)cluster.row, (float)cluster.col, pt}; - nt.Fill(data.data()); } } diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigits.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C similarity index 52% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigits.C rename to Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C index ec1adf500f562..400457fc98585 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigits.C +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C @@ -75,7 +75,7 @@ void addTLines(float pitch) gPad->Update(); } -void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = "o2sim_HitsTRK.root", std::string inputGeom = "o2sim_geometry.root", std::string paramfile = "o2sim_par.root") +void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = "o2sim_HitsTRK.root", std::string inputGeom = "o2sim_geometry.root") { gStyle->SetPalette(55); @@ -97,6 +97,10 @@ void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = auto* gman = o2::trk::GeometryTGeo::Instance(); gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + const int nVDLayers = gman->extractNumberOfLayersVD(); + const int nMLOTLayers = gman->getNumberOfLayersMLOT(); + const int nTotalLayers = nVDLayers + nMLOTLayers; + SegmentationChip seg; // seg.Print(); @@ -117,223 +121,181 @@ void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = std::vector> mc2hitVec(nevH); - // Digits + // Digits — per-layer branches TFile* digFile = TFile::Open(digifile.data()); TTree* digTree = (TTree*)digFile->Get("o2sim"); - std::vector* digArr = nullptr; - digTree->SetBranchAddress("TRKDigit", &digArr); - - o2::dataformats::IOMCTruthContainerView* plabels = nullptr; - digTree->SetBranchAddress("TRKDigitMCTruth", &plabels); - - // Get Read Out Frame arrays - std::vector* ROFRecordArrray = nullptr; - digTree->SetBranchAddress("TRKDigitROF", &ROFRecordArrray); - std::vector& ROFRecordArrrayRef = *ROFRecordArrray; + int nDigitLayers = 0; + std::vector*> digArr(nTotalLayers, nullptr); + std::vector*> rofRecordsArr(nTotalLayers, nullptr); + std::vector plabelsArr(nTotalLayers, nullptr); - std::vector* MC2ROFRecordArrray = nullptr; - digTree->SetBranchAddress("TRKDigitMC2ROF", &MC2ROFRecordArrray); - std::vector& MC2ROFRecordArrrayRef = *MC2ROFRecordArrray; + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + if (!digTree->GetBranch(Form("TRKDigit_%i", iLayer))) { + break; + } + digTree->SetBranchAddress(Form("TRKDigit_%i", iLayer), &digArr[iLayer]); + digTree->SetBranchAddress(Form("TRKDigitROF_%i", iLayer), &rofRecordsArr[iLayer]); + digTree->SetBranchAddress(Form("TRKDigitMCTruth_%i", iLayer), &plabelsArr[iLayer]); + ++nDigitLayers; + } digTree->GetEntry(0); - int nROFRec = (int)ROFRecordArrrayRef.size(); - std::vector mcEvMin(nROFRec, hitTree->GetEntries()); - std::vector mcEvMax(nROFRec, -1); - o2::dataformats::ConstMCTruthContainer labels; - plabels->copyandflatten(labels); - delete plabels; - - // >> build min and max MC events used by each ROF - for (int imc = MC2ROFRecordArrrayRef.size(); imc--;) { - const auto& mc2rof = MC2ROFRecordArrrayRef[imc]; - // printf("MCRecord: "); - // mc2rof.print(); - - if (mc2rof.rofRecordID < 0) { - continue; // this MC event did not contribute to any ROF + // Load all MC hit events upfront and build the hit lookup map. + for (int im = 0; im < nevH; ++im) { + hitTree->SetBranchAddress("TRKHit", &hitArray[im]); + hitTree->GetEntry(im); + auto& mc2hit = mc2hitVec[im]; + for (int ih = hitArray[im]->size(); ih--;) { + const auto& hit = (*hitArray[im])[ih]; + uint64_t key = (uint64_t(hit.GetTrackID()) << 32) + hit.GetDetectorID(); + mc2hit.emplace(key, ih); } + } - for (int irfd = mc2rof.maxROF - mc2rof.minROF + 1; irfd--;) { + // LOOP over layers, then ROFRecords within each layer + for (int iLayer = 0; iLayer < nDigitLayers; ++iLayer) { + auto& rofArr = *rofRecordsArr[iLayer]; + const int nROFRec = (int)rofArr.size(); - int irof = mc2rof.rofRecordID + irfd; + o2::dataformats::ConstMCTruthContainer labels; + plabelsArr[iLayer]->copyandflatten(labels); - if (irof >= nROFRec) { - LOG(error) << "ROF=" << irof << " from MC2ROF record is >= N ROFs=" << nROFRec; - } - if (mcEvMin[irof] > imc) { - mcEvMin[irof] = imc; - } - if (mcEvMax[irof] < imc) { - mcEvMax[irof] = imc; - } - } - } // << build min and max MC events used by each ROF + // LOOP on : ROFRecord array + for (unsigned int iROF = 0; iROF < rofArr.size(); ++iROF) { + + const unsigned int rofIndex = rofArr[iROF].getFirstEntry(); + const unsigned int rofNEntries = rofArr[iROF].getNEntries(); - unsigned int rofIndex = 0; - unsigned int rofNEntries = 0; + // LOOP on : digits array + for (unsigned int iDigit = rofIndex; iDigit < rofIndex + rofNEntries; iDigit++) { + if (iDigit % 1000 == 0) + std::cout << "Layer " << iLayer << ": reading digit " << iDigit << " / " << digArr[iLayer]->size() << std::endl; - // LOOP on : ROFRecord array - for (unsigned int iROF = 0; iROF < ROFRecordArrrayRef.size(); iROF++) { + Int_t ix = (*digArr[iLayer])[iDigit].getRow(), iz = (*digArr[iLayer])[iDigit].getColumn(); + Int_t iDetID = (*digArr[iLayer])[iDigit].getChipIndex(); + Int_t layer = gman->getLayer(iDetID); + Int_t disk = gman->getDisk(iDetID); + Int_t subDetID = gman->getSubDetID(iDetID); + Int_t petalCase = gman->getPetalCase(iDetID); + Int_t stave = gman->getStave(iDetID); + Int_t halfstave = gman->getHalfStave(iDetID); - rofIndex = ROFRecordArrrayRef[iROF].getFirstEntry(); - rofNEntries = ROFRecordArrrayRef[iROF].getNEntries(); + Float_t x = 0.f, y = 0.f, z = 0.f; + Float_t x_flat = 0.f, z_flat = 0.f; - // >> read and map MC events contributing to this ROF - for (int im = mcEvMin[iROF]; im <= mcEvMax[iROF]; im++) { + if (disk != -1) { + continue; // skip disks for the moment + } - if (!hitArray[im]) { + if (subDetID != 0) { + seg.detectorToLocal(ix, iz, x, z, subDetID, layer, disk); + } else if (subDetID == 0) { + seg.detectorToLocal(ix, iz, x_flat, z_flat, subDetID, layer, disk); + o2::math_utils::Vector2D xyCurved = seg.flatToCurved(layer, x_flat, 0.); + x = xyCurved.X(); + y = xyCurved.Y(); + z = z_flat; + } - hitTree->SetBranchAddress("TRKHit", &hitArray[im]); - hitTree->GetEntry(im); + o2::math_utils::Point3D locD(x, y, z); // local Digit curved + o2::math_utils::Point3D locDF(-1, -1, -1); // local Digit flat - auto& mc2hit = mc2hitVec[im]; + Int_t chipID = (*digArr[iLayer])[iDigit].getChipIndex(); + auto lab = (labels.getLabels(iDigit))[0]; - for (int ih = hitArray[im]->size(); ih--;) { + int trID = lab.getTrackID(); - const auto& hit = (*hitArray[im])[ih]; - uint64_t key = (uint64_t(hit.GetTrackID()) << 32) + hit.GetDetectorID(); - mc2hit.emplace(key, ih); + if (!lab.isValid()) { // not a noise + continue; } - } - } - // LOOP on : digits array - for (unsigned int iDigit = rofIndex; iDigit < rofIndex + rofNEntries; iDigit++) { - // if (iDigit % 10000 != 0) /// looking only at a small sample - // continue; - - if (iDigit % 1000 == 0) - std::cout << "Reading digit " << iDigit << " / " << digArr->size() << std::endl; - - Int_t ix = (*digArr)[iDigit].getRow(), iz = (*digArr)[iDigit].getColumn(); - Int_t iDetID = (*digArr)[iDigit].getChipIndex(); - Int_t layer = gman->getLayer(iDetID); - Int_t disk = gman->getDisk(iDetID); - Int_t subDetID = gman->getSubDetID(iDetID); - Int_t petalCase = gman->getPetalCase(iDetID); - Int_t stave = gman->getStave(iDetID); - Int_t halfstave = gman->getHalfStave(iDetID); - - Float_t x = 0.f, y = 0.f, z = 0.f; - Float_t x_flat = 0.f, z_flat = 0.f; - - if (disk != -1) { - continue; // skip disks for the moment - } - - if (subDetID != 0) { - seg.detectorToLocal(ix, iz, x, z, subDetID, layer, disk); - } else if (subDetID == 0) { - seg.detectorToLocal(ix, iz, x_flat, z_flat, subDetID, layer, disk); - o2::math_utils::Vector2D xyCurved = seg.flatToCurved(layer, x_flat, 0.); - x = xyCurved.X(); - y = xyCurved.Y(); - z = z_flat; - } - - o2::math_utils::Point3D locD(x, y, z); // local Digit curved - o2::math_utils::Point3D locDF(-1, -1, -1); // local Digit flat - - Int_t chipID = (*digArr)[iDigit].getChipIndex(); - auto lab = (labels.getLabels(iDigit))[0]; - - int trID = lab.getTrackID(); - - if (!lab.isValid()) { // not a noise - continue; - } - - const auto gloD = gman->getMatrixL2G(chipID)(locD); // convert to global - - std::unordered_map* mc2hit = &mc2hitVec[lab.getEventID()]; - - // get MC info - uint64_t key = (uint64_t(trID) << 32) + chipID; - auto hitEntry = mc2hit->find(key); - - if (hitEntry == mc2hit->end()) { - - LOG(error) << "Failed to find MC hit entry for Tr" << trID << " chipID" << chipID; - continue; - } - - ////// HITS - Hit& hit = (*hitArray[lab.getEventID()])[hitEntry->second]; - - auto xyzLocE = gman->getMatrixL2G(chipID) ^ (hit.GetPos()); // inverse conversion from global to local - auto xyzLocS = gman->getMatrixL2G(chipID) ^ (hit.GetPosStart()); - - // Hit local reference: Both VD and MLOT use response-plane interpolation (in flat local frame). - // For VD, transform curved → flat first, then interpolate. - o2::math_utils::Vector3D locH; /// Hit reference (at response plane) - o2::math_utils::Vector3D locHS; /// Hit, start pos - locHS.SetCoordinates(xyzLocS.X(), xyzLocS.Y(), xyzLocS.Z()); - o2::math_utils::Vector3D locHE; /// Hit, end pos - locHE.SetCoordinates(xyzLocE.X(), xyzLocE.Y(), xyzLocE.Z()); - o2::math_utils::Vector3D locHF; - - if (subDetID == 0) { - // VD: Interpolate to VD reference plane in flat frame; apply same r to X and Z - auto flatSta = seg.curvedToFlat(layer, locHS.X(), locHS.Y()); - auto flatEnd = seg.curvedToFlat(layer, locHE.X(), locHE.Y()); - float x0 = flatSta.X(), y0 = flatSta.Y(), z0 = locHS.Z(); - float dltx = flatEnd.X() - x0, dlty = flatEnd.Y() - y0, dltz = locHE.Z() - z0; - float r = (std::abs(dlty) > 1e-9f) ? (yPlaneVD - y0) / dlty : 0.5f; - locH.SetCoordinates(x0 + r * dltx, yPlaneVD, z0 + r * dltz); - } else { - // MLOT: Interpolate to response plane - float x0 = locHS.X(), y0 = locHS.Y(), z0 = locHS.Z(); - float dltx = locHE.X() - x0, dlty = locHE.Y() - y0, dltz = locHE.Z() - z0; - float r = (std::abs(dlty) > 1e-9f) ? (yPlaneMLOT - y0) / dlty : 0.5f; - locH.SetCoordinates(x0 + r * dltx, yPlaneMLOT, z0 + r * dltz); - } - - int row = 0, col = 0; - float xlc = 0., zlc = 0.; - - if (subDetID == 0) { - Float_t x_flat = 0.f, y_flat = 0.f; - // locH is already in flat frame from interpolation above; convert digit to flat for comparison - o2::math_utils::Vector2D xyFlatD = seg.curvedToFlat(layer, locD.X(), locD.Y()); - locDF.SetCoordinates(xyFlatD.X(), xyFlatD.Y(), locD.Z()); - locHF.SetCoordinates(locH.X(), locH.Y(), locH.Z()); // locH already in flat frame - seg.localToDetector(locHF.X(), locHF.Z(), row, col, subDetID, layer, disk); - } - - else { - seg.localToDetector(locH.X(), locH.Z(), row, col, subDetID, layer, disk); - } - - seg.detectorToLocal(row, col, xlc, zlc, subDetID, layer, disk); - - if (subDetID == 0) { - nt->Fill(chipID, /// detector ID - gloD.X(), gloD.Y(), gloD.Z(), /// global position retrieved from the digit: digit (row, col) ->local position -> global potision - ix, iz, /// row and column of the digit - row, col, /// row and col retrieved from the hit: hit global position -> hit local position -> detector position (row, col) - locH.X(), locH.Z(), /// x and z of the hit in the local reference frame: hit global position -> hit local position - xlc, zlc, /// x and z of the hit in the local frame: hit global position -> hit local position -> detector position (row, col) -> local position - locHF.X() - locDF.X(), locHF.Z() - locDF.Z()); /// difference in x and z between the hit and the digit in the local frame - - nt2->Fill(chipID, gloD.Z(), locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// differences between local hit start and hit end positions - } else { - - nt->Fill(chipID, /// detector ID - gloD.X(), gloD.Y(), gloD.Z(), /// global position retrieved from the digit: digit (row, col) ->local position -> global potision - ix, iz, /// row and column of the digit - row, col, /// row and col retrieved from the hit: hit global position -> hit local position -> detector position (row, col) - locH.X(), locH.Z(), /// x and z of the hit in the local reference frame: hit global position -> hit local position - xlc, zlc, /// x and z of the hit in the local frame: hit global position -> hit local position -> detector position (row, col) -> local position - locH.X() - locD.X(), locH.Z() - locD.Z()); /// difference in x and z between the hit and the digit in the local frame - // locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// difference in x and z between the hit and the digit in the local frame - nt2->Fill(chipID, gloD.Z(), locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// differences between local hit start and hit end positions - } - - } // end loop on digits array - - } // end loop on ROFRecords array + const auto gloD = gman->getMatrixL2G(chipID)(locD); // convert to global + + std::unordered_map* mc2hit = &mc2hitVec[lab.getEventID()]; + + // get MC info + uint64_t key = (uint64_t(trID) << 32) + chipID; + auto hitEntry = mc2hit->find(key); + + if (hitEntry == mc2hit->end()) { + LOG(error) << "Failed to find MC hit entry for Tr" << trID << " chipID" << chipID; + continue; + } + + ////// HITS + Hit& hit = (*hitArray[lab.getEventID()])[hitEntry->second]; + + auto xyzLocE = gman->getMatrixL2G(chipID) ^ (hit.GetPos()); // inverse conversion from global to local + auto xyzLocS = gman->getMatrixL2G(chipID) ^ (hit.GetPosStart()); + + // Hit local reference: Both VD and MLOT use response-plane interpolation (in flat local frame). + // For VD, transform curved → flat first, then interpolate. + o2::math_utils::Vector3D locH; /// Hit reference (at response plane) + o2::math_utils::Vector3D locHS; /// Hit, start pos + locHS.SetCoordinates(xyzLocS.X(), xyzLocS.Y(), xyzLocS.Z()); + o2::math_utils::Vector3D locHE; /// Hit, end pos + locHE.SetCoordinates(xyzLocE.X(), xyzLocE.Y(), xyzLocE.Z()); + o2::math_utils::Vector3D locHF; + + if (subDetID == 0) { + // VD: Interpolate to VD reference plane in flat frame; apply same r to X and Z + auto flatSta = seg.curvedToFlat(layer, locHS.X(), locHS.Y()); + auto flatEnd = seg.curvedToFlat(layer, locHE.X(), locHE.Y()); + float x0 = flatSta.X(), y0 = flatSta.Y(), z0 = locHS.Z(); + float dltx = flatEnd.X() - x0, dlty = flatEnd.Y() - y0, dltz = locHE.Z() - z0; + float r = (std::abs(dlty) > 1e-9f) ? (yPlaneVD - y0) / dlty : 0.5f; + locH.SetCoordinates(x0 + r * dltx, yPlaneVD, z0 + r * dltz); + } else { + // MLOT: Interpolate to response plane + float x0 = locHS.X(), y0 = locHS.Y(), z0 = locHS.Z(); + float dltx = locHE.X() - x0, dlty = locHE.Y() - y0, dltz = locHE.Z() - z0; + float r = (std::abs(dlty) > 1e-9f) ? (yPlaneMLOT - y0) / dlty : 0.5f; + locH.SetCoordinates(x0 + r * dltx, yPlaneMLOT, z0 + r * dltz); + } + + int row = 0, col = 0; + float xlc = 0., zlc = 0.; + + if (subDetID == 0) { + Float_t x_flat = 0.f, y_flat = 0.f; + // locH is already in flat frame from interpolation above; convert digit to flat for comparison + o2::math_utils::Vector2D xyFlatD = seg.curvedToFlat(layer, locD.X(), locD.Y()); + locDF.SetCoordinates(xyFlatD.X(), xyFlatD.Y(), locD.Z()); + locHF.SetCoordinates(locH.X(), locH.Y(), locH.Z()); // locH already in flat frame + seg.localToDetector(locHF.X(), locHF.Z(), row, col, subDetID, layer, disk); + } else { + seg.localToDetector(locH.X(), locH.Z(), row, col, subDetID, layer, disk); + } + + seg.detectorToLocal(row, col, xlc, zlc, subDetID, layer, disk); + + if (subDetID == 0) { + nt->Fill(chipID, /// detector ID + gloD.X(), gloD.Y(), gloD.Z(), /// global position retrieved from the digit: digit (row, col) ->local position -> global potision + ix, iz, /// row and column of the digit + row, col, /// row and col retrieved from the hit: hit global position -> hit local position -> detector position (row, col) + locH.X(), locH.Z(), /// x and z of the hit in the local reference frame: hit global position -> hit local position + xlc, zlc, /// x and z of the hit in the local frame: hit global position -> hit local position -> detector position (row, col) -> local position + locHF.X() - locDF.X(), locHF.Z() - locDF.Z()); /// difference in x and z between the hit and the digit in the local frame + nt2->Fill(chipID, gloD.Z(), locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// differences between local hit start and hit end positions + } else { + nt->Fill(chipID, /// detector ID + gloD.X(), gloD.Y(), gloD.Z(), /// global position retrieved from the digit: digit (row, col) ->local position -> global potision + ix, iz, /// row and column of the digit + row, col, /// row and col retrieved from the hit: hit global position -> hit local position -> detector position (row, col) + locH.X(), locH.Z(), /// x and z of the hit in the local reference frame: hit global position -> hit local position + xlc, zlc, /// x and z of the hit in the local frame: hit global position -> hit local position -> detector position (row, col) -> local position + locH.X() - locD.X(), locH.Z() - locD.Z()); /// difference in x and z between the hit and the digit in the local frame + nt2->Fill(chipID, gloD.Z(), locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// differences between local hit start and hit end positions + } + + } // end loop on digits array + + } // end loop on ROFRecords + + } // end loop on layers // digit maps in the xy and yz planes auto canvXY = new TCanvas("canvXY", "", 1600, 2400); diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h index 70518b2ace593..bcd95155f533f 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h @@ -48,7 +48,6 @@ 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; @@ -167,9 +166,7 @@ class Clusterer std::vector& patterns, std::vector& clusterROFs, const ConstDigitTruth* digitLabels = nullptr, - ClusterTruth* clusterLabels = nullptr, - gsl::span digMC2ROFs = {}, - std::vector* clusterMC2ROFs = nullptr); + ClusterTruth* clusterLabels = nullptr); 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 37a148aa78afb..5d68193e5e375 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h @@ -35,9 +35,7 @@ class ClustererACTS : public Clusterer std::vector& patterns, std::vector& clusterROFs, const ConstDigitTruth* digitLabels = nullptr, - ClusterTruth* clusterLabels = nullptr, - gsl::span digMC2ROFs = {}, - std::vector* clusterMC2ROFs = nullptr) override; + ClusterTruth* clusterLabels = nullptr) override; private: }; diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx index bdaa76319c1f2..e0d689e4db5ed 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx @@ -28,9 +28,7 @@ void Clusterer::process(gsl::span digits, std::vector& patterns, std::vector& clusterROFs, const ConstDigitTruth* digitLabels, - ClusterTruth* clusterLabels, - gsl::span digMC2ROFs, - std::vector* clusterMC2ROFs) + ClusterTruth* clusterLabels) { if (!mThread) { mThread = std::make_unique(this); @@ -81,13 +79,6 @@ 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 2dbf56ae610e3..b764fcdd1cd79 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx @@ -162,9 +162,7 @@ void ClustererACTS::process(gsl::span digits, std::vector& patterns, std::vector& clusterROFs, const ConstDigitTruth* digitLabels, - ClusterTruth* clusterLabels, - gsl::span digMC2ROFs, - std::vector* clusterMC2ROFs) + ClusterTruth* clusterLabels) { if (!mThread) { mThread = std::make_unique(this); diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h index 3bb58f21dd33b..d7d1ea28bfcf7 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h @@ -15,9 +15,12 @@ #ifndef ALICEO2_TRK_DIGIPARAMS_H #define ALICEO2_TRK_DIGIPARAMS_H +#include + #include #include "ITSMFTSimulation/AlpideSignalTrapezoid.h" #include "ITSMFTSimulation/AlpideSimResponse.h" +#include "TRKBase/AlmiraParam.h" #include "TRKBase/TRKBaseParam.h" #include "TRKBase/GeometryTGeo.h" @@ -50,27 +53,24 @@ class DigiParams void setNoisePerPixel(float v) { mNoisePerPixel = v; } float getNoisePerPixel() const { return mNoisePerPixel; } - void setContinuous(bool v) { mIsContinuous = v; } - bool isContinuous() const { return mIsContinuous; } - - int getROFrameLengthInBC() const { return mROFrameLengthInBC; } - void setROFrameLengthInBC(int n) { mROFrameLengthInBC = n; } + int getROFrameLengthInBC(int layer) const { return mROFrameLayerLengthInBC[layer]; } + void setROFrameLengthInBC(int n, int layer) { mROFrameLayerLengthInBC[layer] = n; } - void setROFrameLength(float ns); - float getROFrameLength() const { return mROFrameLength; } - float getROFrameLengthInv() const { return mROFrameLengthInv; } + void setROFrameLength(float ns, int layer); + float getROFrameLength(int layer) const { return mROFrameLayerLength[layer]; } + float getROFrameLengthInv(int layer) const { return mROFrameLayerLengthInv[layer]; } - void setStrobeDelay(float ns) { mStrobeDelay = ns; } - float getStrobeDelay() const { return mStrobeDelay; } + void setStrobeDelay(float ns, int layer) { mStrobeLayerDelay[layer] = ns; } + float getStrobeDelay(int layer) const { return mStrobeLayerDelay[layer]; } - void setStrobeLength(float ns) { mStrobeLength = ns; } - float getStrobeLength() const { return mStrobeLength; } + void setStrobeLength(float ns, int layer) { mStrobeLayerLength[layer] = ns; } + float getStrobeLength(int layer) const { return mStrobeLayerLength[layer]; } void setTimeOffset(double sec) { mTimeOffset = sec; } double getTimeOffset() const { return mTimeOffset; } - void setROFrameBiasInBC(int n) { mROFrameBiasInBC = n; } - int getROFrameBiasInBC() const { return mROFrameBiasInBC; } + void setROFrameBiasInBC(int n, int layer) { mROFrameLayerBiasInBC[layer] = n; } + int getROFrameBiasInBC(int layer) const { return mROFrameLayerBiasInBC[layer]; } void setChargeThreshold(int v, float frac2Account = 0.1); void setNSimSteps(int v); @@ -102,14 +102,8 @@ class DigiParams private: static constexpr double infTime = 1e99; - bool mIsContinuous = false; ///< flag for continuous simulation float mNoisePerPixel = 1.e-7; ///< Noise per chip - int mROFrameLengthInBC = 0; ///< ROF length in BC for continuos mode - float mROFrameLength = 0; ///< length of RO frame in ns - float mStrobeDelay = 0.; ///< strobe start (in ns) wrt ROF start - float mStrobeLength = 0; ///< length of the strobe in ns (sig. over threshold checked in this window only) double mTimeOffset = -2 * infTime; ///< time offset (in seconds!) to calculate ROFrame from hit time - int mROFrameBiasInBC = 0; ///< misalignment of the ROF start in BC int mChargeThreshold = 75; ///< charge threshold in Nelectrons int mMinChargeToAccount = 7; ///< minimum charge contribution to account int mNSimSteps = 475; ///< number of steps in response simulation @@ -121,12 +115,18 @@ class DigiParams float mIBVbb = 0.0; ///< back bias absolute value for ITS Inner Barrel (in Volt) float mOBVbb = 0.0; ///< back bias absolute value for ITS Outter Barrel (in Volt) + std::array mROFrameLayerLengthInBC; ///< staggering ROF length in BC for continuous mode per layer + std::array mROFrameLayerBiasInBC; ///< staggering ROF bias in BC for continuous mode per layer + std::array mROFrameLayerLength; ///< staggering ROF length in ns for continuous mode per layer + std::array mStrobeLayerLength; ///< staggering strobe length in ns per layer + std::array mStrobeLayerDelay; ///< staggering strobe delay in ns per layer + o2::itsmft::AlpideSignalTrapezoid mSignalShape; ///< signal timeshape parameterization std::unique_ptr mResponse; //!< pointer on external response // auxiliary precalculated parameters - float mROFrameLengthInv = 0; ///< inverse length of RO frame in ns + std::array mROFrameLayerLengthInv; ///< inverse length of RO frame in ns per layer // ClassDef(DigiParams, 2); }; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h index 362de63fb8cb6..5910fc98134aa 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h @@ -55,18 +55,20 @@ class Digitizer const o2::trk::ChipSimResponse* getChipResponse(int chipID); /// Steer conversion of hits to digits - void process(const std::vector* hits, int evID, int srcID); - void setEventTime(const o2::InteractionTimeRecord& irt); - double getEndTimeOfROFMax() const + void process(const std::vector* hits, int evID, int srcID, int layer); + void setEventTime(const o2::InteractionTimeRecord& irt, int layer); + + void fillOutputContainer(uint32_t maxFrame, int layer); + + void resetROFrameBounds() { - ///< return the time corresponding to end of the last reserved ROFrame : mROFrameMax - return mParams.getROFrameLength() * (mROFrameMax + 1) + mParams.getTimeOffset(); + mROFrameMin = 0; + mROFrameMax = 0; + mNewROFrame = 0; + mIsBeforeFirstRO = false; + mExtraBuff.clear(); } - void setContinuous(bool v) { mParams.setContinuous(v); } - bool isContinuous() const { return mParams.isContinuous(); } - void fillOutputContainer(uint32_t maxFrame = 0xffffffff); - const o2::trk::DigiParams& getDigitParams() const { return mParams; } // provide the common trk::GeometryTGeo to access matrices and segmentation @@ -83,9 +85,9 @@ class Digitizer void setDeadChannelsMap(const o2::itsmft::NoiseMap* mp) { mDeadChanMap = mp; } private: - void processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID); + void processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer); void registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, - uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl); + uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int layer); ExtraDig* getExtraDigBuffer(uint32_t roFrame) { diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx index 66ace4746d399..3fa51afe3ba2b 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx @@ -13,6 +13,7 @@ #include "DetectorsBase/Stack.h" +#include "TRKBase/Specs.h" #include "TRKBase/TRKBaseParam.h" #include "TRKSimulation/Hit.h" #include "TRKSimulation/VDGeometryBuilder.h" @@ -99,7 +100,7 @@ void Detector::configMLOT() case kCylindrical: { const std::vector length{128.35f, 128.35f, 128.35f, 128.35f, 128.35f, 256.7f, 256.7f, 256.7f}; LOGP(warning, "Loading cylindrical configuration for ALICE3 TRK"); - for (int i{0}; i < 8; ++i) { + for (int i{0}; i < constants::ML::nLayers + constants::OT::nLayers; ++i) { std::string name = GeometryTGeo::getTRKLayerPattern() + std::to_string(i); mLayers.push_back(std::make_unique(i, name, rInn[i], length[i], thick, MatBudgetParamMode::Thickness)); } @@ -115,9 +116,9 @@ void Detector::configMLOT() const std::vector stagOffsets{0.f, 0.f, 0.f, 1.17f, 0.89f}; LOGP(warning, "Loading segmented configuration for ALICE3 TRK"); - for (int i{0}; i < 8; ++i) { + for (int i{0}; i < constants::ML::nLayers + constants::OT::nLayers; ++i) { std::string name = GeometryTGeo::getTRKLayerPattern() + std::to_string(i); - if (i < 5) { + if (i < constants::ML::nLayers) { mLayers.push_back(std::make_unique(i, name, rInn[i], stagOffsets[i], tiltAngles[i], nStaves[i], nMods[i], thick, MatBudgetParamMode::Thickness)); } else { mLayers.push_back(std::make_unique(i, name, rInn[i], tiltAngles[i], nStaves[i], nMods[i], thick, MatBudgetParamMode::Thickness)); @@ -202,8 +203,8 @@ void Detector::configFromFile(std::string fileName) // Default mode is Thickness MatBudgetParamMode mode = MatBudgetParamMode::Thickness; - if (layerCount < 5) { - // ML layers (0 to 4) require stagOffset (index 5) + if (layerCount < constants::ML::nLayers) { + // ML layers require stagOffset (index 5) if (tmpBuff.size() < 6) { LOGP(fatal, "Invalid configuration for ML layer {}: stagOffset is missing.", layerCount); } @@ -215,7 +216,7 @@ void Detector::configFromFile(std::string fileName) mLayers.push_back(std::make_unique(layerCount, name, rInn, stagOffset, tiltAngle, nStaves, nMods, thick, mode)); } else { - // OT layers (5+) do NOT have stagOffset. The optional mode is at index 5. + // OT layers do NOT have stagOffset. The optional mode is at index 5. if (tmpBuff.size() >= 6) { mode = static_cast(static_cast(tmpBuff[5])); } diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx index d5d47b3658b04..3558a6a87ce71 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx @@ -25,12 +25,12 @@ DigiParams::DigiParams() setNSimSteps(mNSimSteps); } -void DigiParams::setROFrameLength(float lNS) +void DigiParams::setROFrameLength(float lNS, int layer) { // set ROFrame length in nanosecongs - mROFrameLength = lNS; - assert(mROFrameLength > 1.); - mROFrameLengthInv = 1. / mROFrameLength; + mROFrameLayerLength[layer] = lNS; + assert(mROFrameLayerLength[layer] > 1.); + mROFrameLayerLengthInv[layer] = 1. / mROFrameLayerLength[layer]; } void DigiParams::setNSimSteps(int v) @@ -59,10 +59,6 @@ void DigiParams::print() const { // print settings printf("TRK digitization params:\n"); - printf("Continuous readout : %s\n", mIsContinuous ? "ON" : "OFF"); - printf("Readout Frame Length(ns) : %f\n", mROFrameLength); - printf("Strobe delay (ns) : %f\n", mStrobeDelay); - printf("Strobe length (ns) : %f\n", mStrobeLength); printf("Threshold (N electrons) : %d\n", mChargeThreshold); printf("Min N electrons to account : %d\n", mMinChargeToAccount); printf("Number of charge sharing steps : %d\n", mNSimSteps); diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx index 31b9a25b7e5f8..890c272fefbc2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx @@ -23,6 +23,7 @@ #include #include #include +#include #include // for LOG using o2::itsmft::Digit; @@ -113,14 +114,13 @@ const o2::trk::ChipSimResponse* Digitizer::getChipResponse(int chipID) }; //_______________________________________________________________________ -void Digitizer::process(const std::vector* hits, int evID, int srcID) +void Digitizer::process(const std::vector* hits, int evID, int srcID, int layer) { // digitize single event, the time must have been set beforehand LOG(info) << " Digitizing " << mGeometry->getName() << " (ID: " << mGeometry->getDetID() << ") hits of event " << evID << " from source " << srcID << " at time " << mEventTime.getTimeNS() << " ROFrame = " << mNewROFrame - << " cont.mode: " << isContinuous() << " Min/Max ROFrames " << mROFrameMin << "/" << mROFrameMax; std::cout << "Printing segmentation info: " << std::endl; @@ -128,7 +128,7 @@ void Digitizer::process(const std::vector* hits, int evID, int srcID) // // is there something to flush ? if (mNewROFrame > mROFrameMin) { - fillOutputContainer(mNewROFrame - 1); // flush out all frames preceding the new one + fillOutputContainer(mNewROFrame - 1, layer); // flush out all frames preceding the new one } int nHits = hits->size(); @@ -140,66 +140,55 @@ void Digitizer::process(const std::vector* hits, int evID, int srcID) return (*hits)[lhs].GetDetectorID() < (*hits)[rhs].GetDetectorID(); }); LOG(info) << "Processing " << nHits << " hits"; - for (int i : hitIdx) { - processHit((*hits)[i], mROFrameMax, evID, srcID); - } - - // in the triggered mode store digits after every MC event - // TODO: in the real triggered mode this will not be needed, this is actually for the - // single event processing only - if (!mParams.isContinuous()) { - fillOutputContainer(mROFrameMax); + for (int i : hitIdx | std::views::filter([&](int idx) { + if (layer < 0) { + return true; + } + return mGeometry->getLayerTRK((*hits)[idx].GetDetectorID()) == layer; + })) { + processHit((*hits)[i], mROFrameMax, evID, srcID, layer); } } //_______________________________________________________________________ -void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt) +void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) { LOG(info) << "Setting event time to " << irt.getTimeNS() << " ns after orbit 0 bc 0"; // assign event time in ns mEventTime = irt; - if (!mParams.isContinuous()) { - mROFrameMin = 0; // in triggered mode reset the frame counters - mROFrameMax = 0; - } // RO frame corresponding to provided time mCollisionTimeWrtROF = mEventTime.timeInBCNS; // in triggered mode the ROF starts at BC (is there a delay?) - if (mParams.isContinuous()) { - auto nbc = mEventTime.differenceInBC(mIRFirstSampledTF); - - if (mCollisionTimeWrtROF < 0 && nbc > 0) { - nbc--; - } - - if (nbc < 0) { - mNewROFrame = 0; - mIsBeforeFirstRO = true; - } else { - mNewROFrame = nbc / mParams.getROFrameLengthInBC(); - mIsBeforeFirstRO = false; - } + auto nbc = mEventTime.differenceInBC(mIRFirstSampledTF); - LOG(debug) << " NewROFrame " << mNewROFrame << " = " << nbc << "/" << mParams.getROFrameLengthInBC() << " (nbc/mParams.getROFrameLengthInBC()"; + if (mCollisionTimeWrtROF < 0 && nbc > 0) { + nbc--; + } - // in continuous mode depends on starts of periodic readout frame - mCollisionTimeWrtROF += (nbc % mParams.getROFrameLengthInBC()) * o2::constants::lhc::LHCBunchSpacingNS; - } else { + if (nbc < 0) { mNewROFrame = 0; + mIsBeforeFirstRO = true; + } else { + mNewROFrame = nbc / mParams.getROFrameLengthInBC(layer); mIsBeforeFirstRO = false; } + LOG(debug) << " NewROFrame " << mNewROFrame << " = " << nbc << "/" << mParams.getROFrameLengthInBC(layer) << " (nbc/mParams.getROFrameLengthInBC()"; + + // in continuous mode depends on starts of periodic readout frame + mCollisionTimeWrtROF += (nbc % mParams.getROFrameLengthInBC(layer)) * o2::constants::lhc::LHCBunchSpacingNS; + if (mNewROFrame < mROFrameMin) { LOG(error) << "New ROFrame " << mNewROFrame << " (" << irt << ") precedes currently cashed " << mROFrameMin; throw std::runtime_error("deduced ROFrame precedes already processed one"); } - if (mParams.isContinuous() && mROFrameMax < mNewROFrame) { + if (mROFrameMax < mNewROFrame) { mROFrameMax = mNewROFrame - 1; // all frames up to this are finished } } //_______________________________________________________________________ -void Digitizer::fillOutputContainer(uint32_t frameLast) +void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) { // // fill output with digits from min.cached up to requested frame, generating the noise beforehand if (frameLast > mROFrameMax) { @@ -219,7 +208,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast) auto& extra = *(mExtraBuff.front().get()); for (auto& chip : mChips) { - if (chip.isDisabled()) { + if (chip.isDisabled() || (layer >= 0 && mGeometry->getLayerTRK(chip.getChipIndex()) != layer)) { continue; } chip.addNoise(mROFrameMin, mROFrameMin, &mParams, mGeometry->getSubDetID(chip.getChipIndex()), mGeometry->getLayer(chip.getChipIndex())); /// TODO: add noise @@ -251,11 +240,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast) } // finalize ROF record rcROF.setNEntries(mDigits->size() - rcROF.getFirstEntry()); // number of digits - if (isContinuous()) { - rcROF.getBCData().setFromLong(mIRFirstSampledTF.toLong() + mROFrameMin * mParams.getROFrameLengthInBC()); - } else { - rcROF.getBCData() = mEventTime; // RSTODO do we need to add trigger delay? - } + rcROF.getBCData().setFromLong(mIRFirstSampledTF.toLong() + mROFrameMin * mParams.getROFrameLengthInBC(layer)); if (mROFRecords) { mROFRecords->push_back(rcROF); } @@ -267,7 +252,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast) } //_______________________________________________________________________ -void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID) +void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer) { int chipID = hit.GetDetectorID(); //// the chip ID at the moment is not referred to the chip but to a wider detector element (e.g. quarter of layer or disk in VD, stave in ML, half stave in OT) int subDetID = mGeometry->getSubDetID(chipID); @@ -297,9 +282,7 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i } return; } - if (isContinuous()) { - timeInROF += mCollisionTimeWrtROF; - } + timeInROF += mCollisionTimeWrtROF; if (mIsBeforeFirstRO && timeInROF < 0) { // disregard this hit because it comes from an event byefore readout starts and it does not effect this RO LOG(debug) << "Ignoring hit with timeInROF = " << timeInROF; @@ -312,9 +295,9 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i } float tTot = mParams.getSignalShape().getMaxDuration(); // frame of the hit signal start wrt event ROFrame - int roFrameRel = int(timeInROF * mParams.getROFrameLengthInv()); + int roFrameRel = int(timeInROF * mParams.getROFrameLengthInv(rofLayer)); // frame of the hit signal end wrt event ROFrame: in the triggered mode we read just 1 frame - uint32_t roFrameRelMax = mParams.isContinuous() ? (timeInROF + tTot) * mParams.getROFrameLengthInv() : roFrameRel; + uint32_t roFrameRelMax = (timeInROF + tTot) * mParams.getROFrameLengthInv(rofLayer); int nFrames = roFrameRelMax + 1 - roFrameRel; uint32_t roFrameMax = mNewROFrame + roFrameRelMax; if (roFrameMax > maxFr) { @@ -509,25 +492,25 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i if (mDeadChanMap && mDeadChanMap->isNoisy(chipID, rowIS, colIS)) { continue; } - registerDigits(chip, roFrameAbs, timeInROF, nFrames, rowIS, colIS, nEle, lbl); + registerDigits(chip, roFrameAbs, timeInROF, nFrames, rowIS, colIS, nEle, lbl, rofLayer); } } } //________________________________________________________________________________ void Digitizer::registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, - uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl) + uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int layer) { // Register digits for given pixel, accounting for the possible signal contribution to // multiple ROFrame. The signal starts at time tInROF wrt the start of provided roFrame // In every ROFrame we check the collected signal during strobe LOG(debug) << "Registering digits for chip " << chip.getChipIndex() << " at ROFrame " << roFrame << " row " << row << " col " << col << " nEle " << nEle << " label " << lbl; - float tStrobe = mParams.getStrobeDelay() - tInROF; // strobe start wrt signal start - for (int i = 0; i < nROF; i++) { // loop on all the ROFs occupied by the same signal to calculate the charge accumulated in that ROF + float tStrobe = mParams.getStrobeDelay(layer) - tInROF; // strobe start wrt signal start + for (int i = 0; i < nROF; i++) { // loop on all the ROFs occupied by the same signal to calculate the charge accumulated in that ROF uint32_t roFr = roFrame + i; - int nEleROF = mParams.getSignalShape().getCollectedCharge(nEle, tStrobe, tStrobe + mParams.getStrobeLength()); - tStrobe += mParams.getROFrameLength(); // for the next ROF + int nEleROF = mParams.getSignalShape().getCollectedCharge(nEle, tStrobe, tStrobe + mParams.getStrobeLength(layer)); + tStrobe += mParams.getROFrameLength(layer); // for the next ROF // discard too small contributions, they have no chance to produce a digit if (nEleROF < mParams.getMinChargeToAccount()) { /// use threshold instead? diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h index 18cc6d245025a..9d072e85d574a 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h @@ -14,6 +14,7 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" +#include "TRKBase/AlmiraParam.h" #include "TRKReconstruction/Clusterer.h" #ifdef O2_WITH_ACTS #include "TRKReconstruction/ClustererACTS.h" @@ -30,6 +31,7 @@ class ClustererDPL : public o2::framework::Task void run(o2::framework::ProcessingContext& pc) final; private: + static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; bool mUseMC = true; int mNThreads = 1; o2::trk::Clusterer mClusterer; diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h index 2a0acd792f4a9..92b64e0815cfb 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h @@ -12,16 +12,20 @@ #ifndef O2_TRK_DIGITREADER #define O2_TRK_DIGITREADER +#include + #include "TFile.h" #include "TTree.h" #include "DataFormatsITSMFT/Digit.h" #include "DataFormatsITSMFT/GBTCalibData.h" #include "DataFormatsITSMFT/ROFRecord.h" +#include "SimulationDataFormat/IOMCTruthContainerView.h" #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" #include "Headers/DataHeader.h" #include "DataFormatsITSMFT/ROFRecord.h" #include "DetectorsCommonDataFormats/DetID.h" +#include "TRKBase/AlmiraParam.h" using namespace o2::framework; @@ -41,11 +45,16 @@ class DigitReader : public Task protected: void connectTree(const std::string& filename); + template + void setBranchAddress(const std::string& base, Ptr& addr, int layer = -1); + std::string getBranchName(const std::string& base, int index) const; + + static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; - std::vector mDigits, *mDigitsPtr = &mDigits; + std::vector*> mDigits{nullptr}; std::vector mCalib, *mCalibPtr = &mCalib; - std::vector mDigROFRec, *mDigROFRecPtr = &mDigROFRec; - std::vector mDigMC2ROFs, *mDigMC2ROFsPtr = &mDigMC2ROFs; + std::vector*> mDigROFRec{nullptr}; + std::vector mPLabels{nullptr}; o2::header::DataOrigin mOrigin = o2::header::gDataOriginInvalid; @@ -64,7 +73,6 @@ class DigitReader : public Task std::string mCalibBranchName = "Calib"; std::string mDigtMCTruthBranchName = "DigitMCTruth"; - std::string mDigtMC2ROFBranchName = "DigitMC2ROF"; }; class TRKDigitReader : public DigitReader diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx index bc3a75c646198..863915bac0572 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx @@ -11,9 +11,16 @@ /// @file ClusterWriterSpec.cxx +#include +#include +#include #include +#include #include "TRKWorkflow/ClusterWriterSpec.h" +#include "Framework/ConcreteDataMatcher.h" +#include "Framework/DataRef.h" +#include "TRKBase/AlmiraParam.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" #include "DataFormatsTRK/Cluster.h" #include "DataFormatsTRK/ROFRecord.h" @@ -35,31 +42,68 @@ using ROFRecLblType = std::vector; DataProcessorSpec getClusterWriterSpec(bool useMC) { - auto clustersSize = std::make_shared(0); - auto clustersSizeGetter = [clustersSize](ClustersType const& clusters) { - *clustersSize = clusters.size(); + static constexpr o2::header::DataOrigin Origin{o2::header::gDataOriginTRK}; + static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; + const auto detName = Origin.as(); + + auto compClusterSizes = std::make_shared>(nLayers, 0); + auto compClustersSizeGetter = [compClusterSizes](ClustersType const& compClusters, DataRef const& ref) { + auto const* dh = DataRefUtils::getHeader(ref); + (*compClusterSizes)[dh->subSpecification] = compClusters.size(); + }; + auto logger = [detName, compClusterSizes](ROFrameType const& rofs, DataRef const& ref) { + auto const* dh = DataRefUtils::getHeader(ref); + const auto i = dh->subSpecification; + LOG(info) << detName << "ClusterWriter on layer " << i + << " pulled " << (*compClusterSizes)[i] << " clusters, in " << rofs.size() << " RO frames"; }; - auto logger = [clustersSize](ROFrameType const& rofs) { - LOG(info) << "TRKClusterWriter pulled " << *clustersSize << " clusters, in " << rofs.size() << " RO frames"; + auto getIndex = [](DataRef const& ref) -> size_t { + auto const* dh = DataRefUtils::getHeader(ref); + return static_cast(dh->subSpecification); }; + auto getName = [](std::string base, size_t index) -> std::string { + return base + "_" + std::to_string(index); + }; + auto detNameLC = detName; + std::transform(detNameLC.begin(), detNameLC.end(), detNameLC.begin(), [](unsigned char c) { return std::tolower(c); }); + + std::vector vecInpSpecClus, vecInpSpecPatt, vecInpSpecROF, vecInpSpecLbl; + vecInpSpecClus.reserve(nLayers); + vecInpSpecPatt.reserve(nLayers); + vecInpSpecROF.reserve(nLayers); + vecInpSpecLbl.reserve(nLayers); + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + vecInpSpecClus.emplace_back(getName("compclus", iLayer), Origin, "COMPCLUSTERS", iLayer); + vecInpSpecPatt.emplace_back(getName("patterns", iLayer), Origin, "PATTERNS", iLayer); + vecInpSpecROF.emplace_back(getName("ROframes", iLayer), Origin, "CLUSTERSROF", iLayer); + vecInpSpecLbl.emplace_back(getName("labels", iLayer), Origin, "CLUSTERSMCTR", iLayer); + } - return MakeRootTreeWriterSpec("trk-cluster-writer", + return MakeRootTreeWriterSpec(std::format("{}-cluster-writer", detNameLC).c_str(), "o2clus_trk.root", - MakeRootTreeWriterSpec::TreeAttributes{"o2sim", "Tree with TRK clusters"}, - BranchDefinition{InputSpec{"compclus", "TRK", "COMPCLUSTERS", 0}, - "TRKClusterComp", - clustersSizeGetter}, - BranchDefinition{InputSpec{"patterns", "TRK", "PATTERNS", 0}, - "TRKClusterPatt"}, - BranchDefinition{InputSpec{"ROframes", "TRK", "CLUSTERSROF", 0}, - "TRKClustersROF", - logger}, - BranchDefinition{InputSpec{"labels", "TRK", "CLUSTERSMCTR", 0}, - "TRKClusterMCTruth", - (useMC ? 1 : 0)}, - BranchDefinition{InputSpec{"MC2ROframes", "TRK", "CLUSTERSMC2ROF", 0}, - "TRKClustersMC2ROF", - (useMC ? 1 : 0)})(); + MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = "Tree with TRK clusters"}, + BranchDefinition{vecInpSpecClus, + "TRKClusterComp", "compact-cluster-branch", + nLayers, + compClustersSizeGetter, + getIndex, + getName}, + BranchDefinition{vecInpSpecPatt, + "TRKClusterPatt", "cluster-pattern-branch", + nLayers, + getIndex, + getName}, + BranchDefinition{vecInpSpecROF, + "TRKClustersROF", "cluster-rof-branch", + nLayers, + logger, + getIndex, + getName}, + BranchDefinition{vecInpSpecLbl, + "TRKClusterMCTruth", "cluster-label-branch", + (useMC ? nLayers : 0), + getIndex, + getName})(); } } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx index 5d9ac463b3f54..f91262e021a55 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx @@ -17,6 +17,8 @@ #include "Framework/Logger.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" +#include + namespace o2::trk { @@ -30,82 +32,84 @@ void ClustererDPL::init(o2::framework::InitContext& ic) void ClustererDPL::run(o2::framework::ProcessingContext& pc) { - auto digits = pc.inputs().get>("digits"); - auto rofs = pc.inputs().get>("ROframes"); + o2::base::GeometryManager::loadGeometry("sgn_geometry.root", false, true); - gsl::span mc2rofs; - gsl::span labelbuffer; - if (mUseMC) { - labelbuffer = pc.inputs().get>("labels"); - mc2rofs = pc.inputs().get>("MC2ROframes"); - } - o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); + uint64_t totalClusters = 0; + for (int iLayer = 0; iLayer < mLayers; ++iLayer) { + auto digits = pc.inputs().get>(std::format("digits_{}", iLayer)); + auto rofs = pc.inputs().get>(std::format("ROframes_{}", iLayer)); - std::vector clusters; - std::vector patterns; - std::vector clusterROFs; - std::unique_ptr> clusterLabels; - std::vector clusterMC2ROFs; - if (mUseMC) { - clusterLabels = std::make_unique>(); - } - o2::base::GeometryManager::loadGeometry("o2sim_geometry.root", false, true); + gsl::span labelbuffer; + if (mUseMC) { + labelbuffer = pc.inputs().get>(std::format("labels_{}", iLayer)); + } + o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); + + std::vector clusters; + std::vector patterns; + std::vector clusterROFs; + std::unique_ptr> clusterLabels; + if (mUseMC) { + clusterLabels = std::make_unique>(); + } #ifdef O2_WITH_ACTS - if (mUseACTS) { - LOG(info) << "Running TRKClusterer with ACTS"; - mClustererACTS.process(digits, - rofs, - clusters, - patterns, - clusterROFs, - mUseMC ? &labels : nullptr, - clusterLabels.get(), - mc2rofs, - mUseMC ? &clusterMC2ROFs : nullptr); - } else + if (mUseACTS) { + LOG(info) << "Running TRKClusterer with ACTS on layer " << iLayer; + mClustererACTS.process(digits, + rofs, + clusters, + patterns, + clusterROFs, + mUseMC ? &labels : nullptr, + clusterLabels.get()); + } else #endif - { - LOG(info) << "Running TRKClusterer"; - mClusterer.process(digits, - rofs, - clusters, - patterns, - clusterROFs, - mUseMC ? &labels : nullptr, - clusterLabels.get(), - mc2rofs, - mUseMC ? &clusterMC2ROFs : nullptr); - } - - pc.outputs().snapshot(o2::framework::Output{"TRK", "COMPCLUSTERS", 0}, clusters); - pc.outputs().snapshot(o2::framework::Output{"TRK", "PATTERNS", 0}, patterns); - pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSROF", 0}, clusterROFs); + { + LOG(info) << "Running TRKClusterer on layer " << iLayer; + mClusterer.process(digits, + rofs, + clusters, + patterns, + clusterROFs, + mUseMC ? &labels : nullptr, + clusterLabels.get()); + } - if (mUseMC) { - pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSMCTR", 0}, *clusterLabels); - pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSMC2ROF", 0}, clusterMC2ROFs); + const auto subspec = static_cast(iLayer); + pc.outputs().snapshot(o2::framework::Output{"TRK", "COMPCLUSTERS", subspec}, clusters); + pc.outputs().snapshot(o2::framework::Output{"TRK", "PATTERNS", subspec}, patterns); + pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSROF", subspec}, clusterROFs); + if (mUseMC) { + pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSMCTR", subspec}, *clusterLabels); + } + totalClusters += clusters.size(); + LOGP(info, "TRKClusterer layer {} pushed {} clusters in {} ROFs", iLayer, clusters.size(), clusterROFs.size()); } - LOGP(info, "TRKClusterer pushed {} clusters in {} ROFs", clusters.size(), clusterROFs.size()); + LOGP(info, "TRKClusterer produced {} clusters", totalClusters); } o2::framework::DataProcessorSpec getClustererSpec(bool useMC) { + static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; std::vector inputs; - inputs.emplace_back("digits", "TRK", "DIGITS", 0, o2::framework::Lifetime::Timeframe); - inputs.emplace_back("ROframes", "TRK", "DIGITSROF", 0, o2::framework::Lifetime::Timeframe); + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + inputs.emplace_back(std::format("digits_{}", iLayer), "TRK", "DIGITS", iLayer, o2::framework::Lifetime::Timeframe); + inputs.emplace_back(std::format("ROframes_{}", iLayer), "TRK", "DIGITSROF", iLayer, o2::framework::Lifetime::Timeframe); + if (useMC) { + inputs.emplace_back(std::format("labels_{}", iLayer), "TRK", "DIGITSMCTR", iLayer, o2::framework::Lifetime::Timeframe); + } + } std::vector outputs; - outputs.emplace_back("TRK", "COMPCLUSTERS", 0, o2::framework::Lifetime::Timeframe); - outputs.emplace_back("TRK", "PATTERNS", 0, o2::framework::Lifetime::Timeframe); - outputs.emplace_back("TRK", "CLUSTERSROF", 0, o2::framework::Lifetime::Timeframe); - - if (useMC) { - inputs.emplace_back("labels", "TRK", "DIGITSMCTR", 0, o2::framework::Lifetime::Timeframe); - inputs.emplace_back("MC2ROframes", "TRK", "DIGITSMC2ROF", 0, o2::framework::Lifetime::Timeframe); - outputs.emplace_back("TRK", "CLUSTERSMCTR", 0, o2::framework::Lifetime::Timeframe); - outputs.emplace_back("TRK", "CLUSTERSMC2ROF", 0, o2::framework::Lifetime::Timeframe); + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + outputs.emplace_back("TRK", "COMPCLUSTERS", iLayer, o2::framework::Lifetime::Timeframe); + outputs.emplace_back("TRK", "PATTERNS", iLayer, o2::framework::Lifetime::Timeframe); + outputs.emplace_back("TRK", "CLUSTERSROF", iLayer, o2::framework::Lifetime::Timeframe); + if (useMC) { + outputs.emplace_back("TRK", "CLUSTERSMCTR", iLayer, o2::framework::Lifetime::Timeframe); + } } return o2::framework::DataProcessorSpec{ diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx index 09bb1f12a48e4..ec2b6d4d66192 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx @@ -36,12 +36,15 @@ DigitReader::DigitReader(o2::detectors::DetID id, bool useMC, bool useCalib) mDetNameLC = mDetName = id.getName(); mDigTreeName = "o2sim"; + mDigits.resize(mLayers, nullptr); + mDigROFRec.resize(mLayers, nullptr); + mPLabels.resize(mLayers, nullptr); + mDigitBranchName = mDetName + mDigitBranchName; mDigROFBranchName = mDetName + mDigROFBranchName; mCalibBranchName = mDetName + mCalibBranchName; mDigtMCTruthBranchName = mDetName + mDigtMCTruthBranchName; - mDigtMC2ROFBranchName = mDetName + mDigtMC2ROFBranchName; mUseMC = useMC; mUseCalib = useCalib; @@ -58,30 +61,27 @@ void DigitReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; assert(ent < mTree->GetEntries()); // this should not happen + mTree->GetEntry(ent); + + for (int iLayer = 0; iLayer < mLayers; ++iLayer) { + LOG(info) << mDetName << "DigitReader on layer " << iLayer << " pushes " << mDigROFRec[iLayer]->size() << " ROFRecords, " + << mDigits[iLayer]->size() << " digits at entry " << ent; - o2::dataformats::IOMCTruthContainerView* plabels = nullptr; - if (mUseMC) { - mTree->SetBranchAddress(mDigtMCTruthBranchName.c_str(), &plabels); + pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", static_cast(iLayer)}, *mDigROFRec[iLayer]); + pc.outputs().snapshot(Output{mOrigin, "DIGITS", static_cast(iLayer)}, *mDigits[iLayer]); + + if (mUseMC) { + auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", static_cast(iLayer)}); + mPLabels[iLayer]->copyandflatten(sharedlabels); + delete mPLabels[iLayer]; + mPLabels[iLayer] = nullptr; + } } - mTree->GetEntry(ent); - LOG(info) << mDetName << "DigitReader pushes " << mDigROFRec.size() << " ROFRecords, " - << mDigits.size() << " digits at entry " << ent; - // This is a very ugly way of providing DataDescription, which anyway does not need to contain detector name. - // To be fixed once the names-definition class is ready - pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", 0}, mDigROFRec); - pc.outputs().snapshot(Output{mOrigin, "DIGITS", 0}, mDigits); if (mUseCalib) { pc.outputs().snapshot(Output{mOrigin, "GBTCALIB", 0}, mCalib); } - if (mUseMC) { - auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", 0}); - plabels->copyandflatten(sharedlabels); - delete plabels; - pc.outputs().snapshot(Output{mOrigin, "DIGITSMC2ROF", 0}, mDigMC2ROFs); - } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); @@ -96,35 +96,59 @@ void DigitReader::connectTree(const std::string& filename) mTree.reset((TTree*)mFile->Get(mDigTreeName.c_str())); assert(mTree); - mTree->SetBranchAddress(mDigROFBranchName.c_str(), &mDigROFRecPtr); - mTree->SetBranchAddress(mDigitBranchName.c_str(), &mDigitsPtr); + for (int iLayer = 0; iLayer < mLayers; ++iLayer) { + setBranchAddress(mDigROFBranchName, mDigROFRec[iLayer], iLayer); + setBranchAddress(mDigitBranchName, mDigits[iLayer], iLayer); + if (mUseMC) { + const auto mctruthBranch = getBranchName(mDigtMCTruthBranchName, iLayer); + if (!mTree->GetBranch(mctruthBranch.c_str())) { + throw std::runtime_error("MC data requested but missing branch(es) at layer " + std::to_string(iLayer) + + ": " + mctruthBranch); + } + setBranchAddress(mDigtMCTruthBranchName, mPLabels[iLayer], iLayer); + } + } + if (mUseCalib) { if (!mTree->GetBranch(mCalibBranchName.c_str())) { throw std::runtime_error("GBT calibration data requested but not found in the tree"); } - mTree->SetBranchAddress(mCalibBranchName.c_str(), &mCalibPtr); - } - if (mUseMC) { - if (!mTree->GetBranch(mDigtMC2ROFBranchName.c_str()) || !mTree->GetBranch(mDigtMCTruthBranchName.c_str())) { - throw std::runtime_error("MC data requested but not found in the tree"); - } - mTree->SetBranchAddress(mDigtMC2ROFBranchName.c_str(), &mDigMC2ROFsPtr); + setBranchAddress(mCalibBranchName, mCalibPtr); } LOG(info) << "Loaded tree from " << filename << " with " << mTree->GetEntries() << " entries"; } +std::string DigitReader::getBranchName(const std::string& base, int index) const +{ + if (index >= 0) { + return base + "_" + std::to_string(index); + } + return base; +} + +template +void DigitReader::setBranchAddress(const std::string& base, Ptr& addr, int layer) +{ + const auto name = getBranchName(base, layer); + if (Int_t ret = mTree->SetBranchAddress(name.c_str(), &addr); ret != 0) { + LOGP(fatal, "failed to set branch address for {} ret={}", name, ret); + } +} + DataProcessorSpec getTRKDigitReaderSpec(bool useMC, bool useCalib, std::string defname) { + static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; std::vector outputSpec; - outputSpec.emplace_back("TRK", "DIGITS", 0, Lifetime::Timeframe); - outputSpec.emplace_back("TRK", "DIGITSROF", 0, Lifetime::Timeframe); + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + outputSpec.emplace_back("TRK", "DIGITS", iLayer, Lifetime::Timeframe); + outputSpec.emplace_back("TRK", "DIGITSROF", iLayer, Lifetime::Timeframe); + if (useMC) { + outputSpec.emplace_back("TRK", "DIGITSMCTR", iLayer, Lifetime::Timeframe); + } + } if (useCalib) { outputSpec.emplace_back("TRK", "GBTCALIB", 0, Lifetime::Timeframe); } - if (useMC) { - outputSpec.emplace_back("TRK", "DIGITSMCTR", 0, Lifetime::Timeframe); - outputSpec.emplace_back("TRK", "DIGITSMC2ROF", 0, Lifetime::Timeframe); - } return DataProcessorSpec{ "trk-digit-reader", diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx index 2a743551adddb..591b084aee3ba 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx @@ -9,9 +9,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// @brief Processor spec for a ROOT file writer for ITSMFT digits +/// @brief Processor spec for a ROOT file writer for TRK digits (per-layer) #include "TRKWorkflow/DigitWriterSpec.h" +#include "Framework/ConcreteDataMatcher.h" +#include "Framework/DataRef.h" +#include "TRKBase/AlmiraParam.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" #include "DataFormatsITSMFT/Digit.h" #include "DataFormatsITSMFT/GBTCalibData.h" @@ -24,6 +27,7 @@ #include #include #include +#include using namespace o2::framework; using SubSpecificationType = o2::framework::DataAllocator::SubSpecificationType; @@ -37,16 +41,22 @@ template using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; using MCCont = o2::dataformats::ConstMCTruthContainer; -/// create the processor spec -/// describing a processor receiving digits for ITS/MFT and writing them to file -DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib, o2::header::DataOrigin detOrig, o2::detectors::DetID detId) +DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) { - std::string detStr = o2::detectors::DetID::getName(detId); - std::string detStrL = dec ? "o2_" : ""; // for decoded digits prepend by o2 - detStrL += detStr; - std::transform(detStrL.begin(), detStrL.end(), detStrL.begin(), ::tolower); - auto logger = [](std::vector const& inDigits) { - LOG(info) << "RECEIVED DIGITS SIZE " << inDigits.size(); + static constexpr o2::header::DataOrigin Origin = o2::header::gDataOriginTRK; + const int mLayers = o2::trk::AlmiraParam::kNLayers; + std::string detStr = "TRK"; + std::string detStrL = dec ? "o2_trk" : "trk"; + + auto digitSizes = std::make_shared>(mLayers, 0); + auto digitSizeGetter = [digitSizes](std::vector const& inDigits, DataRef const& ref) { + auto const* dh = DataRefUtils::getHeader(ref); + (*digitSizes)[dh->subSpecification] = inDigits.size(); + }; + auto rofSizes = std::make_shared>(mLayers, 0); + auto rofSizeGetter = [rofSizes](std::vector const& inROFs, DataRef const& ref) { + auto const* dh = DataRefUtils::getHeader(ref); + (*rofSizes)[dh->subSpecification] = inROFs.size(); }; // the callback to be set as hook for custom action when the writer is closed @@ -61,16 +71,18 @@ DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib, o2::hea nent = n; } outputtree->SetEntries(nent); - outputtree->Write("", TObject::kOverwrite); + outputfile->Write("", TObject::kOverwrite); outputfile->Close(); }; // handler for labels - // This is necessary since we can't store the original label buffer in a ROOT entry -- as is -- if it exceeds a certain size. - // We therefore convert it to a special split class. - auto fillLabels = [](TBranch& branch, std::vector const& labelbuffer, DataRef const& /*ref*/) { + auto fillLabels = [detStr, digitSizes, rofSizes](TBranch& branch, std::vector const& labelbuffer, DataRef const& ref) { o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); - LOG(info) << "WRITING " << labels.getNElements() << " LABELS "; + auto const* dh = DataRefUtils::getHeader(ref); + auto layer = static_cast(dh->subSpecification); + LOG(info) << detStr << ": WRITING " << labels.getNElements() << " LABELS" + << std::format(" FOR LAYER {}", layer) << " WITH " << (*digitSizes)[layer] + << " DIGITS IN " << (*rofSizes)[layer] << " ROFS"; o2::dataformats::IOMCTruthContainerView outputcontainer; auto ptr = &outputcontainer; @@ -80,30 +92,49 @@ DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib, o2::hea br->ResetAddress(); }; - return MakeRootTreeWriterSpec((detStr + "DigitWriter" + (dec ? "_dec" : "")).c_str(), + auto getIndex = [](DataRef const& ref) -> size_t { + auto const* dh = DataRefUtils::getHeader(ref); + return static_cast(dh->subSpecification); + }; + auto getName = [](std::string base, size_t index) -> std::string { + return base + "_" + std::to_string(index); + }; + + std::vector vecInpSpecDig, vecInpSpecROF, vecInpSpecLbl; + vecInpSpecDig.reserve(mLayers); + vecInpSpecROF.reserve(mLayers); + vecInpSpecLbl.reserve(mLayers); + for (int iLayer = 0; iLayer < mLayers; iLayer++) { + vecInpSpecDig.emplace_back(getName(detStr + "digits", iLayer), Origin, "DIGITS", iLayer); + vecInpSpecROF.emplace_back(getName(detStr + "digitsROF", iLayer), Origin, "DIGITSROF", iLayer); + vecInpSpecLbl.emplace_back(getName(detStr + "_digitsMCTR", iLayer), Origin, "DIGITSMCTR", iLayer); + } + + return MakeRootTreeWriterSpec(("TRKDigitWriter" + std::string(dec ? "_dec" : "")).c_str(), (detStrL + "digits.root").c_str(), - MakeRootTreeWriterSpec::TreeAttributes{"o2sim", "Digits tree"}, + MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = detStr + " Digits tree"}, MakeRootTreeWriterSpec::CustomClose(finishWriting), - // in case of labels we first read them as std::vector and process them correctly in the fillLabels hook - BranchDefinition>{InputSpec{"digitsMCTR", detOrig, "DIGITSMCTR", 0}, - (detStr + "DigitMCTruth").c_str(), - (mctruth ? 1 : 0), fillLabels}, - BranchDefinition>{InputSpec{"digitsMC2ROF", detOrig, "DIGITSMC2ROF", 0}, - (detStr + "DigitMC2ROF").c_str(), - (mctruth ? 1 : 0)}, - BranchDefinition>{InputSpec{"digits", detOrig, "DIGITS", 0}, - (detStr + "Digit").c_str(), - logger}, - BranchDefinition>{InputSpec{"calib", detOrig, "GBTCALIB", 0}, - (detStr + "Calib").c_str(), - (calib ? 1 : 0)}, - BranchDefinition>{InputSpec{"digitsROF", detOrig, "DIGITSROF", 0}, - (detStr + "DigitROF").c_str()})(); -} - -DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) -{ - return getDigitWriterSpec(mctruth, dec, calib, o2::header::gDataOriginTRK, o2::detectors::DetID::TRK); + BranchDefinition>{vecInpSpecDig, + detStr + "Digit", "digit-branch", + mLayers, + digitSizeGetter, + getIndex, + getName}, + BranchDefinition>{vecInpSpecROF, + detStr + "DigitROF", "digit-rof-branch", + mLayers, + rofSizeGetter, + getIndex, + getName}, + BranchDefinition>{vecInpSpecLbl, + detStr + "DigitMCTruth", "digit-mctruth-branch", + (mctruth ? mLayers : 0), + fillLabels, + getIndex, + getName}, + BranchDefinition>{InputSpec{detStr + "calib", ConcreteDataTypeMatcher{Origin, "GBTCALIB"}}, + detStr + "Calib", "digit-calib-branch", + (calib ? 1 : 0)})(); } } // end namespace trk diff --git a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx index 8957ebed223b2..06d922cc1a117 100644 --- a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx +++ b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx @@ -30,6 +30,7 @@ #include "TRKSimulation/DPLDigitizerParam.h" #include "TRKBase/AlmiraParam.h" #include "TRKBase/GeometryTGeo.h" +#include "TRKBase/Specs.h" #include "TRKBase/TRKBaseParam.h" #include @@ -47,11 +48,13 @@ namespace std::vector makeOutChannels(o2::header::DataOrigin detOrig, bool mctruth) { std::vector outputs; - outputs.emplace_back(detOrig, "DIGITS", 0, Lifetime::Timeframe); - outputs.emplace_back(detOrig, "DIGITSROF", 0, Lifetime::Timeframe); - if (mctruth) { - outputs.emplace_back(detOrig, "DIGITSMC2ROF", 0, Lifetime::Timeframe); - outputs.emplace_back(detOrig, "DIGITSMCTR", 0, Lifetime::Timeframe); + for (uint32_t iLayer = 0; iLayer < o2::trk::AlmiraParam::getNLayers(); ++iLayer) { + outputs.emplace_back(detOrig, "DIGITS", iLayer, Lifetime::Timeframe); + outputs.emplace_back(detOrig, "DIGITSROF", iLayer, Lifetime::Timeframe); + if (mctruth) { + outputs.emplace_back(detOrig, "DIGITSMC2ROF", iLayer, Lifetime::Timeframe); + outputs.emplace_back(detOrig, "DIGITSMCTR", iLayer, Lifetime::Timeframe); + } } outputs.emplace_back(detOrig, "ROMode", 0, Lifetime::Timeframe); return outputs; @@ -99,163 +102,151 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer timer.Start(); LOG(info) << " CALLING TRK DIGITIZATION "; - mDigitizer.setDigits(&mDigits); - mDigitizer.setROFRecords(&mROFRecords); - mDigitizer.setMCLabels(&mLabels); + auto& eventParts = context->getEventParts(withQED); + uint64_t nDigits{0}; + for (uint32_t iLayer = 0; iLayer < static_cast(mLayers); ++iLayer) { + mDigits[iLayer].clear(); + mROFRecords[iLayer].clear(); + mROFRecordsAccum[iLayer].clear(); + if (mWithMCTruth) { + mLabels[iLayer].clear(); + mLabelsAccum[iLayer].clear(); + mMC2ROFRecordsAccum[iLayer].clear(); + } + + mDigitizer.setDigits(&mDigits[iLayer]); + mDigitizer.setROFRecords(&mROFRecords[iLayer]); + mDigitizer.setMCLabels(&mLabels[iLayer]); + mDigitizer.resetROFrameBounds(); - // digits are directly put into DPL owned resource - auto& digitsAccum = pc.outputs().make>(Output{mOrigin, "DIGITS", 0}); + // digits are directly put into DPL owned resource + auto& digitsAccum = pc.outputs().make>(Output{mOrigin, "DIGITS", iLayer}); - const int roFrameLengthInBC = mDigitizer.getParams().getROFrameLengthInBC(); - const int nROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / roFrameLengthInBC; - const int nROFsTF = nROFsPerOrbit * raw::HBFUtils::Instance().getNOrbitsPerTF(); - mROFRecordsAccum.reserve(nROFsTF); + const int roFrameLengthInBC = mDigitizer.getParams().getROFrameLengthInBC(iLayer); + const int nROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / roFrameLengthInBC; + const int nROFsTF = nROFsPerOrbit * raw::HBFUtils::Instance().getNOrbitsPerTF(); + mROFRecordsAccum[iLayer].reserve(nROFsTF); - auto accumulate = [this, &digitsAccum]() { - // accumulate result of single event processing, called after processing every event supplied - // AND after the final flushing via digitizer::fillOutputContainer - if (mDigits.empty()) { - LOG(debug) << "No digits to accumulate"; - return; // no digits were flushed, nothing to accumulate - } - LOG(debug) << "Accumulating " << mDigits.size() << " digits "; - auto ndigAcc = digitsAccum.size(); - std::copy(mDigits.begin(), mDigits.end(), std::back_inserter(digitsAccum)); - - // fix ROFrecords references on ROF entries - auto nROFRecsOld = mROFRecordsAccum.size(); - - for (int i = 0; i < mROFRecords.size(); i++) { - auto& rof = mROFRecords[i]; - rof.setFirstEntry(ndigAcc + rof.getFirstEntry()); - rof.print(); - - if (mFixMC2ROF < mMC2ROFRecordsAccum.size()) { // fix ROFRecord entry in MC2ROF records - for (int m2rid = mFixMC2ROF; m2rid < mMC2ROFRecordsAccum.size(); m2rid++) { - // need to register the ROFRecors entry for MC event starting from this entry - auto& mc2rof = mMC2ROFRecordsAccum[m2rid]; - if (rof.getROFrame() == mc2rof.minROF) { - mFixMC2ROF++; - mc2rof.rofRecordID = nROFRecsOld + i; - mc2rof.print(); - } - } + auto accumulate = [this, &digitsAccum, &iLayer]() { + // accumulate result of single event processing on one layer, called after each collision + // and after the final flushing via digitizer::fillOutputContainer + if (mDigits[iLayer].empty()) { + return; } - } + auto ndigAcc = digitsAccum.size(); + std::copy(mDigits[iLayer].begin(), mDigits[iLayer].end(), std::back_inserter(digitsAccum)); - std::copy(mROFRecords.begin(), mROFRecords.end(), std::back_inserter(mROFRecordsAccum)); - if (mWithMCTruth) { - mLabelsAccum.mergeAtBack(mLabels); - } - LOG(info) << "Added " << mDigits.size() << " digits "; - // clean containers from already accumulated stuff - mLabels.clear(); - mDigits.clear(); - mROFRecords.clear(); - }; // end accumulate lambda + for (auto& rof : mROFRecords[iLayer]) { + rof.setFirstEntry(ndigAcc + rof.getFirstEntry()); + } - auto& eventParts = context->getEventParts(withQED); - // loop over all composite collisions given from context (aka loop over all the interaction records) - const int bcShift = mDigitizer.getParams().getROFrameBiasInBC(); - // loop over all composite collisions given from context (aka loop over all the interaction records) - for (size_t collID = 0; collID < timesview.size(); ++collID) { - auto irt = timesview[collID]; - if (irt.toLong() < bcShift) { // due to the ROF misalignment the collision would go to negative ROF ID, discard - continue; - } - irt -= bcShift; // account for the ROF start shift - - mDigitizer.setEventTime(irt); - mDigitizer.resetEventROFrames(); // to estimate min/max ROF for this collID - // for each collision, loop over the constituents event and source IDs - // (background signal merging is basically taking place here) - for (auto& part : eventParts[collID]) { - - // get the hits for this event and this source - mHits.clear(); - context->retrieveHits(mSimChains, o2::detectors::SimTraits::DETECTORBRANCHNAMES[mID][0].c_str(), part.sourceID, part.entryID, &mHits); - - if (!mHits.empty()) { - LOG(debug) << "For collision " << collID << " eventID " << part.entryID - << " found " << mHits.size() << " hits "; - mDigitizer.process(&mHits, part.entryID, part.sourceID); // call actual digitization procedure + std::copy(mROFRecords[iLayer].begin(), mROFRecords[iLayer].end(), std::back_inserter(mROFRecordsAccum[iLayer])); + if (mWithMCTruth) { + mLabelsAccum[iLayer].mergeAtBack(mLabels[iLayer]); } + LOG(info) << "Added " << mDigits[iLayer].size() << " digits on layer " << iLayer; + mLabels[iLayer].clear(); + mDigits[iLayer].clear(); + mROFRecords[iLayer].clear(); + }; + + const int bcShift = mDigitizer.getParams().getROFrameBiasInBC(iLayer); + for (size_t collID = 0; collID < timesview.size(); ++collID) { + auto irt = timesview[collID]; + if (irt.toLong() < bcShift) { + continue; + } + irt -= bcShift; + + mDigitizer.setEventTime(irt, iLayer); + mDigitizer.resetEventROFrames(); + for (auto& part : eventParts[collID]) { + mHits.clear(); + context->retrieveHits(mSimChains, o2::detectors::SimTraits::DETECTORBRANCHNAMES[mID][0].c_str(), part.sourceID, part.entryID, &mHits); + + if (!mHits.empty()) { + LOG(debug) << "For collision " << collID << " eventID " << part.entryID + << " found " << mHits.size() << " hits on layer " << iLayer; + mDigitizer.process(&mHits, part.entryID, part.sourceID, iLayer); + } + } + if (mWithMCTruth) { + mMC2ROFRecordsAccum[iLayer].emplace_back(collID, -1, mDigitizer.getEventROFrameMin(), mDigitizer.getEventROFrameMax()); + } + accumulate(); } - mMC2ROFRecordsAccum.emplace_back(collID, -1, mDigitizer.getEventROFrameMin(), mDigitizer.getEventROFrameMax()); + mDigitizer.fillOutputContainer(0xffffffff, iLayer); accumulate(); - } - mDigitizer.fillOutputContainer(); - LOG(debug) << "mDigits size after fill: " << mDigits.size(); - accumulate(); - - // here we have all digits and labels and we can send them to consumer (aka snapshot it onto output) - std::vector expDigitRofVec(nROFsTF); - for (int iROF = 0; iROF < nROFsTF; ++iROF) { - auto& rof = expDigitRofVec[iROF]; - const int orb = iROF * roFrameLengthInBC / o2::constants::lhc::LHCMaxBunches + mFirstOrbitTF; - const int bc = iROF * roFrameLengthInBC % o2::constants::lhc::LHCMaxBunches; - rof.setBCData(o2::InteractionRecord(bc, orb)); - rof.setROFrame(iROF); - rof.setNEntries(0); - rof.setFirstEntry(-1); - } - - for (const auto& rof : mROFRecordsAccum) { - const auto& ir = rof.getBCData(); - const auto irToFirst = ir - firstIR; - const auto irROF = irToFirst.toLong() / roFrameLengthInBC; - if (irROF < 0 || irROF >= nROFsTF) { - continue; + nDigits += digitsAccum.size(); + + std::vector expDigitRofVec(nROFsTF); + for (int iROF = 0; iROF < nROFsTF; ++iROF) { + auto& rof = expDigitRofVec[iROF]; + const int orb = iROF * roFrameLengthInBC / o2::constants::lhc::LHCMaxBunches + mFirstOrbitTF; + const int bc = iROF * roFrameLengthInBC % o2::constants::lhc::LHCMaxBunches; + rof.setBCData(o2::InteractionRecord(bc, orb)); + rof.setROFrame(iROF); + rof.setNEntries(0); + rof.setFirstEntry(-1); } - auto& expROF = expDigitRofVec[irROF]; - expROF.setFirstEntry(rof.getFirstEntry()); - expROF.setNEntries(rof.getNEntries()); - if (expROF.getBCData() != rof.getBCData()) { - LOGP(fatal, "detected mismatch between expected {} and received {}", expROF.asString(), rof.asString()); + + for (const auto& rof : mROFRecordsAccum[iLayer]) { + const auto& ir = rof.getBCData(); + const auto irToFirst = ir - firstIR; + const auto irROF = irToFirst.toLong() / roFrameLengthInBC; + if (irROF < 0 || irROF >= nROFsTF) { + continue; + } + auto& expROF = expDigitRofVec[irROF]; + expROF.setFirstEntry(rof.getFirstEntry()); + expROF.setNEntries(rof.getNEntries()); + if (expROF.getBCData() != rof.getBCData()) { + LOGP(fatal, "detected mismatch between expected {} and received {}", expROF.asString(), rof.asString()); + } } - } - int prevFirst = 0; - for (auto& rof : expDigitRofVec) { - if (rof.getFirstEntry() < 0) { - rof.setFirstEntry(prevFirst); + int prevFirst = 0; + for (auto& rof : expDigitRofVec) { + if (rof.getFirstEntry() < 0) { + rof.setFirstEntry(prevFirst); + } + prevFirst = rof.getFirstEntry(); } - prevFirst = rof.getFirstEntry(); - } - pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", 0}, expDigitRofVec); - if (mWithMCTruth) { - std::vector clippedMC2ROFRecords; - clippedMC2ROFRecords.reserve(mMC2ROFRecordsAccum.size()); - for (auto mc2rof : mMC2ROFRecordsAccum) { - if (mc2rof.rofRecordID < 0 || mc2rof.minROF >= static_cast(nROFsTF)) { - mc2rof.rofRecordID = -1; - mc2rof.minROF = 0; - mc2rof.maxROF = 0; - } else { - mc2rof.maxROF = std::min(mc2rof.maxROF, nROFsTF - 1); - if (mc2rof.minROF > mc2rof.maxROF) { + pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", iLayer}, expDigitRofVec); + if (mWithMCTruth) { + std::vector clippedMC2ROFRecords; + clippedMC2ROFRecords.reserve(mMC2ROFRecordsAccum[iLayer].size()); + for (auto mc2rof : mMC2ROFRecordsAccum[iLayer]) { + if (mc2rof.minROF >= static_cast(nROFsTF) || mc2rof.minROF > mc2rof.maxROF) { mc2rof.rofRecordID = -1; mc2rof.minROF = 0; mc2rof.maxROF = 0; } else { - mc2rof.rofRecordID = mc2rof.minROF; + mc2rof.maxROF = std::min(mc2rof.maxROF, nROFsTF - 1); + if (mc2rof.minROF > mc2rof.maxROF) { + mc2rof.rofRecordID = -1; + mc2rof.minROF = 0; + mc2rof.maxROF = 0; + } else { + mc2rof.rofRecordID = mc2rof.minROF; + } } + clippedMC2ROFRecords.push_back(mc2rof); } - clippedMC2ROFRecords.push_back(mc2rof); + pc.outputs().snapshot(Output{mOrigin, "DIGITSMC2ROF", iLayer}, clippedMC2ROFRecords); + auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", iLayer}); + mLabelsAccum[iLayer].flatten_to(sharedlabels); + mLabels[iLayer].clear_andfreememory(); + mLabelsAccum[iLayer].clear_andfreememory(); } - pc.outputs().snapshot(Output{mOrigin, "DIGITSMC2ROF", 0}, clippedMC2ROFRecords); - auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", 0}); - mLabelsAccum.flatten_to(sharedlabels); - // free space of existing label containers - mLabels.clear_andfreememory(); - mLabelsAccum.clear_andfreememory(); } LOG(info) << mID.getName() << ": Sending ROMode= " << mROMode << " to GRPUpdater"; pc.outputs().snapshot(Output{mOrigin, "ROMode", 0}, mROMode); timer.Stop(); LOG(info) << "Digitization took " << timer.CpuTime() << "s"; + LOG(info) << "Produced " << nDigits << " digits"; // we should be only called once; tell DPL that this process is ready to exit pc.services().get().readyToQuit(QuitRequest::Me); @@ -288,13 +279,25 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer const auto& dopt = o2::trk::DPLDigitizerParam::Instance(); // pc.inputs().get("TRK_almiraparam"); const auto& aopt = o2::trk::AlmiraParam::Instance(); - auto frameNS = aopt.roFrameLengthInBC * o2::constants::lhc::LHCBunchSpacingNS; - digipar.setContinuous(true); - digipar.setROFrameBiasInBC(aopt.roFrameBiasInBC); - digipar.setROFrameLengthInBC(aopt.roFrameLengthInBC); - digipar.setROFrameLength(frameNS); // RO frame in ns - digipar.setStrobeDelay(aopt.strobeDelay); - digipar.setStrobeLength(aopt.strobeLengthCont > 0 ? aopt.strobeLengthCont : frameNS - aopt.strobeDelay); + mLayers = constants::VD::petal::nLayers + geom->getNumberOfLayersMLOT(); + mDigits.resize(mLayers); + mROFRecords.resize(mLayers); + mROFRecordsAccum.resize(mLayers); + mLabels.resize(mLayers); + mLabelsAccum.resize(mLayers); + mMC2ROFRecordsAccum.resize(mLayers); + + for (int iLayer = 0; iLayer < mLayers; ++iLayer) { + const auto roFrameLengthInBC = aopt.getROFLengthInBC(iLayer); + const auto frameNS = roFrameLengthInBC * o2::constants::lhc::LHCBunchSpacingNS; + digipar.setROFrameLengthInBC(roFrameLengthInBC, iLayer); + // ROF delay is treated as an additional bias from the digitizer point of view. + digipar.setROFrameBiasInBC(aopt.getROFBiasInBC(iLayer) + aopt.getROFDelayInBC(iLayer), iLayer); + digipar.setStrobeDelay(aopt.getStrobeDelay(iLayer), iLayer); + const auto strobeLengthCont = aopt.getStrobeLengthCont(iLayer); + digipar.setStrobeLength(strobeLengthCont > 0 ? strobeLengthCont : frameNS - aopt.getStrobeDelay(iLayer), iLayer); + digipar.setROFrameLength(frameNS, iLayer); + } // parameters of signal time response: flat-top duration, max rise time and q @ which rise time is 0 digipar.getSignalShape().setParameters(dopt.strobeFlatTop, dopt.strobeMaxRiseTime, dopt.strobeQRiseTime0); digipar.setChargeThreshold(dopt.chargeThreshold); // charge threshold in electrons @@ -352,17 +355,16 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer const o2::detectors::DetID mID{o2::detectors::DetID::TRK}; const o2::header::DataOrigin mOrigin{o2::header::gDataOriginTRK}; o2::trk::Digitizer mDigitizer{}; - std::vector mDigits{}; - std::vector mROFRecords{}; - std::vector mROFRecordsAccum{}; + int mLayers{0}; + std::vector> mDigits{}; + std::vector> mROFRecords{}; + std::vector> mROFRecordsAccum{}; std::vector mHits{}; std::vector* mHitsP{&mHits}; - o2::dataformats::MCTruthContainer mLabels{}; - o2::dataformats::MCTruthContainer mLabelsAccum{}; - std::vector mMC2ROFRecordsAccum{}; + std::vector> mLabels{}; + std::vector> mLabelsAccum{}; + std::vector> mMC2ROFRecordsAccum{}; std::vector mSimChains{}; - - int mFixMC2ROF = 0; // 1st entry in mc2rofRecordsAccum to be fixed for ROFRecordID o2::parameters::GRPObject::ROMode mROMode = o2::parameters::GRPObject::PRESENT; // readout mode }; From 72a2278ff53149436a5500d5375a1a3182d683b1 Mon Sep 17 00:00:00 2001 From: SuJeong Ji <120470463+SuJeong-Ji@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:22:27 +0900 Subject: [PATCH 019/102] Added physics constants for K1(1270) --- Common/Constants/include/CommonConstants/PhysicsConstants.h | 6 +++++- Common/Constants/include/CommonConstants/make_pdg_header.py | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Common/Constants/include/CommonConstants/PhysicsConstants.h b/Common/Constants/include/CommonConstants/PhysicsConstants.h index 46aeff98d6033..e95b4343a63d4 100644 --- a/Common/Constants/include/CommonConstants/PhysicsConstants.h +++ b/Common/Constants/include/CommonConstants/PhysicsConstants.h @@ -92,7 +92,9 @@ enum Pdg { kHyperHelium4 = 1010020040, kHyperHelium5 = 1010020050, kHyperHelium4Sigma = 1110020040, - kLambda1520_Py = 102134 + kLambda1520_Py = 102134, + kK1_1270_0 = 10313, + kK1_1270Plus = 10323 }; /// \brief Declarations of masses for additional particles @@ -158,6 +160,8 @@ constexpr double MassHyperHelium4 = 3.921728; constexpr double MassHyperHelium5 = 4.839961; constexpr double MassHyperHelium4Sigma = 3.995; constexpr double MassLambda1520_Py = 1.5195; +constexpr double MassK1_1270_0 = 1.253; +constexpr double MassK1_1270Plus = 1.272; /// \brief Declarations of masses for particles in ROOT PDG_t constexpr double MassDown = 0.00467; diff --git a/Common/Constants/include/CommonConstants/make_pdg_header.py b/Common/Constants/include/CommonConstants/make_pdg_header.py index f83c44bb401db..ccea1863771f3 100755 --- a/Common/Constants/include/CommonConstants/make_pdg_header.py +++ b/Common/Constants/include/CommonConstants/make_pdg_header.py @@ -151,6 +151,8 @@ class Pdg(Enum): kHyperHelium5 = 1010020050 kHyperHelium4Sigma = 1110020040 kLambda1520_Py = 102134 # PYTHIA code different from PDG + kK1_1270_0 = 10313 + kK1_1270Plus = 10323 dbPdg = o2.O2DatabasePDG From a3da6e46ee5f33b2aee7ba29dc21a6a08dbc3565 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:25:12 +0100 Subject: [PATCH 020/102] DPL: allow determining the origin from user provide input In order to support embedding, we need allow the user to provide a mapping between the desired origin and the level in the parent file chain where the table should be found. --- Framework/AnalysisSupport/CMakeLists.txt | 9 ++ .../src/AODJAlienReaderHelpers.cxx | 34 ++++- .../AnalysisSupport/src/DataInputDirector.cxx | 84 +++++++++-- .../AnalysisSupport/src/DataInputDirector.h | 26 +++- .../test/test_NavigateToLevel.cxx | 135 ++++++++++++++++++ Framework/Core/src/Plugin.cxx | 3 + 6 files changed, 268 insertions(+), 23 deletions(-) create mode 100644 Framework/AnalysisSupport/test/test_NavigateToLevel.cxx diff --git a/Framework/AnalysisSupport/CMakeLists.txt b/Framework/AnalysisSupport/CMakeLists.txt index 6024134a5495d..956c4a44c5684 100644 --- a/Framework/AnalysisSupport/CMakeLists.txt +++ b/Framework/AnalysisSupport/CMakeLists.txt @@ -47,6 +47,15 @@ o2_add_test(DataInputDirector NAME test_Framework_test_DataInputDirector LABELS framework PUBLIC_LINK_LIBRARIES O2::FrameworkAnalysisSupport) +add_executable(o2-test-framework-analysis-support + test/test_NavigateToLevel.cxx) +target_link_libraries(o2-test-framework-analysis-support PRIVATE O2::FrameworkAnalysisSupport O2::Catch2) + +get_filename_component(outdir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../tests ABSOLUTE) +set_property(TARGET o2-test-framework-analysis-support PROPERTY RUNTIME_OUTPUT_DIRECTORY ${outdir}) + +add_test(NAME framework:analysis-support COMMAND o2-test-framework-analysis-support) + o2_add_test(TableToTree NAME benchmark_TableToTree SOURCES test/benchmark_TableToTree.cxx COMPONENT_NAME Framework diff --git a/Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx b/Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx index 57a397822d167..8fde9f52a0e09 100644 --- a/Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx +++ b/Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx @@ -10,7 +10,10 @@ // or submit itself to any jurisdiction. #include "AODJAlienReaderHelpers.h" +#include #include +#include +#include #include "Framework/TableTreeHelpers.h" #include "Framework/AnalysisHelpers.h" #include "Framework/DataProcessingStats.h" @@ -111,10 +114,31 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const if (ctx.options().isSet("aod-parent-access-level")) { parentAccessLevel = ctx.options().get("aod-parent-access-level"); } - auto callback = AlgorithmSpec{adaptStateful([parentFileReplacement, parentAccessLevel](ConfigParamRegistry const& options, - DeviceSpec const& spec, - Monitoring& monitoring, - DataProcessingStats& stats) { + std::vector> originLevelMapping; + if (ctx.options().isSet("aod-origin-level-mapping")) { + auto originLevelMappingStr = ctx.options().get("aod-origin-level-mapping"); + for (auto pairRange : originLevelMappingStr | std::views::split(',')) { + std::string_view pair{pairRange.begin(), pairRange.end()}; + auto colonPos = pair.find(':'); + if (colonPos == std::string_view::npos) { + LOGP(fatal, "Badly formatted aod-origin-level-mapping entry: \"{}\"", pair); + continue; + } + std::string key(pair.substr(0, colonPos)); + std::string_view valueStr = pair.substr(colonPos + 1); + int value{}; + auto [ptr, ec] = std::from_chars(valueStr.data(), valueStr.data() + valueStr.size(), value); + if (ec == std::errc{}) { + originLevelMapping.emplace_back(std::move(key), value); + } else { + LOGP(fatal, "Unable to parse level in aod-origin-level-mapping entry: \"{}\"", pair); + } + } + } + auto callback = AlgorithmSpec{adaptStateful([parentFileReplacement, parentAccessLevel, originLevelMapping](ConfigParamRegistry const& options, + DeviceSpec const& spec, + Monitoring& monitoring, + DataProcessingStats& stats) { // FIXME: not actually needed, since data processing stats can specify that we should // send the initial value. stats.updateStats({static_cast(ProcessingStatsId::ARROW_BYTES_CREATED), DataProcessingStats::Op::Set, 0}); @@ -134,7 +158,7 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const auto maxRate = options.get("aod-max-io-rate"); // create a DataInputDirector - auto didir = std::make_shared(std::vector{filename}, DataInputDirectorContext{&monitoring, parentAccessLevel, parentFileReplacement}); + auto didir = std::make_shared(std::vector{filename}, DataInputDirectorContext{&monitoring, parentAccessLevel, parentFileReplacement, originLevelMapping}); if (options.isSet("aod-reader-json")) { auto jsonFile = options.get("aod-reader-json"); if (!didir->readJson(jsonFile)) { diff --git a/Framework/AnalysisSupport/src/DataInputDirector.cxx b/Framework/AnalysisSupport/src/DataInputDirector.cxx index 7027655b7abe7..46674f19400a6 100644 --- a/Framework/AnalysisSupport/src/DataInputDirector.cxx +++ b/Framework/AnalysisSupport/src/DataInputDirector.cxx @@ -122,7 +122,7 @@ void DataInputDescriptor::addFileNameHolder(FileNameHolder* fn) mfilenames.emplace_back(fn); } -bool DataInputDescriptor::setFile(int counter, std::string_view origin) +bool DataInputDescriptor::setFile(int counter, int wantedParentLevel, std::string_view origin) { // no files left if (counter >= getNumberInputfiles()) { @@ -133,7 +133,9 @@ bool DataInputDescriptor::setFile(int counter, std::string_view origin) // of the filename. In the future we might expand this for proper rewriting of the // filename based on the origin and the original file information. std::string filename = mfilenames[counter]->fileName; - if (!origin.starts_with("AOD")) { + // In case we do not need to remap parent levels, the requested origin is what + // drives the filename. + if (wantedParentLevel == -1 && !origin.starts_with("AOD")) { filename = std::regex_replace(filename, std::regex("[.]root$"), fmt::format("_{}.root", origin)); } @@ -146,7 +148,19 @@ bool DataInputDescriptor::setFile(int counter, std::string_view origin) closeInputFile(); } - mCurrentFilesystem = std::make_shared(TFile::Open(filename.c_str()), 50 * 1024 * 1024, mFactory); + TFile* tfile = nullptr; + bool externalFile = false; + for (auto& [name, f] : mContext.openFiles) { + if (name == filename) { + tfile = f; + externalFile = true; + break; + } + } + if (tfile == nullptr) { + tfile = TFile::Open(filename.c_str()); + } + mCurrentFilesystem = std::make_shared(tfile, 50 * 1024 * 1024, mFactory, !externalFile); if (!mCurrentFilesystem.get()) { throw std::runtime_error(fmt::format("Couldn't open file \"{}\"!", filename)); } @@ -218,11 +232,11 @@ bool DataInputDescriptor::setFile(int counter, std::string_view origin) return true; } -uint64_t DataInputDescriptor::getTimeFrameNumber(int counter, int numTF, std::string_view origin) +uint64_t DataInputDescriptor::getTimeFrameNumber(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin) { // open file - if (!setFile(counter, origin)) { + if (!setFile(counter, wantedParentLevel, wantedOrigin)) { return 0ul; } @@ -234,10 +248,32 @@ uint64_t DataInputDescriptor::getTimeFrameNumber(int counter, int numTF, std::st return (mfilenames[counter]->listOfTimeFrameNumbers)[numTF]; } -arrow::dataset::FileSource DataInputDescriptor::getFileFolder(int counter, int numTF, std::string_view origin) +std::pair DataInputDescriptor::navigateToLevel(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin) +{ + if (!setFile(counter, wantedParentLevel, wantedOrigin)) { + return {nullptr, -1}; + } + auto folderName = fmt::format("DF_{}", mfilenames[counter]->listOfTimeFrameNumbers[numTF]); + auto parentFile = getParentFile(counter, numTF, "", wantedParentLevel, wantedOrigin); + if (parentFile == nullptr) { + return {nullptr, -1}; + } + return {parentFile, parentFile->findDFNumber(0, folderName)}; +} + +arrow::dataset::FileSource DataInputDescriptor::getFileFolder(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin) { + // If mapped to a parent level deeper than current, skip directly to the right level. + if (wantedParentLevel != -1 && mLevel < wantedParentLevel) { + auto [parentFile, parentNumTF] = navigateToLevel(counter, numTF, wantedParentLevel, wantedOrigin); + if (parentFile == nullptr || parentNumTF == -1) { + return {}; + } + return parentFile->getFileFolder(0, parentNumTF, wantedParentLevel, wantedOrigin); + } + // open file - if (!setFile(counter, origin)) { + if (!setFile(counter, wantedParentLevel, wantedOrigin)) { return {}; } @@ -251,7 +287,7 @@ arrow::dataset::FileSource DataInputDescriptor::getFileFolder(int counter, int n return {fmt::format("DF_{}", mfilenames[counter]->listOfTimeFrameNumbers[numTF]), mCurrentFilesystem}; } -DataInputDescriptor* DataInputDescriptor::getParentFile(int counter, int numTF, std::string treename, std::string_view origin) +DataInputDescriptor* DataInputDescriptor::getParentFile(int counter, int numTF, std::string treename, int wantedParentLevel, std::string_view wantedOrigin) { if (!mParentFileMap) { // This file has no parent map @@ -288,7 +324,7 @@ DataInputDescriptor* DataInputDescriptor::getParentFile(int counter, int numTF, mParentFile->mdefaultFilenamesPtr = new std::vector; mParentFile->mdefaultFilenamesPtr->emplace_back(makeFileNameHolder(parentFileName->GetString().Data())); mParentFile->fillInputfiles(); - mParentFile->setFile(0, origin); + mParentFile->setFile(0, wantedParentLevel, wantedOrigin); return mParentFile; } @@ -450,8 +486,26 @@ struct CalculateDelta { bool DataInputDescriptor::readTree(DataAllocator& outputs, header::DataHeader dh, int counter, int numTF, std::string treename, size_t& totalSizeCompressed, size_t& totalSizeUncompressed) { CalculateDelta t(mIOTime); - std::string origin = dh.dataOrigin.as(); - auto folder = getFileFolder(counter, numTF, origin); + std::string wantedOrigin = dh.dataOrigin.as(); + int wantedLevel = mContext.levelForOrigin(wantedOrigin); + + // If this origin is mapped to a parent level deeper than current, skip directly without + // attempting to read from this level. + if (wantedLevel != -1 && mLevel < wantedLevel) { + auto [parentFile, parentNumTF] = navigateToLevel(counter, numTF, wantedLevel, wantedOrigin); + if (parentFile == nullptr) { + auto rootFS = std::dynamic_pointer_cast(mCurrentFilesystem); + throw std::runtime_error(fmt::format(R"(No parent file found for "{}" while looking for level {} in "{}")", treename, wantedLevel, rootFS->GetFile()->GetName())); + } + if (parentNumTF == -1) { + auto parentRootFS = std::dynamic_pointer_cast(parentFile->mCurrentFilesystem); + throw std::runtime_error(fmt::format(R"(DF not found in parent file "{}")", parentRootFS->GetFile()->GetName())); + } + t.deactivate(); + return parentFile->readTree(outputs, dh, 0, parentNumTF, treename, totalSizeCompressed, totalSizeUncompressed); + } + + auto folder = getFileFolder(counter, numTF, wantedLevel, wantedOrigin); if (!folder.filesystem()) { t.deactivate(); return false; @@ -484,7 +538,7 @@ bool DataInputDescriptor::readTree(DataAllocator& outputs, header::DataHeader dh if (!format) { t.deactivate(); LOGP(debug, "Could not find tree {}. Trying in parent file.", fullpath.path()); - auto parentFile = getParentFile(counter, numTF, treename, origin); + auto parentFile = getParentFile(counter, numTF, treename, wantedLevel, wantedOrigin); if (parentFile != nullptr) { int parentNumTF = parentFile->findDFNumber(0, folder.path()); if (parentNumTF == -1) { @@ -817,8 +871,9 @@ arrow::dataset::FileSource DataInputDirector::getFileFolder(header::DataHeader d didesc = mdefaultDataInputDescriptor; } std::string origin = dh.dataOrigin.as(); + int wantedLevel = mContext.levelForOrigin(origin); - return didesc->getFileFolder(counter, numTF, origin); + return didesc->getFileFolder(counter, numTF, wantedLevel, origin); } int DataInputDirector::getTimeFramesInFile(header::DataHeader dh, int counter) @@ -840,8 +895,9 @@ uint64_t DataInputDirector::getTimeFrameNumber(header::DataHeader dh, int counte didesc = mdefaultDataInputDescriptor; } std::string origin = dh.dataOrigin.as(); + int wantedLevel = mContext.levelForOrigin(origin); - return didesc->getTimeFrameNumber(counter, numTF, origin); + return didesc->getTimeFrameNumber(counter, numTF, wantedLevel, origin); } bool DataInputDirector::readTree(DataAllocator& outputs, header::DataHeader dh, int counter, int numTF, size_t& totalSizeCompressed, size_t& totalSizeUncompressed) diff --git a/Framework/AnalysisSupport/src/DataInputDirector.h b/Framework/AnalysisSupport/src/DataInputDirector.h index 2d63a1c71ea77..18ab5c0c1382e 100644 --- a/Framework/AnalysisSupport/src/DataInputDirector.h +++ b/Framework/AnalysisSupport/src/DataInputDirector.h @@ -21,6 +21,7 @@ #include #include +#include #include "rapidjson/fwd.h" namespace o2::monitoring @@ -44,6 +45,20 @@ struct DataInputDirectorContext { o2::monitoring::Monitoring* monitoring = nullptr; int allowedParentLevel = 0; std::string parentFileReplacement = ""; + std::vector> parentLevelToOrigin = {}; + // Optional registry of pre-opened TFiles (keyed by name) used to bypass + // TFile::Open for testing with in-memory TMemFile instances. + std::vector> openFiles = {}; + + int levelForOrigin(std::string_view origin) const + { + for (auto& [o, level] : parentLevelToOrigin) { + if (o == origin) { + return level; + } + } + return -1; + } }; class DataInputDescriptor @@ -71,7 +86,7 @@ class DataInputDescriptor void addFileNameHolder(FileNameHolder* fn); int fillInputfiles(); - bool setFile(int counter, std::string_view origin); + bool setFile(int counter, int wantedParentLevel, std::string_view wantedOrigin); // getters std::string getInputfilesFilename(); @@ -81,9 +96,12 @@ class DataInputDescriptor int getNumberTimeFrames() { return mtotalNumberTimeFrames; } int findDFNumber(int file, std::string dfName); - uint64_t getTimeFrameNumber(int counter, int numTF, std::string_view origin); - arrow::dataset::FileSource getFileFolder(int counter, int numTF, std::string_view origin); - DataInputDescriptor* getParentFile(int counter, int numTF, std::string treename, std::string_view origin); + uint64_t getTimeFrameNumber(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin); + arrow::dataset::FileSource getFileFolder(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin); + // Open the current file to populate the parent map, then return the parent descriptor and + // the TF index within it that corresponds to numTF at this level. Returns {nullptr, -1} on failure. + std::pair navigateToLevel(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin); + DataInputDescriptor* getParentFile(int counter, int numTF, std::string treename, int wantedParentLevel, std::string_view wantedOrigin); int getTimeFramesInFile(int counter); int getReadTimeFramesInFile(int counter); diff --git a/Framework/AnalysisSupport/test/test_NavigateToLevel.cxx b/Framework/AnalysisSupport/test/test_NavigateToLevel.cxx new file mode 100644 index 0000000000000..0072ee3b67d37 --- /dev/null +++ b/Framework/AnalysisSupport/test/test_NavigateToLevel.cxx @@ -0,0 +1,135 @@ +// 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 + +#include "../src/DataInputDirector.h" + +#include +#include +#include +#include + +using namespace o2::framework; + +// Tests for DataInputDirectorContext::levelForOrigin + +TEST_CASE("levelForOrigin empty mapping") +{ + DataInputDirectorContext ctx; + CHECK(ctx.levelForOrigin("AOD") == -1); + CHECK(ctx.levelForOrigin("DYN") == -1); +} + +TEST_CASE("levelForOrigin single entry") +{ + DataInputDirectorContext ctx; + ctx.parentLevelToOrigin = {{"DYN", 1}}; + CHECK(ctx.levelForOrigin("DYN") == 1); + CHECK(ctx.levelForOrigin("AOD") == -1); +} + +TEST_CASE("levelForOrigin multiple entries") +{ + DataInputDirectorContext ctx; + ctx.parentLevelToOrigin = {{"DYN", 1}, {"EMB", 2}, {"EXT", 1}}; + CHECK(ctx.levelForOrigin("DYN") == 1); + CHECK(ctx.levelForOrigin("EMB") == 2); + CHECK(ctx.levelForOrigin("EXT") == 1); + CHECK(ctx.levelForOrigin("AOD") == -1); + CHECK(ctx.levelForOrigin("") == -1); +} + +// Tests for DataInputDescriptor::navigateToLevel + +TEST_CASE("navigateToLevel returns null with no input files") +{ + // With no input files setFile fails immediately → {nullptr, -1} + DataInputDirectorContext ctx; + ctx.allowedParentLevel = 2; + DataInputDescriptor desc(false, 0, ctx); + + auto [parentFile, parentNumTF] = desc.navigateToLevel(0, 0, 1, "DYN"); + CHECK(parentFile == nullptr); + CHECK(parentNumTF == -1); +} + +// --------------------------------------------------------------------------- +// Helpers: build an AO2D-shaped TMemFile with one DF directory. +// The AO2D format uses top-level TDirectory entries named DF_. +// An optional "parentFiles" TMap maps each DF name to its parent file path. +// --------------------------------------------------------------------------- + +static TMemFile* makeAODFile(const char* name) +{ + auto* f = new TMemFile(name, "RECREATE"); + f->mkdir("DF_1"); + f->Write(); + return f; +} + +static TMemFile* makeAODFileWithParent(const char* name, const char* parentName) +{ + auto* f = new TMemFile(name, "RECREATE"); + f->mkdir("DF_1"); + auto* parentMap = new TMap(); + parentMap->Add(new TObjString("DF_1"), new TObjString(parentName)); + parentMap->Write("parentFiles", TObject::kSingleKey); + f->Write(); + return f; +} + +TEST_CASE("navigateToLevel finds parent TMemFile") +{ + // child.root DF_1 parentFiles: {DF_1 -> parent.root} + // parent.root DF_1 + auto* parentMF = makeAODFile("parent.root"); + auto* childMF = makeAODFileWithParent("child.root", "parent.root"); + + DataInputDirectorContext ctx; + ctx.allowedParentLevel = 2; + ctx.openFiles = {{"child.root", childMF}, {"parent.root", parentMF}}; + + DataInputDescriptor desc(false, 0, ctx); + desc.addFileNameHolder(makeFileNameHolder("child.root")); + + auto [parentDesc, parentNumTF] = desc.navigateToLevel(0, 0, 1, "AOD"); + + REQUIRE(parentDesc != nullptr); + // DF_1 is the only timeframe in the parent, so its index is 0 + CHECK(parentNumTF == 0); +} + +TEST_CASE("navigateToLevel returns -1 for missing DF in parent") +{ + // child has DF_2 but parent only has DF_1 — findDFNumber returns -1 + auto* parentMF = makeAODFile("parent2.root"); + + auto* childMF = new TMemFile("child2.root", "RECREATE"); + childMF->mkdir("DF_2"); + auto* parentMap = new TMap(); + parentMap->Add(new TObjString("DF_2"), new TObjString("parent2.root")); + parentMap->Write("parentFiles", TObject::kSingleKey); + childMF->Write(); + + DataInputDirectorContext ctx; + ctx.allowedParentLevel = 2; + ctx.openFiles = {{"child2.root", childMF}, {"parent2.root", parentMF}}; + + DataInputDescriptor desc(false, 0, ctx); + desc.addFileNameHolder(makeFileNameHolder("child2.root")); + + auto [parentDesc, parentNumTF] = desc.navigateToLevel(0, 0, 1, "AOD"); + + // Parent has DF_1 but child references DF_2 — not found in parent + REQUIRE(parentDesc != nullptr); + CHECK(parentNumTF == -1); +} diff --git a/Framework/Core/src/Plugin.cxx b/Framework/Core/src/Plugin.cxx index 8ed683d501906..503133442e794 100644 --- a/Framework/Core/src/Plugin.cxx +++ b/Framework/Core/src/Plugin.cxx @@ -168,6 +168,9 @@ struct DiscoverAODOptionsInCommandLine : o2::framework::ConfigDiscoveryPlugin { if (key == "aod-parent-access-level") { results.push_back(ConfigParamSpec{"aod-parent-access-level", VariantType::String, value, {"Allow parent file access up to specified level. Default: no (0)"}}); } + if (key == "aod-origin-level-mapping") { + results.push_back(ConfigParamSpec{"aod-origin-level-mapping", VariantType::String, value, {"Map origin to parent level for AOD reading. Syntax: ORIGIN:LEVEL[,ORIGIN2:LEVEL2,...]. E.g. \"DYN:1\"."}}); + } } if (injectOption) { results.push_back(ConfigParamSpec{"aod-writer-compression", VariantType::Int, 505, {"AOD Compression options"}}); From a43e1f5d685d271c33426741a87a3e792e091d82 Mon Sep 17 00:00:00 2001 From: rmunzer <97919772+rmunzer@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:20:31 +0200 Subject: [PATCH 021/102] TPC: Add CMV processing (#15283) * Add CMV processing * Add parameters for CMV * Add o2-tpc-cmv-aggragate-task * Cleanup and change of parameters names * Correct white spaces * Remove white space * Change Parameter name * Fix bug for paramter definition * Change default paramters * Reduce lanesCMVaggregate to 8 * don't add ccdb-populator if not needed for IDC calib collection; refactor adding of IDC workflows --------- Co-authored-by: Ernst Hellbar --- .../full-system-test/aggregator-workflow.sh | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/prodtests/full-system-test/aggregator-workflow.sh b/prodtests/full-system-test/aggregator-workflow.sh index a0d091a98d193..0dc30df93669d 100755 --- a/prodtests/full-system-test/aggregator-workflow.sh +++ b/prodtests/full-system-test/aggregator-workflow.sh @@ -161,7 +161,7 @@ if workflow_has_parameter CALIB_PROXIES; then fi CHANNELS_LIST= [[ $EPNSYNCMODE == 0 ]] && FLP_ADDRESS="tcp://localhost:29950" - if [[ -n ${CALIBDATASPEC_TPCIDC_A:-} ]] || [[ -n ${CALIBDATASPEC_TPCIDC_C:-} ]]; then + if [[ -n ${CALIBDATASPEC_TPCIDC_A:-} ]] || [[ -n ${CALIBDATASPEC_TPCIDC_C:-} ]] || [[ -n ${CALIBDATASPEC_TPCCMV:-} ]]; then # define port for FLP : ${TPC_IDC_FLP_PORT:=29950} # expand FLPs; TPC uses from 001 to 145, but 145 is reserved for SAC @@ -190,10 +190,16 @@ if workflow_has_parameter CALIB_PROXIES; then if [[ -n ${CALIBDATASPEC_TPCIDC_C:-} ]]; then add_semicolon_separated DATASPEC_LIST "\"$CALIBDATASPEC_TPCIDC_C\"" fi + if [[ -n ${CALIBDATASPEC_TPCCMV:-} ]]; then + add_semicolon_separated DATASPEC_LIST "\"$CALIBDATASPEC_TPCCMV\"" + fi if [[ -n ${CALIBDATASPEC_TPCSAC:-} ]]; then add_semicolon_separated DATASPEC_LIST "\"$CALIBDATASPEC_TPCSAC\"" fi - add_W o2-dpl-raw-proxy "--proxy-name tpcidc --io-threads 2 --dataspec \"$DATASPEC_LIST\" --sporadic-outputs --channel-config \"$CHANNELS_LIST\" ${TIMEFRAME_SHM_LIMIT+--timeframes-shm-limit} $TIMEFRAME_SHM_LIMIT" "" 0 + if [[ -z ${O2_TPC_IDC_CMV_IO_THREADS:-} ]]; then + O2_TPC_IDC_CMV_IO_THREADS=4; + fi + add_W o2-dpl-raw-proxy "--proxy-name tpcidc --io-threads ${O2_TPC_IDC_CMV_IO_THREADS} --dataspec \"$DATASPEC_LIST\" --sporadic-outputs --channel-config \"$CHANNELS_LIST\" ${TIMEFRAME_SHM_LIMIT+--timeframes-shm-limit} $TIMEFRAME_SHM_LIMIT" "" 0 fi elif [[ $AGGREGATOR_TASKS == CALO_TF ]]; then if [[ -n ${CALIBDATASPEC_CALO_TF:-} ]]; then @@ -301,6 +307,8 @@ threadFactorize=${O2_TPC_IDC_FACTORIZE_NTHREADS:-16} nTFs=$((1000 * 128 / ${NHBPERTF})) nTFs_SAC=$((10000 * 128 / ${NHBPERTF})) nBuffer=$((100 * 128 / ${NHBPERTF})) +nBuffer_cmv=$((50 * 128 / ${NHBPERTF})) +lanesCMVaggregate=${O2_TPC_CMV_AGGREGATE_NLANES:-8} IDC_DELTA="--disable-IDCDelta true" # off by default # deltas are on by default; you need to request explicitly to switch them off; if [[ "${DISABLE_IDC_DELTA:-}" == "1" ]]; then IDC_DELTA=""; fi @@ -308,17 +316,24 @@ if [[ "${ENABLE_IDC_DELTA_FILE:-}" == "1" ]]; then IDC_DELTA+=" --dump-IDCDelta- if [[ "${DISABLE_IDC_PAD_MAP_WRITING:-}" == 1 ]]; then TPC_WRITING_PAD_STATUS_MAP=""; else TPC_WRITING_PAD_STATUS_MAP="--enableWritingPadStatusMap true"; fi -if ! workflow_has_parameter CALIB_LOCAL_INTEGRATED_AGGREGATOR; then - if [[ $CALIB_TPC_IDC == 1 ]] && [[ $AGGREGATOR_TASKS == TPC_IDCBOTH_SAC || $AGGREGATOR_TASKS == ALL ]]; then - add_W o2-tpc-idc-distribute "--crus ${crus} --timeframes ${nTFs} --output-lanes ${lanesFactorize} --send-precise-timestamp true --condition-tf-per-query ${nTFs} --n-TFs-buffer ${nBuffer}" +if ! workflow_has_parameter CALIB_LOCAL_INTEGRATED_AGGREGATOR && [[ $AGGREGATOR_TASKS == TPC_IDCBOTH_SAC || $AGGREGATOR_TASKS == ALL ]]; then + if [[ $CALIB_TPC_IDC == 1 ]]; then + add_W o2-tpc-idc-distribute "--crus ${crus} --timeframes ${nTFs} --output-lanes ${lanesFactorize} --send-precise-timestamp true --condition-tf-per-query ${nTFs} --n-TFs-buffer ${nBuffer}" add_W o2-tpc-idc-factorize "--n-TFs-buffer ${nBuffer} --input-lanes ${lanesFactorize} --crus ${crus} --timeframes ${nTFs} --nthreads-grouping ${threadFactorize} --nthreads-IDC-factorization ${threadFactorize} --sendOutputFFT true --enable-CCDB-output true --enablePadStatusMap true ${TPC_WRITING_PAD_STATUS_MAP} --use-precise-timestamp true $IDC_DELTA" "TPCIDCGroupParam.groupPadsSectorEdges=32211" add_W o2-tpc-idc-ft-aggregator "--rangeIDC 200 --inputLanes ${lanesFactorize} --nFourierCoeff 40 --nthreads 8" fi - if [[ $CALIB_TPC_SAC == 1 ]] && [[ $AGGREGATOR_TASKS == TPC_IDCBOTH_SAC || $AGGREGATOR_TASKS == ALL ]]; then + if [[ $CALIB_TPC_CMV == 1 ]]; then + if [[ -z ${O2_TPC_CMV_COMPRESSION:-} ]]; then O2_TPC_CMV_COMPRESSION="--use-sparse --cmv-zero-threshold 1.0 --cmv-dynamic-precision-mean 1.0 --cmv-dynamic-precision-sigma 8.0 --use-compression-huffman"; fi + if [[ -z ${O2_TPC_CMV_TIMEFRAMES:-} ]]; then O2_TPC_CMV_TIMEFRAMES="2000"; fi + add_W o2-tpc-cmv-distribute "--crus ${crus} --lanes 1 --output-lanes ${lanesCMVaggregate} --n-TFs-buffer ${nBuffer_cmv} --timeframes ${O2_TPC_CMV_TIMEFRAMES} --send-precise-timestamp " + add_W o2-tpc-cmv-aggregate "--crus ${crus} --input-lanes ${lanesCMVaggregate} --n-TFs-buffer ${nBuffer_cmv} --nthreads-compression 4 --timeframes ${O2_TPC_CMV_TIMEFRAMES} --use-precise-timestamp ${O2_TPC_CMV_COMPRESSION} --output-dir $CALIB_DIR --meta-output-dir $EPN2EOS_METAFILES_DIR " + fi + if [[ $CALIB_TPC_SAC == 1 ]]; then add_W o2-tpc-sac-distribute "--timeframes ${nTFs_SAC} --output-lanes 1 " add_W o2-tpc-sac-factorize "--timeframes ${nTFs_SAC} --nthreads-SAC-factorization 4 --input-lanes 1 --compression 2" add_W o2-tpc-idc-ft-aggregator "--rangeIDC 200 --nFourierCoeff 40 --process-SACs true --inputLanes 1" fi + [[ $AGGREGATOR_TASKS == TPC_IDCBOTH_SAC ]] && [[ $CALIB_TPC_IDC == 0 && $CALIB_TPC_SAC == 0 && $CALIB_TPC_CMV == 1 ]] && CCDB_POPULATOR_UPLOAD_PATH="none" fi # Calo cal From b4a57b23e6c6059210b7dba477c541adc72c91c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuba=20G=C3=BCndem?= <48834043+tubagundem@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:14:32 +0200 Subject: [PATCH 022/102] TPC_CMV: Fix handling of buffered TFs, refactor tpc-cmv-distribute and create tpc-cmv-aggregate (#15308) * Fix handling of n-TFs-buffer in tpc-distribute-cmv workflow * Refactor cmv distribute workflow, create cmv aggregate workflow * Renamed nthreads to nthreads-compression * Fix CMV handling for empty and stale TF batches, handle empty Huffman symbol streams in CMVContainer * Add meta-output-dir to aggregate workflow * Fix return statement * Preserve DPL orbit in CMV TFs when FLP orbit is missing --- .../include/TPCCalibration/CMVContainer.h | 14 +- Detectors/TPC/calibration/macro/drawCMV.C | 16 +- .../TPC/calibration/src/CMVContainer.cxx | 13 +- Detectors/TPC/workflow/CMakeLists.txt | 7 +- Detectors/TPC/workflow/README.md | 43 +- .../include/TPCWorkflow/TPCAggregateCMVSpec.h | 720 ++++++++++++++++++ .../TPCWorkflow/TPCDistributeCMVSpec.h | 517 +++++-------- .../TPC/workflow/src/tpc-aggregate-cmv.cxx | 86 +++ .../TPC/workflow/src/tpc-distribute-cmv.cxx | 21 +- 9 files changed, 1070 insertions(+), 367 deletions(-) create mode 100644 Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h create mode 100644 Detectors/TPC/workflow/src/tpc-aggregate-cmv.cxx diff --git a/Detectors/TPC/calibration/include/TPCCalibration/CMVContainer.h b/Detectors/TPC/calibration/include/TPCCalibration/CMVContainer.h index f1904c3db8f8d..6f69a928d29ec 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/CMVContainer.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/CMVContainer.h @@ -58,9 +58,9 @@ struct CMVEncoding { /// kZigzag + kVarint → varint(zigzag(signed(raw))) /// kZigzag + kHuffman → [Huffman table] + [bitstream] of zigzag(signed(raw)) struct CMVPerTFCompressed { - uint32_t firstOrbit{0}; ///< First orbit of this TF - uint16_t firstBC{0}; ///< First bunch crossing of this TF - uint8_t mFlags{0}; ///< Bitmask of CMVEncoding values + uint32_t firstOrbit{0}; ///< First orbit of this TF + uint32_t firstOrbitDPL{0}; ///< First orbit of this TF + uint8_t mFlags{0}; ///< Bitmask of CMVEncoding values std::vector mData; ///< Encoded payload @@ -88,14 +88,14 @@ struct CMVPerTFCompressed { static void decodeDenseValues(const std::vector& symbols, uint8_t flags, CMVPerTF* cmv); public: - ClassDefNV(CMVPerTFCompressed, 1) + ClassDefNV(CMVPerTFCompressed, 2) }; /// CMV data for one TF across all CRUs /// Raw 16-bit CMV values are stored in a flat C array indexed as [cru * NTimeBinsPerTF + timeBin] struct CMVPerTF { - uint32_t firstOrbit{0}; ///< First orbit of this TF, from heartbeatOrbit of the first CMV packet - uint16_t firstBC{0}; ///< First bunch crossing of this TF, from heartbeatBC of the first CMV packet + uint32_t firstOrbit{0}; ///< First orbit of this TF, from heartbeatOrbit of the first CMV packet + uint32_t firstOrbitDPL{0}; ///< First orbit of this TF, from DPL // Raw 16-bit CMV values, flat array indexed as [cru * NTimeBinsPerTF + timeBin] uint16_t mDataPerTF[CRU::MaxCRU * cmv::NTimeBinsPerTF]{}; @@ -133,7 +133,7 @@ struct CMVPerTF { static void encodeVarintInto(uint32_t value, std::vector& out); ///< Varint encode public: - ClassDefNV(CMVPerTF, 1) + ClassDefNV(CMVPerTF, 2) }; } // namespace o2::tpc diff --git a/Detectors/TPC/calibration/macro/drawCMV.C b/Detectors/TPC/calibration/macro/drawCMV.C index 8a89157b75721..4f74db16cecd6 100644 --- a/Detectors/TPC/calibration/macro/drawCMV.C +++ b/Detectors/TPC/calibration/macro/drawCMV.C @@ -32,7 +32,7 @@ using namespace o2::tpc; /// \param filename input ROOT file containing the ccdb_object TTree /// \param outDir output directory for saved plots; nothing is saved if empty /// \return array of canvases -TObjArray* drawCMV(std::string_view filename, std::string_view outDir) +TObjArray* drawCMV(std::string_view filename, std::string_view outDir, std::string_view rootFileName = "CMVCanvases.root") { TObjArray* arrCanvases = new TObjArray; arrCanvases->SetName("CMV"); @@ -82,7 +82,7 @@ TObjArray* drawCMV(std::string_view filename, std::string_view outDir) 110, -100.5, 9.5); h2d->SetStats(1); TH1F* h1d = new TH1F("hCMV", ";Common Mode Values (ADC);Counts", - 1100, -100.5, 9.5); + 110, -100.5, 9.5); h1d->SetStats(1); // auto-detect branch format: compressed or raw @@ -105,6 +105,7 @@ TObjArray* drawCMV(std::string_view filename, std::string_view outDir) } long firstOrbit = -1; + long firstOrbitDPL = -1; for (int i = 0; i < nEntries; ++i) { tree->GetEntry(i); @@ -118,16 +119,15 @@ TObjArray* drawCMV(std::string_view filename, std::string_view outDir) tf = tfRaw; } - if (i == 0) { - firstOrbit = tf->firstOrbit; - } + firstOrbit = tf->firstOrbit; + firstOrbitDPL = tf->firstOrbitDPL; + fmt::print("firstOrbit: {}, firstOrbitDPL: {}\n", firstOrbit, firstOrbitDPL); for (int cru = 0; cru < nCRUs; ++cru) { for (int tb = 0; tb < nTimeBins; ++tb) { const float cmvValue = tf->getCMVFloat(cru, tb); h2d->Fill(tb, cmvValue); h1d->Fill(cmvValue); - // fmt::print("cru: {}, tb: {}, cmv: {}\n", cru, tb, cmvValue); } } } @@ -136,8 +136,6 @@ TObjArray* drawCMV(std::string_view filename, std::string_view outDir) tree->ResetBranchAddresses(); delete tfCompressed; - fmt::print("firstOrbit: {}\n", firstOrbit); - // draw auto* c = new TCanvas("cCMVvsTimeBin", ""); c->SetLogz(); @@ -152,7 +150,7 @@ TObjArray* drawCMV(std::string_view filename, std::string_view outDir) arrCanvases->Add(c1); if (outDir.size()) { - utils::saveCanvases(*arrCanvases, outDir, "png,pdf", "CMVCanvases.root"); + utils::saveCanvases(*arrCanvases, outDir, "", rootFileName); } f.Close(); diff --git a/Detectors/TPC/calibration/src/CMVContainer.cxx b/Detectors/TPC/calibration/src/CMVContainer.cxx index 5a3b8f1c63c3a..0e02d32e754d5 100644 --- a/Detectors/TPC/calibration/src/CMVContainer.cxx +++ b/Detectors/TPC/calibration/src/CMVContainer.cxx @@ -124,6 +124,15 @@ uint32_t decodeVarintLocal(const uint8_t*& data, const uint8_t* end) /// ceil(totalBits/8) bytes: MSB-first bitstream void huffmanEncode(const std::vector& symbols, std::vector& buf) { + if (symbols.empty()) { + // Write a valid empty Huffman stream: numSymbols=0, totalBits=0. + // The decoder handles this correctly (returns an empty symbol vector). + for (int i = 0; i < 12; ++i) { + buf.push_back(0); + } + return; + } + // Frequency count std::map freq; for (const uint32_t z : symbols) { @@ -438,7 +447,7 @@ CMVPerTFCompressed CMVPerTF::compress(uint8_t flags) const { CMVPerTFCompressed out; out.firstOrbit = firstOrbit; - out.firstBC = firstBC; + out.firstOrbitDPL = firstOrbitDPL; out.mFlags = flags; if (flags & CMVEncoding::kSparse) { @@ -661,7 +670,7 @@ void CMVPerTFCompressed::decompress(CMVPerTF* cmv) const throw std::invalid_argument("CMVPerTFCompressed::decompress: cmv pointer is null"); } cmv->firstOrbit = firstOrbit; - cmv->firstBC = firstBC; + cmv->firstOrbitDPL = firstOrbitDPL; std::fill(std::begin(cmv->mDataPerTF), std::end(cmv->mDataPerTF), uint16_t(0)); const uint8_t* ptr = mData.data(); diff --git a/Detectors/TPC/workflow/CMakeLists.txt b/Detectors/TPC/workflow/CMakeLists.txt index 0f8d73b1cbe7e..37ac398db40ec 100644 --- a/Detectors/TPC/workflow/CMakeLists.txt +++ b/Detectors/TPC/workflow/CMakeLists.txt @@ -304,4 +304,9 @@ o2_add_executable(cmv-distribute SOURCES src/tpc-distribute-cmv.cxx PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) -add_subdirectory(readers) \ No newline at end of file +o2_add_executable(cmv-aggregate + COMPONENT_NAME tpc + SOURCES src/tpc-aggregate-cmv.cxx + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + +add_subdirectory(readers) diff --git a/Detectors/TPC/workflow/README.md b/Detectors/TPC/workflow/README.md index b7a19da121e9b..5d2ccd3ac9166 100644 --- a/Detectors/TPC/workflow/README.md +++ b/Detectors/TPC/workflow/README.md @@ -285,7 +285,8 @@ The CMV workflows parse raw TPC data, buffer Common Mode Values per CRU on FLPs, |---|---|---| | `o2-tpc-cmv-to-vector` | `TPC/CMVVECTOR` | Parses raw TPC data and creates vectors of CMVs per CRU | | `o2-tpc-cmv-flp` | `TPC/CMVGROUP` | Buffers N TFs per CRU on the FLP and groups them for forwarding | -| `o2-tpc-cmv-distribute` | TTree / CCDB payload | Merges CRUs over N TFs on the calibration node, serializes the CMVContainer into a TTree, and either writes it to disk (`--dump-cmvs`) or forwards it as a CCDB object (`--enable-CCDB-output`) | +| `o2-tpc-cmv-distribute` | `TPC/CMVAGG*` | Routes grouped CMV batches from the calibration node to the aggregate workflow while preserving buffered TF and lane handling | +| `o2-tpc-cmv-aggregate` | TTree / CCDB payload | Collects all CRUs for each aggregate lane, preprocesses and compresses CMVs per buffered TF slice, then writes the CMVContainer TTree to disk (`--output-dir`) and/or forwards it as a CCDB object (`--enable-CCDB-output`) | #### `o2-tpc-cmv-to-vector` @@ -319,10 +320,27 @@ The CMV workflows parse raw TPC data, buffer Common Mode Values per CRU on FLPs, | `--timeframes` | 2000 | Number of TFs aggregated per calibration interval | | `--firstTF` | -1 | First time frame index; -1 = auto-detect from first incoming TF; values < -1 set an offset of `\|firstTF\|+1` TFs before the first interval begins | | `--lanes` | 1 | Number of parallel lanes (CRUs are split evenly across lanes) | +| `--output-lanes` | 1 | Number of aggregate pipelines downstream; these lanes rotate whole CMV aggregation intervals, not CRU subsets | | `--n-TFs-buffer` | 1 | Number of TFs buffered per group in the upstream `o2-tpc-cmv-flp` (must match that workflow's setting) | +| `--send-precise-timestamp` | false | Forward orbit-reset timing information needed by the aggregate workflow for precise CCDB validity timestamps | +| `--drop-data-after-nTFs` | 0 | Drop data for a relative TF slot after this many TFs have passed without receiving all CRUs; 0 uses the default derived from `--check-data-every-n` | +| `--check-data-every-n` | 0 | Check for missing CRU data every N invocations of the run function; -1 disables checking, 0 uses the default (timeframes/2) | +| `--nFactorTFs` | 1000 | Number of TFs to skip before flushing the oldest incomplete aggregation interval | + +#### `o2-tpc-cmv-aggregate` + +> **Important:** `--n-TFs-buffer` must be set to the same value as in `o2-tpc-cmv-distribute` and `o2-tpc-cmv-flp`. Mismatched values will silently corrupt the relTF mapping and TTree entry count. + +| Option | Default | Description | +|---|---|---| +| `--crus` | `0-359` | Full CRU range expected for each aggregate interval | +| `--timeframes` | 2000 | Number of TFs aggregated per calibration interval | +| `--input-lanes` | 1 | Number of aggregate pipelines; must match `o2-tpc-cmv-distribute --output-lanes` | +| `--n-TFs-buffer` | 1 | Number of real TFs packed into one CMV batch from upstream; **must match** `o2-tpc-cmv-distribute --n-TFs-buffer` | | `--enable-CCDB-output` | false | Forward the CMVContainer TTree as a CCDB object to `o2-calibration-ccdb-populator-workflow` | -| `--use-precise-timestamp` | false | Fetch orbit-reset and GRPECS from CCDB to compute a precise CCDB validity timestamp | -| `--dump-cmvs` | false | Write the CMVContainer TTree to a local ROOT file on disk | +| `--use-precise-timestamp` | false | Use orbit-reset timing forwarded by the distribute lane (requires `o2-tpc-cmv-distribute --send-precise-timestamp`) for precise CCDB validity start timestamps | +| `--output-dir` | `none` | Output directory for writing the CMVContainer ROOT file; must exist | +| `--nthreads` | 1 | Number of threads used for CMV preprocessing and compression; each thread processes a contiguous slice of buffered TFs | | `--use-sparse` | false | Sparse encoding: skip zero time bins (raw uint16 values; combine with `--use-compression-varint` or `--use-compression-huffman` for compressed sparse output) | | `--use-compression-varint` | false | Delta + zigzag + varint compression over all values; combined with `--use-sparse`: varint-encoded exact values at non-zero positions | | `--use-compression-huffman` | false | Huffman encoding over all values; combined with `--use-sparse`: Huffman-encoded exact values at non-zero positions | @@ -330,9 +348,6 @@ The CMV workflows parse raw TPC data, buffer Common Mode Values per CRU on FLPs, | `--cmv-round-integers-threshold` | 0 | Round values to nearest integer ADC for \|v\| ≤ N ADC before compression; 0 disables | | `--cmv-dynamic-precision-mean` | 1.0 | Gaussian centre in \|CMV\| (ADC) where the strongest fractional-bit trimming is applied | | `--cmv-dynamic-precision-sigma` | 0 | Gaussian width (ADC) for smooth CMV fractional-bit trimming; 0 disables | -| `--drop-data-after-nTFs` | 0 | Drop data for a relative TF slot after this many TFs have passed without receiving all CRUs; 0 uses the default derived from `--check-data-every-n` | -| `--check-data-every-n` | 0 | Check for missing CRU data every N invocations of the run function; -1 disables checking, 0 uses the default (timeframes/2) | -| `--nFactorTFs` | 1000 | Number of TFs to skip before flushing the oldest incomplete aggregation interval | ### Example 1 — Simple usage for testing @@ -361,7 +376,12 @@ o2-tpc-cmv-flp $ARGS_ALL \ --crus ${CRUS} | o2-tpc-cmv-distribute $ARGS_ALL \ --crus ${CRUS} \ - --dump-cmvs \ + --output-lanes 1 \ + --send-precise-timestamp \ +| +o2-tpc-cmv-aggregate $ARGS_ALL \ + --crus ${CRUS} \ + --output-dir ./ \ --enable-CCDB-output \ --cmv-zero-threshold 1.0 \ --cmv-dynamic-precision-mean 1.0 \ @@ -450,7 +470,12 @@ o2-dpl-raw-proxy $ARGS_ALL \ --dataspec "A:TPC/CMVGROUP;A:TPC/CMVORBITINFO" | o2-tpc-cmv-distribute $ARGS_ALL \ --crus ${CRUS} \ - --dump-cmvs \ + --output-lanes 1 \ + --send-precise-timestamp \ +| +o2-tpc-cmv-aggregate $ARGS_ALL \ + --crus ${CRUS} \ + --output-dir ./ \ --enable-CCDB-output \ --cmv-zero-threshold 1.0 \ --cmv-dynamic-precision-mean 1.0 \ @@ -461,4 +486,4 @@ o2-calibration-ccdb-populator-workflow $ARGS_ALL \ --ccdb-path ccdb-test.cern.ch:8080 ``` -The aggregator binds the ZeroMQ pull socket and waits for all FLPs to connect. Once `TPC/CMVGROUP` and `TPC/CMVORBITINFO` data arrive, `o2-tpc-cmv-distribute` merges them, applies compression, writes the object to the disk and uploads to the CCDB. +The aggregator binds the ZeroMQ pull socket and waits for all FLPs to connect. Once `TPC/CMVGROUP` and `TPC/CMVORBITINFO` data arrive, `o2-tpc-cmv-distribute` routes the grouped CMV batches, and `o2-tpc-cmv-aggregate` gathers the full CRU set for each interval, applies preprocessing and compression, writes the object to disk, and uploads it to the CCDB. diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h new file mode 100644 index 0000000000000..b46f2169f06c9 --- /dev/null +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h @@ -0,0 +1,720 @@ +// 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 TPCAggregateCMVSpec.h +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief TPC aggregation of distributed CMVs, including preprocessing, compression and CCDB output + +#ifndef O2_TPCAGGREGATECMVSPEC_H +#define O2_TPCAGGREGATECMVSPEC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "TMemFile.h" +#include "TParameter.h" +#include "Framework/Task.h" +#include "Framework/ControlService.h" +#include "Framework/Logger.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/DataTakingContext.h" +#include "Framework/DataRefUtils.h" +#include "Headers/DataHeader.h" +#include "Framework/ConfigParamRegistry.h" +#include "CommonDataFormat/Pair.h" +#include "CCDB/CcdbApi.h" +#include "CCDB/CcdbObjectInfo.h" +#include "DetectorsCalibration/Utils.h" +#include "TPCWorkflow/TPCDistributeCMVSpec.h" +#include "TPCWorkflow/ProcessingHelpers.h" +#include "TPCCalibration/CMVContainer.h" +#include "DataFormatsTPC/CMV.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include "MemoryResources/MemoryResources.h" +#include "CommonUtils/StringUtils.h" +#include "DetectorsCommonDataFormats/FileMetaData.h" + +using namespace o2::framework; +using o2::header::gDataOriginTPC; + +namespace o2::tpc +{ + +class TPCAggregateCMVDevice : public o2::framework::Task +{ + public: + TPCAggregateCMVDevice(const int lane, + const std::vector& crus, + const unsigned int timeframes, + const bool sendCCDB, + const bool usePreciseTimestamp, + const int nTFsBuffer, + std::shared_ptr req) + : mLaneId{lane}, + mCRUs{crus}, + mTimeFrames{timeframes}, + mSendCCDB{sendCCDB}, + mUsePreciseTimestamp{usePreciseTimestamp}, + mNTFsBuffer{nTFsBuffer}, + mProcessedCRU(timeframes), + mProcessedCRUs(timeframes), + mRawCMVs(timeframes), + mOrbitInfo(timeframes), + mOrbitStep(timeframes), + mOrbitInfoSeen(timeframes, false), + mTFCompleted(timeframes, false), + mCCDBRequest(req) + { + std::sort(mCRUs.begin(), mCRUs.end()); + for (auto& crusMap : mProcessedCRUs) { + crusMap.reserve(mCRUs.size()); + for (const auto cruID : mCRUs) { + crusMap.emplace(cruID, false); + } + } + initIntervalTree(); + } + + void init(o2::framework::InitContext& ic) final + { + o2::base::GRPGeomHelper::instance().setRequest(mCCDBRequest); + mOutputDir = ic.options().get("output-dir"); + if (mOutputDir != "/dev/null") { + mOutputDir = o2::utils::Str::rectifyDirectory(mOutputDir); + } + mMetaFileDir = ic.options().get("meta-output-dir"); + if (mMetaFileDir != "/dev/null") { + mMetaFileDir = o2::utils::Str::rectifyDirectory(mMetaFileDir); + } + mUseCompressionVarint = ic.options().get("use-compression-varint"); + mUseSparse = ic.options().get("use-sparse"); + mUseCompressionHuffman = ic.options().get("use-compression-huffman"); + mRoundIntegersThreshold = static_cast(ic.options().get("cmv-round-integers-threshold")); + mZeroThreshold = ic.options().get("cmv-zero-threshold"); + mDynamicPrecisionMean = ic.options().get("cmv-dynamic-precision-mean"); + mDynamicPrecisionSigma = ic.options().get("cmv-dynamic-precision-sigma"); + mThreads = std::max(1, ic.options().get("nthreads-compression")); + LOGP(info, "CMV aggregation settings: output-dir={}, use-compression-varint={}, use-sparse={}, use-compression-huffman={}, cmv-round-integers-threshold={}, cmv-zero-threshold={}, cmv-dynamic-precision-mean={}, cmv-dynamic-precision-sigma={}, nthreads-compression={}", + mOutputDir, mUseCompressionVarint, mUseSparse, mUseCompressionHuffman, mRoundIntegersThreshold, mZeroThreshold, mDynamicPrecisionMean, mDynamicPrecisionSigma, mThreads); + initIntervalTree(); + } + + void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final + { + o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj); + } + + void run(o2::framework::ProcessingContext& pc) final + { + // Consume CCDB inputs; return early when they are the only valid inputs in this slot + int nCCDBInputs = 0; + if (pc.inputs().isValid("grpecs")) { + pc.inputs().get("grpecs"); + ++nCCDBInputs; + } + if (mUsePreciseTimestamp && pc.inputs().isValid("orbitreset")) { + mTFInfo = pc.inputs().get>("orbitreset"); + ++nCCDBInputs; + } + if (nCCDBInputs > 0 && pc.inputs().countValidInputs() == nCCDBInputs) { + return; + } + + if (mSetDataTakingCont) { + mDataTakingContext = pc.services().get(); + mSetDataTakingCont = false; + } + + if (!mRun) { + mRun = processing_helpers::getRunNumber(pc); + } + + const auto currTF = processing_helpers::getCurrentTF(pc); + + if (mTFFirst == -1) { + for (auto& ref : InputRecordWalker(pc.inputs(), mFirstTFFilter)) { + mTFFirst = pc.inputs().get(ref); + mIntervalFirstTF = mTFFirst; + mHasIntervalFirstTF = true; + break; + } + } + + // EOS sentinel forwarded by the distribute lane for partial batches (n-TFs-buffer > actual TFs delivered) + if (currTF == std::numeric_limits::max()) { + if (mTimestampStart == 0) { + mTimestampStart = pc.services().get().creation; + } + collectEOSInputs(pc); + return; + } + + if (mTFFirst == -1) { + mTFFirst = currTF; + mIntervalFirstTF = mTFFirst; + mHasIntervalFirstTF = true; + LOGP(warning, "firstTF not found. Setting {} as first TF for aggregate lane {}", mTFFirst, mLaneId); + } + + const long relTF = (currTF - mTFFirst) / mNTFsBuffer; + if (relTF < 0) { + LOGP(warning, "relTF={} < 0 for TF {}, skipping", relTF, currTF); + return; + } + if (relTF >= static_cast(mTimeFrames)) { + // The distribute has advanced past this interval (empty CRU placeholders sent by checkMissingData + // arrive with the triggering TF's context, not the missing batch's context). + // Force-complete whatever was buffered so the next TF starts a fresh interval. + LOGP(warning, "relTF={} out of range [0, {}) for TF {}: force-completing stale interval and resetting", relTF, mTimeFrames, currTF); + if (mTimestampStart == 0) { + mTimestampStart = static_cast(pc.services().get().creation); + } + materializeBufferedTFs(true); + sendOutput(pc.outputs()); + // Advance mTFFirst to the interval containing currTF so that after reset() clears it to -1 + // we can restore a valid value. Without this, the distribute won't resend CMVFIRSTTF (it was + // already sent for the current interval), causing "firstTF not found" and further bad relTFs. + long nextFirst = mIntervalFirstTF + static_cast(mTimeFrames) * mNTFsBuffer; + while (static_cast(currTF) >= nextFirst + static_cast(mTimeFrames) * mNTFsBuffer) { + nextFirst += static_cast(mTimeFrames) * mNTFsBuffer; + } + reset(); + mTFFirst = nextFirst; + mIntervalFirstTF = nextFirst; + mHasIntervalFirstTF = true; + return; + } + + // Capture orbit info first so setTimestampCCDB can use the measured stride + if (!mOrbitInfoSeen[relTF]) { + // all CRUs within a batch carry identical timing, so the first one is sufficient + for (auto& ref : InputRecordWalker(pc.inputs(), mOrbitFilter)) { + mOrbitInfo[relTF] = pc.inputs().get(ref); + const auto batchFirstOrbit = static_cast(mOrbitInfo[relTF] >> 32); + // TimingInfo.firstTForbit is the orbit of the last real TF in the batch (the TF that triggered the FLP to send). + // The FLP provides the orbit of the first real TF. Interpolating between the two gives the true stride, + // independent of the GRPECS/config nHBFPerTF value. + const auto batchLastOrbit = static_cast(pc.services().get().firstTForbit); + const auto defaultOrbitStep = static_cast(o2::base::GRPGeomHelper::instance().getNHBFPerTF()); + mOrbitStep[relTF] = ((batchFirstOrbit > 0) && (mNTFsBuffer > 1) && (batchLastOrbit > batchFirstOrbit)) ? (batchLastOrbit - batchFirstOrbit) / static_cast(mNTFsBuffer - 1) : defaultOrbitStep; + mLastOrbitStep = mOrbitStep[relTF]; + mOrbitInfoSeen[relTF] = true; + break; + } + } + + if (mTimestampStart == 0) { + setTimestampCCDB(relTF, mOrbitStep[relTF], pc); + } + + for (auto& ref : InputRecordWalker(pc.inputs(), mFilter)) { + auto const* hdr = DataRefUtils::getHeader(ref); + const unsigned int cru = hdr->subSpecification; + if (!(std::binary_search(mCRUs.begin(), mCRUs.end(), cru))) { + LOGP(debug, "Received CMV data from CRU {} which is not part of this aggregate lane", cru); + continue; + } + if (mProcessedCRUs[relTF][cru]) { + continue; + } + + auto cmvVec = pc.inputs().get>(ref); + mRawCMVs[relTF][cru] = std::vector(cmvVec.begin(), cmvVec.end()); + mProcessedCRUs[relTF][cru] = true; + ++mProcessedCRU[relTF]; + } + + if (mProcessedCRU[relTF] == mCRUs.size() && !mTFCompleted[relTF]) { + mTFCompleted[relTF] = true; + ++mProcessedTFs; + mLastSeenTF = currTF; + } + + if (mProcessedTFs == mTimeFrames) { + materializeBufferedTFs(false); + sendOutput(pc.outputs()); + reset(); + } + } + + void endOfStream(o2::framework::EndOfStreamContext& ec) final + { + materializeBufferedTFs(true); + materializeEOSBuffer(); + sendOutput(ec.outputs()); + ec.services().get().readyToQuit(QuitRequest::Me); + } + + static constexpr header::DataDescription getDataDescriptionCCDBCMV() { return header::DataDescription{"TPC_CMV"}; } + + private: + struct PreparedTF { + CMVPerTF tf{}; + CMVPerTFCompressed compressed{}; + }; + + const int mLaneId{0}; ///< aggregate lane index (matches the distribute output lane) + std::vector mCRUs{}; ///< CRUs expected on this lane (sorted for binary_search) + const unsigned int mTimeFrames{}; ///< number of CMV batches per calibration interval (= total TFs / nTFsBuffer) + const bool mSendCCDB{false}; ///< send serialised TTree to the CCDB populator + const bool mUsePreciseTimestamp{false}; ///< use orbit-reset info forwarded by the distribute lane for precise CCDB timestamps + const int mNTFsBuffer{1}; ///< number of real TFs packed into one CMV batch (must match TPCFLPCMVSpec) + std::string mOutputDir{}; ///< directory to write local ROOT files ("/dev/null" to disable) + std::string mMetaFileDir{}; ///< directory to write calibration metadata files ("/dev/null" to disable) + o2::framework::DataTakingContext mDataTakingContext{}; + bool mSetDataTakingCont{true}; ///< flag to capture DataTakingContext only once + bool mUseCompressionVarint{false}; ///< delta+zigzag+varint compression for all values (dense path); combined with mUseSparse → sparse+varint + bool mUseSparse{false}; ///< sparse encoding (skip zero time bins); alone = raw uint16; combined with varint/Huffman → sparse+compressed + bool mUseCompressionHuffman{false}; ///< Huffman encoding; combined with mUseSparse → sparse+Huffman + uint16_t mRoundIntegersThreshold{0}; ///< round values to nearest integer ADC for |v| <= N ADC before compression; 0 = disabled + float mZeroThreshold{0.f}; ///< zero out CMV values whose float magnitude is below this threshold; 0 = disabled + float mDynamicPrecisionMean{1.f}; ///< Gaussian centre in |CMV| ADC where the strongest fractional-bit trimming is applied + float mDynamicPrecisionSigma{0.f}; ///< Gaussian width in ADC for fractional-bit trimming; 0 disables + int mThreads{1}; ///< number of threads for CMV preprocessing and compression in appendBatchToTree() + long mTFFirst{-1}; ///< absolute TF index of the first real TF in the current interval (-1 = not yet received) + long mTimestampStart{0}; ///< CCDB validity start timestamp in ms (0 until set by setTimestampCCDB) + long mIntervalFirstTF{0}; ///< absolute TF counter stored in the TTree UserInfo as "firstTF" + bool mHasIntervalFirstTF{false}; ///< true once mIntervalFirstTF has been set for the current interval + unsigned int mProcessedTFs{0}; ///< number of completed CMV batches in the current interval + std::vector mProcessedCRU{}; ///< counter of received CRUs per relTF slot; triggers completion when it reaches mCRUs.size() + std::vector> mProcessedCRUs{}; ///< per-CRU received flag per relTF ([relTF][CRU]); prevents double-counting on retransmission + std::vector>> mRawCMVs{}; ///< buffered raw CMV data per (relTF, CRU); unpacked in appendBatchToTree() + std::vector mOrbitInfo{}; ///< packed (firstOrbit << 32 | firstBC) per relTF, forwarded by the distribute lane + std::vector mOrbitStep{}; ///< per-sub-TF orbit stride per relTF; derived from actual batch timing + std::vector mOrbitInfoSeen{}; ///< true once orbit/BC has been captured for each relTF slot + std::vector mTFCompleted{}; ///< true once all CRUs have been received for a given relTF slot + std::unordered_map> mEOSRawCMVs{}; ///< CMV data received during the EOS sentinel path (partial batch at end of run) + uint32_t mEOSFirstOrbit{0}; ///< firstOrbit captured from the FLP's EOS partial-buffer flush + uint16_t mEOSFirstBC{0}; ///< firstBC captured from the FLP's EOS partial-buffer flush + uint32_t mLastOrbitStep{0}; ///< cached orbit stride from the last complete batch; fallback for the EOS partial batch + uint32_t mLastSeenTF{0}; ///< last TF counter seen in run(); used to compute lastTF metadata in the TTree + unsigned int mIntervalTFCount{0}; ///< number of TTree entries filled for the current interval + uint64_t mRun{0}; ///< run number, captured once per run + uint32_t mIntervalFirstOrbit{0}; ///< first orbit of the first TF in the current interval + uint32_t mIntervalLastOrbit{0}; ///< first orbit of the last TF in the current interval + uint32_t mFirstOrbitDPL{0}; ///< first orbit of the first TF in the current interval + bool mIntervalOrbitSet{false}; ///< true once first orbit has been captured for the current interval + dataformats::Pair mTFInfo{}; ///< orbit-reset time (ms) and NHBFPerTF forwarded by distribute lane 0 for precise timestamps + std::shared_ptr mCCDBRequest; ///< GRPECS request so GRPGeomHelper::getNHBFPerTF() is valid in this process + std::unique_ptr mIntervalTree{}; ///< in-memory TTree accumulating one entry per real TF; serialised to CCDB/disk at interval end + CMVPerTF mCurrentTF{}; ///< staging object written to the TTree branch for the uncompressed path + CMVPerTFCompressed mCurrentCompressedTF{}; ///< staging object written to the TTree branch when any compression flags are set + const std::vector mFilter{ + {"cmvagg", + ConcreteDataTypeMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(mLaneId)}, + Lifetime::Sporadic}}; + const std::vector mOrbitFilter{ + {"cmvorbit", + ConcreteDataMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(mLaneId), header::DataHeader::SubSpecificationType{static_cast(mLaneId)}}, + Lifetime::Sporadic}}; + const std::vector mFirstTFFilter{ + {"firstTF", + ConcreteDataMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{static_cast(mLaneId)}}, + Lifetime::Sporadic}}; + + uint8_t buildCompressionFlags() const + { + uint8_t flags = CMVEncoding::kNone; + if (mUseSparse) { + flags |= CMVEncoding::kSparse; + } + if (mUseCompressionHuffman) { + flags |= CMVEncoding::kDelta | CMVEncoding::kZigzag | CMVEncoding::kHuffman; + } else if (mUseCompressionVarint) { + flags |= CMVEncoding::kDelta | CMVEncoding::kZigzag | CMVEncoding::kVarint; + } + return flags; + } + + /// Create a fresh in-memory TTree for the next aggregation interval + /// Uses a single CMVPerTFCompressed branch whenever any compression is active or a raw CMVPerTF branch when no compression flags are set. + void initIntervalTree() + { + mIntervalTree = std::make_unique("ccdb_object", "ccdb_object"); + mIntervalTree->SetAutoSave(0); + mIntervalTree->SetDirectory(nullptr); + if (buildCompressionFlags() != CMVEncoding::kNone) { + mIntervalTree->Branch("CMVPerTFCompressed", &mCurrentCompressedTF); + } else { + mIntervalTree->Branch("CMVPerTF", &mCurrentTF); + } + } + + /// Accumulate CMV data from the EOS sentinel (TF == UINT32_MAX), i.e. a partial batch forwarded by the distribute lane when n-TFs-buffer > number of TFs actually delivered + /// Orbit/BC is captured once; raw data is appended per CRU into mEOSRawCMVs + void collectEOSInputs(o2::framework::ProcessingContext& pc) + { + if (mEOSFirstOrbit == 0) { + for (auto& ref : InputRecordWalker(pc.inputs(), mOrbitFilter)) { + const auto orbitBC = pc.inputs().get(ref); + mEOSFirstOrbit = static_cast(orbitBC >> 32); + mEOSFirstBC = static_cast(orbitBC & 0xFFFFu); + break; + } + } + + for (auto& ref : InputRecordWalker(pc.inputs(), mFilter)) { + auto const* hdr = DataRefUtils::getHeader(ref); + const unsigned int cru = hdr->subSpecification; + if (!(std::binary_search(mCRUs.begin(), mCRUs.end(), cru))) { + continue; + } + auto cmvVec = pc.inputs().get>(ref); + auto& buffer = mEOSRawCMVs[cru]; + buffer.insert(buffer.end(), cmvVec.begin(), cmvVec.end()); + } + } + + /// Set the CCDB validity start timestamp + /// When using precise timestamps, back-calculates the orbit-reset-referenced wall-clock time for the first real TF in the interval using the orbit-reset time forwarded by distribute lane 0. + /// orbitStep is the dynamically measured per-sub-TF stride; when non-zero it is preferred over the GRP NHBFPerTF for the orbit-offset calculation. + void setTimestampCCDB(const long relTF, const uint32_t orbitStep, o2::framework::ProcessingContext& pc) + { + const auto& tinfo = pc.services().get(); + if (mUsePreciseTimestamp && !mTFInfo.second) { + // Orbit-reset info (NHBFPerTF) not yet received from the distribute lane. + // Fall back to DPL wall-clock creation time so mTimestampStart is never + // left at 0, which would cause successive intervals to overwrite each other. + mTimestampStart = tinfo.creation; + LOGP(warning, "Orbit reset info not yet received; using DPL creation time {} ms as fallback timestamp for interval starting at TF {}", mTimestampStart, mTFFirst); + return; + } + // prefer the measured stride; fall back to NHBFPerTF from GRPECS + const int nHBFPerTF = (orbitStep > 0) ? static_cast(orbitStep) : o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + const auto nOrbitsOffset = (relTF * mNTFsBuffer + (mNTFsBuffer - 1)) * nHBFPerTF; + mFirstOrbitDPL = tinfo.firstTForbit - nOrbitsOffset; + mTimestampStart = mUsePreciseTimestamp ? (mTFInfo.first + (tinfo.firstTForbit - nOrbitsOffset) * o2::constants::lhc::LHCOrbitMUS * 0.001) : tinfo.creation; + LOGP(info, "Setting timestamp reset reference to: {}, at tfCounter: {}, firstTForbit: {}, NHBFPerTF: {}, relTF: {}, nOrbitsOffset: {}", + mTFInfo.first, tinfo.tfCounter, tinfo.firstTForbit, nHBFPerTF, relTF, nOrbitsOffset); + } + + /// Unpack and fill the TTree for all relTF slots that have been buffered during run(). + /// When includeIncomplete=false (normal interval end) only fully-received batches are filled. + /// When includeIncomplete=true (EOS flush) partial batches are also flushed with a warning. + void materializeBufferedTFs(const bool includeIncomplete) + { + for (unsigned int relTF = 0; relTF < mTimeFrames; ++relTF) { + if (mProcessedCRU[relTF] == 0) { + continue; + } + + if ((mProcessedCRU[relTF] != mCRUs.size()) && !includeIncomplete) { + continue; + } + + if ((mProcessedCRU[relTF] != mCRUs.size()) && includeIncomplete) { + LOGP(warning, "Aggregate lane {} flushing incomplete CMV batch relTF {} at EOS: received {} CRUs out of {}", mLaneId, relTF, mProcessedCRU[relTF], mCRUs.size()); + } + + if (!mHasIntervalFirstTF) { + mIntervalFirstTF = mTFFirst == -1 ? 0 : mTFFirst; + mHasIntervalFirstTF = true; + } + + // derive the actual number of sub-TFs from the buffer size; fall back to mNTFsBuffer if empty + const auto maxBufferSize = getMaxBufferSize(mRawCMVs[relTF]); + const int nTFsInBatch = maxBufferSize ? std::max(1, static_cast(maxBufferSize / cmv::NTimeBinsPerTF)) : mNTFsBuffer; + // fall back to GRP NHBFPerTF only if no orbit stride was measured for this relTF + const auto orbitStep = mOrbitStep[relTF] ? mOrbitStep[relTF] : static_cast(o2::base::GRPGeomHelper::instance().getNHBFPerTF()); + appendBatchToTree(mRawCMVs[relTF], mOrbitInfo[relTF], orbitStep, nTFsInBatch); + } + } + + /// Unpack and fill the TTree from the EOS partial-batch buffer (mEOSRawCMVs). + /// The number of real TFs is inferred from the raw buffer size divided by NTimeBinsPerTF. + /// Uses mLastOrbitStep from the last complete batch as the orbit stride fallback. + void materializeEOSBuffer() + { + if (mEOSRawCMVs.empty()) { + return; + } + + const auto maxBufferSize = getMaxBufferSize(mEOSRawCMVs); + const int nTFsInBatch = static_cast(maxBufferSize / cmv::NTimeBinsPerTF); + if (nTFsInBatch <= 0) { + return; + } + + if (!mHasIntervalFirstTF) { + mIntervalFirstTF = mLastSeenTF + 1; + mHasIntervalFirstTF = true; + } + + const uint64_t orbitInfo = (static_cast(mEOSFirstOrbit) << 32) | static_cast(mEOSFirstBC); + // use the actual stride seen in run(); fall back to GRP only if no complete batch was seen + const auto orbitStep = mLastOrbitStep ? mLastOrbitStep : static_cast(o2::base::GRPGeomHelper::instance().getNHBFPerTF()); + appendBatchToTree(mEOSRawCMVs, orbitInfo, orbitStep, nTFsInBatch); + mLastSeenTF += static_cast(nTFsInBatch); + } + + static size_t getMaxBufferSize(const std::unordered_map>& rawCMVs) + { + size_t maxBufferSize = 0; + for (const auto& [cru, values] : rawCMVs) { + maxBufferSize = std::max(maxBufferSize, values.size()); + } + return maxBufferSize; + } + + /// Unpack nTFsInBatch real TFs from rawCMVs, apply preprocessing (rounding, zeroing, trimming), + /// optionally compress them, and fill one TTree entry per real TF. + /// Processing is parallelised across nThreads workers using std::thread (each thread owns a disjoint chunk). + void appendBatchToTree(const std::unordered_map>& rawCMVs, const uint64_t orbitInfo, const uint32_t orbitStep, const int nTFsInBatch) + { + if (nTFsInBatch <= 0) { + return; + } + + const auto firstOrbit = static_cast(orbitInfo >> 32); + const auto firstBC = static_cast(orbitInfo & 0xFFFFu); + // Use the DPL-derived orbit as fallback when the FLP orbit info is missing (firstOrbit == 0) + const auto batchFirstOrbitDPL = (firstOrbit > 0) ? firstOrbit : mFirstOrbitDPL; + if (!mIntervalOrbitSet) { + mIntervalFirstOrbit = batchFirstOrbitDPL; + mIntervalOrbitSet = true; + } + mIntervalLastOrbit = batchFirstOrbitDPL + static_cast(nTFsInBatch - 1) * orbitStep; + const uint8_t flags = buildCompressionFlags(); + std::vector prepared(nTFsInBatch); + const int nThreads = std::max(1, std::min(mThreads, nTFsInBatch)); + const int chunkSize = (nTFsInBatch + nThreads - 1) / nThreads; + + auto worker = [&](const int iThread) { + const int beginTF = iThread * chunkSize; + const int endTF = std::min(nTFsInBatch, beginTF + chunkSize); + for (int tfIndex = beginTF; tfIndex < endTF; ++tfIndex) { + + auto& preparedTF = prepared[tfIndex]; + preparedTF.tf.firstOrbit = firstOrbit + static_cast(tfIndex) * orbitStep; + preparedTF.tf.firstOrbitDPL = batchFirstOrbitDPL + static_cast(tfIndex) * orbitStep; + + for (const auto& [cru, values] : rawCMVs) { + const uint32_t offset = static_cast(tfIndex) * cmv::NTimeBinsPerTF; + if (offset >= static_cast(values.size())) { + continue; + } + const uint32_t nBins = std::min(static_cast(values.size()) - offset, cmv::NTimeBinsPerTF); + for (uint32_t tb = 0; tb < nBins; ++tb) { + preparedTF.tf.mDataPerTF[cru * cmv::NTimeBinsPerTF + tb] = values[offset + tb]; + } + } + + preparedTF.tf.roundToIntegers(mRoundIntegersThreshold); + if (mZeroThreshold > 0.f) { + preparedTF.tf.zeroSmallValues(mZeroThreshold); + } + if (mDynamicPrecisionSigma > 0.f) { + preparedTF.tf.trimGaussianPrecision(mDynamicPrecisionMean, mDynamicPrecisionSigma); + } + if (flags != CMVEncoding::kNone) { + preparedTF.compressed = preparedTF.tf.compress(flags); + } + } + }; + + std::vector workers; + workers.reserve(nThreads - 1); + for (int iThread = 1; iThread < nThreads; ++iThread) { + workers.emplace_back(worker, iThread); + } + worker(0); + for (auto& thread : workers) { + thread.join(); + } + + for (int tfIndex = 0; tfIndex < nTFsInBatch; ++tfIndex) { + if (flags != CMVEncoding::kNone) { + mCurrentCompressedTF = std::move(prepared[tfIndex].compressed); + } else { + mCurrentTF = std::move(prepared[tfIndex].tf); + } + mIntervalTree->Fill(); + ++mIntervalTFCount; + } + } + + void sendOutput(DataAllocator& output) + { + using timer = std::chrono::high_resolution_clock; + + if (mIntervalTFCount == 0) { + LOGP(warning, "CMV interval is empty at sendOutput for lane {}, skipping", mLaneId); + return; + } + + const auto lastTF = mIntervalFirstTF + static_cast(mIntervalTFCount) - 1; + mIntervalTree->GetUserInfo()->Clear(); + mIntervalTree->GetUserInfo()->Add(new TParameter("firstTF", mIntervalFirstTF)); + mIntervalTree->GetUserInfo()->Add(new TParameter("lastTF", lastTF)); + + LOGP(info, "CMVPerTF TTree lane {}: {} entries, firstTF={}, lastTF={}", mLaneId, mIntervalTFCount, mIntervalFirstTF, lastTF); + auto start = timer::now(); + + const int nHBFPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + const long timeStampEnd = mTimestampStart + static_cast(mIntervalTFCount * nHBFPerTF * o2::constants::lhc::LHCOrbitMUS * 1e-3); + + if (mOutputDir != "/dev/null") { + const std::string calibFName = fmt::format("CMV_run_{}_orbit_{}_{}_timestamp_{}_{}.root", + mRun, mIntervalFirstOrbit, mIntervalLastOrbit, mTimestampStart, timeStampEnd); + try { + CMVPerTF::writeToFile(mOutputDir + calibFName, mIntervalTree); + LOGP(info, "CMV file written to {}", mOutputDir + calibFName); + } catch (const std::exception& e) { + LOGP(error, "Failed to write CMV file {}: {}", mOutputDir + calibFName, e.what()); + } + + if (mMetaFileDir != "/dev/null") { + o2::dataformats::FileMetaData calMetaData; + calMetaData.fillFileData(mOutputDir + calibFName); + calMetaData.setDataTakingContext(mDataTakingContext); + calMetaData.type = "calib"; + calMetaData.priority = "low"; + auto metaFileNameTmp = fmt::format("{}{}.tmp", mMetaFileDir, calibFName); + auto metaFileName = fmt::format("{}{}.done", mMetaFileDir, calibFName); + try { + std::ofstream metaFileOut(metaFileNameTmp); + metaFileOut << calMetaData; + metaFileOut.close(); + std::filesystem::rename(metaFileNameTmp, metaFileName); + } catch (std::exception const& e) { + LOG(error) << "Failed to store CMV meta data file " << metaFileName << ", reason: " << e.what(); + } + } + } + + if ((!mSendCCDB) && (mOutputDir == "/dev/null")) { + LOGP(warning, "Neither CCDB output nor output-dir is enabled for aggregate lane {}, skipping CMV export", mLaneId); + } + if (!mSendCCDB) { + return; + } + + if (timeStampEnd <= mTimestampStart) { + LOGP(warning, "Invalid CCDB timestamp range start:{} end:{}, skipping upload", mTimestampStart, timeStampEnd); + return; + } + + o2::ccdb::CcdbObjectInfo ccdbInfoCMV("TPC/Calib/CMV", "TTree", "CMV.root", {}, mTimestampStart, timeStampEnd); + auto image = o2::ccdb::CcdbApi::createObjectImage((mIntervalTree.get()), &ccdbInfoCMV); + // trim TMemFile zero-padding: GetSize() is block-rounded, GetEND() is the actual file end + { + TMemFile mf("trim", image->data(), static_cast(image->size()), "READ"); + image->resize(static_cast(mf.GetEND())); + mf.Close(); + } + + LOGP(info, "Sending object {} / {} of size {} bytes, valid for {} : {}", ccdbInfoCMV.getPath(), ccdbInfoCMV.getFileName(), image->size(), ccdbInfoCMV.getStartValidityTimestamp(), ccdbInfoCMV.getEndValidityTimestamp()); + output.snapshot(Output{o2::calibration::Utils::gDataOriginCDBPayload, getDataDescriptionCCDBCMV(), 0}, *image); + output.snapshot(Output{o2::calibration::Utils::gDataOriginCDBWrapper, getDataDescriptionCCDBCMV(), 0}, ccdbInfoCMV); + + auto stop = timer::now(); + std::chrono::duration elapsed = stop - start; + LOGP(info, "CMV CCDB serialisation time: {:.3f} s", elapsed.count()); + } + + /// Reset all per-interval state after a successful sendOutput(); prepares for the next interval + void reset() + { + mTFFirst = -1; + mTimestampStart = 0; + mIntervalFirstTF = 0; + mHasIntervalFirstTF = false; + mProcessedTFs = 0; + std::fill(mProcessedCRU.begin(), mProcessedCRU.end(), 0); + std::fill(mOrbitInfo.begin(), mOrbitInfo.end(), 0); + std::fill(mOrbitStep.begin(), mOrbitStep.end(), 0); + std::fill(mOrbitInfoSeen.begin(), mOrbitInfoSeen.end(), false); + std::fill(mTFCompleted.begin(), mTFCompleted.end(), false); + for (auto& processedMap : mProcessedCRUs) { + for (auto& [cru, seen] : processedMap) { + seen = false; + } + } + for (auto& rawPerTF : mRawCMVs) { + rawPerTF.clear(); + } + mEOSRawCMVs.clear(); + mEOSFirstOrbit = 0; + mEOSFirstBC = 0; + mLastOrbitStep = 0; + mLastSeenTF = 0; + mIntervalTFCount = 0; + mIntervalFirstOrbit = 0; + mIntervalLastOrbit = 0; + mFirstOrbitDPL = 0; + mIntervalOrbitSet = false; + mCurrentTF = CMVPerTF{}; + mCurrentCompressedTF = CMVPerTFCompressed{}; + initIntervalTree(); + } +}; + +/// Build a DataProcessorSpec for one aggregate lane +/// Each lane receives CMV data from one distribute output lane (matched by lane index) and expects the full CRU list — the distribute stage already routes per-CRU data to the correct lane +inline DataProcessorSpec getTPCAggregateCMVSpec(const int lane, + const std::vector& crus, + const unsigned int timeframes, + const bool sendCCDB, + const bool usePreciseTimestamp, + const int nTFsBuffer = 1) +{ + std::vector outputSpecs; + if (sendCCDB) { + outputSpecs.emplace_back(ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBPayload, TPCAggregateCMVDevice::getDataDescriptionCCDBCMV()}, Lifetime::Sporadic); + outputSpecs.emplace_back(ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBWrapper, TPCAggregateCMVDevice::getDataDescriptionCCDBCMV()}, Lifetime::Sporadic); + } + + std::vector inputSpecs; + inputSpecs.emplace_back(InputSpec{"cmvagg", ConcreteDataTypeMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(lane)}, Lifetime::Sporadic}); + inputSpecs.emplace_back(InputSpec{"cmvorbit", gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(lane), header::DataHeader::SubSpecificationType{static_cast(lane)}, Lifetime::Sporadic}); + inputSpecs.emplace_back(InputSpec{"firstTF", gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{static_cast(lane)}, Lifetime::Sporadic}); + if (usePreciseTimestamp) { + inputSpecs.emplace_back(InputSpec{"orbitreset", gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{static_cast(lane)}, Lifetime::Sporadic}); + } + + // Request GRPECS from CCDB so that GRPGeomHelper::getNHBFPerTF() is valid in this (separate) process + auto ccdbRequest = std::make_shared(false, // orbitResetTime + true, // GRPECS (NHBFPerTF) + false, // GRPLHCIF + false, // GRPMagField + false, // askMatLUT + o2::base::GRPGeomRequest::None, // geometry + inputSpecs); + + DataProcessorSpec spec{ + fmt::format("tpc-aggregate-cmv-{:02}", lane).data(), + inputSpecs, + outputSpecs, + AlgorithmSpec{adaptFromTask(lane, crus, timeframes, sendCCDB, usePreciseTimestamp, nTFsBuffer, ccdbRequest)}, + Options{{"output-dir", VariantType::String, "/dev/null", {"CMV output directory, must exist (if not /dev/null)"}}, + {"meta-output-dir", VariantType::String, "/dev/null", {"calibration metadata output directory, must exist (if not /dev/null)"}}, + {"nthreads-compression", VariantType::Int, 1, {"Number of threads used for CMV per timeframe preprocessing and compression"}}, + {"use-sparse", VariantType::Bool, false, {"Sparse encoding (skip zero time bins). Alone: raw uint16 values. With --use-compression-varint: varint exact values. With --use-compression-huffman: Huffman exact values"}}, + {"use-compression-varint", VariantType::Bool, false, {"Delta+zigzag+varint compression (all values). Combined with --use-sparse: sparse positions + varint encoded exact CMV values"}}, + {"use-compression-huffman", VariantType::Bool, false, {"Huffman encoding. Combined with --use-sparse: sparse positions + Huffman-encoded exact CMV values"}}, + {"cmv-zero-threshold", VariantType::Float, 0.f, {"Zero out CMV values whose float magnitude is below this threshold after optional integer rounding and before compression; 0 disables"}}, + {"cmv-round-integers-threshold", VariantType::Int, 0, {"Round values to nearest integer ADC for |v| <= N ADC before compression; 0 disables"}}, + {"cmv-dynamic-precision-mean", VariantType::Float, 1.f, {"Gaussian centre in |CMV| ADC where the strongest fractional bit trimming is applied"}}, + {"cmv-dynamic-precision-sigma", VariantType::Float, 0.f, {"Gaussian width in ADC for smooth CMV fractional bit trimming; 0 disables"}}}}; + spec.rank = lane; + return spec; +} + +} // namespace o2::tpc + +#endif diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h index c1744ce86d3ac..f3373070ab7bb 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h @@ -11,15 +11,17 @@ /// @file TPCDistributeCMVSpec.h /// @author Tuba Gündem, tuba.gundem@cern.ch -/// @brief TPC aggregation of grouped CMVs +/// @brief TPC distribution of grouped CMVs towards the CMV aggregation workflow #ifndef O2_TPCDISTRIBUTECMVSPEC_H #define O2_TPCDISTRIBUTECMVSPEC_H +#include +#include +#include +#include #include -#include #include -#include "TParameter.h" #include "Framework/Task.h" #include "Framework/ControlService.h" #include "Framework/Logger.h" @@ -33,12 +35,6 @@ #include "TPCWorkflow/ProcessingHelpers.h" #include "DetectorsBase/GRPGeomHelper.h" #include "CommonDataFormat/Pair.h" -#include "TMemFile.h" -#include "CCDB/CcdbApi.h" -#include "CCDB/CcdbObjectInfo.h" -#include "DetectorsCalibration/Utils.h" -#include "TPCCalibration/CMVContainer.h" -#include "DataFormatsTPC/CMV.h" using namespace o2::framework; using o2::header::gDataOriginTPC; @@ -50,20 +46,25 @@ namespace o2::tpc class TPCDistributeCMVSpec : public o2::framework::Task { public: - TPCDistributeCMVSpec(const std::vector& crus, const unsigned int timeframes, const int nTFsBuffer, const int firstTF, const bool sendCCDB, const bool usePreciseTimestamp, std::shared_ptr req) + TPCDistributeCMVSpec(const std::vector& crus, const unsigned int timeframes, const int nTFsBuffer, const unsigned int outlanes, const int firstTF, std::shared_ptr req) : mCRUs{crus}, mTimeFrames{timeframes}, mNTFsBuffer{nTFsBuffer}, + mOutLanes{outlanes}, mProcessedCRU{{std::vector(timeframes), std::vector(timeframes)}}, - mTFStart{{firstTF, firstTF + timeframes}}, - mTFEnd{{firstTF + timeframes - 1, mTFStart[1] + timeframes - 1}}, + mTFStart{{firstTF, firstTF + static_cast(timeframes) * nTFsBuffer}}, + mTFEnd{{firstTF + static_cast(timeframes) * nTFsBuffer - 1, firstTF + 2LL * timeframes * nTFsBuffer - 1}}, mCCDBRequest(req), - mSendCCDB{sendCCDB}, - mUsePreciseTimestamp{usePreciseTimestamp}, - mSendCCDBOutputOrbitReset(1), - mSendCCDBOutputGRPECS(1), + mSendCCDBOutputOrbitReset(outlanes), + mSendCCDBOutputGRPECS(outlanes), mOrbitInfoForwarded{{std::vector(timeframes, false), std::vector(timeframes, false)}} { + mDataDescrOut.reserve(mOutLanes); + mOrbitDescrOut.reserve(mOutLanes); + for (unsigned int i = 0; i < mOutLanes; ++i) { + mDataDescrOut.emplace_back(getDataDescriptionCMV(i)); + mOrbitDescrOut.emplace_back(getDataDescriptionCMVOrbitInfo(i)); + } // sort vector for binary_search std::sort(mCRUs.begin(), mCRUs.end()); @@ -79,10 +80,7 @@ class TPCDistributeCMVSpec : public o2::framework::Task mFilter.emplace_back(InputSpec{"cmvsgroup", ConcreteDataTypeMatcher{gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup()}, Lifetime::Sporadic}); mOrbitFilter.emplace_back(InputSpec{"cmvorbit", ConcreteDataTypeMatcher{gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo()}, Lifetime::Sporadic}); - - // pre-allocate the accumulator TTree for the current aggregation interval - initIntervalTree(); - }; + } void init(o2::framework::InitContext& ic) final { @@ -97,33 +95,21 @@ class TPCDistributeCMVSpec : public o2::framework::Task } mNTFsDataDrop = mCheckEveryNData; } - mDumpCMVs = ic.options().get("dump-cmvs"); - mUseCompressionVarint = ic.options().get("use-compression-varint"); - mUseSparse = ic.options().get("use-sparse"); - mUseCompressionHuffman = ic.options().get("use-compression-huffman"); - mRoundIntegersThreshold = static_cast(ic.options().get("cmv-round-integers-threshold")); - mZeroThreshold = ic.options().get("cmv-zero-threshold"); - mDynamicPrecisionMean = ic.options().get("cmv-dynamic-precision-mean"); - mDynamicPrecisionSigma = ic.options().get("cmv-dynamic-precision-sigma"); - LOGP(info, "CMV compression settings: use-compression-varint={}, use-sparse={}, use-compression-huffman={}, cmv-round-integers-threshold={}, cmv-zero-threshold={}, cmv-dynamic-precision-mean={}, cmv-dynamic-precision-sigma={}", - mUseCompressionVarint, mUseSparse, mUseCompressionHuffman, mRoundIntegersThreshold, mZeroThreshold, mDynamicPrecisionMean, mDynamicPrecisionSigma); - // re-initialise the interval tree now that compression options are known (constructor used the defaults) - initIntervalTree(); } void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final { o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj); if (matcher == ConcreteDataMatcher("CTP", "ORBITRESET", 0)) { - LOGP(info, "Updating ORBITRESET"); + LOGP(debug, "Updating ORBITRESET"); std::fill(mSendCCDBOutputOrbitReset.begin(), mSendCCDBOutputOrbitReset.end(), true); } else if (matcher == ConcreteDataMatcher("GLO", "GRPECS", 0)) { // check if received object is valid if (o2::base::GRPGeomHelper::instance().getGRPECS()->getRun() != 0) { - LOGP(info, "Updating GRPECS"); + LOGP(debug, "Updating GRPECS"); std::fill(mSendCCDBOutputGRPECS.begin(), mSendCCDBOutputGRPECS.end(), true); } else { - LOGP(info, "Detected default GRPECS object"); + LOGP(debug, "Detected default GRPECS object"); } } } @@ -143,44 +129,43 @@ class TPCDistributeCMVSpec : public o2::framework::Task if (pc.inputs().countValidInputs() == (grpecsValid + orbitResetValid)) { return; } - // update mTFInfo from GRPGeomHelper whenever orbit-reset or GRPECS objects are fresh - if (mSendCCDBOutputOrbitReset[0] && mSendCCDBOutputGRPECS[0]) { - mSendCCDBOutputOrbitReset[0] = false; - mSendCCDBOutputGRPECS[0] = false; - mTFInfo = dataformats::Pair{o2::base::GRPGeomHelper::instance().getOrbitResetTimeMS(), o2::base::GRPGeomHelper::instance().getNHBFPerTF()}; - } } const auto tf = processing_helpers::getCurrentTF(pc); - mLastSeenTF = tf; // track for endOfStream flush + if (tf == std::numeric_limits::max()) { + forwardEOSData(pc); + return; + } // automatically detect firstTF in case firstTF was not specified if (mTFStart.front() <= -1) { - const auto firstTF = tf; + const auto firstTFDetected = tf; const long offsetTF = std::abs(mTFStart.front() + 1); const auto nTotTFs = getNRealTFs(); - mTFStart = {firstTF + offsetTF, firstTF + offsetTF + nTotTFs}; + // tf is the batch TF counter (= last real TF in the first batch), subtract (mNTFsBuffer - 1) to recover the actual first real TF of the interval + const long firstRealTF = static_cast(firstTFDetected) - (mNTFsBuffer - 1) + offsetTF; + mTFStart = {firstRealTF, firstRealTF + nTotTFs}; mTFEnd = {mTFStart[1] - 1, mTFStart[1] - 1 + nTotTFs}; - LOGP(info, "Setting {} as first TF", mTFStart[0]); - LOGP(info, "Using offset of {} TFs for setting the first TF", offsetTF); + LOGP(detail, "Setting {} as first TF", mTFStart[0]); + LOGP(detail, "Using offset of {} TFs for setting the first TF", offsetTF); } // check which buffer to use for current incoming data const bool currentBuffer = (tf > mTFEnd[mBuffer]) ? !mBuffer : mBuffer; if (mTFStart[currentBuffer] > tf) { - LOGP(info, "All CRUs for current TF {} already received. Skipping this TF", tf); + LOGP(detail, "All CRUs for current TF {} already received. Skipping this TF", tf); return; } + const unsigned int currentOutLane = getOutLane(tf); const unsigned int relTF = (tf - mTFStart[currentBuffer]) / mNTFsBuffer; - LOGP(info, "Current TF: {}, relative TF: {}, current buffer: {}, mTFStart: {}", tf, relTF, currentBuffer, mTFStart[currentBuffer]); + LOGP(debug, "Current TF: {}, relative TF: {}, current buffer: {}, current output lane: {}, mTFStart: {}", tf, relTF, currentBuffer, currentOutLane, mTFStart[currentBuffer]); if (relTF >= mProcessedCRU[currentBuffer].size()) { LOGP(warning, "Skipping tf {}: relative tf {} is larger than size of buffer: {}", tf, relTF, mProcessedCRU[currentBuffer].size()); - // check number of processed CRUs for previous TFs. If CRUs are missing for them, they are probably lost/not received mProcessedTotalData = mCheckEveryNData; - checkIntervalsForMissingData(pc, currentBuffer, relTF, tf); + checkIntervalsForMissingData(pc, currentBuffer, relTF, currentOutLane, tf); return; } @@ -188,33 +173,18 @@ class TPCDistributeCMVSpec : public o2::framework::Task return; } - // record the absolute first TF of this aggregation interval - if (mIntervalTFCount == 0) { - mIntervalFirstTF = tf; + if (mSendOutputStartInfo[currentBuffer]) { + mSendOutputStartInfo[currentBuffer] = false; + pc.outputs().snapshot(Output{gDataOriginTPC, getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{currentOutLane}}, mTFStart[currentBuffer]); } - // set CCDB start timestamp once at the start of each aggregation interval - if (mTimestampStart == 0) { - setTimestampCCDB(relTF, pc); + if (mSendCCDBOutputOrbitReset[currentOutLane] && mSendCCDBOutputGRPECS[currentOutLane]) { + mSendCCDBOutputOrbitReset[currentOutLane] = false; + mSendCCDBOutputGRPECS[currentOutLane] = false; + pc.outputs().snapshot(Output{gDataOriginTPC, getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{currentOutLane}}, dataformats::Pair{o2::base::GRPGeomHelper::instance().getOrbitResetTimeMS(), o2::base::GRPGeomHelper::instance().getNHBFPerTF()}); } - // capture orbit/BC info into the interval once per relTF. - // all CRUs within a TF carry identical timing, so the first one is sufficient. - if (!mOrbitInfoForwarded[currentBuffer][relTF]) { - for (auto& ref : InputRecordWalker(pc.inputs(), mOrbitFilter)) { - auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); - const unsigned int cru = hdr->subSpecification >> 7; - if (std::binary_search(mCRUs.begin(), mCRUs.end(), cru)) { - const auto orbitBC = pc.inputs().get(ref); - if (mCurrentTF.firstOrbit == 0 && mCurrentTF.firstBC == 0) { - mCurrentTF.firstOrbit = static_cast(orbitBC >> 32); - mCurrentTF.firstBC = static_cast(orbitBC & 0xFFFFu); - } - mOrbitInfoForwarded[currentBuffer][relTF] = true; - break; // one per relTF is enough - } - } - } + forwardOrbitInfo(pc, currentBuffer, relTF, currentOutLane); for (auto& ref : InputRecordWalker(pc.inputs(), mFilter)) { auto const* tpcCRUHeader = o2::framework::DataRefUtils::getHeader(ref); @@ -222,79 +192,43 @@ class TPCDistributeCMVSpec : public o2::framework::Task // check if cru is specified in input cru list if (!(std::binary_search(mCRUs.begin(), mCRUs.end(), cru))) { - LOGP(info, "Received data from CRU: {} which was not specified as input. Skipping", cru); + LOGP(debug, "Received data from CRU: {} which was not specified as input. Skipping", cru); continue; } if (mProcessedCRUs[currentBuffer][relTF][cru]) { continue; - } else { - // count total number of processed CRUs for given TF - ++mProcessedCRU[currentBuffer][relTF]; - - // to keep track of processed CRUs - mProcessedCRUs[currentBuffer][relTF][cru] = true; } + // count total number of processed CRUs for given TF + ++mProcessedCRU[currentBuffer][relTF]; + // to keep track of processed CRUs + mProcessedCRUs[currentBuffer][relTF][cru] = true; - // accumulate raw 16-bit CMVs into the flat array for the current TF - auto cmvVec = pc.inputs().get>(ref); - const uint32_t nTimeBins = std::min(static_cast(cmvVec.size()), cmv::NTimeBinsPerTF); - for (uint32_t tb = 0; tb < nTimeBins; ++tb) { - mCurrentTF.mDataPerTF[cru * cmv::NTimeBinsPerTF + tb] = cmvVec[tb]; - } + sendOutput(pc, currentOutLane, cru, pc.inputs().get>(ref)); } - LOGP(info, "Number of received CRUs for current TF: {} Needed a total number of processed CRUs of: {} Current TF: {}", mProcessedCRU[currentBuffer][relTF], mCRUs.size(), tf); + LOGP(detail, "Number of received CRUs for current TF: {} Needed a total number of processed CRUs of: {} Current TF: {}", mProcessedCRU[currentBuffer][relTF], mCRUs.size(), tf); // check for missing data if specified if (mNTFsDataDrop > 0) { - checkIntervalsForMissingData(pc, currentBuffer, relTF, tf); + checkIntervalsForMissingData(pc, currentBuffer, relTF, currentOutLane, tf); } if (mProcessedCRU[currentBuffer][relTF] == mCRUs.size()) { ++mProcessedTFs[currentBuffer]; - - // Pre-processing: quantisation / rounding / zeroing (applied before compression) - mCurrentTF.roundToIntegers(mRoundIntegersThreshold); - if (mZeroThreshold > 0.f) { - mCurrentTF.zeroSmallValues(mZeroThreshold); - } - if (mDynamicPrecisionSigma > 0.f) { - mCurrentTF.trimGaussianPrecision(mDynamicPrecisionMean, mDynamicPrecisionSigma); - } - - // Compress; the raw CMVPerTF branch is used when all flags are zero - const uint8_t flags = buildCompressionFlags(); - if (flags != CMVEncoding::kNone) { - mCurrentCompressedTF = mCurrentTF.compress(flags); - } - - mIntervalTree->Fill(); - ++mIntervalTFCount; - mCurrentTF = CMVPerTF{}; } if (mProcessedTFs[currentBuffer] == mTimeFrames) { - sendOutput(pc.outputs(), tf); - finishInterval(pc, currentBuffer, tf); + finishInterval(pc, currentOutLane, currentBuffer, tf); } } - void endOfStream(o2::framework::EndOfStreamContext& ec) final - { - LOGP(info, "End of stream, flushing CMV interval ({} TFs)", mIntervalTFCount); - // correct mTFEnd for the partial last interval so the CCDB validity end timestamp reflects the actual last TF, not the expected interval end - mTFEnd[mBuffer] = mLastSeenTF; - sendOutput(ec.outputs(), mLastSeenTF); - ec.services().get().readyToQuit(QuitRequest::Me); - } - - static constexpr header::DataDescription getDataDescriptionCCDBCMV() { return header::DataDescription{"TPC_CMV"}; } + void endOfStream(o2::framework::EndOfStreamContext& ec) final { ec.services().get().readyToQuit(QuitRequest::Me); } /// Return data description for aggregated CMVs for a given lane static header::DataDescription getDataDescriptionCMV(const unsigned int lane) { - const std::string name = fmt::format("CMVAGG{}", lane).data(); + const std::string name = fmt::format("CMVAGG{}", lane); header::DataDescription description; description.runtimeInit(name.substr(0, 16).c_str()); return description; @@ -315,276 +249,211 @@ class TPCDistributeCMVSpec : public o2::framework::Task private: std::vector mCRUs{}; ///< CRUs to process in this instance const unsigned int mTimeFrames{}; ///< number of TFs per aggregation interval - const int mNTFsBuffer{1}; ///< number of TFs for which the CMVs will be buffered - std::array mProcessedTFs{{0, 0}}; ///< number of processed time frames to keep track of when the writing to CCDB will be done - std::array, 2> mProcessedCRU{}; ///< counter of received data from CRUs per TF to merge incoming data from FLPs. Buffer used in case one FLP delivers the TF after the last TF for the current aggregation interval faster then the other FLPs the last TF. - std::array>, 2> mProcessedCRUs{}; ///< to keep track of the already processed CRUs ([buffer][relTF][CRU]) - std::array mTFStart{}; ///< storing of first TF for buffer interval - std::array mTFEnd{}; ///< storing of last TF for buffer interval - std::shared_ptr mCCDBRequest; ///< info for CCDB request - std::vector mSendCCDBOutputOrbitReset{}; ///< flag for received orbit reset time from CCDB - std::vector mSendCCDBOutputGRPECS{}; ///< flag for received orbit GRPECS from CCDB - bool mBuffer{false}; ///< buffer index - bool mSendCCDB{false}; ///< send output to CCDB populator - bool mUsePreciseTimestamp{false}; ///< use precise timestamp from orbit-reset info - bool mDumpCMVs{false}; ///< write a local ROOT debug file - bool mUseCompressionVarint{false}; ///< use delta+zigzag+varint compression (all values, no sparse skip); combined with mUseSparse → SparseV2 mode 1 - bool mUseSparse{false}; ///< sparse encoding; alone = raw uint16 values; combined with varint/Huffman flag → SparseV2 - bool mUseCompressionHuffman{false}; ///< Huffman encoding; combined with mUseSparse → SparseV2 mode 2 - uint16_t mRoundIntegersThreshold{0}; ///< round values to nearest integer ADC for |v| <= N ADC; 0 = disabled - float mZeroThreshold{0.f}; ///< zero out CMV values whose float magnitude is below this threshold; 0 = disabled - float mDynamicPrecisionMean{1.f}; ///< Gaussian centre in |CMV| ADC where the strongest fractional-bit trimming is applied - float mDynamicPrecisionSigma{0.f}; ///< Gaussian width in ADC for the fractional-bit trimming; 0 disables - long mTimestampStart{0}; ///< CCDB validity start timestamp - dataformats::Pair mTFInfo{}; ///< orbit-reset time and NHBFPerTF for precise timestamp - std::unique_ptr mIntervalTree{}; ///< TTree accumulating one entry per completed TF in the current interval - CMVPerTF mCurrentTF{}; ///< staging object filled per CRU before compression - CMVPerTFCompressed mCurrentCompressedTF{}; ///< compressed output for the current TF (used when flags != kNone) - long mIntervalFirstTF{0}; ///< absolute TF counter of the first TF in the current aggregation interval - unsigned int mIntervalTFCount{0}; ///< number of TTree entries filled for the current aggregation interval - int mNFactorTFs{0}; ///< Number of TFs to skip for sending oldest TF - int mNTFsDataDrop{0}; ///< delay for the check if TFs are missing in TF units - std::array mStartNTFsDataDrop{0}; ///< first relative TF to check - long mProcessedTotalData{0}; ///< used to check for dropeed TF data - int mCheckEveryNData{1}; ///< factor after which to check for missing data (in case data missing -> send dummy data) - std::vector mFilter{}; ///< filter for looping over input data - std::vector mOrbitFilter{}; ///< filter for CMVORBITINFO from FLP - std::array, 2> mOrbitInfoForwarded{}; ///< tracks whether orbit/BC has been captured per (buffer, relTF) - uint32_t mLastSeenTF{0}; ///< last TF counter seen in run(), used to set lastTF in endOfStream flush - - /// Returns real number of TFs taking buffer size into account + const int mNTFsBuffer{1}; ///< number of TFs for which the CMVs will be buffered (must match TPCFLPCMVSpec) + const unsigned int mOutLanes{}; ///< number of parallel aggregate pipelines this distributor feeds + std::array mProcessedTFs{{0, 0}}; ///< number of processed timeframes per buffer; triggers sendOutput when it reaches mTimeFrames + std::array, 2> mProcessedCRU{}; ///< counter of received CRUs per (buffer, relTF); used to detect when a relTF is complete + std::array>, 2> mProcessedCRUs{}; ///< per-CRU received flag ([buffer][relTF][CRU]); prevents double-counting when a CRU re-sends + std::array mTFStart{}; ///< absolute TF counter of the first TF in each buffer interval + std::array mTFEnd{}; ///< absolute TF counter of the last TF in each buffer interval + std::array mSendOutputStartInfo{true, true}; ///< flag to send CMVFIRSTTF message once at the start of each buffer interval + std::shared_ptr mCCDBRequest; ///< info for CCDB request (orbit-reset and GRPECS, only on lane 0 when sendPreciseTimestamp=true) + std::vector mSendCCDBOutputOrbitReset{}; ///< per-output-lane flag: true when a fresh orbit-reset object has been received from CCDB + std::vector mSendCCDBOutputGRPECS{}; ///< per-output-lane flag: true when a fresh GRPECS object has been received from CCDB + unsigned int mCurrentOutLane{0}; ///< output lane currently being filled + bool mBuffer{false}; ///< double-buffer index (false = buffer 0, true = buffer 1) + int mNFactorTFs{0}; ///< number of TFs to skip when setting oldestForChannel; resets to 0 after first interval + int mNTFsDataDrop{0}; ///< delay (in relTF units) before declaring a relTF's missing CRUs as lost + std::array mStartNTFsDataDrop{0}; ///< first relative TF index to check for missing data in each buffer + long mProcessedTotalData{0}; ///< call counter used to throttle checkIntervalsForMissingData checks + int mCheckEveryNData{1}; ///< check for missing data every N run() calls (0 → default = mTimeFrames/2) + std::vector mFilter{}; ///< filter for looping over CMVGROUP input data from FLPs + std::vector mOrbitFilter{}; ///< filter for CMVORBITINFO input from FLPs + std::vector mDataDescrOut{}; ///< per-output-lane CMV data descriptions (CMVAGG0, CMVAGG1, …) + std::vector mOrbitDescrOut{}; ///< per-output-lane orbit-info data descriptions (CMVORB0, CMVORB1, …) + std::array, 2> mOrbitInfoForwarded{}; ///< tracks whether orbit/BC has been forwarded to the aggregate lane per (buffer, relTF) + + /// Returns the output aggregate lane for a given TF counter (advances when the current buffer interval has ended) + unsigned int getOutLane(const uint32_t tf) const { return (tf > mTFEnd[mBuffer]) ? (mCurrentOutLane + 1) % mOutLanes : mCurrentOutLane; } + /// Returns the total number of real TFs per buffer interval (= mNTFsBuffer * mTimeFrames) unsigned int getNRealTFs() const { return mNTFsBuffer * mTimeFrames; } - /// Build the CMVEncoding bitmask from the current option flags. - uint8_t buildCompressionFlags() const + void sendOutput(o2::framework::ProcessingContext& pc, const unsigned int currentOutLane, const unsigned int cru, o2::pmr::vector cmvs) { - uint8_t flags = CMVEncoding::kNone; - if (mUseSparse) { - flags |= CMVEncoding::kSparse; - } - if (mUseCompressionHuffman) { - flags |= CMVEncoding::kZigzag | CMVEncoding::kHuffman; - } else if (mUseCompressionVarint) { - flags |= CMVEncoding::kZigzag | CMVEncoding::kVarint; + pc.outputs().adoptContainer(Output{gDataOriginTPC, mDataDescrOut[currentOutLane], header::DataHeader::SubSpecificationType{cru}}, std::move(cmvs)); + } + + void sendOrbitInfo(o2::framework::ProcessingContext& pc, const unsigned int outLane, const uint64_t orbitInfo) + { + pc.outputs().snapshot(Output{gDataOriginTPC, mOrbitDescrOut[outLane], header::DataHeader::SubSpecificationType{outLane}}, orbitInfo); + } + + void forwardOrbitInfo(o2::framework::ProcessingContext& pc, const bool currentBuffer, const unsigned int relTF, const unsigned int currentOutLane) + { + if (mOrbitInfoForwarded[currentBuffer][relTF]) { + return; } - // Delta coding is only applied for the dense (non-sparse) path with a value compressor - if (!(flags & CMVEncoding::kSparse) && (flags & (CMVEncoding::kVarint | CMVEncoding::kHuffman))) { - flags |= CMVEncoding::kDelta; + + for (auto& ref : InputRecordWalker(pc.inputs(), mOrbitFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const unsigned int cru = hdr->subSpecification >> 7; + if (!std::binary_search(mCRUs.begin(), mCRUs.end(), cru)) { + continue; + } + + sendOrbitInfo(pc, currentOutLane, pc.inputs().get(ref)); + mOrbitInfoForwarded[currentBuffer][relTF] = true; + break; } - return flags; } - /// Create a fresh in-memory TTree for the next aggregation interval. - /// Uses a single CMVPerTFCompressed branch whenever any compression is active, - /// or a raw CMVPerTF branch when no compression flags are set. - void initIntervalTree() + void forwardEOSData(o2::framework::ProcessingContext& pc) { - mIntervalTree = std::make_unique("ccdb_object", "ccdb_object"); - mIntervalTree->SetAutoSave(0); - mIntervalTree->SetDirectory(nullptr); - if (buildCompressionFlags() != CMVEncoding::kNone) { - mIntervalTree->Branch("CMVPerTFCompressed", &mCurrentCompressedTF); - } else { - mIntervalTree->Branch("CMVPerTF", &mCurrentTF); + const unsigned int currentOutLane = mCurrentOutLane; + + if (mSendOutputStartInfo[mBuffer] && (mTFStart[mBuffer] >= 0)) { + mSendOutputStartInfo[mBuffer] = false; + pc.outputs().snapshot(Output{gDataOriginTPC, getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{currentOutLane}}, mTFStart[mBuffer]); + } + + if (mSendCCDBOutputOrbitReset[currentOutLane] && mSendCCDBOutputGRPECS[currentOutLane]) { + mSendCCDBOutputOrbitReset[currentOutLane] = false; + mSendCCDBOutputGRPECS[currentOutLane] = false; + pc.outputs().snapshot(Output{gDataOriginTPC, getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{currentOutLane}}, dataformats::Pair{o2::base::GRPGeomHelper::instance().getOrbitResetTimeMS(), o2::base::GRPGeomHelper::instance().getNHBFPerTF()}); + } + + if (!mOrbitInfoForwarded[mBuffer].empty()) { + for (auto& ref : InputRecordWalker(pc.inputs(), mOrbitFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const unsigned int cru = hdr->subSpecification >> 7; + if (!std::binary_search(mCRUs.begin(), mCRUs.end(), cru)) { + continue; + } + sendOrbitInfo(pc, currentOutLane, pc.inputs().get(ref)); + break; + } + } + + for (auto& ref : InputRecordWalker(pc.inputs(), mFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const unsigned int cru = hdr->subSpecification >> 7; + if (!std::binary_search(mCRUs.begin(), mCRUs.end(), cru)) { + continue; + } + sendOutput(pc, currentOutLane, cru, pc.inputs().get>(ref)); } } void clearBuffer(const bool currentBuffer) { - // resetting received CRUs + // reset per-CRU received flags so the next interval can accept data from all CRUs again for (auto& crusMap : mProcessedCRUs[currentBuffer]) { for (auto& it : crusMap) { it.second = false; } } - mProcessedTFs[currentBuffer] = 0; // reset processed TFs for next aggregation interval + mProcessedTFs[currentBuffer] = 0; std::fill(mProcessedCRU[currentBuffer].begin(), mProcessedCRU[currentBuffer].end(), 0); std::fill(mOrbitInfoForwarded[currentBuffer].begin(), mOrbitInfoForwarded[currentBuffer].end(), false); - // set integration range for next integration interval mTFStart[mBuffer] = mTFEnd[!mBuffer] + 1; mTFEnd[mBuffer] = mTFStart[mBuffer] + getNRealTFs() - 1; - // switch buffer + // switch buffer and advance output lane mBuffer = !mBuffer; + mCurrentOutLane = ++mCurrentOutLane % mOutLanes; } - void checkIntervalsForMissingData(o2::framework::ProcessingContext& pc, const bool currentBuffer, const long relTF, const uint32_t tf) + void checkIntervalsForMissingData(o2::framework::ProcessingContext& pc, const bool currentBuffer, const long relTF, const unsigned int currentOutLane, const uint32_t tf) { if (!(mProcessedTotalData++ % mCheckEveryNData)) { - LOGP(info, "Checking for dropped packages..."); + LOGP(detail, "Checking for dropped packages..."); - // if last buffer has smaller time range check the whole last buffer + // if the last buffer has a smaller time range than expected, flush its remaining uncompleted TFs if ((mTFStart[currentBuffer] > mTFStart[!currentBuffer]) && (relTF > mNTFsDataDrop)) { LOGP(warning, "Checking last buffer from {} to {}", mStartNTFsDataDrop[!currentBuffer], mProcessedCRU[!currentBuffer].size()); - checkMissingData(pc, !currentBuffer, mStartNTFsDataDrop[!currentBuffer], mProcessedCRU[!currentBuffer].size()); - LOGP(info, "All empty TFs for TF {} for current buffer filled with dummy and sent. Clearing buffer", tf); - sendOutput(pc.outputs(), tf); - finishInterval(pc, !currentBuffer, tf); + const unsigned int lastLane = (currentOutLane == 0) ? (mOutLanes - 1) : (currentOutLane - 1); + checkMissingData(pc, !currentBuffer, mStartNTFsDataDrop[!currentBuffer], mProcessedCRU[!currentBuffer].size(), lastLane); + LOGP(detail, "All empty TFs for TF {} for current buffer filled with dummy and sent. Clearing buffer", tf); + finishInterval(pc, lastLane, !currentBuffer, tf); } const int tfEndCheck = std::clamp(static_cast(relTF) - mNTFsDataDrop, 0, static_cast(mProcessedCRU[currentBuffer].size())); - LOGP(info, "Checking current buffer from {} to {}", mStartNTFsDataDrop[currentBuffer], tfEndCheck); - checkMissingData(pc, currentBuffer, mStartNTFsDataDrop[currentBuffer], tfEndCheck); + LOGP(detail, "Checking current buffer from {} to {}", mStartNTFsDataDrop[currentBuffer], tfEndCheck); + checkMissingData(pc, currentBuffer, mStartNTFsDataDrop[currentBuffer], tfEndCheck, currentOutLane); mStartNTFsDataDrop[currentBuffer] = tfEndCheck; } } - void checkMissingData(o2::framework::ProcessingContext& pc, const bool currentBuffer, const int startTF, const int endTF) + void checkMissingData(o2::framework::ProcessingContext& pc, const bool currentBuffer, const int startTF, const int endTF, const unsigned int outLane) { for (int iTF = startTF; iTF < endTF; ++iTF) { if (mProcessedCRU[currentBuffer][iTF] != mCRUs.size()) { - LOGP(warning, "CRUs for rel. TF: {} curr TF {} are missing! Processed {} CRUs out of {}", iTF, mTFStart[currentBuffer] + iTF, mProcessedCRU[currentBuffer][iTF], mCRUs.size()); + LOGP(warning, "CRUs for lane {} rel. TF: {} curr TF {} are missing! Processed {} CRUs out of {}", outLane, iTF, mTFStart[currentBuffer] + static_cast(iTF) * mNTFsBuffer, mProcessedCRU[currentBuffer][iTF], mCRUs.size()); ++mProcessedTFs[currentBuffer]; mProcessedCRU[currentBuffer][iTF] = mCRUs.size(); - // find missing CRUs and leave their interval slots empty (zero-filled) + // send empty payloads for missing CRUs so the aggregate lane sees a complete set for (auto& it : mProcessedCRUs[currentBuffer][iTF]) { if (!it.second) { it.second = true; + sendOutput(pc, outLane, it.first, pmr::vector()); } } - // leave orbit/BC as zero placeholder for missing TFs - mOrbitInfoForwarded[currentBuffer][iTF] = true; + // send zero orbit placeholder for missing TF so the aggregate lane can still reconstruct timing + if (!mOrbitInfoForwarded[currentBuffer][iTF]) { + sendOrbitInfo(pc, outLane, 0); + mOrbitInfoForwarded[currentBuffer][iTF] = true; + } } } } - void finishInterval(o2::framework::ProcessingContext& pc, const bool buffer, const uint32_t tf) + void finishInterval(o2::framework::ProcessingContext& pc, const unsigned int currentOutLane, const bool buffer, const uint32_t tf) { if (mNFactorTFs > 0) { mNFactorTFs = 0; - // ToDo: Find better fix - auto& deviceProxy = pc.services().get(); - if (deviceProxy.getNumOutputChannels() > 0) { - auto& state = deviceProxy.getOutputChannelState({0}); - size_t oldest = std::numeric_limits::max() - 1; // just set to really large value + // ToDo: Find better fix. Set oldestForChannel to a very large value so the DPL dispatcher does not block waiting for older TF data that will never arrive + for (unsigned int ilane = 0; ilane < mOutLanes; ++ilane) { + auto& deviceProxy = pc.services().get(); + auto& state = deviceProxy.getOutputChannelState({static_cast(ilane)}); + size_t oldest = std::numeric_limits::max() - 1; state.oldestForChannel = {oldest}; } } - LOGP(info, "All TFs {} for current buffer received. Clearing buffer", tf); + LOGP(detail, "All TFs {} for current buffer received. Clearing buffer", tf); clearBuffer(buffer); mStartNTFsDataDrop[buffer] = 0; - - // reset per-interval state for the next aggregation interval - initIntervalTree(); - mIntervalFirstTF = 0; - mIntervalTFCount = 0; - mCurrentTF = CMVPerTF{}; - mCurrentCompressedTF = CMVPerTFCompressed{}; - mTimestampStart = 0; - LOGP(info, "Everything cleared. Waiting for new data to arrive."); - } - - void setTimestampCCDB(const long relTF, o2::framework::ProcessingContext& pc) - { - if (mUsePreciseTimestamp && !mTFInfo.second) { - return; - } - const auto& tinfo = pc.services().get(); - const auto nOrbitsOffset = (relTF * mNTFsBuffer + (mNTFsBuffer - 1)) * mTFInfo.second; - mTimestampStart = mUsePreciseTimestamp - ? (mTFInfo.first + (tinfo.firstTForbit - nOrbitsOffset) * o2::constants::lhc::LHCOrbitMUS * 0.001) - : tinfo.creation; - LOGP(info, "Setting timestamp reset reference to: {}, at tfCounter: {}, firstTForbit: {}, NHBFPerTF: {}, relTF: {}, nOrbitsOffset: {}", - mTFInfo.first, tinfo.tfCounter, tinfo.firstTForbit, mTFInfo.second, relTF, nOrbitsOffset); - } - - void sendOutput(DataAllocator& output, const uint32_t tf) - { - using timer = std::chrono::high_resolution_clock; - - if (mIntervalTFCount == 0) { - LOGP(warning, "CMV interval is empty at sendOutput, skipping"); - return; - } - - // attach interval metadata to the TTree (stored once per tree) - mIntervalTree->GetUserInfo()->Clear(); - mIntervalTree->GetUserInfo()->Add(new TParameter("firstTF", mIntervalFirstTF)); - mIntervalTree->GetUserInfo()->Add(new TParameter("lastTF", mLastSeenTF)); - - LOGP(info, "CMVPerTF TTree: {} entries, firstTF={}, lastTF={}", mIntervalTFCount, mIntervalFirstTF, mLastSeenTF); - auto start = timer::now(); - - // write local ROOT file for debugging - if (mDumpCMVs) { - const std::string fname = fmt::format("CMV_timestamp{}.root", mTimestampStart); - try { - mCurrentTF.writeToFile(fname, mIntervalTree); - LOGP(info, "CMV debug file written to {}", fname); - } catch (const std::exception& e) { - LOGP(error, "Failed to write CMV debug file: {}", e.what()); - } - } - - if (!mSendCCDB) { - LOGP(warning, "CCDB output disabled, skipping upload!"); - return; - } - - const int nHBFPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); - // use the actual number of TFs in this interval (mIntervalTFCount) rather than mTimeFrames, so the CCDB validity end is correct for partial last intervals - const long timeStampEnd = mTimestampStart + static_cast(mIntervalTFCount * mNTFsBuffer * nHBFPerTF * o2::constants::lhc::LHCOrbitMUS * 1e-3); - - if (timeStampEnd <= mTimestampStart) { - LOGP(warning, "Invalid CCDB timestamp range start:{} end:{}, skipping upload!", - mTimestampStart, timeStampEnd); - return; - } - - LOGP(info, "CCDB timestamp range start:{} end:{}", mTimestampStart, timeStampEnd); - - o2::ccdb::CcdbObjectInfo ccdbInfoCMV( - "TPC/Calib/CMV", - "TTree", - "CMV.root", - {}, - mTimestampStart, - timeStampEnd); - - auto image = o2::ccdb::CcdbApi::createObjectImage((mIntervalTree.get()), &ccdbInfoCMV); - // trim TMemFile zero-padding: GetSize() is block-rounded, GetEND() is the actual file end - { - TMemFile mf("trim", image->data(), static_cast(image->size()), "READ"); - image->resize(static_cast(mf.GetEND())); - mf.Close(); - } - LOGP(info, "Sending object {} / {} of size {} bytes, valid for {} : {}", - ccdbInfoCMV.getPath(), ccdbInfoCMV.getFileName(), image->size(), - ccdbInfoCMV.getStartValidityTimestamp(), ccdbInfoCMV.getEndValidityTimestamp()); - - output.snapshot(Output{o2::calibration::Utils::gDataOriginCDBPayload, getDataDescriptionCCDBCMV(), 0}, *image); - output.snapshot(Output{o2::calibration::Utils::gDataOriginCDBWrapper, getDataDescriptionCCDBCMV(), 0}, ccdbInfoCMV); - - auto stop = timer::now(); - std::chrono::duration elapsed = stop - start; - LOGP(info, "CMV CCDB serialisation time: {:.3f} s", elapsed.count()); + mSendOutputStartInfo[buffer] = true; } }; -DataProcessorSpec getTPCDistributeCMVSpec(const int ilane, const std::vector& crus, const unsigned int timeframes, const int firstTF, const bool sendCCDB = false, const bool usePreciseTimestamp = false, const int nTFsBuffer = 1) +DataProcessorSpec getTPCDistributeCMVSpec(const int ilane, const std::vector& crus, const unsigned int timeframes, const unsigned int outlanes, const int firstTF, const bool sendPrecisetimeStamp = false, const int nTFsBuffer = 1) { std::vector inputSpecs; inputSpecs.emplace_back(InputSpec{"cmvsgroup", ConcreteDataTypeMatcher{gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup()}, Lifetime::Sporadic}); inputSpecs.emplace_back(InputSpec{"cmvorbit", ConcreteDataTypeMatcher{gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo()}, Lifetime::Sporadic}); std::vector outputSpecs; - if (sendCCDB) { - outputSpecs.emplace_back( - ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBPayload, - TPCDistributeCMVSpec::getDataDescriptionCCDBCMV()}, - Lifetime::Sporadic); - outputSpecs.emplace_back( - ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBWrapper, - TPCDistributeCMVSpec::getDataDescriptionCCDBCMV()}, - Lifetime::Sporadic); + outputSpecs.reserve(3 * outlanes); + for (unsigned int lane = 0; lane < outlanes; ++lane) { + outputSpecs.emplace_back(ConcreteDataTypeMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(lane)}, Lifetime::Sporadic); + outputSpecs.emplace_back(ConcreteDataMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(lane), header::DataHeader::SubSpecificationType{lane}}, Lifetime::Sporadic); + outputSpecs.emplace_back(ConcreteDataMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{lane}}, Lifetime::Sporadic); + } + + // Only lane 0 fetches CCDB orbit-reset/GRPECS objects and broadcasts them to all aggregate lanes, the other distribute lanes do not need them, avoiding redundant CCDB requests + bool fetchCCDB = false; + if (sendPrecisetimeStamp && (ilane == 0)) { + fetchCCDB = true; + for (unsigned int lane = 0; lane < outlanes; ++lane) { + outputSpecs.emplace_back(ConcreteDataMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{lane}}, Lifetime::Sporadic); + } } - const bool fetchCCDB = usePreciseTimestamp; auto ccdbRequest = std::make_shared(fetchCCDB, // orbitResetTime fetchCCDB, // GRPECS=true false, // GRPLHCIF @@ -593,25 +462,15 @@ DataProcessorSpec getTPCDistributeCMVSpec(const int ilane, const std::vector(crus, timeframes, nTFsBuffer, firstTF, sendCCDB, usePreciseTimestamp, ccdbRequest)}, - Options{{"drop-data-after-nTFs", VariantType::Int, 0, {"Number of TFs after which to drop the data"}}, - {"check-data-every-n", VariantType::Int, 0, {"Number of run function called after which to check for missing data (-1 for no checking, 0 for default checking)"}}, - {"nFactorTFs", VariantType::Int, 1000, {"Number of TFs to skip for sending oldest TF"}}, - {"dump-cmvs", VariantType::Bool, false, {"Dump CMVs to a local ROOT file for debugging"}}, - {"use-sparse", VariantType::Bool, false, {"Sparse encoding (skip zero time bins). Alone: raw uint16 values. With --use-compression-varint: varint exact values. With --use-compression-huffman: Huffman exact values"}}, - {"use-compression-varint", VariantType::Bool, false, {"Delta+zigzag+varint compression (all values). Combined with --use-sparse: sparse positions + varint encoded exact CMV values"}}, - {"use-compression-huffman", VariantType::Bool, false, {"Huffman encoding. Combined with --use-sparse: sparse positions + Huffman-encoded exact CMV values"}}, - {"cmv-zero-threshold", VariantType::Float, 0.f, {"Zero out CMV values whose float magnitude is below this threshold after optional integer rounding and before compression; 0 disables"}}, - {"cmv-round-integers-threshold", VariantType::Int, 0, {"Round values to nearest integer ADC for |v| <= N ADC before compression; 0 disables"}}, - {"cmv-dynamic-precision-mean", VariantType::Float, 1.f, {"Gaussian centre in |CMV| ADC where the strongest fractional bit trimming is applied"}}, - {"cmv-dynamic-precision-sigma", VariantType::Float, 0.f, {"Gaussian width in ADC for smooth CMV fractional bit trimming; 0 disables"}}}}; // end DataProcessorSpec - + AlgorithmSpec{adaptFromTask(crus, timeframes, nTFsBuffer, outlanes, firstTF, ccdbRequest)}, + Options{{"drop-data-after-nTFs", VariantType::Int, 0, {"Number of TFs after which to drop the data."}}, + {"check-data-every-n", VariantType::Int, 0, {"Number of run function called after which to check for missing data (-1 for no checking, 0 for default checking)."}}, + {"nFactorTFs", VariantType::Int, 1000, {"Number of TFs to skip for sending oldest TF."}}}}; spec.rank = ilane; return spec; } diff --git a/Detectors/TPC/workflow/src/tpc-aggregate-cmv.cxx b/Detectors/TPC/workflow/src/tpc-aggregate-cmv.cxx new file mode 100644 index 0000000000000..32d2317c3b9b0 --- /dev/null +++ b/Detectors/TPC/workflow/src/tpc-aggregate-cmv.cxx @@ -0,0 +1,86 @@ +// 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 +#include +#include "Algorithm/RangeTokenizer.h" +#include "Framework/WorkflowSpec.h" +#include "Framework/ConfigParamSpec.h" +#include "CommonUtils/ConfigurableParam.h" +#include "TPCWorkflow/TPCAggregateCMVSpec.h" +#include "Framework/CompletionPolicyHelpers.h" + +using namespace o2::framework; + +// customize the completion policy +void customize(std::vector& policies) +{ + using o2::framework::CompletionPolicy; + policies.push_back(CompletionPolicyHelpers::defineByName("tpc-aggregate-*.*", CompletionPolicy::CompletionOp::Consume)); +} + +// we need to add workflow options before including Framework/runDataProcessing +void customize(std::vector& workflowOptions) +{ + const std::string cruDefault = "0-" + std::to_string(o2::tpc::CRU::MaxCRU - 1); + + std::vector options{ + {"configFile", VariantType::String, "", {"Configuration file for configurable parameters"}}, + {"timeframes", VariantType::Int, 2000, {"Number of TFs aggregated per calibration interval"}}, + {"crus", VariantType::String, cruDefault.c_str(), {"List of CRUs, comma-separated ranges, e.g. 0-3,7,9-15"}}, + {"input-lanes", VariantType::Int, 1, {"Number of aggregate pipelines set by --output-lanes in TPCDistributeCMVSpec"}}, + {"use-precise-timestamp", VariantType::Bool, false, {"Use precise timestamp metadata from distribute when writing to CCDB"}}, + {"enable-CCDB-output", VariantType::Bool, false, {"Send output to the CCDB populator"}}, + {"n-TFs-buffer", VariantType::Int, 1, {"Buffer size that was set in TPCFLPCMVSpec"}}, + {"configKeyValues", VariantType::String, "", {"Semicolon-separated key=value strings"}}}; + + std::swap(workflowOptions, options); +} + +#include "Framework/runDataProcessing.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& config) +{ + using namespace o2::tpc; + + // set up configuration + o2::conf::ConfigurableParam::updateFromFile(config.options().get("configFile")); + o2::conf::ConfigurableParam::updateFromString(config.options().get("configKeyValues")); + o2::conf::ConfigurableParam::writeINI("o2tpcaggregatecmv_configuration.ini"); + + const auto tpcCRUs = o2::RangeTokenizer::tokenize(config.options().get("crus")); + auto timeframes = static_cast(config.options().get("timeframes")); + int aggregateLanes = config.options().get("input-lanes"); + if (aggregateLanes <= 0) { + aggregateLanes = 1; + } + const bool usePreciseTimestamp = config.options().get("use-precise-timestamp"); + const bool sendCCDB = config.options().get("enable-CCDB-output"); + + int nTFsBuffer = config.options().get("n-TFs-buffer"); + if (nTFsBuffer <= 0) { + nTFsBuffer = 1; + } + + // convert total TFs per interval to number of buffered TFs + assert(timeframes >= static_cast(nTFsBuffer)); + timeframes /= static_cast(nTFsBuffer); + + const std::vector rangeCRUs(tpcCRUs.begin(), tpcCRUs.end()); + + WorkflowSpec workflow; + workflow.reserve(static_cast(aggregateLanes)); + LOGP(info, "Starting CMV aggregate with {} lanes, {} timeframes, {} n-TFs-buffer", aggregateLanes, timeframes, nTFsBuffer); + for (int ilane = 0; ilane < aggregateLanes; ++ilane) { + workflow.emplace_back(getTPCAggregateCMVSpec(ilane, rangeCRUs, timeframes, sendCCDB, usePreciseTimestamp, nTFsBuffer)); + } + return workflow; +} diff --git a/Detectors/TPC/workflow/src/tpc-distribute-cmv.cxx b/Detectors/TPC/workflow/src/tpc-distribute-cmv.cxx index b6aaaa0a109ad..0fe780ebb16b3 100644 --- a/Detectors/TPC/workflow/src/tpc-distribute-cmv.cxx +++ b/Detectors/TPC/workflow/src/tpc-distribute-cmv.cxx @@ -38,9 +38,9 @@ void customize(std::vector& workflowOptions) {"firstTF", VariantType::Int, -1, {"First time frame index. (if set to -1 the first TF will be automatically detected. Values < -1 are setting an offset for skipping the first TFs)"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, {"lanes", VariantType::Int, 1, {"Number of lanes of this device (CRUs are split per lane)"}}, - {"use-precise-timestamp", VariantType::Bool, false, {"Use precise timestamp which can be used for writing to CCDB"}}, - {"enable-CCDB-output", VariantType::Bool, false, {"Send output to the CCDB populator"}}, - {"n-TFs-buffer", VariantType::Int, 1, {"Buffer which was defined in the TPCFLPCMVSpec."}}}; + {"send-precise-timestamp", VariantType::Bool, false, {"Send precise timestamp information to the CMV aggregate workflow"}}, + {"n-TFs-buffer", VariantType::Int, 1, {"Buffer which was defined in the TPCFLPCMVSpec."}}, + {"output-lanes", VariantType::Int, 1, {"Number of parallel pipelines which will be used in the CMV aggregate device."}}}; std::swap(workflowOptions, options); } @@ -57,17 +57,18 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) const auto tpcCRUs = o2::RangeTokenizer::tokenize(config.options().get("crus")); const auto nCRUs = tpcCRUs.size(); auto timeframes = static_cast(config.options().get("timeframes")); + const auto outlanes = static_cast(config.options().get("output-lanes")); const auto nLanes = static_cast(config.options().get("lanes")); const auto firstTF = static_cast(config.options().get("firstTF")); - const bool usePreciseTimestamp = config.options().get("use-precise-timestamp"); - const bool sendCCDB = config.options().get("enable-CCDB-output"); + const bool sendPrecisetimeStamp = config.options().get("send-precise-timestamp"); int nTFsBuffer = config.options().get("n-TFs-buffer"); if (nTFsBuffer <= 0) { nTFsBuffer = 1; } - assert(timeframes >= nTFsBuffer); - timeframes /= nTFsBuffer; - LOGP(info, "Using {} timeframes as each TF contains {} CMVs", timeframes, nTFsBuffer); + assert(timeframes >= static_cast(nTFsBuffer)); + timeframes /= static_cast(nTFsBuffer); + LOGP(info, "Using {} buffered CMV batches per interval with n-TFs-buffer={}", timeframes, nTFsBuffer); + const auto crusPerLane = nCRUs / nLanes + ((nCRUs % nLanes) != 0); WorkflowSpec workflow; for (int ilane = 0; ilane < nLanes; ++ilane) { @@ -77,8 +78,8 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) } const auto last = std::min(tpcCRUs.end(), first + crusPerLane); const std::vector rangeCRUs(first, last); - workflow.emplace_back(getTPCDistributeCMVSpec(ilane, rangeCRUs, timeframes, firstTF, sendCCDB, usePreciseTimestamp, nTFsBuffer)); + workflow.emplace_back(getTPCDistributeCMVSpec(ilane, rangeCRUs, timeframes, outlanes, firstTF, sendPrecisetimeStamp, nTFsBuffer)); } return workflow; -} \ No newline at end of file +} From dd8e9c1f8dc229d7ee523076f83765c15c825193 Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Thu, 30 Apr 2026 18:39:19 +0200 Subject: [PATCH 023/102] ITS: update Pb-Pb vertex settings (#15341) * ITS: update Pb-Pb vertex settings Signed-off-by: Felix Schlepper * Refine geometrical cut values in TrackingConfigParam.h Updated floating-point values in TrackingConfigParam.h for precision. --------- Signed-off-by: Felix Schlepper --- .../include/ITStracking/TrackingConfigParam.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h index acb55eb1cf993..1d997ef12147a 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h @@ -29,16 +29,16 @@ struct VertexerParamConfig : public o2::conf::ConfigurableParamHelper Date: Thu, 30 Apr 2026 14:33:15 +0200 Subject: [PATCH 024/102] Optionally produce HistoManager file and pdf report + lot of fixes. Add HistoManager class: TObjArray-based histos and graph container. Extra options for o2-check-resid-workfow: --no-tree : skip writing residuals tree ([checkresid.outname].root) --no-hist : skip writing file with HistoManager ([checkresid.outname]_hman.root) --draw-report: draw pdf report (([checkresid.outname]_hman.pdf) Option --draw-external-only to use the workflow for drawing of already saved HistoManager files: They should be provided as comma-separated list "checkresid.ext_hm_list=,..;" passed as configurableParam. The legend will bear the filename unless for every external file the labels are passed as "checkresid.ext_leg_list=,..;". External HistoManager files can be also provided for overlay drawing on top of the standard report of the data being processed. --- .../study/CMakeLists.txt | 4 +- .../GlobalTrackingStudy/CheckResidConfig.h | 24 +- .../{CheckResid.h => CheckResidSpec.h} | 2 +- .../GlobalTrackingStudy/HistoManager.h | 93 ++++ .../{CheckResid.cxx => CheckResidSpec.cxx} | 522 +++++++++++++++--- .../study/src/GlobalTrackingStudyLinkDef.h | 2 + .../study/src/HistoManager.cxx | 505 +++++++++++++++++ .../study/src/check-resid-workflow.cxx | 28 +- 8 files changed, 1098 insertions(+), 82 deletions(-) rename Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/{CheckResid.h => CheckResidSpec.h} (91%) create mode 100644 Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/HistoManager.h rename Detectors/GlobalTrackingWorkflow/study/src/{CheckResid.cxx => CheckResidSpec.cxx} (50%) create mode 100644 Detectors/GlobalTrackingWorkflow/study/src/HistoManager.cxx diff --git a/Detectors/GlobalTrackingWorkflow/study/CMakeLists.txt b/Detectors/GlobalTrackingWorkflow/study/CMakeLists.txt index df42af503db46..4ee9f2f314a08 100644 --- a/Detectors/GlobalTrackingWorkflow/study/CMakeLists.txt +++ b/Detectors/GlobalTrackingWorkflow/study/CMakeLists.txt @@ -25,8 +25,9 @@ o2_add_library(GlobalTrackingStudy src/TrackMCStudyConfig.cxx src/TrackMCStudyTypes.cxx src/TPCClusSelector.cxx - src/CheckResid.cxx + src/CheckResidSpec.cxx src/CheckResidConfig.cxx + src/HistoManager.cxx PUBLIC_LINK_LIBRARIES O2::GlobalTracking O2::GlobalTrackingWorkflowReaders O2::GlobalTrackingWorkflowHelpers @@ -42,6 +43,7 @@ o2_target_root_dictionary(GlobalTrackingStudy include/GlobalTrackingStudy/TrackMCStudyTypes.h include/GlobalTrackingStudy/CheckResidTypes.h include/GlobalTrackingStudy/CheckResidConfig.h + include/GlobalTrackingStudy/HistoManager.h LINKDEF src/GlobalTrackingStudyLinkDef.h ) diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h index 2a07eaf87930f..09ebba2d2e3f2 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h @@ -21,7 +21,8 @@ struct CheckResidConfig : o2::conf::ConfigurableParamHelper { int minTPCCl = 60; int minITSCl = 7; float minPt = 0.4f; - float maxPt = 100.f; + float maxPt = 50.f; + float maxTgl = 2.f; float rCompIBOB = 12.f; bool pvcontribOnly = true; @@ -34,6 +35,27 @@ struct CheckResidConfig : o2::conf::ConfigurableParamHelper { float refitPVMV = false; float refitPVIniScale = 100.f; + std::string outname{"checkResid"}; + // histogram settings + int nBinsRes = 100; + int nBinsPhi = 30; + int nBinsZ = 20; + int nBinsPt = 15; + int nBinsTgl = 20; + int minHistoStat2Fit = 1000; + float maxPull = 4; + float zranges[8] = {10.f, 15.f, 15.f, 15.f, 40.f, 40.f, 74.f, 74.f}; + float maxDYZ[8] = {0.03, 0.015, 0.01, 0.01, 0.08, 0.08, 0.12, 0.1}; + float maxDPar[5] = {0.15, 0.15, 0.015, 0.015, 1.}; + // drawing settings + float resMMLrY[8] = {0.003, 0.003, 0.003, 0.003, 0.005, 0.005, 0.005, 0.005}; + float resMMLrZ[8] = {0.002, 0.0015, 0.0015, 0.0015, 0.005, 0.005, 0.005, 0.005}; + float resMMPar[5] = {0.03, 0.01, 0.005, 0.001, 0.5}; + // + // string with existing histomanagers files to draw (comma or semicolon separated) and optional legends + std::string ext_hm_list{}; + std::string ext_leg_list{}; + O2ParamDef(CheckResidConfig, "checkresid"); }; } // namespace o2::checkresid diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResid.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h similarity index 91% rename from Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResid.h rename to Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h index baba1a1d4d765..fceba6cb000fd 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResid.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h @@ -19,7 +19,7 @@ namespace o2::checkresid { /// create a processor spec -o2::framework::DataProcessorSpec getCheckResidSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC /*, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts*/); +o2::framework::DataProcessorSpec getCheckResidSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool drawOnly); } // namespace o2::checkresid diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/HistoManager.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/HistoManager.h new file mode 100644 index 0000000000000..eb9cf6876333e --- /dev/null +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/HistoManager.h @@ -0,0 +1,93 @@ +// Copyright 2019-2026 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_HISTOMANAGER_H_ +#define _O2_HISTOMANAGER_H_ + +#include +#include +#include "TObjArray.h" + +class TH1; +class TH2; +class TH1F; +class TH2F; +class TProfile; +class TGraph; +class TFile; + +namespace o2 +{ + +class HistoManager : public TObjArray +{ + public: + HistoManager(const std::string& dirname = "", const std::string& fname = "histoman.root", bool load = kFALSE, const std::string& prefix = ""); + ~HistoManager() override { Delete(); } + + HistoManager* createClone(const std::string& prefix) const; + void addPrefix(const std::string& pref = ""); + + int getNHistos() const { return mNHistos; } + TGraph* getGraph(int id) const; + TH1* getHisto(int id) const; + TH1* getHisto(const std::string& name) const; + TH1F* getHisto1F(int id) const; + TH2F* getHisto2F(int id) const; + TProfile* getHistoP(int id) const; + + int addHisto(TH1* histo, int at = -1); + int addGraph(TGraph* gr, int at = -1); + void delHisto(int at); + + void setFile(TFile* file); + void setFileName(const std::string& fname); + const std::string& getFileName() const { return mDefName; } + void setDirName(const std::string& name) { mDirName = name; } + const std::string& getDirName() const { return mDirName; } + + void reset(); + void write(TFile* file = nullptr); + int write(const std::string& flname) + { + setFileName(flname); + write(); + return 0; + } + + void addHistos(const HistoManager* hm, Double_t c1 = 1.); + void divideHistos(const HistoManager* hm); + void multiplyHistos(const HistoManager* hm); + void scaleHistos(Double_t c1 = 1.); + void setColor(int tcolor = 1); + void setMarkerStyle(Style_t mstyle = 1, Size_t msize = 1); + void setMarkerSize(Size_t msize = 1); + void sumw2(); + int load(const std::string& fname, const std::string& dirname = ""); + + void purify(bool emptyToo = kFALSE); + + void Print(Option_t* option = "") const override; + void Clear(Option_t* option = "") override; + void Delete(Option_t* option = "") override; + void Compress() override; + + private: + int mNHistos{0}; //! Number of histograms defined + std::string mDefName{}; //! Default file name + std::string mDirName{}; //! Directory name in the output file + + ClassDefOverride(HistoManager, 0); +}; + +} // namespace o2 + +#endif // _O2_HISTOMANAGER_H_ diff --git a/Detectors/GlobalTrackingWorkflow/study/src/CheckResid.cxx b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx similarity index 50% rename from Detectors/GlobalTrackingWorkflow/study/src/CheckResid.cxx rename to Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx index dc002489c24e2..6f3055aa00806 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/CheckResid.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx @@ -9,7 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "GlobalTrackingStudy/CheckResid.h" +#include "GlobalTrackingStudy/CheckResidSpec.h" #include "GlobalTrackingStudy/CheckResidTypes.h" #include "GlobalTrackingStudy/CheckResidConfig.h" #include @@ -27,6 +27,7 @@ #include "SimulationDataFormat/MCUtils.h" #include "CommonUtils/NameConf.h" #include "Framework/ConfigParamRegistry.h" +#include "Framework/ControlService.h" #include "Framework/CCDBParamSpec.h" #include "Framework/DeviceSpec.h" #include "DataFormatsITSMFT/DPLAlpideParam.h" @@ -38,6 +39,18 @@ #include "CommonUtils/TreeStreamRedirector.h" #include "ReconstructionDataFormats/VtxTrackRef.h" #include "DetectorsVertexing/PVertexer.h" +#include "GlobalTrackingStudy/HistoManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef WITH_OPENMP #include #endif @@ -62,14 +75,9 @@ using timeEst = o2::dataformats::TimeStampWithError; class CheckResidSpec : public Task { public: - CheckResidSpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool useMC /*, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts*/) - : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC) + CheckResidSpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool drawOnly) + : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mDrawOnly(drawOnly) { - /* - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - */ } ~CheckResidSpec() final = default; void init(InitContext& ic) final; @@ -83,6 +91,10 @@ class CheckResidSpec : public Task bool refitPV(o2::dataformats::PrimaryVertex& pv, int vid); bool refitITStrack(o2::track::TrackParCov& track, GTrackID gid); bool processITSTrack(const o2::its::TrackITS& iTrack, const o2::dataformats::PrimaryVertex& pv, o2::checkresid::Track& resTrack); + void bookHistos(); + void fillHistos(const o2::checkresid::Track& trc); + void postProcessHistos(); + void drawHistos(); o2::globaltracking::RecoContainer* mRecoData = nullptr; int mNThreads = 1; @@ -94,30 +106,85 @@ class CheckResidSpec : public Task o2::vertexing::PVertexer mVertexer; std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; - bool mUseMC{false}; ///< MC flag std::unique_ptr mDBGOut; GTrackID::mask_t mTracksSrc{}; + + bool mDrawOnly = false; + bool mDraw = false; + bool mFillHistos = true; + bool mFillTree = true; + std::vector> mHManV{}; + o2::HistoManager* mHMan = nullptr; }; void CheckResidSpec::init(InitContext& ic) { - o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); + mDraw = true; + if (!mDrawOnly) { + mDraw = ic.options().get("draw-report"); + mFillHistos = !ic.options().get("no-hist"); + mFillTree = !ic.options().get("no-tree"); + mNThreads = ic.options().get("nthreads"); + } + const auto& params = o2::checkresid::CheckResidConfig::Instance(); int lane = ic.services().get().inputTimesliceId; int maxLanes = ic.services().get().maxInputTimeslices; - std::string dbgnm = maxLanes == 1 ? "checkResid.root" : fmt::format("checkResid_t{}.root", lane); - mDBGOut = std::make_unique(dbgnm.c_str(), "recreate"); - mNThreads = ic.options().get("nthreads"); + std::string nm = params.outname; + if (maxLanes > 1) { + o2::conf::ConfigurableParam::updateFromString(fmt::format("checkresid.outname={}_{}", nm, lane)); + } + if (mDraw) { + mFillHistos = true; + } + if (!mDrawOnly && mFillHistos) { + bookHistos(); + } + if (!params.ext_hm_list.empty()) { + auto vecNames = o2::utils::Str::tokenize(params.ext_hm_list, ','); + auto vecLegends = o2::utils::Str::tokenize(params.ext_leg_list, ','); + bool useLeg = true; + if (vecNames.size() != vecLegends.size()) { + LOGP(warn, "{} legend names provided for {} external histomanagers, will use file names as legends", vecLegends.size(), vecNames.size()); + useLeg = false; + } + int cntH = 0; + for (const auto& vn : vecNames) { + LOGP(info, "Loading external HistoManager {}", vn); + mHManV.emplace_back() = std::make_unique("", vn, true); + auto hm = mHManV.back().get(); + if (!hm) { + LOGP(error, "Failed to load histograms from {}", vn); + mHManV.pop_back(); + } else { + hm->SetName(useLeg ? vecLegends[cntH].c_str() : vn.c_str()); + } + cntH++; + } + } + if (mDrawOnly) { + return; + } + o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); #ifndef WITH_OPENMP if (mNThreads > 1) { LOGP(warn, "No OpenMP"); } mNThreads = 1; #endif - // mTPCCorrMapsLoader.init(ic); + if (mFillTree) { + nm += ".root"; + mDBGOut = std::make_unique(nm.c_str(), "recreate"); + } } void CheckResidSpec::run(ProcessingContext& pc) { + if (mDrawOnly) { + drawHistos(); + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } o2::globaltracking::RecoContainer recoData; mRecoData = &recoData; mRecoData->collectData(pc, *mDataRequest.get()); // select tracks of needed type, with minimal cuts, the real selected will be done in the vertexer @@ -131,8 +198,6 @@ void CheckResidSpec::updateTimeDependentParams(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); pc.inputs().get("meanvtx"); - // mTPCVDriftHelper.extractCCDBInputs(pc); - // mTPCCorrMapsLoader.extractCCDBInputs(pc); static bool initOnceDone = false; if (!initOnceDone) { // this params need to be queried only once const auto& params = o2::checkresid::CheckResidConfig::Instance(); @@ -155,24 +220,6 @@ void CheckResidSpec::updateTimeDependentParams(ProcessingContext& pc) mVertexer.setMeanVertex(&mMeanVtx); mVertexer.initMeanVertexConstraint(); } - bool updateMaps = false; - /* - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - if (mTPCVDriftHelper.isUpdated()) { - LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", - mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, - mTPCVDriftHelper.getVDriftObject().timeOffsetCorr, mTPCVDriftHelper.getVDriftObject().refTimeOffset, - mTPCVDriftHelper.getSourceName()); - mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); - } - */ } void CheckResidSpec::process() @@ -249,14 +296,18 @@ void CheckResidSpec::process() continue; } const auto& trc = mRecoData->getTrackParam(vid); + const auto& itsTrack = mRecoData->getITSTrack(gidITS); + if (itsTrack.getNClusters() < params.minITSCl) { + continue; + } auto pt = trc.getPt(); if (pt < params.minPt || pt > params.maxPt) { continue; } - const auto& itsTrack = mRecoData->getITSTrack(gidITS); - if (itsTrack.getNClusters() < params.minITSCl) { + if (std::abs(trc.getTgl()) > params.maxTgl) { continue; } + #ifdef WITH_OPENMP auto& accum = slots[omp_get_thread_num()]; #else @@ -275,6 +326,9 @@ void CheckResidSpec::process() for (const auto& accum : slots) { for (const auto& tr : accum) { (*mDBGOut) << "res" << "tr=" << tr << "\n"; + if (mHMan) { + fillHistos(tr); + } } } LOGP(info, "processed {} PVs out of {} good vertices (out of {} in total), PV refits took {} mus, {} refits failed", nvUse, nvGood, nv, pvFitDuration, nvRefFail); @@ -439,10 +493,10 @@ bool CheckResidSpec::refitPV(o2::dataformats::PrimaryVertex& pv, int vid) auto trackIndex = mRecoData->getPrimaryVertexMatchedTracks(); int itr = vtref.getFirstEntry(), itLim = itr + vtref.getEntries(); for (; itr < itLim; itr++) { - auto vid = trackIndex[itr]; - if (vid.isPVContributor()) { - tracks.emplace_back().setPID(mRecoData->getTrackParam(vid).getPID()); - gidsITS.push_back(mRecoData->getITSContributorGID(vid)); + auto tid = trackIndex[itr]; + if (tid.isPVContributor() && mRecoData->isTrackSourceLoaded(tid.getSource())) { + tracks.emplace_back().setPID(mRecoData->getTrackParam(tid).getPID()); + gidsITS.push_back(mRecoData->getITSContributorGID(tid)); } } ntr = tracks.size(); @@ -507,9 +561,345 @@ bool CheckResidSpec::refitITStrack(o2::track::TrackParCov& track, GTrackID gid) return true; } +void CheckResidSpec::fillHistos(const o2::checkresid::Track& trc) +{ + const auto& params = CheckResidConfig::Instance(); + int np = trc.points.size(); + auto pt = trc.track.getPt(); + if (pt < params.minPt || pt > params.maxPt) { + return; + } + for (int ip = 0; ip < np; ip++) { + const auto& pnt = trc.points[ip]; + int il = pnt.lr >= 0 ? pnt.lr + 1 : 0; + mHMan->getHisto2F(il * 10 + 0 * 100)->Fill(pnt.phi, pnt.dy); + mHMan->getHisto2F(il * 10 + 0 * 100 + 1000)->Fill(pnt.z, pnt.dy); + mHMan->getHisto2F(il * 10 + 0 * 100 + 2000)->Fill(pt, pnt.dy); + mHMan->getHisto2F(il * 10 + 0 * 100 + 3000)->Fill(trc.track.getTgl(), pnt.dy); + if (pnt.sig2y > 0) { + auto pull = pnt.dy / std::sqrt(pnt.sig2y); + mHMan->getHisto2F(il * 10 + 0 * 100 + 5)->Fill(pnt.phi, pull); + mHMan->getHisto2F(il * 10 + 0 * 100 + 5 + 1000)->Fill(pnt.z, pull); + mHMan->getHisto2F(il * 10 + 0 * 100 + 5 + 2000)->Fill(pt, pull); + mHMan->getHisto2F(il * 10 + 0 * 100 + 5 + 3000)->Fill(trc.track.getTgl(), pull); + } + mHMan->getHisto2F(il * 10 + 1 * 100)->Fill(pnt.phi, pnt.dz); + mHMan->getHisto2F(il * 10 + 1 * 100 + 1000)->Fill(pnt.z, pnt.dz); + mHMan->getHisto2F(il * 10 + 1 * 100 + 2000)->Fill(pt, pnt.dz); + mHMan->getHisto2F(il * 10 + 1 * 100 + 3000)->Fill(trc.track.getTgl(), pnt.dz); + if (pnt.sig2z > 0) { + auto pull = pnt.dz / std::sqrt(pnt.sig2z); + mHMan->getHisto2F(il * 10 + 1 * 100 + 5)->Fill(pnt.phi, pull); + mHMan->getHisto2F(il * 10 + 1 * 100 + 5 + 1000)->Fill(pnt.z, pull); + mHMan->getHisto2F(il * 10 + 1 * 100 + 5 + 2000)->Fill(pt, pull); + mHMan->getHisto2F(il * 10 + 1 * 100 + 5 + 3000)->Fill(trc.track.getTgl(), pull); + } + } + //-------------- + if (trc.trIBOut.getX() > 1 && std::abs(trc.trIBOut.getX() - trc.trOBInw.getX()) < 0.1) { + for (int ip = 0; ip < 5; ip++) { + float d = trc.trIBOut.getParam(ip) - trc.trOBInw.getParam(ip); + mHMan->getHisto2F(10000 + ip * 10)->Fill(trc.trIBOut.getPhiPos(), d); + mHMan->getHisto2F(11000 + ip * 10)->Fill(trc.trIBOut.getZ(), d); + mHMan->getHisto2F(12000 + ip * 10)->Fill(pt, d); + mHMan->getHisto2F(13000 + ip * 10)->Fill(trc.track.getTgl(), d); + float sg = trc.trIBOut.getCovarElem(ip, ip) + trc.trOBInw.getCovarElem(ip, ip); + if (sg > 0) { + auto pull = d / std::sqrt(sg); + mHMan->getHisto2F(10000 + ip * 10 + 5)->Fill(trc.trIBOut.getPhiPos(), pull); + mHMan->getHisto2F(11000 + ip * 10 + 5)->Fill(trc.trIBOut.getZ(), pull); + mHMan->getHisto2F(12000 + ip * 10 + 5)->Fill(pt, pull); + mHMan->getHisto2F(13000 + ip * 10 + 5)->Fill(trc.track.getTgl(), pull); + } + } + } +} + +void CheckResidSpec::bookHistos() +{ + const auto& params = o2::checkresid::CheckResidConfig::Instance(); + mHManV.emplace_back() = std::make_unique("", fmt::format("{}_hman.root", params.outname)); + mHMan = mHManV.back().get(); + mHMan->SetName(params.outname.c_str()); + auto defLogAxis = [](float xMn, float xMx, int nbin) { // get array for log axis + if (xMn <= 0 || xMx <= xMn || nbin < 2) { + LOGP(fatal, "Wrong log axis request: xmin = {} xmax = {} nbins = {}", xMn, xMx, nbin); + } + auto dx = std::log(xMx / xMn) / nbin; + std::vector xax(nbin + 1); + for (int i = 0; i <= nbin; i++) { + xax[i] = xMn * std::exp(dx * i); + } + return xax; + }; + float minPt = std::max(0.1f, params.minPt), maxPt = std::min(50.f, params.maxPt); + auto ptax = defLogAxis(minPt, maxPt, params.nBinsPt); + + for (int il = 0; il < 8; il++) { + std::string lrName = il == 0 ? "Vtx" : fmt::format("Lr{}", il - 1); + for (int iyz = 0; iyz < 2; iyz++) { + std::string dname = iyz == 0 ? "dy" : "dz", dtit = iyz == 0 ? "#DeltaY" : "#DeltaZ"; + auto h2 = new TH2F(fmt::format("{}_{}_{}", dname, lrName, "phi").c_str(), fmt::format("{}_{{{}}} vs {};#phi;{}", dtit, lrName, "#phi", dtit).c_str(), params.nBinsPhi, 0, TMath::Pi() * 2, params.nBinsRes, -params.maxDYZ[il], params.maxDYZ[il]); + mHMan->addHisto(h2, il * 10 + iyz * 100); + auto h2p = new TH2F(fmt::format("{}_{}_{}_pull", dname, lrName, "phi").c_str(), fmt::format("pull {}_{{{}}} vs {};#phi; pull{}", dtit, lrName, "phi", dtit).c_str(), params.nBinsPhi, 0, TMath::Pi() * 2, params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(h2p, il * 10 + iyz * 100 + 5); + + auto hz2 = new TH2F(fmt::format("{}_{}_{}", dname, lrName, "Z").c_str(), fmt::format("{}_{{{}}} vs {};Z;{}", dtit, lrName, "Z", dtit).c_str(), params.nBinsZ, -params.zranges[il], params.zranges[il], params.nBinsRes, -params.maxDYZ[il], params.maxDYZ[il]); + mHMan->addHisto(hz2, il * 10 + iyz * 100 + 1000); + auto hz2p = new TH2F(fmt::format("{}_{}_{}_pull", dname, lrName, "Z").c_str(), fmt::format("pull {}_{{{}}} vs {};Z; pull{}", dtit, lrName, "Z", dtit).c_str(), params.nBinsZ, -params.zranges[il], params.zranges[il], params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(hz2p, il * 10 + iyz * 100 + 5 + 1000); + + auto hpt2 = new TH2F(fmt::format("{}_{}_{}", dname, lrName, "Pt").c_str(), fmt::format("{}_{{{}}} vs {};p_{{T}};{}", dtit, lrName, "p_{T}", dtit).c_str(), params.nBinsPt, ptax.data(), params.nBinsRes, -params.maxDYZ[il], params.maxDYZ[il]); + mHMan->addHisto(hpt2, il * 10 + iyz * 100 + 2000); + auto hpt2p = new TH2F(fmt::format("{}_{}_{}_pull", dname, lrName, "Pt").c_str(), fmt::format("pull {}_{{{}}} vs {};p_{{T}}; pull{}", dtit, lrName, "p_{T}", dtit).c_str(), params.nBinsPt, ptax.data(), params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(hpt2p, il * 10 + iyz * 100 + 5 + 2000); + + auto htgl2 = new TH2F(fmt::format("{}_{}_{}", dname, lrName, "tgl").c_str(), fmt::format("{}_{{{}}} vs {};tg#lambda;{}", dtit, lrName, "tg#lambda", dtit).c_str(), params.nBinsTgl, -params.maxTgl, params.maxTgl, params.nBinsRes, -params.maxDYZ[il], params.maxDYZ[il]); + mHMan->addHisto(htgl2, il * 10 + iyz * 100 + 3000); + auto htgl2p = new TH2F(fmt::format("{}_{}_{}_pull", dname, lrName, "tgl").c_str(), fmt::format("pull {}_{{{}}} vs {};tg#lambda; pull{}", dtit, lrName, "tg#lambda", dtit).c_str(), params.nBinsTgl, -params.maxTgl, params.maxTgl, params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(htgl2p, il * 10 + iyz * 100 + 5 + 3000); + } + } + + for (int ip = 0; ip < 5; ip++) { + auto h2 = new TH2F(fmt::format("dPar{}_IBOBphi", ip).c_str(), fmt::format("#Delta par{} IB-OB vs phi;#phi;#Delta par{}", ip, ip).c_str(), params.nBinsPhi, 0, TMath::Pi() * 2, params.nBinsRes, -params.maxDPar[ip], params.maxDPar[ip]); + mHMan->addHisto(h2, 10000 + ip * 10); + auto h2p = new TH2F(fmt::format("dPar{}_IBOBphi_pull", ip).c_str(), fmt::format("pull #Delta par{} IB-OB vs phi;#phi;pull #Delta par{}", ip, ip).c_str(), params.nBinsPhi, 0, TMath::Pi() * 2, params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(h2p, 10000 + ip * 10 + 5); + + auto hz2 = new TH2F(fmt::format("dPar{}_IBOBz", ip).c_str(), fmt::format("#Delta par{} IB-OB vs Z;Z;#Delta par{}", ip, ip).c_str(), params.nBinsZ, -20., 20., params.nBinsRes, -params.maxDPar[ip], params.maxDPar[ip]); + mHMan->addHisto(hz2, 11000 + ip * 10); + auto hz2p = new TH2F(fmt::format("dPar{}_IBOBz_pull", ip).c_str(), fmt::format("pull #Delta par{} IB-OB vs Z;Z;pull #Delta par{}", ip, ip).c_str(), params.nBinsZ, -20., 20., params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(hz2p, 11000 + ip * 10 + 5); + + auto hpt2 = new TH2F(fmt::format("dPar{}_IBOBpt", ip).c_str(), fmt::format("#Delta par{} IB-OB vs pT;p_{{T}};#Delta par{}", ip, ip).c_str(), params.nBinsPt, ptax.data(), params.nBinsRes, -params.maxDPar[ip], params.maxDPar[ip]); + mHMan->addHisto(hpt2, 12000 + ip * 10); + auto hpt2p = new TH2F(fmt::format("dPar{}_IBOBpt_pull", ip).c_str(), fmt::format("pull #Delta par{} IB-OB vs pT;p_{{T}};pull #Delta par{}", ip, ip).c_str(), params.nBinsPt, ptax.data(), params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(hpt2p, 12000 + ip * 10 + 5); + + auto htgl2 = new TH2F(fmt::format("dPar{}_IBOBtgl", ip).c_str(), fmt::format("#Delta par{} IB-OB vs tg#lambda;tg#lambda;#Delta par{}", ip, ip).c_str(), params.nBinsTgl, -params.maxTgl, params.maxTgl, params.nBinsRes, -params.maxDPar[ip], params.maxDPar[ip]); + mHMan->addHisto(htgl2, 13000 + ip * 10); + auto htgl2p = new TH2F(fmt::format("dPar{}_IBOBtgl_pull", ip).c_str(), fmt::format("pull #Delta par{} IB-OB vs tg#lambda;tg#lambda;pull #Delta par{}", ip, ip).c_str(), params.nBinsTgl, -params.maxTgl, params.maxTgl, params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(htgl2p, 13000 + ip * 10 + 5); + } +} + +void CheckResidSpec::postProcessHistos() +{ + printf("Fitting histos\n"); + const auto& params = o2::checkresid::CheckResidConfig::Instance(); + auto gs = new TF1("gs", "gaus", -1, 1); + TObjArray arr; + auto* histm = mHMan; + auto fitSlices = [&](int id) { + auto h2 = histm->getHisto2F(id); + if (!h2 || h2->GetEntries() < params.minHistoStat2Fit) { + return; + } + h2->FitSlicesY(gs, 0, -1, 0, "QNR", &arr); + arr.SetOwner(true); + TH1* hmean = (TH1*)arr.RemoveAt(1); + if (hmean) { + hmean->SetTitle(Form("<%s>", h2->GetTitle())); + histm->addHisto(hmean, id + 1); + } + TH1* hsig = (TH1*)arr.RemoveAt(2); + if (hsig) { + hsig->SetTitle(Form("#sigma(%s)", h2->GetTitle())); + histm->addHisto(hsig, id + 2); + } + }; + for (int ioffs = 0; ioffs <= 3; ioffs++) { // vs phi, Z, pT, tgl + int offs = ioffs * 1000; + for (int iht = 0; iht < 2; iht++) { // resid, pull + int offsV = iht == 0 ? 0 : 5; + for (int il = 0; il < 8; il++) { + for (int iyz = 0; iyz < 2; iyz++) { + fitSlices(il * 10 + iyz * 100 + offsV + offs); + } + } + for (int ip = 0; ip < 5; ip++) { + fitSlices(10000 + ip * 10 + offsV + offs); + } + } + } + histm->write(); + delete gs; +} + +void CheckResidSpec::drawHistos() +{ + gROOT->SetBatch(true); + gStyle->SetTitleX(0.2); + gStyle->SetTitleY(0.88); + gStyle->SetTitleW(0.25); + gStyle->SetOptStat(0); + int nhm = mHManV.size(); + std::array hcol{EColor::kRed, EColor::kBlue, EColor::kGreen + 2}; + std::unique_ptr lg; + lg = std::make_unique(0.12, 0.13, 0.9, 0.13 + std::min(0.5f, nhm * 0.2f / 3.f)); + lg->SetFillStyle(0); + lg->SetBorderSize(0); + for (int i = 0; i < nhm; i++) { + auto hman = mHManV[i].get(); + if (!hman || hman->GetLast() < 1) { + continue; + } + hman->setMarkerStyle(20 + i + (i % 2) * 4, 0.5); + hman->setColor(hcol[i % hcol.size()]); + auto le = lg->AddEntry(hman->getHisto(1), hman->GetName(), "lp"); + le->SetTextColor(hcol[i % hcol.size()]); + } + TCanvas cly("cly", "", 600, 800), clz("clz", "", 600, 800), clpar("clpar", "", 600, 800); + TCanvas czly("czly", "", 600, 800), czlz("czlz", "", 600, 800), czlpar("czlpar", "", 600, 800); + const auto& params = o2::checkresid::CheckResidConfig::Instance(); + + auto AddLabel = [](const char* txt, float x = 0.1, float y = 0.9, int color = kBlack, float size = 0.04) { + TLatex* lt = new TLatex(x, y, txt); + lt->SetNDC(); + lt->SetTextColor(color); + lt->SetTextSize(size); + lt->Draw(); + return lt; + }; + + auto drawResLr = [this](TCanvas& canv, int offs, const float resMM[8], bool logX) { + canv.Clear(); + canv.Divide(2, 4); + int nh = this->mHManV.size(); + for (int i = 0; i < 8; i++) { + canv.cd(i + 1); + bool same = false; + for (int j = 0; j < nh; j++) { + auto hman = this->mHManV[j].get(); + if (!hman || hman->GetLast() < 1) { + continue; + } + if (auto histo = hman->getHisto(10 * i + offs)) { + histo->Draw(same ? "same" : ""); + if (!same) { + histo->SetMinimum(-resMM[i]); + histo->SetMaximum(resMM[i]); + same = true; + } + } + } + gPad->SetGrid(); + gPad->SetLogx(logX); + } + }; + + auto drawResPar = [this](TCanvas& canv, int offs, const float resMM[8], bool logX) { + canv.Clear(); + canv.Divide(2, 3); + int nh = this->mHManV.size(); + for (int i = 0; i < 5; i++) { + canv.cd(i + 1); + bool same = false; + for (int j = 0; j < nh; j++) { + auto hman = this->mHManV[j].get(); + if (!hman || hman->GetLast() < 1) { + continue; + } + if (auto histo = hman->getHisto(10 * i + offs)) { + histo->Draw(same ? "same" : ""); + if (!same) { + histo->SetMinimum(-resMM[i]); + histo->SetMaximum(resMM[i]); + same = true; + } + } + } + gPad->SetGrid(); + gPad->SetLogx(logX); + } + }; + + cly.Print(Form("%s_hman.pdf[", params.outname.c_str())); + drawResLr(cly, 1, params.resMMLrY, false); + cly.cd(2); + lg->Draw(); + AddLabel("Y residuals", 0.1, 0.95); + cly.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(clz, 101, params.resMMLrZ, false); + clz.cd(2); + lg->Draw(); + AddLabel("Z residuals", 0.1, 0.95); + clz.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czly, 1001, params.resMMLrY, false); + czly.cd(2); + lg->Draw(); + AddLabel("Y residuals", 0.1, 0.95); + czly.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czlz, 1101, params.resMMLrZ, false); + czlz.cd(2); + lg->Draw(); + AddLabel("Z residuals", 0.1, 0.95); + czlz.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czly, 2001, params.resMMLrY, true); + czly.cd(2); + lg->Draw(); + AddLabel("Y residuals", 0.1, 0.95); + czly.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czlz, 2101, params.resMMLrZ, true); + czlz.cd(2); + lg->Draw(); + AddLabel("Z residuals", 0.1, 0.95); + czlz.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czly, 3001, params.resMMLrY, false); + czly.cd(2); + lg->Draw(); + AddLabel("Y residuals", 0.1, 0.95); + czly.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czlz, 3101, params.resMMLrZ, false); + czlz.cd(2); + lg->Draw(); + AddLabel("Z residuals", 0.1, 0.95); + czlz.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResPar(clpar, 10001, params.resMMPar, false); + clpar.cd(6); + lg->Draw(); + AddLabel("IB-OB tracks params differences at R = 12 cm", 0.2, 0.8); + clpar.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResPar(czlpar, 11001, params.resMMPar, false); + czlpar.cd(6); + lg->Draw(); + AddLabel("IB-OB tracks params differences at R = 12 cm", 0.2, 0.8); + czlpar.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResPar(czlpar, 12001, params.resMMPar, true); + czlpar.cd(6); + lg->Draw(); + AddLabel("IB-OB tracks params differences at R = 12 cm", 0.2, 0.8); + czlpar.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResPar(czlpar, 13001, params.resMMPar, false); + czlpar.cd(6); + lg->Draw(); + AddLabel("IB-OB tracks params differences at R = 12 cm", 0.2, 0.8); + czlpar.Print(Form("%s_hman.pdf", params.outname.c_str())); + + cly.Print(Form("%s_hman.pdf]", params.outname.c_str())); +} + void CheckResidSpec::endOfStream(EndOfStreamContext& ec) { mDBGOut.reset(); + if (mHManV.size()) { + postProcessHistos(); + } + if (mDraw) { + drawHistos(); + } } void CheckResidSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) @@ -517,14 +907,6 @@ void CheckResidSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { return; } - /* - if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { - return; - } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } - */ if (matcher == ConcreteDataMatcher("GLO", "MEANVERTEX", 0)) { LOG(info) << "Imposing new MeanVertex: " << ((const o2::dataformats::MeanVertexObject*)obj)->asString(); mMeanVtx = *(const o2::dataformats::MeanVertexObject*)obj; @@ -538,33 +920,39 @@ void CheckResidSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) } } -DataProcessorSpec getCheckResidSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC /*, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts*/) +DataProcessorSpec getCheckResidSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool drawOnly) { std::vector outputs; auto dataRequest = std::make_shared(); - dataRequest->requestTracks(srcTracks, useMC); - dataRequest->requestClusters(srcClusters, useMC); - dataRequest->requestPrimaryVertices(useMC); - auto ggRequest = std::make_shared(false, // orbitResetTime - true, // GRPECS=true - true, // GRPLHCIF - true, // GRPMagField - true, // askMatLUT - o2::base::GRPGeomRequest::Aligned, // geometry - dataRequest->inputs, - true); - dataRequest->inputs.emplace_back("meanvtx", "GLO", "MEANVERTEX", 0, Lifetime::Condition, ccdbParamSpec("GLO/Calib/MeanVertex", {}, 1)); - Options opts{ - {"nthreads", VariantType::Int, 1, {"number of threads"}}, - }; - // o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - // o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); + if (!drawOnly) { + bool useMC = false; + dataRequest->requestTracks(srcTracks, useMC); + dataRequest->requestClusters(srcClusters, useMC); + dataRequest->requestPrimaryVertices(useMC); + dataRequest->inputs.emplace_back("meanvtx", "GLO", "MEANVERTEX", 0, Lifetime::Condition, ccdbParamSpec("GLO/Calib/MeanVertex", {}, 1)); + } + auto ggRequest = drawOnly ? std::make_shared(false, false, false, false, false, o2::base::GRPGeomRequest::None, dataRequest->inputs) : std::make_shared(false, // orbitResetTime + true, // GRPECS=true + true, // GRPLHCIF + true, // GRPMagField + true, // askMatLUT + o2::base::GRPGeomRequest::Aligned, // geometry + dataRequest->inputs, true); + Options opts; + if (!drawOnly) { + opts = Options{ + {"nthreads", VariantType::Int, 1, {"number of threads"}}, + {"no-tree", VariantType::Bool, false, {"do not fill residuals tree"}}, + {"no-hist", VariantType::Bool, false, {"do not fill residuals histograms"}}, + {"draw-report", VariantType::Bool, false, {"fill residuals report"}}, + }; + } return DataProcessorSpec{ "check-resid", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, useMC /*, sclOpts*/)}, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, drawOnly)}, opts}; } diff --git a/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h b/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h index 416820fc9aebb..b1fe732fbefc4 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h +++ b/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h @@ -49,4 +49,6 @@ #pragma link C++ class o2::checkresid::CheckResidConfig + ; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::checkresid::CheckResidConfig> + ; +#pragma link C++ class o2::HistoManager + ; + #endif diff --git a/Detectors/GlobalTrackingWorkflow/study/src/HistoManager.cxx b/Detectors/GlobalTrackingWorkflow/study/src/HistoManager.cxx new file mode 100644 index 0000000000000..e57a78e4b202d --- /dev/null +++ b/Detectors/GlobalTrackingWorkflow/study/src/HistoManager.cxx @@ -0,0 +1,505 @@ +// Copyright 2019-2026 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Framework/Logger.h" +#include "GlobalTrackingStudy/HistoManager.h" + +namespace o2 +{ + +HistoManager::HistoManager(const std::string& dirname, const std::string& fname, bool load, const std::string& prefix) : mDirName(dirname) +{ + setFileName(fname); + if (load && !mDefName.empty()) { + int nh = this->load(fname, dirname); + LOGP(info, "HistoManager::load was requested: got {} histos from {}/{}", nh, fname, dirname); + if (!prefix.empty()) { + addPrefix(prefix); + } + } +} + +HistoManager* HistoManager::createClone(const std::string& prefix) const +{ + auto* hm = static_cast(Clone()); + hm->addPrefix(prefix); + for (int i = 0; i < GetLast() + 1; ++i) { + TObject* obj = hm->UncheckedAt(i); + if (!obj) { + continue; + } + if (auto* histo = dynamic_cast(obj)) { + histo->SetDirectory(nullptr); + } + } + hm->mNHistos = mNHistos; + hm->setFileName(mDefName); + hm->setDirName(mDirName); + return hm; +} + +int HistoManager::addHisto(TH1* histo, int at) +{ + if (!histo) { + return mNHistos; + } + if (at < 0) { + at = mNHistos; + } + AddAtAndExpand(histo, at); + histo->SetDirectory(nullptr); + histo->SetUniqueID(at + 1); + return mNHistos++; +} + +TGraph* HistoManager::getGraph(int id) const +{ + return id <= GetLast() ? dynamic_cast(UncheckedAt(id)) : nullptr; +} + +TH1* HistoManager::getHisto(int id) const +{ + return id <= GetLast() ? dynamic_cast(UncheckedAt(id)) : nullptr; +} + +TH1* HistoManager::getHisto(const std::string& name) const +{ + return dynamic_cast(FindObject(name.c_str())); +} + +TH1F* HistoManager::getHisto1F(int id) const +{ + return dynamic_cast(UncheckedAt(id)); +} + +TH2F* HistoManager::getHisto2F(int id) const +{ + return dynamic_cast(UncheckedAt(id)); +} + +TProfile* HistoManager::getHistoP(int id) const +{ + return dynamic_cast(UncheckedAt(id)); +} + +int HistoManager::addGraph(TGraph* gr, int at) +{ + if (!gr) { + return mNHistos; + } + if (at < 0) { + at = mNHistos; + } + AddAtAndExpand(gr, at); + gr->SetUniqueID(at + 1); + return mNHistos++; +} + +void HistoManager::Compress() +{ + TObjArray::Compress(); + for (int i = 0; i < GetLast() + 1; ++i) { + if (TObject* histo = At(i)) { + histo->SetUniqueID(i + 1); + } + } +} + +void HistoManager::write(TFile* file) +{ + if (!mNHistos) { + return; + } + + bool localFile = kFALSE; + TFile* lfile = nullptr; + const char* dirName = nullptr; + if (file) { + lfile = file; + } else { + auto* tmpF = static_cast(gROOT->GetListOfFiles()->FindObject(mDefName.c_str())); + if (tmpF && tmpF->IsOpen()) { + TString opt = tmpF->GetOption(); + opt.ToLower(); + if (!opt.Contains("read")) { + lfile = tmpF; + tmpF->cd(); + } + } + } + + TString pwd = gDirectory->GetPath(); + if (!lfile) { + std::string originalName = mDefName; + if (mDefName.empty() || mDefName[0] == ' ') { + mDefName = "histoman"; + } + TString rootName = mDefName.c_str(); + if (!rootName.Contains(".root")) { + mDefName += ".root"; + } + lfile = TFile::Open(mDefName.c_str(), "UPDATE"); + mDefName = originalName; + localFile = kTRUE; + } + + lfile->cd(); + dirName = mDirName.c_str(); + if (dirName && dirName[0] && dirName[0] != ' ') { + if (!lfile->Get(dirName)) { + lfile->mkdir(dirName); + } + lfile->cd(dirName); + } + LOGP(info, "Writing histograms to: {}/{}", lfile->GetPath(), dirName); + + for (int i = 0; i < GetLast() + 1; ++i) { + TObject* obj = UncheckedAt(i); + if (!obj) { + continue; + } + auto* histo = dynamic_cast(obj); + TDirectory* dr = nullptr; + if (histo) { + dr = histo->GetDirectory(); + histo->SetDirectory(nullptr); + } + obj->Write(nullptr, TObject::kOverwrite); + if (dr && histo) { + histo->SetDirectory(dr); + } + } + + if (localFile) { + lfile->Close(); + delete lfile; + } + auto* oldDir = static_cast(gROOT->GetListOfFiles()->FindObject(pwd.Data())); + if (oldDir) { + oldDir->cd(); + } +} + +void HistoManager::Clear(Option_t*) +{ + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + TObject* hh = UncheckedAt(i); + if (!hh) { + continue; + } + RemoveAt(i); + --mNHistos; + } +} + +//_______________________________________________________________ +void HistoManager::Delete(Option_t*) +{ + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + TObject* hh = UncheckedAt(i); + if (!hh) { + continue; + } + RemoveAt(i); + delete hh; + } + mNHistos = 0; +} + +void HistoManager::Print(Option_t* option) const +{ + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + TObject* hh = UncheckedAt(i); + if (!hh) { + continue; + } + LOGP(info, "At position #{}", i); + hh->Print(option); + } + LOGP(info, "Total number of defined histograms: %d", mNHistos); + LOGP(info, "Current output path: {}/{}", mDefName, mDirName); +} + +void HistoManager::addPrefix(const std::string& pref) +{ + if (pref.empty()) { + return; + } + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + TObject* hh = UncheckedAt(i); + if (!hh) { + continue; + } + if (hh->InheritsFrom("TNamed")) { + auto name = pref + hh->GetName(); + static_cast(hh)->SetName(name.c_str()); + } + } +} + +void HistoManager::addHistos(const HistoManager* hm, Double_t c1) +{ + if (!hm) { + return; + } + int nent = GetLast() + 1; + int nent1 = hm->GetLast() + 1; + if (nent != nent1) { + Error("addHistos", "HistoManagers have different content: %d vs %d", nent, nent1); + return; + } + for (int i = 0; i < nent; ++i) { + TH1* hh1 = getHisto(i); + TH1* hh2 = hm->getHisto(i); + if (!hh1 || !hh2) { + continue; + } + hh1->Add(hh2, c1); + } +} + +void HistoManager::divideHistos(const HistoManager* hm) +{ + if (!hm) { + return; + } + int nent = GetLast() + 1; + int nent1 = hm->GetLast() + 1; + if (nent != nent1) { + Error("divideHistos", "HistoManagers have different content: %d vs %d", nent, nent1); + return; + } + for (int i = 0; i < nent; ++i) { + TH1* hh1 = getHisto(i); + TH1* hh2 = hm->getHisto(i); + if (!hh1 || !hh2) { + continue; + } + hh1->Divide(hh2); + } +} + +//_______________________________________________________________ +void HistoManager::multiplyHistos(const HistoManager* hm) +{ + if (!hm) { + return; + } + int nent = GetLast() + 1; + int nent1 = hm->GetLast() + 1; + if (nent != nent1) { + Error("multiplyHistos", "HistoManagers have different content: %d vs %d", nent, nent1); + return; + } + for (int i = 0; i < nent; ++i) { + TH1* hh1 = getHisto(i); + TH1* hh2 = hm->getHisto(i); + if (!hh1 || !hh2) { + continue; + } + hh1->Multiply(hh2); + } +} + +void HistoManager::scaleHistos(Double_t c1) +{ + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + TH1* hh1 = getHisto(i); + if (hh1) { + hh1->Scale(c1); + } + } +} + +void HistoManager::sumw2() +{ + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + auto* hh1 = dynamic_cast(UncheckedAt(i)); + if (hh1) { + hh1->Sumw2(); + } + } +} + +void HistoManager::setFile(TFile* file) +{ + if (file) { + mDefName = file->GetName(); + } +} + +void HistoManager::delHisto(int at) +{ + TH1* hist = getHisto(at); + if (hist) { + RemoveAt(at); + delete hist; + --mNHistos; + } +} + +void HistoManager::purify(bool emptyToo) +{ + int last = GetLast() + 1; + if (emptyToo) { + for (int i = 0; i < last; ++i) { + TH1* hist = getHisto(i); + if (!hist) { + continue; + } + if (hist->GetEntries() < 1) { + delHisto(i); + } + } + } + Compress(); +} + +void HistoManager::setFileName(const std::string& name) +{ + mDefName = gSystem->ExpandPathName(name.c_str()); +} + +void HistoManager::reset() +{ + int last = GetLast() + 1; + for (int i = 0; i < last; ++i) { + TH1* hist = getHisto(i); + if (!hist) { + continue; + } + hist->Reset(); + } +} + +int HistoManager::load(const std::string& fname, const std::string& dirname) +{ + TFile* file = TFile::Open(gSystem->ExpandPathName(fname.c_str())); + if (!file) { + LOGP(error, "No file {}", fname); + return 0; + } + if (!dirname.empty() && dirname[0] != ' ') { + if (!file->Get(dirname.c_str())) { + LOGP(error, "No {} directory in file {}", dirname, fname); + file->Close(); + delete file; + return 0; + } + file->cd(dirname.c_str()); + } + int count = 0; + TList* lst = gDirectory->GetListOfKeys(); + TIter nextKey(lst); + TKey* key = nullptr; + while ((key = static_cast(nextKey()))) { + if (FindObject(key->GetName())) { + continue; + } + TString clName = key->GetClassName(); + if (!(clName.BeginsWith("TH") || clName.BeginsWith("TProfile") || clName.BeginsWith("TGraph"))) { + printf("Object %s of type %s is not processed\n", key->GetName(), clName.Data()); + continue; + } + TObject* hst = key->ReadObj(); + int id = hst->GetUniqueID(); + if (auto* h = dynamic_cast(hst)) { + addHisto(h, id - 1); + ++count; + continue; + } + if (auto* gr = dynamic_cast(hst)) { + addGraph(gr, id - 1); + ++count; + } + } + file->Close(); + delete file; + auto nm = fname; + if (!dirname.empty()) { + nm += fmt::format("/{}", dirname); + } + SetName(nm.c_str()); + return count; +} + +void HistoManager::setColor(int tcolor) +{ + int last = GetLast() + 1; + for (int i = 0; i < last; ++i) { + TH1* hist = getHisto(i); + if (!hist) { + continue; + } + hist->SetLineColor(tcolor); + hist->SetMarkerColor(tcolor); + TList* lst = hist->GetListOfFunctions(); + if (lst) { + int nf = lst->GetSize(); + for (int j = 0; j < nf; ++j) { + TObject* fnc = lst->At(j); + if (fnc->InheritsFrom("TF1")) { + static_cast(fnc)->SetLineColor(tcolor); + static_cast(fnc)->SetLineWidth(1); + static_cast(fnc)->ResetBit(TF1::kNotDraw); + } else if (fnc->InheritsFrom("TPaveStats")) { + static_cast(fnc)->SetTextColor(tcolor); + } + } + } + } +} + +void HistoManager::setMarkerStyle(Style_t mstyle, Size_t msize) +{ + int last = GetLast() + 1; + for (int i = 0; i < last; ++i) { + TH1* hist = getHisto(i); + if (!hist) { + continue; + } + hist->SetMarkerStyle(mstyle); + hist->SetMarkerSize(msize); + } +} + +void HistoManager::setMarkerSize(Size_t msize) +{ + int last = GetLast() + 1; + for (int i = 0; i < last; ++i) { + TH1* hist = getHisto(i); + if (!hist) { + continue; + } + hist->SetMarkerSize(msize); + } +} + +} // namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx b/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx index 86e4bb9ca234a..72188eb5f06c6 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx @@ -9,7 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "GlobalTrackingStudy/CheckResid.h" +#include "GlobalTrackingStudy/CheckResidSpec.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DetectorsCommonDataFormats/DetID.h" #include "CommonUtils/ConfigurableParam.h" @@ -20,8 +20,6 @@ #include "DetectorsBase/DPLWorkflowUtils.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "DetectorsRaw/HBFUtilsInitializer.h" -#include "TPCCalibration/CorrectionMapsOptions.h" -#include "TPCWorkflow/TPCScalerSpec.h" #include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; @@ -39,14 +37,13 @@ void customize(std::vector& workflowOptions) { // option allowing to set parameters std::vector options{ - {"enable-mc", o2::framework::VariantType::Bool, false, {"enable MC propagation"}}, + {"draw-external-only", VariantType::Bool, false, {"just draw content of comma-separated list of histomanagers from checkresid.ext_hm_list"}}, {"track-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of track sources to use"}}, {"cluster-sources", VariantType::String, "ITS", {"comma-separated list of cluster sources to use"}}, {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; - // o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); - o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -59,23 +56,30 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { WorkflowSpec specs; + bool drawOnly = configcontext.options().get("draw-external-only"); + GID::mask_t allowedSourcesTrc = GID::getSourcesMask("ITS,TPC,ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); GID::mask_t allowedSourcesClus = GID::getSourcesMask("ITS"); // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); - auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); - auto useMC = configcontext.options().get("enable-mc"); GID::mask_t srcTrc = allowedSourcesTrc & GID::getSourcesMask(configcontext.options().get("track-sources")); GID::mask_t srcCls = allowedSourcesClus & GID::getSourcesMask(configcontext.options().get("cluster-sources")); - o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC); - o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, useMC); // P-vertex is always needed - specs.emplace_back(o2::checkresid::getCheckResidSpec(srcTrc, srcCls, useMC)); + if (!drawOnly) { + o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, false); + o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, false); // P-vertex is always needed + } else { + allowedSourcesTrc = {}; + allowedSourcesClus = {}; + } + specs.emplace_back(o2::checkresid::getCheckResidSpec(srcTrc, srcCls, drawOnly)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit - o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); + if (!drawOnly) { + o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); + } return std::move(specs); } From e2c7cc9878306f3dd455d4be6ce5ebdabf499290 Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Mon, 27 Apr 2026 16:37:47 +0200 Subject: [PATCH 025/102] ITS: GPU: try to fix illegal access Signed-off-by: Felix Schlepper --- .../ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx index 0359f2cfb0d03..67b6b0bf5cdfe 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx @@ -209,16 +209,22 @@ void TrackerTraitsGPU::computeLayerCells(const int iteration) this->mTrkParams[iteration].LayerxX0, mTimeFrameGPU->getStreams()); } + mTimeFrameGPU->syncStreams(false); } template void TrackerTraitsGPU::findCellsNeighbours(const int iteration) { for (int iLayer{0}; iLayer < this->mTrkParams[iteration].NeighboursPerRoad(); ++iLayer) { + if (iLayer > 0) { + // Previous layer updates levels in this layer's cells. + mTimeFrameGPU->waitEvent(iLayer, iLayer - 1); + } const int currentLayerCellsNum{static_cast(mTimeFrameGPU->getNCells()[iLayer])}; const int nextLayerCellsNum{static_cast(mTimeFrameGPU->getNCells()[iLayer + 1])}; if (!nextLayerCellsNum || !currentLayerCellsNum) { mTimeFrameGPU->getNNeighbours()[iLayer] = 0; + mTimeFrameGPU->recordEvent(iLayer); continue; } mTimeFrameGPU->createNeighboursIndexTablesDevice(iLayer); @@ -239,6 +245,7 @@ void TrackerTraitsGPU::findCellsNeighbours(const int iteration) mTimeFrameGPU->getStream(iLayer)); mTimeFrameGPU->createNeighboursDevice(iLayer); if (mTimeFrameGPU->getNNeighbours()[iLayer] == 0) { + mTimeFrameGPU->recordEvent(iLayer); continue; } computeCellNeighboursHandler(mTimeFrameGPU->getDeviceArrayCells(), @@ -259,6 +266,7 @@ void TrackerTraitsGPU::findCellsNeighbours(const int iteration) mTimeFrameGPU->getArrayNNeighbours()[iLayer], mTimeFrameGPU->getStream(iLayer), mTimeFrameGPU->getFrameworkAllocator()); + mTimeFrameGPU->recordEvent(iLayer); } mTimeFrameGPU->syncStreams(false); } From a5da00aaaddd3c7045d2bdf6289b605e49a3c835 Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Tue, 28 Apr 2026 10:41:44 +0200 Subject: [PATCH 026/102] ITS: account in selectReseedMidLayer for possible holes Signed-off-by: Felix Schlepper --- .../include/ITStracking/TrackHelpers.h | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h index 2224495607ee1..a8cddd1190c16 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h @@ -29,19 +29,22 @@ namespace o2::its::track { -GPUdi() int selectReseedMidLayer(int minLayer, int maxLayer, int nLayers, const float* layerRadii) +// Find the populated interior layer closest to the radial midpoint. +// If no layer can be found, return constants::UnusedIndex. +// Should minimize the sagitta bias. +template +GPUdi() int selectReseedMidLayer(int minLayer, int maxLayer, const float* layerRadii, const TrackSeed& seed) { - if (maxLayer - minLayer == nLayers - 1) { - return (minLayer + maxLayer) / 2; - } - int midLayer = minLayer + 1; + int midLayer = constants::UnusedIndex; + float distanceToMidR = layerRadii[NLayers - 1]; // midpoint cannot be last layer const float midR = 0.5f * (layerRadii[maxLayer] + layerRadii[minLayer]); - float distanceToMidR = o2::gpu::CAMath::Abs(midR - layerRadii[midLayer]); - for (int iLayer = midLayer + 1; iLayer < maxLayer; ++iLayer) { // find the midpoint as closest to the midR - const float distance = o2::gpu::CAMath::Abs(midR - layerRadii[iLayer]); - if (distance < distanceToMidR) { - midLayer = iLayer; - distanceToMidR = distance; + for (int iLayer = minLayer + 1; iLayer < maxLayer; ++iLayer) { + if (seed.getCluster(iLayer) != constants::UnusedIndex) { + const float distance = o2::gpu::CAMath::Abs(midR - layerRadii[iLayer]); + if (distance < distanceToMidR) { // keep the smaller-radius layer on ties + midLayer = iLayer; + distanceToMidR = distance; + } } } return midLayer; @@ -106,12 +109,14 @@ GPUdi() TrackITSExt seedTrackForRefit(const TrackSeed& seed, } const int ncl = temporaryTrack.getNClusters(); - if (ncl < reseedIfShorter && ncl > 1) { - const int lrMid = selectReseedMidLayer(lrMin, lrMax, NLayers, layerRadii); - const auto& cluster0TF = foundTrackingFrameInfo[lrMin][seed.getCluster(lrMin)]; - const auto& cluster1GL = unsortedClusters[lrMid][seed.getCluster(lrMid)]; - const auto& cluster2GL = unsortedClusters[lrMax][seed.getCluster(lrMax)]; - temporaryTrack.getParamIn() = buildTrackSeed(cluster2GL, cluster1GL, cluster0TF, bz, true); + if (ncl < reseedIfShorter && ncl > 2) { + const int lrMid = selectReseedMidLayer(lrMin, lrMax, layerRadii, seed); + if (lrMid != constants::UnusedIndex) { + const auto& cluster0TF = foundTrackingFrameInfo[lrMin][seed.getCluster(lrMin)]; + const auto& cluster1GL = unsortedClusters[lrMid][seed.getCluster(lrMid)]; + const auto& cluster2GL = unsortedClusters[lrMax][seed.getCluster(lrMax)]; + temporaryTrack.getParamIn() = buildTrackSeed(cluster2GL, cluster1GL, cluster0TF, bz, true); + } } resetTrackCovariance(temporaryTrack); From d87cff5580733c2f480f7bb8ad4cf97178349280 Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Fri, 1 May 2026 15:05:39 +0200 Subject: [PATCH 027/102] ITS: do not use NAN in helpers for new ROCm fixes: /home/fschlepp/alice/sw/SOURCES/O2/dev_head/0/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h:65:60: warning: use of NaN is undefined behavior due to the currently enabled floating-point options [-Wnan-infinity-disabled] 65 | float ca = NAN, sa = NAN, snp = NAN, q2pt = NAN, q2pt2 = NAN; | ^~~ /opt/rocm-7.1.1/lib/llvm/lib/clang/20/include/float.h:174:16: note: expanded from macro 'NAN' 174 | # define NAN (__builtin_nanf("")) Signed-off-by: Felix Schlepper --- .../ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h index a8cddd1190c16..885cb0f2b9ca5 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h @@ -62,7 +62,7 @@ GPUdi() o2::track::TrackParCov buildTrackSeed(const Cluster& cluster1, const float bz, const bool reverse = false) { - float ca = NAN, sa = NAN, snp = NAN, q2pt = NAN, q2pt2 = NAN; + float ca = constants::UnsetValue, sa = constants::UnsetValue, snp = constants::UnsetValue, q2pt = constants::UnsetValue, q2pt2 = constants::UnsetValue; o2::gpu::CAMath::SinCos(tf3.alphaTrackingFrame, sa, ca); const float sign = reverse ? -1.f : 1.f; const float x1 = (cluster1.xCoordinate * ca) + (cluster1.yCoordinate * sa); From 34bd36764fc89a31139f6de6bad2629649ea83e1 Mon Sep 17 00:00:00 2001 From: shahoian Date: Mon, 4 May 2026 17:55:10 +0200 Subject: [PATCH 028/102] Check for streamer presence before writing the output --- Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx index 6f3055aa00806..ff73be857346f 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx @@ -325,7 +325,9 @@ void CheckResidSpec::process() // output for (const auto& accum : slots) { for (const auto& tr : accum) { - (*mDBGOut) << "res" << "tr=" << tr << "\n"; + if (mDBGOut) { + (*mDBGOut) << "res" << "tr=" << tr << "\n"; + } if (mHMan) { fillHistos(tr); } From cc6e4aeafde8454027562d22e4239f96ae71be18 Mon Sep 17 00:00:00 2001 From: Justus Rudolph <44173718+JustusRudolph@users.noreply.github.com> Date: Mon, 4 May 2026 18:50:07 +0200 Subject: [PATCH 029/102] [ALICE3] FT3: Tiling Stave Geometry (#15339) * Add separate file for constants in FT3 module creation * Create modular design in similar fashion as before, but with slightly different module placement. Rewrite structure to be more granular and extendable. The old code remains for backward compatibility for now * Give new geometry creation an FT3Layout enum and change Layer&Module accordingly. Also simplify create_layout_scopingV3 function since the layout_type isn't needed. * Add functionality for the user to enforce a strict cut on module placement to remain within nominal radii. Also remove initial placement of sensor stack before calling fill_stave, moving to using y_start instead. * Make forward and backward directions mirrored, front and back faces swap based on direction * Add loop over all sensors in stack so all sensors are added to volume, not just the first in the stack * Add constants for hollow stave based geometry * Add carbon fiber material, functionality to toggle between stave and old geometry. Also rename material counts to 'volume' instead of 'sensor'. EDIT: Change TGeo layer thickness back to original. EDIT2: Change Middle layer disc usage to newly added segmentation. * Add full hollow stave geometry functionality * Make Stave layout the standard and remove old kSegmentedMarch26: EDIT 20/04/26: Revert to newly added middle disc layer segmentation. * Remove all instances of old slab geometry and work only with staves. * Remove all log(info) statements, except one which is changed to debug. * Don't create the separation layer for stave layout, since we already get the structural support (in carbon) from the staves * add bools for cutting staves and sensors on staves on nominal radii * merge two if statements with the same if * Add implementations for cutting staves and sensors on nominal radii. Also change kSensorsPerStack to a vector in which order of sensor stack height we will pad the staves. * Fix bug in default y range for staves & sensor placements * Bugfix: Stack correctly by using previous stack height in fill_stave, and move in correct direction when placing sensors later * Remove now stale info statements. TODO: let staves be cut as well on nominal radii * Add splitting of stave in case of strict inner cutoff * Add support to place either a stack gap or single sensor around y=0 in staves that have full +Rout to -Rout coverage * Bugfix: When starting sensor placement around the x-axis you can have a different number of sensor stacks on the positive/negative side of the stave leading to garbage memory access. Fixed now. * Add option to draw reference circles onto the layer -- strictly for visualisation purposes * remove stale info statements * Remove stale overlap argument, and add local offset in z. In contrast to previous layout, to encapsulate the staves with face at local z=0 in air, we need to shift the staves and sensors locally since the mother volume is always around local z=0. Shift the layer volume by the same amount when adding it to get the right global position. * Fix placement issue to get sensor materials inside the volume. This now means that the stave faces are not at local z=0 but instead at z=+-totalSensorMaterialThickness+0.1. Also fix global position bug since movement outwards is directional. * Add OT only segmentation and change defaults * Make stave geometry available with middle layer disks as well. Currently use simple calculated values for stave placements, these are subject to change. Hence the existence of kSegmentedOTOnly * Change default to stave segmentation for outer disks only * Please consider the following formatting changes --------- Co-authored-by: ALICE Action Bot --- .../FT3/base/include/FT3Base/FT3BaseParam.h | 14 +- .../include/FT3Simulation/FT3Layer.h | 1 + .../include/FT3Simulation/FT3Module.h | 65 +- .../FT3Simulation/FT3ModuleConstants.h | 206 ++++++ .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 87 ++- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 660 ++++++++++++++++++ 6 files changed, 1022 insertions(+), 11 deletions(-) create mode 100644 Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index 67bf42458a88a..b0f26bc4675d4 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -24,10 +24,12 @@ enum eFT3Layout { kCylindrical = 0, kTrapezoidal, kSegmented, + kSegmentedStave, + kSegmentedStaveOTOnly }; struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // Geometry Builder parameters - eFT3Layout layoutFT3 = kSegmented; + eFT3Layout layoutFT3 = kSegmentedStaveOTOnly; int nTrapezoidalSegments = 32; // for the simple trapezoidal disks // FT3Geometry::Telescope parameters @@ -38,6 +40,16 @@ struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { Float_t etaOut = 1.5; Float_t Layerx2X0 = 0.01; + // override values from FT3ModuleConstants, inner and outer + bool cutStavesOnNominalRadius_inner = true; + bool cutStavesOnNominalRadius_outer = false; + + // What to place over x=0 line in case of full outer-outer stave: Gap or Sensor + bool placeSensorInMiddleOfStave = false; + + // Draw reference circles at inner and outer radius of stave layer, for visualisation + bool drawReferenceCircles = false; + O2ParamDef(FT3BaseParam, "FT3Base"); }; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h index f6acebe80ac33..282f8fd274ec0 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h @@ -64,6 +64,7 @@ class FT3Layer : public TObject // create layer for disk support void createSeparationLayer(TGeoVolume* motherVolume, const std::string& separationLayerName); void createSeparationLayer_waterCooling(TGeoVolume* motherVolume, const std::string& separationLayerName); + void createReferenceCircles(TGeoVolume* motherVolume, const std::string& name); static TGeoMaterial* carbonFiberMat; static TGeoMedium* medCarbonFiber; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index 15ac6be995646..1311c6a4ff1b5 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -17,6 +17,17 @@ #include #include +#include + +#include "FT3Simulation/FT3ModuleConstants.h" + +// define types for y positions, second element is the stack height +using PositionType = std::pair; +using PositionTypes = std::vector; +using PosNegPositionTypes = std::pair; +// define type of the y position range: First pair is (min, max) for positive y +using PositionRangeType = std::pair, std::pair>; +namespace Constants = o2::ft3::ModuleConstants; class FT3Module { @@ -33,13 +44,63 @@ class FT3Module static TGeoMedium* epoxyMed; static TGeoMaterial* AluminumMat; static TGeoMedium* AluminumMed; + static TGeoMaterial* carbonFiberMat; + static TGeoMedium* carbonFiberMed; const char* mDetName; - static void createModule(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume); + static void createModule( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double overlap, const std::string& face, + const std::string& layout_type, TGeoVolume* motherVolume); + + void createModule_staveGeo( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double z_offset_local, const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume); private: - static void create_layout(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume); + static void create_layout( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double overlap, const std::string& face, + const std::string& layout_type, TGeoVolume* motherVolume); + + void create_layout_staveGeo( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double z_offset_local, const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume); + + // Helper functions + void fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, + double x_left, unsigned kSensorStack, PositionRangeType y_range, + std::pair& absAllowedYRange); + void addStaveVolume( + TGeoVolume* motherVolume, std::string volumeName, int direction, + unsigned* volume_count, double staveLength, + std::array, 4> staveTriangles, + std::pair& absAllowedYRange, + double x_mid, double y_mid, double z_stave_shift_forward); + void addDetectorVolume( + TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* volume_count, + double x_mid, double y_mid, double z_mid, + double x_half_length, double y_half_length, double z_half_length); + + void add2x1GlueVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid, + std::string element_glued_to); + + void add2x1CopperVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid); + + void add2x1KaptonVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid); + + void addSingleSensorVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double active_x_mid, double y_mid, double z_mid, bool isLeft); }; #endif // FT3MODULE_H diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h new file mode 100644 index 0000000000000..1fe9f404245c1 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -0,0 +1,206 @@ +// 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 FT3ModuleConstants.h +/// \brief Definition of various constants for tiling the modules of sensors + +#ifndef FT3MODULECONSTANTS_H +#define FT3MODULECONSTANTS_H + +#include +#include +#include +#include + +namespace o2::ft3::ModuleConstants +{ +/* CURRENT STATUS: + * 25x32mm sensors, 2mm inactive on one side + * Most granular layout is 2x1 sensors, where the one on the right has the inactive region + * on the right, and the one on the left has the inactive region on the left. + * When stacking 2x1 modules, there is a 0.2mm gap between them. By default, we assume this + * gap to be ABOVE the most recently placed module. + * + * |<- 25mm ->|<- 25mm ->| + * _______________________ + * ----------------------- 0.2mm gap above + * | | | | | + * | | | | | + * | | | | | + * | | | | | 32mm sensor height + * | | | | | + * | | | | | + * ------------------------ + * + */ +// First set all layout constants for the rest of the function +const double single_sensor_width = 2.5; +const double single_sensor_height = 3.2; +const double inactive_width = 0.2; +const double sensor2x1_gap = 0.02; +const double stackGap = sensor2x1_gap; // gap between 2xN module stacks + +const double active_width = single_sensor_width - inactive_width; +const double active_height = single_sensor_height; + +const double sensor2x1_width = 2 * single_sensor_width; +const double sensor2x1_active_width = 2 * active_width; +const double sensor2x1_height = single_sensor_height; +const std::vector kSensorsPerStack = {4, 2, 1}; +inline const double getStackHeight(unsigned nSensorsPerStack) +{ + return nSensorsPerStack * sensor2x1_height + + (nSensorsPerStack - 1) * sensor2x1_gap; +} + +// small helper function to get 1-indexed stave ID, counting from the middle outwards, +// with negative IDs on the left and positive IDs on the right +inline const int staveIdxToID(int staveIdx, unsigned nStavesPerDisc) +{ + unsigned nStavesOneSide = nStavesPerDisc / 2; + bool isRight = staveIdx >= nStavesOneSide; + return staveIdx - nStavesOneSide + isRight; +} + +// material properties +const double siliconThickness = 0.01; +const double copperThickness = 0.006; +const double kaptonThickness = 0.03; +const double epoxyThickness = 0.0012; + +const double effectiveCarbonThickness_Stave = 0.02; // foam + shell +const double staveOpeningAngle = 60 * TMath::DegToRad(); +const double sinTheta = TMath::Sin(staveOpeningAngle / 2); +const double alpha = TMath::Pi() / 2 - staveOpeningAngle / 2; // bottom angles +const double staveSensorGap = 0.1; // 2mm padding on each side when sensor is glued +const double staveTriangleHeight = (sensor2x1_width + 2 * staveSensorGap) / 2.0 / tan(staveOpeningAngle / 2.0); +/* + * Now describe the offset of every other stave in z to avoid overlaps + * ______ ______ + * \ /______\ / | <-- z_offsetStave + * \ / \ / \ / + * \/ \ / \/ + * \/ + */ +// If midpoint spacing becomes non constant, this becomes a function +// TODO: add some tolerance to avoid overlaps? +inline const double z_offsetStave(double x_midpoint_spacing) +{ + return staveTriangleHeight * + (2 - x_midpoint_spacing / (sensor2x1_width / 2 + staveSensorGap)); +} + +const int SiColor = kGreen; +const int SiInactiveColor = kRed; +const int glueColor = kBlue; +const int CuColor = kOrange; +const int kaptonColor = kYellow; +const int carbonColor = kBlack; + +// Struct for stave position configuration (varies between IT/OT) +struct StaveConfig { + /* + * Constants for staves are written for both positive + * and negative x even though they are just mirrored now, + * because there might be design changes in the future + * that require a non-mirrored layout, making it easier to + * change here if so required, even though it looks uglier now. + * + * The second element in the mapping pair is whether the stave + * with a certain ID should be mirrored around the x-axis. + */ + // map from Stave ID (1-indexed from other documents) to midpoint + // Do NOT add any zero midpoints, this is taken off separately + const std::map>& staveID_to_y_midpoint; + // lengths of staves, their midpoint, and their face + const std::vector& y_lengths; + const std::vector& x_midpoints; + double x_midpoint_spacing; + // which side of the disc do we place the stave? + // kSegmentedStave: staggering staves in z (see z_offsetStave) + // accessed via stave index, NOT stave ID + const std::vector& staveOnFront; +}; + +namespace OT_StavePositions +{ +const std::map> staveID_to_y_midpoint = { + {-2, {39.0, true}}, + {-1, {41.4, true}}, + {1, {41.4, true}}, + {2, {39.0, true}}}; +const std::vector y_lengths = { + 52.8, 66.0, 79.2, 92.4, 99.0, 105.6, 118.8, 118.8, + 128.7, 132.0, 132.0, 138.6, 138.6, 56.1, 52.8, + 52.8, 56.1, 138.6, 138.6, 132.0, 132.0, 128.7, + 118.8, 118.8, 105.6, 99.0, 92.4, 79.2, 66.0, 52.8}; +const std::vector x_midpoints = { + -65.25, -60.75, -56.25, -51.75, -47.25, -42.75, -38.25, // L + -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L + 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75, // R + 38.25, 42.75, 47.25, 51.75, 56.25, 60.75, 65.25 // R +}; +const double x_midpoint_spacing = 4.5; // assume constant for now +const std::vector staveOnFront = + { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, // L + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 // R +}; +} // namespace OT_StavePositions + +namespace ML_StavePositions +{ +// Use prelim numbers for now, these will change! TODO +const std::map> staveID_to_y_midpoint = { + {-3, {19.1, true}}, + {-2, {21.8, true}}, + {-1, {22.5, true}}, + {1, {22.5, true}}, + {2, {21.8, true}}, + {3, {19.1, true}}}; +const std::vector y_lengths = { + 30.5, 44.5, 53.6, 60.0, 64.6, 29.5, 25.8, 25.0, + 25.0, 25.8, 29.5, 64.6, 60.0, 53.6, 44.5, 30.5}; +const std::vector x_midpoints = { + -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L + 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75 // R +}; +const double x_midpoint_spacing = 4.5; +const std::vector staveOnFront = + { + 1, 0, 1, 0, 1, 0, 1, 0, // L + 0, 1, 0, 1, 0, 1, 0, 1 // R +}; +} // namespace ML_StavePositions + +// Get stave configuration based on tracker type +inline StaveConfig getStaveConfig(bool isInnerDisk) +{ + if (isInnerDisk) { + return StaveConfig{ + ML_StavePositions::staveID_to_y_midpoint, + ML_StavePositions::y_lengths, + ML_StavePositions::x_midpoints, + ML_StavePositions::x_midpoint_spacing, + ML_StavePositions::staveOnFront}; + } else { + return StaveConfig{ + OT_StavePositions::staveID_to_y_midpoint, + OT_StavePositions::y_lengths, + OT_StavePositions::x_midpoints, + OT_StavePositions::x_midpoint_spacing, + OT_StavePositions::staveOnFront}; + } +} + +} // namespace o2::ft3::ModuleConstants + +#endif // FT3MODULECONSTANTS_H \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 5be3c7abc30a3..cc6accda3adb8 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -17,6 +17,7 @@ #include "FT3Simulation/FT3Layer.h" #include "FT3Base/GeometryTGeo.h" #include "FT3Base/FT3BaseParam.h" +#include "FT3Simulation/FT3ModuleConstants.h" #include // for TGeoManager, gGeoManager #include // for TGeoCombiTrans, TGeoRotation, etc @@ -226,6 +227,25 @@ void FT3Layer::createSeparationLayer(TGeoVolume* motherVolume, const std::string motherVolume->AddNode(carbonFiberLayerVol2, 1, new TGeoTranslation(0, 0, 0 + zSeparation)); } +void FT3Layer::createReferenceCircles(TGeoVolume* motherVolume, const std::string& name) +{ + + // create reference circles at the inner and outer radius of the layer, for visualization purposes + TGeoTube* innerCircle = new TGeoTube(mInnerRadius - 0.1, mInnerRadius + 0.1, 0.01); + TGeoTube* outerCircle = new TGeoTube(mOuterRadius - 0.1, mOuterRadius + 0.1, 0.01); + + TGeoVolume* innerCircleVol = new TGeoVolume((mLayerName + "_InnerCircle").c_str(), innerCircle, gGeoManager->GetMedium("FT3_AIR$")); + TGeoVolume* outerCircleVol = new TGeoVolume((mLayerName + "_OuterCircle").c_str(), outerCircle, gGeoManager->GetMedium("FT3_AIR$")); + + innerCircleVol->SetLineColor(kRed); + outerCircleVol->SetLineColor(kBlue); + + double z_position = mDirection ? 0.5 : -0.5; + + motherVolume->AddNode(innerCircleVol, 1, new TGeoTranslation(0, 0, z_position)); + motherVolume->AddNode(outerCircleVol, 1, new TGeoTranslation(0, 0, z_position)); +} + void FT3Layer::createLayer(TGeoVolume* motherVolume) { auto& ft3Params = FT3BaseParam::Instance(); @@ -234,7 +254,9 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(fatal) << "Invalid layer number " << mLayerNumber << " for FT3 layer."; } - LOG(info) << "FT3: ft3Params.layoutFT3 = " << ft3Params.layoutFT3; + LOG(info) << "FT3: ft3Params.layoutFT3 = " << ft3Params.layoutFT3 + << " Creating Layer " << mLayerNumber << " at z=" << mZ + << " with direction " << mDirection; // ### options for ML and OT disk layout if (ft3Params.layoutFT3 == kTrapezoidal /*|| (mIsMiddleLayer && ft3Params.layoutFT3 == kSegmented)*/) { @@ -381,7 +403,8 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); - } else if (ft3Params.layoutFT3 == kSegmented) { + } else if (ft3Params.layoutFT3 == kSegmented || + (ft3Params.layoutFT3 == kSegmentedStaveOTOnly && mIsMiddleLayer)) { FT3Module module; // layer structure @@ -390,20 +413,68 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); // Add a little additional room in radius; Try with 1.5 cm thickness - TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + TGeoVolume* layerVol = nullptr; + // Add a little additional room in radius + TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); + layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); layerVol->SetLineColor(kYellow + 2); - // createSeparationLayer_waterCooling(motherVolume, separationLayerName); createSeparationLayer(layerVol, separationLayerName); - - // create disk faces module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); // Finally put everything in the mother volume auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); - auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ, FwdDiskRotation); + // need to shift outwards always, so + forwards and - backwards + auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ + 0, FwdDiskRotation); + + LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); + motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); + } else if (ft3Params.layoutFT3 == kSegmentedStave || + ft3Params.layoutFT3 == kSegmentedStaveOTOnly) { + FT3Module module; + + // layer structure + std::string frontLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Front"; + std::string backLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Back"; + std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); + + TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); + TGeoVolume* layerVol = nullptr; + + // set up stave config, differs between ML and OT disks + const Constants::StaveConfig& staveConfig = Constants::getStaveConfig(mIsMiddleLayer); + + // need a thicker air layer to encompass the staves (4.5cm high, 1.2cm offsets) + // stave face is at z=0 (or +-z_offset_stave), meaning that volumes are at + // ~-+1cm < z < ~+-6cm, the +- referring forward/backward discs + double z_layer_thickness = // need to shift internally with this + o2::ft3::ModuleConstants::staveTriangleHeight + + o2::ft3::ModuleConstants::z_offsetStave(staveConfig.x_midpoint_spacing) + + o2::ft3::ModuleConstants::siliconThickness + + o2::ft3::ModuleConstants::copperThickness + + o2::ft3::ModuleConstants::kaptonThickness + + o2::ft3::ModuleConstants::epoxyThickness * 2 + + 0.5; // add some extra room to ensure all volumes are encapsulated + + // shift stave volumes into layer volume, since nominal z_{stave face} = 0 + double z_local_offset = z_layer_thickness / 2.0; + TGeoTube* layer = new TGeoTube(mInnerRadius - 12, mOuterRadius + 5, z_layer_thickness / 2); + layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + + if (ft3Params.drawReferenceCircles) { + std::string referenceCirclesName = "ReferenceCircles_Dir" + std::to_string(mDirection) + "_Layer" + std::to_string(mLayerNumber); + createReferenceCircles(layerVol, referenceCirclesName); // for visualization purposes + } + + // need the -0.5 added to local offset to ensure all sensor modules are inside the layer + module.createModule_staveGeo(0., mLayerNumber, mDirection, mInnerRadius, + mOuterRadius, z_local_offset, staveConfig, layerVol); + // Finally put everything in the mother volume + auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); + // need to shift outwards always, so + forwards and - backwards + double z_offset_directional = mDirection ? z_local_offset : -z_local_offset; + auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ + z_offset_directional, FwdDiskRotation); LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 4ed330c35ae59..221136000a371 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -13,17 +13,21 @@ /// \brief Implementation of the FT3Module class #include "FT3Simulation/FT3Module.h" +#include "FT3Base/FT3BaseParam.h" #include #include #include #include +#include #include +#include #include #include #include #include #include #include +#include TGeoMaterial* FT3Module::siliconMat = nullptr; TGeoMedium* FT3Module::siliconMed = nullptr; @@ -40,6 +44,9 @@ TGeoMedium* FT3Module::epoxyMed = nullptr; TGeoMaterial* FT3Module::AluminumMat = nullptr; TGeoMedium* FT3Module::AluminumMed = nullptr; +TGeoMaterial* FT3Module::carbonFiberMat = nullptr; +TGeoMedium* FT3Module::carbonFiberMed = nullptr; + void FT3Module::initialize_materials() { LOG(debug) << "FT3Module: initialize_materials"; @@ -62,6 +69,10 @@ void FT3Module::initialize_materials() kaptonMat = new TGeoMaterial("FT3_Kapton", 13.84, 6.88, 1.346); kaptonMed = new TGeoMedium("FT3_Kapton", 3, kaptonMat); + // TODO: Check with Rene the exact type of carbon fiber + carbonFiberMat = new TGeoMaterial("FT3_Carbon", 12.0107, 6, 1.8); + carbonFiberMed = new TGeoMedium("FT3_Carbon", 6, carbonFiberMat); + // Epoxy: C18 H19 O3 auto* itsEpoxy = new TGeoMixture("FT3_Epoxy", 3); itsEpoxy->AddElement(itsC, 18); @@ -82,6 +93,643 @@ double calculate_y_circle(double x, double radius) return (x * x < radius * radius) ? std::sqrt(radius * radius - x * x) : 0; } +std::pair calculate_y_range( + double x_left, double x_right, double Rin, double Rout) +{ + double max_y_abs; + double min_y_abs; + /* + * Have 5 cases: + * (1) Stave wholly on the left of inner radius + * (2) Stave wholly on the left, but within inner radius + * (3) Stave crosses the middle x=0 + * (4) Stave wholly on the right, but within inner radius + * (5) Stave wholly on the right of inner radius + */ + if (x_right < -Rin) { + // Stave is completely on the left of inner radius + min_y_abs = 0; + max_y_abs = calculate_y_circle(x_left, Rout); + } else if (x_left < -Constants::sensor2x1_width) { + // Stave is completely on the left, but within inner radius + min_y_abs = calculate_y_circle(x_right, Rin); + max_y_abs = calculate_y_circle(x_left, Rout); + } else if (x_left < 0) { + // Stave crosses the middle x=0 + min_y_abs = Rin; + // x_right should be > 0, but might have FLP issues, so do abs nonetheless + max_y_abs = calculate_y_circle(std::max(std::abs(x_left), std::abs(x_right)), Rout); + } else if (x_left < Rin) { + // Stave is completely on the right, but within inner radius + min_y_abs = calculate_y_circle(x_left, Rin); + max_y_abs = calculate_y_circle(x_right, Rout); + } else { + // Stave is completely on the right of inner radius + min_y_abs = 0.; + max_y_abs = calculate_y_circle(x_right, Rout); + } + return {min_y_abs, max_y_abs}; +} + +/* + * This function is a helper function which will pad out the stave with sensors + * until there is no more space available. + * + * Arguments: + * y_positions: a pair of vectors, where each vector contains pairs of + * y position and stack height for the positive and negative y positions respectively. + * This argument will be appended with the new sensor positions and stack heights. + * Rout: the outer radius of the layer + * Rin: the inner radius of the layer + * x_left: the x position of the left edge of the sensor to be placed + * kSensorStack: the number of sensors to be stacked on top of each other + * tolerance: the tolerance to be subtracted from the maximum y position to avoid + * placing sensors too close to the edge. If this is negative, it effectively + * means that you can place sensors beyond the nominal disc edge + * y_start: the y positions to start placing sensors, + * for positive and negative y respectively + */ +void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, + double x_left, unsigned kSensorStack, PositionRangeType y_ranges, + std::pair& absAllowedYRange) +{ + // start with upper half of the stave, then mirror to the bottom half + // add the height of kSensorStack sensors + the gaps in between them + double sensorStackHeight = Constants::getStackHeight(kSensorStack); + double sensorAbsStackYShift = sensorStackHeight + Constants::stackGap; + + // in case a big tolerance is given, cut on the given range instead + double max_sensor_y_abs = std::min(absAllowedYRange.second, y_ranges.first.second); + + double y_top; // top half of the xy grid, y>0 + // either start at given value (adjusted for tolerance), or at last placed sensors + if (!y_positions.first.empty()) { // sensors already placed + double previousStackHeight = Constants::getStackHeight(y_positions.first.back().second); + y_top = y_positions.first.back().first + previousStackHeight + Constants::stackGap; + } else if (absAllowedYRange.first > 0) { + // there is a minimum inner value --> start at the max of the two + y_top = std::max(absAllowedYRange.first, y_ranges.first.first); + } else { + // No inner minimum value, start at given value + y_top = y_ranges.first.first; + } + // fill positive y sensor positions + while ((y_top + sensorStackHeight) <= max_sensor_y_abs) { + y_positions.first.emplace_back(y_top, kSensorStack); + y_top += sensorAbsStackYShift; + } + + // now we do the same for the negative y positions + // they do not have to be exactly mirrored, hence done separately + double y_bottom; + if (!y_positions.second.empty()) { + // subtract instead to move further down + double previousStackHeight = Constants::getStackHeight(y_positions.second.back().second); + y_bottom = y_positions.second.back().first - previousStackHeight - Constants::stackGap; + } else if (absAllowedYRange.first > 0) { + // there is a minimum inner value --> start at the min of the two + y_bottom = std::min(-absAllowedYRange.first, y_ranges.second.first); + } else { + // No inner minimum value, start at given value + y_bottom = y_ranges.second.first; + } + // fill in the sensors on negative y + while ((y_bottom - sensorStackHeight) >= -max_sensor_y_abs) { + y_positions.second.emplace_back(y_bottom, kSensorStack); + y_bottom -= sensorAbsStackYShift; + } +} + +/* + * Create the vertices of the triangles that make up the stave cross section + * + * Each array of 3 corresponds to x or z values of the 3 triangle vertices, + * and the outer array corresponds to which triangle: + * + * [x_outer, z_outer, x_inner, z_inner], each of which has three values + */ +std::array, 4> buildStaveTriangle(int direction) +{ + // Set some constants for readability + double d = Constants::effectiveCarbonThickness_Stave; + double H = Constants::staveTriangleHeight; + /* + * Inner and outer vertices of the stave cross section triangle + * all vertices are at y_mid, we simply extend the triangle into y dir. + * We work in the local coordinate system of the stave, but still + * call the coordinates x and z for readability. + * + * 1. Get all local coordinates of the two triangle vertices + * 2. Extrude a volume from the subtracted triangle cross section area + * 3. Rotate the volume around the x-axis since it is by default in xy, + * and extruded in z. Rotate by -90 for xz -> xy, otherwise xz -> x(-y) + * 4. Translate the volume to the given position (arguments) + * + */ + std::array xv_inner, xv_outer, zv_inner, zv_outer; + // calculate the coordinates of the triangle vertices + // Top/bottom vertex (apex) + xv_outer[0] = 0; + zv_outer[0] = (direction == 1) ? -H + : H; + ; + // right + xv_outer[1] = Constants::sensor2x1_width / 2 + Constants::staveSensorGap; + zv_outer[1] = 0; + // left + xv_outer[2] = -xv_outer[1]; + zv_outer[2] = 0; + + // now get inner vertices, shifted inwards by effective carbon thickness + xv_inner[0] = xv_outer[0]; + double z_shift_inner = d / Constants::sinTheta; + zv_inner[0] = (direction == 1) ? zv_outer[0] + z_shift_inner + : zv_outer[0] - z_shift_inner; + // face vertices, first right + zv_inner[1] = (direction == 1) ? zv_outer[1] - d + : zv_outer[1] + d; + double x_shift_abs = d / TMath::Tan(Constants::alpha / 2); + xv_inner[1] = xv_outer[1] - x_shift_abs; + // left + zv_inner[2] = zv_inner[1]; + xv_inner[2] = -xv_inner[1]; + + return {xv_outer, zv_outer, xv_inner, zv_inner}; +} + +/* + * This function creates a carbon fibre volume for the stave, + * onto which the sensor and its support will be glued. + */ +void FT3Module::addStaveVolume( + TGeoVolume* motherVolume, std::string volumeName, int direction, + unsigned* volume_count, double staveLength, + std::array, 4> staveTriangles, + std::pair& absAllowedYRange, + double x_mid, double y_mid, double z_stave_shift_forward) +{ + // The allowed y range is assumed to be non-negative. + if (absAllowedYRange.first < 0 || absAllowedYRange.second < 0 || + absAllowedYRange.first >= absAllowedYRange.second) { + LOG(error) << "Invalid allowed y range in addStaveVolume(): (" + << absAllowedYRange.first << ", " << absAllowedYRange.second + << "). Both values must be non-negative and the first " + << "value must be less than the second value."; + return; + } + // Set the lower and upper y values of the stave: + double y_lower = y_mid - staveLength / 2; + double y_upper = y_mid + staveLength / 2; + bool splitStave = false; + if (y_lower > 0) { // This stave is fully above x-axis + y_lower = std::max(y_lower, absAllowedYRange.first); + y_upper = std::min(y_upper, absAllowedYRange.second); + } else if (y_upper < 0) { // stave entirely below x-axis + y_lower = std::max(y_lower, -absAllowedYRange.second); + y_upper = std::min(y_upper, -absAllowedYRange.first); + } else { // Full range stave that goes across x-axis + // Here we might have to cut the stave up into two pieces + if (absAllowedYRange.first > 0) { + // There is a minimum inner value --> Split stave + splitStave = true; + y_lower = absAllowedYRange.first; + } else { + // regular stave, use full length, but don't forget outer cut + y_lower = std::max(y_lower, -absAllowedYRange.second); + } + y_upper = std::min(y_upper, absAllowedYRange.second); + } + double staveLengthToUse = y_upper - y_lower; + /* + * create the extruded volumes from z=0 (later y=0 after rotation) to stave length + * and not from midpoint - staveLength/2 to midpoint + staveLength/2, translate later + * + * Note also that we first need to check if the length is allowed given the inner + * and outer radius of the layer. + */ + TGeoXtru* staveFull = new TGeoXtru(2); + staveFull->SetName((volumeName + "_Xtru_outer").c_str()); + staveFull->DefinePolygon(3, staveTriangles[0].data(), staveTriangles[1].data()); + staveFull->DefineSection(0, 0); + staveFull->DefineSection(1, staveLengthToUse); + + TGeoXtru* staveInner = new TGeoXtru(2); + staveInner->SetName((volumeName + "_Xtru_inner").c_str()); + staveInner->DefinePolygon(3, staveTriangles[2].data(), staveTriangles[3].data()); + staveInner->DefineSection(0, 0); + staveInner->DefineSection(1, staveLengthToUse); + + TGeoCompositeShape* staveShape = new TGeoCompositeShape( + (volumeName + "_shape").c_str(), + Form("%s - %s", staveFull->GetName(), staveInner->GetName())); + TGeoVolume* staveVolume = new TGeoVolume( + (volumeName).c_str(), + staveShape, + carbonFiberMed); + TGeoRotation* rot = new TGeoRotation(); + rot->RotateX(-90); // lift from xy plane into xz plane + /* + * After rotations the face of the stave lies in the xy-plane, + * facing downwards for direction == 1 and upwards for direction == 0. + * We still need to shift it in z to get the right staggered layout. + * This means moving the staves that must be shifted in the opposite + * direction they are facing: up for direction 1, and down for direction 0. + * + * Unlike a regular node placement, we have to put the stave at its + * starting point in y, not the midpoint. Hence, if we have the mirror, + * the starting point is the upper y value, since that is the bottom + * of the mirrored stave -- by the outer radius + */ + double z_shift = (direction == 1) ? z_stave_shift_forward : -z_stave_shift_forward; + TGeoCombiTrans* combiTrans = + new TGeoCombiTrans(x_mid, y_lower, z_shift, rot); + motherVolume->AddNode(staveVolume, + *volume_count, + combiTrans); + (*volume_count)++; + + // if the stave needs to be split, reuse the same volume on opposite side + if (splitStave) { + TGeoCombiTrans* combiTransSplit = + new TGeoCombiTrans(x_mid, -y_upper, z_shift, rot); + motherVolume->AddNode(staveVolume, + *volume_count, + combiTransSplit); + (*volume_count)++; + } +} + +/* + * Generic helper function that adds a box at the given position with + * the given dimensions to the given mother volume, with the given color and name. + */ + +void FT3Module::addDetectorVolume( + TGeoVolume* motherVolume, std::string volumeName, int color, + unsigned* volume_count, double x_mid, double y_mid, double z_mid, + double x_half_length, double y_half_length, double z_half_length) +{ + TGeoManager* geoManager = gGeoManager; + TGeoVolume* volume = geoManager->MakeBox(volumeName.c_str(), siliconMed, x_half_length, + y_half_length, z_half_length); + volume->SetLineColor(color); + volume->SetFillColorAlpha(color, 0.4); + motherVolume->AddNode( + volume, + *volume_count, + new TGeoTranslation( // midpoint of box to add + x_mid, + y_mid, + z_mid) // TGeoTranslation + ); // addNode + (*volume_count)++; +} + +/* + * This function adds a glue volume between two element layers, + * immediately for a whole 2x1 layout, under both the active and inactive region. + */ +void FT3Module::add2x1GlueVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid, + std::string element_glued_to) +{ + std::string glue_name = "FT3glue_" + element_glued_to + "_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); + addDetectorVolume( + motherVolume, glue_name, Constants::glueColor, volume_count, + x_mid, y_mid, z_mid, + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::epoxyThickness / 2); +} + +/* + * This function adds a copper volume onto which the silicon sensor is glued. + * As with the glue, this is a whole 2x1 layout volume. + */ +void FT3Module::add2x1CopperVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid) +{ + std::string copper_name = "FT3Copper_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); + addDetectorVolume( + motherVolume, copper_name, Constants::CuColor, volume_count, + x_mid, y_mid, z_mid, + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::copperThickness / 2); +} + +/* + * This function adds a kapton volume behind the copper, which represents the ??? + * As with copper and glue, this is a whole 2x1 layout volume. + */ +void FT3Module::add2x1KaptonVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid) +{ + std::string kapton_name = "FT3Kapton_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); + addDetectorVolume( + motherVolume, kapton_name, Constants::kaptonColor, volume_count, + x_mid, y_mid, z_mid, + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::kaptonThickness / 2); +} + +/* + * This function adds a single sensor (currently 2.5x3.2mm) to the given mother volume + * at the given (x,y,z) position of the module. + * + * Because the sensor has an inactive region of 0.2mm on one side, we also add a + * separate volume for the inactive region, which will be either on the left or + * or right dependent on the if the sensor is on the left or right in a 2x1 layout. + * See FT3Module.h for more details on the layout. + * + * Arguments: + * motherVolume: the volume to which the sensor volume will be added + * layerNumber: the layer number of the sensor, used for naming + * direction: the direction of the sensor (forward or backward eta), used for naming + * x_mid: the x position of the center of the sensor volume + * y_mid: the y position of the center of the sensor volume + * z_mid: the z position of the center of the sensor volume + * isLeft: whether the sensor is on the left or right in the 2x1 layout + */ +void FT3Module::addSingleSensorVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double active_x_mid, double y_mid, double z_mid, + bool isLeft) +{ + TGeoVolume* sensor; + TGeoManager* geoManager = gGeoManager; + // ACTIVE AREA + std::string sensor_name = "FT3Sensor_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); + sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, Constants::active_width / 2, + Constants::single_sensor_height / 2, Constants::siliconThickness / 2); + sensor->SetLineColor(Constants::SiColor); + sensor->SetFillColorAlpha(Constants::SiColor, 0.4); + motherVolume->AddNode( + sensor, + *volume_count, + new TGeoTranslation( // midpoint of box to add + active_x_mid, + y_mid, + z_mid) // TGeoTranslation + ); // addNode + (*volume_count)++; + // INACTIVE STRIP ON LEFT OR RIGHT + double inactive_x_mid = isLeft ? (active_x_mid - Constants::active_width / 2 - Constants::inactive_width / 2) + : (active_x_mid + Constants::active_width / 2 + Constants::inactive_width / 2); + std::string sensor_inactive_name = + "FT3Sensor_Inactive_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); + sensor = geoManager->MakeBox(sensor_inactive_name.c_str(), siliconMed, Constants::inactive_width / 2, + Constants::single_sensor_height / 2, Constants::siliconThickness / 2); + sensor->SetLineColor(Constants::SiInactiveColor); + sensor->SetFillColorAlpha(Constants::SiInactiveColor, 0.4); + motherVolume->AddNode( + sensor, + *volume_count, + new TGeoTranslation( // midpoint of box to add + inactive_x_mid, + y_mid, + z_mid) // TGeoTranslation + ); // addNode + (*volume_count)++; +} + +void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction, + double Rin, double Rout, double z_offset_local, + const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume) +{ + LOG(debug) << "FT3Module: create_layout_staveGeo - Layer " + << layerNumber << ", Direction " << direction; + + FT3Module::initialize_materials(); + auto& ft3Params = o2::ft3::FT3BaseParam::Instance(); + + // First let's define some constants used throughout + /* + * we build the volume from the outside in, starting with the silicon, + * then glue & materials towards the stave. Depending on direction, + * the distance from the center will be mirrored. + * + * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON STAVE | + * ----------------------------------------------------------------> z + * + * Naturally, this will be mirrored for layers in the backwards direction, + * such that the face of the sensors always face the interaction region. + * + * Currently, we stipulate that the default stave face is at local z=0, + * that is then shifted by the half air thickness encapsulating the layer + * to avoid overlaps with the air and services. All offsets are + * calculated for backward direction (since that is a positive shift), + * and then flipped for forward. At that point, the innermost/frontmost + * stave face is at the edge of the air volume, so we shift it back a little + * to make space for the sensor materials and a slight margin. + */ + double totalSensorMaterialThickness = + Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness + Constants::siliconThickness; + double z_offset_to_carbon_face = z_offset_local - totalSensorMaterialThickness - 0.1; + double z_offset_to_glue_Ka = + z_offset_to_carbon_face + Constants::epoxyThickness / 2; + double z_offset_to_kapton = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness / 2; + double z_offset_to_copper = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness + Constants::copperThickness / 2; + double z_offset_to_glue_Si = + z_offset_to_carbon_face + Constants::epoxyThickness + Constants::kaptonThickness + + Constants::copperThickness + Constants::epoxyThickness / 2; + double z_offset_to_silicon = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness + Constants::siliconThickness / 2; + + // initialise all y_positions, vector over all staves/columns + std::vector y_positionsPosNeg; + unsigned volume_count = 0; // give each subvolume a unique ID + // stave triangle cross sections are the same for every stave (direction based) + std::array, 4> staveTriangles = buildStaveTriangle(direction); + // Create the stave volumes and fill the y positions where to put sensors on the stave + for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) { + y_positionsPosNeg.emplace_back(PosNegPositionTypes{PositionTypes{}, PositionTypes{}}); + const int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size()); + + double y_midpoint = 0.; + bool mirrorStaveAroundX = false; + // default positive and negative starting points has a gap around x-axis for symmetry + double stave_half_length = staveConfig.y_lengths[i_stave] / 2; + PositionRangeType y_ranges; + if (ft3Params.placeSensorInMiddleOfStave) { + /* + * We want a sensor to cross over the x-axis for coverage at y=0 + * N.B. not necessarily exactly mirrored, only if stack gap is the same + * as the gap between sensors in a stack. + */ + y_ranges = {{-Constants::sensor2x1_height / 2, + stave_half_length}, + {-Constants::sensor2x1_height / 2 - Constants::stackGap, + -stave_half_length}}; + } else { + /* + * Otherwise have a gap around y=0, so sensors are not placed there. + * This means the stave is perfectly mirrored around the x-axis. + */ + y_ranges = {{Constants::stackGap / 2, stave_half_length}, + {-Constants::stackGap / 2, -stave_half_length}}; + } + auto y_midpoint_it = staveConfig.staveID_to_y_midpoint.find(staveID); + if (y_midpoint_it != staveConfig.staveID_to_y_midpoint.end()) { + // there is a defined midpoint for this stave, use this for starting points + y_midpoint = y_midpoint_it->second.first; // avoid double map lookup + mirrorStaveAroundX = y_midpoint_it->second.second; + y_ranges.first = {y_midpoint - stave_half_length, y_midpoint + stave_half_length}; + y_ranges.second = {-y_midpoint + stave_half_length, -y_midpoint - stave_half_length}; + } + + // Define tolerances for cutting staves and placing sensors + double tolerance_inner = -1000; // large negative number to allow given numbers + double tolerance_outer = -1000; + // cut staves on nominal inner radius if specified + if (ft3Params.cutStavesOnNominalRadius_inner) { + tolerance_inner = 0.; + } + if (ft3Params.cutStavesOnNominalRadius_outer) { + tolerance_outer = 0.; + } + + /* + * There are three cases in which we want to mirror the stave around the x-axis, + * which correspond to the stave not going fully from + to - Rout in y. + * + * (1) The inner tolerance is 0 (or positive) + * a) AND either x_left or x_right lies within the inner radius + * (2) The inner tolerance is large (allow stave placement as wished) + * a) AND the given stave midpoint is above the inner radius + */ + double x_left = staveConfig.x_midpoints[i_stave] - Constants::sensor2x1_width / 2; + double x_right = x_left + Constants::sensor2x1_width; + std::pair absAllowedYRange = + calculate_y_range(x_left, x_right, Rin, Rout); + + /* + * Shift allowed range by tolerance. Note that both values in the range must + * be non-negative, and if the inner is not, then set it to 0. This just means + * that there is no lower limit. The upper limit must however be larger than 0, + * if it is not, then skip this stave and give a warning. + */ + absAllowedYRange.first += tolerance_inner; + absAllowedYRange.second -= tolerance_outer; + + if (absAllowedYRange.first < 0) { + absAllowedYRange.first = 0; + } + if (absAllowedYRange.second <= 0) { + LOG(warning) << "For stave " << i_stave << " in layer " << layerNumber + << " with direction " << direction << ": no space to place sensors after applying tolerances, skipping stave."; + continue; + } + + // Get whether the stave is shifted backward or not before creating + double z_stave_shift_abs = staveConfig.staveOnFront[i_stave] ? 0 : Constants::z_offsetStave(staveConfig.x_midpoint_spacing); + double z_stave_shift_forward = // move staves more inward to fit in layer volume + -z_offset_to_carbon_face + z_stave_shift_abs; + std::string stave_volume_name = + "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + + "_" + std::to_string(direction); + addStaveVolume( + motherVolume, stave_volume_name, direction, &volume_count, + staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, + staveConfig.x_midpoints[i_stave], y_midpoint, z_stave_shift_forward); + // Now create the mirrored stave + if (mirrorStaveAroundX) { + addStaveVolume( + motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, + staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, + staveConfig.x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward); + } + + // now add the sensor positions on the stave + for (unsigned i_kSens = 0; i_kSens < Constants::kSensorsPerStack.size(); i_kSens++) { + fill_stave(y_positionsPosNeg.back(), Rin, Rout, x_left, + Constants::kSensorsPerStack[i_kSens], y_ranges, + absAllowedYRange); + } + } + + // Create volumes for the sensors and the support materials on top of the stave + for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) { + double x_mid = staveConfig.x_midpoints[i_stave]; + int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size()); + /* + * Declare an offset multiplier for the z offsets, used for distinguishing + * sensors facing either forward or backward. + * + * In the stave layout, all sensors face inward, and isFront + * refers to whether a stave is shifted backwards or not. Thus, + * we decide the offset multiplier only with direction, to + * keep the face facing inwards. + */ + bool isFront; + if (direction == 1) { // direction = 1 is forward + isFront = staveConfig.staveOnFront[i_stave]; + } else { + isFront = !(staveConfig.staveOnFront[i_stave]); + } + int z_offset_multiplier = (direction == 1) ? -1 : 1; + + // Get whether the stave is shifted for staggering or not + double z_stave_shift = 0; + if (!staveConfig.staveOnFront[i_stave]) { + // in forward direction, shifting backwards means +z shift + z_stave_shift = (direction == 1) ? Constants::z_offsetStave(staveConfig.x_midpoint_spacing) + : -Constants::z_offsetStave(staveConfig.x_midpoint_spacing); + } + + for (int y_sign = -1; y_sign < 2; y_sign += 2) { + // place sensors at positive and negative y + const auto& positions = (y_sign == 1) ? y_positionsPosNeg[i_stave].first + : y_positionsPosNeg[i_stave].second; + // define starting midpoint: y = y_start +- distance to middle of sensor + for (unsigned i_y_pos = 0; i_y_pos < positions.size(); i_y_pos++) { + double y_mid = positions[i_y_pos].first + y_sign * Constants::sensor2x1_height / 2; + for (unsigned i_sens = 0; i_sens < positions[i_y_pos].second; i_sens++) { + TGeoVolume* sensor; + // ------------ (1) Silicon sensor ------------ + // left single sensor of the 2x1 + double z_mid = z_offset_to_silicon * z_offset_multiplier + z_stave_shift; + addSingleSensorVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid - Constants::active_width / 2, y_mid, z_mid, true); + // right single sensor of the 2x1 + addSingleSensorVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid + Constants::active_width / 2, y_mid, z_mid, false); + // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ + z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift; + add2x1GlueVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid, y_mid, z_mid, "SiCu"); + // ------------ (3) Copper layer (FPC) ------------ + z_mid = z_offset_to_copper * z_offset_multiplier + z_stave_shift; + add2x1CopperVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid, y_mid, z_mid); + // ------------ (4) Kapton layer (FPC) ------------ + z_mid = z_offset_to_kapton * z_offset_multiplier + z_stave_shift; + add2x1KaptonVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid, y_mid, z_mid); + // ------------ (5) Epoxy glue layer between stave and Kapton ------------ + z_mid = z_offset_to_glue_Ka * z_offset_multiplier + z_stave_shift; + add2x1GlueVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid, y_mid, z_mid, "CarbonKapton"); + // increment to next sensor: (height + gap of one sensor) + y_mid += y_sign * (Constants::sensor2x1_height + Constants::sensor2x1_gap); + } // sensors in stack + } // for y_sign (writing of positive or negative y positions) + } // i_y_pos + } // i_stave +} + void FT3Module::create_layout(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume) { @@ -740,3 +1388,15 @@ void FT3Module::createModule(double mZ, int layerNumber, int direction, double R create_layout(mZ, layerNumber, direction, Rin, Rout, overlap, face, layout_type, motherVolume); LOG(debug) << "FT3Module: done createModule"; } + +void FT3Module::createModule_staveGeo(double mZ, int layerNumber, int direction, + double Rin, double Rout, double z_offset_local, + const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume) +{ + LOG(debug) << "FT3Module: createModule_staveGeo - Layer " << layerNumber + << " at z=" << mZ << ", Direction " << direction; + create_layout_staveGeo(mZ, layerNumber, direction, Rin, Rout, + z_offset_local, staveConfig, motherVolume); + LOG(debug) << "FT3Module: done createModule_staveGeo"; +} From d477eed4d180328b3411ceb78bc000c5c05c1aed Mon Sep 17 00:00:00 2001 From: shahoian Date: Mon, 4 May 2026 18:58:36 +0200 Subject: [PATCH 030/102] Optionally postpocess histoManagers before drawing --- .../GlobalTrackingStudy/CheckResidSpec.h | 2 +- .../study/src/CheckResidSpec.cxx | 90 +++++++++++-------- .../study/src/check-resid-workflow.cxx | 5 +- 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h index fceba6cb000fd..3cae8e94b8e68 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h @@ -19,7 +19,7 @@ namespace o2::checkresid { /// create a processor spec -o2::framework::DataProcessorSpec getCheckResidSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool drawOnly); +o2::framework::DataProcessorSpec getCheckResidSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool drawOnly, bool postProcOnly); } // namespace o2::checkresid diff --git a/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx index ff73be857346f..01ec999fce1eb 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx @@ -75,8 +75,8 @@ using timeEst = o2::dataformats::TimeStampWithError; class CheckResidSpec : public Task { public: - CheckResidSpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool drawOnly) - : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mDrawOnly(drawOnly) + CheckResidSpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool drawOnly, bool postProcOnly) + : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mDrawOnly(drawOnly), mPostProcOnly(postProcOnly) { } ~CheckResidSpec() final = default; @@ -110,6 +110,7 @@ class CheckResidSpec : public Task GTrackID::mask_t mTracksSrc{}; bool mDrawOnly = false; + bool mPostProcOnly = false; bool mDraw = false; bool mFillHistos = true; bool mFillTree = true; @@ -179,8 +180,17 @@ void CheckResidSpec::init(InitContext& ic) void CheckResidSpec::run(ProcessingContext& pc) { + bool quit = false; + if (mPostProcOnly) { + + postProcessHistos(); + quit = true; + } if (mDrawOnly) { drawHistos(); + quit = true; + } + if (quit) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); return; @@ -689,43 +699,53 @@ void CheckResidSpec::bookHistos() void CheckResidSpec::postProcessHistos() { printf("Fitting histos\n"); + if (!mHMan) { + if (mHManV.empty()) { + LOGP(warn, "nothing to process"); + return; + } + mHMan = mHManV[0].get(); + } const auto& params = o2::checkresid::CheckResidConfig::Instance(); auto gs = new TF1("gs", "gaus", -1, 1); + int maxH = mPostProcOnly ? mHManV.size() : 1; TObjArray arr; - auto* histm = mHMan; - auto fitSlices = [&](int id) { - auto h2 = histm->getHisto2F(id); - if (!h2 || h2->GetEntries() < params.minHistoStat2Fit) { - return; - } - h2->FitSlicesY(gs, 0, -1, 0, "QNR", &arr); - arr.SetOwner(true); - TH1* hmean = (TH1*)arr.RemoveAt(1); - if (hmean) { - hmean->SetTitle(Form("<%s>", h2->GetTitle())); - histm->addHisto(hmean, id + 1); - } - TH1* hsig = (TH1*)arr.RemoveAt(2); - if (hsig) { - hsig->SetTitle(Form("#sigma(%s)", h2->GetTitle())); - histm->addHisto(hsig, id + 2); - } - }; - for (int ioffs = 0; ioffs <= 3; ioffs++) { // vs phi, Z, pT, tgl - int offs = ioffs * 1000; - for (int iht = 0; iht < 2; iht++) { // resid, pull - int offsV = iht == 0 ? 0 : 5; - for (int il = 0; il < 8; il++) { - for (int iyz = 0; iyz < 2; iyz++) { - fitSlices(il * 10 + iyz * 100 + offsV + offs); - } + for (int ihm = 0; ihm < maxH; ihm++) { + auto* histm = mHManV[ihm].get(); + auto fitSlices = [&](int id) { + auto h2 = histm->getHisto2F(id); + if (!h2 || h2->GetEntries() < params.minHistoStat2Fit) { + return; + } + h2->FitSlicesY(gs, 0, -1, 0, "QNR", &arr); + arr.SetOwner(true); + TH1* hmean = (TH1*)arr.RemoveAt(1); + if (hmean) { + hmean->SetTitle(Form("<%s>", h2->GetTitle())); + histm->addHisto(hmean, id + 1); + } + TH1* hsig = (TH1*)arr.RemoveAt(2); + if (hsig) { + hsig->SetTitle(Form("#sigma(%s)", h2->GetTitle())); + histm->addHisto(hsig, id + 2); } - for (int ip = 0; ip < 5; ip++) { - fitSlices(10000 + ip * 10 + offsV + offs); + }; + for (int ioffs = 0; ioffs <= 3; ioffs++) { // vs phi, Z, pT, tgl + int offs = ioffs * 1000; + for (int iht = 0; iht < 2; iht++) { // resid, pull + int offsV = iht == 0 ? 0 : 5; + for (int il = 0; il < 8; il++) { + for (int iyz = 0; iyz < 2; iyz++) { + fitSlices(il * 10 + iyz * 100 + offsV + offs); + } + } + for (int ip = 0; ip < 5; ip++) { + fitSlices(10000 + ip * 10 + offsV + offs); + } } } + histm->write(); } - histm->write(); delete gs; } @@ -922,11 +942,11 @@ void CheckResidSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) } } -DataProcessorSpec getCheckResidSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool drawOnly) +DataProcessorSpec getCheckResidSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool drawOnly, bool postProcOnly) { std::vector outputs; auto dataRequest = std::make_shared(); - if (!drawOnly) { + if (!drawOnly && !postProcOnly) { bool useMC = false; dataRequest->requestTracks(srcTracks, useMC); dataRequest->requestClusters(srcClusters, useMC); @@ -954,7 +974,7 @@ DataProcessorSpec getCheckResidSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t "check-resid", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, drawOnly)}, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, drawOnly, postProcOnly)}, opts}; } diff --git a/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx b/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx index 72188eb5f06c6..0791d72474ad3 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx @@ -38,6 +38,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ {"draw-external-only", VariantType::Bool, false, {"just draw content of comma-separated list of histomanagers from checkresid.ext_hm_list"}}, + {"postproc-external-only", VariantType::Bool, false, {"just post-process raw content of comma-separated list of histomanagers from checkresid.ext_hm_list"}}, {"track-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of track sources to use"}}, {"cluster-sources", VariantType::String, "ITS", {"comma-separated list of cluster sources to use"}}, {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, @@ -57,7 +58,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) WorkflowSpec specs; bool drawOnly = configcontext.options().get("draw-external-only"); - + bool postProcOnly = configcontext.options().get("postproc-external-only"); GID::mask_t allowedSourcesTrc = GID::getSourcesMask("ITS,TPC,ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); GID::mask_t allowedSourcesClus = GID::getSourcesMask("ITS"); @@ -74,7 +75,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) allowedSourcesTrc = {}; allowedSourcesClus = {}; } - specs.emplace_back(o2::checkresid::getCheckResidSpec(srcTrc, srcCls, drawOnly)); + specs.emplace_back(o2::checkresid::getCheckResidSpec(srcTrc, srcCls, drawOnly, postProcOnly)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit if (!drawOnly) { From 1678fd707a1a519e87e1d7c6b8c735852c71126f Mon Sep 17 00:00:00 2001 From: shahoian Date: Mon, 4 May 2026 15:28:07 +0200 Subject: [PATCH 031/102] Option to write only ROFRecord to ITS/MFT clusters file: With --cluster-rof-branch-only passed to the ITS or MFT reco workflows the cluster writing will be activated (even if --disable-root-output was globally passed to the workflows) and only the ROFRecords branch will be stored. Option to scale the ITS entropy decoding with N_ITSENTDEC or MULTIPLICITY_PROCESS_its_entropy_decoder env. var. --- .../ITSWorkflow/ClusterWriterWorkflow.h | 2 +- .../include/ITSWorkflow/RecoWorkflow.h | 2 +- .../ITS/workflow/src/ClusterWriterWorkflow.cxx | 4 ++-- .../ITSMFT/ITS/workflow/src/RecoWorkflow.cxx | 5 +++-- .../src/its-cluster-writer-workflow.cxx | 10 +++++++++- .../ITS/workflow/src/its-reco-workflow.cxx | 5 ++++- .../include/MFTWorkflow/RecoWorkflow.h | 1 + .../ITSMFT/MFT/workflow/src/RecoWorkflow.cxx | 5 +++-- .../src/mft-cluster-writer-workflow.cxx | 7 ++++--- .../MFT/workflow/src/mft-reco-workflow.cxx | 5 ++++- .../include/ITSMFTWorkflow/ClusterWriterSpec.h | 6 +++--- .../common/workflow/src/ClusterWriterSpec.cxx | 18 +++++++++++++++--- prodtests/full-system-test/dpl-workflow.sh | 2 +- 13 files changed, 51 insertions(+), 21 deletions(-) diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterWorkflow.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterWorkflow.h index a91038b32a1c1..868c57f70df24 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterWorkflow.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterWorkflow.h @@ -23,7 +23,7 @@ namespace its namespace cluster_writer_workflow { -framework::WorkflowSpec getWorkflow(bool useMC, bool doStag); +framework::WorkflowSpec getWorkflow(bool useMC, bool doStag, bool clusterROFOnly = false); } } // namespace its diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/RecoWorkflow.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/RecoWorkflow.h index bfbde0093d55d..3068954c92003 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/RecoWorkflow.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/RecoWorkflow.h @@ -27,7 +27,7 @@ namespace reco_workflow { framework::WorkflowSpec getWorkflow(bool useMC, bool doStag, TrackingMode::Type trmode, const bool overrideBeamPosition = false, - bool upstreamDigits = false, bool upstreamClusters = false, bool disableRootOutput = false, bool useGeom = false, int useTrig = 0, + bool upstreamDigits = false, bool upstreamClusters = false, bool clrofOnly = false, bool disableRootOutput = false, bool useGeom = false, int useTrig = 0, bool useGPUWF = false, o2::gpu::gpudatatypes::DeviceType dType = o2::gpu::gpudatatypes::DeviceType::CPU); } diff --git a/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx index 35c911f856436..e05e55ffabd18 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx @@ -22,11 +22,11 @@ namespace its namespace cluster_writer_workflow { -framework::WorkflowSpec getWorkflow(bool useMC, bool doStag) +framework::WorkflowSpec getWorkflow(bool useMC, bool doStag, bool clusterROFOnly) { framework::WorkflowSpec specs; - specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC, doStag)); + specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC, doStag, clusterROFOnly)); return specs; } diff --git a/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx index 5da4b080995b5..06b3f019a6be7 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx @@ -32,6 +32,7 @@ framework::WorkflowSpec getWorkflow(bool useMC, bool doStag, const bool overrideBeamPosition, bool upstreamDigits, bool upstreamClusters, + bool clrofOnly, bool disableRootOutput, bool useGeom, int useTrig, @@ -45,8 +46,8 @@ framework::WorkflowSpec getWorkflow(bool useMC, bool doStag, if (!upstreamClusters) { specs.emplace_back(o2::itsmft::getITSClustererSpec(useMC, doStag)); } - if (!disableRootOutput) { - specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC, doStag)); + if (!disableRootOutput || clrofOnly) { + specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC, doStag, clrofOnly)); } if ((trmode != TrackingMode::Off) && (TrackerParamConfig::Instance().trackingMode != TrackingMode::Off)) { if (useGPUWF) { diff --git a/Detectors/ITSMFT/ITS/workflow/src/its-cluster-writer-workflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/its-cluster-writer-workflow.cxx index c10a1659d5f76..6c46f6b038571 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/its-cluster-writer-workflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/its-cluster-writer-workflow.cxx @@ -30,6 +30,13 @@ void customize(std::vector& workflowOptions) o2::framework::VariantType::Bool, false, {"disable MC propagation even if available"}}); + workflowOptions.push_back( + ConfigParamSpec{ + "cluster-rof-branch-only", + o2::framework::VariantType::Bool, + false, + {"writer will store only ClustersROF brunch"}}); + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(workflowOptions); } @@ -39,5 +46,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { auto useMC = !configcontext.options().get("disable-mc"); auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); - return std::move(o2::its::cluster_writer_workflow::getWorkflow(useMC, doStag)); + auto clrofOnly = configcontext.options().get("cluster-rof-branch-only"); + return std::move(o2::its::cluster_writer_workflow::getWorkflow(useMC, doStag, clrofOnly)); } diff --git a/Detectors/ITSMFT/ITS/workflow/src/its-reco-workflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/its-reco-workflow.cxx index bdade0effcbf0..f1d60b8ac2c9b 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/its-reco-workflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/its-reco-workflow.cxx @@ -50,7 +50,8 @@ void customize(std::vector& workflowOptions) {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, {"use-full-geometry", o2::framework::VariantType::Bool, false, {"use full geometry instead of the light-weight ITS part"}}, {"use-gpu-workflow", o2::framework::VariantType::Bool, false, {"use GPU workflow (default: false)"}}, - {"gpu-device", o2::framework::VariantType::Int, 1, {"use gpu device: CPU=1,CUDA=2,HIP=3 (default: CPU)"}}}; + {"gpu-device", o2::framework::VariantType::Int, 1, {"use gpu device: CPU=1,CUDA=2,HIP=3 (default: CPU)"}}, + {"cluster-rof-branch-only", o2::framework::VariantType::Bool, false, {"writer will store only ClustersROF brunch"}}}; o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); @@ -75,6 +76,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto disableRootOutput = configcontext.options().get("disable-root-output"); auto useGeom = configcontext.options().get("use-full-geometry"); auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); + auto clrofOnly = configcontext.options().get("cluster-rof-branch-only"); if (configcontext.options().get("disable-tracking")) { trmode = "off"; } @@ -97,6 +99,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) beamPosOVerride, extDigits, extClusters, + clrofOnly, disableRootOutput, useGeom, trType, diff --git a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/RecoWorkflow.h b/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/RecoWorkflow.h index 51234e2e8017d..73ffc744f915b 100644 --- a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/RecoWorkflow.h +++ b/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/RecoWorkflow.h @@ -29,6 +29,7 @@ framework::WorkflowSpec getWorkflow( bool useGeom, bool upstreamDigits, bool upstreamClusters, + bool clrofOnly, bool disableRootOutput, bool runAssessment, bool processGen, diff --git a/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx b/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx index fb99715cae4ee..178c1dd50f4df 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx @@ -36,6 +36,7 @@ framework::WorkflowSpec getWorkflow( bool useGeom, bool upstreamDigits, bool upstreamClusters, + bool clrofOnly, bool disableRootOutput, bool runAssessment, bool processGen, @@ -55,8 +56,8 @@ framework::WorkflowSpec getWorkflow( if (!upstreamClusters) { specs.emplace_back(o2::itsmft::getMFTClustererSpec(useMC, doStag)); } - if (!disableRootOutput) { - specs.emplace_back(o2::itsmft::getMFTClusterWriterSpec(useMC, doStag)); + if (!disableRootOutput || clrofOnly) { + specs.emplace_back(o2::itsmft::getMFTClusterWriterSpec(useMC, doStag, clrofOnly)); } if (runTracking) { diff --git a/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-writer-workflow.cxx b/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-writer-workflow.cxx index 5a5112e03c866..99aad4d8c57f4 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-writer-workflow.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-writer-workflow.cxx @@ -24,8 +24,8 @@ void customize(std::vector& policies) void customize(std::vector& workflowOptions) { - workflowOptions.push_back( - ConfigParamSpec{"disable-mc", o2::framework::VariantType::Bool, false, {"disable MC propagation even if available"}}); + workflowOptions.push_back(ConfigParamSpec{"disable-mc", o2::framework::VariantType::Bool, false, {"disable MC propagation even if available"}}); + workflowOptions.push_back(ConfigParamSpec{"cluster-rof-branch-only", o2::framework::VariantType::Bool, false, {"writer will store only ClustersROF brunch"}}); o2::itsmft::DPLAlpideParamInitializer::addMFTConfigOption(workflowOptions); } @@ -35,7 +35,8 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { auto useMC = !configcontext.options().get("disable-mc"); auto doStag = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(configcontext); + auto clrofOnly = configcontext.options().get("cluster-rof-branch-only"); WorkflowSpec specs; - specs.emplace_back(o2::itsmft::getMFTClusterWriterSpec(useMC, doStag)); + specs.emplace_back(o2::itsmft::getMFTClusterWriterSpec(useMC, doStag, clrofOnly)); return specs; } diff --git a/Detectors/ITSMFT/MFT/workflow/src/mft-reco-workflow.cxx b/Detectors/ITSMFT/MFT/workflow/src/mft-reco-workflow.cxx index 11b4fc233c6b4..494d36cc609ec 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/mft-reco-workflow.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/mft-reco-workflow.cxx @@ -44,7 +44,8 @@ void customize(std::vector& workflowOptions) {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, {"nThreads", VariantType::Int, 1, {"Number of threads"}}, {"use-full-geometry", o2::framework::VariantType::Bool, false, {"use full geometry instead of the light-weight MFT part"}}, - {"run-tracks2records", o2::framework::VariantType::Bool, false, {"run MFT alignment tracks to records workflow"}}}; + {"run-tracks2records", o2::framework::VariantType::Bool, false, {"run MFT alignment tracks to records workflow"}}, + {"cluster-rof-branch-only", o2::framework::VariantType::Bool, false, {"writer will store only ClustersROF brunch"}}}; o2::raw::HBFUtilsInitializer::addConfigOption(options); o2::itsmft::DPLAlpideParamInitializer::addMFTConfigOption(options); std::swap(workflowOptions, options); @@ -70,6 +71,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto runTracks2Records = configcontext.options().get("run-tracks2records"); auto useGeom = configcontext.options().get("use-full-geometry"); auto doStag = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(configcontext); + auto clrofOnly = configcontext.options().get("cluster-rof-branch-only"); auto wf = o2::mft::reco_workflow::getWorkflow( useMC, @@ -77,6 +79,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) useGeom, extDigits, extClusters, + clrofOnly, disableRootOutput, runAssessment, processGen, diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h index 6607c05fb141d..e43e5def18482 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h @@ -20,9 +20,9 @@ namespace o2::itsmft { template -framework::DataProcessorSpec getClusterWriterSpec(bool useMC, bool doStag); -framework::DataProcessorSpec getITSClusterWriterSpec(bool useMC, bool doStag); -framework::DataProcessorSpec getMFTClusterWriterSpec(bool useMC, bool doStag); +framework::DataProcessorSpec getClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly = false); +framework::DataProcessorSpec getITSClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly = false); +framework::DataProcessorSpec getMFTClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly = false); } // namespace o2::itsmft diff --git a/Detectors/ITSMFT/common/workflow/src/ClusterWriterSpec.cxx b/Detectors/ITSMFT/common/workflow/src/ClusterWriterSpec.cxx index 52520194537b8..b189e9c644e27 100644 --- a/Detectors/ITSMFT/common/workflow/src/ClusterWriterSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/ClusterWriterSpec.cxx @@ -41,7 +41,7 @@ using ROFRecLblT = std::vector; using namespace o2::header; template -DataProcessorSpec getClusterWriterSpec(bool useMC, bool doStag) +DataProcessorSpec getClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly) { static constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; const int nLayers = (doStag) ? DPLAlpideParam::getNLayers() : 1; @@ -82,6 +82,18 @@ DataProcessorSpec getClusterWriterSpec(bool useMC, bool doStag) vecInpSpecLbl.emplace_back(getName("labels", iLayer), Origin, "CLUSTERSMCTR", iLayer); } + if (clusterROFOnly) { + return MakeRootTreeWriterSpec(std::format("{}-cluster-writer", detNameLC).c_str(), + (o2::detectors::DetID::ITS == N) ? "o2clus_its.root" : "mftclusters.root", + MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = std::format("Tree with {} cluster ROFs only", detName)}, + BranchDefinition{vecInpSpecROF, + (detName + "ClustersROF").c_str(), "cluster-rof-branch", + nLayers, + logger, + getIndex, + getName})(); + } + return MakeRootTreeWriterSpec(std::format("{}-cluster-writer", detNameLC).c_str(), (o2::detectors::DetID::ITS == N) ? "o2clus_its.root" : "mftclusters.root", MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = std::format("Tree with {} clusters", detName)}, @@ -114,7 +126,7 @@ DataProcessorSpec getClusterWriterSpec(bool useMC, bool doStag) getName})(); } -framework::DataProcessorSpec getITSClusterWriterSpec(bool useMC, bool doStag) { return getClusterWriterSpec(useMC, doStag); } -framework::DataProcessorSpec getMFTClusterWriterSpec(bool useMC, bool doStag) { return getClusterWriterSpec(useMC, doStag); } +framework::DataProcessorSpec getITSClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly) { return getClusterWriterSpec(useMC, doStag, clusterROFOnly); } +framework::DataProcessorSpec getMFTClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly) { return getClusterWriterSpec(useMC, doStag, clusterROFOnly); } } // namespace o2::itsmft diff --git a/prodtests/full-system-test/dpl-workflow.sh b/prodtests/full-system-test/dpl-workflow.sh index 5ab54f9cf4b43..ca466d311ed30 100755 --- a/prodtests/full-system-test/dpl-workflow.sh +++ b/prodtests/full-system-test/dpl-workflow.sh @@ -468,7 +468,7 @@ if [[ -n $INPUT_DETECTOR_LIST ]]; then if [[ $NTIMEFRAMES == -1 ]]; then NTIMEFRAMES_CMD= ; else NTIMEFRAMES_CMD="--max-tf $NTIMEFRAMES"; fi CTF_EMC_SUBSPEC= ( workflow_has_parameter AOD || [[ -z "$DISABLE_ROOT_OUTPUT" ]] || needs_root_output o2-emcal-cell-writer-workflow ) && has_detector EMC && CTF_EMC_SUBSPEC="--emcal-decoded-subspec 1" - add_W o2-ctf-reader-workflow "$RANS_OPT --delay $TFDELAY --loop $TFLOOP $NTIMEFRAMES_CMD $ITS_STAGGERED $MFT_STAGGERED --ctf-input ${CTFName} ${INPUT_FILE_COPY_CMD+--copy-cmd} ${INPUT_FILE_COPY_CMD:-} --onlyDet $INPUT_DETECTOR_LIST $CTF_EMC_SUBSPEC ${TIMEFRAME_SHM_LIMIT+--timeframes-shm-limit} ${TIMEFRAME_SHM_LIMIT:-} --pipeline $(get_N tpc-entropy-decoder TPC REST 1 TPCENTDEC)" + add_W o2-ctf-reader-workflow "$RANS_OPT --delay $TFDELAY --loop $TFLOOP $NTIMEFRAMES_CMD $ITS_STAGGERED $MFT_STAGGERED --ctf-input ${CTFName} ${INPUT_FILE_COPY_CMD+--copy-cmd} ${INPUT_FILE_COPY_CMD:-} --onlyDet $INPUT_DETECTOR_LIST $CTF_EMC_SUBSPEC ${TIMEFRAME_SHM_LIMIT+--timeframes-shm-limit} ${TIMEFRAME_SHM_LIMIT:-} --pipeline $(get_N tpc-entropy-decoder TPC REST 1 TPCENTDEC),$(get_N its-entropy-decoder ITS REST 1 ITSENTDEC)" elif [[ $RAWTFINPUT == 1 ]]; then TFName=`ls -t $RAWINPUTDIR/o2_*.tf 2> /dev/null | head -n1` [[ -z $TFName && $WORKFLOWMODE == "print" ]] && TFName='$TFName' From 9b38548d37a892ddbafca06e5de771062a13a611 Mon Sep 17 00:00:00 2001 From: spulawsk Date: Tue, 5 May 2026 09:12:58 +0200 Subject: [PATCH 032/102] FT0: fix return type mismatch in ChannelData getters (int16_t) (#15303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FT0: fix type mismatch in ChannelData getters (uint → int) * Additionally fixing mismatch in FV0 --------- Co-authored-by: Szymon Pulawski --- .../Detectors/FIT/FT0/include/DataFormatsFT0/ChannelData.h | 4 ++-- .../Detectors/FIT/FV0/include/DataFormatsFV0/ChannelData.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/ChannelData.h b/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/ChannelData.h index 9b3d6ec805604..dfe41525af480 100644 --- a/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/ChannelData.h +++ b/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/ChannelData.h @@ -76,8 +76,8 @@ struct ChannelData { void print() const; void printLog() const; [[nodiscard]] uint8_t getChannelID() const { return ChId; } - [[nodiscard]] uint16_t getTime() const { return CFDTime; } - [[nodiscard]] uint16_t getAmp() const { return QTCAmpl; } + [[nodiscard]] int16_t getTime() const { return CFDTime; } + [[nodiscard]] int16_t getAmp() const { return QTCAmpl; } bool operator==(ChannelData const& other) const { diff --git a/DataFormats/Detectors/FIT/FV0/include/DataFormatsFV0/ChannelData.h b/DataFormats/Detectors/FIT/FV0/include/DataFormatsFV0/ChannelData.h index 054b336510c4f..29447dfa04202 100644 --- a/DataFormats/Detectors/FIT/FV0/include/DataFormatsFV0/ChannelData.h +++ b/DataFormats/Detectors/FIT/FV0/include/DataFormatsFV0/ChannelData.h @@ -76,8 +76,8 @@ struct ChannelData { void print() const; void printLog() const; [[nodiscard]] uint8_t getChannelID() const { return ChId; } - [[nodiscard]] uint16_t getTime() const { return CFDTime; } - [[nodiscard]] uint16_t getAmp() const { return QTCAmpl; } + [[nodiscard]] int16_t getTime() const { return CFDTime; } + [[nodiscard]] int16_t getAmp() const { return QTCAmpl; } bool operator==(ChannelData const& other) const { From 32f4cad1145b0f2ccd92497dcd90551a26def180 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Sun, 26 Apr 2026 16:03:34 +0200 Subject: [PATCH 033/102] DPL: make sure data preparation remains on the main thread This way we can process incoming data while doing computation separately, without having to worry about thready safety of the DataRelayer itself. --- Framework/Core/src/DataProcessingDevice.cxx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Framework/Core/src/DataProcessingDevice.cxx b/Framework/Core/src/DataProcessingDevice.cxx index 8677aaa42a363..be25133158072 100644 --- a/Framework/Core/src/DataProcessingDevice.cxx +++ b/Framework/Core/src/DataProcessingDevice.cxx @@ -212,9 +212,8 @@ DataProcessingDevice::DataProcessingDevice(RunningDeviceRef running, ServiceRegi }); } -// Callback to execute the processing. Notice how the data is -// is a vector of DataProcessorContext so that we can index the correct -// one with the thread id. For the moment we simply use the first one. +// Callback to execute the processing. Receives and relays data (doPrepare) +// happens on the main thread before this is queued, so we only dispatch here. void run_callback(uv_work_t* handle) { auto* task = (TaskStreamInfo*)handle->data; @@ -223,7 +222,6 @@ void run_callback(uv_work_t* handle) auto& dataProcessorContext = ref.get(); O2_SIGNPOST_ID_FROM_POINTER(sid, device, &dataProcessorContext); O2_SIGNPOST_START(device, sid, "run_callback", "Starting run callback on stream %d", task->id.index); - DataProcessingDevice::doPrepare(ref); DataProcessingDevice::doRun(ref); O2_SIGNPOST_END(device, sid, "run_callback", "Done processing data for stream %d", task->id.index); } @@ -1332,6 +1330,10 @@ void DataProcessingDevice::Run() handleRegionCallbacks(mServiceRegistry, mPendingRegionInfos); } + // Receive and relay incoming data on the main thread so that I/O + // overlaps with computation running concurrently on work threads. + DataProcessingDevice::doPrepare(ref); + assert(mStreams.size() == mHandles.size()); /// Decide which task to use TaskStreamRef streamRef{-1}; From 6938e1e3c846ba76080fbbf1670a9cc22dc47536 Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Wed, 29 Apr 2026 14:17:57 +0200 Subject: [PATCH 034/102] Common: Allow EnumFlags on gpu Signed-off-by: Felix Schlepper --- Common/Utils/include/CommonUtils/EnumFlags.h | 32 +++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Common/Utils/include/CommonUtils/EnumFlags.h b/Common/Utils/include/CommonUtils/EnumFlags.h index e7481c903e666..5032b6c0f1483 100644 --- a/Common/Utils/include/CommonUtils/EnumFlags.h +++ b/Common/Utils/include/CommonUtils/EnumFlags.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,9 @@ #include #include +#ifndef GPUCA_GPUCODE #include "CommonUtils/StringUtils.h" +#endif namespace o2::utils { @@ -55,6 +58,7 @@ concept EnumFlagHelper = requires { // This is very much inspired by much more extensive libraries like magic_enum. // Inspiration by its c++20 version (https://github.com/fix8mt/conjure_enum). // NOTE: Cannot detect if bit values past the underlying type are defined. +#ifndef GPUCA_GPUCODE template struct FlagsHelper final { using U = std::underlying_type_t; @@ -317,10 +321,12 @@ struct FlagsHelper final { return false; } }; +#endif } // namespace details::enum_flags // Require an enum to fullfil what one would except from a bitset. +#ifndef GPUCA_GPUCODE template concept EnumFlag = requires { // range checks @@ -332,6 +338,10 @@ concept EnumFlag = requires { requires !details::enum_flags::FlagsHelper::hasNone(); // added automatically requires !details::enum_flags::FlagsHelper::hasAll(); // added automatically }; +#else +template +concept EnumFlag = details::enum_flags::EnumFlagHelper; +#endif /** * \brief Class to aggregate and manage enum-based on-off flags. @@ -358,7 +368,9 @@ template class EnumFlags { static constexpr int DefaultBase{2}; +#ifndef GPUCA_GPUCODE using H = details::enum_flags::FlagsHelper; +#endif using U = std::underlying_type_t; U mBits{0}; @@ -388,18 +400,21 @@ class EnumFlags // Initialize with a list of flags. constexpr EnumFlags(std::initializer_list flags) noexcept { - std::for_each(flags.begin(), flags.end(), [this](const E f) noexcept { mBits |= to_bit(f); }); + for (const E f : flags) { + mBits |= to_bit(f); + } } +#ifndef GPUCA_GPUCODE // Init from a string. // explicit EnumFlags(const std::string& str, int base = DefaultBase) { set(str, base); } - // Destructor. - constexpr ~EnumFlags() = default; +#endif - static constexpr U None{0}; // Represents no flags set. + static constexpr U None{0}; // Represents no flags set. +#ifndef GPUCA_GPUCODE static constexpr U All{H::MaxRep}; // Represents all flags set. // Return list of all enum values @@ -432,6 +447,7 @@ class EnumFlags throw; } } +#endif // Returns the raw bitset value. [[nodiscard]] constexpr auto value() const noexcept { @@ -493,6 +509,7 @@ class EnumFlags } // Checks if all flags are set. +#ifndef GPUCA_GPUCODE [[nodiscard]] constexpr bool all() const noexcept { return mBits == All; @@ -537,6 +554,7 @@ class EnumFlags } return oss.str(); } +#endif // Checks if any flag is set (Boolean context). [[nodiscard]] constexpr explicit operator bool() const noexcept @@ -645,6 +663,7 @@ class EnumFlags } // Serializes the flag set to a string. +#ifndef GPUCA_GPUCODE [[nodiscard]] std::string serialize() const { return std::to_string(mBits); @@ -659,6 +678,7 @@ class EnumFlags } mBits = static_cast(v); } +#endif // Counts the number of set bits (active flags). [[nodiscard]] constexpr size_t count() const noexcept @@ -686,6 +706,7 @@ class EnumFlags private: // Set implementation, bits was zeroed before. +#ifndef GPUCA_GPUCODE void setImpl(const std::string& s, int base = 2) { // Helper to check if character is valid for given base @@ -782,14 +803,17 @@ class EnumFlags throw std::invalid_argument("Cannot parse string!"); } } +#endif }; +#ifndef GPUCA_GPUCODE template std::ostream& operator<<(std::ostream& os, const EnumFlags& f) { os << f.pstring(true); return os; } +#endif } // namespace o2::utils From bd54ed4b604c681ca5d5b07c80eb5a927a35da3e Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Tue, 5 May 2026 10:18:37 +0200 Subject: [PATCH 035/102] DPL: improve catching of low memory related bugs --- Framework/Core/src/DataAllocator.cxx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Framework/Core/src/DataAllocator.cxx b/Framework/Core/src/DataAllocator.cxx index f0de6a40935b7..c6fe5f3d82877 100644 --- a/Framework/Core/src/DataAllocator.cxx +++ b/Framework/Core/src/DataAllocator.cxx @@ -263,9 +263,15 @@ void DataAllocator::adopt(const Output& spec, LifetimeHolder& f // Serialization happens in here, so that we can // get rid of the intermediate tree 2 table object, saving memory. auto batch = source.finalize(); + if (!batch) { + throw std::runtime_error("FragmentToBatch::finalize() returned null RecordBatch"); + } auto mock = std::make_shared(); int64_t expectedSize = 0; auto mockWriter = arrow::ipc::MakeStreamWriter(mock.get(), batch->schema()); + if (!mockWriter.ok()) { + throw std::runtime_error(fmt::format("Unable to create mock stream writer: {}", mockWriter.status().ToString())); + } arrow::Status outStatus = mockWriter.ValueOrDie()->WriteRecordBatch(*batch); expectedSize = mock->Tell().ValueOrDie(); @@ -275,6 +281,9 @@ void DataAllocator::adopt(const Output& spec, LifetimeHolder& f } auto deferredWriterStream = source.streamer(buffer); + if (!deferredWriterStream) { + throw std::runtime_error("FragmentToBatch streamer returned null OutputStream"); + } auto outBatch = arrow::ipc::MakeStreamWriter(deferredWriterStream, batch->schema()); if (outBatch.ok() == false) { From 1615376151510e8cca9060e3ba05ac11f14429f3 Mon Sep 17 00:00:00 2001 From: shahoian Date: Tue, 5 May 2026 12:28:13 +0200 Subject: [PATCH 036/102] Use currentTS+14days to define EOV of TRD DCS-DP processor objects --- Detectors/TRD/calibration/src/DCSProcessor.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Detectors/TRD/calibration/src/DCSProcessor.cxx b/Detectors/TRD/calibration/src/DCSProcessor.cxx index f110ba844791e..6f719b71e10c3 100644 --- a/Detectors/TRD/calibration/src/DCSProcessor.cxx +++ b/Detectors/TRD/calibration/src/DCSProcessor.cxx @@ -382,7 +382,7 @@ bool DCSProcessor::updateGasDPsCCDB() } std::map md; md["responsible"] = "Ole Schmidt"; - o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSGas, mCcdbGasDPsInfo, "TRD/Calib/DCSDPsGas", md, mGasStartTS, mGasStartTS + 3 * o2::ccdb::CcdbObjectInfo::DAY); + o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSGas, mCcdbGasDPsInfo, "TRD/Calib/DCSDPsGas", md, mGasStartTS, mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } @@ -410,7 +410,7 @@ bool DCSProcessor::updateCurrentsDPsCCDB() } std::map md; md["responsible"] = "Ole Schmidt"; - o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSCurrents, mCcdbCurrentsDPsInfo, "TRD/Calib/DCSDPsI", md, mCurrentsStartTS, mCurrentsStartTS + 3 * o2::ccdb::CcdbObjectInfo::DAY); + o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSCurrents, mCcdbCurrentsDPsInfo, "TRD/Calib/DCSDPsI", md, mCurrentsStartTS, mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } @@ -437,7 +437,7 @@ bool DCSProcessor::updateVoltagesDPsCCDB() } std::map md; md["responsible"] = "Ole Schmidt"; - o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSVoltages, mCcdbVoltagesDPsInfo, "TRD/Calib/DCSDPsU", md, mVoltagesStartTS, mVoltagesStartTS + 7 * o2::ccdb::CcdbObjectInfo::DAY); + o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSVoltages, mCcdbVoltagesDPsInfo, "TRD/Calib/DCSDPsU", md, mVoltagesStartTS, mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } @@ -465,7 +465,7 @@ bool DCSProcessor::updateEnvDPsCCDB() } std::map md; md["responsible"] = "Leonardo Barreto"; - o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSEnv, mCcdbEnvDPsInfo, "TRD/Calib/DCSDPsEnv", md, mEnvStartTS, mEnvStartTS + 3 * o2::ccdb::CcdbObjectInfo::DAY); + o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSEnv, mCcdbEnvDPsInfo, "TRD/Calib/DCSDPsEnv", md, mEnvStartTS, mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } @@ -498,7 +498,7 @@ bool DCSProcessor::updateFedChamberStatusDPsCCDB() // LB: set start timestamp 30000 miliseconds before DPs are received o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSFedChamberStatus, mCcdbFedChamberStatusDPsInfo, "TRD/Calib/DCSDPsFedChamberStatus", md, mFedChamberStatusStartTS - 30000, - mFedChamberStatusStartTS + 3 * o2::ccdb::CcdbObjectInfo::DAY); + mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } @@ -531,7 +531,7 @@ bool DCSProcessor::updateFedCFGtagDPsCCDB() // LB: set start timestamp 30000 seconds before DPs are received o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSFedCFGtag, mCcdbFedCFGtagDPsInfo, "TRD/Calib/DCSDPsFedCFGtag", md, mFedCFGtagStartTS - 30000, - mFedCFGtagStartTS + 3 * o2::ccdb::CcdbObjectInfo::DAY); + mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } From fadee8bcee862baf30405647e00ec0764b6889ba Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Tue, 5 May 2026 17:28:04 +0200 Subject: [PATCH 037/102] DPL: more debug messages for the reader --- Framework/Core/src/FragmentToBatch.cxx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Framework/Core/src/FragmentToBatch.cxx b/Framework/Core/src/FragmentToBatch.cxx index 1a6c3bca3cc60..ada31be814fc8 100644 --- a/Framework/Core/src/FragmentToBatch.cxx +++ b/Framework/Core/src/FragmentToBatch.cxx @@ -44,7 +44,14 @@ void FragmentToBatch::fill(std::shared_ptr schema, std::shared_pt options->dataset_schema = schema; auto scanner = format->ScanBatchesAsync(options, mFragment); auto batch = (*scanner)(); - mRecordBatch = *batch.result(); + auto result = batch.result(); + if (!result.ok()) { + throw std::runtime_error("FragmentToBatch::fill: scan failed: " + result.status().ToString()); + } + mRecordBatch = *result; + if (!mRecordBatch) { + throw std::runtime_error("FragmentToBatch::fill: scan returned null RecordBatch"); + } // Notice that up to here the buffer was not yet filled. } From 85ac260325f1e7a09bada0e3625cda805d08f490 Mon Sep 17 00:00:00 2001 From: altsybee Date: Wed, 6 May 2026 00:13:37 +0200 Subject: [PATCH 038/102] [ALICE3] TRK: adjust zLengthCylinderMiddleServices for ML barrel services, make it parametrized (#15366) Co-authored-by: Igor Altsybeev --- .../ALICE3/TRK/simulation/src/TRKServices.cxx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx index 7cf7dc863607e..be88412186533 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx @@ -568,8 +568,9 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) // Get geometry information from TRK which is already present float rMinMiddleServices = 35.f; float rMinMiddleBarrel = rMinMiddleServices; - const float zLengthCylinderMiddleServices = 40.5f; + const float zLengthMiddleBarrel = 64.5f; const float zLengthMiddleServices = 143.f; + const float zLengthCylinderMiddleServices = zLengthMiddleServices - zLengthMiddleBarrel; // Middle layer barrel services are only on A side rMinMiddleServices = 35.f; @@ -578,7 +579,6 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) // Middle barrel connection disks const float rMinMiddleBarrelDisk = 5.68f; const float rMaxMiddleBarrelDisk = 35.f; - const float zLengthMiddleBarrel = 64.5f; auto orientation = Orientation::kASide; float diskCircumference = rMaxMiddleBarrelDisk * 3.14; // Use only half circumference @@ -628,29 +628,29 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) // create data fiber volumes double rCur = rMinMiddleServices; double dR = siO2FiberAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLD_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleDiskFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLD_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleDiskFiberSIO2Volume = new TGeoVolume(Form("TRK_MLD_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), middleDiskFiberSIO2, medSiO2); middleDiskFiberSIO2Volume->SetLineColor(kGray); rCur += dR; dR = peFiberAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskFiberPE = new TGeoTubeSeg(Form("TRK_MLD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleDiskFiberPE = new TGeoTubeSeg(Form("TRK_MLD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleDiskFiberPEVolume = new TGeoVolume(Form("TRK_MLD_FIBER_PE_%s%d", orLabel.c_str(), iSide), middleDiskFiberPE, medPE); middleDiskFiberPEVolume->SetLineColor(kGray); - auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zLengthMiddleServices - zLengthCylinderMiddleServices), new TGeoRotation("", refAngle + iSide * 180., 0, 0)); + auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zLengthMiddleServices - zLengthCylinderMiddleServices / 2), new TGeoRotation("", refAngle + iSide * 180., 0, 0)); motherVolume->AddNode(middleDiskFiberSIO2Volume, 1, combiTrans); motherVolume->AddNode(middleDiskFiberPEVolume, 1, combiTrans); // Create powerlines rCur += dR; dR = cuPowerAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskPowerCu = new TGeoTubeSeg(Form("TRK_MLD_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleDiskPowerCu = new TGeoTubeSeg(Form("TRK_MLD_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleDiskPowerCuVolume = new TGeoVolume(Form("TRK_MLD_POWER_CU_%s%d", orLabel.c_str(), iSide), middleDiskPowerCu, medCu); middleDiskPowerCuVolume->SetLineColor(kGray); rCur += dR; dR = pePowerAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskPowerPE = new TGeoTubeSeg(Form("TRK_MLD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleDiskPowerPE = new TGeoTubeSeg(Form("TRK_MLD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleDiskPowerPEVolume = new TGeoVolume(Form("TRK_MLD_POWER_PE_%s%d", orLabel.c_str(), iSide), middleDiskPowerPE, medPE); middleDiskPowerPEVolume->SetLineColor(kGray); @@ -662,29 +662,29 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) // create data fiber volumes rCur += dR; dR = siO2FiberAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLB_FIBER_SIO2sh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleBarrelFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLB_FIBER_SIO2sh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleBarrelFiberSIO2Volume = new TGeoVolume(Form("TRK_MLB_FIBER_SIO2_A%d", iSide), middleBarrelFiberSIO2, medSiO2); middleBarrelFiberSIO2Volume->SetLineColor(kGray); rCur += dR; dR = peFiberAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelFiberPE = new TGeoTubeSeg(Form("TRK_MLB_FIBER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleBarrelFiberPE = new TGeoTubeSeg(Form("TRK_MLB_FIBER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleBarrelFiberPEVolume = new TGeoVolume(Form("TRK_MLB_FIBER_PE_A%d", iSide), middleBarrelFiberPE, medPE); middleBarrelFiberPEVolume->SetLineColor(kGray); - auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zLengthMiddleServices - zLengthCylinderMiddleServices), new TGeoRotation(nullptr, refAngle + iSide * 180., 0, 0)); + auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zLengthMiddleServices - zLengthCylinderMiddleServices / 2), new TGeoRotation(nullptr, refAngle + iSide * 180., 0, 0)); motherVolume->AddNode(middleBarrelFiberSIO2Volume, 1, combiTrans); motherVolume->AddNode(middleBarrelFiberPEVolume, 1, combiTrans); // Create powerlines rCur += dR; dR = cuPowerAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelPowerCu = new TGeoTubeSeg(Form("TRK_MLB_POWER_CUsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleBarrelPowerCu = new TGeoTubeSeg(Form("TRK_MLB_POWER_CUsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleBarrelPowerCuVolume = new TGeoVolume(Form("TRK_MLB_POWER_CU_A%d", iSide), middleBarrelPowerCu, medCu); middleBarrelPowerCuVolume->SetLineColor(kGray); rCur += dR; dR = pePowerAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelPowerPE = new TGeoTubeSeg(Form("TRK_MLB_POWER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleBarrelPowerPE = new TGeoTubeSeg(Form("TRK_MLB_POWER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleBarrelPowerPEVolume = new TGeoVolume(Form("TRK_MLB_POWER_PE_A%d", iSide), middleBarrelPowerPE, medPE); middleBarrelPowerPEVolume->SetLineColor(kGray); From fb65f6e7642bdb6ca478f617826431c27c967923 Mon Sep 17 00:00:00 2001 From: Justus Rudolph <44173718+JustusRudolph@users.noreply.github.com> Date: Wed, 6 May 2026 11:29:11 +0200 Subject: [PATCH 039/102] [ ALICE3] FT3: Stave tiling bugfixes & readme (#15365) * Change carbon fiber colour to very dark grey, and add it to stave volume to get consistent colours. Also update sensor height to 2.9cm in line with new documents, and fix bug in ML stave positioning to get staggering right. * update readme with recent changes --- Detectors/Upgrades/ALICE3/FT3/README.md | 18 +++++++++++------- .../include/FT3Simulation/FT3ModuleConstants.h | 6 +++--- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 3 +++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/README.md b/Detectors/Upgrades/ALICE3/FT3/README.md index 34a6782a2b0c2..c11352607db85 100644 --- a/Detectors/Upgrades/ALICE3/FT3/README.md +++ b/Detectors/Upgrades/ALICE3/FT3/README.md @@ -12,18 +12,22 @@ This is top page for the FT3 detector documentation. Configuration of the endcap disks can be done by setting values for the `FT3Base.layoutFT3` configurable, the available options are presented in the following Table: -| Option | Comments | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------- | -| `kSegmented` (default) | Currently, only OT disks have realistic implementation, for ML - simple trapezoids | -| `kTrapezoidal` | Simple trapezoisal disks (in both ML and OT), with `FT3Base.nTrapezoidalSegments=32` | -| `kCylindrical` | Simplest possible disks as TGeoTubes (ML and OT), bad for ACTS (wrong digi due to polar coorinates on disk sides) | +| Option | Comments | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `kSegmentedStave` | Segmentation of ML and OT disks: Modules are placed on staggered staves with user defined constants | +| `kSegmentedStaveOTOnly` (default) | Only OT disks are contain staves with modules, ML layers are segmented with strips of modules on front/back | +| `kSegmented` | Segmentation of ML and OT disk with strips of modules of chips on the front and back of a layer | +| `kTrapezoidal` | Simple trapezoidal disks (in both ML and OT), with `FT3Base.nTrapezoidalSegments=32` | +| `kCylindrical` | Simplest possible disks as TGeoTubes (ML and OT), bad for ACTS (wrong digi due to polar coorinates on disk sides) | + +Furthermore, there are more options in the case of stave segmentation -- for only OT or both. The user can set to cut the staves exactly on the nominal inner radii (true by default), and outer radii (false by default) of the disks. This exists since (planned) placements of sensors & staves often protrude out of the nominal radii to be more able to cover the nominal disk area. In addition, it is possible to draw reference circles in root for the stave segmented layouts for both the inner (red) and outer (blue) radii. This is off by default, yet can be toggled if the user wants to see how tight the tiling is to the nominal radii -- for visualisation purposes only. [ [Link to definitions](./base/include/FT3Base/FT3BaseParam.h) ] -For example, a geometry with the endcaps-only can be obtained by +For example, see the command below to generate a geometry with the endcaps only, all layers with the stave geometry, and including reference circles of nominal radii for visualisation. ```bash o2-sim-serial-run5 -n 1 -g pythia8hi -m FT3 \ - --configKeyValues "FT3Base.layoutFT3=kTrapezoidal" + --configKeyValues "FT3Base.layoutFT3=kSegmented; FT3Base.drawReferenceCircles=true" ``` diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h index 0ed7ca6a8a8d4..b484e13f3546e 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h @@ -16,7 +16,9 @@ #define O2_ALICE_TRK_SPECS #include + #include + // This is a temporary version with the specs for the ALICE3 TRK // This files defines the design specifications of the chips for VD, ML, OT. // Each TGeoShape has the following properties @@ -78,7 +80,7 @@ constexpr double thickness{0 * mu}; // thickness of the copper metal stack - for namespace chip { constexpr double width{25 * mm}; // width of the chip -constexpr double length{32 * mm}; // length of the chip +constexpr double length{29 * mm}; // length of the chip constexpr double pitchX{20 * mu}; // pitch of the row constexpr double pitchZ{20 * mu}; // pitch of the column constexpr double totalThickness{silicon::thickness + metalstack::thickness}; // total thickness of the chip diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h index 63e961db44505..65194ad6edfcb 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h @@ -40,7 +40,8 @@ enum eSrvLayout { struct TRKBaseParam : public o2::conf::ConfigurableParamHelper { std::string configFile = ""; float serviceTubeX0 = 0.02f; // X0 Al2O3 - Bool_t irisOpen = false; + bool irisOpen = false; + bool includeLowServices = false; eVDLayout layoutVD = kIRIS4; // VD detector layout design eMLOTLayout layoutMLOT = kSegmented; // ML and OT detector layout design diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h index 79033f48cb0b9..dedbbb096b8e8 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h @@ -21,6 +21,7 @@ // Water bundle disk PU 0,44 19 H2O 0,56 36,08 #include + #include namespace o2 @@ -51,6 +52,7 @@ class TRKServices : public FairModule void createMiddleServices(TGeoVolume* motherVolume); void createOuterDisksServices(TGeoVolume* motherVolume); void createOuterBarrelServices(TGeoVolume* motherVolume); + void createServicesAroundBeamPipe(TGeoVolume* motherVolume); void createMLServicesPeacock(TGeoVolume* motherVolume); void createOTServicesPeacock(TGeoVolume* motherVolume); void createVacuumCompositeShape(); @@ -81,4 +83,4 @@ class TRKServices : public FairModule }; } // namespace trk } // namespace o2 -#endif // O2_TRK_SERVICES_H \ No newline at end of file +#endif // O2_TRK_SERVICES_H diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx index 3fa51afe3ba2b..196727b2c140f 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx @@ -98,7 +98,7 @@ void Detector::configMLOT() switch (trkPars.layoutMLOT) { case kCylindrical: { - const std::vector length{128.35f, 128.35f, 128.35f, 128.35f, 128.35f, 256.7f, 256.7f, 256.7f}; + const std::vector length{127.985f, 127.985f, 127.985f, 127.985f, 127.985f, 255.9f, 255.9f, 255.9f}; LOGP(warning, "Loading cylindrical configuration for ALICE3 TRK"); for (int i{0}; i < constants::ML::nLayers + constants::OT::nLayers; ++i) { std::string name = GeometryTGeo::getTRKLayerPattern() + std::to_string(i); @@ -111,7 +111,7 @@ void Detector::configMLOT() // const std::vector tiltAngles{10.f, 16.1f, 19.2f, 0.f, 0.f, 0.f, 0.f, 0.f}; const std::vector nStaves{10, 14, 18, 26, 38, 32, 42, 56}; // const std::vector nStaves{10, 16, 22, 26, 38, 32, 42, 56}; - const std::vector nMods{10, 10, 10, 10, 10, 20, 20, 20}; + const std::vector nMods{11, 11, 11, 11, 11, 22, 22, 22}; const std::vector stagOffsets{0.f, 0.f, 0.f, 1.17f, 0.89f}; @@ -165,18 +165,28 @@ void Detector::configFromFile(std::string fileName) switch (trkPars.layoutMLOT) { case kCylindrical: { + // Expected column mapping in the text file (separated by \t): + // tmpBuff[0] = rInn + // tmpBuff[1] = length + // tmpBuff[2] = thick + // tmpBuff[3] = matBudgetMode (optional, default = Thickness) + // Cylindrical requires at least 3 parameters if (tmpBuff.size() < 3) { LOGP(fatal, "Invalid configuration for cylindrical layer {}: insufficient parameters.", layerCount); } + float rInn = tmpBuff[0]; + float length = tmpBuff[1]; + float thick = tmpBuff[2]; + // Default mode is Thickness - MatBudgetParamMode mode = MatBudgetParamMode::Thickness; + MatBudgetParamMode matBudgetMode = MatBudgetParamMode::Thickness; if (tmpBuff.size() >= 4) { - mode = static_cast(static_cast(tmpBuff[3])); + matBudgetMode = static_cast(static_cast(tmpBuff[3])); } - mLayers.push_back(std::make_unique(layerCount, name, tmpBuff[0], tmpBuff[1], tmpBuff[2], mode)); + mLayers.push_back(std::make_unique(layerCount, name, rInn, length, thick, matBudgetMode)); break; } case kSegmented: { @@ -201,7 +211,7 @@ void Detector::configFromFile(std::string fileName) int nMods = static_cast(tmpBuff[4]); // Default mode is Thickness - MatBudgetParamMode mode = MatBudgetParamMode::Thickness; + MatBudgetParamMode matBudgetMode = MatBudgetParamMode::Thickness; if (layerCount < constants::ML::nLayers) { // ML layers require stagOffset (index 5) @@ -211,17 +221,17 @@ void Detector::configFromFile(std::string fileName) float stagOffset = tmpBuff[5]; if (tmpBuff.size() >= 7) { - mode = static_cast(static_cast(tmpBuff[6])); + matBudgetMode = static_cast(static_cast(tmpBuff[6])); } - mLayers.push_back(std::make_unique(layerCount, name, rInn, stagOffset, tiltAngle, nStaves, nMods, thick, mode)); + mLayers.push_back(std::make_unique(layerCount, name, rInn, stagOffset, tiltAngle, nStaves, nMods, thick, matBudgetMode)); } else { // OT layers do NOT have stagOffset. The optional mode is at index 5. if (tmpBuff.size() >= 6) { - mode = static_cast(static_cast(tmpBuff[5])); + matBudgetMode = static_cast(static_cast(tmpBuff[5])); } - mLayers.push_back(std::make_unique(layerCount, name, rInn, tiltAngle, nStaves, nMods, thick, mode)); + mLayers.push_back(std::make_unique(layerCount, name, rInn, tiltAngle, nStaves, nMods, thick, matBudgetMode)); } break; } diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx index 01a945ee7a753..7b61d2e120196 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx @@ -9,22 +9,23 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include #include +#include + +#include +#include +#include +#include +#include #include #include +#include + #include -#include -#include -#include -#include -#include -#include -#include -#include +#include -using std::string; +#include namespace o2 { @@ -140,6 +141,9 @@ void TRKServices::createServices(TGeoVolume* motherVolume) createOuterBarrelServices(vol); } else { LOGP(info, "TRK services: Peacock layout"); + if (trkPars.includeLowServices) { + createServicesAroundBeamPipe(vol); + } createMLServicesPeacock(vol); createOTServicesPeacock(vol); } @@ -521,9 +525,63 @@ void TRKServices::createOuterBarrelServices(TGeoVolume* motherVolume) motherVolume->AddNode(outerBarrelCoolingH2OVolume, 1, nullptr); } +void TRKServices::createServicesAroundBeamPipe(TGeoVolume* motherVolume) +{ + // This method hardcodes the shape for the low services around the beam pipe + auto& matmgr = o2::base::MaterialManager::Instance(); + + TGeoMedium* medCu = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_COPPER"); + + const float tolleranceLowServices = 0.3f; + + // Low services start longitudinally from middle barrel on the C side, while from the middle barrel connection disks on the A side + const float zStartASideFirstBlock = 65.265f + tolleranceLowServices; + const float zStartCSideFirstBlock = 64.5f + tolleranceLowServices; + const float zStartSecondBlock = 150.f; + const float zStartThirdBlock = 365.f; + const float zEndThirdBlock = 400.f; + + // Low services start radially from IRIS out-vacuum services on the A side, while from beam pipe on the C side + const float rInASide = 3.333f + tolleranceLowServices; + const float rInCSide = 5.6f + tolleranceLowServices; + + // Low services end radially at the disks inners radius + const float rOutFirstBlock = 10.f - tolleranceLowServices; + const float rOutSecondBlock = 20.f - tolleranceLowServices; + const float rOutThirdBlock = 15.f - tolleranceLowServices; + + for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { + std::string orLabel = orientation == Orientation::kASide ? "A" : "C"; + + float zStartLowServices = orientation == Orientation::kASide ? zStartASideFirstBlock : zStartCSideFirstBlock; + float rInLowServices = orientation == Orientation::kASide ? rInASide : rInCSide; + + TGeoTube* lowServicesFirstBlock = new TGeoTube(Form("TRK_LOWSERVICES_FIRSTBLOCKsh_%s", orLabel.c_str()), rInLowServices, rOutFirstBlock, (zStartSecondBlock - zStartLowServices) / 2.); + TGeoVolume* lowServicesFirstBlockVolume = new TGeoVolume(Form("TRK_LOWSERVICES_FIRSTBLOCK_%s", orLabel.c_str()), lowServicesFirstBlock, medCu); + lowServicesFirstBlockVolume->SetLineColor(kGray); + + TGeoTube* lowServicesSecondBlock = new TGeoTube(Form("TRK_LOWSERVICES_SECONDBLOCKsh_%s", orLabel.c_str()), rInLowServices, rOutSecondBlock, (zStartThirdBlock - zStartSecondBlock) / 2.); + TGeoVolume* lowServicesSecondBlockVolume = new TGeoVolume(Form("TRK_LOWSERVICES_SECONDBLOCK_%s", orLabel.c_str()), lowServicesSecondBlock, medCu); + lowServicesSecondBlockVolume->SetLineColor(kGray); + + TGeoTube* lowServicesThirdBlock = new TGeoTube(Form("TRK_LOWSERVICES_THIRDBLOCKsh_%s", orLabel.c_str()), rInLowServices, rOutThirdBlock, (zEndThirdBlock - zStartThirdBlock) / 2.); + TGeoVolume* lowServicesThirdBlockVolume = new TGeoVolume(Form("TRK_LOWSERVICES_THIRDBLOCK_%s", orLabel.c_str()), lowServicesThirdBlock, medCu); + lowServicesThirdBlockVolume->SetLineColor(kGray); + + auto* rot = new TGeoRotation("", 0, 0, 180); + auto* combiTransFirstBlock = new TGeoCombiTrans(0, 0, (int)orientation * (zStartLowServices + (zStartSecondBlock - zStartLowServices) / 2.), rot); + auto* combiTransSecondBlock = new TGeoCombiTrans(0, 0, (int)orientation * (zStartSecondBlock + (zStartThirdBlock - zStartSecondBlock) / 2.), rot); + auto* combiTransThirdBlock = new TGeoCombiTrans(0, 0, (int)orientation * (zStartThirdBlock + (zEndThirdBlock - zStartThirdBlock) / 2.), rot); + + motherVolume->AddNode(lowServicesFirstBlockVolume, 1, combiTransFirstBlock); + motherVolume->AddNode(lowServicesSecondBlockVolume, 1, combiTransSecondBlock); + motherVolume->AddNode(lowServicesThirdBlockVolume, 1, combiTransThirdBlock); + } +} + void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) { - // This method hardcoes the yellow shape for the middle services + // This method hardcodes the yellow shape for the middle services auto& matmgr = o2::base::MaterialManager::Instance(); TGeoMedium* medSiO2 = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_SILICONDIOXIDE"); @@ -619,7 +677,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { for (int iSide = 0; iSide < 2; iSide++) { // left/right or top/bottom float refAngle = 0; - string orLabel("A"); + std::string orLabel("A"); if (orientation == Orientation::kCSide) { orLabel = "C"; refAngle = 90; @@ -703,7 +761,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) diskCircumference = rMaxMiddleServicesBarFwd * 3.14; // Only half of the area is used for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { float refAngle = 0; - string orLabel("A"); + std::string orLabel("A"); if (orientation == Orientation::kCSide) { refAngle = 90; orLabel = "C"; @@ -872,7 +930,7 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) motherVolume->AddNode(outerBarrelCarbonSupportVolume, 1, nullptr); for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { - string orLabel = "A"; + std::string orLabel = "A"; float refAngle = 0; if (orientation == Orientation::kCSide) { orLabel = "C"; From 9ad6dd4b29fdf5749b138ac8bf4ea62c49405378 Mon Sep 17 00:00:00 2001 From: shahor02 Date: Mon, 11 May 2026 20:14:10 +0200 Subject: [PATCH 064/102] Raw TF dump workflow (#15374) * Raw TF dump workflow * Add trigger * Add throttling to requested dumping rate, + readme * Add optional o2-raw-tf-dump-workflow to the dpl-worlflow.sh Will be added in the raw-data driven modes (CTFINPUT==0 and DIGITINPUT==0) if the env.var DPL_RAWTFDUMP is defined and ==1. If an external trigger for dumping is provided, e.g. `TPC/CMVTRIGGER`, it will be added to the PROXY_INSPEC (also passed to the raw-proxy) and passed to the o2-raw-tf-dump-workflow via option ``` --triggerspec ${DPL_RAWTFDUMP_TRIGGER} ``` Other hardcoded options are: ``` --dataspec ${PROXY_INSPEC} : the usual output string of the raw-input proxy --output-dir ${RAWTF_DIR:-$CTF_DIR}: output directory, if not specified, then CTF directory will be used (where a temporary run-specific sub-directory will be created) --meta-output-dir ${EPN2EOS_METAFILES_DIR} --max-dump-rate ${RAWTF_DUMPRATE:-0.1}: max. output rate in percents (either as a standalone trigger or a throttling device on top of the external trigger, if provided) --min-file-size ${RAWTF_MINSIZE:-$CTF_MINSIZE}: max. output file size to accumulate (if not provided, CTF-writer settings will be used) --max-tf-per-file ${RAWTF_MAX_PER_FILE:-$CTF_MAX_PER_FILE}: max. TFs in the output file to accumulate (if not provided, CTF-writer settings will be used) --mute-warn-period ${RAWTF_MUTE_PERIOD:-200}: if externally triggered TF is throttled by the max-dump-rate, warn at most once per this number of TFs. --max-warn ${RAWTF_MAX_WARN:-5}: maximum throttling warnings to issue ``` For other possible options see Detectors/Raw/README.md#raw-tf-dd-format-dumping-workflow. --- Common/Utils/include/CommonUtils/NameConf.h | 3 + Common/Utils/src/NameConf.cxx | 5 + Detectors/Raw/README.md | 76 +++ Detectors/Raw/TFReaderDD/CMakeLists.txt | 7 + .../Raw/TFReaderDD/src/RawTFDumpSpec.cxx | 574 ++++++++++++++++++ Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h | 23 + .../TFReaderDD/src/SubTimeFrameFileReader.cxx | 5 +- .../TFReaderDD/src/tf-data-dump-workflow.cxx | 46 ++ prodtests/full-system-test/dpl-workflow.sh | 90 ++- 9 files changed, 794 insertions(+), 35 deletions(-) create mode 100644 Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx create mode 100644 Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h create mode 100644 Detectors/Raw/TFReaderDD/src/tf-data-dump-workflow.cxx diff --git a/Common/Utils/include/CommonUtils/NameConf.h b/Common/Utils/include/CommonUtils/NameConf.h index fb10f929c9782..8d4c0a2c1c4f8 100644 --- a/Common/Utils/include/CommonUtils/NameConf.h +++ b/Common/Utils/include/CommonUtils/NameConf.h @@ -100,6 +100,9 @@ class NameConf : public o2::conf::ConfigurableParamHelper // CTF Dictionary static std::string getCTFDictFileName(); + // O2 Raw TF Filename + static std::string getRawTFFileName(uint32_t run, uint32_t orb, uint32_t id, const std::string& host, const std::string_view prefix = "o2_rawtf_dump"); + // Default CCDB server static std::string getCCDBServer(); diff --git a/Common/Utils/src/NameConf.cxx b/Common/Utils/src/NameConf.cxx index 45646284a878b..48cefacaf14c7 100644 --- a/Common/Utils/src/NameConf.cxx +++ b/Common/Utils/src/NameConf.cxx @@ -95,6 +95,11 @@ std::string NameConf::getCTFFileName(uint32_t run, uint32_t orb, uint32_t id, co return o2::utils::Str::concat_string(prefix, '_', fmt::format("run{:08d}_orbit{:010d}_tf{:010d}_{}", run, orb, id, host), ".root"); } +std::string NameConf::getRawTFFileName(uint32_t run, uint32_t orb, uint32_t id, const std::string& host, const std::string_view prefix) +{ + return o2::utils::Str::concat_string(prefix, '_', fmt::format("run{:08d}_orbit{:010d}_tf{:010d}_{}", run, orb, id, host), ".tf"); +} + std::string NameConf::getCTFDictFileName() { return o2::utils::Str::concat_string(CTFDICT, ".root"); diff --git a/Detectors/Raw/README.md b/Detectors/Raw/README.md index 557245030b980..d896b232d2647 100644 --- a/Detectors/Raw/README.md +++ b/Detectors/Raw/README.md @@ -548,6 +548,82 @@ list of detectors for which raw outputs are discarded. The raw data will be propagated (if present) only if the detector is selected in `--onlyDet` and `NOT` selected in `--non-raw-only-det`. The non-raw data will be propagated (if defined for the given detector and present in the file) only if the detector is selected in `--onlyDet` and `NOT` selected in `--raw-only-det`. +## Raw TF (DD format) dumping workflow + +Use `o2-raw-tf-dump-workflow` to dump raw TF data in DD format. The options are: +``` +--dataspec arg (=tst:TST/A) +``` +Optional selection string for the data to be dumped, e.g. the same string supplied to the input raw proxy +``` +--triggerspec arg (="") +``` +Selection string for the external trigger to dump particular TF. Must be contained in the `--dataspec`. The workflow will loop over all available trigger inputs, interpreting them as span: any `span[0]==true` will trigger writing process (modulo throttling). +``` +--include-deadbeef (false) +``` +Include data with DPL-generated 0xdeadbeef subspecs (for data missing in the original TF). +``` +--max-dump-rate arg (=0) +``` +Fraction in (`%`) of TFs to dump. W/o external trigger: random(>0) or periodic(<0) rejection. With external trigger: throttle dumping to have the lowest estimated acceptance rate compatible with this rate. +``` +--rate-est-conf-limit arg (=0.05) +``` +Quantile for the lowest rate estimate confidence limit +``` +--max-warn arg (=5) +``` +If throttling, max allowed warnings +``` +--mute-warn-period arg (=100) +``` +Mute warnings about throttling for this number of TFs +``` +--output-dir arg (=none) +``` +Dumped TFs output directory, must exist. `none` means current dir., `/dev/null`: ignort writing (dry run) +``` +--meta-output-dir arg (=/dev/null) +``` +TF metadata output directory, must exist (if not /dev/null, in which case the metadata will not be created) +``` +--md5-for-meta (false) +``` +Fill CTF file MD5 sum in the metadata file +``` +--min-file-size arg (=0) +``` +Accumulate TFs until given file size reached +``` +--max-file-size arg (=0) +``` +If > 0, try to avoid exceeding given file size, also used for space check +``` +--max-tf-per-file arg (=0) +``` +If > 0, avoid storing more than requested CTFs per file +``` +--require-free-disk arg (=0) +``` +Pause writing op. if available disk space is below this margin, in bytes if >0, as a fraction of total if <0 +``` +--wait-for-free-disk arg (=10) +``` +If paused due to the low disk space, recheck after this time (in s) +``` +--max-wait-for-free-disk arg (=60) +``` +Produce fatal if paused due to the low disk space for more than time in seconds. +``` +--verbosity-level (=0) +``` +Verbose mode: 1: decision on every TF, 2: details of saved TF, 3: more details. +``` +--ignore-partition-run-dir +``` +Do not creare partition-run directory in output-dir + ## TF rate limiting To apply TF rate limiting (i.e. make sure that no more than N TFs are in processing) provide `--timeframes-rate-limit --timeframes-rate-limit-ipcid ` diff --git a/Detectors/Raw/TFReaderDD/CMakeLists.txt b/Detectors/Raw/TFReaderDD/CMakeLists.txt index 12ecc9ca8795d..f87d1b5a7704e 100644 --- a/Detectors/Raw/TFReaderDD/CMakeLists.txt +++ b/Detectors/Raw/TFReaderDD/CMakeLists.txt @@ -26,3 +26,10 @@ o2_add_executable(tf-reader-workflow SOURCES src/TFReaderSpec.cxx src/tf-reader-workflow.cxx PUBLIC_LINK_LIBRARIES O2::TFReaderDD) + + +o2_add_executable(tf-dump-workflow + COMPONENT_NAME raw + SOURCES src/RawTFDumpSpec.cxx + src/tf-data-dump-workflow.cxx + PUBLIC_LINK_LIBRARIES O2::TFReaderDD) diff --git a/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx new file mode 100644 index 0000000000000..4e2d0372b3069 --- /dev/null +++ b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx @@ -0,0 +1,574 @@ +// 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 "Framework/WorkflowSpec.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/RawDeviceService.h" +#include "Framework/DataProcessingHelpers.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/Task.h" +#include "Framework/DataTakingContext.h" +#include "Framework/TimingInfo.h" +#include "DataFormatsParameters/GRPECSObject.h" +#include "DetectorsCommonDataFormats/FileMetaData.h" +#include "RawTFDumpSpec.h" +#include "TFReaderDD/SubTimeFrameFile.h" +#include "CommonUtils/NameConf.h" +#include "CommonUtils/FileSystemUtils.h" +#include "CommonUtils/StringUtils.h" +#include "Algorithm/RangeTokenizer.h" +#include +#include +#include +#include + +namespace o2::rawdd +{ +namespace o2h = o2::header; +using namespace o2::framework; +using DataHeader = o2::header::DataHeader; +using DetID = o2::detectors::DetID; +using ios = std::ios_base; + +class RawTFDump : public Task +{ + public: + static constexpr o2h::DataDescription DESCRaw{"RAWDATA"}, DESCCRaw{"CRAWDATA"}; + + RawTFDump(const std::string& trigger); + void init(InitContext& ic) final; + void run(ProcessingContext& pc) final; + void endOfStream(EndOfStreamContext& ec) final; + + private: + bool triggerTF(ProcessingContext& pc); + void updateTimeDependentParams(ProcessingContext& pc); + void prepareTFForWriting(ProcessingContext& pc); + size_t getTFSizeInFile() const; + size_t getCurrentFileSize(); + void prepareTFFile(); + void closeTFFile(); + bool checkFreeSpace(ProcessingContext& pc); + std::string reportRates() const; + + SubTimeFrameFileDataIndex mTFDataIndex; + std::vector> mTFData; + std::map> mDataMap; + std::vector mFilter{}; + std::vector mTriggerFilter{}; + + size_t mTFSize = 0; + size_t mMinFileSize = 0; // if > 0, accumulate TFs in the same file until the total size exceeds this minimum + size_t mMaxFileSize = 0; // if > MinSize, and accumulated size will exceed this value, stop accumulation (even if mMinFileSize is not reached) + + int mNTFsSeen = 0; // total number of TFs seen + int mNTFsExtTrig = 0; // total nunber of TFs externally triggered + int mNTFsAccepted = 0; // total number of TFs written + int mNTFsInFile = 0; // total number of TFs accumulated in the current file + int mNTFFiles = 0; // total number of TF files written + int mLastWarned = 0; // TF when last warned about throttling + int mMaxTFPerFile = 0; // max TFs per files to store + int mNWarnThrottle = 0; // number of times we warned about the throttling + int mMaxWarnThrottle = 0; // max allowed warnings about the throttling + int mWarnThrottleTF = 0; // min period (in TFs) between the warnings about the throttling + int mWaitDiskFull = 0; // if mCheckDiskFull triggers, pause for this amount of ms before new attempt + int mWaitDiskFullMax = -1; // produce fatal mCheckDiskFull block the workflow for more than this time (in ms) + float mCheckDiskFull = 0.; // wait for if available abs. disk space is < mCheckDiskFull (if >0) or if its fraction is < -mCheckDiskFull (if <0) + float mMaxAccRate = 0.f; // max acceptance rate + float mConfLim = 0.05f; // confidence limit for rate esimate (lower quantile) + float mRateEstAccLow = 0.f; // lower limit on accepted TFs rate + float mRateEstAccUpp = 0.f; // upper limit on accepted TFs rate + float mRateEstTrgLow = 0.f; // lower limit on triggered TFs rate + float mRateEstTrgUpp = 0.f; // upper limit on triggered TFs rate + + bool mFillMD5 = false; + bool mWriteTF = true; // for dry run + bool mStoreMetaFile = false; + bool mCreateRunEnvDir = true; + bool mAcceptCurrentTF = false; + bool mRejectDEADBEEF = false; + int mVerbose = 0; + std::vector mTFOrbits{}; // 1st orbits of TF accumulated in current file + o2::framework::DataTakingContext mDataTakingContext{}; + o2::framework::TimingInfo mTimingInfo{}; + + std::string mTrigger{}; // external trigger input + std::string mHostName{}; + std::string mTFDir{}; + std::string mTFMetaFileDir = "/dev/null"; + std::string mCurrentTFFileName{}; + std::string mCurrentTFFileNameFull{}; + std::string mCurrentTFFileNameFullTmp{}; + std::string mMetaDataType{}; + + static constexpr size_t MiB = 1ul << 20; + static constexpr std::streamsize sBuffSize = MiB; // 1 MiB + static constexpr std::streamsize sChunkSize = 512; + static const std::string TMPFileEnding; + std::unique_ptr mFileBuf; + std::ofstream mFile; + std::uniform_real_distribution mUniformDist{0.0, 100.0}; + std::default_random_engine mRGen; + + // helper to make sure the written blocks are buffered + template < + typename pointer, + typename std::enable_if< + std::is_pointer::value && // pointers only + (std::is_void>::value || // void* or standard layout! + std::is_standard_layout>::value)>::type* = nullptr> + void buffered_write(const pointer p, std::streamsize pCount) + { + // make sure we're not doing a short write + assert((pCount % sizeof(std::conditional_t>::value, + char, std::remove_pointer_t>) == + 0) && + "Performing short write?"); + + const char* lPtr = reinterpret_cast(p); + // avoid the optimization if the write is large enough + if (pCount >= sBuffSize) { + mFile.write(lPtr, pCount); + } else { + // split the write to smaller chunks + while (pCount > 0) { + const auto lToWrite = std::min(pCount, sChunkSize); + assert(lToWrite > 0 && lToWrite <= sChunkSize && lToWrite <= pCount); + + mFile.write(lPtr, lToWrite); + lPtr += lToWrite; + pCount -= lToWrite; + } + } + } +}; + +const std::string RawTFDump::TMPFileEnding{".part"}; + +//________________________________________ +RawTFDump::RawTFDump(const std::string& trigger) : mTrigger{trigger} +{ + mTriggerFilter = select(trigger.c_str()); + mFileBuf = std::make_unique(sBuffSize); + mFile.rdbuf()->pubsetbuf(mFileBuf.get(), sBuffSize); + mFile.clear(); + mFile.exceptions(std::fstream::failbit | std::fstream::badbit); +} + +//________________________________________ +void RawTFDump::init(InitContext& ic) +{ + mRGen = std::default_random_engine(getpid()); + mTFMetaFileDir = ic.options().get("meta-output-dir"); + if (mTFMetaFileDir != "/dev/null") { + mTFMetaFileDir = o2::utils::Str::rectifyDirectory(mTFMetaFileDir); + mStoreMetaFile = true; + mFillMD5 = ic.options().get("md5-for-meta"); + } + + mTFDir = ic.options().get("output-dir"); + if (mTFDir != "/dev/null") { + mTFDir = o2::utils::Str::rectifyDirectory(mTFDir); + mWriteTF = true; + } else { + mWriteTF = false; + mStoreMetaFile = false; + } + + mRejectDEADBEEF = !ic.options().get("include-deadbeef"); + mCreateRunEnvDir = !ic.options().get("ignore-partition-run-dir"); + mMinFileSize = ic.options().get("min-file-size"); + mMaxFileSize = ic.options().get("max-file-size"); + mMaxTFPerFile = ic.options().get("max-tf-per-file"); + mMaxAccRate = ic.options().get("max-dump-rate"); + float cl = ic.options().get("rate-est-conf-limit"); + if (mConfLim < 0.001 || mConfLim > 0.32) { + LOGP(warn, "Bad confidence limit {} for rate estimate, setting to default {}", cl, mConfLim); + } else { + mConfLim = cl; + } + mMaxWarnThrottle = ic.options().get("max-warn"); + mWarnThrottleTF = ic.options().get("mute-warn-period"); + + mVerbose = ic.options().get("verbosity-level"); + if (mTrigger.empty()) { + if (mMaxAccRate >= 0.f) { + LOGP(info, "Will accept randomly {}% of TFs", mMaxAccRate); + } else { + LOGP(info, "Will accept every {}-th TF", int(std::ceil(-100.f / mMaxAccRate))); + } + } else { + mMaxAccRate = std::abs(mMaxAccRate); + LOGP(info, "Will limit TFs triggered with {} by {}% at most", mTrigger, mMaxAccRate); + } + + if (mWriteTF) { + if (mMinFileSize > 0) { + LOGP(info, "Multiple TFs will be accumulated in the file until its size exceeds {}{}", + mMinFileSize, mMaxFileSize > mMinFileSize ? fmt::format(" but does not exceed {} B", mMaxFileSize) : std::string{}); + } + } + + mCheckDiskFull = ic.options().get("require-free-disk"); + mWaitDiskFull = 1000 * ic.options().get("wait-for-free-disk"); + mWaitDiskFullMax = 1000 * ic.options().get("max-wait-for-free-disk"); + + char hostname[_POSIX_HOST_NAME_MAX]; + gethostname(hostname, _POSIX_HOST_NAME_MAX); + mHostName = hostname; + mHostName = mHostName.substr(0, mHostName.find('.')); +} + +//________________________________________ +void RawTFDump::run(ProcessingContext& pc) +{ + mNTFsSeen++; + updateTimeDependentParams(pc); + mAcceptCurrentTF = triggerTF(pc); + if (mAcceptCurrentTF) { + prepareTFForWriting(pc); + } else { + return; + } + + prepareTFFile(); + if (mWriteTF && checkFreeSpace(pc)) { // write data + try { + size_t lTFSizeInFile = getTFSizeInFile(); + SubTimeFrameFileMeta lTFFileMeta(lTFSizeInFile); + + mFile << lTFFileMeta; // Write DataHeader + SubTimeFrameFileMeta + mFile << mTFDataIndex; // Write DataHeader + SubTimeFrameFileDataIndex + + for (const auto& eqEntry : mDataMap) { + auto& [lSize, lCnt, lEntry] = eqEntry.second; + for (size_t part = 0; part < lCnt; part++) { + const auto& dataPtr = mTFData[lEntry + part]; + DataHeader hdToWrite = *reinterpret_cast(dataPtr.first); // make a local DataHeader copy to clear flagsNextHeader bit + hdToWrite.flagsNextHeader = 0; + buffered_write(reinterpret_cast(&hdToWrite), sizeof(DataHeader)); + buffered_write(dataPtr.second, hdToWrite.payloadSize); + } + } + mFile.flush(); // flush the buffer and check the state + mTFOrbits.push_back(mTimingInfo.firstTForbit); + mNTFsInFile++; + } catch (const std::ios_base::failure& eFailExc) { + LOGP(error, "Writing of TF {} to file {} failed. error={}", mTimingInfo.tfCounter, mCurrentTFFileNameFullTmp, eFailExc.what()); + } + } + // cleanup + mTFData.clear(); + mDataMap.clear(); + mTFDataIndex.clear(); + mTFSize = 0; +} + +//____________________________________________________________ +void RawTFDump::endOfStream(EndOfStreamContext&) +{ + closeTFFile(); + LOGP(info, "Dumped {} TFs to {} files", mNTFsAccepted, mNTFFiles); + if (!mTriggerFilter.empty()) { + LOGP(info, "External trigger summary: {}", reportRates()); + } +} + +//________________________________________ +size_t RawTFDump::getTFSizeInFile() const +{ + return SubTimeFrameFileMeta::getSizeInFile() + mTFDataIndex.getSizeInFile() + mTFSize; +} + +//________________________________________ +size_t RawTFDump::getCurrentFileSize() +{ + return mFile.is_open() ? size_t(mFile.tellp()) : 0; +} + +//___________________________________________________________________ +void RawTFDump::prepareTFFile() +{ + if (!mWriteTF) { + return; + } + bool needToOpen; + if (!mFile.is_open()) { + needToOpen = true; + } else { + auto currSize = getCurrentFileSize(); + if ((mNTFsInFile >= mMaxTFPerFile) || + (currSize >= mMinFileSize) || // min size exceeded, may close the file. + (currSize && mMaxFileSize > mMinFileSize && ((currSize + mTFSize) > mMaxFileSize))) { // this is not the 1st TF in the file and the new size will exceed allowed max + needToOpen = true; + } else { + LOGP(info, "Will add new TF of size {} to existing file of size {} with {} TFs", mTFSize, currSize, mNTFsInFile); + needToOpen = false; + } + } + if (needToOpen) { + closeTFFile(); + auto TFDir = mTFDir.empty() ? o2::utils::Str::rectifyDirectory("./") : mTFDir; + if (mCreateRunEnvDir && !mDataTakingContext.envId.empty() && (mDataTakingContext.envId != o2::framework::DataTakingContext::UNKNOWN)) { + TFDir += fmt::format("{}_{}tf/", mDataTakingContext.envId, mDataTakingContext.runNumber); + if (!TFDir.empty()) { + o2::utils::createDirectoriesIfAbsent(TFDir); + LOGP(info, "Created {} directory for TFs output", TFDir); + } + } + mCurrentTFFileName = o2::base::NameConf::getRawTFFileName(mTimingInfo.runNumber, mTimingInfo.firstTForbit, mTimingInfo.tfCounter, mHostName); + mCurrentTFFileNameFull = fmt::format("{}{}", TFDir, mCurrentTFFileName); + mCurrentTFFileNameFullTmp = TMPFileEnding.empty() ? mCurrentTFFileNameFull : o2::utils::Str::concat_string(mCurrentTFFileNameFull, TMPFileEnding); + mFile.open(mCurrentTFFileNameFullTmp.c_str(), ios::binary | ios::trunc | ios::out | ios::ate); + LOGP(info, "Opened new raw-tf dump file {}[{}]", mCurrentTFFileNameFull, TMPFileEnding); + mNTFFiles++; + } +} + +//___________________________________________________________________ +void RawTFDump::updateTimeDependentParams(ProcessingContext& pc) +{ + namespace GRPECS = o2::parameters::GRPECS; + mTimingInfo = pc.services().get(); + if (mTimingInfo.globalRunNumberChanged) { + mDataTakingContext = pc.services().get(); + // determine the output type for the TF metadata + mMetaDataType = GRPECS::getRawDataPersistencyMode(mDataTakingContext.runType, mDataTakingContext.forcedRaw); + } +} + +//___________________________________________________________________ +void RawTFDump::closeTFFile() +{ + if (!mFile.is_open()) { + return; + } + try { + LOGP(info, "Closing output file {}[{}]", mCurrentTFFileNameFull, TMPFileEnding); + mFile.close(); + // write TF file metaFile data + if (mStoreMetaFile) { + o2::dataformats::FileMetaData TFMetaData; + if (!TFMetaData.fillFileData(mCurrentTFFileNameFullTmp, mFillMD5, TMPFileEnding)) { + throw std::runtime_error("metadata file was requested but not created"); + } + TFMetaData.setDataTakingContext(mDataTakingContext); + TFMetaData.type = mMetaDataType; + TFMetaData.priority = "high"; + TFMetaData.tfOrbits.swap(mTFOrbits); + auto metaFileNameTmp = fmt::format("{}{}.tmp", mTFMetaFileDir, mCurrentTFFileName); + auto metaFileName = fmt::format("{}{}.done", mTFMetaFileDir, mCurrentTFFileName); + try { + std::ofstream metaFileOut(metaFileNameTmp); + metaFileOut << TFMetaData; + metaFileOut.close(); + if (!TMPFileEnding.empty()) { + std::filesystem::rename(mCurrentTFFileNameFullTmp, mCurrentTFFileNameFull); + } + std::filesystem::rename(metaFileNameTmp, metaFileName); + LOGP(info, "wrote meta file {}", metaFileName); + } catch (std::exception const& e) { + LOGP(error, "Failed to store TF meta data file {}, reason {}", metaFileName, e.what()); + } + } else if (!TMPFileEnding.empty()) { + std::filesystem::rename(mCurrentTFFileNameFullTmp, mCurrentTFFileNameFull); + } + } catch (std::exception const& e) { + LOGP(error, "Failed to finalize TF file {}, reason: ", mCurrentTFFileNameFull, e.what()); + } + mTFOrbits.clear(); + mNTFsInFile = 0; +} + +//________________________________________ +bool RawTFDump::checkFreeSpace(ProcessingContext& pc) +{ + int totalWait = 0, nwaitCycles = 0; + while (mCheckDiskFull) { + constexpr int showFirstN = 10, prsecaleWarnings = 50; + try { + const auto si = std::filesystem::space(mCurrentTFFileNameFullTmp); + std::string wmsg{}; + if (mCheckDiskFull > 0.f && si.available < mCheckDiskFull) { + nwaitCycles++; + wmsg = fmt::format("Disk has {} MiB available while at least {} MiB is requested, wait for {} ms (on top of {} ms)", si.available / MiB, size_t(mCheckDiskFull) / MiB, mWaitDiskFull, totalWait); + } else if (mCheckDiskFull < 0.f && float(si.available) / si.capacity < -mCheckDiskFull) { // relative margin requested + nwaitCycles++; + wmsg = fmt::format("Disk has {:.3f}% available while at least {:.3f}% is requested, wait for {} ms (on top of {} ms)", si.capacity ? float(si.available) / si.capacity * 100.f : 0., -mCheckDiskFull, mWaitDiskFull, totalWait); + } else { + nwaitCycles = 0; + } + if (nwaitCycles) { + if (mWaitDiskFullMax > 0 && totalWait > mWaitDiskFullMax) { + closeTFFile(); // try to save whatever we have + LOGP(fatal, "Disk has {} MiB available out of {} MiB after waiting for {} ms", si.available / MiB, si.capacity / MiB, mWaitDiskFullMax); + } + if (nwaitCycles < showFirstN + 1 || (prsecaleWarnings && (nwaitCycles % prsecaleWarnings) == 0)) { + LOGP(alarm, "{}", wmsg); + } + pc.services().get().waitFor((unsigned int)(mWaitDiskFull)); + totalWait += mWaitDiskFull; + continue; + } + } catch (std::exception const& e) { + LOGP(fatal, "unable to query disk space info for path {}, reason {}", mCurrentTFFileNameFull, e.what()); // do we want this? + } + break; + } + return true; +} + +//________________________________________ +bool RawTFDump::triggerTF(ProcessingContext& pc) +{ + bool trig = false; + if (mTrigger.empty()) { // random + if (mMaxAccRate > 0.f) { + trig = (mUniformDist(mRGen) <= mMaxAccRate); + } else if (mMaxAccRate < 0.f) { + trig = (mTimingInfo.tfCounter % int(std::ceil(-100.f / mMaxAccRate))) == 0; + } + } else { + for (auto const& ref : InputRecordWalker(pc.inputs(), mTriggerFilter)) { + auto const* dh = DataRefUtils::getHeader(ref); + if (!dh) { + LOGP(error, "Failed to extract header for trigger input"); + continue; + } + auto extTrig = DataRefUtils::as(ref); + if (mVerbose > 0) { + LOGP(info, "trigger input {}, part: {} of {}, payload {}, 1stTFOrbit: {} TF: {} | span size: {} span[0]={}", + DataSpecUtils::describe(OutputSpec{dh->dataOrigin, dh->dataDescription, dh->subSpecification}), + dh->splitPayloadIndex, dh->splitPayloadParts, dh->payloadSize, dh->firstTForbit, dh->tfCounter, extTrig.size(), extTrig.size() > 0 ? extTrig[0] : false); + } + if (extTrig.size() && extTrig[0]) { + trig = true; + break; + } + } + if (trig) { // do we need to throttle? + mNTFsExtTrig++; + mRateEstTrgLow = TMath::ChisquareQuantile(mConfLim, 2 * (mNTFsExtTrig)) / (2 * mNTFsSeen); + mRateEstTrgUpp = TMath::ChisquareQuantile(1. - mConfLim, 2 * (mNTFsExtTrig + 1)) / (2 * mNTFsSeen); + mRateEstAccLow = TMath::ChisquareQuantile(mConfLim, 2 * (mNTFsAccepted)) / (2 * mNTFsSeen); + mRateEstAccUpp = TMath::ChisquareQuantile(1. - mConfLim, 2 * (mNTFsAccepted + 1)) / (2 * mNTFsSeen); + if (mRateEstAccLow > 0.01 * mMaxAccRate) { // current lowest estimate on the acceptance rate exceeds desired limit -> ignore trigger + trig = false; + // do we need to warn? + if ((mNTFsSeen - mLastWarned) > mWarnThrottleTF && ((mNWarnThrottle < mMaxWarnThrottle) || mMaxWarnThrottle < 0)) { + mLastWarned = mNTFsSeen; + std::string swarn = reportRates(); + if (++mNWarnThrottle == mMaxWarnThrottle) { + swarn += " Will not warn anymore."; + } else { + swarn += fmt::format(" Will suppress this warnings for {} TFs", mWarnThrottleTF); + } + LOGP(alarm, "Ignoring TF triggered for dumping: {}", swarn); + } + } + } + } + if (trig) { + mNTFsAccepted++; + } + if (mVerbose > 0) { + LOGP(info, "TF#{} (slice#{}) will{} be written, {}", mTimingInfo.tfCounter, mTimingInfo.timeslice, trig ? "" : " not", reportRates()); + } + return trig; +} + +//________________________________________ +void RawTFDump::prepareTFForWriting(ProcessingContext& pc) +{ + for (auto const& ref : InputRecordWalker(pc.inputs(), mFilter)) { + auto const* dh = DataRefUtils::getHeader(ref); + if (!dh) { + LOGP(error, "Failed to extract header"); + continue; + } + if (dh->subSpecification == 0xdeadbeef && mRejectDEADBEEF) { + continue; + } + const auto lHdrDataSize = sizeof(DataHeader) + dh->payloadSize; + mTFSize += lHdrDataSize; + + auto& [lSize, lCnt, lEntry] = mDataMap[EquipmentIdentifier(*dh)]; + if (!lCnt) { + lEntry = mTFData.size(); // flag where the data of this spec starts + } + lSize += lHdrDataSize; + lCnt++; + mTFData.push_back({ref.header, ref.payload}); + if (mVerbose > 2) { + LOGP(info, "{}, part: {} of {}, payload {}, 1stTFOrbit: {} TF: {}", + DataSpecUtils::describe(OutputSpec{dh->dataOrigin, dh->dataDescription, dh->subSpecification}), + dh->splitPayloadIndex, dh->splitPayloadParts, dh->payloadSize, dh->firstTForbit, dh->tfCounter); + } + } + + // build the index + { + LOGP(info, "Creating dump image for TF {} of run {}, starting orbit {}, size = {}", mTimingInfo.tfCounter, mTimingInfo.runNumber, mTimingInfo.firstTForbit, mTFSize); + std::uint64_t lCurrOff = 0; + for (const auto& eqEntry : mDataMap) { + const auto& eq = eqEntry.first; + auto& [lSize, lCnt, lEntry] = eqEntry.second; + assert(lSize > sizeof(DataHeader)); + + OutputSpec spec{eq.mDataOrigin, eq.mDataDescription, eq.mSubSpecification}; + if (mVerbose > 1) { + LOGP(info, "{} : {} parts of size {} | offset: {}", DataSpecUtils::describe(spec), lCnt, lSize, lCurrOff); + } + mTFDataIndex.AddStfElement(eq, lCnt, lCurrOff, lSize); + lCurrOff += lSize; + } + } +} + +//____________________________________________________________ +std::string RawTFDump::reportRates() const +{ + std::string rep = fmt::format("{} TFs seen, {} accepted", mNTFsSeen, mNTFsAccepted); + if (!mTrigger.empty()) { + rep += fmt::format(", {} ext.triggered, est.rate: [{:.2e}:{:.2e}]/[{:.2e}:{:.2e}].", mNTFsExtTrig, mRateEstAccLow, mRateEstAccUpp, mRateEstTrgLow, mRateEstTrgUpp); + } + return rep; +} + +//__________________________________________________________ +DataProcessorSpec getRawTFDumpSpec(const std::string& inpconfig, const std::string& trigger) +{ + std::vector inputs = select(inpconfig.c_str()); + return DataProcessorSpec{ + "raw-tf-dump", + inputs, + {}, + AlgorithmSpec{adaptFromTask(trigger)}, + Options{ + {"include-deadbeef", VariantType::Bool, false, {"Include DPL-generated 0xdeadbeef subspecs for missing data"}}, + {"max-dump-rate", VariantType::Float, 0.f, {"%-age of TFs to dump. W/o external trigger: random(>0) or periodic(<0) rejection, with: max limit"}}, + {"rate-est-conf-limit", VariantType::Float, 0.05f, {"quantile for the lowest rate estimate confidence limit"}}, + {"max-warn", VariantType::Int, 5, {"max allowed warnings on throttling"}}, + {"mute-warn-period", VariantType::Int, 100, {"mute warnings on throttling for this number of TFs"}}, + {"output-dir", VariantType::String, "none", {"TF output directory, must exist"}}, + {"meta-output-dir", VariantType::String, "/dev/null", {"TF metadata output directory, must exist (if not /dev/null)"}}, + {"md5-for-meta", VariantType::Bool, false, {"fill CTF file MD5 sum in the metadata file"}}, + {"min-file-size", VariantType::Int64, 0l, {"accumulate TFs until given file size reached"}}, + {"max-file-size", VariantType::Int64, 0l, {"if > 0, try to avoid exceeding given file size, also used for space check"}}, + {"max-tf-per-file", VariantType::Int, 0, {"if > 0, avoid storing more than requested CTFs per file"}}, + {"require-free-disk", VariantType::Float, 0.f, {"pause writing op. if available disk space is below this margin, in bytes if >0, as a fraction of total if <0"}}, + {"wait-for-free-disk", VariantType::Float, 10.f, {"if paused due to the low disk space, recheck after this time (in s)"}}, + {"max-wait-for-free-disk", VariantType::Float, 60.f, {"produce fatal if paused due to the low disk space for more than this amount in s."}}, + {"verbosity-level", VariantType::Int, 0, {"Verbose mode: 1: decision on every TF, 2: details of saved TF, 3: more details"}}, + {"ignore-partition-run-dir", VariantType::Bool, false, {"Do not creare partition-run directory in output-dir"}}}}; +} + +} // namespace o2::rawdd diff --git a/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h new file mode 100644 index 0000000000000..a39cfb026ed52 --- /dev/null +++ b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h @@ -0,0 +1,23 @@ +// 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_RAW_TF_DUMP_SPEC_ +#define O2_RAW_TF_DUMP_SPEC_ + +#include "DetectorsCommonDataFormats/DetID.h" +#include "Framework/DeviceSpec.h" + +namespace o2::rawdd +{ +o2::framework::DataProcessorSpec getRawTFDumpSpec(const std::string& inpconfig, const std::string& trigger); +} + +#endif diff --git a/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx b/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx index f227390e67ef3..5f862dffe512f 100644 --- a/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx +++ b/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx @@ -115,7 +115,7 @@ std::size_t SubTimeFrameFileReader::getHeaderStackSize() // throws ios_base::fai LOGP(error, "FileReader: Reached max number of headers allowed: {}.", cMaxHeaders); return 0; } - + LOGP(debug, "getHeaderStackSize, pos = {}, size = {}", lFilePosStart, lStackSize); return lStackSize; } @@ -252,6 +252,7 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* return nullptr; } lStfMetaDataHdr = o2::header::DataHeader::Get(lMetaHdrStack.first()); + LOGP(debug, "read filemeta, pos = {}, size = {}", position(), sizeof(SubTimeFrameFileMeta)); if (!read_advance(&lStfFileMeta, sizeof(SubTimeFrameFileMeta))) { return nullptr; } @@ -320,7 +321,6 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* STFHeader stfHeader{tfID, -1u, -1u}; // read pairs while (lLeftToRead > 0) { - // allocate and read the Headers std::size_t lDataHeaderStackSize = 0; Stack lDataHeaderStack = getHeaderStack(lDataHeaderStackSize); @@ -398,6 +398,7 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* msgSW.Stop(); #endif memcpy(lHdrStackMsg->GetData(), headerStack.data(), headerStack.size()); + LOGP(debug, "read data, pos = {}, size = {} leftToRead {}", position(), lDataSize, lLeftToRead); if (!read_advance(lDataMsg->GetData(), lDataSize)) { return nullptr; diff --git a/Detectors/Raw/TFReaderDD/src/tf-data-dump-workflow.cxx b/Detectors/Raw/TFReaderDD/src/tf-data-dump-workflow.cxx new file mode 100644 index 0000000000000..fbade100d202f --- /dev/null +++ b/Detectors/Raw/TFReaderDD/src/tf-data-dump-workflow.cxx @@ -0,0 +1,46 @@ +// 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 "CommonUtils/ConfigurableParam.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/CompletionPolicy.h" +#include "Framework/CompletionPolicyHelpers.h" + +using namespace o2::framework; + +void customize(std::vector& workflowOptions) +{ + std::vector options; + options.push_back(ConfigParamSpec{"dataspec", VariantType::String, "tst:TST/A", {"selection string for the data to be proxied"}}); + options.push_back(ConfigParamSpec{"triggerspec", VariantType::String, "", {"selection string for the trigger input (must be also in dataspec if non-empty)"}}); + options.push_back(ConfigParamSpec{"configKeyValues", VariantType::String, "", {"semicolon separated key=value strings"}}); + std::swap(workflowOptions, options); +} + +void customize(std::vector& policies) +{ + policies.push_back({CompletionPolicyHelpers::consumeWhenPastOldestPossibleTimeframe("raw-tf-dump", [](auto const&) -> bool { return true; })}); + // policies.push_back({CompletionPolicyHelpers::consumeWhenAllOrdered("raw-tf-dump", [](auto const&) -> bool { return true; })}); // RSTOREM +} + +// ------------------------------------------------------------------ + +#include "Framework/runDataProcessing.h" +#include "RawTFDumpSpec.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) +{ + o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); + auto inpconfig = configcontext.options().get("dataspec"); + auto trigger = configcontext.options().get("triggerspec"); + WorkflowSpec specs{o2::rawdd::getRawTFDumpSpec(inpconfig, trigger)}; + return specs; +} diff --git a/prodtests/full-system-test/dpl-workflow.sh b/prodtests/full-system-test/dpl-workflow.sh index ca466d311ed30..3619cc38a0c09 100755 --- a/prodtests/full-system-test/dpl-workflow.sh +++ b/prodtests/full-system-test/dpl-workflow.sh @@ -71,6 +71,50 @@ elif [[ -z ${SYNCRAWMODE:-} ]]; then SYNCRAWMODE=0 fi +# --------------------------------------------------------------------------------------------------------------------- +# build incoming raw inputs specs +define_raw_inputs() +{ + PROXY_INSPEC="dd:FLP/DISTSUBTIMEFRAME/0" + PROXY_IN_N=0 + for i in ${INPUT_DETECTOR_LIST//,/ }; do + if has_detector_flp_processing $i; then + case $i in + TOF) + PROXY_INTYPE="CRAWDATA";; + FT0 | FV0 | FDD) + PROXY_INTYPE="DIGITSBC/0 DIGITSCH/0";; + PHS) + PROXY_INTYPE="CELLS CELLTRIGREC";; + CPV) + PROXY_INTYPE="DIGITS/0 DIGITTRIGREC/0 RAWHWERRORS";; + EMC) + PROXY_INTYPE="CELLS/0 CELLSTRGR/0 DECODERERR";; + CTP) + PROXY_INTYPE="LUMI/0 RAWDATA" + CTP_CONFIG=" --no-lumi " + ;; + *) + echo Input type for detector $i with FLP processing not defined 1>&2 + exit 1;; + esac + else + PROXY_INTYPE=RAWDATA + fi + for j in $PROXY_INTYPE; do + PROXY_INNAME="RAWIN$PROXY_IN_N" + let PROXY_IN_N=$PROXY_IN_N+1 + PROXY_INSPEC+=";$PROXY_INNAME:$i/$j" + done + done + # do we have DPL_RAWTFDUMP_TRIGGER trigger (e.g. TPC/CMVTRIGGER)? If so, add its spec + if has_detector TPC && [[ -n ${DPL_RAWTFDUMP_TRIGGER:-} ]]; then + PROXY_INNAME="RAWIN$PROXY_IN_N" + let PROXY_IN_N=$PROXY_IN_N+1 + PROXY_INSPEC+=";$PROXY_INNAME:${DPL_RAWTFDUMP_TRIGGER}" + fi +} + # --------------------------------------------------------------------------------------------------------------------- # Set some individual workflow arguments depending on configuration GPU_INPUT=zsraw @@ -109,6 +153,7 @@ EVE_OPT=" --jsons-folder $EDJSONS_DIR" : ${ALPIDE_ERR_DUMPS:=} : ${ITSSTAGGERED:=} : ${MFTSTAGGERED:=} +: ${PROXY_INSPEC:=} [[ -z $ALPIDE_ERR_DUMPS ]] && [[ $EPNSYNCMODE == 1 && $RUNTYPE == "PHYSICS" ]] && ALPIDE_ERR_DUMPS=1 || ALPIDE_ERR_DUMPS=0 @@ -483,38 +528,7 @@ if [[ -n $INPUT_DETECTOR_LIST ]]; then add_W o2-raw-tf-reader-workflow "--delay $TFDELAY $TFRAWOPT --loop $TFLOOP $NTIMEFRAMES_CMD --input-data ${TFName} ${INPUT_FILE_COPY_CMD+--copy-cmd} ${INPUT_FILE_COPY_CMD:-} --onlyDet $INPUT_DETECTOR_LIST ${TIMEFRAME_SHM_LIMIT+--timeframes-shm-limit} ${TIMEFRAME_SHM_LIMIT:-}" elif [[ $EXTINPUT == 1 ]]; then PROXY_CHANNEL="name=readout-proxy,type=pull,method=connect,address=ipc://${UDS_PREFIX}${INRAWCHANNAME},transport=shmem,rateLogging=$EPNSYNCMODE" - PROXY_INSPEC="dd:FLP/DISTSUBTIMEFRAME/0" - PROXY_IN_N=0 - for i in ${INPUT_DETECTOR_LIST//,/ }; do - if has_detector_flp_processing $i; then - case $i in - TOF) - PROXY_INTYPE="CRAWDATA";; - FT0 | FV0 | FDD) - PROXY_INTYPE="DIGITSBC/0 DIGITSCH/0";; - PHS) - PROXY_INTYPE="CELLS CELLTRIGREC";; - CPV) - PROXY_INTYPE="DIGITS/0 DIGITTRIGREC/0 RAWHWERRORS";; - EMC) - PROXY_INTYPE="CELLS/0 CELLSTRGR/0 DECODERERR";; - CTP) - PROXY_INTYPE="LUMI/0 RAWDATA" - CTP_CONFIG=" --no-lumi " - ;; - *) - echo Input type for detector $i with FLP processing not defined 1>&2 - exit 1;; - esac - else - PROXY_INTYPE=RAWDATA - fi - for j in $PROXY_INTYPE; do - PROXY_INNAME="RAWIN$PROXY_IN_N" - let PROXY_IN_N=$PROXY_IN_N+1 - PROXY_INSPEC+=";$PROXY_INNAME:$i/$j" - done - done + define_raw_inputs [[ -n ${TIMEFRAME_RATE_LIMIT:-} ]] && [[ $TIMEFRAME_RATE_LIMIT != 0 ]] && PROXY_CHANNEL+=";name=metric-feedback,type=pull,method=connect,address=ipc://${UDS_PREFIX}metric-feedback-${O2JOBID:-$NUMAID},transport=shmem,rateLogging=0" if [[ $EPNSYNCMODE == 1 ]]; then RAWPROXY_CONFIG="--print-input-sizes 1000" @@ -544,8 +558,18 @@ if [[ -z ${WORKFLOW_DETECTORS_USE_GLOBAL_READER_TRACKS} ]] && [[ -z ${WORKFLOW_D fi # --------------------------------------------------------------------------------------------------------------------- -# Raw decoder workflows - disabled in async mode + if [[ $CTFINPUT == 0 && $DIGITINPUT == 0 ]]; then +# Check if raw TF data dump was requested, RAWTF_DUMPRATE must be in % + if [[ ${DPL_RAWTFDUMP:-} == 1 ]]; then + [[ -z ${PROXY_INSPEC} ]] && define_raw_inputs + CONFIG_RAWTFDUMP="--dataspec \"${PROXY_INSPEC}\" --output-dir \"${RAWTF_DIR:-$CTF_DIR}\" --meta-output-dir \"${EPN2EOS_METAFILES_DIR}\" --max-dump-rate ${RAWTF_DUMPRATE:-0.1} " + CONFIG_RAWTFDUMP+=" --min-file-size ${RAWTF_MINSIZE:-$CTF_MINSIZE} --max-tf-per-file ${RAWTF_MAX_PER_FILE:-$CTF_MAX_PER_FILE} --mute-warn-period ${RAWTF_MUTE_PERIOD:-200} --max-warn ${RAWTF_MAX_WARN:-5} " + [[ -n ${DPL_RAWTFDUMP_TRIGGER:-} ]] && CONFIG_RAWTFDUMP+=" --triggerspec \"${DPL_RAWTFDUMP_TRIGGER}\" " + add_W o2-raw-tf-dump-workflow "$CONFIG_RAWTFDUMP" + fi + +# Raw decoder workflows - disabled in async mode if has_detector TPC && [[ "${TPC_CONVERT_LINKZS_TO_RAW:-}" == "1" ]]; then GPU_INPUT=zsonthefly RAWTODIGITOPTIONS= From 3159c533bd690d8713692722982ae405190069f8 Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Sun, 10 May 2026 10:06:51 +0200 Subject: [PATCH 065/102] ITS: simplify Configuration Signed-off-by: Felix Schlepper --- .../GPU/ITStrackingGPU/TimeFrameGPU.h | 46 ++++----- .../GPU/ITStrackingGPU/TrackingKernels.h | 4 +- .../ITS/tracking/GPU/cuda/TimeFrameGPU.cu | 96 +++++++++---------- .../tracking/GPU/cuda/TrackerTraitsGPU.cxx | 94 +++++++++--------- .../ITS/tracking/GPU/cuda/TrackingKernels.cu | 20 ++-- .../include/ITStracking/Configuration.h | 19 +++- .../tracking/include/ITStracking/TimeFrame.h | 2 +- .../include/ITStracking/TrackerTraits.h | 3 +- .../include/ITStracking/TrackingInterface.h | 6 +- .../include/ITStracking/VertexerTraits.h | 24 +---- .../ITSMFT/ITS/tracking/src/Configuration.cxx | 18 +++- .../ITSMFT/ITS/tracking/src/TimeFrame.cxx | 8 +- Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx | 2 +- .../ITSMFT/ITS/tracking/src/TrackerTraits.cxx | 8 +- .../ITS/tracking/src/TrackingInterface.cxx | 8 +- .../ITSMFT/ITS/tracking/src/Vertexer.cxx | 11 ++- .../ITS/tracking/src/VertexerTraits.cxx | 11 ++- .../ALICE3/TRK/workflow/src/TrackerSpec.cxx | 4 +- 18 files changed, 210 insertions(+), 174 deletions(-) diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h index 8852810399eeb..1ecff79f2d200 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h @@ -42,30 +42,30 @@ class TimeFrameGPU : public TimeFrame void popMemoryStack(const int); void registerHostMemory(const int); void unregisterHostMemory(const int); - void initialise(const int, const TrackingParameters&, const int); - void loadIndexTableUtils(const int); - void loadTrackingFrameInfoDevice(const int, const int); - void createTrackingFrameInfoDeviceArray(const int); - void loadUnsortedClustersDevice(const int, const int); - void createUnsortedClustersDeviceArray(const int, const int = NLayers); - void loadClustersDevice(const int, const int); - void createClustersDeviceArray(const int, const int = NLayers); - void loadClustersIndexTables(const int, const int); - void createClustersIndexTablesArray(const int); - void createUsedClustersDevice(const int, const int); - void createUsedClustersDeviceArray(const int, const int = NLayers); + void initialise(const TrackingParameters&, int maxLayers); + void loadIndexTableUtils(); + void loadTrackingFrameInfoDevice(const int); + void createTrackingFrameInfoDeviceArray(); + void loadUnsortedClustersDevice(const int); + void createUnsortedClustersDeviceArray(const int = NLayers); + void loadClustersDevice(const int); + void createClustersDeviceArray(const int = NLayers); + void loadClustersIndexTables(const int); + void createClustersIndexTablesArray(); + void createUsedClustersDevice(const int); + void createUsedClustersDeviceArray(const int = NLayers); void loadUsedClustersDevice(); - void loadROFrameClustersDevice(const int, const int); - void createROFrameClustersDeviceArray(const int); + void loadROFrameClustersDevice(const int); + void createROFrameClustersDeviceArray(); void loadROFCutMask(const int); - void loadVertices(const int); - void loadROFOverlapTable(const int); - void loadROFVertexLookupTable(const int); - void updateROFVertexLookupTable(const int); + void loadVertices(); + void loadROFOverlapTable(); + void loadROFVertexLookupTable(); + void updateROFVertexLookupTable(); /// - void createTrackletsLUTDevice(const int, const int); - void createTrackletsLUTDeviceArray(const int); + void createTrackletsLUTDevice(bool, const int); + void createTrackletsLUTDeviceArray(); void loadTrackletsDevice(); void loadTrackletsLUTDevice(); void loadCellsDevice(); @@ -74,12 +74,12 @@ class TimeFrameGPU : public TimeFrame void loadTrackSeedsChi2Device(); void loadTrackSeedsDevice(bounded_vector&); void createTrackletsBuffers(const int); - void createTrackletsBuffersArray(const int); + void createTrackletsBuffersArray(); void createCellsBuffers(const int); - void createCellsBuffersArray(const int); + void createCellsBuffersArray(); void createCellsDevice(); void createCellsLUTDevice(const int); - void createCellsLUTDeviceArray(const int); + void createCellsLUTDeviceArray(); void createNeighboursIndexTablesDevice(const int); void createNeighboursDevice(const unsigned int layer); void createNeighboursLUTDevice(const int, const unsigned int); diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h index bf004426f9134..fe272f6f8d3bb 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h @@ -51,7 +51,7 @@ void countTrackletsInROFsHandler(const IndexTableUtils* utils, const int** clustersIndexTables, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, bounded_vector& phiCuts, const float resolutionPV, @@ -82,7 +82,7 @@ void computeTrackletsInROFsHandler(const IndexTableUtils* utils, gsl::span nTracklets, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, bounded_vector& phiCuts, const float resolutionPV, diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu index 03957cd678df0..b541518a88119 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu @@ -52,10 +52,10 @@ void TimeFrameGPU::allocMem(void** ptr, size_t size, bool extAllocator, } template -void TimeFrameGPU::loadIndexTableUtils(const int iteration) +void TimeFrameGPU::loadIndexTableUtils() { GPUTimer timer("loading indextable utils"); - if (!iteration) { + { GPULog("gpu-allocation: allocating IndexTableUtils buffer, for {:.2f} MB.", sizeof(IndexTableUtilsN) / constants::MB); allocMem(reinterpret_cast(&mIndexTableUtilsDevice), sizeof(IndexTableUtilsN), this->hasFrameworkAllocator()); } @@ -64,9 +64,9 @@ void TimeFrameGPU::loadIndexTableUtils(const int iteration) } template -void TimeFrameGPU::createUnsortedClustersDeviceArray(const int iteration, const int maxLayers) +void TimeFrameGPU::createUnsortedClustersDeviceArray(const int maxLayers) { - if (!iteration) { + { GPUTimer timer("creating unsorted clusters array"); allocMem(reinterpret_cast(&mUnsortedClustersDeviceArray), NLayers * sizeof(Cluster*), this->hasFrameworkAllocator()); GPUChkErrS(cudaHostRegister(mUnsortedClustersDevice.data(), NLayers * sizeof(Cluster*), cudaHostRegisterPortable)); @@ -81,9 +81,9 @@ void TimeFrameGPU::createUnsortedClustersDeviceArray(const int iteratio } template -void TimeFrameGPU::loadUnsortedClustersDevice(const int iteration, const int layer) +void TimeFrameGPU::loadUnsortedClustersDevice(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "loading unsorted clusters", layer); GPULog("gpu-transfer: loading {} unsorted clusters on layer {}, for {:.2f} MB.", this->mUnsortedClusters[layer].size(), layer, this->mUnsortedClusters[layer].size() * sizeof(Cluster) / constants::MB); allocMemAsync(reinterpret_cast(&mUnsortedClustersDevice[layer]), this->mUnsortedClusters[layer].size() * sizeof(Cluster), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -93,9 +93,9 @@ void TimeFrameGPU::loadUnsortedClustersDevice(const int iteration, cons } template -void TimeFrameGPU::createClustersDeviceArray(const int iteration, const int maxLayers) +void TimeFrameGPU::createClustersDeviceArray(const int maxLayers) { - if (!iteration) { + { GPUTimer timer("creating sorted clusters array"); allocMem(reinterpret_cast(&mClustersDeviceArray), NLayers * sizeof(Cluster*), this->hasFrameworkAllocator()); GPUChkErrS(cudaHostRegister(mClustersDevice.data(), NLayers * sizeof(Cluster*), cudaHostRegisterPortable)); @@ -110,9 +110,9 @@ void TimeFrameGPU::createClustersDeviceArray(const int iteration, const } template -void TimeFrameGPU::loadClustersDevice(const int iteration, const int layer) +void TimeFrameGPU::loadClustersDevice(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "loading sorted clusters", layer); GPULog("gpu-transfer: loading {} clusters on layer {}, for {:.2f} MB.", this->mClusters[layer].size(), layer, this->mClusters[layer].size() * sizeof(Cluster) / constants::MB); allocMemAsync(reinterpret_cast(&mClustersDevice[layer]), this->mClusters[layer].size() * sizeof(Cluster), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -122,9 +122,9 @@ void TimeFrameGPU::loadClustersDevice(const int iteration, const int la } template -void TimeFrameGPU::createClustersIndexTablesArray(const int iteration) +void TimeFrameGPU::createClustersIndexTablesArray() { - if (!iteration) { + { GPUTimer timer("creating clustersindextable array"); allocMem(reinterpret_cast(&mClustersIndexTablesDeviceArray), NLayers * sizeof(int*), this->hasFrameworkAllocator()); GPUChkErrS(cudaHostRegister(mClustersIndexTablesDevice.data(), NLayers * sizeof(int*), cudaHostRegisterPortable)); @@ -139,9 +139,9 @@ void TimeFrameGPU::createClustersIndexTablesArray(const int iteration) } template -void TimeFrameGPU::loadClustersIndexTables(const int iteration, const int layer) +void TimeFrameGPU::loadClustersIndexTables(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "loading sorted clusters", layer); GPULog("gpu-transfer: loading clusters indextable for layer {} with {} elements, for {:.2f} MB.", layer, this->mIndexTables[layer].size(), this->mIndexTables[layer].size() * sizeof(int) / constants::MB); allocMemAsync(reinterpret_cast(&mClustersIndexTablesDevice[layer]), this->mIndexTables[layer].size() * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -151,9 +151,9 @@ void TimeFrameGPU::loadClustersIndexTables(const int iteration, const i } template -void TimeFrameGPU::createUsedClustersDeviceArray(const int iteration, const int maxLayers) +void TimeFrameGPU::createUsedClustersDeviceArray(const int maxLayers) { - if (!iteration) { + { GPUTimer timer("creating used clusters flags"); allocMem(reinterpret_cast(&mUsedClustersDeviceArray), NLayers * sizeof(uint8_t*), this->hasFrameworkAllocator()); GPUChkErrS(cudaHostRegister(mUsedClustersDevice.data(), NLayers * sizeof(uint8_t*), cudaHostRegisterPortable)); @@ -168,9 +168,9 @@ void TimeFrameGPU::createUsedClustersDeviceArray(const int iteration, c } template -void TimeFrameGPU::createUsedClustersDevice(const int iteration, const int layer) +void TimeFrameGPU::createUsedClustersDevice(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "creating used clusters flags", layer); GPULog("gpu-transfer: creating {} used clusters flags on layer {}, for {:.2f} MB.", this->mUsedClusters[layer].size(), layer, this->mUsedClusters[layer].size() * sizeof(unsigned char) / constants::MB); allocMemAsync(reinterpret_cast(&mUsedClustersDevice[layer]), this->mUsedClusters[layer].size() * sizeof(unsigned char), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -190,9 +190,9 @@ void TimeFrameGPU::loadUsedClustersDevice() } template -void TimeFrameGPU::createROFrameClustersDeviceArray(const int iteration) +void TimeFrameGPU::createROFrameClustersDeviceArray() { - if (!iteration) { + { GPUTimer timer("creating ROFrame clusters array"); allocMem(reinterpret_cast(&mROFramesClustersDeviceArray), NLayers * sizeof(int*), this->hasFrameworkAllocator()); GPUChkErrS(cudaHostRegister(mROFramesClustersDevice.data(), NLayers * sizeof(int*), cudaHostRegisterPortable)); @@ -207,9 +207,9 @@ void TimeFrameGPU::createROFrameClustersDeviceArray(const int iteration } template -void TimeFrameGPU::loadROFrameClustersDevice(const int iteration, const int layer) +void TimeFrameGPU::loadROFrameClustersDevice(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "loading ROframe clusters", layer); GPULog("gpu-transfer: loading {} ROframe clusters info on layer {}, for {:.2f} MB.", this->mROFramesClusters[layer].size(), layer, this->mROFramesClusters[layer].size() * sizeof(int) / constants::MB); allocMemAsync(reinterpret_cast(&mROFramesClustersDevice[layer]), this->mROFramesClusters[layer].size() * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -219,9 +219,9 @@ void TimeFrameGPU::loadROFrameClustersDevice(const int iteration, const } template -void TimeFrameGPU::createTrackingFrameInfoDeviceArray(const int iteration) +void TimeFrameGPU::createTrackingFrameInfoDeviceArray() { - if (!iteration) { + { GPUTimer timer("creating trackingframeinfo array"); allocMem(reinterpret_cast(&mTrackingFrameInfoDeviceArray), NLayers * sizeof(TrackingFrameInfo*), this->hasFrameworkAllocator()); GPUChkErrS(cudaHostRegister(mTrackingFrameInfoDevice.data(), NLayers * sizeof(TrackingFrameInfo*), cudaHostRegisterPortable)); @@ -236,9 +236,9 @@ void TimeFrameGPU::createTrackingFrameInfoDeviceArray(const int iterati } template -void TimeFrameGPU::loadTrackingFrameInfoDevice(const int iteration, const int layer) +void TimeFrameGPU::loadTrackingFrameInfoDevice(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "loading trackingframeinfo", layer); GPULog("gpu-transfer: loading {} tfinfo on layer {}, for {:.2f} MB.", this->mTrackingFrameInfo[layer].size(), layer, this->mTrackingFrameInfo[layer].size() * sizeof(TrackingFrameInfo) / constants::MB); allocMemAsync(reinterpret_cast(&mTrackingFrameInfoDevice[layer]), this->mTrackingFrameInfo[layer].size() * sizeof(TrackingFrameInfo), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -250,7 +250,7 @@ void TimeFrameGPU::loadTrackingFrameInfoDevice(const int iteration, con template void TimeFrameGPU::loadROFCutMask(const int iteration) { - if (!iteration || iteration == 3) { // we need to re-load the swapped mult-mask in upc iteration + { GPUTimer timer("loading multiplicity cut mask"); const auto& hostTable = *(this->mROFMask); const auto hostView = hostTable.getView(); @@ -270,9 +270,9 @@ void TimeFrameGPU::loadROFCutMask(const int iteration) } template -void TimeFrameGPU::loadVertices(const int iteration) +void TimeFrameGPU::loadVertices() { - if (!iteration) { + { GPUTimer timer("loading seeding vertices"); GPULog("gpu-transfer: loading {} seeding vertices, for {:.2f} MB.", this->mPrimaryVertices.size(), this->mPrimaryVertices.size() * sizeof(Vertex) / constants::MB); allocMem(reinterpret_cast(&mPrimaryVerticesDevice), this->mPrimaryVertices.size() * sizeof(Vertex), this->hasFrameworkAllocator()); @@ -281,9 +281,9 @@ void TimeFrameGPU::loadVertices(const int iteration) } template -void TimeFrameGPU::loadROFOverlapTable(const int iteration) +void TimeFrameGPU::loadROFOverlapTable() { - if (!iteration) { + { GPUTimer timer("initialising device view of ROFOverlapTable"); const auto& hostTable = this->getROFOverlapTable(); const auto& hostView = this->getROFOverlapTableView(); @@ -305,9 +305,9 @@ void TimeFrameGPU::loadROFOverlapTable(const int iteration) } template -void TimeFrameGPU::loadROFVertexLookupTable(const int iteration) +void TimeFrameGPU::loadROFVertexLookupTable() { - if (!iteration) { + { GPUTimer timer("initialising device view of ROFVertexLookupTable"); const auto& hostTable = this->getROFVertexLookupTable(); const auto& hostView = this->getROFVertexLookupTableView(); @@ -329,10 +329,10 @@ void TimeFrameGPU::loadROFVertexLookupTable(const int iteration) } template -void TimeFrameGPU::updateROFVertexLookupTable(const int iteration) +void TimeFrameGPU::updateROFVertexLookupTable() { const auto& hostTable = this->getROFVertexLookupTable(); - if (!iteration) { + { GPUTimer timer("updating device view of ROFVertexLookupTable"); const auto& hostView = this->getROFVertexLookupTableView(); using TableEntry = ROFVertexLookupTable::TableEntry; @@ -345,19 +345,19 @@ void TimeFrameGPU::updateROFVertexLookupTable(const int iteration) } template -void TimeFrameGPU::createTrackletsLUTDeviceArray(const int iteration) +void TimeFrameGPU::createTrackletsLUTDeviceArray() { - if (!iteration) { + { allocMem(reinterpret_cast(&mTrackletsLUTDeviceArray), (NLayers - 1) * sizeof(int*), this->hasFrameworkAllocator()); } } template -void TimeFrameGPU::createTrackletsLUTDevice(const int iteration, const int layer) +void TimeFrameGPU::createTrackletsLUTDevice(bool allocate, const int layer) { GPUTimer timer(mGpuStreams[layer], "creating tracklets LUTs", layer); const int ncls = this->mClusters[layer].size() + 1; - if (!iteration) { + if (allocate) { GPULog("gpu-allocation: creating tracklets LUT for {} elements on layer {}, for {:.2f} MB.", ncls, layer, ncls * sizeof(int) / constants::MB); allocMemAsync(reinterpret_cast(&mTrackletsLUTDevice[layer]), ncls * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpyAsync(&mTrackletsLUTDeviceArray[layer], &mTrackletsLUTDevice[layer], sizeof(int*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); @@ -366,9 +366,9 @@ void TimeFrameGPU::createTrackletsLUTDevice(const int iteration, const } template -void TimeFrameGPU::createTrackletsBuffersArray(const int iteration) +void TimeFrameGPU::createTrackletsBuffersArray() { - if (!iteration) { + { GPUTimer timer("creating tracklet buffers array"); allocMem(reinterpret_cast(&mTrackletsDeviceArray), (NLayers - 1) * sizeof(Tracklet*), this->hasFrameworkAllocator()); } @@ -442,9 +442,9 @@ void TimeFrameGPU::loadCellsDevice() } template -void TimeFrameGPU::createCellsLUTDeviceArray(const int iteration) +void TimeFrameGPU::createCellsLUTDeviceArray() { - if (!iteration) { + { GPUTimer timer("creating cells LUTs array"); allocMem(reinterpret_cast(&mCellsLUTDeviceArray), (NLayers - 2) * sizeof(int*), this->hasFrameworkAllocator()); } @@ -461,9 +461,9 @@ void TimeFrameGPU::createCellsLUTDevice(const int layer) } template -void TimeFrameGPU::createCellsBuffersArray(const int iteration) +void TimeFrameGPU::createCellsBuffersArray() { - if (!iteration) { + { GPUTimer timer("creating cells buffers array"); allocMem(reinterpret_cast(&mCellsDeviceArray), (NLayers - 2) * sizeof(CellSeed*), this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpy(mCellsDeviceArray, mCellsDevice.data(), mCellsDevice.size() * sizeof(CellSeed*), cudaMemcpyHostToDevice)); @@ -646,12 +646,10 @@ void TimeFrameGPU::popMemoryStack(const int iteration) } template -void TimeFrameGPU::initialise(const int iteration, - const TrackingParameters& trkParam, - const int maxLayers) +void TimeFrameGPU::initialise(const TrackingParameters& trkParam, int maxLayers) { mGpuStreams.resize(NLayers); - o2::its::TimeFrame::initialise(iteration, trkParam, maxLayers, false); + o2::its::TimeFrame::initialise(trkParam, maxLayers); } template diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx index 8bb0bb5310b52..2d2ca5432cdf9 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx @@ -10,16 +10,11 @@ // or submit itself to any jurisdiction. /// -#include -#include #include -#include "DataFormatsITS/TrackITS.h" - -#include "ITStracking/TrackHelpers.h" #include "ITStrackingGPU/TrackerTraitsGPU.h" #include "ITStrackingGPU/TrackingKernels.h" -#include "ITStracking/Constants.h" +#include "ITStracking/Configuration.h" namespace o2::its { @@ -27,28 +22,33 @@ namespace o2::its template void TrackerTraitsGPU::initialiseTimeFrame(const int iteration) { - mTimeFrameGPU->initialise(iteration, this->mTrkParams[iteration], NLayers); - // on default stream - mTimeFrameGPU->loadVertices(iteration); - // TODO these tables can be put in persistent memory - mTimeFrameGPU->loadROFOverlapTable(iteration); // this can be put in constant memory actually - mTimeFrameGPU->loadROFVertexLookupTable(iteration); - // once the tables are in persistent memory just update the vertex one - // mTimeFrameGPU->updateROFVertexLookupTable(iteration); - mTimeFrameGPU->loadIndexTableUtils(iteration); - mTimeFrameGPU->loadROFCutMask(iteration); - // pinned on host - mTimeFrameGPU->createUsedClustersDeviceArray(iteration); - mTimeFrameGPU->createClustersDeviceArray(iteration); - mTimeFrameGPU->createUnsortedClustersDeviceArray(iteration); - mTimeFrameGPU->createClustersIndexTablesArray(iteration); - mTimeFrameGPU->createTrackingFrameInfoDeviceArray(iteration); - mTimeFrameGPU->createROFrameClustersDeviceArray(iteration); - // device array - mTimeFrameGPU->createTrackletsLUTDeviceArray(iteration); - mTimeFrameGPU->createTrackletsBuffersArray(iteration); - mTimeFrameGPU->createCellsBuffersArray(iteration); - mTimeFrameGPU->createCellsLUTDeviceArray(iteration); + mTimeFrameGPU->initialise(this->mTrkParams[iteration], NLayers); + + if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { + // on default stream + mTimeFrameGPU->loadVertices(); + // TODO these tables can be put in persistent memory + mTimeFrameGPU->loadROFOverlapTable(); // this can be put in constant memory actually + mTimeFrameGPU->loadROFVertexLookupTable(); + // once the tables are in persistent memory just update the vertex one + // mTimeFrameGPU->updateROFVertexLookupTable(); + mTimeFrameGPU->loadIndexTableUtils(); + // pinned on host + mTimeFrameGPU->createUsedClustersDeviceArray(); + mTimeFrameGPU->createClustersDeviceArray(); + mTimeFrameGPU->createUnsortedClustersDeviceArray(); + mTimeFrameGPU->createClustersIndexTablesArray(); + mTimeFrameGPU->createTrackingFrameInfoDeviceArray(); + mTimeFrameGPU->createROFrameClustersDeviceArray(); + // device array + mTimeFrameGPU->createTrackletsLUTDeviceArray(); + mTimeFrameGPU->createTrackletsBuffersArray(); + mTimeFrameGPU->createCellsBuffersArray(); + mTimeFrameGPU->createCellsLUTDeviceArray(); + } + if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass] || this->mTrkParams[iteration].PassFlags[IterationStep::UseUPCMask]) { + mTimeFrameGPU->loadROFCutMask(iteration); + } // push every create artefact on the stack mTimeFrameGPU->pushMemoryStack(iteration); } @@ -65,22 +65,26 @@ void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int i { // start by queuing loading needed of two last layers for (int iLayer{NLayers}; iLayer-- > NLayers - 2;) { - mTimeFrameGPU->createUsedClustersDevice(iteration, iLayer); - mTimeFrameGPU->loadClustersDevice(iteration, iLayer); - mTimeFrameGPU->loadClustersIndexTables(iteration, iLayer); - mTimeFrameGPU->loadROFrameClustersDevice(iteration, iLayer); + if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { + mTimeFrameGPU->createUsedClustersDevice(iLayer); + mTimeFrameGPU->loadClustersDevice(iLayer); + mTimeFrameGPU->loadClustersIndexTables(iLayer); + mTimeFrameGPU->loadROFrameClustersDevice(iLayer); + } mTimeFrameGPU->recordEvent(iLayer); } for (int iLayer{this->mTrkParams[iteration].TrackletsPerRoad()}; iLayer--;) { if (iLayer) { // queue loading data of next layer in parallel, this the copies are overlapping with computation kernels - mTimeFrameGPU->createUsedClustersDevice(iteration, iLayer - 1); - mTimeFrameGPU->loadClustersDevice(iteration, iLayer - 1); - mTimeFrameGPU->loadClustersIndexTables(iteration, iLayer - 1); - mTimeFrameGPU->loadROFrameClustersDevice(iteration, iLayer - 1); + if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { + mTimeFrameGPU->createUsedClustersDevice(iLayer - 1); + mTimeFrameGPU->loadClustersDevice(iLayer - 1); + mTimeFrameGPU->loadClustersIndexTables(iLayer - 1); + mTimeFrameGPU->loadROFrameClustersDevice(iLayer - 1); + } mTimeFrameGPU->recordEvent(iLayer - 1); } - mTimeFrameGPU->createTrackletsLUTDevice(iteration, iLayer); + mTimeFrameGPU->createTrackletsLUTDevice(this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass], iLayer); mTimeFrameGPU->waitEvent(iLayer, iLayer + 1); // wait stream until all data is available countTrackletsInROFsHandler(mTimeFrameGPU->getDeviceIndexTableUtils(), mTimeFrameGPU->getDeviceROFMaskTableView(), @@ -97,7 +101,7 @@ void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int i mTimeFrameGPU->getDeviceArrayClustersIndexTables(), mTimeFrameGPU->getDeviceArrayTrackletsLUT(), mTimeFrameGPU->getDeviceTrackletsLUTs(), - iteration, + this->mTrkParams[iteration].PassFlags[IterationStep::SelectUPCVertices], this->mTrkParams[iteration].NSigmaCut, mTimeFrameGPU->getPhiCuts(), this->mTrkParams[iteration].PVres, @@ -130,7 +134,7 @@ void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int i mTimeFrameGPU->getNTracklets(), mTimeFrameGPU->getDeviceArrayTrackletsLUT(), mTimeFrameGPU->getDeviceTrackletsLUTs(), - iteration, + this->mTrkParams[iteration].PassFlags[IterationStep::SelectUPCVertices], this->mTrkParams[iteration].NSigmaCut, mTimeFrameGPU->getPhiCuts(), this->mTrkParams[iteration].PVres, @@ -149,15 +153,19 @@ void TrackerTraitsGPU::computeLayerCells(const int iteration) { // start by queuing loading needed of three last layers for (int iLayer{NLayers}; iLayer-- > NLayers - 3;) { - mTimeFrameGPU->loadUnsortedClustersDevice(iteration, iLayer); - mTimeFrameGPU->loadTrackingFrameInfoDevice(iteration, iLayer); + if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { + mTimeFrameGPU->loadUnsortedClustersDevice(iLayer); + mTimeFrameGPU->loadTrackingFrameInfoDevice(iLayer); + } mTimeFrameGPU->recordEvent(iLayer); } for (int iLayer{this->mTrkParams[iteration].CellsPerRoad()}; iLayer--;) { if (iLayer) { - mTimeFrameGPU->loadUnsortedClustersDevice(iteration, iLayer - 1); - mTimeFrameGPU->loadTrackingFrameInfoDevice(iteration, iLayer - 1); + if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { + mTimeFrameGPU->loadUnsortedClustersDevice(iLayer - 1); + mTimeFrameGPU->loadTrackingFrameInfoDevice(iLayer - 1); + } mTimeFrameGPU->recordEvent(iLayer - 1); } diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu index 6d778f17dc932..a732327a64d15 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu @@ -322,7 +322,7 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( const int** indexTables, Tracklet** tracklets, int** trackletsLUT, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, const float phiCut, const float resolutionPV, @@ -383,7 +383,7 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( if (!vertexLUT.isVertexCompatible(layerIndex, pivotROF, primaryVertex)) { continue; } - if ((primaryVertex.isFlagSet(Vertex::Flags::UPCMode) && iteration != 3) || (iteration == 3 && !primaryVertex.isFlagSet(Vertex::Flags::UPCMode))) { + if (primaryVertex.isFlagSet(Vertex::Flags::UPCMode) != selectUPCVertices) { continue; } @@ -576,7 +576,7 @@ void countTrackletsInROFsHandler(const IndexTableUtils* utils, const int** clustersIndexTables, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, bounded_vector& phiCuts, const float resolutionPV, @@ -603,7 +603,7 @@ void countTrackletsInROFsHandler(const IndexTableUtils* utils, clustersIndexTables, nullptr, trackletsLUTs, - iteration, + selectUPCVertices, NSigmaCut, phiCuts[layer], resolutionPV, @@ -635,7 +635,7 @@ void computeTrackletsInROFsHandler(const IndexTableUtils* utils, gsl::span nTracklets, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, bounded_vector& phiCuts, const float resolutionPV, @@ -662,7 +662,7 @@ void computeTrackletsInROFsHandler(const IndexTableUtils* utils, clustersIndexTables, tracklets, trackletsLUTs, - iteration, + selectUPCVertices, NSigmaCut, phiCuts[layer], resolutionPV, @@ -1094,7 +1094,7 @@ template void countTrackletsInROFsHandler<7>(const IndexTableUtils<7>* utils, const int** clustersIndexTables, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, bounded_vector& phiCuts, const float resolutionPV, @@ -1124,7 +1124,7 @@ template void computeTrackletsInROFsHandler<7>(const IndexTableUtils<7>* utils, gsl::span nTracklets, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, bounded_vector& phiCuts, const float resolutionPV, @@ -1275,7 +1275,7 @@ template void countTrackletsInROFsHandler<11>(const IndexTableUtils<11>* utils, const int** clustersIndexTables, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, bounded_vector& phiCuts, const float resolutionPV, @@ -1305,7 +1305,7 @@ template void computeTrackletsInROFsHandler<11>(const IndexTableUtils<11>* utils gsl::span nTracklets, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, bounded_vector& phiCuts, const float resolutionPV, diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h index dbce5e0dc08a7..ce7b3e5a87630 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h @@ -23,12 +23,25 @@ #include #endif +#include "CommonUtils/EnumFlags.h" #include "DetectorsBase/Propagator.h" #include "ITStracking/Constants.h" namespace o2::its { +// Steering of dedicated steps in an iteration +enum class IterationStep : uint8_t { + FirstPass = 0, + RebuildClusterLUT, + UseUPCMask, + SelectUPCVertices, + ResetVertices, + SkipROFsAboveThreshold, + MarkVerticesAsUPC, +}; +using IterationSteps = o2::utils::EnumFlags; + struct TrackingParameters { int CellMinimumLevel() const noexcept { return MinTrackLength - constants::ClustersPerCell + 1; } int NeighboursPerRoad() const noexcept { return NLayers - 3; } @@ -36,6 +49,7 @@ struct TrackingParameters { int TrackletsPerRoad() const noexcept { return NLayers - 1; } std::string asString() const; + IterationSteps PassFlags{IterationStep::FirstPass, IterationStep::RebuildClusterLUT}; int NLayers = 7; std::vector AddTimeError = {0, 0, 0, 0, 0, 0, 0}; std::vector LayerZ = {16.333f + 1, 16.333f + 1, 16.333f + 1, 42.140f + 1, 42.140f + 1, 73.745f + 1, 73.745f + 1}; @@ -73,9 +87,7 @@ struct TrackingParameters { bool SaveTimeBenchmarks = false; bool DoUPCIteration = false; bool FataliseUponFailure = true; - - bool createArtefactLabels{false}; - + bool CreateArtefactLabels{false}; bool PrintMemory = false; // print allocator usage in epilog report size_t MaxMemory = std::numeric_limits::max(); bool DropTFUponFailure = false; @@ -84,6 +96,7 @@ struct TrackingParameters { struct VertexingParameters { std::string asString() const; + IterationSteps PassFlags{IterationStep::FirstPass, IterationStep::ResetVertices}; std::vector LayerZ = {16.333f + 1, 16.333f + 1, 16.333f + 1, 42.140f + 1, 42.140f + 1, 73.745f + 1, 73.745f + 1}; std::vector LayerRadii = {2.33959f, 3.14076f, 3.91924f, 19.6213f, 24.5597f, 34.388f, 39.3329f}; int vertPerRofThreshold = 0; // Maximum number of vertices per ROF to trigger second a round diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h index 300abb2a3b10d..b78540bddfabf 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h @@ -177,7 +177,7 @@ struct TimeFrame { auto& getCellsLabel(int layer) { return mCellLabels[layer]; } bool hasMCinformation() const { return mClusterLabels[0] != nullptr; } - void initialise(const int iteration, const TrackingParameters& trkParam, const int maxLayers = NLayers, bool resetVertices = true); + void initialise(const TrackingParameters& trkParam, const int maxLayers = NLayers); bool isClusterUsed(int layer, int clusterId) const { return mUsedClusters[layer][clusterId]; } void markUsedCluster(int layer, int clusterId) { mUsedClusters[layer][clusterId] = true; } diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h index 1c3c642429686..aa4592c63f404 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h @@ -18,7 +18,6 @@ #include -#include "DetectorsBase/Propagator.h" #include "ITStracking/Configuration.h" #include "ITStracking/IndexTableUtils.h" #include "ITStracking/TimeFrame.h" @@ -46,7 +45,7 @@ class TrackerTraits virtual ~TrackerTraits() = default; virtual void adoptTimeFrame(TimeFrame* tf) { mTimeFrame = tf; } - virtual void initialiseTimeFrame(const int iteration) { mTimeFrame->initialise(iteration, mTrkParams[iteration], mTrkParams[iteration].NLayers, false); } + virtual void initialiseTimeFrame(const int iteration) { mTimeFrame->initialise(mTrkParams[iteration], mTrkParams[iteration].NLayers); } virtual void computeLayerTracklets(const int iteration, int iVertex); virtual void computeLayerCells(const int iteration); diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h index ac4b99a0a8cd8..14c5d6a62e0ad 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h @@ -34,6 +34,7 @@ namespace o2::its { class ITSTrackingInterface { + public: static constexpr int NLayers{7}; using VertexerN = Vertexer; using VertexerTraitsN = VertexerTraits; @@ -41,7 +42,6 @@ class ITSTrackingInterface using TrackerTraitsN = TrackerTraits; using TimeFrameN = TimeFrame; - public: ITSTrackingInterface(bool isMC, bool doStag, int trgType, @@ -80,6 +80,8 @@ class ITSTrackingInterface TimeFrameN* mTimeFrame = nullptr; protected: + virtual void overrideParameters(std::vector& t, std::vector& v) {} + virtual void requestTopologyDictionary(framework::ProcessingContext& pc); virtual void loadROF(gsl::span& trackROFspan, gsl::span clusters, gsl::span::iterator& pattIt, @@ -98,7 +100,7 @@ class ITSTrackingInterface const o2::itsmft::TopologyDictionary* mDict = nullptr; std::unique_ptr mTracker = nullptr; std::unique_ptr mVertexer = nullptr; - const o2::dataformats::MeanVertexObject* mMeanVertex; + const o2::dataformats::MeanVertexObject* mMeanVertex{}; std::shared_ptr mMemoryPool; std::shared_ptr mTaskArena; }; diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h index 3230737a0f87c..daf8d708e1e23 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h @@ -53,14 +53,11 @@ class VertexerTraits VertexerTraits() = default; virtual ~VertexerTraits() = default; - GPUhd() static const int2 getPhiBins(float phi, float deltaPhi, const IndexTableUtilsN&); - GPUhd() const int2 getPhiBins(float phi, float deltaPhi) { return getPhiBins(phi, deltaPhi, mIndexTableUtils); } - // virtual vertexer interface - virtual void initialise(const TrackingParameters& trackingParams, const int iteration = 0); - virtual void computeTracklets(const int iteration = 0); - virtual void computeTrackletMatching(const int iteration = 0); - virtual void computeVertices(const int iteration = 0); + virtual void initialise(const TrackingParameters& trackingParams); + virtual void computeTracklets(const int iteration); + virtual void computeTrackletMatching(const int iteration); + virtual void computeVertices(const int iteration); virtual void adoptTimeFrame(TimeFrameN* tf) noexcept { mTimeFrame = tf; } virtual void updateVertexingParameters(const std::vector& vrtPar); @@ -115,19 +112,6 @@ class VertexerTraits std::shared_ptr mTaskArena; }; -template -inline void VertexerTraits::initialise(const TrackingParameters& trackingParams, const int iteration) -{ - mTimeFrame->initialise(0, trackingParams, 3, (bool)(!iteration)); // iteration for initialisation must be 0 for correctly resetting the frame, we need to pass the non-reset flag for vertices as well, tho. -} - -template -GPUhdi() const int2 VertexerTraits::getPhiBins(float phi, float dPhi, const IndexTableUtilsN& utils) -{ - return int2{utils.getPhiBinIndex(math_utils::getNormalizedPhi(phi - dPhi)), - utils.getPhiBinIndex(math_utils::getNormalizedPhi(phi + dPhi))}; -} - } // namespace its } // namespace o2 #endif diff --git a/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx b/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx index 49bf9b5b1887d..c425d467a8061 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx @@ -143,7 +143,7 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode // check if something was overridden via configurable params if (ip < constants::MaxIter) { if (tc.startLayerMask[ip] > 0) { - trackParams[2].StartLayerMask = tc.startLayerMask[ip]; + param.StartLayerMask = tc.startLayerMask[ip]; } if (tc.minTrackLgtIter[ip] > 0) { param.MinTrackLength = tc.minTrackLgtIter[ip]; @@ -174,6 +174,14 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode LOGP(fatal, "Unsupported ITS tracking mode {} ", toString(mode)); } + for (auto& param : trackParams) { + param.PassFlags.reset(); + } + trackParams[0].PassFlags.set(IterationStep::FirstPass, IterationStep::RebuildClusterLUT); + if (trackParams.size() > 3 && tc.doUPCIteration) { + trackParams[3].PassFlags.set(IterationStep::UseUPCMask, IterationStep::RebuildClusterLUT, IterationStep::SelectUPCVertices); + } + float bFactor = std::abs(o2::base::Propagator::Instance()->getNominalBz()) / 5.0066791f; float bFactorTracklets = bFactor < 0.01f ? 1.f : bFactor; // for tracklets only @@ -188,7 +196,7 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode p.ReseedIfShorter = tc.reseedIfShorter; p.RepeatRefitOut = tc.repeatRefitOut; p.ShiftRefToCluster = tc.shiftRefToCluster; - p.createArtefactLabels = tc.createArtefactLabels; + p.CreateArtefactLabels = tc.createArtefactLabels; p.PrintMemory = tc.printMemory; p.MaxMemory = tc.maxMemory; @@ -241,6 +249,12 @@ std::vector TrackingMode::getVertexingParameters(TrackingMo { const auto& vc = o2::its::VertexerParamConfig::Instance(); std::vector vertParams(2); // The number of actual iterations will be set as a configKeyVal to allow for pp/PbPb choice + for (auto& param : vertParams) { + param.PassFlags.reset(); + } + vertParams[0].PassFlags.set(IterationStep::FirstPass, IterationStep::ResetVertices); + vertParams[1].PassFlags.set(IterationStep::SkipROFsAboveThreshold, IterationStep::MarkVerticesAsUPC); + // global parameters set for every iteration for (auto& p : vertParams) { p.vertPerRofThreshold = vc.vertPerRofThreshold; diff --git a/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx b/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx index cafddfcc41a76..fc99bf0f35403 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx @@ -241,14 +241,14 @@ void TimeFrame::prepareClusters(const TrackingParameters& trkParam, con } template -void TimeFrame::initialise(const int iteration, const TrackingParameters& trkParam, const int maxLayers, bool resetVertices) +void TimeFrame::initialise(const TrackingParameters& trkParam, const int maxLayers) { - if (iteration == 0) { + if (trkParam.PassFlags[IterationStep::FirstPass]) { deepVectorClear(mTracks); deepVectorClear(mTracksLabel); deepVectorClear(mLines); deepVectorClear(mLinesLabels); - if (resetVertices) { + if (trkParam.PassFlags[IterationStep::ResetVertices]) { deepVectorClear(mPrimaryVertices); deepVectorClear(mPrimaryVerticesLabels); } @@ -293,7 +293,7 @@ void TimeFrame::initialise(const int iteration, const TrackingParameter for (auto& v : mNTrackletsPerROF) { v = bounded_vector(getNrof(1) + 1, 0, mMemoryPool.get()); } - if (iteration == 0 || iteration == 3) { + if (trkParam.PassFlags[IterationStep::RebuildClusterLUT]) { prepareClusters(trkParam, maxLayers); } mTotalTracklets = {0, 0}; diff --git a/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx b/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx index 382f2314b2e6a..f17d961fc7bb7 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx @@ -68,7 +68,7 @@ float Tracker::clustersToTracks(const LogFunc& logger, const LogFunc& e try { for (iteration = 0; iteration < (int)mTrkParams.size(); ++iteration) { mMemoryPool->setMaxMemory(mTrkParams[iteration].MaxMemory); - if (iteration == 3 && mTrkParams[0].DoUPCIteration) { + if (mTrkParams[iteration].PassFlags[IterationStep::UseUPCMask]) { mTimeFrame->useUPCMask(); } float timeFrame{0.}, timeTracklets{0.}, timeCells{0.}, timeNeighbours{0.}, timeRoads{0.}; diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx index dc2d6e8889973..19cae4b70f158 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx @@ -101,7 +101,7 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer if (!mTimeFrame->getROFVertexLookupTableView().isVertexCompatible(iLayer, pivotROF, pv)) { continue; } - if ((pv.isFlagSet(Vertex::Flags::UPCMode) && iteration != 3) || (iteration == 3 && !pv.isFlagSet(Vertex::Flags::UPCMode))) { + if (pv.isFlagSet(Vertex::Flags::UPCMode) != mTrkParams[iteration].PassFlags[IterationStep::SelectUPCVertices]) { continue; } const float resolution = o2::gpu::CAMath::Sqrt(math_utils::Sq(mTimeFrame->getPositionResolution(iLayer)) + math_utils::Sq(mTrkParams[iteration].PVres) / float(pv.getNContributors())); @@ -224,7 +224,7 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer }); /// Create tracklets labels - if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].createArtefactLabels) { + if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].CreateArtefactLabels) { tbb::parallel_for(0, mTrkParams[iteration].TrackletsPerRoad(), [&](const int iLayer) { for (auto& trk : mTimeFrame->getTracklets()[iLayer]) { MCCompLabel label; @@ -256,7 +256,7 @@ void TrackerTraits::computeLayerCells(const int iteration) if (iLayer > 0) { deepVectorClear(mTimeFrame->getCellsLookupTable()[iLayer - 1]); } - if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].createArtefactLabels) { + if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].CreateArtefactLabels) { deepVectorClear(mTimeFrame->getCellsLabel(iLayer)); } } @@ -390,7 +390,7 @@ void TrackerTraits::computeLayerCells(const int iteration) std::copy_n(perTrackletCount.begin(), currentLayerTrackletsNum + 1, lut.begin()); } - if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].createArtefactLabels) { + if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].CreateArtefactLabels) { auto& labels = mTimeFrame->getCellsLabel(iLayer); labels.reserve(layerCells.size()); for (const auto& cell : layerCells) { diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx index a48f23c5eb8f1..f745d671419af 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx @@ -50,6 +50,7 @@ void ITSTrackingInterface::initialise() } auto trackParams = TrackingMode::getTrackingParameters(mMode); auto vertParams = TrackingMode::getVertexingParameters(mMode); + overrideParameters(trackParams, vertParams); LOGP(info, "Initializing tracker in {} phase reconstruction with {} passes for tracking and {}/{} for vertexing", TrackingMode::toString(mMode), trackParams.size(), o2::its::VertexerParamConfig::Instance().nIterations, vertParams.size()); mTracker->setParameters(trackParams); mVertexer->setParameters(vertParams); @@ -372,7 +373,7 @@ void ITSTrackingInterface::updateTimeDependentParams(framework::ProcessingContex } if (!initOnceDone) { // this params need to be queried only once initOnceDone = true; - pc.inputs().get("itscldict"); // just to trigger the finaliseCCDB + requestTopologyDictionary(pc); pc.inputs().get*>("itsalppar"); if (pc.inputs().getPos("itsTGeo") >= 0) { pc.inputs().get("itsTGeo"); @@ -485,6 +486,11 @@ void ITSTrackingInterface::setTraitsFromProvider(VertexerTraitsN* vertexerTraits mVertexer->setMemoryPool(mMemoryPool); } +void ITSTrackingInterface::requestTopologyDictionary(framework::ProcessingContext& pc) +{ + pc.inputs().get("itscldict"); // just to trigger the finaliseCCDB +} + void ITSTrackingInterface::loadROF(gsl::span& trackROFspan, gsl::span clusters, gsl::span::iterator& pattIt, diff --git a/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx b/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx index b4585929d2dcf..ba37275f87688 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx @@ -61,9 +61,14 @@ float Vertexer::clustersToVertices(LogFunc logger) mMemoryPool->setMaxMemory(mVertParams[iteration].MaxMemory); unsigned int nTracklets01{0}, nTracklets12{0}; logger(fmt::format("=== ITS {} Seeding vertexer iteration {} summary:", mTraits->getName(), iteration)); - trkPars.PhiBins = mTraits->getVertexingParameters()[0].PhiBins; - trkPars.ZBins = mTraits->getVertexingParameters()[0].ZBins; - auto timeInitIteration = evaluateTask(&Vertexer::initialiseVertexer, StateNames[mCurStep = Init], iteration, evalLog, trkPars, iteration); + const auto& currentVtxPars = mTraits->getVertexingParameters()[iteration]; + trkPars.PhiBins = currentVtxPars.PhiBins; + trkPars.ZBins = currentVtxPars.ZBins; + trkPars.LayerZ = currentVtxPars.LayerZ; + trkPars.LayerRadii = currentVtxPars.LayerRadii; + trkPars.PassFlags = mVertParams[iteration].PassFlags; + trkPars.PassFlags.set(IterationStep::FirstPass, IterationStep::RebuildClusterLUT); + auto timeInitIteration = evaluateTask(&Vertexer::initialiseVertexer, StateNames[mCurStep = Init], iteration, evalLog, trkPars); auto timeTrackletIteration = evaluateTask(&Vertexer::findTracklets, StateNames[mCurStep = Trackleting], iteration, evalLog, iteration); nTracklets01 = mTimeFrame->getTotalTrackletsTF(0); nTracklets12 = mTimeFrame->getTotalTrackletsTF(1); diff --git a/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx b/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx index 00674b715b97d..237e99e57e0da 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx @@ -156,6 +156,12 @@ void trackletSelectionKernelHost( } } // namespace +template +void VertexerTraits::initialise(const TrackingParameters& trackingParams) +{ + mTimeFrame->initialise(trackingParams, 3); +} + template void VertexerTraits::updateVertexingParameters(const std::vector& vrtPar) { @@ -528,7 +534,7 @@ void VertexerTraits::computeVertices(const int iteration) cluster.getRMS2(), (ushort)cluster.getSize(), cluster.getAvgDistance2()}; - if (iteration) { + if (mVrtParams[iteration].PassFlags[IterationStep::MarkVerticesAsUPC]) { vertex.setFlags(Vertex::UPCMode); } vertex.setTimeStamp(cluster.getTimeStamp()); @@ -629,7 +635,8 @@ void VertexerTraits::setNThreads(int n, std::shared_ptr bool VertexerTraits::skipROF(int iteration, int rof) const { - return iteration && (int)mTimeFrame->getROFVertexLookupTableView().getVertices(1, rof).getEntries() > mVrtParams[iteration].vertPerRofThreshold; + return mVrtParams[iteration].PassFlags[IterationStep::SkipROFsAboveThreshold] && + (int)mTimeFrame->getROFVertexLookupTableView().getVertices(1, rof).getEntries() > mVrtParams[iteration].vertPerRofThreshold; } template class VertexerTraits<7>; diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx index c9d793a3ec78f..cb4cc3897ae9e 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx @@ -177,7 +177,7 @@ std::vector TrackerDPL::createTrackingParamsFromCon // params.UseTrackFollowerMix = paramConfig["UseTrackFollowerMix"].get(); // } if (paramConfig.contains("createArtefactLabels")) { - params.createArtefactLabels = paramConfig["createArtefactLabels"].get(); + params.CreateArtefactLabels = paramConfig["createArtefactLabels"].get(); } if (paramConfig.contains("PrintMemory")) { params.PrintMemory = paramConfig["PrintMemory"].get(); @@ -300,7 +300,7 @@ void TrackerDPL::run(ProcessingContext& pc) 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); + timeFrame.initialise(trackingParams[iter], 11); itsTrackerTraits.computeLayerTracklets(iter, -1); LOGP(info, "Number of tracklets in iteration {}: {}", iter, timeFrame.getNumberOfTracklets()); itsTrackerTraits.computeLayerCells(iter); From a204d4181b2ecbea705cec293ae80bbaad3eef4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuba=20G=C3=BCndem?= <48834043+tubagundem@users.noreply.github.com> Date: Tue, 12 May 2026 08:55:25 +0200 Subject: [PATCH 066/102] TPC_CMV: Improving the CMV workflows (#15360) * Implemented Vit's comments * Minor fixes * Added trigger to FLP workflow * Add helper for CMV files, improve draw macro, move tpc-cmv-trigger to test folder --- .../TPC/include/DataFormatsTPC/CMV.h | 8 +- Detectors/TPC/calibration/CMakeLists.txt | 4 +- .../include/TPCCalibration/CMVHelper.h | 52 ++++++++ Detectors/TPC/calibration/macro/drawCMV.C | 118 ++++++++--------- Detectors/TPC/calibration/src/CMVHelper.cxx | 98 ++++++++++++++ .../calibration/src/TPCCalibrationLinkDef.h | 1 + Detectors/TPC/workflow/CMakeLists.txt | 5 + .../include/TPCWorkflow/CMVToVectorSpec.h | 2 +- .../include/TPCWorkflow/TPCAggregateCMVSpec.h | 103 ++++++++------- .../TPCWorkflow/TPCDistributeCMVSpec.h | 76 ++++++----- .../include/TPCWorkflow/TPCFLPCMVSpec.h | 121 +++++++++++++----- .../TPC/workflow/src/CMVToVectorSpec.cxx | 18 +-- .../TPC/workflow/test/test_cmv-trigger.cxx | 85 ++++++++++++ 13 files changed, 488 insertions(+), 203 deletions(-) create mode 100644 Detectors/TPC/calibration/include/TPCCalibration/CMVHelper.h create mode 100644 Detectors/TPC/calibration/src/CMVHelper.cxx create mode 100644 Detectors/TPC/workflow/test/test_cmv-trigger.cxx diff --git a/DataFormats/Detectors/TPC/include/DataFormatsTPC/CMV.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/CMV.h index 109eff2654466..8195b3e39c689 100644 --- a/DataFormats/Detectors/TPC/include/DataFormatsTPC/CMV.h +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/CMV.h @@ -85,11 +85,13 @@ struct Data { return positive ? magnitude : -magnitude; } - // Encode from float: clamps magnitude to 15 bits, range ±255.992 + // Encode from float: truncates magnitude to 15 bits, range ±255.992 void setCMVFloat(float value) { const bool positive = (value >= 0.f); - const uint16_t magnitude = static_cast(std::abs(value) * 128.f + 0.5f) & 0x7FFF; + const uint16_t magnitude = static_cast( + std::lround(std::abs(value) * 128.f)) & + 0x7FFF; cmv = (positive ? 0x8000 : 0x0000) | magnitude; } }; @@ -119,4 +121,4 @@ struct Container { } // namespace o2::tpc::cmv -#endif \ No newline at end of file +#endif diff --git a/Detectors/TPC/calibration/CMakeLists.txt b/Detectors/TPC/calibration/CMakeLists.txt index 675f15e89258b..6aeb497c1cf23 100644 --- a/Detectors/TPC/calibration/CMakeLists.txt +++ b/Detectors/TPC/calibration/CMakeLists.txt @@ -60,6 +60,7 @@ o2_add_library(TPCCalibration src/PressureTemperatureHelper.cxx src/CMVContainer.cxx src/CorrectionMapsLoader.cxx + src/CMVHelper.cxx PUBLIC_LINK_LIBRARIES O2::DataFormatsTPC O2::TPCBaseRecSim O2::TPCReconstruction ROOT::Minuit Microsoft.GSL::GSL @@ -119,7 +120,8 @@ o2_target_root_dictionary(TPCCalibration include/TPCCalibration/CorrectdEdxDistortions.h include/TPCCalibration/PressureTemperatureHelper.h include/TPCCalibration/CMVContainer.h - include/TPCCalibration/CorrectionMapsLoader.h) + include/TPCCalibration/CorrectionMapsLoader.h + include/TPCCalibration/CMVHelper.h) o2_add_test_root_macro(macro/comparePedestalsAndNoise.C PUBLIC_LINK_LIBRARIES O2::TPCBaseRecSim diff --git a/Detectors/TPC/calibration/include/TPCCalibration/CMVHelper.h b/Detectors/TPC/calibration/include/TPCCalibration/CMVHelper.h new file mode 100644 index 0000000000000..d687c6872b8df --- /dev/null +++ b/Detectors/TPC/calibration/include/TPCCalibration/CMVHelper.h @@ -0,0 +1,52 @@ +// 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 CMVHelper.h +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief Helper utilities for reading CMV ROOT files + +#ifndef ALICEO2_TPC_CMVHELPER_H_ +#define ALICEO2_TPC_CMVHELPER_H_ + +#include + +#include "TFile.h" +#include "TTree.h" + +namespace o2::tpc +{ + +struct CMVPerTF; +struct CMVPerTFCompressed; + +struct CMVFileHandle { + TFile* file{nullptr}; + TTree* tree{nullptr}; + bool isCompressed{false}; + CMVPerTFCompressed* tfCompressed{nullptr}; + CMVPerTF* tfRaw{nullptr}; + CMVPerTF* tfDecoded{nullptr}; ///< scratch buffer used when decompressing + long firstTFInTree{-1}; ///< first global TF index from tree UserInfo ("firstTF"); -1 if absent + long lastTFInTree{-1}; ///< last global TF index from tree UserInfo ("lastTF"); -1 if absent + + /// Open path and set up branch addresses. Returns false on any error + bool open(const std::string& path); + + /// Load entry iEntry and return a pointer to the decoded CMVPerTF, or nullptr on error + const CMVPerTF* getEntry(long long iEntry); + + /// Release all resources + void close(); +}; + +} // namespace o2::tpc + +#endif // ALICEO2_TPC_CMVHELPER_H_ diff --git a/Detectors/TPC/calibration/macro/drawCMV.C b/Detectors/TPC/calibration/macro/drawCMV.C index 4f74db16cecd6..78e951fcfd676 100644 --- a/Detectors/TPC/calibration/macro/drawCMV.C +++ b/Detectors/TPC/calibration/macro/drawCMV.C @@ -11,19 +11,19 @@ #if !defined(__CLING__) || defined(__ROOTCLING__) #include -#include #include +#include #include -#include "TFile.h" -#include "TParameter.h" #include "TTree.h" #include "TH1F.h" #include "TH2F.h" #include "TCanvas.h" -#include "TPCCalibration/CMVContainer.h" #include "TPCBase/Utils.h" +#include "TPCCalibration/CMVContainer.h" +#include "TPCCalibration/CMVHelper.h" + #endif using namespace o2::tpc; @@ -38,39 +38,20 @@ TObjArray* drawCMV(std::string_view filename, std::string_view outDir, std::stri arrCanvases->SetName("CMV"); // open file - TFile f(filename.data(), "READ"); - if (f.IsZombie()) { + CMVFileHandle fh; + if (!fh.open(std::string(filename))) { fmt::print("ERROR: cannot open '{}'\n", filename); return arrCanvases; } fmt::print("Opened file: {}\n", filename); + fmt::print("Tree 'ccdb_object' found, entries: {}\n", fh.tree->GetEntries()); - // get TTree - TTree* tree = nullptr; - f.GetObject("ccdb_object", tree); - if (!tree) { - fmt::print("ERROR: TTree 'ccdb_object' not found\n"); - return arrCanvases; - } - fmt::print("Tree 'ccdb_object' found, entries: {}\n", tree->GetEntries()); - - // read metadata - long firstTF = -1, lastTF = -1; - if (auto* userInfo = tree->GetUserInfo()) { - for (int i = 0; i < userInfo->GetSize(); ++i) { - if (auto* p = dynamic_cast*>(userInfo->At(i))) { - if (std::string(p->GetName()) == "firstTF") - firstTF = p->GetVal(); - if (std::string(p->GetName()) == "lastTF") - lastTF = p->GetVal(); - } - } - } - fmt::print("firstTF: {}, lastTF: {}\n", firstTF, lastTF); + fmt::print("firstTF: {}, lastTF: {}\n", fh.firstTFInTree, fh.lastTFInTree); - const int nEntries = tree->GetEntries(); + const int nEntries = fh.tree->GetEntries(); if (nEntries == 0) { fmt::print("ERROR: no entries in tree\n"); + fh.close(); return arrCanvases; } @@ -80,61 +61,62 @@ TObjArray* drawCMV(std::string_view filename, std::string_view outDir, std::stri TH2F* h2d = new TH2F("hCMVvsTimeBin", ";Timebin (200 ns);Common Mode Values (ADC)", 100, 0, nTimeBins, 110, -100.5, 9.5); + h2d->SetDirectory(nullptr); h2d->SetStats(1); TH1F* h1d = new TH1F("hCMV", ";Common Mode Values (ADC);Counts", 110, -100.5, 9.5); + h1d->SetDirectory(nullptr); h1d->SetStats(1); - // auto-detect branch format: compressed or raw - const bool isCompressed = (tree->GetBranch("CMVPerTFCompressed") != nullptr); - const bool isRaw = (tree->GetBranch("CMVPerTF") != nullptr); - if (!isCompressed && !isRaw) { - fmt::print("ERROR: no recognised branch found (expected 'CMVPerTFCompressed' or 'CMVPerTF')\n"); - return arrCanvases; - } - fmt::print("Branch format: {}\n", isCompressed ? "CMVPerTFCompressed" : "CMVPerTF (raw)"); - - o2::tpc::CMVPerTFCompressed* tfCompressed = nullptr; - o2::tpc::CMVPerTF* tfRaw = nullptr; - CMVPerTF* tfDecoded = isCompressed ? new CMVPerTF() : nullptr; + TH1F* h1dCRU = new TH1F("hCRU", ";CRU;Counts", + 360, -0.5, 359.5); + h1dCRU->SetDirectory(nullptr); + h1dCRU->SetStats(1); + TH2F* h2dCRU = new TH2F("hCMVvsCRU", ";CRU;Common Mode Values (ADC)", + 360, -0.5, 359.5, + 110, -100.5, 9.5); + h2dCRU->SetDirectory(nullptr); + h2dCRU->SetStats(0); - if (isCompressed) { - tree->SetBranchAddress("CMVPerTFCompressed", &tfCompressed); - } else { - tree->SetBranchAddress("CMVPerTF", &tfRaw); - } + fmt::print("Branch format: {}\n", fh.isCompressed ? "CMVPerTFCompressed" : "CMVPerTF (raw)"); long firstOrbit = -1; long firstOrbitDPL = -1; + // Pre-allocate fill arrays once; x-values (timebins) are constant across entries and CRUs + const int fillsPerEntry = nCRUs * nTimeBins; + std::vector xArr(fillsPerEntry), yArr(fillsPerEntry), wArr(fillsPerEntry, 1.0), cruArr(fillsPerEntry); + for (int cru = 0; cru < nCRUs; ++cru) { + for (int tb = 0; tb < nTimeBins; ++tb) { + xArr[cru * nTimeBins + tb] = tb; + cruArr[cru * nTimeBins + tb] = cru; + } + } + for (int i = 0; i < nEntries; ++i) { - tree->GetEntry(i); - - // Decompress if needed; resolve to a unified CMVPerTF pointer - const CMVPerTF* tf = nullptr; - if (isCompressed) { - tfCompressed->decompress(tfDecoded); - tf = tfDecoded; - } else { - tf = tfRaw; + const CMVPerTF* tf = fh.getEntry(i); + if (!tf) { + continue; } firstOrbit = tf->firstOrbit; firstOrbitDPL = tf->firstOrbitDPL; - fmt::print("firstOrbit: {}, firstOrbitDPL: {}\n", firstOrbit, firstOrbitDPL); + + fmt::print("Entry {}: firstOrbit: {}, firstOrbitDPL: {}\n", i, firstOrbit, firstOrbitDPL); for (int cru = 0; cru < nCRUs; ++cru) { for (int tb = 0; tb < nTimeBins; ++tb) { - const float cmvValue = tf->getCMVFloat(cru, tb); - h2d->Fill(tb, cmvValue); - h1d->Fill(cmvValue); + yArr[cru * nTimeBins + tb] = tf->getCMVFloat(cru, tb); + // fmt::print("Entry {}: cru: {}, tb: {}, cmv: {}\n", i, cru, tb, tf->getCMVFloat(cru, tb)); } } + h2d->FillN(fillsPerEntry, xArr.data(), yArr.data(), wArr.data()); + h1d->FillN(fillsPerEntry, yArr.data(), wArr.data()); + h2dCRU->FillN(fillsPerEntry, cruArr.data(), yArr.data(), wArr.data()); + h1dCRU->FillN(fillsPerEntry, cruArr.data(), wArr.data()); } - delete tfDecoded; - tree->ResetBranchAddresses(); - delete tfCompressed; + fh.close(); // draw auto* c = new TCanvas("cCMVvsTimeBin", ""); @@ -149,10 +131,20 @@ TObjArray* drawCMV(std::string_view filename, std::string_view outDir, std::stri arrCanvases->Add(c1); + auto* c2 = new TCanvas("cCRUDistribution", ""); + h1dCRU->Draw(); + + arrCanvases->Add(c2); + + auto* c3 = new TCanvas("cCMVvsCRU", ""); + c3->SetLogz(); + h2dCRU->Draw("colz"); + + arrCanvases->Add(c3); + if (outDir.size()) { utils::saveCanvases(*arrCanvases, outDir, "", rootFileName); } - f.Close(); return arrCanvases; } diff --git a/Detectors/TPC/calibration/src/CMVHelper.cxx b/Detectors/TPC/calibration/src/CMVHelper.cxx new file mode 100644 index 0000000000000..abcbd977a9acb --- /dev/null +++ b/Detectors/TPC/calibration/src/CMVHelper.cxx @@ -0,0 +1,98 @@ +// 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 CMVHelper.cxx +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief Helper utilities for reading CMV ROOT files + +#include "TPCCalibration/CMVHelper.h" + +#include + +#include "TPCCalibration/CMVContainer.h" +#include "TParameter.h" + +namespace o2::tpc +{ + +bool CMVFileHandle::open(const std::string& path) +{ + file = TFile::Open(path.c_str()); + if (!file || file->IsZombie()) { + std::cerr << "CMVFileHandle: failed to open: " << path << "\n"; + return false; + } + file->GetObject("ccdb_object", tree); + if (!tree) { + std::cerr << "CMVFileHandle: TTree 'ccdb_object' not found in: " << path << "\n"; + close(); + return false; + } + + // Extract firstTF / lastTF from UserInfo if stored by the aggregation workflow + if (auto* ui = tree->GetUserInfo()) { + if (auto* p = dynamic_cast*>(ui->FindObject("firstTF"))) { + firstTFInTree = p->GetVal(); + } + if (auto* p = dynamic_cast*>(ui->FindObject("lastTF"))) { + lastTFInTree = p->GetVal(); + } + } + + isCompressed = (tree->GetBranch("CMVPerTFCompressed") != nullptr); + const bool isRaw = (tree->GetBranch("CMVPerTF") != nullptr); + if (!isCompressed && !isRaw) { + std::cerr << "CMVFileHandle: no recognised branch (CMVPerTFCompressed / CMVPerTF) in: " + << path << "\n"; + close(); + return false; + } + + if (isCompressed) { + tree->SetBranchAddress("CMVPerTFCompressed", &tfCompressed); + tfDecoded = new CMVPerTF(); + } else { + tree->SetBranchAddress("CMVPerTF", &tfRaw); + } + return true; +} + +const CMVPerTF* CMVFileHandle::getEntry(long long iEntry) +{ + tree->GetEntry(iEntry); + if (isCompressed) { + if (!tfCompressed) { + return nullptr; + } + tfCompressed->decompress(tfDecoded); + return tfDecoded; + } + return tfRaw; +} + +void CMVFileHandle::close() +{ + if (tree) { + tree->ResetBranchAddresses(); + tree = nullptr; + } + tfCompressed = nullptr; + tfRaw = nullptr; + delete tfDecoded; + tfDecoded = nullptr; + if (file) { + file->Close(); + delete file; + file = nullptr; + } +} + +} // namespace o2::tpc diff --git a/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h b/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h index 14d3d0a8ffb8e..847ae5ad7d788 100644 --- a/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h +++ b/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h @@ -124,6 +124,7 @@ #pragma link C++ class std::vector < o2::tpc::DigitAdd> + ; #pragma link C++ class o2::tpc::PressureTemperatureHelper + ; +#pragma link C++ struct o2::tpc::CMVFileHandle + ; #pragma link C++ class o2::tpc::CMVPerTF + ; #pragma link C++ class o2::tpc::CMVPerTFCompressed + ; diff --git a/Detectors/TPC/workflow/CMakeLists.txt b/Detectors/TPC/workflow/CMakeLists.txt index 37ac398db40ec..f64a223f683d8 100644 --- a/Detectors/TPC/workflow/CMakeLists.txt +++ b/Detectors/TPC/workflow/CMakeLists.txt @@ -309,4 +309,9 @@ o2_add_executable(cmv-aggregate SOURCES src/tpc-aggregate-cmv.cxx PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) +o2_add_executable(cmv-trigger + COMPONENT_NAME tpc + SOURCES test/test_cmv-trigger.cxx + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + add_subdirectory(readers) diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/CMVToVectorSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/CMVToVectorSpec.h index add37af5706e5..2f9209ee07da8 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/CMVToVectorSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/CMVToVectorSpec.h @@ -23,7 +23,7 @@ namespace o2::tpc /// create a processor spec /// convert CMV raw values to a vector in a CRU -o2::framework::DataProcessorSpec getCMVToVectorSpec(const std::string inputSpec, std::vector const& crus); +o2::framework::DataProcessorSpec getCMVToVectorSpec(std::string const& inputSpec, std::vector const& crus); } // end namespace o2::tpc diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h index b46f2169f06c9..3383da527cccf 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h @@ -49,9 +49,6 @@ #include "CommonUtils/StringUtils.h" #include "DetectorsCommonDataFormats/FileMetaData.h" -using namespace o2::framework; -using o2::header::gDataOriginTPC; - namespace o2::tpc { @@ -114,7 +111,7 @@ class TPCAggregateCMVDevice : public o2::framework::Task initIntervalTree(); } - void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) final { o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj); } @@ -136,7 +133,7 @@ class TPCAggregateCMVDevice : public o2::framework::Task } if (mSetDataTakingCont) { - mDataTakingContext = pc.services().get(); + mDataTakingContext = pc.services().get(); mSetDataTakingCont = false; } @@ -147,7 +144,7 @@ class TPCAggregateCMVDevice : public o2::framework::Task const auto currTF = processing_helpers::getCurrentTF(pc); if (mTFFirst == -1) { - for (auto& ref : InputRecordWalker(pc.inputs(), mFirstTFFilter)) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFirstTFFilter)) { mTFFirst = pc.inputs().get(ref); mIntervalFirstTF = mTFFirst; mHasIntervalFirstTF = true; @@ -203,7 +200,7 @@ class TPCAggregateCMVDevice : public o2::framework::Task // Capture orbit info first so setTimestampCCDB can use the measured stride if (!mOrbitInfoSeen[relTF]) { // all CRUs within a batch carry identical timing, so the first one is sufficient - for (auto& ref : InputRecordWalker(pc.inputs(), mOrbitFilter)) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mOrbitFilter)) { mOrbitInfo[relTF] = pc.inputs().get(ref); const auto batchFirstOrbit = static_cast(mOrbitInfo[relTF] >> 32); // TimingInfo.firstTForbit is the orbit of the last real TF in the batch (the TF that triggered the FLP to send). @@ -222,8 +219,8 @@ class TPCAggregateCMVDevice : public o2::framework::Task setTimestampCCDB(relTF, mOrbitStep[relTF], pc); } - for (auto& ref : InputRecordWalker(pc.inputs(), mFilter)) { - auto const* hdr = DataRefUtils::getHeader(ref); + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); const unsigned int cru = hdr->subSpecification; if (!(std::binary_search(mCRUs.begin(), mCRUs.end(), cru))) { LOGP(debug, "Received CMV data from CRU {} which is not part of this aggregate lane", cru); @@ -233,7 +230,7 @@ class TPCAggregateCMVDevice : public o2::framework::Task continue; } - auto cmvVec = pc.inputs().get>(ref); + auto cmvVec = pc.inputs().get>(ref); mRawCMVs[relTF][cru] = std::vector(cmvVec.begin(), cmvVec.end()); mProcessedCRUs[relTF][cru] = true; ++mProcessedCRU[relTF]; @@ -257,7 +254,7 @@ class TPCAggregateCMVDevice : public o2::framework::Task materializeBufferedTFs(true); materializeEOSBuffer(); sendOutput(ec.outputs()); - ec.services().get().readyToQuit(QuitRequest::Me); + ec.services().get().readyToQuit(o2::framework::QuitRequest::Me); } static constexpr header::DataDescription getDataDescriptionCCDBCMV() { return header::DataDescription{"TPC_CMV"}; } @@ -314,18 +311,18 @@ class TPCAggregateCMVDevice : public o2::framework::Task std::unique_ptr mIntervalTree{}; ///< in-memory TTree accumulating one entry per real TF; serialised to CCDB/disk at interval end CMVPerTF mCurrentTF{}; ///< staging object written to the TTree branch for the uncompressed path CMVPerTFCompressed mCurrentCompressedTF{}; ///< staging object written to the TTree branch when any compression flags are set - const std::vector mFilter{ + const std::vector mFilter{ {"cmvagg", - ConcreteDataTypeMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(mLaneId)}, - Lifetime::Sporadic}}; - const std::vector mOrbitFilter{ + o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(mLaneId)}, + o2::framework::Lifetime::Sporadic}}; + const std::vector mOrbitFilter{ {"cmvorbit", - ConcreteDataMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(mLaneId), header::DataHeader::SubSpecificationType{static_cast(mLaneId)}}, - Lifetime::Sporadic}}; - const std::vector mFirstTFFilter{ + o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(mLaneId), header::DataHeader::SubSpecificationType{static_cast(mLaneId)}}, + o2::framework::Lifetime::Sporadic}}; + const std::vector mFirstTFFilter{ {"firstTF", - ConcreteDataMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{static_cast(mLaneId)}}, - Lifetime::Sporadic}}; + o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{static_cast(mLaneId)}}, + o2::framework::Lifetime::Sporadic}}; uint8_t buildCompressionFlags() const { @@ -360,7 +357,7 @@ class TPCAggregateCMVDevice : public o2::framework::Task void collectEOSInputs(o2::framework::ProcessingContext& pc) { if (mEOSFirstOrbit == 0) { - for (auto& ref : InputRecordWalker(pc.inputs(), mOrbitFilter)) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mOrbitFilter)) { const auto orbitBC = pc.inputs().get(ref); mEOSFirstOrbit = static_cast(orbitBC >> 32); mEOSFirstBC = static_cast(orbitBC & 0xFFFFu); @@ -368,13 +365,13 @@ class TPCAggregateCMVDevice : public o2::framework::Task } } - for (auto& ref : InputRecordWalker(pc.inputs(), mFilter)) { - auto const* hdr = DataRefUtils::getHeader(ref); + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); const unsigned int cru = hdr->subSpecification; if (!(std::binary_search(mCRUs.begin(), mCRUs.end(), cru))) { continue; } - auto cmvVec = pc.inputs().get>(ref); + auto cmvVec = pc.inputs().get>(ref); auto& buffer = mEOSRawCMVs[cru]; buffer.insert(buffer.end(), cmvVec.begin(), cmvVec.end()); } @@ -548,7 +545,7 @@ class TPCAggregateCMVDevice : public o2::framework::Task } } - void sendOutput(DataAllocator& output) + void sendOutput(o2::framework::DataAllocator& output) { using timer = std::chrono::high_resolution_clock; @@ -619,8 +616,8 @@ class TPCAggregateCMVDevice : public o2::framework::Task } LOGP(info, "Sending object {} / {} of size {} bytes, valid for {} : {}", ccdbInfoCMV.getPath(), ccdbInfoCMV.getFileName(), image->size(), ccdbInfoCMV.getStartValidityTimestamp(), ccdbInfoCMV.getEndValidityTimestamp()); - output.snapshot(Output{o2::calibration::Utils::gDataOriginCDBPayload, getDataDescriptionCCDBCMV(), 0}, *image); - output.snapshot(Output{o2::calibration::Utils::gDataOriginCDBWrapper, getDataDescriptionCCDBCMV(), 0}, ccdbInfoCMV); + output.snapshot(o2::framework::Output{o2::calibration::Utils::gDataOriginCDBPayload, getDataDescriptionCCDBCMV(), 0}, *image); + output.snapshot(o2::framework::Output{o2::calibration::Utils::gDataOriginCDBWrapper, getDataDescriptionCCDBCMV(), 0}, ccdbInfoCMV); auto stop = timer::now(); std::chrono::duration elapsed = stop - start; @@ -666,25 +663,25 @@ class TPCAggregateCMVDevice : public o2::framework::Task /// Build a DataProcessorSpec for one aggregate lane /// Each lane receives CMV data from one distribute output lane (matched by lane index) and expects the full CRU list — the distribute stage already routes per-CRU data to the correct lane -inline DataProcessorSpec getTPCAggregateCMVSpec(const int lane, - const std::vector& crus, - const unsigned int timeframes, - const bool sendCCDB, - const bool usePreciseTimestamp, - const int nTFsBuffer = 1) +inline o2::framework::DataProcessorSpec getTPCAggregateCMVSpec(const int lane, + const std::vector& crus, + const unsigned int timeframes, + const bool sendCCDB, + const bool usePreciseTimestamp, + const int nTFsBuffer = 1) { - std::vector outputSpecs; + std::vector outputSpecs; if (sendCCDB) { - outputSpecs.emplace_back(ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBPayload, TPCAggregateCMVDevice::getDataDescriptionCCDBCMV()}, Lifetime::Sporadic); - outputSpecs.emplace_back(ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBWrapper, TPCAggregateCMVDevice::getDataDescriptionCCDBCMV()}, Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBPayload, TPCAggregateCMVDevice::getDataDescriptionCCDBCMV()}, o2::framework::Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBWrapper, TPCAggregateCMVDevice::getDataDescriptionCCDBCMV()}, o2::framework::Lifetime::Sporadic); } - std::vector inputSpecs; - inputSpecs.emplace_back(InputSpec{"cmvagg", ConcreteDataTypeMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(lane)}, Lifetime::Sporadic}); - inputSpecs.emplace_back(InputSpec{"cmvorbit", gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(lane), header::DataHeader::SubSpecificationType{static_cast(lane)}, Lifetime::Sporadic}); - inputSpecs.emplace_back(InputSpec{"firstTF", gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{static_cast(lane)}, Lifetime::Sporadic}); + std::vector inputSpecs; + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvagg", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(lane)}, o2::framework::Lifetime::Sporadic}); + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvorbit", o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(lane), header::DataHeader::SubSpecificationType{static_cast(lane)}, o2::framework::Lifetime::Sporadic}); + inputSpecs.emplace_back(o2::framework::InputSpec{"firstTF", o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{static_cast(lane)}, o2::framework::Lifetime::Sporadic}); if (usePreciseTimestamp) { - inputSpecs.emplace_back(InputSpec{"orbitreset", gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{static_cast(lane)}, Lifetime::Sporadic}); + inputSpecs.emplace_back(o2::framework::InputSpec{"orbitreset", o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{static_cast(lane)}, o2::framework::Lifetime::Sporadic}); } // Request GRPECS from CCDB so that GRPGeomHelper::getNHBFPerTF() is valid in this (separate) process @@ -696,21 +693,21 @@ inline DataProcessorSpec getTPCAggregateCMVSpec(const int lane, o2::base::GRPGeomRequest::None, // geometry inputSpecs); - DataProcessorSpec spec{ + o2::framework::DataProcessorSpec spec{ fmt::format("tpc-aggregate-cmv-{:02}", lane).data(), inputSpecs, outputSpecs, - AlgorithmSpec{adaptFromTask(lane, crus, timeframes, sendCCDB, usePreciseTimestamp, nTFsBuffer, ccdbRequest)}, - Options{{"output-dir", VariantType::String, "/dev/null", {"CMV output directory, must exist (if not /dev/null)"}}, - {"meta-output-dir", VariantType::String, "/dev/null", {"calibration metadata output directory, must exist (if not /dev/null)"}}, - {"nthreads-compression", VariantType::Int, 1, {"Number of threads used for CMV per timeframe preprocessing and compression"}}, - {"use-sparse", VariantType::Bool, false, {"Sparse encoding (skip zero time bins). Alone: raw uint16 values. With --use-compression-varint: varint exact values. With --use-compression-huffman: Huffman exact values"}}, - {"use-compression-varint", VariantType::Bool, false, {"Delta+zigzag+varint compression (all values). Combined with --use-sparse: sparse positions + varint encoded exact CMV values"}}, - {"use-compression-huffman", VariantType::Bool, false, {"Huffman encoding. Combined with --use-sparse: sparse positions + Huffman-encoded exact CMV values"}}, - {"cmv-zero-threshold", VariantType::Float, 0.f, {"Zero out CMV values whose float magnitude is below this threshold after optional integer rounding and before compression; 0 disables"}}, - {"cmv-round-integers-threshold", VariantType::Int, 0, {"Round values to nearest integer ADC for |v| <= N ADC before compression; 0 disables"}}, - {"cmv-dynamic-precision-mean", VariantType::Float, 1.f, {"Gaussian centre in |CMV| ADC where the strongest fractional bit trimming is applied"}}, - {"cmv-dynamic-precision-sigma", VariantType::Float, 0.f, {"Gaussian width in ADC for smooth CMV fractional bit trimming; 0 disables"}}}}; + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(lane, crus, timeframes, sendCCDB, usePreciseTimestamp, nTFsBuffer, ccdbRequest)}, + o2::framework::Options{{"output-dir", o2::framework::VariantType::String, "/dev/null", {"CMV output directory, must exist (if not /dev/null)"}}, + {"meta-output-dir", o2::framework::VariantType::String, "/dev/null", {"calibration metadata output directory, must exist (if not /dev/null)"}}, + {"nthreads-compression", o2::framework::VariantType::Int, 1, {"Number of threads used for CMV per timeframe preprocessing and compression"}}, + {"use-sparse", o2::framework::VariantType::Bool, false, {"Sparse encoding (skip zero time bins). Alone: raw uint16 values. With --use-compression-varint: varint exact values. With --use-compression-huffman: Huffman exact values"}}, + {"use-compression-varint", o2::framework::VariantType::Bool, false, {"Delta+zigzag+varint compression (all values). Combined with --use-sparse: sparse positions + varint encoded exact CMV values"}}, + {"use-compression-huffman", o2::framework::VariantType::Bool, false, {"Huffman encoding. Combined with --use-sparse: sparse positions + Huffman-encoded exact CMV values"}}, + {"cmv-zero-threshold", o2::framework::VariantType::Float, 0.f, {"Zero out CMV values whose float magnitude is below this threshold after optional integer rounding and before compression; 0 disables"}}, + {"cmv-round-integers-threshold", o2::framework::VariantType::Int, 0, {"Round values to nearest integer ADC for |v| <= N ADC before compression; 0 disables"}}, + {"cmv-dynamic-precision-mean", o2::framework::VariantType::Float, 1.f, {"Gaussian centre in |CMV| ADC where the strongest fractional bit trimming is applied"}}, + {"cmv-dynamic-precision-sigma", o2::framework::VariantType::Float, 0.f, {"Gaussian width in ADC for smooth CMV fractional bit trimming; 0 disables"}}}}; spec.rank = lane; return spec; } diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h index f3373070ab7bb..af576b2f30a5b 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h @@ -36,10 +36,6 @@ #include "DetectorsBase/GRPGeomHelper.h" #include "CommonDataFormat/Pair.h" -using namespace o2::framework; -using o2::header::gDataOriginTPC; -using namespace o2::tpc; - namespace o2::tpc { @@ -78,8 +74,8 @@ class TPCDistributeCMVSpec : public o2::framework::Task } } - mFilter.emplace_back(InputSpec{"cmvsgroup", ConcreteDataTypeMatcher{gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup()}, Lifetime::Sporadic}); - mOrbitFilter.emplace_back(InputSpec{"cmvorbit", ConcreteDataTypeMatcher{gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo()}, Lifetime::Sporadic}); + mFilter.emplace_back(o2::framework::InputSpec{"cmvsgroup", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup()}, o2::framework::Lifetime::Sporadic}); + mOrbitFilter.emplace_back(o2::framework::InputSpec{"cmvorbit", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo()}, o2::framework::Lifetime::Sporadic}); } void init(o2::framework::InitContext& ic) final @@ -97,13 +93,13 @@ class TPCDistributeCMVSpec : public o2::framework::Task } } - void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) final { o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj); - if (matcher == ConcreteDataMatcher("CTP", "ORBITRESET", 0)) { + if (matcher == o2::framework::ConcreteDataMatcher("CTP", "ORBITRESET", 0)) { LOGP(debug, "Updating ORBITRESET"); std::fill(mSendCCDBOutputOrbitReset.begin(), mSendCCDBOutputOrbitReset.end(), true); - } else if (matcher == ConcreteDataMatcher("GLO", "GRPECS", 0)) { + } else if (matcher == o2::framework::ConcreteDataMatcher("GLO", "GRPECS", 0)) { // check if received object is valid if (o2::base::GRPGeomHelper::instance().getGRPECS()->getRun() != 0) { LOGP(debug, "Updating GRPECS"); @@ -175,18 +171,18 @@ class TPCDistributeCMVSpec : public o2::framework::Task if (mSendOutputStartInfo[currentBuffer]) { mSendOutputStartInfo[currentBuffer] = false; - pc.outputs().snapshot(Output{gDataOriginTPC, getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{currentOutLane}}, mTFStart[currentBuffer]); + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{currentOutLane}}, mTFStart[currentBuffer]); } if (mSendCCDBOutputOrbitReset[currentOutLane] && mSendCCDBOutputGRPECS[currentOutLane]) { mSendCCDBOutputOrbitReset[currentOutLane] = false; mSendCCDBOutputGRPECS[currentOutLane] = false; - pc.outputs().snapshot(Output{gDataOriginTPC, getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{currentOutLane}}, dataformats::Pair{o2::base::GRPGeomHelper::instance().getOrbitResetTimeMS(), o2::base::GRPGeomHelper::instance().getNHBFPerTF()}); + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{currentOutLane}}, dataformats::Pair{o2::base::GRPGeomHelper::instance().getOrbitResetTimeMS(), o2::base::GRPGeomHelper::instance().getNHBFPerTF()}); } forwardOrbitInfo(pc, currentBuffer, relTF, currentOutLane); - for (auto& ref : InputRecordWalker(pc.inputs(), mFilter)) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { auto const* tpcCRUHeader = o2::framework::DataRefUtils::getHeader(ref); const unsigned int cru = tpcCRUHeader->subSpecification >> 7; @@ -204,7 +200,7 @@ class TPCDistributeCMVSpec : public o2::framework::Task // to keep track of processed CRUs mProcessedCRUs[currentBuffer][relTF][cru] = true; - sendOutput(pc, currentOutLane, cru, pc.inputs().get>(ref)); + sendOutput(pc, currentOutLane, cru, pc.inputs().get>(ref)); } LOGP(detail, "Number of received CRUs for current TF: {} Needed a total number of processed CRUs of: {} Current TF: {}", mProcessedCRU[currentBuffer][relTF], mCRUs.size(), tf); @@ -223,7 +219,7 @@ class TPCDistributeCMVSpec : public o2::framework::Task } } - void endOfStream(o2::framework::EndOfStreamContext& ec) final { ec.services().get().readyToQuit(QuitRequest::Me); } + void endOfStream(o2::framework::EndOfStreamContext& ec) final { ec.services().get().readyToQuit(o2::framework::QuitRequest::Me); } /// Return data description for aggregated CMVs for a given lane static header::DataDescription getDataDescriptionCMV(const unsigned int lane) @@ -267,8 +263,8 @@ class TPCDistributeCMVSpec : public o2::framework::Task std::array mStartNTFsDataDrop{0}; ///< first relative TF index to check for missing data in each buffer long mProcessedTotalData{0}; ///< call counter used to throttle checkIntervalsForMissingData checks int mCheckEveryNData{1}; ///< check for missing data every N run() calls (0 → default = mTimeFrames/2) - std::vector mFilter{}; ///< filter for looping over CMVGROUP input data from FLPs - std::vector mOrbitFilter{}; ///< filter for CMVORBITINFO input from FLPs + std::vector mFilter{}; ///< filter for looping over CMVGROUP input data from FLPs + std::vector mOrbitFilter{}; ///< filter for CMVORBITINFO input from FLPs std::vector mDataDescrOut{}; ///< per-output-lane CMV data descriptions (CMVAGG0, CMVAGG1, …) std::vector mOrbitDescrOut{}; ///< per-output-lane orbit-info data descriptions (CMVORB0, CMVORB1, …) std::array, 2> mOrbitInfoForwarded{}; ///< tracks whether orbit/BC has been forwarded to the aggregate lane per (buffer, relTF) @@ -280,12 +276,12 @@ class TPCDistributeCMVSpec : public o2::framework::Task void sendOutput(o2::framework::ProcessingContext& pc, const unsigned int currentOutLane, const unsigned int cru, o2::pmr::vector cmvs) { - pc.outputs().adoptContainer(Output{gDataOriginTPC, mDataDescrOut[currentOutLane], header::DataHeader::SubSpecificationType{cru}}, std::move(cmvs)); + pc.outputs().adoptContainer(o2::framework::Output{o2::header::gDataOriginTPC, mDataDescrOut[currentOutLane], header::DataHeader::SubSpecificationType{cru}}, std::move(cmvs)); } void sendOrbitInfo(o2::framework::ProcessingContext& pc, const unsigned int outLane, const uint64_t orbitInfo) { - pc.outputs().snapshot(Output{gDataOriginTPC, mOrbitDescrOut[outLane], header::DataHeader::SubSpecificationType{outLane}}, orbitInfo); + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, mOrbitDescrOut[outLane], header::DataHeader::SubSpecificationType{outLane}}, orbitInfo); } void forwardOrbitInfo(o2::framework::ProcessingContext& pc, const bool currentBuffer, const unsigned int relTF, const unsigned int currentOutLane) @@ -294,7 +290,7 @@ class TPCDistributeCMVSpec : public o2::framework::Task return; } - for (auto& ref : InputRecordWalker(pc.inputs(), mOrbitFilter)) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mOrbitFilter)) { auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); const unsigned int cru = hdr->subSpecification >> 7; if (!std::binary_search(mCRUs.begin(), mCRUs.end(), cru)) { @@ -313,17 +309,17 @@ class TPCDistributeCMVSpec : public o2::framework::Task if (mSendOutputStartInfo[mBuffer] && (mTFStart[mBuffer] >= 0)) { mSendOutputStartInfo[mBuffer] = false; - pc.outputs().snapshot(Output{gDataOriginTPC, getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{currentOutLane}}, mTFStart[mBuffer]); + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{currentOutLane}}, mTFStart[mBuffer]); } if (mSendCCDBOutputOrbitReset[currentOutLane] && mSendCCDBOutputGRPECS[currentOutLane]) { mSendCCDBOutputOrbitReset[currentOutLane] = false; mSendCCDBOutputGRPECS[currentOutLane] = false; - pc.outputs().snapshot(Output{gDataOriginTPC, getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{currentOutLane}}, dataformats::Pair{o2::base::GRPGeomHelper::instance().getOrbitResetTimeMS(), o2::base::GRPGeomHelper::instance().getNHBFPerTF()}); + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{currentOutLane}}, dataformats::Pair{o2::base::GRPGeomHelper::instance().getOrbitResetTimeMS(), o2::base::GRPGeomHelper::instance().getNHBFPerTF()}); } if (!mOrbitInfoForwarded[mBuffer].empty()) { - for (auto& ref : InputRecordWalker(pc.inputs(), mOrbitFilter)) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mOrbitFilter)) { auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); const unsigned int cru = hdr->subSpecification >> 7; if (!std::binary_search(mCRUs.begin(), mCRUs.end(), cru)) { @@ -334,13 +330,13 @@ class TPCDistributeCMVSpec : public o2::framework::Task } } - for (auto& ref : InputRecordWalker(pc.inputs(), mFilter)) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); const unsigned int cru = hdr->subSpecification >> 7; if (!std::binary_search(mCRUs.begin(), mCRUs.end(), cru)) { continue; } - sendOutput(pc, currentOutLane, cru, pc.inputs().get>(ref)); + sendOutput(pc, currentOutLane, cru, pc.inputs().get>(ref)); } } @@ -398,7 +394,7 @@ class TPCDistributeCMVSpec : public o2::framework::Task for (auto& it : mProcessedCRUs[currentBuffer][iTF]) { if (!it.second) { it.second = true; - sendOutput(pc, outLane, it.first, pmr::vector()); + sendOutput(pc, outLane, it.first, o2::pmr::vector()); } } @@ -417,7 +413,7 @@ class TPCDistributeCMVSpec : public o2::framework::Task mNFactorTFs = 0; // ToDo: Find better fix. Set oldestForChannel to a very large value so the DPL dispatcher does not block waiting for older TF data that will never arrive for (unsigned int ilane = 0; ilane < mOutLanes; ++ilane) { - auto& deviceProxy = pc.services().get(); + auto& deviceProxy = pc.services().get(); auto& state = deviceProxy.getOutputChannelState({static_cast(ilane)}); size_t oldest = std::numeric_limits::max() - 1; state.oldestForChannel = {oldest}; @@ -431,18 +427,18 @@ class TPCDistributeCMVSpec : public o2::framework::Task } }; -DataProcessorSpec getTPCDistributeCMVSpec(const int ilane, const std::vector& crus, const unsigned int timeframes, const unsigned int outlanes, const int firstTF, const bool sendPrecisetimeStamp = false, const int nTFsBuffer = 1) +o2::framework::DataProcessorSpec getTPCDistributeCMVSpec(const int ilane, const std::vector& crus, const unsigned int timeframes, const unsigned int outlanes, const int firstTF, const bool sendPrecisetimeStamp = false, const int nTFsBuffer = 1) { - std::vector inputSpecs; - inputSpecs.emplace_back(InputSpec{"cmvsgroup", ConcreteDataTypeMatcher{gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup()}, Lifetime::Sporadic}); - inputSpecs.emplace_back(InputSpec{"cmvorbit", ConcreteDataTypeMatcher{gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo()}, Lifetime::Sporadic}); + std::vector inputSpecs; + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvsgroup", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup()}, o2::framework::Lifetime::Sporadic}); + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvorbit", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo()}, o2::framework::Lifetime::Sporadic}); - std::vector outputSpecs; + std::vector outputSpecs; outputSpecs.reserve(3 * outlanes); for (unsigned int lane = 0; lane < outlanes; ++lane) { - outputSpecs.emplace_back(ConcreteDataTypeMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(lane)}, Lifetime::Sporadic); - outputSpecs.emplace_back(ConcreteDataMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(lane), header::DataHeader::SubSpecificationType{lane}}, Lifetime::Sporadic); - outputSpecs.emplace_back(ConcreteDataMatcher{gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{lane}}, Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(lane)}, o2::framework::Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(lane), header::DataHeader::SubSpecificationType{lane}}, o2::framework::Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{lane}}, o2::framework::Lifetime::Sporadic); } // Only lane 0 fetches CCDB orbit-reset/GRPECS objects and broadcasts them to all aggregate lanes, the other distribute lanes do not need them, avoiding redundant CCDB requests @@ -450,7 +446,7 @@ DataProcessorSpec getTPCDistributeCMVSpec(const int ilane, const std::vector(crus, timeframes, nTFsBuffer, outlanes, firstTF, ccdbRequest)}, - Options{{"drop-data-after-nTFs", VariantType::Int, 0, {"Number of TFs after which to drop the data."}}, - {"check-data-every-n", VariantType::Int, 0, {"Number of run function called after which to check for missing data (-1 for no checking, 0 for default checking)."}}, - {"nFactorTFs", VariantType::Int, 1000, {"Number of TFs to skip for sending oldest TF."}}}}; + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(crus, timeframes, nTFsBuffer, outlanes, firstTF, ccdbRequest)}, + o2::framework::Options{{"drop-data-after-nTFs", o2::framework::VariantType::Int, 0, {"Number of TFs after which to drop the data."}}, + {"check-data-every-n", o2::framework::VariantType::Int, 0, {"Number of run function called after which to check for missing data (-1 for no checking, 0 for default checking)."}}, + {"nFactorTFs", o2::framework::VariantType::Int, 1000, {"Number of TFs to skip for sending oldest TF."}}}}; spec.rank = ilane; return spec; } diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h index 9931c27c9d3fa..4b7886d4493a7 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h @@ -13,8 +13,8 @@ /// @author Tuba Gündem, tuba.gundem@cern.ch /// @brief TPC device for processing CMVs on FLPs -#ifndef O2_TPCFLPIDCSPEC_H -#define O2_TPCFLPIDCSPEC_H +#ifndef O2_TPCFLPCMVSPEC_H +#define O2_TPCFLPCMVSPEC_H #include #include @@ -28,12 +28,9 @@ #include "Headers/DataHeader.h" #include "TPCWorkflow/ProcessingHelpers.h" #include "TPCBase/CRU.h" +#include "DataFormatsTPC/CMV.h" #include "TFile.h" -using namespace o2::framework; -using o2::header::gDataOriginTPC; -using namespace o2::tpc; - namespace o2::tpc { @@ -46,6 +43,12 @@ class TPCFLPCMVDevice : public o2::framework::Task void init(o2::framework::InitContext& ic) final { mDumpCMVs = ic.options().get("dump-cmvs-flp"); + mEnableTrigger = ic.options().get("trigger"); + mTriggerThresholdCMV = ic.options().get("trigger-threshold-cmv"); + mTriggerThresholdMeanMax = ic.options().get("trigger-threshold-cmvMeanMax"); + mTriggerThresholdMeanMin = ic.options().get("trigger-threshold-cmvMeanMin"); + mTriggerTimebinMin = ic.options().get("trigger-threshold-timebinMin"); + mTriggerTimebinMax = ic.options().get("trigger-threshold-timebinMax"); } void run(o2::framework::ProcessingContext& pc) final @@ -56,7 +59,7 @@ class TPCFLPCMVDevice : public o2::framework::Task // Capture heartbeatOrbit / heartbeatBC from the first TF in the buffer if (mCountTFsForBuffer == 1) { - for (auto& ref : InputRecordWalker(pc.inputs(), mOrbitFilter)) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mOrbitFilter)) { auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); const uint32_t cru = hdr->subSpecification >> 7; if (mFirstOrbitBC.find(cru) == mFirstOrbitBC.end()) { @@ -68,12 +71,18 @@ class TPCFLPCMVDevice : public o2::framework::Task } } - for (auto& ref : InputRecordWalker(pc.inputs(), mFilter)) { + bool triggered = false; + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { auto const* tpcCRUHeader = o2::framework::DataRefUtils::getHeader(ref); - const int cru = tpcCRUHeader->subSpecification >> 7; + const uint32_t cru = tpcCRUHeader->subSpecification >> 7; auto vecCMVs = pc.inputs().get>(ref); mCMVs[cru].insert(mCMVs[cru].end(), vecCMVs.begin(), vecCMVs.end()); + + const bool cruTriggered = mEnableTrigger && evaluateTrigger(vecCMVs); + triggered |= cruTriggered; } + const header::DataHeader::SubSpecificationType trigSubSpec{mCRUs.front() << 7}; + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVTrigger(), trigSubSpec}, triggered); if (mCountTFsForBuffer >= mNTFsBuffer) { mCountTFsForBuffer = 0; @@ -86,7 +95,7 @@ class TPCFLPCMVDevice : public o2::framework::Task if (mDumpCMVs) { TFile fOut(fmt::format("CMVs_{}_tf_{}.root", mLane, processing_helpers::getCurrentTF(pc)).data(), "RECREATE"); - for (auto& ref : InputRecordWalker(pc.inputs(), mFilter)) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { auto const* tpcCRUHeader = o2::framework::DataRefUtils::getHeader(ref); const int cru = tpcCRUHeader->subSpecification >> 7; auto vec = pc.inputs().get>(ref); @@ -103,7 +112,7 @@ class TPCFLPCMVDevice : public o2::framework::Task sendOutput(ec.outputs(), cru); } } - ec.services().get().readyToQuit(QuitRequest::Me); + ec.services().get().readyToQuit(o2::framework::QuitRequest::Me); } static constexpr header::DataDescription getDataDescriptionCMVGroup() { return header::DataDescription{"CMVGROUP"}; } @@ -111,6 +120,9 @@ class TPCFLPCMVDevice : public o2::framework::Task /// Data description for the packed (orbit<<32|bc) scalar forwarded alongside each CRU's CMVGROUP. static constexpr header::DataDescription getDataDescriptionCMVOrbitInfo() { return header::DataDescription{"CMVORBITINFO"}; } + /// Data description for the per-CRU per-TF trigger flag (empty span = not triggered or disabled; {1} = triggered). + static constexpr header::DataDescription getDataDescriptionCMVTrigger() { return header::DataDescription{"CMVTRIGGER"}; } + private: const int mLane{}; ///< lane number of processor const std::vector mCRUs{}; ///< CRUs to process in this instance @@ -119,13 +131,53 @@ class TPCFLPCMVDevice : public o2::framework::Task int mCountTFsForBuffer{0}; ///< counts TFs to track when to send output std::unordered_map> mCMVs{}; ///< buffered raw 16-bit CMV values per CRU std::unordered_map mFirstOrbitBC{}; ///< first packed orbit/BC per CRU for the current buffer window + bool mEnableTrigger{false}; ///< enable CMV trigger evaluation + float mTriggerThresholdCMV{-10.f}; ///< CMV value threshold: trigger sequence starts when value drops below this + float mTriggerThresholdMeanMax{-40.f}; ///< upper bound on trigger-sequence mean CMV value + float mTriggerThresholdMeanMin{-80.f}; ///< lower bound on trigger-sequence mean CMV value + int mTriggerTimebinMin{4}; ///< minimum trigger-sequence length (timebins) to accept + int mTriggerTimebinMax{-1}; ///< maximum trigger-sequence length (timebins) to accept; -1 disables /// Filter for CMV float vectors (one CMVVECTOR message per CRU per TF) - const std::vector mFilter = {{"cmvs", ConcreteDataTypeMatcher{gDataOriginTPC, "CMVVECTOR"}, Lifetime::Timeframe}}; + const std::vector mFilter = {{"cmvs", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "CMVVECTOR"}, o2::framework::Lifetime::Timeframe}}; /// Filter for CMV packet timing info (one CMVORBITS message per CRU per TF, sent by CMVToVectorSpec) - const std::vector mOrbitFilter = {{"cmvorbits", ConcreteDataTypeMatcher{gDataOriginTPC, "CMVORBITS"}, Lifetime::Timeframe}}; + const std::vector mOrbitFilter = {{"cmvorbits", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "CMVORBITS"}, o2::framework::Lifetime::Timeframe}}; + + // Scan a CRU's CMV vector for contiguous below-threshold sequences. + // Returns true as soon as one sequence satisfies both the length and mean criteria. + bool evaluateTrigger(const o2::pmr::vector& cmvs) const + { + float seqSum = 0.f; + int seqLen = 0; + + auto checkSequence = [&]() -> bool { + if (seqLen == 0) { + return false; + } + const float mean = seqSum / seqLen; + return (seqLen >= mTriggerTimebinMin) && + (mTriggerTimebinMax < 0 || seqLen <= mTriggerTimebinMax) && + (mean >= mTriggerThresholdMeanMin) && + (mean <= mTriggerThresholdMeanMax); + }; + + for (const auto raw : cmvs) { + const float val = cmv::Data{raw}.getCMVFloat(); + if (val < mTriggerThresholdCMV) { + seqSum += val; + ++seqLen; + } else { + if (checkSequence()) { + return true; + } + seqLen = 0; + seqSum = 0.f; + } + } + return checkSequence(); // trailing sequence that reached end of buffer + } - void sendOutput(DataAllocator& output, const uint32_t cru) + void sendOutput(o2::framework::DataAllocator& output, const uint32_t cru) { const header::DataHeader::SubSpecificationType subSpec{cru << 7}; @@ -134,39 +186,50 @@ class TPCFLPCMVDevice : public o2::framework::Task if (auto it = mFirstOrbitBC.find(cru); it != mFirstOrbitBC.end()) { orbitBC = it->second; } - output.snapshot(Output{gDataOriginTPC, getDataDescriptionCMVOrbitInfo(), subSpec}, orbitBC); + output.snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVOrbitInfo(), subSpec}, orbitBC); - output.adoptContainer(Output{gDataOriginTPC, getDataDescriptionCMVGroup(), subSpec}, std::move(mCMVs[cru])); + output.adoptContainer(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVGroup(), subSpec}, std::move(mCMVs[cru])); } }; -DataProcessorSpec getTPCFLPCMVSpec(const int ilane, const std::vector& crus, const int nTFsBuffer = 1) +o2::framework::DataProcessorSpec getTPCFLPCMVSpec(const int ilane, const std::vector& crus, const int nTFsBuffer = 1) { - std::vector outputSpecs; - std::vector inputSpecs; - outputSpecs.reserve(crus.size()); - inputSpecs.reserve(crus.size()); + std::vector outputSpecs; + std::vector inputSpecs; + outputSpecs.reserve(crus.size() * 2 + 1); + inputSpecs.reserve(crus.size() * 2); for (const auto& cru : crus) { const header::DataHeader::SubSpecificationType subSpec{cru << 7}; // Inputs from CMVToVectorSpec - inputSpecs.emplace_back(InputSpec{"cmvs", gDataOriginTPC, "CMVVECTOR", subSpec, Lifetime::Timeframe}); - inputSpecs.emplace_back(InputSpec{"cmvorbits", gDataOriginTPC, "CMVORBITS", subSpec, Lifetime::Timeframe}); + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvs", o2::header::gDataOriginTPC, "CMVVECTOR", subSpec, o2::framework::Lifetime::Timeframe}); + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvorbits", o2::header::gDataOriginTPC, "CMVORBITS", subSpec, o2::framework::Lifetime::Timeframe}); // Outputs to TPCDistributeCMVSpec - outputSpecs.emplace_back(ConcreteDataMatcher{gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup(), subSpec}, Lifetime::Sporadic); - outputSpecs.emplace_back(ConcreteDataMatcher{gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo(), subSpec}, Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup(), subSpec}, o2::framework::Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo(), subSpec}, o2::framework::Lifetime::Sporadic); } + // Single per-FLP trigger output, subspec keyed on the first CRU + const header::DataHeader::SubSpecificationType trigSubSpec{crus.front() << 7}; + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVTrigger(), trigSubSpec}, o2::framework::Lifetime::Timeframe); + const auto id = fmt::format("tpc-flp-cmv-{:02}", ilane); - return DataProcessorSpec{ + return o2::framework::DataProcessorSpec{ id.data(), inputSpecs, outputSpecs, - AlgorithmSpec{adaptFromTask(ilane, crus, nTFsBuffer)}, - Options{{"dump-cmvs-flp", VariantType::Bool, false, {"Dump CMVs to file"}}}}; + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(ilane, crus, nTFsBuffer)}, + o2::framework::Options{ + {"dump-cmvs-flp", o2::framework::VariantType::Bool, false, {"Dump CMVs to file"}}, + {"trigger", o2::framework::VariantType::Bool, false, {"Enable CMV trigger evaluation"}}, + {"trigger-threshold-cmv", o2::framework::VariantType::Float, -10.f, {"CMV threshold: sequence starts when value drops below this (ADC units)"}}, + {"trigger-threshold-cmvMeanMax", o2::framework::VariantType::Float, -40.f, {"Upper bound on trigger-sequence mean CMV value"}}, + {"trigger-threshold-cmvMeanMin", o2::framework::VariantType::Float, -80.f, {"Lower bound on trigger-sequence mean CMV value"}}, + {"trigger-threshold-timebinMin", o2::framework::VariantType::Int, 4, {"Minimum trigger-sequence length in timebins"}}, + {"trigger-threshold-timebinMax", o2::framework::VariantType::Int, -1, {"Maximum trigger-sequence length in timebins (-1 disables upper bound)"}}}}; } } // namespace o2::tpc -#endif \ No newline at end of file +#endif diff --git a/Detectors/TPC/workflow/src/CMVToVectorSpec.cxx b/Detectors/TPC/workflow/src/CMVToVectorSpec.cxx index 81ce358d1a809..86cf4ca97aa19 100644 --- a/Detectors/TPC/workflow/src/CMVToVectorSpec.cxx +++ b/Detectors/TPC/workflow/src/CMVToVectorSpec.cxx @@ -76,7 +76,6 @@ class CMVToVectorDevice : public o2::framework::Task { const auto runNumber = processing_helpers::getRunNumber(pc); std::vector filter = {{"check", ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "RAWDATA"}, Lifetime::Timeframe}}; - const auto& mapper = Mapper::instance(); // open files if necessary if ((mWriteDebug || mWriteDebugOnError) && !mDebugStream) { @@ -95,10 +94,7 @@ class CMVToVectorDevice : public o2::framework::Task mRawOutputFile.open(rawFileName, std::ios::binary); } - uint32_t heartbeatOrbit = 0; - uint16_t heartbeatBC = 0; uint32_t tfCounter = 0; - bool first = true; bool hasErrors = false; for (auto const& ref : InputRecordWalker(pc.inputs(), filter)) { @@ -149,7 +145,7 @@ class CMVToVectorDevice : public o2::framework::Task LOGP(debug, "Processing firstTForbit {:9}, tfCounter {:5}, run {:6}, feeId {:6}, cruID {:3}, link {:2}", dh->firstTForbit, dh->tfCounter, dh->runNumber, feeId, cruID, link); if (std::find(mCRUs.begin(), mCRUs.end(), cruID) == mCRUs.end()) { - LOGP(warning, "CMV CRU {:3} not configured in CRUs, skipping", cruID); + // LOGP(debug, "CMV CRU {:3} not configured in CRUs, skipping", cruID); continue; } @@ -171,7 +167,7 @@ class CMVToVectorDevice : public o2::framework::Task cmvVec.reserve(cmvVec.size() + cmv::NTimeBinsPerPacket); for (uint32_t tb = 0; tb < cmv::NTimeBinsPerPacket; ++tb) { cmvVec.push_back(cmvs.getCMV(tb)); - // LOGP(debug, "Appended CMV {} for timebin {}, CRU {}, orbit {}, bc {}", cmvs.getCMV(tb), tb, cruID, orbit, bc); + // LOGP(debug, "For CRU {}, timebin {}, orbit {}, bc {}, appended CMV {} float: {}", cruID, tb, orbit, bc, cmvs.getCMV(tb), cmvs.getCMVFloat(tb)); } } } catch (const std::exception& e) { @@ -204,7 +200,7 @@ class CMVToVectorDevice : public o2::framework::Task } } - hasErrors |= snapshotCMVs(pc.outputs(), tfCounter); + hasErrors |= snapshotCMVs(pc.outputs()); if (mWriteDebug || (mWriteDebugOnError && hasErrors)) { writeDebugOutput(tfCounter); @@ -274,7 +270,7 @@ class CMVToVectorDevice : public o2::framework::Task std::string mRawOutputFileName; ///< name of the raw output file //____________________________________________________________________________ - bool snapshotCMVs(DataAllocator& output, uint32_t tfCounter) + bool snapshotCMVs(DataAllocator& output) { bool hasErrors = false; @@ -321,12 +317,8 @@ class CMVToVectorDevice : public o2::framework::Task //____________________________________________________________________________ void writeDebugOutput(uint32_t tfCounter) { - const auto& mapper = Mapper::instance(); - mDebugStream->GetFile()->cd(); auto& stream = (*mDebugStream) << "cmvs"; - uint32_t seen = 0; - static uint32_t firstOrbit = std::numeric_limits::max(); for (auto cru : mCRUs) { if (mCMVInfos.find(cru) == mCMVInfos.end()) { @@ -404,7 +396,7 @@ class CMVToVectorDevice : public o2::framework::Task } }; -o2::framework::DataProcessorSpec getCMVToVectorSpec(const std::string inputSpec, std::vector const& crus) +o2::framework::DataProcessorSpec getCMVToVectorSpec(std::string const& inputSpec, std::vector const& crus) { using device = o2::tpc::CMVToVectorDevice; diff --git a/Detectors/TPC/workflow/test/test_cmv-trigger.cxx b/Detectors/TPC/workflow/test/test_cmv-trigger.cxx new file mode 100644 index 0000000000000..c102a5ae531f4 --- /dev/null +++ b/Detectors/TPC/workflow/test/test_cmv-trigger.cxx @@ -0,0 +1,85 @@ +// 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 test_cmv-trigger.cxx +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief Test workflow: reads CMVTRIGGER packets from tpc-flp-cmv and logs results + +#include +#include +#include "Framework/WorkflowSpec.h" +#include "Framework/ConfigParamSpec.h" +#include "Framework/Task.h" +#include "Framework/ControlService.h" +#include "Framework/Logger.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/DataRefUtils.h" +#include "Headers/DataHeader.h" +#include "TPCWorkflow/ProcessingHelpers.h" +#include "TPCWorkflow/TPCFLPCMVSpec.h" + +using namespace o2::framework; + +void customize(std::vector&) {} + +#include "Framework/runDataProcessing.h" + +namespace o2::tpc +{ + +class CMVTriggerDevice : public o2::framework::Task +{ + public: + void run(o2::framework::ProcessingContext& pc) final + { + const auto tf = processing_helpers::getCurrentTF(pc); + + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const uint32_t firstCRU = hdr->subSpecification >> 7; + const bool triggered = pc.inputs().get(ref); + if (triggered) { + LOGP(info, "TF {:6} first CRU {:3}: {}", tf, firstCRU, "triggered"); + } + } + } + + void endOfStream(o2::framework::EndOfStreamContext& ec) final + { + ec.services().get().readyToQuit(o2::framework::QuitRequest::Me); + } + + private: + const std::vector mFilter = { + {"cmvtrigger", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, o2::tpc::TPCFLPCMVDevice::getDataDescriptionCMVTrigger()}, o2::framework::Lifetime::Timeframe}}; +}; + +o2::framework::DataProcessorSpec getCMVTriggerSpec() +{ + std::vector inputSpecs; + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvtrigger", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, o2::tpc::TPCFLPCMVDevice::getDataDescriptionCMVTrigger()}, o2::framework::Lifetime::Timeframe}); + + return o2::framework::DataProcessorSpec{ + "tpc-cmv-trigger", + inputSpecs, + {}, + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask()}}; +} + +} // namespace o2::tpc + +WorkflowSpec defineDataProcessing(ConfigContext const& config) +{ + WorkflowSpec workflow; + workflow.emplace_back(o2::tpc::getCMVTriggerSpec()); + return workflow; +} From 3ddeb15d809b3ccc224282d5862c988115e88560 Mon Sep 17 00:00:00 2001 From: altsybee Date: Tue, 12 May 2026 09:57:30 +0200 Subject: [PATCH 067/102] [ALICE3] TRK: update of TRK and FT3 services, to better match with Corrado's scheme (#15392) * update of TRK and FT3 services, to better match with Corrado's scheme * adjust spacing --------- Co-authored-by: Igor Altsybeev --- .../FT3/base/include/FT3Base/FT3BaseParam.h | 2 +- .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 7 +- .../ALICE3/TRK/simulation/src/TRKServices.cxx | 151 +++++++++++------- 3 files changed, 102 insertions(+), 58 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index b0f26bc4675d4..bb2969b69dc79 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -42,7 +42,7 @@ struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // override values from FT3ModuleConstants, inner and outer bool cutStavesOnNominalRadius_inner = true; - bool cutStavesOnNominalRadius_outer = false; + bool cutStavesOnNominalRadius_outer = true; // What to place over x=0 line in case of full outer-outer stave: Gap or Sensor bool placeSensorInMiddleOfStave = false; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index cc6accda3adb8..517bad767a645 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -428,7 +428,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) // need to shift outwards always, so + forwards and - backwards auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ + 0, FwdDiskRotation); - LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); + LOG(info) << "Inserting " << layerVol->GetName() << " (Rmin=" << mInnerRadius << ", Rmax=" << mOuterRadius << ", z=" << mZ << "cm) inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); } else if (ft3Params.layoutFT3 == kSegmentedStave || ft3Params.layoutFT3 == kSegmentedStaveOTOnly) { @@ -459,7 +459,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) // shift stave volumes into layer volume, since nominal z_{stave face} = 0 double z_local_offset = z_layer_thickness / 2.0; - TGeoTube* layer = new TGeoTube(mInnerRadius - 12, mOuterRadius + 5, z_layer_thickness / 2); + TGeoTube* layer = new TGeoTube(mInnerRadius - 0.2, mOuterRadius + 0.4, z_layer_thickness / 2); // margins to ensure staves are fully encapsulated in the layer volume layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); if (ft3Params.drawReferenceCircles) { @@ -476,7 +476,8 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) double z_offset_directional = mDirection ? z_local_offset : -z_local_offset; auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ + z_offset_directional, FwdDiskRotation); - LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); + LOG(info) << "Inserting " << layerVol->GetName() << " (Rmin=" << mInnerRadius << ", Rmax=" << mOuterRadius << ", z=" << mZ << "cm, segmented disk with staves) inside " << motherVolume->GetName(); + motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); } else { LOG(fatal) << "Unknown FT3 layout option: " << static_cast(ft3Params.layoutFT3); diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx index 7b61d2e120196..09745dc35f4a6 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx @@ -191,7 +191,7 @@ void TRKServices::registerVacuum(TGeoVolume* motherVolume) TGeoVolume* vacuumVolume = new TGeoVolume("A3IP_VACUUM", vacuumComposite, kMedVac); // Add the vacuum to the barrel - vacuumVolume->SetLineColor(kAzure + 7); + vacuumVolume->SetLineColor(kAzure + 6); vacuumVolume->SetTransparency(80); motherVolume->AddNode(vacuumVolume, 1, new TGeoTranslation(0, 0, 0)); @@ -614,9 +614,9 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) float pePowerAreaD = ITDisknPower * mPowerBundleArea * mPowerBundleComposition[1]; // Carbon Fiber Cylinder support for the middle tracker - float rMinMiddleCarbonSupport = 34.8f; // Arbitrary value - float rMaxMiddleCarbonSupport = 35.f; // 2 mm of carbon fiber - const float zLengthMiddleCarbon = 129.f; + float rMinMiddleCarbonSupport = 38.5f; // cm, from Corrado's drawing + float rMaxMiddleCarbonSupport = 39.0f; // 5 mm of carbon fiber + const float zLengthMiddleCarbon = 282.f; // cm, to cover the full length of ML barrel and disks, from Corrado's drawing TGeoTube* middleBarrelCarbonSupport = new TGeoTube("TRK_MID_CARBONSUPPORTsh", rMinMiddleCarbonSupport, rMaxMiddleCarbonSupport, zLengthMiddleCarbon / 2.); TGeoVolume* middleBarrelCarbonSupportVolume = new TGeoVolume("TRK_MID_CARBONSUPPORT", middleBarrelCarbonSupport, medCFiber); middleBarrelCarbonSupportVolume->SetLineColor(kGray); @@ -624,19 +624,18 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) motherVolume->AddNode(middleBarrelCarbonSupportVolume, 1, nullptr); // Get geometry information from TRK which is already present - float rMinMiddleServices = 35.f; + float rMinMiddleServices = 36.5f; // maximum radius allowed for sensors, plus some margin float rMinMiddleBarrel = rMinMiddleServices; const float zLengthMiddleBarrel = 64.5f; const float zLengthMiddleServices = 143.f; const float zLengthCylinderMiddleServices = zLengthMiddleServices - zLengthMiddleBarrel; // Middle layer barrel services are only on A side - rMinMiddleServices = 35.f; LOGP(info, "Building services for Middle Tracker rminMiddleServices"); // Middle barrel connection disks const float rMinMiddleBarrelDisk = 5.68f; - const float rMaxMiddleBarrelDisk = 35.f; + const float rMaxMiddleBarrelDisk = rMinMiddleServices; auto orientation = Orientation::kASide; float diskCircumference = rMaxMiddleBarrelDisk * 3.14; // Use only half circumference @@ -644,7 +643,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) double dZ = siO2FiberAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskSIO2 = new TGeoTube("TRK_MIDBARCONN_DISK_FIBER_SIO2sh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskSIO2Volume = new TGeoVolume("TRK_MIDBARCONN_DISK_FIBER_SIO2", middleBarrelConnDiskSIO2, medSiO2); - middleBarrelConnDiskSIO2Volume->SetLineColor(kGray); + middleBarrelConnDiskSIO2Volume->SetLineColor(kOrange + 1); auto* rot = new TGeoRotation("", 0, 0, 180); // Why this? auto* combiTransSIO2 = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); @@ -652,7 +651,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) dZ = peFiberAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskPE = new TGeoTube("TRK_MIDBARCONN_DISK_FIBER_PEsh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskPEVolume = new TGeoVolume("TRK_MIDBARCONN_DISK_FIBER_PE", middleBarrelConnDiskPE, medPE); - middleBarrelConnDiskPEVolume->SetLineColor(kGray); + middleBarrelConnDiskPEVolume->SetLineColor(kOrange + 1); auto* combiTransPE = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); motherVolume->AddNode(middleBarrelConnDiskSIO2Volume, 1, combiTransSIO2); @@ -662,14 +661,14 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) dZ = cuPowerAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskCu = new TGeoTube("TRK_MIDBARCONN_DISK_POWER_CUsh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskCuVolume = new TGeoVolume("TRK_MIDBARCONN_DISK_POWER_CU", middleBarrelConnDiskCu, medCu); - middleBarrelConnDiskCuVolume->SetLineColor(kGray); + middleBarrelConnDiskCuVolume->SetLineColor(kOrange + 1); auto* combiTransCu = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); zCur += 2. * dZ; dZ = pePowerAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskPEPower = new TGeoTube("TRK_MIDBARCONN_DISK_POWER_PEsh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskPEPowerVolume = new TGeoVolume("TRK_MIDBARCONN_DISK_POWER_PE", middleBarrelConnDiskPEPower, medPE); - middleBarrelConnDiskPEPowerVolume->SetLineColor(kGray); + middleBarrelConnDiskPEPowerVolume->SetLineColor(kOrange + 1); auto* combiTransPEPower = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); motherVolume->AddNode(middleBarrelConnDiskCuVolume, 1, combiTransCu); motherVolume->AddNode(middleBarrelConnDiskPEPowerVolume, 1, combiTransPEPower); @@ -688,13 +687,13 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) double dR = siO2FiberAreaD / (3.14 * rCur); TGeoTubeSeg* middleDiskFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLD_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleDiskFiberSIO2Volume = new TGeoVolume(Form("TRK_MLD_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), middleDiskFiberSIO2, medSiO2); - middleDiskFiberSIO2Volume->SetLineColor(kGray); + middleDiskFiberSIO2Volume->SetLineColor(kOrange + 1); rCur += dR; dR = peFiberAreaD / (3.14 * rCur); TGeoTubeSeg* middleDiskFiberPE = new TGeoTubeSeg(Form("TRK_MLD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleDiskFiberPEVolume = new TGeoVolume(Form("TRK_MLD_FIBER_PE_%s%d", orLabel.c_str(), iSide), middleDiskFiberPE, medPE); - middleDiskFiberPEVolume->SetLineColor(kGray); + middleDiskFiberPEVolume->SetLineColor(kOrange + 1); auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zLengthMiddleServices - zLengthCylinderMiddleServices / 2), new TGeoRotation("", refAngle + iSide * 180., 0, 0)); motherVolume->AddNode(middleDiskFiberSIO2Volume, 1, combiTrans); motherVolume->AddNode(middleDiskFiberPEVolume, 1, combiTrans); @@ -704,13 +703,13 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) dR = cuPowerAreaD / (3.14 * rCur); TGeoTubeSeg* middleDiskPowerCu = new TGeoTubeSeg(Form("TRK_MLD_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleDiskPowerCuVolume = new TGeoVolume(Form("TRK_MLD_POWER_CU_%s%d", orLabel.c_str(), iSide), middleDiskPowerCu, medCu); - middleDiskPowerCuVolume->SetLineColor(kGray); + middleDiskPowerCuVolume->SetLineColor(kOrange + 1); rCur += dR; dR = pePowerAreaD / (3.14 * rCur); TGeoTubeSeg* middleDiskPowerPE = new TGeoTubeSeg(Form("TRK_MLD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleDiskPowerPEVolume = new TGeoVolume(Form("TRK_MLD_POWER_PE_%s%d", orLabel.c_str(), iSide), middleDiskPowerPE, medPE); - middleDiskPowerPEVolume->SetLineColor(kGray); + middleDiskPowerPEVolume->SetLineColor(kOrange + 1); motherVolume->AddNode(middleDiskPowerCuVolume, 1, combiTrans); motherVolume->AddNode(middleDiskPowerPEVolume, 1, combiTrans); @@ -722,13 +721,13 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) dR = siO2FiberAreaB / (3.14 * rCur); TGeoTubeSeg* middleBarrelFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLB_FIBER_SIO2sh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleBarrelFiberSIO2Volume = new TGeoVolume(Form("TRK_MLB_FIBER_SIO2_A%d", iSide), middleBarrelFiberSIO2, medSiO2); - middleBarrelFiberSIO2Volume->SetLineColor(kGray); + middleBarrelFiberSIO2Volume->SetLineColor(kOrange + 1); rCur += dR; dR = peFiberAreaB / (3.14 * rCur); TGeoTubeSeg* middleBarrelFiberPE = new TGeoTubeSeg(Form("TRK_MLB_FIBER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleBarrelFiberPEVolume = new TGeoVolume(Form("TRK_MLB_FIBER_PE_A%d", iSide), middleBarrelFiberPE, medPE); - middleBarrelFiberPEVolume->SetLineColor(kGray); + middleBarrelFiberPEVolume->SetLineColor(kOrange + 1); auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zLengthMiddleServices - zLengthCylinderMiddleServices / 2), new TGeoRotation(nullptr, refAngle + iSide * 180., 0, 0)); motherVolume->AddNode(middleBarrelFiberSIO2Volume, 1, combiTrans); motherVolume->AddNode(middleBarrelFiberPEVolume, 1, combiTrans); @@ -738,13 +737,13 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) dR = cuPowerAreaB / (3.14 * rCur); TGeoTubeSeg* middleBarrelPowerCu = new TGeoTubeSeg(Form("TRK_MLB_POWER_CUsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleBarrelPowerCuVolume = new TGeoVolume(Form("TRK_MLB_POWER_CU_A%d", iSide), middleBarrelPowerCu, medCu); - middleBarrelPowerCuVolume->SetLineColor(kGray); + middleBarrelPowerCuVolume->SetLineColor(kOrange + 1); rCur += dR; dR = pePowerAreaB / (3.14 * rCur); TGeoTubeSeg* middleBarrelPowerPE = new TGeoTubeSeg(Form("TRK_MLB_POWER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); TGeoVolume* middleBarrelPowerPEVolume = new TGeoVolume(Form("TRK_MLB_POWER_PE_A%d", iSide), middleBarrelPowerPE, medPE); - middleBarrelPowerPEVolume->SetLineColor(kGray); + middleBarrelPowerPEVolume->SetLineColor(kOrange + 1); motherVolume->AddNode(middleBarrelPowerCuVolume, 1, combiTrans); motherVolume->AddNode(middleBarrelPowerPEVolume, 1, combiTrans); @@ -788,8 +787,8 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleBarFwdFiberPE = new TGeoTubeSeg(Form("TRK_MIDBARFWD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rMinMiddleBarrel, rMaxMiddleServicesBarFwd, dZ, -45, 45); TGeoVolume* middleBarFwdFiberPEVolume = new TGeoVolume(Form("TRK_MIDBARFWD_FIBER_PE_%s%d", orLabel.c_str(), iSide), middleBarFwdFiberPE, medPE); - middleBarFwdFiberSIO2Volume->SetLineColor(kGray); - middleBarFwdFiberPEVolume->SetLineColor(kGray); + middleBarFwdFiberSIO2Volume->SetLineColor(kOrange + 1); + middleBarFwdFiberPEVolume->SetLineColor(kOrange + 1); auto* combiTransPE = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); motherVolume->AddNode(middleBarFwdFiberSIO2Volume, 1, combiTransSIO2); motherVolume->AddNode(middleBarFwdFiberPEVolume, 1, combiTransPE); @@ -813,8 +812,8 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleBarFwdPowerPE = new TGeoTubeSeg(Form("TRK_MIDBARFWD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rMinMiddleBarrel, rMaxMiddleServicesBarFwd, dZ, -45, 45); TGeoVolume* middleBarFwdPowerPEVolume = new TGeoVolume(Form("TRK_MIDBARFWD_POWER_PE_%s%d", orLabel.c_str(), iSide), middleBarFwdPowerPE, medPE); - middleBarFwdPowerCuVolume->SetLineColor(kGray); - middleBarFwdPowerPEVolume->SetLineColor(kGray); + middleBarFwdPowerCuVolume->SetLineColor(kOrange + 1); + middleBarFwdPowerPEVolume->SetLineColor(kOrange + 1); auto* combiTransPEPower = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); motherVolume->AddNode(middleBarFwdPowerCuVolume, 1, combiTransCu); motherVolume->AddNode(middleBarFwdPowerPEVolume, 1, combiTransPEPower); @@ -838,7 +837,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleFwdFiberSIO2 = new TGeoTubeSeg(Form("TRK_MIDFWD_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthMiddleServicesFwd / 2, -45, 45); TGeoVolume* middleFwdFiberSIO2Volume = new TGeoVolume(Form("TRK_MIDFWD_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), middleFwdFiberSIO2, medSiO2); - middleFwdFiberSIO2Volume->SetLineColor(kGray); + middleFwdFiberSIO2Volume->SetLineColor(kOrange + 1); rCur += dR; dR = peFiberAreaD / (3.14 * rCur); @@ -847,7 +846,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleFwdFiberPE = new TGeoTubeSeg(Form("TRK_MIDFWD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthMiddleServicesFwd / 2, -45, 45); TGeoVolume* middleFwdFiberPEVolume = new TGeoVolume(Form("TRK_MIDFWD_FIBER_PE_%s%d", orLabel.c_str(), iSide), middleFwdFiberPE, medPE); - middleFwdFiberPEVolume->SetLineColor(kGray); + middleFwdFiberPEVolume->SetLineColor(kOrange + 1); auto* rot = new TGeoRotation("", refAngle + iSide * 180., 0, 0.); auto* combiTrans = new TGeoCombiTrans(0, 0, translation, rot); @@ -862,7 +861,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleFwdPowerCu = new TGeoTubeSeg(Form("TRK_MIDFWD_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthMiddleServicesFwd / 2, -45, 45); TGeoVolume* middleFwdPowerCuVolume = new TGeoVolume(Form("TRK_MIDFWD_POWER_CU_%s%d", orLabel.c_str(), iSide), middleFwdPowerCu, medCu); - middleFwdPowerCuVolume->SetLineColor(kGray); + middleFwdPowerCuVolume->SetLineColor(kOrange + 1); rCur += dR; dR = pePowerAreaD / (3.14 * rCur); @@ -871,7 +870,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleFwdPowerPE = new TGeoTubeSeg(Form("TRK_MIDFWD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthMiddleServicesFwd / 2, -45, 45); TGeoVolume* middleFwdPowerPEVolume = new TGeoVolume(Form("TRK_MIDFWD_POWER_PE_%s%d", orLabel.c_str(), iSide), middleFwdPowerPE, medPE); - middleFwdPowerPEVolume->SetLineColor(kGray); + middleFwdPowerPEVolume->SetLineColor(kOrange + 1); motherVolume->AddNode(middleFwdPowerCuVolume, 1, combiTrans); motherVolume->AddNode(middleFwdPowerPEVolume, 1, combiTrans); @@ -916,8 +915,20 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) float cuPowerAreaB = OTBarrelnPower * mPowerBundleArea * mPowerBundleComposition[0]; float pePowerAreaB = OTBarrelnPower * mPowerBundleArea * mPowerBundleComposition[1]; - float rMinOuterServices = 68.5f; // 68.5cm - float zLengthOuterServices = 201.f; // 201cm + // geometry of service "disk" for OT barrel + double rMinOTbarrelServices = 45.0; // cm, radius of first OT barrel layer + double rMaxOTbarrelServices = 78.0; // cm, radius of last OT barrel layer + double zOTbarrelServices = 132.0; // cm, approximate position of OT services in z + + // geometry of service "tubes" for OT barrel + float rMinOuterBarrelTubeServices = rMaxOTbarrelServices; // cm, IA, May 11, 2026: temporary radius (?) + float zStartOuterBarrelTubeServices = zOTbarrelServices + 0.8f; // cm, IA, May 11, 2026: start "OT service tubes" close in z to the "OT service disks" + float zLengthOuterBarrelTubeServices = 215.f; // cm, IA, May 11, 2026: temporary length (?) + + // geometry of service "tubes" for OT disks + float rMinOuterDiskServices = 68.5f; // cm + float zStartOuterDiskServices = 149.f; // cm + float zLengthOuterDiskServices = 201.f; // cm // Carbon Fiber Cylinder support for the middle tracker float rMinOuterCarbonSupport = 82.0f; // TODO: get more precise location @@ -937,15 +948,12 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) refAngle = 90; } // TODO: add cables/connections at ends of OT barrels - // Set rMin, rMax and dZ + double zCur = zOTbarrelServices; - double rMinOTbarrelServices = 45.0; // cm, radius of first OT layer - double rMaxOTbarrelServices = 80; // cm, radius of last OT layer - double zCur = 135.0; // cm, approximate position of OT services in z double dZ = siO2FiberAreaB / (4 * 3.14 * rMaxOTbarrelServices); TGeoTube* outerBarrelFiberSIO2 = new TGeoTube(Form("TRK_OUTERBARREL_FIBER_SIO2sh_%s", orLabel.c_str()), rMinOTbarrelServices, rMaxOTbarrelServices, dZ); TGeoVolume* outerBarrelFiberSIO2Volume = new TGeoVolume(Form("TRK_OUTERBARREL_FIBER_SIO2_%s", orLabel.c_str()), outerBarrelFiberSIO2, medSiO2); - outerBarrelFiberSIO2Volume->SetLineColor(kGray); + outerBarrelFiberSIO2Volume->SetLineColor(kAzure + 6); auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), nullptr); motherVolume->AddNode(outerBarrelFiberSIO2Volume, 1, combiTrans); @@ -953,7 +961,7 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) dZ = peFiberAreaB / (4 * 3.14 * rMaxOTbarrelServices); TGeoTube* outerBarrelFiberPE = new TGeoTube(Form("TRK_OUTERBARREL_FIBER_PEsh_%s", orLabel.c_str()), rMinOTbarrelServices, rMaxOTbarrelServices, dZ); TGeoVolume* outerBarrelFiberPEVolume = new TGeoVolume(Form("TRK_OUTERBARREL_FIBER_PE_%s", orLabel.c_str()), outerBarrelFiberPE, medPE); - outerBarrelFiberPEVolume->SetLineColor(kGray); + outerBarrelFiberPEVolume->SetLineColor(kAzure + 6); combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), nullptr); motherVolume->AddNode(outerBarrelFiberPEVolume, 1, combiTrans); @@ -961,7 +969,7 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) dZ = cuPowerAreaB / (4 * 3.14 * rMaxOTbarrelServices); TGeoTube* outerBarrelPowerCu = new TGeoTube(Form("TRK_OUTERBARREL_POWER_CUsh_%s", orLabel.c_str()), rMinOTbarrelServices, rMaxOTbarrelServices, dZ); TGeoVolume* outerBarrelPowerCuVolume = new TGeoVolume(Form("TRK_OUTERBARREL_POWER_CU_%s", orLabel.c_str()), outerBarrelPowerCu, medCu); - outerBarrelPowerCuVolume->SetLineColor(kGray); + outerBarrelPowerCuVolume->SetLineColor(kAzure + 6); combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), nullptr); motherVolume->AddNode(outerBarrelPowerCuVolume, 1, combiTrans); @@ -969,41 +977,76 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) dZ = pePowerAreaB / (4 * 3.14 * rMaxOTbarrelServices); TGeoTube* outerBarrelPowerPE = new TGeoTube(Form("TRK_OUTERBARREL_POWER_PEsh_%s", orLabel.c_str()), rMinOTbarrelServices, rMaxOTbarrelServices, dZ); TGeoVolume* outerBarrelPowerPEVolume = new TGeoVolume(Form("TRK_OUTERBARREL_POWER_PE_%s", orLabel.c_str()), outerBarrelPowerPE, medPE); - outerBarrelPowerPEVolume->SetLineColor(kGray); + outerBarrelPowerPEVolume->SetLineColor(kAzure + 6); combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), nullptr); motherVolume->AddNode(outerBarrelPowerPEVolume, 1, combiTrans); for (int iSide = 0; iSide < 2; iSide++) { - // Create fibers - double rCur = rMinOuterServices; - double dR = (siO2FiberAreaD + siO2FiberAreaB) / (3.14 * rCur); - TGeoTubeSeg* outerDisksFiberSIO2 = new TGeoTubeSeg(Form("TRK_OUTERDISKS_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterServices / 2, -45, 45); + // #### OT barrel services, implemented as tubes + // Create fibers for service barrel tubes + double rCur = rMinOuterBarrelTubeServices; // set starting radius for barrel service tube + double dR = siO2FiberAreaB / (3.14 * rCur); + TGeoTubeSeg* outerBarrelTubeFiberSIO2 = new TGeoTubeSeg(Form("TRK_OUTERBARREL_TUBE_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterBarrelTubeServices / 2, -45, 45); + TGeoVolume* outerBarrelTubeFiberSIO2Volume = new TGeoVolume(Form("TRK_OUTERBARREL_TUBE_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), outerBarrelTubeFiberSIO2, medSiO2); + outerBarrelTubeFiberSIO2Volume->SetLineColor(kAzure + 6); + + rCur += dR; + dR = peFiberAreaB / (3.14 * rCur); + TGeoTubeSeg* outerBarrelTubeFiberPE = new TGeoTubeSeg(Form("TRK_OUTERBARREL_TUBE_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterBarrelTubeServices / 2, -45, 45); + TGeoVolume* outerBarrelTubeFiberPEVolume = new TGeoVolume(Form("TRK_OUTERBARREL_TUBE_FIBER_PE_%s%d", orLabel.c_str(), iSide), outerBarrelTubeFiberPE, medPE); + outerBarrelTubeFiberPEVolume->SetLineColor(kAzure + 6); + + float translation = (int)orientation * (zStartOuterBarrelTubeServices + zLengthOuterBarrelTubeServices / 2); + auto* combiTrans = new TGeoCombiTrans(0, 0, translation, new TGeoRotation("", refAngle + iSide * 180., 0, 0)); + motherVolume->AddNode(outerBarrelTubeFiberSIO2Volume, 1, combiTrans); + motherVolume->AddNode(outerBarrelTubeFiberPEVolume, 1, combiTrans); + + // Create power lines for service barrel tubes + rCur += dR; + dR = cuPowerAreaB / (3.14 * rCur); + TGeoTubeSeg* outerBarrelTubePowerCu = new TGeoTubeSeg(Form("TRK_OUTERBARREL_TUBE_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterBarrelTubeServices / 2, -45, 45); + TGeoVolume* outerBarrelTubePowerCuVolume = new TGeoVolume(Form("TRK_OUTERBARREL_TUBE_POWER_CU_%s%d", orLabel.c_str(), iSide), outerBarrelTubePowerCu, medCu); + outerBarrelTubePowerCuVolume->SetLineColor(kAzure + 6); + + rCur += dR; + dR = pePowerAreaB / (3.14 * rCur); + TGeoTubeSeg* outerBarrelTubePowerPE = new TGeoTubeSeg(Form("TRK_OUTERBARREL_TUBE_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterBarrelTubeServices / 2, -45, 45); + TGeoVolume* outerBarrelTubePowerPEVolume = new TGeoVolume(Form("TRK_OUTERBARREL_TUBE_POWER_PE_%s%d", orLabel.c_str(), iSide), outerBarrelTubePowerPE, medPE); + outerBarrelTubePowerPEVolume->SetLineColor(kAzure + 6); + motherVolume->AddNode(outerBarrelTubePowerCuVolume, 1, combiTrans); + motherVolume->AddNode(outerBarrelTubePowerPEVolume, 1, combiTrans); + + // #### OT disk services, implemented as tubes + // Create fibers for disks + rCur = rMinOuterDiskServices; // set starting radius for disk service tube + dR = siO2FiberAreaD / (3.14 * rCur); + TGeoTubeSeg* outerDisksFiberSIO2 = new TGeoTubeSeg(Form("TRK_OUTERDISKS_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterDiskServices / 2, -45, 45); TGeoVolume* outerDisksFiberSIO2Volume = new TGeoVolume(Form("TRK_OUTERDISKS_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), outerDisksFiberSIO2, medSiO2); - outerDisksFiberSIO2Volume->SetLineColor(kGray); + outerDisksFiberSIO2Volume->SetLineColor(kMagenta); rCur += dR; - dR = (peFiberAreaD + peFiberAreaB) / (3.14 * rCur); - TGeoTubeSeg* outerDisksFiberPE = new TGeoTubeSeg(Form("TRK_OUTERDISKS_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterServices / 2, -45, 45); + dR = peFiberAreaD / (3.14 * rCur); + TGeoTubeSeg* outerDisksFiberPE = new TGeoTubeSeg(Form("TRK_OUTERDISKS_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterDiskServices / 2, -45, 45); TGeoVolume* outerDisksFiberPEVolume = new TGeoVolume(Form("TRK_OUTERDISKS_FIBER_PE_%s%d", orLabel.c_str(), iSide), outerDisksFiberPE, medPE); - outerDisksFiberPEVolume->SetLineColor(kGray); + outerDisksFiberPEVolume->SetLineColor(kMagenta); - float translation = (int)orientation * (149.f + zLengthOuterServices / 2); // ±149cm - auto* combiTrans = new TGeoCombiTrans(0, 0, translation, new TGeoRotation("", refAngle + iSide * 180., 0, 0)); + translation = (int)orientation * (zStartOuterDiskServices + zLengthOuterDiskServices / 2); + combiTrans = new TGeoCombiTrans(0, 0, translation, new TGeoRotation("", refAngle + iSide * 180., 0, 0)); motherVolume->AddNode(outerDisksFiberSIO2Volume, 1, combiTrans); motherVolume->AddNode(outerDisksFiberPEVolume, 1, combiTrans); - // Create power lines + // Create power lines for disks rCur += dR; - dR = (cuPowerAreaD + cuPowerAreaB) / (3.14 * rCur); - TGeoTubeSeg* outerDisksPowerCu = new TGeoTubeSeg(Form("TRK_OUTERDISKS_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterServices / 2, -45, 45); + dR = cuPowerAreaD / (3.14 * rCur); + TGeoTubeSeg* outerDisksPowerCu = new TGeoTubeSeg(Form("TRK_OUTERDISKS_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterDiskServices / 2, -45, 45); TGeoVolume* outerDisksPowerCuVolume = new TGeoVolume(Form("TRK_OUTERDISKS_POWER_CU_%s%d", orLabel.c_str(), iSide), outerDisksPowerCu, medCu); - outerDisksPowerCuVolume->SetLineColor(kGray); + outerDisksPowerCuVolume->SetLineColor(kMagenta + 1); rCur += dR; - dR = (pePowerAreaD + pePowerAreaB) / (3.14 * rCur); - TGeoTubeSeg* outerDisksPowerPE = new TGeoTubeSeg(Form("TRK_OUTERDISKS_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterServices / 2, -45, 45); + dR = pePowerAreaD / (3.14 * rCur); + TGeoTubeSeg* outerDisksPowerPE = new TGeoTubeSeg(Form("TRK_OUTERDISKS_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterDiskServices / 2, -45, 45); TGeoVolume* outerDisksPowerPEVolume = new TGeoVolume(Form("TRK_OUTERDISKS_POWER_PE_%s%d", orLabel.c_str(), iSide), outerDisksPowerPE, medPE); - outerDisksPowerPEVolume->SetLineColor(kGray); + outerDisksPowerPEVolume->SetLineColor(kMagenta + 1); motherVolume->AddNode(outerDisksPowerCuVolume, 1, combiTrans); motherVolume->AddNode(outerDisksPowerPEVolume, 1, combiTrans); From 761ffa28e324bcf49f846832133009b962ff5c5c Mon Sep 17 00:00:00 2001 From: shahoian Date: Tue, 12 May 2026 11:47:28 +0200 Subject: [PATCH 068/102] Optionally exclude certain specs from the trigger --- Detectors/Raw/README.md | 5 ++++ .../Raw/TFReaderDD/src/RawTFDumpSpec.cxx | 26 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Detectors/Raw/README.md b/Detectors/Raw/README.md index d896b232d2647..1fece239723ec 100644 --- a/Detectors/Raw/README.md +++ b/Detectors/Raw/README.md @@ -564,6 +564,11 @@ Selection string for the external trigger to dump particular TF. Must be contain ``` Include data with DPL-generated 0xdeadbeef subspecs (for data missing in the original TF). ``` +--exclude-trigger-specs (="") + +``` +Ignore trigger seen in these inputs of triggerspec (e.g. to suppress noisy trigger inputs) +``` --max-dump-rate arg (=0) ``` Fraction in (`%`) of TFs to dump. W/o external trigger: random(>0) or periodic(<0) rejection. With external trigger: throttle dumping to have the lowest estimated acceptance rate compatible with this rate. diff --git a/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx index 4e2d0372b3069..64c39fa7ef75a 100644 --- a/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx +++ b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx @@ -29,6 +29,7 @@ #include #include #include +#include namespace o2::rawdd { @@ -64,6 +65,7 @@ class RawTFDump : public Task std::map> mDataMap; std::vector mFilter{}; std::vector mTriggerFilter{}; + std::vector mExclTriggerFilter{}; size_t mTFSize = 0; size_t mMinFileSize = 0; // if > 0, accumulate TFs in the same file until the total size exceeds this minimum @@ -101,6 +103,7 @@ class RawTFDump : public Task o2::framework::TimingInfo mTimingInfo{}; std::string mTrigger{}; // external trigger input + std::string mExclTriggerSpecs{}; // trigger specs to ignore std::string mHostName{}; std::string mTFDir{}; std::string mTFMetaFileDir = "/dev/null"; @@ -182,7 +185,6 @@ void RawTFDump::init(InitContext& ic) mWriteTF = false; mStoreMetaFile = false; } - mRejectDEADBEEF = !ic.options().get("include-deadbeef"); mCreateRunEnvDir = !ic.options().get("ignore-partition-run-dir"); mMinFileSize = ic.options().get("min-file-size"); @@ -199,6 +201,10 @@ void RawTFDump::init(InitContext& ic) mWarnThrottleTF = ic.options().get("mute-warn-period"); mVerbose = ic.options().get("verbosity-level"); + mExclTriggerSpecs = ic.options().get("exclude-trigger-specs"); + if (!mExclTriggerSpecs.empty()) { + mExclTriggerFilter = select(mExclTriggerSpecs.c_str()); + } if (mTrigger.empty()) { if (mMaxAccRate >= 0.f) { LOGP(info, "Will accept randomly {}% of TFs", mMaxAccRate); @@ -208,6 +214,9 @@ void RawTFDump::init(InitContext& ic) } else { mMaxAccRate = std::abs(mMaxAccRate); LOGP(info, "Will limit TFs triggered with {} by {}% at most", mTrigger, mMaxAccRate); + if (!mExclTriggerFilter.empty()) { + LOGP(info, "Inputs excluded from the trigger: {}", mExclTriggerSpecs); + } } if (mWriteTF) { @@ -450,6 +459,20 @@ bool RawTFDump::triggerTF(ProcessingContext& pc) dh->splitPayloadIndex, dh->splitPayloadParts, dh->payloadSize, dh->firstTForbit, dh->tfCounter, extTrig.size(), extTrig.size() > 0 ? extTrig[0] : false); } if (extTrig.size() && extTrig[0]) { + // is the input with this trigger vetoed? + bool veto = false; + for (const auto& excl : mExclTriggerFilter) { + if (DataRefUtils::match(ref, excl)) { + if (mVerbose > 0) { + LOGP(info, "ignoring trigger from black-listed {}", DataSpecUtils::describe(OutputSpec{dh->dataOrigin, dh->dataDescription, dh->subSpecification})); + } + veto = true; + break; + } + } + if (veto) { + continue; + } trig = true; break; } @@ -554,6 +577,7 @@ DataProcessorSpec getRawTFDumpSpec(const std::string& inpconfig, const std::stri AlgorithmSpec{adaptFromTask(trigger)}, Options{ {"include-deadbeef", VariantType::Bool, false, {"Include DPL-generated 0xdeadbeef subspecs for missing data"}}, + {"exclude-trigger-specs", VariantType::String, "", {"Ignore trigger seen in these inputs of triggerspec"}}, {"max-dump-rate", VariantType::Float, 0.f, {"%-age of TFs to dump. W/o external trigger: random(>0) or periodic(<0) rejection, with: max limit"}}, {"rate-est-conf-limit", VariantType::Float, 0.05f, {"quantile for the lowest rate estimate confidence limit"}}, {"max-warn", VariantType::Int, 5, {"max allowed warnings on throttling"}}, From 88fae317e014b2adf20714a6667f6baa88fc71f1 Mon Sep 17 00:00:00 2001 From: shahoian Date: Tue, 12 May 2026 13:56:56 +0200 Subject: [PATCH 069/102] Optionally aggregate CMV triggers per FLP --- .../workflow/include/TPCWorkflow/TPCFLPCMVSpec.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h index 4b7886d4493a7..bb7f9632c6308 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h @@ -44,6 +44,7 @@ class TPCFLPCMVDevice : public o2::framework::Task { mDumpCMVs = ic.options().get("dump-cmvs-flp"); mEnableTrigger = ic.options().get("trigger"); + mTriggerPerFLP = ic.options().get("trigger-per-flp"); mTriggerThresholdCMV = ic.options().get("trigger-threshold-cmv"); mTriggerThresholdMeanMax = ic.options().get("trigger-threshold-cmvMeanMax"); mTriggerThresholdMeanMin = ic.options().get("trigger-threshold-cmvMeanMin"); @@ -79,10 +80,16 @@ class TPCFLPCMVDevice : public o2::framework::Task mCMVs[cru].insert(mCMVs[cru].end(), vecCMVs.begin(), vecCMVs.end()); const bool cruTriggered = mEnableTrigger && evaluateTrigger(vecCMVs); - triggered |= cruTriggered; + if (!mTriggerPerFLP) { + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVTrigger(), tpcCRUHeader->subSpecification}, cruTriggered); + } else { + triggered |= cruTriggered; + } + } + if (mTriggerPerFLP) { + const header::DataHeader::SubSpecificationType trigSubSpec{mCRUs.front() << 7}; + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVTrigger(), trigSubSpec}, triggered); } - const header::DataHeader::SubSpecificationType trigSubSpec{mCRUs.front() << 7}; - pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVTrigger(), trigSubSpec}, triggered); if (mCountTFsForBuffer >= mNTFsBuffer) { mCountTFsForBuffer = 0; @@ -128,6 +135,7 @@ class TPCFLPCMVDevice : public o2::framework::Task const std::vector mCRUs{}; ///< CRUs to process in this instance int mNTFsBuffer{1}; ///< number of TFs to buffer before sending bool mDumpCMVs{}; ///< dump CMVs to file for debugging + bool mTriggerPerFLP{false}; ///< send per-FLP trigger decision aggregated over CRUs int mCountTFsForBuffer{0}; ///< counts TFs to track when to send output std::unordered_map> mCMVs{}; ///< buffered raw 16-bit CMV values per CRU std::unordered_map mFirstOrbitBC{}; ///< first packed orbit/BC per CRU for the current buffer window @@ -224,6 +232,7 @@ o2::framework::DataProcessorSpec getTPCFLPCMVSpec(const int ilane, const std::ve o2::framework::Options{ {"dump-cmvs-flp", o2::framework::VariantType::Bool, false, {"Dump CMVs to file"}}, {"trigger", o2::framework::VariantType::Bool, false, {"Enable CMV trigger evaluation"}}, + {"trigger-per-flp", o2::framework::VariantType::Bool, false, {"Aggregate triggers of CRUs on FLP to a single trigger"}}, {"trigger-threshold-cmv", o2::framework::VariantType::Float, -10.f, {"CMV threshold: sequence starts when value drops below this (ADC units)"}}, {"trigger-threshold-cmvMeanMax", o2::framework::VariantType::Float, -40.f, {"Upper bound on trigger-sequence mean CMV value"}}, {"trigger-threshold-cmvMeanMin", o2::framework::VariantType::Float, -80.f, {"Lower bound on trigger-sequence mean CMV value"}}, From b3e2e7ab0e2663ba6c9cca8731c761c863970d02 Mon Sep 17 00:00:00 2001 From: altsybee Date: Wed, 13 May 2026 10:54:08 +0200 Subject: [PATCH 070/102] [ALICE3] TRK: adjustments for z and r of services, split OT barrel into two halves (#15395) * adjustments for service z and r positions, split OT barrel into two halves * reverting back OT stave tilting angle * Please consider the following formatting changes --------- Co-authored-by: Igor Altsybeev Co-authored-by: ALICE Action Bot --- .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 2 +- .../ALICE3/IOTOF/simulation/src/Layer.cxx | 2 +- .../include/TRKSimulation/TRKLayer.h | 3 + .../ALICE3/TRK/simulation/src/TRKLayer.cxx | 33 ++++++--- .../ALICE3/TRK/simulation/src/TRKServices.cxx | 70 ++++++++++--------- 5 files changed, 65 insertions(+), 45 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 517bad767a645..d8245fa1d34b4 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -459,7 +459,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) // shift stave volumes into layer volume, since nominal z_{stave face} = 0 double z_local_offset = z_layer_thickness / 2.0; - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.2, mOuterRadius + 0.4, z_layer_thickness / 2); // margins to ensure staves are fully encapsulated in the layer volume + TGeoTube* layer = new TGeoTube(mInnerRadius - 0.2, mOuterRadius + 2.5, z_layer_thickness / 2); // margins to ensure staves are fully encapsulated in the layer volume layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); if (ft3Params.drawReferenceCircles) { diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx index 4f76d71b63aa3..627fb599ff8ae 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx @@ -168,7 +168,7 @@ void ITOFLayer::createLayer(TGeoVolume* motherVolume) const double deltaForTilt = 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); // we increase the size of the layer to account for the tilt of the staves const double radiusMax = std::sqrt(avgRadius * avgRadius + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY + avgRadius * 2. * deltaForTilt); // we increase the outer radius to account for the tilt of the staves const double radiusMin = std::sqrt(avgRadius * avgRadius + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY - avgRadius * 2. * deltaForTilt); // we decrease the inner radius to account for the tilt of the staves - TGeoTube* layer = new TGeoTube(radiusMin, radiusMax, mZLength / 2); + TGeoTube* layer = new TGeoTube(radiusMin - 0.05, radiusMax + 0.05, mZLength / 2); // cm, small margins to ensure staves are fully encapsulated in the layer volume TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); setLayerStyle(layerVol); diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h index ef4d5657a1b4f..e900cfa679ffe 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h @@ -141,6 +141,9 @@ class TRKOTLayer : public TRKSegmentedLayer TGeoVolume* createHalfStave(); void createLayer(TGeoVolume* motherVolume) override; + protected: + static constexpr float sGapBetweenOuterTrackerBarrelHalves = 0.8; // cm, gap between the two halves of the OT barrel + private: static constexpr double sHalfStaveWidth = constants::OT::halfstave::width; static constexpr double sInStaveOverlap = constants::moduleMLOT::gaps::outerEdgeLongSide + constants::moduleMLOT::chip::passiveEdgeReadOut + 0.1; // 1.5mm outer-edge + 1mm deadzone + 1mm (true) overlap diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx index 7a4b7bef34e03..5206985992ecf 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx @@ -388,13 +388,15 @@ TGeoVolume* TRKOTLayer::createHalfStave() { TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); std::string halfStaveName = GeometryTGeo::getTRKHalfStavePattern() + std::to_string(mLayerNumber); - TGeoShape* halfStave = new TGeoBBox(sHalfStaveWidth / 2, mChipThickness / 2, mLength / 2); + float lengthHalfBarrel = mLength / 2; + TGeoShape* halfStave = new TGeoBBox(sHalfStaveWidth / 2, mChipThickness / 2, lengthHalfBarrel / 2); TGeoVolume* halfStaveVol = new TGeoVolume(halfStaveName.c_str(), halfStave, medSi); halfStaveVol->SetLineColor(kYellow); - for (int iModule = 0; iModule < mNumberOfModules; iModule++) { + int nModulesPerHalfBarrel = mNumberOfModules / 2; // assuming mNumberOfModules is always even, which should be the case given the current specifications + for (int iModule = 0; iModule < nModulesPerHalfBarrel; iModule++) { TGeoVolume* moduleVol = createModule(); - double zPos = -0.5 * mNumberOfModules * sModuleLength + (iModule + 0.5) * sModuleLength; + double zPos = -0.5 * nModulesPerHalfBarrel * sModuleLength + (iModule + 0.5) * sModuleLength; TGeoCombiTrans* trans = new TGeoCombiTrans(); trans->SetTranslation(0, 0, zPos); LOGP(debug, "Inserting {} in {} ", moduleVol->GetName(), halfStaveVol->GetName()); @@ -431,35 +433,46 @@ void TRKOTLayer::createLayer(TGeoVolume* motherVolume) TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); // TGeoTube* layer = new TGeoTube(mInnerRadius - 0.333 * sLogicalVolumeThickness, mInnerRadius + 0.667 * sLogicalVolumeThickness, mLength / 2); - TGeoTube* layer = new TGeoTube(rMin, rMax, mLength / 2); + TGeoTube* layer = new TGeoTube(rMin, rMax, (mLength + sGapBetweenOuterTrackerBarrelHalves) / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); layerVol->SetLineColor(kYellow); // Compute the number of staves - int nStaves = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / sStaveWidth); - nStaves += nStaves % 2; // Require an even number of staves + int nStavesHalfBarrel = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / sStaveWidth); + nStavesHalfBarrel += nStavesHalfBarrel % 2; // Require an even number of staves // Nominal average radius used as the placement barycenter for all staves const double avgRadius = 0.5 * (mInnerRadius + mOuterRadius); // Compute the size of the overlap region - double theta = 2. * TMath::Pi() / nStaves; + double theta = 2. * TMath::Pi() / nStavesHalfBarrel; double theta1 = std::atan(sStaveWidth / 2 / mInnerRadius); double st = std::sin(theta); double ct = std::cos(theta); double theta2 = std::atan((mInnerRadius * st - sStaveWidth / 2 * ct) / (mInnerRadius * ct + sStaveWidth / 2 * st)); double overlap = (theta1 - theta2) * mInnerRadius; - LOGP(info, "Creating a layer with {} staves and {} mm overlap", nStaves, overlap * 10); + LOGP(info, "Creating a layer with two half barrels, each with {} staves and {} mm overlap", nStavesHalfBarrel, overlap * 10); + + float lengthHalfBarrel = mLength / 2; + int nStaves = nStavesHalfBarrel * 2; // since we now have two half-barrels (separated by a small gap), we double the number of staves for (int iStave = 0; iStave < nStaves; iStave++) { TGeoVolume* staveVol = createStave(); + int whichHalfBarrel = iStave / nStavesHalfBarrel; // 0 for the first half (negative z), 1 for the second half (positive z) TGeoCombiTrans* trans = new TGeoCombiTrans(); double phi = theta * iStave; double phiDeg = phi * TMath::RadToDeg(); - TGeoRotation* rot = new TGeoRotation("rot", phiDeg + 90 + mTiltAngle, 0, 0); + // TGeoRotation* rot = new TGeoRotation("rot", phiDeg + 90 + mTiltAngle, 0, 0); + TGeoRotation* rot = new TGeoRotation("rot"); + if (whichHalfBarrel == 1) { + rot->RotateY(180.); // degrees, rotate the second half barrel by 180 degrees around Y to achieve the correct staggering orientation + } + rot->RotateZ(phiDeg + 90 + (whichHalfBarrel == 0 ? +1 : -1) * mTiltAngle); // phi in degrees, tilting depends on the half-barrel side trans->SetRotation(rot); // trans->SetTranslation(mInnerRadius * std::cos(phi), mInnerRadius * std::sin(phi), 0); - trans->SetTranslation(avgRadius * std::cos(phi), avgRadius * std::sin(phi), 0); + // trans->SetTranslation(avgRadius * std::cos(phi), avgRadius * std::sin(phi), 0); + double zPos = (whichHalfBarrel == 0 ? -1 : 1) * (0.5 * lengthHalfBarrel + sGapBetweenOuterTrackerBarrelHalves / 2); + trans->SetTranslation(avgRadius * std::cos(phi), avgRadius * std::sin(phi), zPos); LOGP(debug, "Inserting {} in {} ", staveVol->GetName(), layerVol->GetName()); layerVol->AddNode(staveVol, iStave, trans); } diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx index 09745dc35f4a6..53ac0a4b12865 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx @@ -109,7 +109,7 @@ void TRKServices::createMaterials() matmgr.Material("ALICE3_TRKSERVICES", 73, "BERYLLIUM", 9.01, 4., 1.848, 35.3, 36.7); // Beryllium - Candidate for IRIS vacuum vessel matmgr.Mixture("ALICE3_TRKSERVICES", 74, "ALUMINIUM5083", aAl5083, zAl5083, dAl5083, 9, wAl5083); // AL5083 - Candidate for IRIS vacuum vessel matmgr.Mixture("ALICE3_TRKSERVICES", 75, "ALUMINIUMBERYLLIUMMETAL", aAlBeMet, zAlBeMet, dAlBeMet, 2, wAlBeMet); // Aluminium-Beryllium metal - Candidate for IRIS vacuum vessel - matmgr.Material("ALICE3_TRKSERVICES", 76, "CARBONFIBERM55J6K", 12.0107, 6, 1.92, 22.4, 999); // Carbon Fiber M55J + matmgr.Material("ALICE3_TRKSERVICES", 76, "CARBONFIBERM55J6K", 12.0107, 6, 1.92, 22.4, 45.4); // Carbon Fiber M55J matmgr.Mixture("ALICE3_PIPE", 77, "VACUUM", aAir, zAir, dAir1, 4, wAir); matmgr.Medium("ALICE3_TRKSERVICES", 1, "CERAMIC", 66, 0, ifield, fieldm, tmaxfd, stemax, deemax, epsil, stmin); // Ceramic for cold plate @@ -614,8 +614,9 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) float pePowerAreaD = ITDisknPower * mPowerBundleArea * mPowerBundleComposition[1]; // Carbon Fiber Cylinder support for the middle tracker - float rMinMiddleCarbonSupport = 38.5f; // cm, from Corrado's drawing - float rMaxMiddleCarbonSupport = 39.0f; // 5 mm of carbon fiber + // (from ICD_ALICE3_V3.b.3 drawing: 38.5 cm are allocated for staves and services, + 1 cm for the support; we assume less for the support - to be reconsidered if necessary) + float rMinMiddleCarbonSupport = 39.3f; // cm + float rMaxMiddleCarbonSupport = 39.5f; // cm, assume 2 mm of carbon fiber, ~0.88% X/X0 const float zLengthMiddleCarbon = 282.f; // cm, to cover the full length of ML barrel and disks, from Corrado's drawing TGeoTube* middleBarrelCarbonSupport = new TGeoTube("TRK_MID_CARBONSUPPORTsh", rMinMiddleCarbonSupport, rMaxMiddleCarbonSupport, zLengthMiddleCarbon / 2.); TGeoVolume* middleBarrelCarbonSupportVolume = new TGeoVolume("TRK_MID_CARBONSUPPORT", middleBarrelCarbonSupport, medCFiber); @@ -624,14 +625,16 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) motherVolume->AddNode(middleBarrelCarbonSupportVolume, 1, nullptr); // Get geometry information from TRK which is already present - float rMinMiddleServices = 36.5f; // maximum radius allowed for sensors, plus some margin - float rMinMiddleBarrel = rMinMiddleServices; - const float zLengthMiddleBarrel = 64.5f; - const float zLengthMiddleServices = 143.f; - const float zLengthCylinderMiddleServices = zLengthMiddleServices - zLengthMiddleBarrel; + float rMinMiddleServices = 38.0f; // cm, start radius of the ML services = maximum radius allowed for sensors (35 cm), plus some margin for disk paving with modules + const float zMiddleServicesBarrel = 64.5f; // cm, z position of the first barrel ML service disk + const float zMiddleServicesBarrelFwdConnection = 143.f; // cm, z position of barrel to forward connection services + const float zLengthCylinderMiddleServicesBarrel = zMiddleServicesBarrelFwdConnection - zMiddleServicesBarrel; + + const float zStartServicesForMiddleDisks = 77.0f; // cm, starting z position of ML disk services, assumed to be the same as of the first ML disk + const float zLengthCylinderMiddleServicesDisk = zMiddleServicesBarrelFwdConnection - zStartServicesForMiddleDisks; // Middle layer barrel services are only on A side - LOGP(info, "Building services for Middle Tracker rminMiddleServices"); + LOGP(info, "Building services for barrel Middle Layers"); // Middle barrel connection disks const float rMinMiddleBarrelDisk = 5.68f; @@ -639,11 +642,11 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) auto orientation = Orientation::kASide; float diskCircumference = rMaxMiddleBarrelDisk * 3.14; // Use only half circumference - double zCur = zLengthMiddleBarrel; + double zCur = zMiddleServicesBarrel; double dZ = siO2FiberAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskSIO2 = new TGeoTube("TRK_MIDBARCONN_DISK_FIBER_SIO2sh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskSIO2Volume = new TGeoVolume("TRK_MIDBARCONN_DISK_FIBER_SIO2", middleBarrelConnDiskSIO2, medSiO2); - middleBarrelConnDiskSIO2Volume->SetLineColor(kOrange + 1); + middleBarrelConnDiskSIO2Volume->SetLineColor(kOrange - 9); auto* rot = new TGeoRotation("", 0, 0, 180); // Why this? auto* combiTransSIO2 = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); @@ -651,7 +654,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) dZ = peFiberAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskPE = new TGeoTube("TRK_MIDBARCONN_DISK_FIBER_PEsh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskPEVolume = new TGeoVolume("TRK_MIDBARCONN_DISK_FIBER_PE", middleBarrelConnDiskPE, medPE); - middleBarrelConnDiskPEVolume->SetLineColor(kOrange + 1); + middleBarrelConnDiskPEVolume->SetLineColor(kOrange - 9); auto* combiTransPE = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); motherVolume->AddNode(middleBarrelConnDiskSIO2Volume, 1, combiTransSIO2); @@ -661,14 +664,14 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) dZ = cuPowerAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskCu = new TGeoTube("TRK_MIDBARCONN_DISK_POWER_CUsh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskCuVolume = new TGeoVolume("TRK_MIDBARCONN_DISK_POWER_CU", middleBarrelConnDiskCu, medCu); - middleBarrelConnDiskCuVolume->SetLineColor(kOrange + 1); + middleBarrelConnDiskCuVolume->SetLineColor(kOrange - 9); auto* combiTransCu = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); zCur += 2. * dZ; dZ = pePowerAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskPEPower = new TGeoTube("TRK_MIDBARCONN_DISK_POWER_PEsh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskPEPowerVolume = new TGeoVolume("TRK_MIDBARCONN_DISK_POWER_PE", middleBarrelConnDiskPEPower, medPE); - middleBarrelConnDiskPEPowerVolume->SetLineColor(kOrange + 1); + middleBarrelConnDiskPEPowerVolume->SetLineColor(kOrange - 9); auto* combiTransPEPower = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); motherVolume->AddNode(middleBarrelConnDiskCuVolume, 1, combiTransCu); motherVolume->AddNode(middleBarrelConnDiskPEPowerVolume, 1, combiTransPEPower); @@ -685,29 +688,29 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) // create data fiber volumes double rCur = rMinMiddleServices; double dR = siO2FiberAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLD_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); + TGeoTubeSeg* middleDiskFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLD_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesDisk / 2, -45, 45); TGeoVolume* middleDiskFiberSIO2Volume = new TGeoVolume(Form("TRK_MLD_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), middleDiskFiberSIO2, medSiO2); middleDiskFiberSIO2Volume->SetLineColor(kOrange + 1); rCur += dR; dR = peFiberAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskFiberPE = new TGeoTubeSeg(Form("TRK_MLD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); + TGeoTubeSeg* middleDiskFiberPE = new TGeoTubeSeg(Form("TRK_MLD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesDisk / 2, -45, 45); TGeoVolume* middleDiskFiberPEVolume = new TGeoVolume(Form("TRK_MLD_FIBER_PE_%s%d", orLabel.c_str(), iSide), middleDiskFiberPE, medPE); middleDiskFiberPEVolume->SetLineColor(kOrange + 1); - auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zLengthMiddleServices - zLengthCylinderMiddleServices / 2), new TGeoRotation("", refAngle + iSide * 180., 0, 0)); + auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zMiddleServicesBarrelFwdConnection - zLengthCylinderMiddleServicesDisk / 2), new TGeoRotation("", refAngle + iSide * 180., 0, 0)); motherVolume->AddNode(middleDiskFiberSIO2Volume, 1, combiTrans); motherVolume->AddNode(middleDiskFiberPEVolume, 1, combiTrans); // Create powerlines rCur += dR; dR = cuPowerAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskPowerCu = new TGeoTubeSeg(Form("TRK_MLD_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); + TGeoTubeSeg* middleDiskPowerCu = new TGeoTubeSeg(Form("TRK_MLD_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesDisk / 2, -45, 45); TGeoVolume* middleDiskPowerCuVolume = new TGeoVolume(Form("TRK_MLD_POWER_CU_%s%d", orLabel.c_str(), iSide), middleDiskPowerCu, medCu); middleDiskPowerCuVolume->SetLineColor(kOrange + 1); rCur += dR; dR = pePowerAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskPowerPE = new TGeoTubeSeg(Form("TRK_MLD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); + TGeoTubeSeg* middleDiskPowerPE = new TGeoTubeSeg(Form("TRK_MLD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesDisk / 2, -45, 45); TGeoVolume* middleDiskPowerPEVolume = new TGeoVolume(Form("TRK_MLD_POWER_PE_%s%d", orLabel.c_str(), iSide), middleDiskPowerPE, medPE); middleDiskPowerPEVolume->SetLineColor(kOrange + 1); @@ -719,31 +722,31 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) // create data fiber volumes rCur += dR; dR = siO2FiberAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLB_FIBER_SIO2sh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); + TGeoTubeSeg* middleBarrelFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLB_FIBER_SIO2sh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); TGeoVolume* middleBarrelFiberSIO2Volume = new TGeoVolume(Form("TRK_MLB_FIBER_SIO2_A%d", iSide), middleBarrelFiberSIO2, medSiO2); - middleBarrelFiberSIO2Volume->SetLineColor(kOrange + 1); + middleBarrelFiberSIO2Volume->SetLineColor(kOrange - 9); rCur += dR; dR = peFiberAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelFiberPE = new TGeoTubeSeg(Form("TRK_MLB_FIBER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); + TGeoTubeSeg* middleBarrelFiberPE = new TGeoTubeSeg(Form("TRK_MLB_FIBER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); TGeoVolume* middleBarrelFiberPEVolume = new TGeoVolume(Form("TRK_MLB_FIBER_PE_A%d", iSide), middleBarrelFiberPE, medPE); - middleBarrelFiberPEVolume->SetLineColor(kOrange + 1); - auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zLengthMiddleServices - zLengthCylinderMiddleServices / 2), new TGeoRotation(nullptr, refAngle + iSide * 180., 0, 0)); + middleBarrelFiberPEVolume->SetLineColor(kOrange - 9); + auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zMiddleServicesBarrelFwdConnection - zLengthCylinderMiddleServicesBarrel / 2), new TGeoRotation(nullptr, refAngle + iSide * 180., 0, 0)); motherVolume->AddNode(middleBarrelFiberSIO2Volume, 1, combiTrans); motherVolume->AddNode(middleBarrelFiberPEVolume, 1, combiTrans); // Create powerlines rCur += dR; dR = cuPowerAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelPowerCu = new TGeoTubeSeg(Form("TRK_MLB_POWER_CUsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); + TGeoTubeSeg* middleBarrelPowerCu = new TGeoTubeSeg(Form("TRK_MLB_POWER_CUsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); TGeoVolume* middleBarrelPowerCuVolume = new TGeoVolume(Form("TRK_MLB_POWER_CU_A%d", iSide), middleBarrelPowerCu, medCu); - middleBarrelPowerCuVolume->SetLineColor(kOrange + 1); + middleBarrelPowerCuVolume->SetLineColor(kOrange - 9); rCur += dR; dR = pePowerAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelPowerPE = new TGeoTubeSeg(Form("TRK_MLB_POWER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices / 2, -45, 45); + TGeoTubeSeg* middleBarrelPowerPE = new TGeoTubeSeg(Form("TRK_MLB_POWER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); TGeoVolume* middleBarrelPowerPEVolume = new TGeoVolume(Form("TRK_MLB_POWER_PE_A%d", iSide), middleBarrelPowerPE, medPE); - middleBarrelPowerPEVolume->SetLineColor(kOrange + 1); + middleBarrelPowerPEVolume->SetLineColor(kOrange - 9); motherVolume->AddNode(middleBarrelPowerCuVolume, 1, combiTrans); motherVolume->AddNode(middleBarrelPowerPEVolume, 1, combiTrans); @@ -757,6 +760,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) // A side: barrel + disk services // C side: only disk services float rMaxMiddleServicesBarFwd = 74.5f; // TODO: add thickness of service barrels + float rMinMiddleBarrel = rMinMiddleServices; // min radius of the service disk diskCircumference = rMaxMiddleServicesBarFwd * 3.14; // Only half of the area is used for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { float refAngle = 0; @@ -768,7 +772,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) double totalThickness = 0; for (int iSide = 0; iSide < 2; iSide++) { // Create fibers - double zCur = zLengthMiddleServices; // Change to f + double zCur = zMiddleServicesBarrelFwdConnection; // Change to f double dZ = siO2FiberAreaD / diskCircumference / 2.; totalThickness += 2 * dZ; if (orientation == Orientation::kASide) { @@ -822,13 +826,13 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } // Forward part - float zLengthMiddleServicesFwd = 350.f - (143.f + totalThickness); + float zLengthMiddleServicesFwd = 350.f - (zMiddleServicesBarrelFwdConnection + totalThickness); + float rMinMiddleServicesFwd = 74.5f; // 74.5cm for (int iSide = 0; iSide < 2; iSide++) { // Create fibers - float rMinMiddleServicesFwd = 74.5f; // 74.5cm - float translation = (int)orientation * (143.f + totalThickness + zLengthMiddleServicesFwd / 2); + float translation = (int)orientation * (zMiddleServicesBarrelFwdConnection + totalThickness + zLengthMiddleServicesFwd / 2); double rCur = rMinMiddleServicesFwd; double dR = siO2FiberAreaD / (3.14 * rCur); @@ -926,7 +930,7 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) float zLengthOuterBarrelTubeServices = 215.f; // cm, IA, May 11, 2026: temporary length (?) // geometry of service "tubes" for OT disks - float rMinOuterDiskServices = 68.5f; // cm + float rMinOuterDiskServices = 70.5f; // cm float zStartOuterDiskServices = 149.f; // cm float zLengthOuterDiskServices = 201.f; // cm From 510ed793182aec6e2e2d35738ecc0860b1e4c56e Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Wed, 13 May 2026 10:55:25 +0200 Subject: [PATCH 071/102] ALICE3: start global tracking directory with TRK tracking (CPU+GPU and ROF staggering) (#15351) * ALICE3: move tracking to GlobalReconstruction * ALICE3: add cluster and GPU tracking paths * ALICE3: derive ROF timing for TRK inputs * ALICE3: write track ROFs and IR frames * ALICE3: assign cluster MC labels in TimeFrame loading * ALICE3: share TimeFrame code between CPU and GPU * ALICE3: validate configuration before running * ALICE3: clang-format * ALICE3: fix initialisation with MC vertices Co-authored-by: Copilot * ALICE3: add new performance macro * ALICE3: Fix check cluster macro * ALICE3: add protections in reading clusters --------- Co-authored-by: Copilot --- Detectors/Upgrades/ALICE3/CMakeLists.txt | 1 + .../GlobalReconstruction/CMakeLists.txt | 14 + .../macros/CMakeLists.txt | 21 + .../macros/CheckTracksALICE3.C | 619 ++++++++++++++++++ .../reconstruction/CMakeLists.txt | 74 +++ .../GPUExternalAllocator.h | 65 ++ .../ALICE3GlobalReconstruction/TimeFrame.h | 35 + .../ALICE3GlobalReconstruction/TimeFrameGPU.h | 35 + .../TimeFrameMixin.h | 557 ++++++++++++++++ .../ALICE3GlobalReconstruction}/TrackerACTS.h | 6 +- .../src/GPUExternalAllocator.cxx | 210 ++++++ .../reconstruction/src/TimeFrame.cxx | 25 + .../reconstruction/src/TimeFrameGPU.cxx | 25 + .../reconstruction/src/TrackerACTS.cxx | 6 +- .../workflow/CMakeLists.txt | 35 + .../GlobalReconstruction/workflow/README.md | 133 ++++ .../RecoWorkflow.h | 30 + .../TrackWriterSpec.h | 0 .../TrackerSpec.h | 8 +- .../workflow/src/RecoWorkflow.cxx | 40 ++ .../workflow/src/TrackWriterSpec.cxx | 2 +- .../workflow/src/TrackerSpec.cxx | 549 ++++++++++++++++ .../alice3-global-reconstruction-workflow.cxx | 65 ++ .../ALICE3/TRK/macros/test/CheckClusters.C | 236 +++++-- .../ALICE3/TRK/reconstruction/CMakeLists.txt | 18 +- .../include/TRKReconstruction/Clusterer.h | 9 +- .../include/TRKReconstruction/ClustererACTS.h | 4 +- .../include/TRKReconstruction/TimeFrame.h | 68 -- .../TRK/reconstruction/src/Clusterer.cxx | 57 +- .../TRK/reconstruction/src/ClustererACTS.cxx | 4 +- .../TRK/reconstruction/src/TimeFrame.cxx | 225 ------- .../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 +- .../ALICE3/TRK/workflow/src/TrackerSpec.cxx | 439 ------------- .../TRK/workflow/src/trk-reco-workflow.cxx | 24 +- 37 files changed, 2795 insertions(+), 998 deletions(-) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h rename Detectors/Upgrades/ALICE3/{TRK/reconstruction/include/TRKReconstruction => GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction}/TrackerACTS.h (96%) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cxx create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx rename Detectors/Upgrades/ALICE3/{TRK => GlobalReconstruction}/reconstruction/src/TrackerACTS.cxx (98%) 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 (84%) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx rename Detectors/Upgrades/ALICE3/{TRK => GlobalReconstruction}/workflow/src/TrackWriterSpec.cxx (97%) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx delete mode 100644 Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TimeFrame.h delete mode 100644 Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TimeFrame.cxx delete mode 100644 Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.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..6b859412a0ff5 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt @@ -0,0 +1,14 @@ +# 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) +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; +} diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt new file mode 100644 index 0000000000000..8805c1885b079 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt @@ -0,0 +1,74 @@ +# 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() + +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 + O2::GPUCommon + Microsoft.GSL::GSL + O2::CommonConstants + O2::DataFormatsITSMFT + O2::DataFormatsTRK + O2::SimulationDataFormat + O2::ITSBase + O2::ITSReconstruction + O2::ITSMFTReconstruction + O2::DataFormatsITS + O2::TRKBase + O2::TRKReconstruction + O2::TRKSimulation + nlohmann_json::nlohmann_json + ${alice3GlobalRecoGpuTargets} + ${actsTarget} + PRIVATE_LINK_LIBRARIES + O2::Steer + 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) +endif() 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..e873931a5a46c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h @@ -0,0 +1,65 @@ +// 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 new file mode 100644 index 0000000000000..6daefb2346e2c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h @@ -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. +/// +/// \file TimeFrame.h +/// \brief CPU TRK TimeFrame wrapper. +/// + +#ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H +#define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H + +#include "ALICE3GlobalReconstruction/TimeFrameMixin.h" +#include "ITStracking/TimeFrame.h" + +namespace o2::trk +{ + +template +class TimeFrame : public TimeFrameMixin> +{ + public: + TimeFrame() = default; + ~TimeFrame() override = default; +}; + +} // 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 new file mode 100644 index 0000000000000..744fca166489f --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h @@ -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. +/// +/// \file TimeFrameGPU.h +/// \brief GPU TRK TimeFrame wrapper. +/// + +#ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H +#define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H + +#include "ALICE3GlobalReconstruction/TimeFrameMixin.h" +#include "ITStrackingGPU/TimeFrameGPU.h" + +namespace o2::trk +{ + +template +class TimeFrameGPU : public TimeFrameMixin> +{ + public: + TimeFrameGPU() = default; + ~TimeFrameGPU() override = default; +}; + +} // namespace o2::trk + +#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..6e95be32dd0e1 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h @@ -0,0 +1,557 @@ +// 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 + +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(); + + 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()); + } + + std::array, nLayers> patternOffsetsPerLayer; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + auto& offsets = patternOffsetsPerLayer[iLayer]; + offsets.resize(layerClusters[iLayer].size(), std::numeric_limits::max()); + size_t pattPos = 0; + bool validPatterns = true; + for (size_t clusterId{0}; clusterId < layerClusters[iLayer].size(); ++clusterId) { + if (pattPos + 2 > layerPatterns[iLayer].size()) { + validPatterns = false; + break; + } + offsets[clusterId] = pattPos; + const uint8_t rowSpan = layerPatterns[iLayer][pattPos]; + const uint8_t colSpan = layerPatterns[iLayer][pattPos + 1]; + const size_t nBytes = (size_t(rowSpan) * colSpan + 7) / 8; + if (pattPos + 2 + nBytes > layerPatterns[iLayer].size()) { + validPatterns = false; + break; + } + pattPos += 2 + nBytes; + } + if (!validPatterns || pattPos != layerPatterns[iLayer].size()) { + LOGP(fatal, "Malformed TRK pattern stream for layer {}: {} bytes for {} clusters", + iLayer, layerPatterns[iLayer].size(), layerClusters[iLayer].size()); + } + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + 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 (clusterId < 0 || clusterId >= static_cast(layerClusters[iLayer].size())) { + LOGP(warning, "Skipping out-of-range TRK cluster {} on layer {}", clusterId, iLayer); + continue; + } + + const auto& c = layerClusters[iLayer][clusterId]; + if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { + 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); + continue; + } + + const auto pattOffset = patternOffsetsPerLayer[iLayer][clusterId]; + const uint8_t* pattForCluster = layerPatterns[iLayer].data() + pattOffset; + auto locXYZ = Clusterer::getClusterLocalCoordinates(c, pattForCluster, yPlaneMLOT); + + 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() +{ + 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); + + const int64_t anchorBC = mTFAnchorIR.toLong(); + const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); + const auto rofLength = clockLayer.mROFLength; + + using Vertex = o2::its::Vertex; + struct VertEntry { + int64_t bc; + Vertex vertex; + int event; + }; + std::vector entries; + + 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() - anchorBC; + if (evBC >= 0) { + Vertex vert; + vert.setTimeStamp(o2::its::TimeEstBC{ + static_cast(evBC), + 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); + entries.push_back({evBC, vert, iEve}); + } + } + mcReader.releaseTracksForSourceAndEvent(iSrc, iEve); + } + + // 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: added {} vertices", entries.size()); +} + +} // namespace o2::trk + +#endif // ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEMIXIN_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 96% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TrackerACTS.h rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TrackerACTS.h index 2910abf480961..ee69b32a23895 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/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 new file mode 100644 index 0000000000000..1f7997b2e3968 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx @@ -0,0 +1,25 @@ +// 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 TimeFrame.cxx +/// \brief Explicit instantiation of TimeFrameMixin and TimeFrame for the +/// ITS CPU base. Shared method bodies live in TimeFrameMixin.h. +/// + +#include "ALICE3GlobalReconstruction/TimeFrame.h" + +namespace o2::trk +{ + +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 new file mode 100644 index 0000000000000..714ead765b005 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx @@ -0,0 +1,25 @@ +// 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 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" + +namespace o2::trk +{ + +template class TimeFrameMixin<11, o2::its::gpu::TimeFrameGPU<11>>; +template class TimeFrameGPU<11>; + +} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TrackerACTS.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx similarity index 98% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TrackerACTS.cxx rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx index 732a0acc14b66..e870ee934816f 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 @@ -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/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..f22e95d6971db --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md @@ -0,0 +1,133 @@ +# 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 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 + +### 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-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 + +## 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], + "AddTimeError": [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, 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..98a5176d5db44 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h @@ -0,0 +1,30 @@ +// 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, + const std::string& clusterRecoConfig, + 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 84% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackerSpec.h rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h index 304b32041c2dc..006bb4cbf5260 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/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 new file mode 100644 index 0000000000000..024bd3b4425f8 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx @@ -0,0 +1,40 @@ +// 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, + const std::string& clusterRecoConfig, + bool disableRootOutput, + o2::gpu::gpudatatypes::DeviceType dtype) +{ + framework::WorkflowSpec specs; + + 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)); + } + } + + 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/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx new file mode 100644 index 0000000000000..9fb2899ab3ef5 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -0,0 +1,549 @@ +// 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 +#include +#include +#include +#include +#include +#include + +#include "CommonDataFormat/IRFrame.h" +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" +#include "DetectorsBase/GeometryManager.h" +#include "ITStracking/TimeFrame.h" +#include "ITStracking/Configuration.h" +#include "Field/MagneticField.h" +#include "Field/MagFieldParam.h" +#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 + +#ifdef O2_WITH_ACTS +#include "ALICE3GlobalReconstruction/TrackerACTS.h" +#endif + +#include +#include + +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; +} + +void TrackerDPL::init(InitContext& ic) +{ +#ifdef O2_WITH_ACTS + mUseACTS = ic.options().get("useACTS"); +#endif +} + +void TrackerDPL::stop() +{ + LOGF(info, "CPU Reconstruction total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); +} + +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 (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(); + } + + 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("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(); + } + + 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>(); + } + 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(); + params.Diamond[1] = paramConfig["Diamond"][1].get(); + params.Diamond[2] = paramConfig["Diamond"][2].get(); + } + + if (paramConfig.contains("MaxMemory")) { + params.MaxMemory = paramConfig["MaxMemory"].get(); + } + + if (paramConfig.contains("CorrType")) { + int corrTypeInt = paramConfig["CorrType"].get(); + 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); + } + }; + + 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()); + return trackingParams; +} + +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 + } + + auto trackingParams = createTrackingParamsFromConfig(); + + auto cput = mTimer.CpuTime(); + auto realt = mTimer.RealTime(); + mTimer.Start(false); + + 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 + + auto runTracking = [&](auto& timeFrame, auto& trackerTraits) { + o2::its::Tracker<11> itsTracker(&trackerTraits); + timeFrame.setMemoryPool(mMemoryPool); + 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(); + } + } + + timeFrame.deriveAndInitTiming(layerROFs); + + const float yPlaneMLOT = 0.0010f; + nRofs = timeFrame.loadROFrameData(layerROFs, layerClusters, layerPatterns, mIsMC ? &layerLabels : nullptr, yPlaneMLOT); + timeFrame.addTruthSeedingVertices(); + } + + const auto trackingLoopStart = std::chrono::steady_clock::now(); + for (size_t iter{0}; iter < trackingParams.size(); ++iter) { + LOGP(info, "{}", trackingParams[iter].asString()); + trackerTraits.initialiseTimeFrame(iter); + trackerTraits.computeLayerTracklets(iter, -1); + LOGP(info, "Number of tracklets in iteration {}: {}", iter, timeFrame.getNumberOfTracklets()); + trackerTraits.computeLayerCells(iter); + LOGP(info, "Number of cells in iteration {}: {}", iter, timeFrame.getNumberOfCells()); + trackerTraits.findCellsNeighbours(iter); + LOGP(info, "Number of cell neighbours in iteration {}: {}", iter, timeFrame.getNumberOfNeighbours()); + 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); + + if (mIsMC) { + itsTracker.computeTracksMClabels(); + } + + const auto& tracks = timeFrame.getTracks(); + const auto& labels = timeFrame.getTracksLabel(); + std::vector allTracks(tracks.begin(), tracks.end()); + std::vector allLabels; + + int totalTracks = allTracks.size(); + int goodTracks = 0; + int fakeTracks = 0; + + if (mIsMC) { + allLabels.assign(labels.begin(), labels.end()); + for (const auto& label : allLabels) { + if (label.isFake()) { + ++fakeTracks; + } else { + ++goodTracks; + } + } + } + + LOGP(info, "=== Tracking Summary ==="); + LOGP(info, "Total tracks reconstructed: {}", totalTracks); + 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); + + 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, "TRK pushed {} tracks in {} ROFs and {} IR frames{}", + allTracks.size(), allTrackROFs.size(), irFrames.size(), + mIsMC ? " (with MC labels)" : ""); + + timeFrame.wipe(); + }; + +#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); +} + +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, const std::string& clusterRecoConfig, o2::gpu::gpudatatypes::DeviceType dType) +{ + 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 + false, // GRPMagField + false, // askMatLUT + o2::base::GRPGeomRequest::None, // geometry, but ignored until it will be put in the CCDB + inputs, + true); + + if (!hitRecoConfig.empty()) { + if (useMC) { + outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); + } + return DataProcessorSpec{ + "trk-hits-tracker", + {}, + outputs, + AlgorithmSpec{adaptFromTask(ggRequest, + useMC, + hitRecoConfig, + clusterRecoConfig, + dType)}, + Options{ConfigParamSpec{"max-loops", VariantType::Int, 1, {"max number of loops"}} +#ifdef O2_WITH_ACTS + , + {"useACTS", o2::framework::VariantType::Bool, false, {"Use ACTS for tracking"}} +#endif + }}; + } + + inputs.emplace_back("dummy", "TRK", "DUMMY", 0, Lifetime::Timeframe); + + if (!clusterRecoConfig.empty()) { + inputs.pop_back(); + 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); + } + } + } + + if (useMC) { + outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); + } + + return DataProcessorSpec{ + "trk-tracker", + inputs, + outputs, + AlgorithmSpec{adaptFromTask(ggRequest, + useMC, + hitRecoConfig, + clusterRecoConfig, + dType)}, + Options{}}; +} + +} // namespace trk +} // namespace o2 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..7e9950f4def2e --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx @@ -0,0 +1,65 @@ +// 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 +#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"}}, + {"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); +} + +#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 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, clusterRecoConfig, disableRootOutput, gpuDevice); +} diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C index 28dc61aed9c8b..7b9365dbe2011 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C @@ -22,7 +22,12 @@ #include #include #include +#include +#include +#include +#include #include +#include #include "DataFormatsTRK/Cluster.h" #include "DataFormatsTRK/ROFRecord.h" @@ -48,11 +53,8 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", { gROOT->SetBatch(batch); - using o2::MCCompLabel; - using ROFRec = o2::trk::ROFRecord; - using MC2ROF = o2::trk::MC2ROFRecord; using HitVec = std::vector; - using MC2HITS_map = std::unordered_map; // maps (trackID << 32) + chipID -> hit index + using MC2HITS_map = std::unordered_map>; // maps (trackID << 32) + chipID -> hit indices // ── Chip response (for hit-segment propagation to charge-collection plane) ── // Fetches the same AlpideSimResponse from CCDB as the digitizer (IT3/Calib/APTSResponse) @@ -130,6 +132,10 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", LOGP(error, "Cannot find o2sim tree in {}", hitfile); return; } + if (hitTree->GetBranch("TRKHit") == nullptr) { + LOGP(error, "Cannot find TRKHit branch in {}", hitfile); + return; + } std::vector mc2hitVec; std::vector hitVecPool; mc2hitVec.resize(hitTree->GetEntries()); @@ -149,8 +155,10 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", std::vector*> rofRecVecPerLayer(nLayers, nullptr); std::vector*> patternsPerLayer(nLayers, nullptr); std::vector*> clusLabArrPerLayer(nLayers, nullptr); + std::vector> patternOffsetsPerLayer(nLayers); + std::vector layerActive(nLayers, false); - bool hasMC = true; + bool hasAnyMC = false; for (int iLayer = 0; iLayer < nLayers; iLayer++) { std::string brClus = std::string("TRKClusterComp_") + std::to_string(iLayer); std::string brROF = std::string("TRKClustersROF_") + std::to_string(iLayer); @@ -161,45 +169,112 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", LOGP(warning, "Branch {} not found, skipping layer {}", brClus, iLayer); continue; } + if (clusTree->GetBranch(brROF.c_str()) == nullptr) { + LOGP(error, "Branch {} not found, skipping layer {}", brROF, iLayer); + continue; + } clusTree->SetBranchAddress(brClus.c_str(), &clusArrPerLayer[iLayer]); clusTree->SetBranchAddress(brROF.c_str(), &rofRecVecPerLayer[iLayer]); if (clusTree->GetBranch(brPatt.c_str()) != nullptr) { clusTree->SetBranchAddress(brPatt.c_str(), &patternsPerLayer[iLayer]); + } else { + LOGP(warning, "Branch {} not found, layer {} cluster positions use bbox origins", brPatt, iLayer); } if (clusTree->GetBranch(brMCTruth.c_str()) != nullptr) { clusTree->SetBranchAddress(brMCTruth.c_str(), &clusLabArrPerLayer[iLayer]); - } else { - hasMC = false; + hasAnyMC = true; } + layerActive[iLayer] = true; } // Read entry and accumulate all layers - clusTree->GetEntry(0); + if (clusTree->GetEntry(0) <= 0) { + LOGP(error, "Cannot read entry 0 from {}", clusfile); + return; + } + + auto hasAnyActiveLayer = false; + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + hasAnyActiveLayer = hasAnyActiveLayer || layerActive[iLayer]; + } + if (!hasAnyActiveLayer) { + LOGP(error, "No usable TRK cluster layers found in {}", clusfile); + return; + } + // Print total clusters per layer for (int iLayer = 0; iLayer < nLayers; iLayer++) { + if (!layerActive[iLayer]) { + continue; + } + if (clusArrPerLayer[iLayer] == nullptr || rofRecVecPerLayer[iLayer] == nullptr) { + LOGP(error, "Layer {} branches were declared but did not load usable data, skipping layer", iLayer); + layerActive[iLayer] = false; + continue; + } LOGP(info, "Layer {}: {} clusters", iLayer, clusArrPerLayer[iLayer]->size()); } + // The pattern stream is variable-length, so index it by cluster entry once. + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + if (!layerActive[iLayer] || patternsPerLayer[iLayer] == nullptr) { + continue; + } + const auto nClusters = clusArrPerLayer[iLayer]->size(); + const auto& patterns = *patternsPerLayer[iLayer]; + auto& offsets = patternOffsetsPerLayer[iLayer]; + offsets.resize(nClusters, std::numeric_limits::max()); + size_t pattPos = 0; + bool validPatterns = true; + for (size_t icl = 0; icl < nClusters; icl++) { + if (pattPos + 2 > patterns.size()) { + validPatterns = false; + break; + } + offsets[icl] = pattPos; + const uint8_t rowSpan = patterns[pattPos]; + const uint8_t colSpan = patterns[pattPos + 1]; + const size_t nBytes = (size_t(rowSpan) * colSpan + 7) / 8; + if (pattPos + 2 + nBytes > patterns.size()) { + validPatterns = false; + break; + } + pattPos += 2 + nBytes; + } + if (!validPatterns || pattPos != patterns.size()) { + LOGP(error, "Malformed pattern stream for layer {}: {} pattern bytes for {} clusters, disabling CoG corrections for this layer", + iLayer, patterns.size(), nClusters); + patternsPerLayer[iLayer] = nullptr; + offsets.clear(); + } + } + // Accumulate max ROF count across all layers unsigned int nROFRec = 0; for (int iLayer = 0; iLayer < nLayers; iLayer++) { + if (!layerActive[iLayer]) { + continue; + } nROFRec = std::max(nROFRec, (unsigned int)rofRecVecPerLayer[iLayer]->size()); } LOGP(info, "Number of ROF records: {}", nROFRec); // ── Load all MC hit events upfront (TRK has no MC2ROF mapping) ────────────── - if (hasMC) { + if (hasAnyMC) { LOGP(info, "Pre-loading {} MC events", hitTree->GetEntries()); for (int im = 0; im < (int)hitTree->GetEntries(); im++) { if (hitVecPool[im] == nullptr) { hitTree->SetBranchAddress("TRKHit", &hitVecPool[im]); - hitTree->GetEntry(im); + if (hitTree->GetEntry(im) <= 0 || hitVecPool[im] == nullptr) { + LOGP(error, "Cannot read TRKHit entry {} from {}", im, hitfile); + return; + } auto& mc2hit = mc2hitVec[im]; const auto* hv = hitVecPool[im]; for (int ih = (int)hv->size(); ih--;) { const auto& hit = (*hv)[ih]; uint64_t key = (uint64_t(hit.GetTrackID()) << 32) + hit.GetDetectorID(); - mc2hit.emplace(key, ih); + mc2hit[key].push_back(ih); } } } @@ -210,40 +285,45 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", // columns: event, MC track label, // local hit x/z (flat frame), global hit x/y/z (midpoint), // global cluster x/y/z, local cluster x/z, - // residuals dx/dz (local, cluster - hit), // ROF frame, cluster size, chipID, layer, disk, subDetID, row, col, pt TNtuple nt("ntc", "TRK cluster ntuple", "event:mcTrackID:hitLocX:hitLocZ:hitGlobX:hitGlobY:hitGlobZ:clusGlobX:clusGlobY:clusGlobZ:clusLocX:clusLocZ:rofFrame:clusSize:chipID:layer:disk:subdet:row:col:pt"); // ── Counters ─────────────────────────────────────────────────────────────── - long nTot{0}, nInvalidLabel{0}, nNoMCHit{0}, nValid{0}; + long nTot{0}, nInvalidLabel{0}, nInvalidEvent{0}, nNoMCHit{0}, nValid{0}; // ── Main loop ────────────────────────────────────────────────────────────── for (unsigned int irof = 0; irof < nROFRec; irof++) { // Process each layer for (int iLayer = 0; iLayer < nLayers; iLayer++) { + if (!layerActive[iLayer]) { + continue; + } if (rofRecVecPerLayer[iLayer]->empty() || irof >= rofRecVecPerLayer[iLayer]->size()) { continue; } const auto& rofRec = (*rofRecVecPerLayer[iLayer])[irof]; const auto& clusArr = *clusArrPerLayer[iLayer]; - const auto& patternsPtr = (patternsPerLayer[iLayer] == nullptr) ? nullptr : patternsPerLayer[iLayer]; const auto& clusLabArr = clusLabArrPerLayer[iLayer]; - - // Create per-layer pattern iterator - auto pattIt = patternsPtr ? patternsPtr->cbegin() : std::vector::const_iterator{}; + const auto* patternsPtr = patternsPerLayer[iLayer]; + const auto& patternOffsets = patternOffsetsPerLayer[iLayer]; for (int icl = 0; icl < rofRec.getNEntries(); icl++) { const int clEntry = rofRec.getFirstEntry() + icl; + if (clEntry < 0 || clEntry >= (int)clusArr.size()) { + LOGP(error, "Layer {} ROF {} points to cluster entry {} outside {} clusters", + iLayer, irof, clEntry, clusArr.size()); + continue; + } const auto& cluster = clusArr[clEntry]; nTot++; // ── Parse pattern → center-of-gravity within bounding box ────────── - // The cluster stores the bounding-box top-left pixel (row, col). - // The pattern stream encodes [rowSpan, colSpan, bitmap...] for each cluster. - // We accumulate pixel row/col offsets to obtain a sub-pixel CoG correction. + // Keep this in sync with Clusterer::getClusterLocalCoordinates(). float cogDr{0.f}, cogDc{0.f}; // mean offsets from bbox origin (pixels) - if (patternsPtr) { + if (patternsPtr != nullptr) { + const auto pattOffset = patternOffsets[clEntry]; + const auto* pattIt = patternsPtr->data() + pattOffset; const uint8_t rowSpan = *pattIt++; const uint8_t colSpan = *pattIt++; const int nBytes = (rowSpan * colSpan + 7) / 8; @@ -267,10 +347,6 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", // ── Cluster local → global (CoG position) ───────────────────────────── // Get local coords of the bounding-box corner pixel, then apply the // fractional CoG displacement using the pixel pitch. - // Formula from detectorToLocalUnchecked: - // VD : xRow = 0.5*(width[lay]-pitchRow) - row*pitchRow → row↑ xRow↓ - // zCol = col*pitchCol + 0.5*(pitchCol-length) → col↑ zCol↑ - // MLOT: same structure with MLOT pitches float clLocX{0.f}, clLocZ{0.f}; o2::trk::SegmentationChip::detectorToLocalUnchecked( cluster.row, cluster.col, clLocX, clLocZ, @@ -281,24 +357,19 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", const float pitchCol = (cluster.subDetID == 0) ? o2::trk::SegmentationChip::PitchColVD : o2::trk::SegmentationChip::PitchColMLOT; - clLocX -= cogDr * pitchRow; // increasing row → decreasing xRow - clLocZ += cogDc * pitchCol; // increasing col → increasing zCol - const float yResponse = (cluster.subDetID == 0) ? yPlaneVD : yPlaneMLOT; - // For VD the L2G matrix is built in the *curved* local frame (quasi-Cartesian, - // origin at the beam axis). Convert flat (clLocX, 0) → curved (xC, yC) first. - // For MLOT (flat sensors) the local frame is already Cartesian: pass directly. - // clLocX is already in the flat frame from detectorToLocalUnchecked + CoG and - // does NOT need any further transformation for the residual comparison. + clLocX -= cogDr * pitchRow; // increasing row -> decreasing xRow + clLocZ += cogDc * pitchCol; // increasing col -> increasing zCol + o2::math_utils::Point3D locC; if (cluster.subDetID == 0) { auto cv = o2::trk::SegmentationChip::flatToCurved(cluster.layer, clLocX, 0.f); locC = {cv.X(), cv.Y(), clLocZ}; } else { - locC = {clLocX, yResponse, clLocZ}; + locC = {clLocX, yPlaneMLOT, clLocZ}; } auto gloC = gman->getMatrixL2G(cluster.chipID)(locC); - if (!hasMC || clusLabArr == nullptr) { + if (!hasAnyMC || clusLabArr == nullptr) { // No MC info: just fill geometry columns, leave residuals as 0 std::array data = { -1.f, -1.f, @@ -321,6 +392,10 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", const auto& lab = labels[0]; const int trID = lab.getTrackID(); const int evID = lab.getEventID(); + if (evID < 0 || evID >= (int)mc2hitVec.size()) { + nInvalidEvent++; + continue; + } // ── Find matching MC hit ──────────────────────────────────────────── const auto& mc2hit = mc2hitVec[evID]; @@ -330,7 +405,53 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", nNoMCHit++; continue; } - const auto& hit = (*hitVecPool[evID])[hitEntry->second]; + auto projectHitToResponsePlane = [&](const o2::trk::Hit& hit, float& hitLocX, float& hitLocZ) { + const auto& gloHend = hit.GetPos(); + const auto& gloHsta = hit.GetPosStart(); + o2::math_utils::Point3D locHsta = gman->getMatrixL2G(cluster.chipID) ^ (gloHsta); // inverse L2G + o2::math_utils::Point3D locHend = gman->getMatrixL2G(cluster.chipID) ^ (gloHend); // inverse L2G + + // Rather than the geometric midpoint, find where the track segment crosses + // the response plane. For VD convert the curved endpoints to the flat frame first. + if (cluster.subDetID == 0) { + auto flatSta = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHsta.X(), locHsta.Y()); + auto flatEnd = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHend.X(), locHend.Y()); + float x0 = flatSta.X(), y0 = flatSta.Y(), z0 = locHsta.Z(); + float dltx = flatEnd.X() - x0, dlty = flatEnd.Y() - y0, dltz = locHend.Z() - z0; + float r = (std::abs(dlty) > 1e-9f) ? (yPlaneVD - y0) / dlty : 0.5f; + hitLocX = x0 + r * dltx; + hitLocZ = z0 + r * dltz; + } else { + float x0 = locHsta.X(), y0 = locHsta.Y(), z0 = locHsta.Z(); + float dltx = locHend.X() - x0, dlty = locHend.Y() - y0, dltz = locHend.Z() - z0; + float r = (std::abs(dlty) > 1e-9f) ? (yPlaneMLOT - y0) / dlty : 0.5f; + hitLocX = x0 + r * dltx; + hitLocZ = z0 + r * dltz; + } + }; + + const o2::trk::Hit* bestHit = nullptr; + float hitLocX{0.f}, hitLocZ{0.f}; + float bestDist2 = std::numeric_limits::max(); + for (const auto ih : hitEntry->second) { + const auto& candHit = (*hitVecPool[evID])[ih]; + float candLocX{0.f}, candLocZ{0.f}; + projectHitToResponsePlane(candHit, candLocX, candLocZ); + const float dx = clLocX - candLocX; + const float dz = clLocZ - candLocZ; + const float dist2 = dx * dx + dz * dz; + if (dist2 < bestDist2) { + bestDist2 = dist2; + bestHit = &candHit; + hitLocX = candLocX; + hitLocZ = candLocZ; + } + } + if (bestHit == nullptr) { + nNoMCHit++; + continue; + } + const auto& hit = *bestHit; const float pt = TMath::Hypot(hit.GetPx(), hit.GetPy()); // ── Hit global midpoint ──────────────────────────────────────────── @@ -341,32 +462,6 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", 0.5f * (gloHend.Y() + gloHsta.Y()), 0.5f * (gloHend.Z() + gloHsta.Z())); - // ── Hit global → local ───────────────────────────── - o2::math_utils::Point3D locHsta = gman->getMatrixL2G(cluster.chipID) ^ (gloHsta); // inverse L2G - o2::math_utils::Point3D locHend = gman->getMatrixL2G(cluster.chipID) ^ (gloHend); // inverse L2G - - // ── Propagate hit segment to the sensor response surface ─────────────── - // Rather than the geometric midpoint, find where the track segment crosses - // the response plane (y = responseYShift in the flat local frame). - // For VD (curved): convert both endpoints to flat frame first. - // For ML/OT (flat): use local coordinates directly. - float hitLocX{0.f}, hitLocZ{0.f}; - if (cluster.subDetID == 0) { // VD – curved sensor - auto flatSta = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHsta.X(), locHsta.Y()); - auto flatEnd = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHend.X(), locHend.Y()); - float x0 = flatSta.X(), y0 = flatSta.Y(), z0 = locHsta.Z(); - float dltx = flatEnd.X() - x0, dlty = flatEnd.Y() - y0, dltz = locHend.Z() - z0; - float r = (std::abs(dlty) > 1e-9f) ? (yPlaneVD - y0) / dlty : 0.5f; - hitLocX = x0 + r * dltx; - hitLocZ = z0 + r * dltz; - } else { // ML/OT – flat sensor - float x0 = locHsta.X(), y0 = locHsta.Y(), z0 = locHsta.Z(); - float dltx = locHend.X() - x0, dlty = locHend.Y() - y0, dltz = locHend.Z() - z0; - float r = (std::abs(dlty) > 1e-9f) ? (yPlaneMLOT - y0) / dlty : 0.5f; - hitLocX = x0 + r * dltx; - hitLocZ = z0 + r * dltz; - } - nValid++; std::array data = { (float)evID, (float)trID, @@ -387,6 +482,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", LOGP(info, "Total clusters: {}", nTot); LOGP(info, "Valid (hit matched): {}", nValid); LOGP(info, "Invalid/noise MC labels: {}", nInvalidLabel); + LOGP(info, "Invalid MC event IDs: {}", nInvalidEvent); LOGP(info, "MC hit not found: {}", nNoMCHit); // ── Visualisation ────────────────────────────────────────────────────────── auto canvGlobal = new TCanvas("canvGlobal", "Cluster global positions", 1600, 800); @@ -400,25 +496,25 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", auto canvRes = new TCanvas("canvRes", "Residuals (cluster - hit) [cm]", 1600, 1200); canvRes->Divide(2, 3); canvRes->cd(1)->SetLogy(); - nt.Draw("hitLocX-clusLocX>>h_dx_VD(200,-0.02,0.02)", "subdet==0&&event>=0"); + nt.Draw("clusLocX-hitLocX>>h_dx_VD(200,-0.02,0.02)", "subdet==0&&event>=0"); canvRes->cd(2)->SetLogy(); - nt.Draw("hitLocZ-clusLocZ>>h_dz_VD(200,-0.02,0.02)", "subdet==0&&event>=0"); + nt.Draw("clusLocZ-hitLocZ>>h_dz_VD(200,-0.02,0.02)", "subdet==0&&event>=0"); canvRes->cd(3)->SetLogy(); - nt.Draw("hitLocX-clusLocX>>h_dx_MLOT(200,-0.02,0.02)", "subdet==1&&event>=0"); + nt.Draw("clusLocX-hitLocX>>h_dx_MLOT(200,-0.02,0.02)", "subdet==1&&event>=0"); canvRes->cd(4)->SetLogy(); - nt.Draw("hitLocZ-clusLocZ>>h_dz_MLOT(200,-0.02,0.02)", "subdet==1&&event>=0"); + nt.Draw("clusLocZ-hitLocZ>>h_dz_MLOT(200,-0.02,0.02)", "subdet==1&&event>=0"); canvRes->cd(5)->SetLogz(); - nt.Draw("hitLocX-clusLocX:hitLocZ-clusLocZ>>h_dxdz_VD(200,-0.02,0.02,200,-0.02,0.02)", "subdet==0&&event>=0", "colz"); + nt.Draw("clusLocX-hitLocX:clusLocZ-hitLocZ>>h_dxdz_VD(200,-0.02,0.02,200,-0.02,0.02)", "subdet==0&&event>=0", "colz"); canvRes->cd(6); - nt.Draw("hitLocX-clusLocX:hitLocZ-clusLocZ>>h_dxdz_MLOT(200,-0.02,0.02,200,-0.02,0.02)", "subdet==1&&event>=0", "colz"); + nt.Draw("clusLocX-hitLocX:clusLocZ-hitLocZ>>h_dxdz_MLOT(200,-0.02,0.02,200,-0.02,0.02)", "subdet==1&&event>=0", "colz"); canvRes->SaveAs("trk_residuals.png"); auto canvResVsLayer = new TCanvas("canvResVsLayer", "Residuals vs layer", 1600, 600); canvResVsLayer->Divide(2, 1); canvResVsLayer->cd(1); - nt.Draw("hitLocX-clusLocX:layer>>h_dx_vs_lay(20,0,20,200,-0.02,0.02)", "event>=0", "prof"); + nt.Draw("clusLocX-hitLocX:layer>>h_dx_vs_lay(20,0,20,200,-0.02,0.02)", "event>=0", "prof"); canvResVsLayer->cd(2); - nt.Draw("hitLocZ-clusLocZ:layer>>h_dz_vs_lay(20,0,20,200,-0.02,0.02)", "event>=0", "prof"); + nt.Draw("clusLocZ-hitLocZ:layer>>h_dz_vs_lay(20,0,20,200,-0.02,0.02)", "event>=0", "prof"); canvResVsLayer->SaveAs("trk_residuals_vs_layer.png"); fout.cd(); 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/reconstruction/include/TRKReconstruction/Clusterer.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h index bcd95155f533f..3d30eb5068efe 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/include/TRKReconstruction/TimeFrame.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TimeFrame.h deleted file mode 100644 index 005237fe28839..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TimeFrame.h +++ /dev/null @@ -1,68 +0,0 @@ -// 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 TimeFrame.h -/// \brief TRK TimeFrame class derived from ITS TimeFrame -/// - -#ifndef ALICEO2_TRK_TIMEFRAME_H -#define ALICEO2_TRK_TIMEFRAME_H - -#include "ITStracking/TimeFrame.h" -#include "ITStracking/Constants.h" -#include "ITStracking/Configuration.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include -#include -#include -#include - -#include - -class TTree; - -namespace o2 -{ -namespace 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 -{ - 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 gman TRK geometry manager instance - /// \param config Configuration parameters for hit reconstruction - int loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config); - - /// 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); -}; - -} // namespace trk -} // namespace o2 - -#endif // ALICEO2_TRK_TIMEFRAME_H 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); diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TimeFrame.cxx b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TimeFrame.cxx deleted file mode 100644 index 957560aea8cae..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TimeFrame.cxx +++ /dev/null @@ -1,225 +0,0 @@ -// 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 TimeFrame.cxx -/// \brief TRK TimeFrame implementation -/// - -#include "TRKReconstruction/TimeFrame.h" -#include "TRKSimulation/Hit.h" -#include "TRKBase/GeometryTGeo.h" -#include "Framework/Logger.h" -#include "SimulationDataFormat/MCEventHeader.h" -#include -#include -#include -#include - -using o2::its::clearResizeBoundedVector; - -namespace o2::trk -{ - -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 - 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); - } - vtxLookupTable.init(); // pre-allocate without vertices - this->setROFVertexLookupTable(vtxLookupTable); - - // 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(); - } - - // 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 (mClusterSize is now per-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]); - clearResizeBoundedVector(this->mClusterSize[iLayer], clusterCountPerLayer[iLayer], this->mMemoryPool.get()); - } - - 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) { - 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()); - - // One shared MC label container for all layers - auto* labels = new dataformats::MCTruthContainer(); - - int hitCounter{0}; - 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 - const int clusterIdxInLayer = this->mUnsortedClusters[layer].size(); - this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), clusterIdxInLayer); - this->addClusterExternalIndexToLayer(layer, hitCounter); - MCCompLabel label{hit.GetTrackID(), static_cast(iEvent), 0}; - labels->addElement(hitCounter, label); - this->mClusterSize[layer][clusterIdxInLayer] = 1; - 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 - } - } - } - - // Set the shared labels container for all layers - for (int iLayer = 0; iLayer < NLayers; ++iLayer) { - this->mClusterLabels[iLayer] = labels; - } - - return nRofs; -} - -template -void TimeFrame::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup, uint32_t rofLength) -{ - auto mcheader = new o2::dataformats::MCEventHeader; - mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); - - this->mPrimaryVertices.clear(); - - int iRof{0}; - for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { - mcHeaderTree->GetEntry(iEvent); - o2::its::Vertex vertex; - 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); - this->addPrimaryVertex(vertex); - 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 class TimeFrame<11>; - -} // namespace o2::trk 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/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx deleted file mode 100644 index cb4cc3897ae9e..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx +++ /dev/null @@ -1,439 +0,0 @@ -// 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 -#include - -#include "DetectorsBase/GeometryManager.h" -#include "ITStracking/TimeFrame.h" -#include "ITStracking/Configuration.h" -#include "Field/MagneticField.h" -#include "Field/MagFieldParam.h" -#include "Framework/ControlService.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/CCDBParamSpec.h" -#include "SimulationDataFormat/MCEventHeader.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "TRKBase/GeometryTGeo.h" -#include "TRKBase/SegmentationChip.h" -#include "TRKSimulation/Hit.h" -#include "TRKReconstruction/TimeFrame.h" -#include "TRKWorkflow/TrackerSpec.h" -#include - -#ifdef O2_WITH_ACTS -#include "TRKReconstruction/TrackerACTS.h" -#endif - -#include -#include - -namespace o2 -{ -using namespace framework; -namespace trk -{ - -TrackerDPL::TrackerDPL(std::shared_ptr gr, - bool isMC, - const std::string& hitRecoConfigFileName, - o2::gpu::gpudatatypes::DeviceType dType) -{ - if (!hitRecoConfigFileName.empty()) { - std::ifstream configFile(hitRecoConfigFileName); - mHitRecoConfig = nlohmann::json::parse(configFile); - } - - // 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 -} - -void TrackerDPL::stop() -{ - LOGF(info, "CPU Reconstruction total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); -} - -std::vector TrackerDPL::createTrackingParamsFromConfig() -{ - std::vector trackingParams; - - 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; - } - - for (const auto& paramConfig : mHitRecoConfig["trackingparams"]) { - o2::its::TrackingParameters params; - - // 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 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 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); - } - - trackingParams.push_back(params); - } - - LOGP(info, "Loaded {} tracking parameter sets from configuration", trackingParams.size()); - return trackingParams; -} - -void TrackerDPL::run(ProcessingContext& pc) -{ - 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 - } - - o2::trk::TimeFrame<11> timeFrame; - o2::its::TrackerTraits<11> itsTrackerTraits; - o2::its::Tracker<11> itsTracker(&itsTrackerTraits); - 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(); - itsTracker.adoptTimeFrame(timeFrame); - - 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(); - - itsTrackerTraits.updateTrackingParameters(trackingParams); - -#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(); - } -#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(trackingParams[iter], 11); - itsTrackerTraits.computeLayerTracklets(iter, -1); - LOGP(info, "Number of tracklets in iteration {}: {}", iter, timeFrame.getNumberOfTracklets()); - itsTrackerTraits.computeLayerCells(iter); - LOGP(info, "Number of cells in iteration {}: {}", iter, timeFrame.getNumberOfCells()); - itsTrackerTraits.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()); - } - const auto trackingLoopElapsedMs = std::chrono::duration_cast(std::chrono::steady_clock::now() - trackingLoopStart).count(); - LOGP(info, "Tracking iterations block took {} ms", trackingLoopElapsedMs); - - 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()); - - int totalTracks = allTracks.size(); - int goodTracks = 0; - int fakeTracks = 0; - - for (const auto& label : allLabels) { - if (label.isFake()) { - fakeTracks++; - } else { - goodTracks++; - } - } - - LOGP(info, "=== Tracking Summary ==="); - LOGP(info, "Total tracks reconstructed: {}", totalTracks); - 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); - - LOGP(info, "Tracks and MC labels streamed to output"); - - 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); -} - -// 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); -} - -DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, o2::gpu::gpudatatypes::DeviceType dType) -{ - std::vector inputs; - std::vector outputs; - outputs.emplace_back("TRK", "TRACKS", 0, Lifetime::Timeframe); - auto ggRequest = std::make_shared(false, // orbitResetTime - false, // GRPECS=true - false, // GRPLHCIF - false, // GRPMagField - false, // askMatLUT - o2::base::GRPGeomRequest::None, // geometry, but ignored until it will be put in the CCDB - inputs, - true); - - if (!hitRecoConfig.empty()) { - outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); - return DataProcessorSpec{ - "trk-hits-tracker", - {}, - outputs, - AlgorithmSpec{adaptFromTask(ggRequest, - useMC, - hitRecoConfig, - dType)}, - Options{ConfigParamSpec{"max-loops", VariantType::Int, 1, {"max number of loops"}} -#ifdef O2_WITH_ACTS - , - {"useACTS", o2::framework::VariantType::Bool, false, {"Use ACTS for tracking"}} -#endif - }}; - } - - inputs.emplace_back("dummy", "TRK", "DUMMY", 0, Lifetime::Timeframe); - - constexpr bool expectClusterInputs = false; - if (expectClusterInputs) { - 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); - } - - // 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")); - - // 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) { - // 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", "TRKTrackMC2ROF", 0, Lifetime::Timeframe); - } - - return DataProcessorSpec{ - "trk-tracker", - inputs, - outputs, - AlgorithmSpec{adaptFromTask(ggRequest, - useMC, - hitRecoConfig, - dType)}, - Options{}}; -} - -} // namespace trk -} // namespace o2 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..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,21 +9,8 @@ // 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" -#include "ITStracking/TrackingConfigParam.h" -#include "ITStracking/Configuration.h" #include "Framework/CallbacksPolicy.h" #include "Framework/ConfigContext.h" @@ -52,11 +39,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 +50,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 +58,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 01f370ca8c4af38375b301e4bb450014fdbfad79 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Wed, 13 May 2026 14:12:58 +0200 Subject: [PATCH 072/102] DPL: add actual handling of --aod-origin-level-mapping (#15396) --- Framework/Core/src/Plugin.cxx | 6 +++++- Framework/Core/src/runDataProcessing.cxx | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Framework/Core/src/Plugin.cxx b/Framework/Core/src/Plugin.cxx index 503133442e794..82599310eafe9 100644 --- a/Framework/Core/src/Plugin.cxx +++ b/Framework/Core/src/Plugin.cxx @@ -70,6 +70,10 @@ auto lookForCommandLineAODOptions = [](ConfigParamRegistry& registry, int argc, O2_SIGNPOST_EVENT_EMIT(capabilities, sid, "DiscoverAODOptionsInCommandLineCapability", "AOD options found in arguments. Populating from them."); return true; } + if (arg.starts_with("--aod-origin-")) { + O2_SIGNPOST_EVENT_EMIT(capabilities, sid, "DiscoverAODOptionsInCommandLineCapability", "AOD options found in arguments. Populating from them."); + return true; + } } return false; }; @@ -150,7 +154,7 @@ struct DiscoverAODOptionsInCommandLine : o2::framework::ConfigDiscoveryPlugin { bool injectOption = true; for (size_t i = 0; i < argc; i++) { std::string_view arg = argv[i]; - if (!arg.starts_with("--aod-writer-") && !arg.starts_with("--aod-parent-")) { + if (!arg.starts_with("--aod-writer-") && !arg.starts_with("--aod-parent-") && !arg.starts_with("--aod-origin-")) { continue; } std::string key = arg.data() + 2; diff --git a/Framework/Core/src/runDataProcessing.cxx b/Framework/Core/src/runDataProcessing.cxx index d012e1656efc4..c58f8e7287b3b 100644 --- a/Framework/Core/src/runDataProcessing.cxx +++ b/Framework/Core/src/runDataProcessing.cxx @@ -2052,6 +2052,7 @@ int runStateMachine(DataProcessorSpecs const& workflow, "--aod-max-io-rate", "--aod-parent-access-level", "--aod-parent-base-path-replacement", + "--aod-origin-level-mapping", "--driver-client-backend", "--fairmq-ipc-prefix", "--readers", From f781bf7b234974bd7664399d823e4af3facd0871 Mon Sep 17 00:00:00 2001 From: shahoian Date: Tue, 12 May 2026 14:54:09 +0200 Subject: [PATCH 073/102] Throw on the mismatch only if N decoded clusters is > N declared Due to the ensureContinuousROF some stray ROFs with clusters may be eliminated, which was not accounted in the cluster vector. --- .../include/ITSMFTReconstruction/CTFCoder.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/CTFCoder.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/CTFCoder.h index 4f9bc90c1c758..76ac8878562de 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/CTFCoder.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/CTFCoder.h @@ -356,8 +356,10 @@ void CTFCoder::decompress(const CompressedClusters& compCl, VROF& rofRecVec, assert(chipCount == compCl.header.nChips); if (clCount != compCl.header.nClusters) { - LOG(error) << "expected " << compCl.header.nClusters << " but counted " << clCount << " in ROFRecords"; - throw std::runtime_error("mismatch between expected and counter number of clusters"); + LOGP(error, "expected {} but counted {} clusters in {} ROFRecords", compCl.header.nClusters, clCount, compCl.header.nROFs); + if (clCount > compCl.header.nClusters) { + throw std::runtime_error("mismatch between expected and counter number of clusters"); + } } } @@ -456,8 +458,10 @@ void CTFCoder::decompress(const CompressedClusters& compCl, VROF& rofRecVec, assert(chipCount == compCl.header.nChips); if (clCount != compCl.header.nClusters) { - LOG(error) << "expected " << compCl.header.nClusters << " but counted " << clCount << " in ROFRecords"; - throw std::runtime_error("mismatch between expected and counter number of clusters"); + LOGP(error, "expected {} but counted {} clusters in {} ROFRecords", compCl.header.nClusters, clCount, compCl.header.nROFs); + if (clCount > compCl.header.nClusters) { + throw std::runtime_error("mismatch between expected and counter number of clusters"); + } } } From b9e3cd3b36bc36257f6213cffb2c80c669a8303c Mon Sep 17 00:00:00 2001 From: shahoian Date: Tue, 12 May 2026 18:40:08 +0200 Subject: [PATCH 074/102] Fix clusters/patterns/digits after ensureContinuousROF --- .../include/ITSMFTReconstruction/Clusterer.h | 2 + .../include/ITSMFTReconstruction/LookUp.h | 2 +- .../include/ITSMFTWorkflow/STFDecoderSpec.h | 4 +- .../common/workflow/src/STFDecoderSpec.cxx | 93 +++++++++++++++++-- 4 files changed, 91 insertions(+), 10 deletions(-) diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/Clusterer.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/Clusterer.h index 0bdbb701a9356..dd3052e2cc5bd 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/Clusterer.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/Clusterer.h @@ -236,6 +236,8 @@ class Clusterer ///< load the dictionary of cluster topologies void loadDictionary(const std::string& fileName) { mPattIdConverter.loadDictionary(fileName); } void setDictionary(const TopologyDictionary* dict) { mPattIdConverter.setDictionary(dict); } + const TopologyDictionary& getDictionary() const { return mPattIdConverter.getDictionary(); } + auto& getPattIdConverter() const { return mPattIdConverter; } TStopwatch& getTimer() { return mTimer; } // cannot be const TStopwatch& getTimerMerge() { return mTimerMerge; } // cannot be const diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/LookUp.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/LookUp.h index 3537a1f408886..4f84a838efc70 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/LookUp.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/LookUp.h @@ -43,7 +43,7 @@ class LookUp bool isGroup(int id) const { return mDictionary.isGroup(id); } int size() const { return mDictionary.getSize(); } auto getPattern(int id) const { return mDictionary.getPattern(id); } - auto getDictionaty() const { return mDictionary; } + auto& getDictionary() const { return mDictionary; } private: TopologyDictionary mDictionary; diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h index 29b9f75bcbc4e..1739822d121cf 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h @@ -75,7 +75,9 @@ class STFDecoder : public Task void finalize(); void reset(); std::unique_ptr setupClusterer(const std::string& dictName); - void ensureContinuousROF(const std::vector& in, std::vector& out, int lr, int nROFsTF, const char* name); + bool ensureContinuousROF(const std::vector& in, std::vector& out, int lr, int nROFsTF, const char* name); + void rectifyDigits(std::vector& rofVec, std::vector& digVec); + void rectifyClusters(std::vector& rofVec, std::vector& clusVec, std::vector& pattVec); TStopwatch mTimer; bool mDoClusters = false; diff --git a/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx index 3cc88752d290c..cc7200a1120a6 100644 --- a/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx @@ -258,10 +258,14 @@ void STFDecoder::run(ProcessingContext& pc) } } if (mDoDigits) { - pc.outputs().snapshot(Output{orig, "DIGITS", iLayer}, digVec); std::vector expDigRofVec(nROFsTF); - ensureContinuousROF(digROFVec, expDigRofVec, iLayer, nROFsTF, "digits"); - pc.outputs().snapshot(Output{orig, "DIGITSROF", iLayer}, digROFVec); + if (ensureContinuousROF(digROFVec, expDigRofVec, iLayer, nROFsTF, "digits")) { + auto oldNDig = digVec.size(); + rectifyDigits(expDigRofVec, digVec); + LOGP(warn, "Rectified {} digits out of original {} on layer {} following ensureContinuousROF", digVec.size(), oldNDig, iLayer); + } + pc.outputs().snapshot(Output{orig, "DIGITS", iLayer}, digVec); + pc.outputs().snapshot(Output{orig, "DIGITSROF", iLayer}, expDigRofVec); mEstNDig[iLayer] = std::max(mEstNDig[iLayer], size_t(digVec.size() * 1.2)); if (mDoCalibData) { pc.outputs().snapshot(Output{orig, "GBTCALIB", iLayer}, calVec); @@ -272,7 +276,11 @@ void STFDecoder::run(ProcessingContext& pc) if (mDoClusters) { // we are not obliged to create vectors which are not requested, but other devices might not know the options of this one std::vector expClusRofVec(nROFsTF); - ensureContinuousROF(clusROFVec, expClusRofVec, iLayer, nROFsTF, "clusters"); + if (ensureContinuousROF(clusROFVec, expClusRofVec, iLayer, nROFsTF, "clusters")) { + auto oldNClus = clusCompVec.size(), oldNPatt = clusPattVec.size(); + rectifyClusters(expClusRofVec, clusCompVec, clusPattVec); + LOGP(warn, "Rectified {} clusters and {} patterns out of original {} and {} on layer {} following ensureContinuousROF", clusCompVec.size(), clusPattVec.size(), oldNClus, oldNPatt, iLayer); + } pc.outputs().snapshot(Output{orig, "COMPCLUSTERS", iLayer}, clusCompVec); pc.outputs().snapshot(Output{orig, "PATTERNS", iLayer}, clusPattVec); pc.outputs().snapshot(Output{orig, "CLUSTERSROF", iLayer}, expClusRofVec); @@ -416,7 +424,7 @@ void STFDecoder::reset() ///_______________________________________ template -void STFDecoder::ensureContinuousROF(const std::vector& rofVec, std::vector& expROFVec, int lr, int nROFsTF, const char* name) +bool STFDecoder::ensureContinuousROF(const std::vector& rofVec, std::vector& expROFVec, int lr, int nROFsTF, const char* name) { const auto& par = AlpideParam::Instance(); // ensure that the rof output is continuous @@ -465,13 +473,82 @@ void STFDecoder::ensureContinuousROF(const std::vector& rofV } } } - int prevFirst{0}; + int prevLast{0}; + bool reReference = false; // in case a non-last ROF with non-0 entries is removed, ROF references need to be shifted and clusters/digits rewritten for (auto& rof : expROFVec) { if (rof.getFirstEntry() < 0) { - rof.setFirstEntry(prevFirst); + rof.setFirstEntry(prevLast); + } else if (rof.getFirstEntry() != prevLast) { + reReference = true; // there is jump + } + prevLast = rof.getFirstEntry() + rof.getNEntries(); + } + return reReference; +} + +///_______________________________________ +template +void STFDecoder::rectifyDigits(std::vector& rofVec, std::vector& digVec) +{ + // following ensureContinuousROF call some old ROFs might have been dropped, need to rebuild digits vector and rereference ROF + std::vector digVecTmp; + digVecTmp.reserve(digVec.size()); + auto beg0 = digVec.begin(); + for (auto& rof : rofVec) { + int firstEntry = digVecTmp.size(); + if (rof.getNEntries()) { + auto beg = beg0 + rof.getFirstEntry(), end = beg + rof.getNEntries(); + std::copy(beg, end, std::back_inserter(digVecTmp)); + } + rof.setFirstEntry(firstEntry); + } + digVec.swap(digVecTmp); +} + +///_______________________________________ +template +void STFDecoder::rectifyClusters(std::vector& rofVec, std::vector& clusVec, std::vector& pattVec) +{ + // following ensureContinuousROF call some old ROFs might have been dropped, need to rebuild clusters and patterns vectors and rereference ROF + std::vector clusVecTmp; + clusVecTmp.reserve(clusVec.size()); + std::vector pattVecTmp; + pattVecTmp.reserve(pattVec.size()); + const auto& dict = mClusterer->getDictionary(); + auto begCl0 = clusVec.begin(), begClForPatt = begCl0; + auto pattIt = pattVec.begin(); + + auto skipToLastPattern = [&begClForPatt, &pattIt, &dict](const decltype(begCl0) tgt) { + while (begClForPatt < tgt) { // iterate clusters skipping their patterns until we reach targed cluster + const auto& clp = *begClForPatt; + auto pattID = clp.getPatternID(); + if (pattID == itsmft::CompCluster::InvalidPatternID || dict.isGroup(pattID)) { + ClusterPattern::skipPattern(pattIt); + } + begClForPatt++; + } + }; + + for (auto& rof : rofVec) { + int firstEntry = clusVecTmp.size(); + if (rof.getNEntries()) { + auto begClROF = begCl0 + rof.getFirstEntry(), endClROF = begClROF + rof.getNEntries(); // clusters to copy start/end here + if (mDoPatterns) { + if (begClForPatt > begClROF) { // normally should no happen unless original ROFs were not ordered + begClForPatt = begCl0; // start from the beginning + } + skipToLastPattern(begClROF); // iterate clusters skipping their patterns until we reach the 1st cluster to be copied + auto begPattToCopy = pattIt; // the 1st pattern corresponding to the needed ROF + skipToLastPattern(endClROF); // iterate clusters skipping their patterns until we reach the last cluster to be copied + std::copy(begPattToCopy, pattIt, std::back_inserter(pattVecTmp)); + } + std::copy(begClROF, endClROF, std::back_inserter(clusVecTmp)); } - prevFirst = rof.getFirstEntry(); + // copy patterns corresponding to this ROF + rof.setFirstEntry(firstEntry); } + clusVec.swap(clusVecTmp); + pattVec.swap(pattVecTmp); } ///_______________________________________ From aafbebfdbc2f211a12d361a901e2fd8da41a27f4 Mon Sep 17 00:00:00 2001 From: Stefano Cannito <143754257+scannito@users.noreply.github.com> Date: Wed, 13 May 2026 19:54:37 +0200 Subject: [PATCH 075/102] [ALICE 3] TRK: Fix sensitive volumes definition for FT3 (#15397) * Fix sensitive volumes name for FT3 * Removed unused variables --- .../include/FT3Simulation/Detector.h | 22 +-- .../ALICE3/FT3/simulation/src/Detector.cxx | 126 ++++++++++-------- 2 files changed, 81 insertions(+), 67 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h index 8bc4b7f634d7c..361d94463ef56 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h @@ -15,16 +15,20 @@ #ifndef ALICEO2_FT3_DETECTOR_H_ #define ALICEO2_FT3_DETECTOR_H_ -#include // for vector -#include "DetectorsBase/GeometryManager.h" // for getSensID +#include "Rtypes.h" // for Int_t, Double_t, Float_t, Bool_t, etc + #include "DetectorsBase/Detector.h" // for Detector +#include "DetectorsBase/GeometryManager.h" // for getSensID #include "DetectorsCommonDataFormats/DetID.h" // for Detector #include "ITSMFTSimulation/Hit.h" // for Hit -#include "Rtypes.h" // for Int_t, Double_t, Float_t, Bool_t, etc -#include "TArrayD.h" // for TArrayD -#include "TGeoManager.h" // for gGeoManager, TGeoManager (ptr only) -#include "TLorentzVector.h" // for TLorentzVector -#include "TVector3.h" // for TVector3 + +#include "TArrayD.h" // for TArrayD +#include "TGeoManager.h" // for gGeoManager, TGeoManager (ptr only) +#include "TLorentzVector.h" // for TLorentzVector +#include "TVector3.h" // for TVector3 + +#include +#include class FairVolume; class TGeoVolume; @@ -111,8 +115,8 @@ class Detector : public o2::base::DetImpl void buildFT3ScopingV3(); protected: - std::vector mLayerID; std::array, 2> mLayerName; // Two sets of layer names, one per direction (forward/backward) + std::unordered_map mActiveSensorMap; private: /// this is transient data about track passing the sensor @@ -145,7 +149,7 @@ class Detector : public o2::base::DetImpl template friend class o2::base::DetImpl; - ClassDefOverride(Detector, 1); + ClassDefOverride(Detector, 2); }; } // namespace o2::ft3 diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx index 2a9a9633cdd11..1f9f95c1914dd 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx @@ -12,32 +12,34 @@ /// \file Detector.cxx /// \brief Implementation of the Detector class -#include "ITSMFTSimulation/Hit.h" -#include "FT3Base/GeometryTGeo.h" #include "FT3Simulation/Detector.h" -#include "FT3Simulation/FT3Layer.h" -#include "FT3Base/FT3BaseParam.h" #include "DetectorsBase/Stack.h" +#include "ITSMFTSimulation/Hit.h" #include "SimulationDataFormat/TrackReference.h" +#include "FT3Base/FT3BaseParam.h" +#include "FT3Base/GeometryTGeo.h" +#include "FT3Simulation/FT3Layer.h" + // FairRoot includes -#include "FairDetector.h" // for FairDetector -#include // for LOG, LOG_IF -#include "FairRootManager.h" // for FairRootManager -#include "FairRun.h" // for FairRun -#include "FairRuntimeDb.h" // for FairRuntimeDb -#include "FairVolume.h" // for FairVolume +#include "FairDetector.h" // for FairDetector +#include "FairRootManager.h" // for FairRootManager #include "FairRootManager.h" +#include "FairRun.h" // for FairRun +#include "FairRuntimeDb.h" // for FairRuntimeDb +#include "FairVolume.h" // for FairVolume #include "TGeoManager.h" // for TGeoManager, gGeoManager -#include "TGeoTube.h" // for TGeoTube #include "TGeoPcon.h" // for TGeoPcon +#include "TGeoTube.h" // for TGeoTube #include "TGeoVolume.h" // for TGeoVolume, TGeoVolumeAssembly #include "TString.h" // for TString, operator+ #include "TVirtualMC.h" // for gMC, TVirtualMC #include "TVirtualMCStack.h" // for TVirtualMCStack +#include // for LOG, LOG_IF + #include // for NULL, snprintf #define MAX_SENSORS 2000 @@ -75,7 +77,6 @@ void Detector::buildBasicFT3(const FT3BaseParam& param) const auto Layerx2X0 = param.Layerx2X0; mLayerName[IdxBackwardDisks].resize(numberOfLayers); mLayerName[IdxForwardDisks].resize(numberOfLayers); - mLayerID.clear(); for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { @@ -117,7 +118,6 @@ void Detector::buildFT3V1() mLayerName[IdxBackwardDisks].resize(numberOfLayers); mLayerName[IdxForwardDisks].resize(numberOfLayers); - mLayerID.clear(); for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { @@ -165,7 +165,6 @@ void Detector::buildFT3V3b() mLayerName[IdxBackwardDisks].resize(numberOfLayers); mLayerName[IdxForwardDisks].resize(numberOfLayers); - mLayerID.clear(); for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { @@ -224,7 +223,6 @@ void Detector::buildFT3NewVacuumVessel() mLayerName[IdxBackwardDisks].resize(numberOfLayers); mLayerName[IdxForwardDisks].resize(numberOfLayers); - mLayerID.clear(); for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { @@ -280,8 +278,6 @@ void Detector::buildFT3ScopingV3() LayerConfig{220., 20.0, 68.f, layersx2X0}}; const std::array enabled{true, true, true, true, true, true}; // To enable or disable layers for debug purpose - mLayerID.clear(); - for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { mLayerName[direction].clear(); const std::array& layerConfig = (direction == IdxBackwardDisks) ? layersConfigCSide : layersConfigASide; @@ -330,7 +326,6 @@ void Detector::buildFT3Scoping() mLayerName[IdxBackwardDisks].resize(numberOfLayers); mLayerName[IdxForwardDisks].resize(numberOfLayers); - mLayerID.clear(); for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { @@ -367,8 +362,8 @@ Detector::Detector(const Detector& rhs) /// Container for data points mHits(o2::utils::createSimVector()) { - mLayerID = rhs.mLayerID; mLayerName = rhs.mLayerName; + mActiveSensorMap = rhs.mActiveSensorMap; } //_________________________________________________________________________________________________ @@ -399,8 +394,8 @@ Detector& Detector::operator=(const Detector& rhs) // base class assignment base::Detector::operator=(rhs); - mLayerID = rhs.mLayerID; mLayerName = rhs.mLayerName; + mActiveSensorMap = rhs.mActiveSensorMap; mLayers = rhs.mLayers; mTrackData = rhs.mTrackData; @@ -427,11 +422,15 @@ bool Detector::ProcessHits(FairVolume* vol) return kFALSE; } - int lay = 0, volID = vol->getMCid(); - while ((lay <= mLayerID.size()) && (volID != mLayerID[lay])) { - ++lay; + int volID = vol->getMCid(); + + auto it = mActiveSensorMap.find(volID); + if (it == mActiveSensorMap.end()) { + return kFALSE; // Not a sensitive volume } + int lay = it->second; + auto stack = (o2::data::Stack*)fMC->GetStack(); bool startHit = false, stopHit = false; @@ -605,58 +604,69 @@ void Detector::createGeometry() A3IPvac->AddNode(volIFT3, 2, new TGeoTranslation(0., 0., 0.)); vALIC->AddNode(volFT3, 2, new TGeoTranslation(0., 30., 0.)); } - - for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { - std::string directionString = direction ? "Forward" : "Backward"; - LOG(info) << " Registering FT3 " << directionString << " LayerIDs for " << mLayers[direction].size() << " layers:"; - for (int iLayer = 0; iLayer < mLayers[direction].size(); iLayer++) { - auto layerID = gMC ? TVirtualMC::GetMC()->VolId(Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), direction, iLayer)) : 0; - mLayerID.push_back(layerID); - LOG(info) << " " << directionString << " layer " << iLayer << " LayerID " << layerID; - } - } } //_________________________________________________________________________________________________ void Detector::defineSensitiveVolumes() { TGeoManager* geoManager = gGeoManager; - TGeoVolume* v; - TString volumeName; - LOG(info) << "Adding FT3 Sensitive Volumes"; + // Get the flat list of ALL volumes present in the geometry + TObjArray* allVolumes = geoManager->GetListOfVolumes(); + int nVolumes = allVolumes->GetEntriesFast(); + + LOG(info) << "Adding FT3 Sensitive Volumes by iterating over all geometry volumes..."; for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { for (int iLayer = 0; iLayer < getNumberOfLayers(); iLayer++) { - LOG(info) << "Adding FT3 Sensitive Volume for direction " << direction << " layer " << iLayer << "/" << getNumberOfLayers(); - volumeName = o2::ft3::GeometryTGeo::getFT3SensorPattern() + std::to_string(iLayer); int iSens = 0; - /*if (mLayers[direction][iLayer].getIsInMiddleLayer()) { // ML disks - const std::string sensorName = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), direction, iLayer); - v = geoManager->GetVolume(sensorName.c_str()); - if (!v) { - geoManager->GetListOfVolumes()->ls(); - LOG(fatal) << "Could not find volume " << sensorName << " for direction " << direction << " layer " << iLayer; + + // Build the "signatures" (prefixes) of the names for the various layouts for this specific layer and direction: + + // 1. Trapezoidal/Cylindrical (format: FT3Sensor__) + std::string sig1 = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), direction, iLayer); + + // 2. Segmented front/back (format: FT3Sensor_front___...) + std::string sig2 = "FT3Sensor_front_" + std::to_string(iLayer) + "_" + std::to_string(direction); + std::string sig3 = "FT3Sensor_back_" + std::to_string(iLayer) + "_" + std::to_string(direction); + + // 3. SegmentedStave (format: FT3Sensor___...) + // Add the trailing underscore to avoid confusing it with sig1 + std::string sig4 = "FT3Sensor_" + std::to_string(iLayer) + "_" + std::to_string(direction) + "_"; + + // Iterate over all existing volumes to find matches + for (int i = 0; i < nVolumes; ++i) { + TGeoVolume* v = (TGeoVolume*)allVolumes->At(i); + std::string vName = v->GetName(); + + // Explicitly exclude the inactive silicon regions created in FT3Module + if (vName.find("Inactive") != std::string::npos || vName.find("inactive") != std::string::npos) { + continue; } - AddSensitiveVolume(v); - iSens++; - } else { // OT disks*/ - for (int sensor_count = 0; sensor_count < MAX_SENSORS; ++sensor_count) { - std::string sensor_name_front = "FT3Sensor_front_" + std::to_string(iLayer) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); - std::string sensor_name_back = "FT3Sensor_back_" + std::to_string(iLayer) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); - v = geoManager->GetVolume(sensor_name_front.c_str()); - if (v) { - AddSensitiveVolume(v); - iSens++; + + // Check if the volume name matches one of our active sensors + bool isMatch = false; + if (vName == sig1) { + isMatch = true; // Exact match for Trapezoidal/Cylindrical layouts + } else if (vName.find(sig2) == 0 || vName.find(sig3) == 0 || vName.find(sig4) == 0) { + isMatch = true; // Prefix match for Segmented and SegmentedStave layouts } - v = geoManager->GetVolume(sensor_name_back.c_str()); - if (v) { + + if (isMatch) { AddSensitiveVolume(v); + int volID = gMC ? TVirtualMC::GetMC()->VolId(vName.c_str()) : 0; + if (volID > 0) { + mActiveSensorMap[volID] = iLayer; + } iSens++; } } - //} - LOG(info) << iSens << " sensitive volumes added"; + + if (iSens == 0) { + LOG(error) << "NO sensitive volume found for direction " << direction << ", layer " << iLayer; + } else { + LOG(info) << iSens << " sensitive volume(s) added for direction " << direction << " layer " << iLayer; + } } } } From 877dc1f3cca0d35a5538423bb26322301768fb37 Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Fri, 15 May 2026 09:28:13 +0200 Subject: [PATCH 076/102] ITS: allow to reconstruct incomplete track topologies (#15390) * ITS: hole-tracking Signed-off-by: Felix Schlepper * Add HoleLayerMask parameter to function call --------- Signed-off-by: Felix Schlepper --- .../GPU/ITStrackingGPU/TimeFrameGPU.h | 57 +- .../GPU/ITStrackingGPU/TrackingKernels.h | 71 ++- .../ITS/tracking/GPU/cuda/TimeFrameGPU.cu | 101 +++- .../tracking/GPU/cuda/TrackerTraitsGPU.cxx | 241 ++++---- .../ITS/tracking/GPU/cuda/TrackingKernels.cu | 550 ++++++++++-------- .../ITS/tracking/include/ITStracking/Cell.h | 65 ++- .../include/ITStracking/Configuration.h | 9 +- .../tracking/include/ITStracking/LayerMask.h | 115 ++++ .../tracking/include/ITStracking/MathUtils.h | 2 + .../tracking/include/ITStracking/TimeFrame.h | 27 +- .../include/ITStracking/TrackHelpers.h | 12 +- .../include/ITStracking/TrackerTraits.h | 4 +- .../include/ITStracking/TrackingConfigParam.h | 2 + .../include/ITStracking/TrackingTopology.h | 219 +++++++ .../ITSMFT/ITS/tracking/src/Configuration.cxx | 9 +- .../ITSMFT/ITS/tracking/src/TimeFrame.cxx | 123 ++-- .../ITSMFT/ITS/tracking/src/TrackerTraits.cxx | 473 +++++++-------- .../ITS/tracking/src/TrackingInterface.cxx | 6 + .../ITSMFT/ITS/tracking/test/CMakeLists.txt | 6 + .../ITS/tracking/test/testROFLookupTables.cxx | 1 + .../tracking/test/testTrackingTopology.cxx | 119 ++++ 21 files changed, 1484 insertions(+), 728 deletions(-) create mode 100644 Detectors/ITSMFT/ITS/tracking/include/ITStracking/LayerMask.h create mode 100644 Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingTopology.h create mode 100644 Detectors/ITSMFT/ITS/tracking/test/testTrackingTopology.cxx diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h index 1ecff79f2d200..5f56e3f272473 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h @@ -31,7 +31,11 @@ class TimeFrameGPU : public TimeFrame using typename TimeFrame::ROFOverlapTableN; using typename TimeFrame::ROFVertexLookupTableN; using typename TimeFrame::ROFMaskTableN; + using typename TimeFrame::TrackingTopologyN; using typename TimeFrame::TrackSeedN; + static constexpr int MaxTransitions = TrackingTopologyN::MaxTransitions; + static constexpr int MaxCells = TrackingTopologyN::MaxCells; + static constexpr int MaxStreams = MaxCells > NLayers ? MaxCells : NLayers; public: TimeFrameGPU() = default; @@ -43,7 +47,9 @@ class TimeFrameGPU : public TimeFrame void registerHostMemory(const int); void unregisterHostMemory(const int); void initialise(const TrackingParameters&, int maxLayers); + void initialise(const TrackingParameters&, int maxLayers, int iteration); void loadIndexTableUtils(); + void loadTrackingTopologies(); void loadTrackingFrameInfoDevice(const int); void createTrackingFrameInfoDeviceArray(); void loadUnsortedClustersDevice(const int); @@ -85,7 +91,7 @@ class TimeFrameGPU : public TimeFrame void createNeighboursLUTDevice(const int, const unsigned int); void createTrackITSExtDevice(const size_t); void downloadTrackITSExtDevice(); - void downloadCellsNeighboursDevice(std::vector>>&, const int); + void downloadCellsNeighboursDevice(std::vector>&, const int); void downloadNeighboursLUTDevice(bounded_vector&, const int); void downloadCellsDevice(); void downloadCellsLUTDevice(); @@ -109,6 +115,7 @@ class TimeFrameGPU : public TimeFrame const auto getDeviceROFOverlapTableView() { return mDeviceROFOverlapTableView; } const auto getDeviceROFVertexLookupTableView() { return mDeviceROFVertexLookupTableView; } const auto getDeviceROFMaskTableView() { return mDeviceROFMaskTableView; } + const auto getDeviceTrackingTopologyView() const { return mDeviceTrackingTopologyView; } int* getDeviceROFramesClusters(const int layer) { return mROFramesClustersDevice[layer]; } auto& getTrackITSExt() { return mTrackITSExt; } Vertex* getDeviceVertices() { return mPrimaryVerticesDevice; } @@ -120,10 +127,9 @@ class TimeFrameGPU : public TimeFrame TrackITSExt* getDeviceTrackITSExt() { return mTrackITSExtDevice; } int* getDeviceNeighboursLUT(const int layer) { return mNeighboursLUTDevice[layer]; } gsl::span getDeviceNeighboursLUTs() { return mNeighboursLUTDevice; } - gpuPair* getDeviceNeighbourPairs(const int layer) { return mNeighbourPairsDevice[layer]; } - std::array& getDeviceNeighboursAll() { return mNeighboursDevice; } - int* getDeviceNeighbours(const int layer) { return mNeighboursDevice[layer]; } - int** getDeviceNeighboursArray() { return mNeighboursDevice.data(); } + CellNeighbour** getDeviceArrayNeighbours() { return mNeighboursDeviceArray; } + std::array& getDeviceNeighboursAll() { return mNeighboursDevice; } + CellNeighbour* getDeviceNeighbours(const int layer) { return mNeighboursDevice[layer]; } TrackingFrameInfo* getDeviceTrackingFrameInfo(const int); const TrackingFrameInfo** getDeviceArrayTrackingFrameInfo() const { return mTrackingFrameInfoDeviceArray; } const Cluster** getDeviceArrayClusters() const { return mClustersDeviceArray; } @@ -147,10 +153,10 @@ class TimeFrameGPU : public TimeFrame void setDevicePropagator(const o2::base::PropagatorImpl* p) final { this->mPropagatorDevice = p; } // Host-specific getters - gsl::span getNTracklets() { return mNTracklets; } - gsl::span getNCells() { return mNCells; } + gsl::span getNTracklets() { return {mNTracklets.data(), static_cast::size_type>(this->mTrackingTopologyView.nTransitions)}; } + gsl::span getNCells() { return {mNCells.data(), static_cast::size_type>(this->mTrackingTopologyView.nCells)}; } auto& getArrayNCells() { return mNCells; } - gsl::span getNNeighbours() { return mNNeighbours; } + gsl::span getNNeighbours() { return {mNNeighbours.data(), static_cast::size_type>(this->mTrackingTopologyView.nCells)}; } auto& getArrayNNeighbours() { return mNNeighbours; } // Host-available device getters @@ -169,9 +175,9 @@ class TimeFrameGPU : public TimeFrame void allocMem(void**, size_t, bool, int32_t = o2::gpu::GPUMemoryResource::MEMORY_GPU); // Abstract owned and unowned memory allocations on default stream // Host-available device buffer sizes - std::array mNTracklets; - std::array mNCells; - std::array mNNeighbours; + std::array mNTracklets{}; + std::array mNCells{}; + std::array mNNeighbours{}; // Device pointers IndexTableUtilsN* mIndexTableUtilsDevice; @@ -179,6 +185,8 @@ class TimeFrameGPU : public TimeFrame ROFOverlapTableN::View mDeviceROFOverlapTableView; ROFVertexLookupTableN::View mDeviceROFVertexLookupTableView; ROFMaskTableN::View mDeviceROFMaskTableView; + std::vector mDeviceTrackerTopologyViews; + typename TrackingTopologyN::View mDeviceTrackingTopologyView; // Hybrid pref Vertex* mPrimaryVerticesDevice; @@ -193,30 +201,29 @@ class TimeFrameGPU : public TimeFrame const int** mClustersIndexTablesDeviceArray; uint8_t** mUsedClustersDeviceArray; const int** mROFramesClustersDeviceArray; - std::array mTrackletsDevice; - std::array mTrackletsLUTDevice; - std::array mCellsLUTDevice; - std::array mNeighboursLUTDevice; + std::array mTrackletsDevice{}; + std::array mTrackletsLUTDevice{}; + std::array mCellsLUTDevice{}; + std::array mNeighboursLUTDevice{}; Tracklet** mTrackletsDeviceArray{nullptr}; int** mCellsLUTDeviceArray{nullptr}; - int** mNeighboursCellDeviceArray{nullptr}; int** mNeighboursCellLUTDeviceArray{nullptr}; int** mTrackletsLUTDeviceArray{nullptr}; - std::array mCellsDevice; + std::array mCellsDevice{}; CellSeed** mCellsDeviceArray; - std::array mNeighboursIndexTablesDevice; + std::array mNeighboursIndexTablesDevice{}; TrackSeedN* mTrackSeedsDevice{nullptr}; int* mTrackSeedsLUTDevice{nullptr}; unsigned int mNTracks{0}; - std::array mCellSeedsDevice; + std::array mCellSeedsDevice{}; o2::track::TrackParCovF** mCellSeedsDeviceArray; - std::array mCellSeedsChi2Device; + std::array mCellSeedsChi2Device{}; float** mCellSeedsChi2DeviceArray; TrackITSExt* mTrackITSExtDevice; - std::array*, NLayers - 2> mNeighbourPairsDevice; - std::array mNeighboursDevice; + std::array mNeighboursDevice{}; + CellNeighbour** mNeighboursDeviceArray{nullptr}; std::array mTrackingFrameInfoDevice; const TrackingFrameInfo** mTrackingFrameInfoDeviceArray; @@ -245,19 +252,19 @@ inline std::vector TimeFrameGPU::getClusterSizes() template inline size_t TimeFrameGPU::getNumberOfTracklets() const { - return std::accumulate(mNTracklets.begin(), mNTracklets.end(), 0); + return std::accumulate(mNTracklets.begin(), mNTracklets.begin() + this->mTrackingTopologyView.nTransitions, 0); } template inline size_t TimeFrameGPU::getNumberOfCells() const { - return std::accumulate(mNCells.begin(), mNCells.end(), 0); + return std::accumulate(mNCells.begin(), mNCells.begin() + this->mTrackingTopologyView.nCells, 0); } template inline size_t TimeFrameGPU::getNumberOfNeighbours() const { - return std::accumulate(mNNeighbours.begin(), mNNeighbours.end(), 0); + return std::accumulate(mNNeighbours.begin(), mNNeighbours.begin() + this->mTrackingTopologyView.nCells, 0); } } // namespace o2::its::gpu diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h index fe272f6f8d3bb..161283db2a2bc 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h @@ -17,14 +17,14 @@ #include "ITStracking/BoundedAllocator.h" #include "ITStracking/ROFLookupTables.h" -#include "ITStracking/Definitions.h" +#include "ITStracking/TrackingTopology.h" #include "ITStrackingGPU/Utils.h" #include "DetectorsBase/Propagator.h" -#include "GPUCommonDef.h" namespace o2::its { class CellSeed; +struct CellNeighbour; template class TrackSeed; class TrackingFrameInfo; @@ -38,7 +38,9 @@ class ExternalAllocator; template void countTrackletsInROFsHandler(const IndexTableUtils* utils, const typename ROFMaskTable::View& rofMask, - const int layer, + const int transitionId, + const int fromLayer, + const int toLayer, const typename ROFOverlapTable::View& rofOverlaps, const typename ROFVertexLookupTable::View& vertexLUT, const int vertexId, @@ -53,20 +55,23 @@ void countTrackletsInROFsHandler(const IndexTableUtils* utils, gsl::span trackletsLUTsHost, const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const typename TrackingTopology::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, std::array& minR, std::array& maxR, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, gpu::Streams& streams); template void computeTrackletsInROFsHandler(const IndexTableUtils* utils, const typename ROFMaskTable::View& rofMask, - const int layer, + const int transitionId, + const int fromLayer, + const int toLayer, const typename ROFOverlapTable::View& rofOverlaps, const typename ROFVertexLookupTable::View& vertexLUT, const int vertexId, @@ -84,13 +89,14 @@ void computeTrackletsInROFsHandler(const IndexTableUtils* utils, gsl::span trackletsLUTsHost, const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const typename TrackingTopology::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, std::array& minR, std::array& maxR, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, gpu::Streams& streams); @@ -101,7 +107,8 @@ void countCellsHandler(const Cluster** sortedClusters, Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, + const int cellTopologyId, + const typename TrackingTopology::View topology, CellSeed* cells, int** cellsLUTsDeviceArray, int* cellsLUTsHost, @@ -120,7 +127,8 @@ void computeCellsHandler(const Cluster** sortedClusters, Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, + const int cellTopologyId, + const typename TrackingTopology::View topology, CellSeed* cells, int** cellsLUTsDeviceArray, int* cellsLUTsHost, @@ -133,33 +141,31 @@ void computeCellsHandler(const Cluster** sortedClusters, template void countCellNeighboursHandler(CellSeed** cellsLayersDevice, - int* neighboursLUTs, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, + const int sourceCellTopologyId, + const int targetCellTopologyId, 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); +void scanCellNeighboursHandler(int* neighboursCursor, + int* neighboursLUT, + const unsigned int nCells, + o2::its::ExternalAllocator* alloc, + gpu::Stream& stream); + template void computeCellNeighboursHandler(CellSeed** cellsLayersDevice, - int* neighboursLUTs, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, + CellNeighbour* cellNeighbours, + const int sourceCellTopologyId, + const int targetCellTopologyId, const float maxChi2ClusterAttachment, const float bz, - const int layerIndex, const unsigned int nCells, - const unsigned int nCellsNext, - const int maxCellNeighbours, gpu::Stream& stream); int filterCellNeighboursHandler(gpuPair*, @@ -169,19 +175,24 @@ int filterCellNeighboursHandler(gpuPair*, o2::its::ExternalAllocator* = nullptr); template -void processNeighboursHandler(const int startLayer, - const int startLevel, +void processNeighboursHandler(const int startLevel, + const int defaultCellTopologyId, CellSeed** allCellSeeds, CellSeed* currentCellSeeds, - std::array& nCells, + const int* currentCellTopologyIds, + const int* currentCellIds, + const int* nCells, const unsigned char** usedClusters, - std::array& neighbours, - gsl::span neighboursDeviceLUTs, + CellNeighbour** neighbours, + int** neighboursDeviceLUTs, const TrackingFrameInfo** foundTrackingFrameInfo, bounded_vector>& seedsHost, const float bz, const float MaxChi2ClusterAttachment, const float maxChi2NDF, + const int maxHoles, + const int minTrackLength, + const LayerMask holeLayerMask, const std::vector& layerxX0Host, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu index b541518a88119..5fff30f5162b1 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu @@ -328,6 +328,37 @@ void TimeFrameGPU::loadROFVertexLookupTable() } } +template +void TimeFrameGPU::loadTrackingTopologies() +{ + GPUTimer timer("initialising device views of TrackingTopology"); + const auto& hostTopologies = this->getTrackerTopologies(); + mDeviceTrackerTopologyViews.resize(hostTopologies.size()); + using LayerTransition = typename TrackingTopologyN::LayerTransition; + using CellTopology = typename TrackingTopologyN::CellTopology; + using Range = typename TrackingTopologyN::Range; + using Id = typename TrackingTopologyN::Id; + for (size_t iteration = 0; iteration < hostTopologies.size(); ++iteration) { + const auto& topology = hostTopologies[iteration]; + LayerTransition* dTransitions{nullptr}; + CellTopology* dCells{nullptr}; + Range* dCellsByFirstTransitionIndex{nullptr}; + Id* dCellsByFirstTransition{nullptr}; + allocMem(reinterpret_cast(&dTransitions), topology.getNTransitions() * sizeof(LayerTransition), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&dCells), topology.getNCells() * sizeof(CellTopology), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&dCellsByFirstTransitionIndex), topology.getNTransitions() * sizeof(Range), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&dCellsByFirstTransition), topology.getNCellsByFirstTransition() * sizeof(Id), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemcpy(dTransitions, topology.getTransitions().data(), topology.getNTransitions() * sizeof(LayerTransition), cudaMemcpyHostToDevice)); + GPUChkErrS(cudaMemcpy(dCells, topology.getCells().data(), topology.getNCells() * sizeof(CellTopology), cudaMemcpyHostToDevice)); + GPUChkErrS(cudaMemcpy(dCellsByFirstTransitionIndex, topology.getCellsByFirstTransitionIndex().data(), topology.getNTransitions() * sizeof(Range), cudaMemcpyHostToDevice)); + GPUChkErrS(cudaMemcpy(dCellsByFirstTransition, topology.getCellsByFirstTransition().data(), topology.getNCellsByFirstTransition() * sizeof(Id), cudaMemcpyHostToDevice)); + mDeviceTrackerTopologyViews[iteration] = topology.getDeviceView(dTransitions, dCells, dCellsByFirstTransitionIndex, dCellsByFirstTransition); + } + if (!mDeviceTrackerTopologyViews.empty()) { + mDeviceTrackingTopologyView = mDeviceTrackerTopologyViews.front(); + } +} + template void TimeFrameGPU::updateROFVertexLookupTable() { @@ -348,7 +379,7 @@ template void TimeFrameGPU::createTrackletsLUTDeviceArray() { { - allocMem(reinterpret_cast(&mTrackletsLUTDeviceArray), (NLayers - 1) * sizeof(int*), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&mTrackletsLUTDeviceArray), MaxTransitions * sizeof(int*), this->hasFrameworkAllocator()); } } @@ -356,8 +387,9 @@ template void TimeFrameGPU::createTrackletsLUTDevice(bool allocate, const int layer) { GPUTimer timer(mGpuStreams[layer], "creating tracklets LUTs", layer); - const int ncls = this->mClusters[layer].size() + 1; - if (allocate) { + const int fromLayer = this->mTrackingTopologyView.getTransition(layer).fromLayer; + const int ncls = this->mClusters[fromLayer].size() + 1; + if (allocate || mTrackletsLUTDevice[layer] == nullptr) { GPULog("gpu-allocation: creating tracklets LUT for {} elements on layer {}, for {:.2f} MB.", ncls, layer, ncls * sizeof(int) / constants::MB); allocMemAsync(reinterpret_cast(&mTrackletsLUTDevice[layer]), ncls * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpyAsync(&mTrackletsLUTDeviceArray[layer], &mTrackletsLUTDevice[layer], sizeof(int*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); @@ -370,7 +402,7 @@ void TimeFrameGPU::createTrackletsBuffersArray() { { GPUTimer timer("creating tracklet buffers array"); - allocMem(reinterpret_cast(&mTrackletsDeviceArray), (NLayers - 1) * sizeof(Tracklet*), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&mTrackletsDeviceArray), MaxTransitions * sizeof(Tracklet*), this->hasFrameworkAllocator()); } } @@ -379,7 +411,8 @@ void TimeFrameGPU::createTrackletsBuffers(const int layer) { GPUTimer timer(mGpuStreams[layer], "creating tracklet buffers", layer); mNTracklets[layer] = 0; - GPUChkErrS(cudaMemcpyAsync(&mNTracklets[layer], mTrackletsLUTDevice[layer] + this->mClusters[layer].size(), sizeof(int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); + const int fromLayer = this->mTrackingTopologyView.getTransition(layer).fromLayer; + GPUChkErrS(cudaMemcpyAsync(&mNTracklets[layer], mTrackletsLUTDevice[layer] + this->mClusters[fromLayer].size(), sizeof(int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); mGpuStreams[layer].sync(); // ensure number of tracklets is correct GPULog("gpu-transfer: creating tracklets buffer for {} elements on layer {}, for {:.2f} MB.", mNTracklets[layer], layer, mNTracklets[layer] * sizeof(Tracklet) / constants::MB); allocMemAsync(reinterpret_cast(&mTrackletsDevice[layer]), mNTracklets[layer] * sizeof(Tracklet), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); @@ -426,6 +459,7 @@ void TimeFrameGPU::createNeighboursLUTDevice(const int layer, const uns GPULog("gpu-allocation: reserving neighbours LUT for {} elements on layer {} , for {:.2f} MB.", nCells + 1, layer, (nCells + 1) * sizeof(int) / constants::MB); allocMemAsync(reinterpret_cast(&mNeighboursLUTDevice[layer]), (nCells + 1) * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); // We need one element more to move exc -> inc GPUChkErrS(cudaMemsetAsync(mNeighboursLUTDevice[layer], 0, (nCells + 1) * sizeof(int), mGpuStreams[layer].get())); + GPUChkErrS(cudaMemcpyAsync(&mNeighboursCellLUTDeviceArray[layer], &mNeighboursLUTDevice[layer], sizeof(int*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } template @@ -446,7 +480,9 @@ void TimeFrameGPU::createCellsLUTDeviceArray() { { GPUTimer timer("creating cells LUTs array"); - allocMem(reinterpret_cast(&mCellsLUTDeviceArray), (NLayers - 2) * sizeof(int*), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&mCellsLUTDeviceArray), MaxCells * sizeof(int*), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&mNeighboursCellLUTDeviceArray), MaxCells * sizeof(int*), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemset(mNeighboursCellLUTDeviceArray, 0, MaxCells * sizeof(int*))); } } @@ -454,9 +490,10 @@ template void TimeFrameGPU::createCellsLUTDevice(const int layer) { GPUTimer timer(mGpuStreams[layer], "creating cells LUTs", layer); - GPULog("gpu-transfer: creating cell LUT for {} elements on layer {}, for {:.2f} MB.", mNTracklets[layer] + 1, layer, (mNTracklets[layer] + 1) * sizeof(int) / constants::MB); - allocMemAsync(reinterpret_cast(&mCellsLUTDevice[layer]), (mNTracklets[layer] + 1) * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); - GPUChkErrS(cudaMemsetAsync(mCellsLUTDevice[layer], 0, (mNTracklets[layer] + 1) * sizeof(int), mGpuStreams[layer].get())); + const int firstTransition = this->mTrackingTopologyView.getCell(layer).firstTransition; + GPULog("gpu-transfer: creating cell LUT for {} elements on layer {}, for {:.2f} MB.", mNTracklets[firstTransition] + 1, layer, (mNTracklets[firstTransition] + 1) * sizeof(int) / constants::MB); + allocMemAsync(reinterpret_cast(&mCellsLUTDevice[layer]), (mNTracklets[firstTransition] + 1) * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + GPUChkErrS(cudaMemsetAsync(mCellsLUTDevice[layer], 0, (mNTracklets[firstTransition] + 1) * sizeof(int), mGpuStreams[layer].get())); GPUChkErrS(cudaMemcpyAsync(&mCellsLUTDeviceArray[layer], &mCellsLUTDevice[layer], sizeof(int*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } @@ -465,7 +502,9 @@ void TimeFrameGPU::createCellsBuffersArray() { { GPUTimer timer("creating cells buffers array"); - allocMem(reinterpret_cast(&mCellsDeviceArray), (NLayers - 2) * sizeof(CellSeed*), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&mCellsDeviceArray), MaxCells * sizeof(CellSeed*), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&mNeighboursDeviceArray), MaxCells * sizeof(CellNeighbour*), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemset(mNeighboursDeviceArray, 0, MaxCells * sizeof(CellNeighbour*))); GPUChkErrS(cudaMemcpy(mCellsDeviceArray, mCellsDevice.data(), mCellsDevice.size() * sizeof(CellSeed*), cudaMemcpyHostToDevice)); } } @@ -475,7 +514,8 @@ void TimeFrameGPU::createCellsBuffers(const int layer) { GPUTimer timer(mGpuStreams[layer], "creating cells buffers"); mNCells[layer] = 0; - GPUChkErrS(cudaMemcpyAsync(&mNCells[layer], mCellsLUTDevice[layer] + mNTracklets[layer], sizeof(int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); + const int firstTransition = this->mTrackingTopologyView.getCell(layer).firstTransition; + GPUChkErrS(cudaMemcpyAsync(&mNCells[layer], mCellsLUTDevice[layer] + mNTracklets[firstTransition], sizeof(int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); mGpuStreams[layer].sync(); // ensure number of cells is correct GPULog("gpu-transfer: creating cell buffer for {} elements on layer {}, for {:.2f} MB.", mNCells[layer], layer, mNCells[layer] * sizeof(CellSeed) / constants::MB); allocMemAsync(reinterpret_cast(&mCellsDevice[layer]), mNCells[layer] * sizeof(CellSeed), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); @@ -511,13 +551,22 @@ void TimeFrameGPU::createNeighboursDevice(const unsigned int layer) { GPUTimer timer(mGpuStreams[layer], "reserving neighbours", layer); this->mNNeighbours[layer] = 0; - GPUChkErrS(cudaMemcpyAsync(&(this->mNNeighbours[layer]), &(mNeighboursLUTDevice[layer][this->mNCells[layer + 1] - 1]), sizeof(unsigned int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); + if (this->mNCells[layer] == 0) { + mNeighboursDevice[layer] = nullptr; + GPUChkErrS(cudaMemcpyAsync(&mNeighboursDeviceArray[layer], &mNeighboursDevice[layer], sizeof(CellNeighbour*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); + return; + } + GPUChkErrS(cudaMemcpyAsync(&(this->mNNeighbours[layer]), &(mNeighboursLUTDevice[layer][this->mNCells[layer]]), sizeof(unsigned int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); mGpuStreams[layer].sync(); // ensure number of neighbours is correct - GPULog("gpu-allocation: reserving {} neighbours (pairs), for {:.2f} MB.", this->mNNeighbours[layer], (this->mNNeighbours[layer]) * sizeof(gpuPair) / constants::MB); - allocMemAsync(reinterpret_cast(&mNeighbourPairsDevice[layer]), (this->mNNeighbours[layer]) * sizeof(gpuPair), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); - GPUChkErrS(cudaMemsetAsync(mNeighbourPairsDevice[layer], -1, (this->mNNeighbours[layer]) * sizeof(gpuPair), mGpuStreams[layer].get())); - GPULog("gpu-allocation: reserving {} neighbours, for {:.2f} MB.", this->mNNeighbours[layer], (this->mNNeighbours[layer]) * sizeof(gpuPair) / constants::MB); - allocMemAsync(reinterpret_cast(&mNeighboursDevice[layer]), (this->mNNeighbours[layer]) * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + if (this->mNNeighbours[layer] == 0) { + mNeighboursDevice[layer] = nullptr; + GPUChkErrS(cudaMemcpyAsync(&mNeighboursDeviceArray[layer], &mNeighboursDevice[layer], sizeof(CellNeighbour*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); + return; + } + GPULog("gpu-allocation: reserving {} neighbours, for {:.2f} MB.", this->mNNeighbours[layer], (this->mNNeighbours[layer]) * sizeof(CellNeighbour) / constants::MB); + allocMemAsync(reinterpret_cast(&mNeighboursDevice[layer]), (this->mNNeighbours[layer]) * sizeof(CellNeighbour), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + GPUChkErrS(cudaMemsetAsync(mNeighboursDevice[layer], -1, (this->mNNeighbours[layer]) * sizeof(CellNeighbour), mGpuStreams[layer].get())); + GPUChkErrS(cudaMemcpyAsync(&mNeighboursDeviceArray[layer], &mNeighboursDevice[layer], sizeof(CellNeighbour*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } template @@ -555,11 +604,11 @@ void TimeFrameGPU::downloadCellsLUTDevice() } template -void TimeFrameGPU::downloadCellsNeighboursDevice(std::vector>>& neighbours, const int layer) +void TimeFrameGPU::downloadCellsNeighboursDevice(std::vector>& neighbours, const int layer) { GPUTimer timer(mGpuStreams[layer], "downloading neighbours from layer", layer); - GPULog("gpu-transfer: downloading {} neighbours, for {:.2f} MB.", neighbours[layer].size(), neighbours[layer].size() * sizeof(std::pair) / constants::MB); - GPUChkErrS(cudaMemcpyAsync(neighbours[layer].data(), mNeighbourPairsDevice[layer], neighbours[layer].size() * sizeof(gpuPair), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); + GPULog("gpu-transfer: downloading {} neighbours, for {:.2f} MB.", neighbours[layer].size(), neighbours[layer].size() * sizeof(CellNeighbour) / constants::MB); + GPUChkErrS(cudaMemcpyAsync(neighbours[layer].data(), mNeighboursDevice[layer], neighbours[layer].size() * sizeof(CellNeighbour), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); } template @@ -648,10 +697,20 @@ void TimeFrameGPU::popMemoryStack(const int iteration) template void TimeFrameGPU::initialise(const TrackingParameters& trkParam, int maxLayers) { - mGpuStreams.resize(NLayers); + mGpuStreams.resize(MaxStreams); o2::its::TimeFrame::initialise(trkParam, maxLayers); } +template +void TimeFrameGPU::initialise(const TrackingParameters& trkParam, int maxLayers, int iteration) +{ + mGpuStreams.resize(MaxStreams); + o2::its::TimeFrame::initialise(trkParam, maxLayers, iteration); + if (iteration != constants::UnusedIndex && iteration < static_cast(mDeviceTrackerTopologyViews.size())) { + mDeviceTrackingTopologyView = mDeviceTrackerTopologyViews[iteration]; + } +} + template void TimeFrameGPU::syncStream(const size_t stream) { diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx index 2d2ca5432cdf9..f1812c9f6f764 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx @@ -22,7 +22,7 @@ namespace o2::its template void TrackerTraitsGPU::initialiseTimeFrame(const int iteration) { - mTimeFrameGPU->initialise(this->mTrkParams[iteration], NLayers); + mTimeFrameGPU->initialise(this->mTrkParams[iteration], NLayers, iteration); if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { // on default stream @@ -30,6 +30,7 @@ void TrackerTraitsGPU::initialiseTimeFrame(const int iteration) // TODO these tables can be put in persistent memory mTimeFrameGPU->loadROFOverlapTable(); // this can be put in constant memory actually mTimeFrameGPU->loadROFVertexLookupTable(); + mTimeFrameGPU->loadTrackingTopologies(); // once the tables are in persistent memory just update the vertex one // mTimeFrameGPU->updateROFVertexLookupTable(); mTimeFrameGPU->loadIndexTableUtils(); @@ -63,8 +64,9 @@ void TrackerTraitsGPU::adoptTimeFrame(TimeFrame* tf) template void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int iVertex) { - // start by queuing loading needed of two last layers - for (int iLayer{NLayers}; iLayer-- > NLayers - 2;) { + const auto topology = mTimeFrameGPU->getDeviceTrackingTopologyView(); + const auto hostTopology = mTimeFrameGPU->getTrackingTopologyView(); + for (int iLayer{0}; iLayer < this->mTrkParams[iteration].NLayers; ++iLayer) { if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { mTimeFrameGPU->createUsedClustersDevice(iLayer); mTimeFrameGPU->loadClustersDevice(iLayer); @@ -74,21 +76,16 @@ void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int i mTimeFrameGPU->recordEvent(iLayer); } - for (int iLayer{this->mTrkParams[iteration].TrackletsPerRoad()}; iLayer--;) { - if (iLayer) { // queue loading data of next layer in parallel, this the copies are overlapping with computation kernels - if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { - mTimeFrameGPU->createUsedClustersDevice(iLayer - 1); - mTimeFrameGPU->loadClustersDevice(iLayer - 1); - mTimeFrameGPU->loadClustersIndexTables(iLayer - 1); - mTimeFrameGPU->loadROFrameClustersDevice(iLayer - 1); - } - mTimeFrameGPU->recordEvent(iLayer - 1); - } - mTimeFrameGPU->createTrackletsLUTDevice(this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass], iLayer); - mTimeFrameGPU->waitEvent(iLayer, iLayer + 1); // wait stream until all data is available + for (int transitionId{0}; transitionId < hostTopology.nTransitions; ++transitionId) { + const auto transition = hostTopology.getTransition(transitionId); + mTimeFrameGPU->createTrackletsLUTDevice(this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass], transitionId); + mTimeFrameGPU->waitEvent(transitionId, transition.fromLayer); + mTimeFrameGPU->waitEvent(transitionId, transition.toLayer); countTrackletsInROFsHandler(mTimeFrameGPU->getDeviceIndexTableUtils(), mTimeFrameGPU->getDeviceROFMaskTableView(), - iLayer, + transitionId, + transition.fromLayer, + transition.toLayer, mTimeFrameGPU->getDeviceROFOverlapTableView(), mTimeFrameGPU->getDeviceROFVertexLookupTableView(), iVertex, @@ -103,22 +100,26 @@ void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int i mTimeFrameGPU->getDeviceTrackletsLUTs(), this->mTrkParams[iteration].PassFlags[IterationStep::SelectUPCVertices], this->mTrkParams[iteration].NSigmaCut, - mTimeFrameGPU->getPhiCuts(), + topology, + mTimeFrameGPU->getTransitionPhiCuts(), this->mTrkParams[iteration].PVres, mTimeFrameGPU->getMinRs(), mTimeFrameGPU->getMaxRs(), mTimeFrameGPU->getPositionResolutions(), this->mTrkParams[iteration].LayerRadii, - mTimeFrameGPU->getMSangles(), + mTimeFrameGPU->getTransitionMSAngles(), mTimeFrameGPU->getFrameworkAllocator(), mTimeFrameGPU->getStreams()); - mTimeFrameGPU->createTrackletsBuffers(iLayer); - if (mTimeFrameGPU->getNTracklets()[iLayer] == 0) { + mTimeFrameGPU->createTrackletsBuffers(transitionId); + if (mTimeFrameGPU->getNTracklets()[transitionId] == 0) { + mTimeFrameGPU->recordEvent(transitionId); continue; } computeTrackletsInROFsHandler(mTimeFrameGPU->getDeviceIndexTableUtils(), mTimeFrameGPU->getDeviceROFMaskTableView(), - iLayer, + transitionId, + transition.fromLayer, + transition.toLayer, mTimeFrameGPU->getDeviceROFOverlapTableView(), mTimeFrameGPU->getDeviceROFVertexLookupTableView(), iVertex, @@ -136,23 +137,26 @@ void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int i mTimeFrameGPU->getDeviceTrackletsLUTs(), this->mTrkParams[iteration].PassFlags[IterationStep::SelectUPCVertices], this->mTrkParams[iteration].NSigmaCut, - mTimeFrameGPU->getPhiCuts(), + topology, + mTimeFrameGPU->getTransitionPhiCuts(), this->mTrkParams[iteration].PVres, mTimeFrameGPU->getMinRs(), mTimeFrameGPU->getMaxRs(), mTimeFrameGPU->getPositionResolutions(), this->mTrkParams[iteration].LayerRadii, - mTimeFrameGPU->getMSangles(), + mTimeFrameGPU->getTransitionMSAngles(), mTimeFrameGPU->getFrameworkAllocator(), mTimeFrameGPU->getStreams()); + mTimeFrameGPU->recordEvent(transitionId); } } template void TrackerTraitsGPU::computeLayerCells(const int iteration) { - // start by queuing loading needed of three last layers - for (int iLayer{NLayers}; iLayer-- > NLayers - 3;) { + const auto topology = mTimeFrameGPU->getDeviceTrackingTopologyView(); + const auto hostTopology = mTimeFrameGPU->getTrackingTopologyView(); + for (int iLayer{0}; iLayer < this->mTrkParams[iteration].NLayers; ++iLayer) { if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { mTimeFrameGPU->loadUnsortedClustersDevice(iLayer); mTimeFrameGPU->loadTrackingFrameInfoDevice(iLayer); @@ -160,35 +164,33 @@ void TrackerTraitsGPU::computeLayerCells(const int iteration) mTimeFrameGPU->recordEvent(iLayer); } - for (int iLayer{this->mTrkParams[iteration].CellsPerRoad()}; iLayer--;) { - if (iLayer) { - if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { - mTimeFrameGPU->loadUnsortedClustersDevice(iLayer - 1); - mTimeFrameGPU->loadTrackingFrameInfoDevice(iLayer - 1); - } - mTimeFrameGPU->recordEvent(iLayer - 1); - } - - // if there are no tracklets skip entirely - const int currentLayerTrackletsNum{static_cast(mTimeFrameGPU->getNTracklets()[iLayer])}; - if (!currentLayerTrackletsNum || !mTimeFrameGPU->getNTracklets()[iLayer + 1]) { - mTimeFrameGPU->getNCells()[iLayer] = 0; + for (int cellTopologyId{hostTopology.nCells}; cellTopologyId--;) { + const auto cellTopology = hostTopology.getCell(cellTopologyId); + const auto first = hostTopology.getTransition(cellTopology.firstTransition); + const auto second = hostTopology.getTransition(cellTopology.secondTransition); + const int currentLayerTrackletsNum{static_cast(mTimeFrameGPU->getNTracklets()[cellTopology.firstTransition])}; + if (!currentLayerTrackletsNum || !mTimeFrameGPU->getNTracklets()[cellTopology.secondTransition]) { + mTimeFrameGPU->getNCells()[cellTopologyId] = 0; continue; } - mTimeFrameGPU->createCellsLUTDevice(iLayer); - mTimeFrameGPU->waitEvent(iLayer, iLayer + 1); // wait stream until all data is available - mTimeFrameGPU->waitEvent(iLayer, iLayer + 2); // wait stream until all data is available + mTimeFrameGPU->createCellsLUTDevice(cellTopologyId); + mTimeFrameGPU->waitEvent(cellTopologyId, cellTopology.firstTransition); + mTimeFrameGPU->waitEvent(cellTopologyId, cellTopology.secondTransition); + mTimeFrameGPU->waitEvent(cellTopologyId, first.fromLayer); + mTimeFrameGPU->waitEvent(cellTopologyId, first.toLayer); + mTimeFrameGPU->waitEvent(cellTopologyId, second.toLayer); countCellsHandler(mTimeFrameGPU->getDeviceArrayClusters(), mTimeFrameGPU->getDeviceArrayUnsortedClusters(), mTimeFrameGPU->getDeviceArrayTrackingFrameInfo(), mTimeFrameGPU->getDeviceArrayTracklets(), mTimeFrameGPU->getDeviceArrayTrackletsLUT(), currentLayerTrackletsNum, - iLayer, + cellTopologyId, + topology, nullptr, mTimeFrameGPU->getDeviceArrayCellsLUT(), - mTimeFrameGPU->getDeviceCellLUTs()[iLayer], + mTimeFrameGPU->getDeviceCellLUTs()[cellTopologyId], this->mBz, this->mTrkParams[iteration].MaxChi2ClusterAttachment, this->mTrkParams[iteration].CellDeltaTanLambdaSigma, @@ -196,8 +198,9 @@ void TrackerTraitsGPU::computeLayerCells(const int iteration) this->mTrkParams[iteration].LayerxX0, mTimeFrameGPU->getFrameworkAllocator(), mTimeFrameGPU->getStreams()); - mTimeFrameGPU->createCellsBuffers(iLayer); - if (mTimeFrameGPU->getNCells()[iLayer] == 0) { + mTimeFrameGPU->createCellsBuffers(cellTopologyId); + if (mTimeFrameGPU->getNCells()[cellTopologyId] == 0) { + mTimeFrameGPU->recordEvent(cellTopologyId); continue; } computeCellsHandler(mTimeFrameGPU->getDeviceArrayClusters(), @@ -206,16 +209,18 @@ void TrackerTraitsGPU::computeLayerCells(const int iteration) mTimeFrameGPU->getDeviceArrayTracklets(), mTimeFrameGPU->getDeviceArrayTrackletsLUT(), currentLayerTrackletsNum, - iLayer, - mTimeFrameGPU->getDeviceCells()[iLayer], + cellTopologyId, + topology, + mTimeFrameGPU->getDeviceCells()[cellTopologyId], mTimeFrameGPU->getDeviceArrayCellsLUT(), - mTimeFrameGPU->getDeviceCellLUTs()[iLayer], + mTimeFrameGPU->getDeviceCellLUTs()[cellTopologyId], this->mBz, this->mTrkParams[iteration].MaxChi2ClusterAttachment, this->mTrkParams[iteration].CellDeltaTanLambdaSigma, this->mTrkParams[iteration].NSigmaCut, this->mTrkParams[iteration].LayerxX0, mTimeFrameGPU->getStreams()); + mTimeFrameGPU->recordEvent(cellTopologyId); } mTimeFrameGPU->syncStreams(false); } @@ -223,58 +228,71 @@ void TrackerTraitsGPU::computeLayerCells(const int iteration) template void TrackerTraitsGPU::findCellsNeighbours(const int iteration) { - for (int iLayer{0}; iLayer < this->mTrkParams[iteration].NeighboursPerRoad(); ++iLayer) { - if (iLayer > 0) { - // Previous layer updates levels in this layer's cells. - mTimeFrameGPU->waitEvent(iLayer, iLayer - 1); - } - const int currentLayerCellsNum{static_cast(mTimeFrameGPU->getNCells()[iLayer])}; - const int nextLayerCellsNum{static_cast(mTimeFrameGPU->getNCells()[iLayer + 1])}; - if (!nextLayerCellsNum || !currentLayerCellsNum) { - mTimeFrameGPU->getNNeighbours()[iLayer] = 0; - mTimeFrameGPU->recordEvent(iLayer); - continue; - } - mTimeFrameGPU->createNeighboursIndexTablesDevice(iLayer); - mTimeFrameGPU->createNeighboursLUTDevice(iLayer, nextLayerCellsNum); - countCellNeighboursHandler(mTimeFrameGPU->getDeviceArrayCells(), - mTimeFrameGPU->getDeviceNeighboursLUT(iLayer), // LUT is initialised here. - mTimeFrameGPU->getDeviceArrayCellsLUT(), - mTimeFrameGPU->getDeviceNeighbourPairs(iLayer), - mTimeFrameGPU->getDeviceNeighboursIndexTables(iLayer), - (const Tracklet**)mTimeFrameGPU->getDeviceArrayTracklets(), - this->mTrkParams[iteration].MaxChi2ClusterAttachment, - this->mBz, - iLayer, - currentLayerCellsNum, - nextLayerCellsNum, - 1e2, - mTimeFrameGPU->getFrameworkAllocator(), - mTimeFrameGPU->getStream(iLayer)); - mTimeFrameGPU->createNeighboursDevice(iLayer); - if (mTimeFrameGPU->getNNeighbours()[iLayer] == 0) { - mTimeFrameGPU->recordEvent(iLayer); - continue; + const auto hostTopology = mTimeFrameGPU->getTrackingTopologyView(); + for (int outerLayer{0}; outerLayer < NLayers; ++outerLayer) { + for (int targetCellTopologyId{0}; targetCellTopologyId < hostTopology.nCells; ++targetCellTopologyId) { + const auto targetCellTopology = hostTopology.getCell(targetCellTopologyId); + if (targetCellTopology.hitLayerMask.last() != outerLayer) { + continue; + } + const int targetCellsNum{static_cast(mTimeFrameGPU->getNCells()[targetCellTopologyId])}; + if (!targetCellsNum) { + mTimeFrameGPU->getNNeighbours()[targetCellTopologyId] = 0; + mTimeFrameGPU->recordEvent(targetCellTopologyId); + continue; + } + mTimeFrameGPU->createNeighboursIndexTablesDevice(targetCellTopologyId); + mTimeFrameGPU->createNeighboursLUTDevice(targetCellTopologyId, targetCellsNum); + + for (int sourceCellTopologyId{0}; sourceCellTopologyId < hostTopology.nCells; ++sourceCellTopologyId) { + const auto sourceCellTopology = hostTopology.getCell(sourceCellTopologyId); + const int sourceCellsNum{static_cast(mTimeFrameGPU->getNCells()[sourceCellTopologyId])}; + if (!sourceCellsNum || sourceCellTopology.secondTransition != targetCellTopology.firstTransition) { + continue; + } + mTimeFrameGPU->waitEvent(targetCellTopologyId, sourceCellTopologyId); + countCellNeighboursHandler(mTimeFrameGPU->getDeviceArrayCells(), + mTimeFrameGPU->getDeviceNeighboursIndexTables(targetCellTopologyId), + mTimeFrameGPU->getDeviceArrayCellsLUT(), + sourceCellTopologyId, + targetCellTopologyId, + this->mTrkParams[iteration].MaxChi2ClusterAttachment, + this->mBz, + sourceCellsNum, + mTimeFrameGPU->getStream(targetCellTopologyId)); + } + + scanCellNeighboursHandler(mTimeFrameGPU->getDeviceNeighboursIndexTables(targetCellTopologyId), + mTimeFrameGPU->getDeviceNeighboursLUT(targetCellTopologyId), + targetCellsNum, + mTimeFrameGPU->getFrameworkAllocator(), + mTimeFrameGPU->getStream(targetCellTopologyId)); + + mTimeFrameGPU->createNeighboursDevice(targetCellTopologyId); + if (mTimeFrameGPU->getNNeighbours()[targetCellTopologyId] == 0) { + mTimeFrameGPU->recordEvent(targetCellTopologyId); + continue; + } + + for (int sourceCellTopologyId{0}; sourceCellTopologyId < hostTopology.nCells; ++sourceCellTopologyId) { + const auto sourceCellTopology = hostTopology.getCell(sourceCellTopologyId); + const int sourceCellsNum{static_cast(mTimeFrameGPU->getNCells()[sourceCellTopologyId])}; + if (!sourceCellsNum || sourceCellTopology.secondTransition != targetCellTopology.firstTransition) { + continue; + } + computeCellNeighboursHandler(mTimeFrameGPU->getDeviceArrayCells(), + mTimeFrameGPU->getDeviceNeighboursIndexTables(targetCellTopologyId), + mTimeFrameGPU->getDeviceArrayCellsLUT(), + mTimeFrameGPU->getDeviceNeighbours(targetCellTopologyId), + sourceCellTopologyId, + targetCellTopologyId, + this->mTrkParams[iteration].MaxChi2ClusterAttachment, + this->mBz, + sourceCellsNum, + mTimeFrameGPU->getStream(targetCellTopologyId)); + } + mTimeFrameGPU->recordEvent(targetCellTopologyId); } - computeCellNeighboursHandler(mTimeFrameGPU->getDeviceArrayCells(), - mTimeFrameGPU->getDeviceNeighboursLUT(iLayer), - mTimeFrameGPU->getDeviceArrayCellsLUT(), - mTimeFrameGPU->getDeviceNeighbourPairs(iLayer), - mTimeFrameGPU->getDeviceNeighboursIndexTables(iLayer), - (const Tracklet**)mTimeFrameGPU->getDeviceArrayTracklets(), - this->mTrkParams[iteration].MaxChi2ClusterAttachment, - this->mBz, - iLayer, - currentLayerCellsNum, - nextLayerCellsNum, - 1e2, - mTimeFrameGPU->getStream(iLayer)); - mTimeFrameGPU->getArrayNNeighbours()[iLayer] = filterCellNeighboursHandler(mTimeFrameGPU->getDeviceNeighbourPairs(iLayer), - mTimeFrameGPU->getDeviceNeighbours(iLayer), - mTimeFrameGPU->getArrayNNeighbours()[iLayer], - mTimeFrameGPU->getStream(iLayer), - mTimeFrameGPU->getFrameworkAllocator()); - mTimeFrameGPU->recordEvent(iLayer); } mTimeFrameGPU->syncStreams(false); } @@ -286,26 +304,33 @@ void TrackerTraitsGPU::findRoads(const int iteration) bounded_vector> sharedFirstClusters(this->mTrkParams[iteration].NLayers, bounded_vector(this->getMemoryPool().get()), this->getMemoryPool().get()); firstClusters.resize(this->mTrkParams[iteration].NLayers); sharedFirstClusters.resize(this->mTrkParams[iteration].NLayers); + const auto hostTopology = mTimeFrameGPU->getTrackingTopologyView(); for (int startLevel{this->mTrkParams[iteration].CellsPerRoad()}; startLevel >= this->mTrkParams[iteration].CellMinimumLevel(); --startLevel) { - const int minimumLayer{startLevel - 1}; bounded_vector> trackSeeds(this->getMemoryPool().get()); - for (int startLayer{this->mTrkParams[iteration].CellsPerRoad() - 1}; startLayer >= minimumLayer; --startLayer) { - if ((this->mTrkParams[iteration].StartLayerMask & (1 << (startLayer + 2))) == 0) { + for (int startCellTopologyId{0}; startCellTopologyId < hostTopology.nCells; ++startCellTopologyId) { + const int startLayer = hostTopology.getCell(startCellTopologyId).hitLayerMask.last(); + if ((this->mTrkParams[iteration].StartLayerMask & (1 << startLayer)) == 0 || + mTimeFrameGPU->getNCells()[startCellTopologyId] == 0) { continue; } - processNeighboursHandler(startLayer, - startLevel, + processNeighboursHandler(startLevel, + startCellTopologyId, mTimeFrameGPU->getDeviceArrayCells(), - mTimeFrameGPU->getDeviceCells()[startLayer], - mTimeFrameGPU->getArrayNCells(), + mTimeFrameGPU->getDeviceCells()[startCellTopologyId], + nullptr, + nullptr, + mTimeFrameGPU->getArrayNCells().data(), (const uint8_t**)mTimeFrameGPU->getDeviceArrayUsedClusters(), - mTimeFrameGPU->getDeviceNeighboursAll(), - mTimeFrameGPU->getDeviceNeighboursLUTs(), + mTimeFrameGPU->getDeviceArrayNeighbours(), + mTimeFrameGPU->getDeviceArrayNeighboursCellLUT(), mTimeFrameGPU->getDeviceArrayTrackingFrameInfo(), trackSeeds, this->mBz, this->mTrkParams[iteration].MaxChi2ClusterAttachment, this->mTrkParams[iteration].MaxChi2NDF, + this->mTrkParams[iteration].MaxHoles, + this->mTrkParams[iteration].MinTrackLength, + this->mTrkParams[iteration].HoleLayerMask, this->mTrkParams[iteration].LayerxX0, mTimeFrameGPU->getDevicePropagator(), this->mTrkParams[iteration].CorrType, diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu index a732327a64d15..571afe08fc209 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu @@ -86,20 +86,25 @@ struct is_valid_pair { template struct seed_selector { - float maxQ2Pt; - float maxChi2; + float mMaxQ2Pt; + float mMaxChi2; + int mMaxHoles; + int mMinTrackLength; + LayerMask mHoleLayerMask; - GPUhd() seed_selector(float maxQ2Pt, float maxChi2) : maxQ2Pt(maxQ2Pt), maxChi2(maxChi2) {} + GPUhd() seed_selector(float maxQ2Pt, float maxChi2, int maxHoles, int minTrackLength, LayerMask holeLayerMask) : mMaxQ2Pt(maxQ2Pt), mMaxChi2(maxChi2), mMaxHoles(maxHoles), mMinTrackLength(minTrackLength), mHoleLayerMask(holeLayerMask) {} GPUhd() bool operator()(const TrackSeed& seed) const { - return !(seed.getQ2Pt() > maxQ2Pt || seed.getChi2() > maxChi2); + return !(seed.getQ2Pt() > mMaxQ2Pt || seed.getChi2() > mMaxChi2) && + seed.getHitLayerMask().length() >= mMinTrackLength && + seed.getHitLayerMask().isAllowed(mMaxHoles, mHoleLayerMask); } }; struct compare_track_chi2 { GPUhd() bool operator()(const TrackITSExt& a, const TrackITSExt& b) const { - return a.getChi2() < b.getChi2(); + return o2::its::track::isBetter(a, b); } }; @@ -160,30 +165,22 @@ GPUg() void __launch_bounds__(256, 1) fitTrackSeedsKernel( template GPUg() void __launch_bounds__(256, 1) computeLayerCellNeighboursKernel( CellSeed** cellSeedArray, - int* neighboursLUT, - int* neighboursIndexTable, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - const Tracklet** tracklets, + CellNeighbour* cellNeighbours, + const int sourceCellTopologyId, + const int targetCellTopologyId, const float maxChi2ClusterAttachment, const float bz, - const int layerIndex, - const unsigned int nCells, - const int maxCellNeighbours = 1e2) + const unsigned int nCells) { for (int iCurrentCellIndex = blockIdx.x * blockDim.x + threadIdx.x; iCurrentCellIndex < nCells; iCurrentCellIndex += blockDim.x * gridDim.x) { - if constexpr (!initRun) { - if (neighboursIndexTable[iCurrentCellIndex] == neighboursIndexTable[iCurrentCellIndex + 1]) { - continue; - } - } - const auto& currentCellSeed{cellSeedArray[layerIndex][iCurrentCellIndex]}; + const auto& currentCellSeed{cellSeedArray[sourceCellTopologyId][iCurrentCellIndex]}; const int nextLayerTrackletIndex{currentCellSeed.getSecondTrackletIndex()}; - const int nextLayerFirstCellIndex{cellsLUTs[layerIndex + 1][nextLayerTrackletIndex]}; - const int nextLayerLastCellIndex{cellsLUTs[layerIndex + 1][nextLayerTrackletIndex + 1]}; - int foundNeighbours{0}; + const int nextLayerFirstCellIndex{cellsLUTs[targetCellTopologyId][nextLayerTrackletIndex]}; + const int nextLayerLastCellIndex{cellsLUTs[targetCellTopologyId][nextLayerTrackletIndex + 1]}; for (int iNextCell{nextLayerFirstCellIndex}; iNextCell < nextLayerLastCellIndex; ++iNextCell) { - auto nextCellSeed{cellSeedArray[layerIndex + 1][iNextCell]}; // Copy + auto nextCellSeed{cellSeedArray[targetCellTopologyId][iNextCell]}; // Copy if (nextCellSeed.getFirstTrackletIndex() != nextLayerTrackletIndex || !currentCellSeed.getTimeStamp().isCompatible(nextCellSeed.getTimeStamp())) { break; } @@ -199,14 +196,13 @@ GPUg() void __launch_bounds__(256, 1) computeLayerCellNeighboursKernel( } if constexpr (initRun) { - atomicAdd(neighboursLUT + iNextCell, 1); - neighboursIndexTable[iCurrentCellIndex]++; + atomicAdd(neighboursCursor + iNextCell, 1); } else { - cellNeighbours[neighboursIndexTable[iCurrentCellIndex] + foundNeighbours] = {iCurrentCellIndex, iNextCell}; - foundNeighbours++; + const int offset = atomicAdd(neighboursCursor + iNextCell, 1); + cellNeighbours[offset] = {sourceCellTopologyId, iCurrentCellIndex, targetCellTopologyId, iNextCell, currentCellSeed.getLevel() + 1}; const int currentCellLevel{currentCellSeed.getLevel()}; if (currentCellLevel >= nextCellSeed.getLevel()) { - atomicMax(cellSeedArray[layerIndex + 1][iNextCell].getLevelPtr(), currentCellLevel + 1); + atomicMax(cellSeedArray[targetCellTopologyId][iNextCell].getLevelPtr(), currentCellLevel + 1); } } } @@ -221,7 +217,8 @@ GPUg() void __launch_bounds__(256, 1) computeLayerCellsKernel( Tracklet** tracklets, int** trackletsLUT, const int nTrackletsCurrent, - const int layer, + const int cellTopologyId, + const typename TrackingTopology::View topology, CellSeed* cells, int** cellsLUTs, const float* layerxX0, @@ -230,25 +227,29 @@ GPUg() void __launch_bounds__(256, 1) computeLayerCellsKernel( const float cellDeltaTanLambdaSigma, const float nSigmaCut) { + const auto cellTopology = topology.getCell(cellTopologyId); + const auto first = topology.getTransition(cellTopology.firstTransition); + const auto second = topology.getTransition(cellTopology.secondTransition); + const int layers[3] = {first.fromLayer, first.toLayer, second.toLayer}; for (int iCurrentTrackletIndex = blockIdx.x * blockDim.x + threadIdx.x; iCurrentTrackletIndex < nTrackletsCurrent; iCurrentTrackletIndex += blockDim.x * gridDim.x) { if constexpr (!initRun) { - if (cellsLUTs[layer][iCurrentTrackletIndex] == cellsLUTs[layer][iCurrentTrackletIndex + 1]) { + if (cellsLUTs[cellTopologyId][iCurrentTrackletIndex] == cellsLUTs[cellTopologyId][iCurrentTrackletIndex + 1]) { continue; } } - const Tracklet& currentTracklet = tracklets[layer][iCurrentTrackletIndex]; + const Tracklet& currentTracklet = tracklets[cellTopology.firstTransition][iCurrentTrackletIndex]; const int nextLayerClusterIndex{currentTracklet.secondClusterIndex}; - const int nextLayerFirstTrackletIndex{trackletsLUT[layer + 1][nextLayerClusterIndex]}; - const int nextLayerLastTrackletIndex{trackletsLUT[layer + 1][nextLayerClusterIndex + 1]}; + const int nextLayerFirstTrackletIndex{trackletsLUT[cellTopology.secondTransition][nextLayerClusterIndex]}; + const int nextLayerLastTrackletIndex{trackletsLUT[cellTopology.secondTransition][nextLayerClusterIndex + 1]}; if (nextLayerFirstTrackletIndex == nextLayerLastTrackletIndex) { continue; } int foundCells{0}; for (int iNextTrackletIndex{nextLayerFirstTrackletIndex}; iNextTrackletIndex < nextLayerLastTrackletIndex; ++iNextTrackletIndex) { - if (tracklets[layer + 1][iNextTrackletIndex].firstClusterIndex != nextLayerClusterIndex) { + if (tracklets[cellTopology.secondTransition][iNextTrackletIndex].firstClusterIndex != nextLayerClusterIndex) { break; } - const Tracklet& nextTracklet = tracklets[layer + 1][iNextTrackletIndex]; + const Tracklet& nextTracklet = tracklets[cellTopology.secondTransition][iNextTrackletIndex]; if (!currentTracklet.getTimeStamp().isCompatible(nextTracklet.getTimeStamp())) { continue; } @@ -256,18 +257,18 @@ GPUg() void __launch_bounds__(256, 1) computeLayerCellsKernel( if (deltaTanLambda / cellDeltaTanLambdaSigma < nSigmaCut) { const int clusId[3]{ - sortedClusters[layer][currentTracklet.firstClusterIndex].clusterId, - sortedClusters[layer + 1][nextTracklet.firstClusterIndex].clusterId, - sortedClusters[layer + 2][nextTracklet.secondClusterIndex].clusterId}; + sortedClusters[layers[0]][currentTracklet.firstClusterIndex].clusterId, + sortedClusters[layers[1]][nextTracklet.firstClusterIndex].clusterId, + sortedClusters[layers[2]][nextTracklet.secondClusterIndex].clusterId}; - const auto& cluster1_glo = unsortedClusters[layer][clusId[0]]; - const auto& cluster2_glo = unsortedClusters[layer + 1][clusId[1]]; - const auto& cluster3_tf = tfInfo[layer + 2][clusId[2]]; + const auto& cluster1_glo = unsortedClusters[layers[0]][clusId[0]]; + const auto& cluster2_glo = unsortedClusters[layers[1]][clusId[1]]; + const auto& cluster3_tf = tfInfo[layers[2]][clusId[2]]; auto track{o2::its::track::buildTrackSeed(cluster1_glo, cluster2_glo, cluster3_tf, bz)}; float chi2{0.f}; bool good{false}; for (int iC{2}; iC--;) { - const TrackingFrameInfo& trackingHit = tfInfo[layer + iC][clusId[iC]]; + const TrackingFrameInfo& trackingHit = tfInfo[layers[iC]][clusId[iC]]; if (!track.rotate(trackingHit.alphaTrackingFrame)) { break; } @@ -275,7 +276,7 @@ GPUg() void __launch_bounds__(256, 1) computeLayerCellsKernel( break; } - if (!track.correctForMaterial(layerxX0[layer + iC], layerxX0[layer + iC] * constants::Radl * constants::Rho, true)) { + if (!track.correctForMaterial(layerxX0[layers[iC]], layerxX0[layers[iC]] * constants::Radl * constants::Rho, true)) { break; } @@ -295,13 +296,13 @@ GPUg() void __launch_bounds__(256, 1) computeLayerCellsKernel( if constexpr (!initRun) { TimeEstBC ts = currentTracklet.getTimeStamp(); ts += nextTracklet.getTimeStamp(); - new (cells + cellsLUTs[layer][iCurrentTrackletIndex] + foundCells) CellSeed{layer, clusId[0], clusId[1], clusId[2], iCurrentTrackletIndex, iNextTrackletIndex, track, chi2, ts}; + new (cells + cellsLUTs[cellTopologyId][iCurrentTrackletIndex] + foundCells) CellSeed{cellTopology.hitLayerMask, clusId[0], clusId[1], clusId[2], iCurrentTrackletIndex, iNextTrackletIndex, track, chi2, ts}; } ++foundCells; } } if constexpr (initRun) { - cellsLUTs[layer][iCurrentTrackletIndex] = foundCells; + cellsLUTs[cellTopologyId][iCurrentTrackletIndex] = foundCells; } } } @@ -310,7 +311,8 @@ template GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( const IndexTableUtils* utils, const typename ROFMaskTable::View rofMask, - const int layerIndex, + const int transitionId, + const typename TrackingTopology::View topology, const typename ROFOverlapTable::View rofOverlaps, const typename ROFVertexLookupTable::View vertexLUT, const Vertex* vertices, @@ -332,17 +334,20 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( const float meanDeltaR, const float MSAngle) { + const auto transition = topology.getTransition(transitionId); + const int fromLayer = transition.fromLayer; + const int toLayer = transition.toLayer; const int phiBins{utils->getNphiBins()}; const int zBins{utils->getNzBins()}; const int tableSize{phiBins * zBins + 1}; - const int totalROFs0 = rofOverlaps.getLayer(layerIndex).mNROFsTF; - const int totalROFs1 = rofOverlaps.getLayer(layerIndex + 1).mNROFsTF; + const int totalROFs0 = rofOverlaps.getLayer(fromLayer).mNROFsTF; + const int totalROFs1 = rofOverlaps.getLayer(toLayer).mNROFsTF; for (unsigned int pivotROF{blockIdx.x}; pivotROF < totalROFs0; pivotROF += gridDim.x) { - if (!rofMask.isROFEnabled(layerIndex, pivotROF)) { + if (!rofMask.isROFEnabled(fromLayer, pivotROF)) { continue; } - const auto& pvs = vertexLUT.getVertices(layerIndex, pivotROF); + const auto& pvs = vertexLUT.getVertices(fromLayer, pivotROF); auto primaryVertices = gpuSpan(&vertices[pvs.getFirstEntry()], pvs.getEntries()); if (primaryVertices.empty()) { continue; @@ -353,12 +358,12 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( continue; } - const auto& rofOverlap = rofOverlaps.getOverlap(layerIndex, layerIndex + 1, pivotROF); + const auto& rofOverlap = rofOverlaps.getOverlap(fromLayer, toLayer, pivotROF); if (!rofOverlap.getEntries()) { continue; } - auto clustersCurrentLayer = getClustersOnLayer(pivotROF, totalROFs0, layerIndex, ROFClusters, clusters); + auto clustersCurrentLayer = getClustersOnLayer(pivotROF, totalROFs0, fromLayer, ROFClusters, clusters); if (clustersCurrentLayer.empty()) { continue; } @@ -367,12 +372,12 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( unsigned int storedTracklets{0}; const auto& currentCluster{clustersCurrentLayer[currentClusterIndex]}; - const int currentSortedIndex{ROFClusters[layerIndex][pivotROF] + currentClusterIndex}; - if (usedClusters[layerIndex][currentCluster.clusterId]) { + const int currentSortedIndex{ROFClusters[fromLayer][pivotROF] + currentClusterIndex}; + if (usedClusters[fromLayer][currentCluster.clusterId]) { continue; } if constexpr (!initRun) { - if (trackletsLUT[layerIndex][currentSortedIndex] == trackletsLUT[layerIndex][currentSortedIndex + 1]) { + if (trackletsLUT[transitionId][currentSortedIndex] == trackletsLUT[transitionId][currentSortedIndex + 1]) { continue; } } @@ -380,7 +385,7 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( const float inverseR0{1.f / currentCluster.radius}; for (int iV{startVtx}; iV < endVtx; ++iV) { auto& primaryVertex{primaryVertices[iV]}; - if (!vertexLUT.isVertexCompatible(layerIndex, pivotROF, primaryVertex)) { + if (!vertexLUT.isVertexCompatible(fromLayer, pivotROF, primaryVertex)) { continue; } if (primaryVertex.isFlagSet(Vertex::Flags::UPCMode) != selectUPCVertices) { @@ -393,7 +398,7 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( const float zAtRmax{tanLambda * (maxR - currentCluster.radius) + currentCluster.zCoordinate}; const float sqInverseDeltaZ0{1.f / (math_utils::Sq(currentCluster.zCoordinate - primaryVertex.getZ()) + constants::Tolerance)}; /// protecting from overflows adding the detector resolution const float sigmaZ{o2::gpu::CAMath::Sqrt(math_utils::Sq(resolution) * math_utils::Sq(tanLambda) * ((math_utils::Sq(inverseR0) + sqInverseDeltaZ0) * math_utils::Sq(meanDeltaR) + 1.f) + math_utils::Sq(meanDeltaR * MSAngle))}; - const int4 selectedBinsRect{o2::its::getBinsRect(currentCluster, layerIndex + 1, zAtRmin, zAtRmax, sigmaZ * NSigmaCut, phiCut, *utils)}; + const int4 selectedBinsRect{o2::its::getBinsRect(currentCluster, toLayer, zAtRmin, zAtRmax, sigmaZ * NSigmaCut, phiCut, *utils)}; if (selectedBinsRect.x < 0) { continue; } @@ -404,14 +409,14 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( } for (short targetROF = rofOverlap.getFirstEntry(); targetROF < rofOverlap.getEntriesBound(); ++targetROF) { - if (!rofMask.isROFEnabled(layerIndex + 1, targetROF)) { + if (!rofMask.isROFEnabled(toLayer, targetROF)) { continue; } - auto clustersNextLayer = getClustersOnLayer(targetROF, totalROFs1, layerIndex + 1, ROFClusters, clusters); + auto clustersNextLayer = getClustersOnLayer(targetROF, totalROFs1, toLayer, ROFClusters, clusters); if (clustersNextLayer.empty()) { continue; } - const auto ts = rofOverlaps.getTimeStamp(layerIndex, pivotROF, layerIndex + 1, targetROF); + const auto ts = rofOverlaps.getTimeStamp(fromLayer, pivotROF, toLayer, targetROF); if (!ts.isCompatible(primaryVertex.getTimeStamp())) { continue; } @@ -419,26 +424,26 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( int iPhiBin = (selectedBinsRect.y + iPhiCount) % phiBins; const int firstBinIndex{utils->getBinIndex(selectedBinsRect.x, iPhiBin)}; const int maxBinIndex{firstBinIndex + selectedBinsRect.z - selectedBinsRect.x + 1}; - const int firstRowClusterIndex = indexTables[layerIndex + 1][(targetROF)*tableSize + firstBinIndex]; - const int maxRowClusterIndex = indexTables[layerIndex + 1][(targetROF)*tableSize + maxBinIndex]; + const int firstRowClusterIndex = indexTables[toLayer][(targetROF)*tableSize + firstBinIndex]; + const int maxRowClusterIndex = indexTables[toLayer][(targetROF)*tableSize + maxBinIndex]; for (int nextClusterIndex{firstRowClusterIndex}; nextClusterIndex < maxRowClusterIndex; ++nextClusterIndex) { if (nextClusterIndex >= clustersNextLayer.size()) { break; } const Cluster& nextCluster{clustersNextLayer[nextClusterIndex]}; - if (usedClusters[layerIndex + 1][nextCluster.clusterId]) { + if (usedClusters[toLayer][nextCluster.clusterId]) { continue; } const float deltaPhi{o2::gpu::CAMath::Abs(currentCluster.phi - nextCluster.phi)}; const float deltaZ{o2::gpu::CAMath::Abs(tanLambda * (nextCluster.radius - currentCluster.radius) + currentCluster.zCoordinate - nextCluster.zCoordinate)}; if (deltaZ / sigmaZ < NSigmaCut && (deltaPhi < phiCut || o2::gpu::CAMath::Abs(deltaPhi - o2::constants::math::TwoPI) < phiCut)) { if constexpr (initRun) { - trackletsLUT[layerIndex][currentSortedIndex]++; // we need l0 as well for usual exclusive sums. + trackletsLUT[transitionId][currentSortedIndex]++; // we need l0 as well for usual exclusive sums. } else { const float phi{o2::gpu::CAMath::ATan2(currentCluster.yCoordinate - nextCluster.yCoordinate, currentCluster.xCoordinate - nextCluster.xCoordinate)}; const float tanL{(currentCluster.zCoordinate - nextCluster.zCoordinate) / (currentCluster.radius - nextCluster.radius)}; - const int nextSortedIndex{ROFClusters[layerIndex + 1][targetROF] + nextClusterIndex}; - new (tracklets[layerIndex] + trackletsLUT[layerIndex][currentSortedIndex] + storedTracklets) Tracklet{currentSortedIndex, nextSortedIndex, tanL, phi, ts}; + const int nextSortedIndex{ROFClusters[toLayer][targetROF] + nextClusterIndex}; + new (tracklets[transitionId] + trackletsLUT[transitionId][currentSortedIndex] + storedTracklets) Tracklet{currentSortedIndex, nextSortedIndex, tanL, phi, ts}; } ++storedTracklets; } @@ -462,18 +467,20 @@ GPUg() void __launch_bounds__(256, 1) compileTrackletsLookupTableKernel( template GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( - const int layer, + const int defaultCellTopologyId, const int level, CellSeed** allCellSeeds, CurrentSeed* currentCellSeeds, const int* currentCellIds, + const int* currentCellTopologyIds, const unsigned int nCurrentCells, TrackSeed* updatedCellSeeds, int* updatedCellsIds, + int* updatedCellTopologyIds, int* foundSeedsTable, // auxiliary only in GPU code to compute the number of cells per iteration const unsigned char** usedClusters, // Used clusters - int* neighbours, - int* neighboursLUT, + CellNeighbour** neighbours, + int** neighboursLUT, const TrackingFrameInfo** foundTrackingFrameInfo, const float* layerxX0, const float bz, @@ -489,22 +496,33 @@ GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( } int foundSeeds{0}; const auto& currentCell{currentCellSeeds[iCurrentCell]}; + const int cellTopologyId = currentCellTopologyIds == nullptr ? defaultCellTopologyId : currentCellTopologyIds[iCurrentCell]; if (currentCell.getLevel() != level) { continue; } - if (currentCellIds == nullptr && (usedClusters[layer][currentCell.getFirstClusterIndex()] || - usedClusters[layer + 1][currentCell.getSecondClusterIndex()] || - usedClusters[layer + 2][currentCell.getThirdClusterIndex()])) { - continue; + if (currentCellIds == nullptr) { + bool used = false; + for (int layer = 0; layer < NLayers; ++layer) { + const int clusterIndex = currentCell.getCluster(layer); + used |= clusterIndex != constants::UnusedIndex && usedClusters[layer][clusterIndex]; + } + if (used) { + continue; + } } const int cellId = currentCellIds == nullptr ? iCurrentCell : currentCellIds[iCurrentCell]; + if (cellTopologyId < 0 || neighboursLUT[cellTopologyId] == nullptr || neighbours[cellTopologyId] == nullptr) { + continue; + } - const int startNeighbourId{cellId ? neighboursLUT[cellId - 1] : 0}; - const int endNeighbourId{neighboursLUT[cellId]}; + const int startNeighbourId{neighboursLUT[cellTopologyId][cellId]}; + const int endNeighbourId{neighboursLUT[cellTopologyId][cellId + 1]}; for (int iNeighbourCell{startNeighbourId}; iNeighbourCell < endNeighbourId; ++iNeighbourCell) { - const int neighbourCellId = neighbours[iNeighbourCell]; - const auto& neighbourCell = allCellSeeds[layer - 1][neighbourCellId]; + const auto& neighbourRef = neighbours[cellTopologyId][iNeighbourCell]; + const int neighbourCellTopologyId = neighbourRef.cellTopology; + const int neighbourCellId = neighbourRef.cell; + const auto& neighbourCell = allCellSeeds[neighbourCellTopologyId][neighbourCellId]; if (neighbourCell.getSecondTrackletIndex() != currentCell.getFirstTrackletIndex()) { continue; @@ -515,11 +533,13 @@ GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( if (currentCell.getLevel() - 1 != neighbourCell.getLevel()) { continue; } - if (usedClusters[layer - 1][neighbourCell.getFirstClusterIndex()]) { + const int neighbourLayer = neighbourCell.getInnerLayer(); + const int neighbourCluster = neighbourCell.getFirstClusterIndex(); + if (usedClusters[neighbourLayer][neighbourCluster]) { continue; } TrackSeed seed{currentCell}; - auto& trHit = foundTrackingFrameInfo[layer - 1][neighbourCell.getFirstClusterIndex()]; + auto& trHit = foundTrackingFrameInfo[neighbourLayer][neighbourCluster]; if (!seed.rotate(trHit.alphaTrackingFrame)) { continue; @@ -530,7 +550,7 @@ GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( } if (matCorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { - if (!seed.correctForMaterial(layerxX0[layer - 1], layerxX0[layer - 1] * constants::Radl * constants::Rho, true)) { + if (!seed.correctForMaterial(layerxX0[neighbourLayer], layerxX0[neighbourLayer] * constants::Radl * constants::Rho, true)) { continue; } } @@ -546,11 +566,15 @@ GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( if constexpr (dryRun) { foundSeedsTable[iCurrentCell]++; } else { - seed.getClusters()[layer - 1] = neighbourCell.getFirstClusterIndex(); + seed.getClusters()[neighbourLayer] = neighbourCluster; + auto mask = seed.getHitLayerMask(); + mask.set(neighbourLayer); + seed.setHitLayerMask(mask); seed.setLevel(neighbourCell.getLevel()); seed.setFirstTrackletIndex(neighbourCell.getFirstTrackletIndex()); seed.setSecondTrackletIndex(neighbourCell.getSecondTrackletIndex()); updatedCellsIds[foundSeedsTable[iCurrentCell] + foundSeeds] = neighbourCellId; + updatedCellTopologyIds[foundSeedsTable[iCurrentCell] + foundSeeds] = neighbourCellTopologyId; updatedCellSeeds[foundSeedsTable[iCurrentCell] + foundSeeds] = seed; } foundSeeds++; @@ -563,7 +587,9 @@ GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( template void countTrackletsInROFsHandler(const IndexTableUtils* utils, const typename ROFMaskTable::View& rofMask, - const int layer, + const int transitionId, + const int fromLayer, + const int toLayer, const typename ROFOverlapTable::View& rofOverlaps, const typename ROFVertexLookupTable::View& vertexLUT, const int vertexId, @@ -578,20 +604,22 @@ void countTrackletsInROFsHandler(const IndexTableUtils* utils, gsl::span trackletsLUTsHost, const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const typename TrackingTopology::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, std::array& minRs, std::array& maxRs, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, gpu::Streams& streams) { - gpu::computeLayerTrackletsMultiROFKernel<<<60, 256, 0, streams[layer].get()>>>( + gpu::computeLayerTrackletsMultiROFKernel<<<60, 256, 0, streams[transitionId].get()>>>( utils, rofMask, - layer, + transitionId, + topology, rofOverlaps, vertexLUT, vertices, @@ -605,21 +633,23 @@ void countTrackletsInROFsHandler(const IndexTableUtils* utils, trackletsLUTs, selectUPCVertices, NSigmaCut, - phiCuts[layer], + transitionPhiCuts[transitionId], resolutionPV, - minRs[layer + 1], - maxRs[layer + 1], - resolutions[layer], - radii[layer + 1] - radii[layer], - mulScatAng[layer]); - auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[layer].get()); - thrust::exclusive_scan(nosync_policy, trackletsLUTsHost[layer], trackletsLUTsHost[layer] + nClusters[layer] + 1, trackletsLUTsHost[layer]); + minRs[toLayer], + maxRs[toLayer], + resolutions[fromLayer], + radii[toLayer] - radii[fromLayer], + transitionMSAngles[transitionId]); + auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[transitionId].get()); + thrust::exclusive_scan(nosync_policy, trackletsLUTsHost[transitionId], trackletsLUTsHost[transitionId] + nClusters[fromLayer] + 1, trackletsLUTsHost[transitionId]); } template void computeTrackletsInROFsHandler(const IndexTableUtils* utils, const typename ROFMaskTable::View& rofMask, - const int layer, + const int transitionId, + const int fromLayer, + const int toLayer, const typename ROFOverlapTable::View& rofOverlaps, const typename ROFVertexLookupTable::View& vertexLUT, const int vertexId, @@ -637,20 +667,22 @@ void computeTrackletsInROFsHandler(const IndexTableUtils* utils, gsl::span trackletsLUTsHost, const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const typename TrackingTopology::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, std::array& minRs, std::array& maxRs, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, gpu::Streams& streams) { - gpu::computeLayerTrackletsMultiROFKernel<<<60, 256, 0, streams[layer].get()>>>( + gpu::computeLayerTrackletsMultiROFKernel<<<60, 256, 0, streams[transitionId].get()>>>( utils, rofMask, - layer, + transitionId, + topology, rofOverlaps, vertexLUT, vertices, @@ -664,25 +696,25 @@ void computeTrackletsInROFsHandler(const IndexTableUtils* utils, trackletsLUTs, selectUPCVertices, NSigmaCut, - phiCuts[layer], + transitionPhiCuts[transitionId], resolutionPV, - minRs[layer + 1], - maxRs[layer + 1], - resolutions[layer], - radii[layer + 1] - radii[layer], - mulScatAng[layer]); - thrust::device_ptr tracklets_ptr(spanTracklets[layer]); - auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[layer].get()); - thrust::sort(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[layer]); - auto unique_end = thrust::unique(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[layer]); - nTracklets[layer] = unique_end - tracklets_ptr; - if (layer) { - GPUChkErrS(cudaMemsetAsync(trackletsLUTsHost[layer], 0, (nClusters[layer] + 1) * sizeof(int), streams[layer].get())); - gpu::compileTrackletsLookupTableKernel<<<60, 256, 0, streams[layer].get()>>>( - spanTracklets[layer], - trackletsLUTsHost[layer], - nTracklets[layer]); - thrust::exclusive_scan(nosync_policy, trackletsLUTsHost[layer], trackletsLUTsHost[layer] + nClusters[layer] + 1, trackletsLUTsHost[layer]); + minRs[toLayer], + maxRs[toLayer], + resolutions[fromLayer], + radii[toLayer] - radii[fromLayer], + transitionMSAngles[transitionId]); + thrust::device_ptr tracklets_ptr(spanTracklets[transitionId]); + auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[transitionId].get()); + thrust::sort(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[transitionId]); + auto unique_end = thrust::unique(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[transitionId]); + nTracklets[transitionId] = unique_end - tracklets_ptr; + if (fromLayer > 0) { + GPUChkErrS(cudaMemsetAsync(trackletsLUTsHost[transitionId], 0, (nClusters[fromLayer] + 1) * sizeof(int), streams[transitionId].get())); + gpu::compileTrackletsLookupTableKernel<<<60, 256, 0, streams[transitionId].get()>>>( + spanTracklets[transitionId], + trackletsLUTsHost[transitionId], + nTracklets[transitionId]); + thrust::exclusive_scan(nosync_policy, trackletsLUTsHost[transitionId], trackletsLUTsHost[transitionId] + nClusters[fromLayer] + 1, trackletsLUTsHost[transitionId]); } } @@ -694,7 +726,8 @@ void countCellsHandler( Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, + const int cellTopologyId, + const typename TrackingTopology::View topology, CellSeed* cells, int** cellsLUTsArrayDevice, int* cellsLUTsHost, @@ -707,14 +740,15 @@ void countCellsHandler( gpu::Streams& streams) { thrust::device_vector layerxX0(layerxX0Host); - gpu::computeLayerCellsKernel<<<60, 256, 0, streams[layer].get()>>>( - sortedClusters, // const Cluster** - unsortedClusters, // const Cluster** - tfInfo, // const TrackingFrameInfo** - tracklets, // const Tracklets** - trackletsLUT, // const int** - nTracklets, // const int - layer, // const int + gpu::computeLayerCellsKernel<<<60, 256, 0, streams[cellTopologyId].get()>>>( + sortedClusters, // const Cluster** + unsortedClusters, // const Cluster** + tfInfo, // const TrackingFrameInfo** + tracklets, // const Tracklets** + trackletsLUT, // const int** + nTracklets, // const int + cellTopologyId, // const int + topology, cells, // CellSeed* cellsLUTsArrayDevice, // int** thrust::raw_pointer_cast(&layerxX0[0]), @@ -722,7 +756,7 @@ void countCellsHandler( maxChi2ClusterAttachment, // const float cellDeltaTanLambdaSigma, // const float nSigmaCut); // const float - auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[layer].get()); + auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[cellTopologyId].get()); thrust::exclusive_scan(nosync_policy, cellsLUTsHost, cellsLUTsHost + nTracklets + 1, cellsLUTsHost); } @@ -734,7 +768,8 @@ void computeCellsHandler( Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, + const int cellTopologyId, + const typename TrackingTopology::View topology, CellSeed* cells, int** cellsLUTsArrayDevice, int* cellsLUTsHost, @@ -746,14 +781,15 @@ void computeCellsHandler( gpu::Streams& streams) { thrust::device_vector layerxX0(layerxX0Host); - gpu::computeLayerCellsKernel<<<60, 256, 0, streams[layer].get()>>>( - sortedClusters, // const Cluster** - unsortedClusters, // const Cluster** - tfInfo, // const TrackingFrameInfo** - tracklets, // const Tracklets** - trackletsLUT, // const int** - nTracklets, // const int - layer, // const int + gpu::computeLayerCellsKernel<<<60, 256, 0, streams[cellTopologyId].get()>>>( + sortedClusters, // const Cluster** + unsortedClusters, // const Cluster** + tfInfo, // const TrackingFrameInfo** + tracklets, // const Tracklets** + trackletsLUT, // const int** + nTracklets, // const int + cellTopologyId, // const int + topology, cells, // CellSeed* cellsLUTsArrayDevice, // int** thrust::raw_pointer_cast(&layerxX0[0]), @@ -765,64 +801,60 @@ void computeCellsHandler( template void countCellNeighboursHandler(CellSeed** cellsLayersDevice, - int* neighboursLUT, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, + const int sourceCellTopologyId, + const int targetCellTopologyId, 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) { gpu::computeLayerCellNeighboursKernel<<<60, 256, 0, stream.get()>>>( cellsLayersDevice, - neighboursLUT, - neighboursIndexTable, + neighboursCursor, cellsLUTs, - cellNeighbours, - tracklets, + nullptr, + sourceCellTopologyId, + targetCellTopologyId, maxChi2ClusterAttachment, bz, - layerIndex, - nCells, - maxCellNeighbours); + nCells); +} + +void scanCellNeighboursHandler(int* neighboursCursor, + int* neighboursLUT, + const unsigned int nCells, + o2::its::ExternalAllocator* alloc, + gpu::Stream& stream) +{ auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(stream.get()); - thrust::inclusive_scan(nosync_policy, neighboursLUT, neighboursLUT + nCellsNext, neighboursLUT); - thrust::exclusive_scan(nosync_policy, neighboursIndexTable, neighboursIndexTable + nCells + 1, neighboursIndexTable); + thrust::exclusive_scan(nosync_policy, neighboursCursor, neighboursCursor + nCells + 1, neighboursCursor); + GPUChkErrS(cudaMemcpyAsync(neighboursLUT, neighboursCursor, (nCells + 1) * sizeof(int), cudaMemcpyDeviceToDevice, stream.get())); } template void computeCellNeighboursHandler(CellSeed** cellsLayersDevice, - int* neighboursLUT, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, + CellNeighbour* cellNeighbours, + const int sourceCellTopologyId, + const int targetCellTopologyId, const float maxChi2ClusterAttachment, const float bz, - const int layerIndex, const unsigned int nCells, - const unsigned int nCellsNext, - const int maxCellNeighbours, gpu::Stream& stream) { gpu::computeLayerCellNeighboursKernel<<<60, 256, 0, stream.get()>>>( cellsLayersDevice, - neighboursLUT, - neighboursIndexTable, + neighboursCursor, cellsLUTs, cellNeighbours, - tracklets, + sourceCellTopologyId, + targetCellTopologyId, maxChi2ClusterAttachment, bz, - layerIndex, - nCells, - maxCellNeighbours); + nCells); } int filterCellNeighboursHandler(gpuPair* cellNeighbourPairs, @@ -842,19 +874,24 @@ int filterCellNeighboursHandler(gpuPair* cellNeighbourPairs, } template -void processNeighboursHandler(const int startLayer, - const int startLevel, +void processNeighboursHandler(const int startLevel, + const int defaultCellTopologyId, CellSeed** allCellSeeds, CellSeed* currentCellSeeds, - std::array& nCells, + const int* currentCellTopologyIds, + const int* currentCellIds, + const int* nCells, const unsigned char** usedClusters, - std::array& neighbours, - gsl::span neighboursDeviceLUTs, + CellNeighbour** neighbours, + int** neighboursDeviceLUTs, const TrackingFrameInfo** foundTrackingFrameInfo, bounded_vector>& seedsHost, const float bz, const float maxChi2ClusterAttachment, const float maxChi2NDF, + const int maxHoles, + const int minTrackLength, + const LayerMask holeLayerMask, const std::vector& layerxX0Host, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, @@ -865,22 +902,24 @@ void processNeighboursHandler(const int startLayer, auto allocInt = gpu::TypedAllocator(alloc); auto allocTrackSeed = gpu::TypedAllocator>(alloc); thrust::device_vector layerxX0(layerxX0Host); - thrust::device_vector> foundSeedsTable(nCells[startLayer] + 1, 0, allocInt); + thrust::device_vector> foundSeedsTable(nCells[defaultCellTopologyId] + 1, 0, allocInt); auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(gpu::Stream::DefaultStream); gpu::processNeighboursKernel<<<60, 256>>>( - startLayer, + defaultCellTopologyId, startLevel, allCellSeeds, currentCellSeeds, nullptr, - nCells[startLayer], + nullptr, + nCells[defaultCellTopologyId], + nullptr, nullptr, nullptr, thrust::raw_pointer_cast(&foundSeedsTable[0]), usedClusters, - neighbours[startLayer - 1], - neighboursDeviceLUTs[startLayer - 1], + neighbours, + neighboursDeviceLUTs, foundTrackingFrameInfo, thrust::raw_pointer_cast(&layerxX0[0]), bz, @@ -890,20 +929,23 @@ void processNeighboursHandler(const int startLayer, thrust::exclusive_scan(nosync_policy, foundSeedsTable.begin(), foundSeedsTable.end(), foundSeedsTable.begin()); thrust::device_vector> updatedCellId(foundSeedsTable.back(), 0, allocInt); + thrust::device_vector> updatedCellTopologyId(foundSeedsTable.back(), 0, allocInt); thrust::device_vector, gpu::TypedAllocator>> updatedCellSeed(foundSeedsTable.back(), allocTrackSeed); gpu::processNeighboursKernel<<<60, 256>>>( - startLayer, + defaultCellTopologyId, startLevel, allCellSeeds, currentCellSeeds, nullptr, - nCells[startLayer], + nullptr, + nCells[defaultCellTopologyId], thrust::raw_pointer_cast(&updatedCellSeed[0]), thrust::raw_pointer_cast(&updatedCellId[0]), + thrust::raw_pointer_cast(&updatedCellTopologyId[0]), thrust::raw_pointer_cast(&foundSeedsTable[0]), usedClusters, - neighbours[startLayer - 1], - neighboursDeviceLUTs[startLayer - 1], + neighbours, + neighboursDeviceLUTs, foundTrackingFrameInfo, thrust::raw_pointer_cast(&layerxX0[0]), bz, @@ -914,29 +956,35 @@ void processNeighboursHandler(const int startLayer, int level = startLevel; thrust::device_vector> lastCellId(allocInt); + thrust::device_vector> lastCellTopologyId(allocInt); thrust::device_vector, gpu::TypedAllocator>> lastCellSeed(allocTrackSeed); - for (int iLayer{startLayer - 1}; iLayer > 0 && level > 2; --iLayer) { + while (level > 2 && !updatedCellSeed.empty()) { lastCellSeed.swap(updatedCellSeed); lastCellId.swap(updatedCellId); + lastCellTopologyId.swap(updatedCellTopologyId); thrust::device_vector, gpu::TypedAllocator>>(allocTrackSeed).swap(updatedCellSeed); thrust::device_vector>(allocInt).swap(updatedCellId); + thrust::device_vector>(allocInt).swap(updatedCellTopologyId); auto lastCellSeedSize{lastCellSeed.size()}; foundSeedsTable.resize(lastCellSeedSize + 1); thrust::fill(nosync_policy, foundSeedsTable.begin(), foundSeedsTable.end(), 0); + --level; gpu::processNeighboursKernel><<<60, 256>>>( - iLayer, - --level, + constants::UnusedIndex, + level, allCellSeeds, thrust::raw_pointer_cast(&lastCellSeed[0]), thrust::raw_pointer_cast(&lastCellId[0]), + thrust::raw_pointer_cast(&lastCellTopologyId[0]), lastCellSeedSize, nullptr, nullptr, + nullptr, thrust::raw_pointer_cast(&foundSeedsTable[0]), usedClusters, - neighbours[iLayer - 1], - neighboursDeviceLUTs[iLayer - 1], + neighbours, + neighboursDeviceLUTs, foundTrackingFrameInfo, thrust::raw_pointer_cast(&layerxX0[0]), bz, @@ -948,22 +996,26 @@ void processNeighboursHandler(const int startLayer, auto foundSeeds{foundSeedsTable.back()}; updatedCellId.resize(foundSeeds); thrust::fill(nosync_policy, updatedCellId.begin(), updatedCellId.end(), 0); + updatedCellTopologyId.resize(foundSeeds); + thrust::fill(nosync_policy, updatedCellTopologyId.begin(), updatedCellTopologyId.end(), 0); updatedCellSeed.resize(foundSeeds); thrust::fill(nosync_policy, updatedCellSeed.begin(), updatedCellSeed.end(), TrackSeed()); gpu::processNeighboursKernel><<<60, 256>>>( - iLayer, + constants::UnusedIndex, level, allCellSeeds, thrust::raw_pointer_cast(&lastCellSeed[0]), thrust::raw_pointer_cast(&lastCellId[0]), + thrust::raw_pointer_cast(&lastCellTopologyId[0]), lastCellSeedSize, thrust::raw_pointer_cast(&updatedCellSeed[0]), thrust::raw_pointer_cast(&updatedCellId[0]), + thrust::raw_pointer_cast(&updatedCellTopologyId[0]), thrust::raw_pointer_cast(&foundSeedsTable[0]), usedClusters, - neighbours[iLayer - 1], - neighboursDeviceLUTs[iLayer - 1], + neighbours, + neighboursDeviceLUTs, foundTrackingFrameInfo, thrust::raw_pointer_cast(&layerxX0[0]), bz, @@ -973,7 +1025,7 @@ void processNeighboursHandler(const int startLayer, } GPUChkErrS(cudaStreamSynchronize(gpu::Stream::DefaultStream)); thrust::device_vector, gpu::TypedAllocator>> outSeeds(updatedCellSeed.size(), allocTrackSeed); - auto end = thrust::copy_if(nosync_policy, updatedCellSeed.begin(), updatedCellSeed.end(), outSeeds.begin(), gpu::seed_selector(1.e3, maxChi2NDF * ((startLevel + 2) * 2 - 5))); + auto end = thrust::copy_if(nosync_policy, updatedCellSeed.begin(), updatedCellSeed.end(), outSeeds.begin(), gpu::seed_selector(1.e3, maxChi2NDF * ((startLevel + 2) * 2 - 5), maxHoles, minTrackLength, holeLayerMask)); auto s{end - outSeeds.begin()}; seedsHost.reserve(seedsHost.size() + s); thrust::copy(outSeeds.begin(), outSeeds.begin() + s, std::back_inserter(seedsHost)); @@ -1081,7 +1133,9 @@ void computeTrackSeedHandler(TrackSeed* trackSeeds, /// Explicit instantiation of ITS2 handlers template void countTrackletsInROFsHandler<7>(const IndexTableUtils<7>* utils, const ROFMaskTable<7>::View& rofMask, - const int layer, + const int transitionId, + const int fromLayer, + const int toLayer, const ROFOverlapTable<7>::View& rofOverlaps, const ROFVertexLookupTable<7>::View& vertexLUT, const int vertexId, @@ -1096,19 +1150,22 @@ template void countTrackletsInROFsHandler<7>(const IndexTableUtils<7>* utils, gsl::span trackletsLUTsHost, const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const TrackingTopology<7>::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, std::array& minRs, std::array& maxRs, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, gpu::Streams& streams); template void computeTrackletsInROFsHandler<7>(const IndexTableUtils<7>* utils, const ROFMaskTable<7>::View& rofMask, - const int layer, + const int transitionId, + const int fromLayer, + const int toLayer, const ROFOverlapTable<7>::View& rofOverlaps, const ROFVertexLookupTable<7>::View& vertexLUT, const int vertexId, @@ -1126,13 +1183,14 @@ template void computeTrackletsInROFsHandler<7>(const IndexTableUtils<7>* utils, gsl::span trackletsLUTsHost, const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const TrackingTopology<7>::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, std::array& minRs, std::array& maxRs, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, gpu::Streams& streams); @@ -1142,7 +1200,8 @@ template void countCellsHandler<7>(const Cluster** sortedClusters, Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, + const int cellTopologyId, + const TrackingTopology<7>::View topology, CellSeed* cells, int** cellsLUTsArrayDevice, int* cellsLUTsHost, @@ -1160,7 +1219,8 @@ template void computeCellsHandler<7>(const Cluster** sortedClusters, Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, + const int cellTopologyId, + const TrackingTopology<7>::View topology, CellSeed* cells, int** cellsLUTsArrayDevice, int* cellsLUTsHost, @@ -1172,47 +1232,44 @@ template void computeCellsHandler<7>(const Cluster** sortedClusters, gpu::Streams& streams); template void countCellNeighboursHandler<7>(CellSeed** cellsLayersDevice, - int* neighboursLUT, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, + const int sourceCellTopologyId, + const int targetCellTopologyId, 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<7>(CellSeed** cellsLayersDevice, - int* neighboursLUT, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, + CellNeighbour* cellNeighbours, + const int sourceCellTopologyId, + const int targetCellTopologyId, 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<7>(const int startLayer, - const int startLevel, +template void processNeighboursHandler<7>(const int startLevel, + const int defaultCellTopologyId, CellSeed** allCellSeeds, CellSeed* currentCellSeeds, - std::array& nCells, + const int* currentCellTopologyIds, + const int* currentCellIds, + const int* nCells, const unsigned char** usedClusters, - std::array& neighbours, - gsl::span neighboursDeviceLUTs, + CellNeighbour** neighbours, + int** neighboursDeviceLUTs, const TrackingFrameInfo** foundTrackingFrameInfo, bounded_vector>& seedsHost, const float bz, const float maxChi2ClusterAttachment, const float maxChi2NDF, + const int maxHoles, + const int minTrackLength, + const LayerMask holeLayerMask, const std::vector& layerxX0Host, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, @@ -1262,7 +1319,9 @@ template void computeTrackSeedHandler(TrackSeed<7>* trackSeeds, #ifdef ENABLE_UPGRADES template void countTrackletsInROFsHandler<11>(const IndexTableUtils<11>* utils, const ROFMaskTable<11>::View& rofMask, - const int layer, + const int transitionId, + const int fromLayer, + const int toLayer, const ROFOverlapTable<11>::View& rofOverlaps, const ROFVertexLookupTable<11>::View& vertexLUT, const int vertexId, @@ -1277,19 +1336,22 @@ template void countTrackletsInROFsHandler<11>(const IndexTableUtils<11>* utils, gsl::span trackletsLUTsHost, const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const TrackingTopology<11>::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, std::array& minRs, std::array& maxRs, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, gpu::Streams& streams); template void computeTrackletsInROFsHandler<11>(const IndexTableUtils<11>* utils, const ROFMaskTable<11>::View& rofMask, - const int layer, + const int transitionId, + const int fromLayer, + const int toLayer, const ROFOverlapTable<11>::View& rofOverlaps, const ROFVertexLookupTable<11>::View& vertexLUT, const int vertexId, @@ -1307,13 +1369,14 @@ template void computeTrackletsInROFsHandler<11>(const IndexTableUtils<11>* utils gsl::span trackletsLUTsHost, const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const TrackingTopology<11>::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, std::array& minRs, std::array& maxRs, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, gpu::Streams& streams); @@ -1323,7 +1386,8 @@ template void countCellsHandler<11>(const Cluster** sortedClusters, Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, + const int cellTopologyId, + const TrackingTopology<11>::View topology, CellSeed* cells, int** cellsLUTsArrayDevice, int* cellsLUTsHost, @@ -1341,7 +1405,8 @@ template void computeCellsHandler<11>(const Cluster** sortedClusters, Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, + const int cellTopologyId, + const TrackingTopology<11>::View topology, CellSeed* cells, int** cellsLUTsArrayDevice, int* cellsLUTsHost, @@ -1353,47 +1418,44 @@ template void computeCellsHandler<11>(const Cluster** sortedClusters, gpu::Streams& streams); template void countCellNeighboursHandler<11>(CellSeed** cellsLayersDevice, - int* neighboursLUT, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, + const int sourceCellTopologyId, + const int targetCellTopologyId, 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* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, + CellNeighbour* cellNeighbours, + const int sourceCellTopologyId, + const int targetCellTopologyId, 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, +template void processNeighboursHandler<11>(const int startLevel, + const int defaultCellTopologyId, CellSeed** allCellSeeds, CellSeed* currentCellSeeds, - std::array& nCells, + const int* currentCellTopologyIds, + const int* currentCellIds, + const int* nCells, const unsigned char** usedClusters, - std::array& neighbours, - gsl::span neighboursDeviceLUTs, + CellNeighbour** neighbours, + int** neighboursDeviceLUTs, const TrackingFrameInfo** foundTrackingFrameInfo, bounded_vector>& seedsHost, const float bz, const float maxChi2ClusterAttachment, const float maxChi2NDF, + const int maxHoles, + const int minTrackLength, + const LayerMask holeLayerMask, const std::vector& layerxX0Host, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h index c7718ee666311..4706977d08ba6 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h @@ -16,7 +16,10 @@ #ifndef TRACKINGITSU_INCLUDE_CACELL_H_ #define TRACKINGITSU_INCLUDE_CACELL_H_ +#include + #include "ITStracking/Constants.h" +#include "ITStracking/LayerMask.h" #include "DataFormatsITS/TimeEstBC.h" #include "ReconstructionDataFormats/Track.h" #include "GPUCommonDef.h" @@ -24,11 +27,21 @@ namespace o2::its { +struct CellNeighbour { + int cellTopology{-1}; + int cell{-1}; + int nextCellTopology{-1}; + int nextCell{-1}; + int level{-1}; +}; + template class SeedBase : public o2::track::TrackParCovF { public: - GPUhd() int getInnerLayer() const { return getUserField(); } + GPUhd() LayerMask getHitLayerMask() const { return LayerMask{static_cast(getUserField())}; } + GPUhd() void setHitLayerMask(LayerMask mask) { setUserField(mask.value()); } + GPUhd() int getInnerLayer() const { return getHitLayerMask().first(); } GPUhd() int getFirstTrackletIndex() const { return mTracklets[0]; }; GPUhd() void setFirstTrackletIndex(int trkl) { mTracklets[0] = trkl; }; GPUhd() int getSecondTrackletIndex() const { return mTracklets[1]; }; @@ -71,9 +84,13 @@ class CellSeed final : public SeedBase public: GPUhdDefault() CellSeed() = default; GPUhd() CellSeed(int innerL, int cl0, int cl1, int cl2, int trkl0, int trkl1, const o2::track::TrackParCovF& tpc, float chi2, const TimeEstBC& time) + : CellSeed(LayerMask(innerL, innerL + 1, innerL + 2), cl0, cl1, cl2, trkl0, trkl1, tpc, chi2, time) + { + } + GPUhd() CellSeed(LayerMask hitLayerMask, int cl0, int cl1, int cl2, int trkl0, int trkl1, const o2::track::TrackParCovF& tpc, float chi2, const TimeEstBC& time) : Base(tpc, chi2, 1, time) { - setUserField(innerL); + setHitLayerMask(hitLayerMask); auto& clusters = this->clustersRaw(); clusters[0] = cl0; clusters[1] = cl1; @@ -92,12 +109,12 @@ class CellSeed final : public SeedBase GPUhd() int getThirdClusterIndex() const { return this->clustersRaw()[2]; }; GPUhd() auto& getClusters() { return this->clustersRaw(); } GPUhd() const auto& getClusters() const { return this->clustersRaw(); } - /// getCluster takes an ABSOLUTE layer index and returns UnusedIndex if the - /// layer is outside the 3 stored slots (innerL, innerL+1, innerL+2). + /// getCluster takes an ABSOLUTE layer index. Compact cluster slots are + /// mapped to absolute layers by set-bit order in the hit-layer mask. GPUhd() int getCluster(int layer) const { - const int rel = layer - getInnerLayer(); - return (rel >= 0 && rel < constants::ClustersPerCell) ? this->clustersRaw()[rel] : constants::UnusedIndex; + const int slot = getHitLayerMask().slot(layer); + return (slot >= 0 && slot < constants::ClustersPerCell) ? this->clustersRaw()[slot] : constants::UnusedIndex; } }; @@ -114,14 +131,17 @@ class TrackSeed final : public SeedBase GPUhd() TrackSeed(const CellSeed& cs) : Base(static_cast(cs), cs.getChi2(), cs.getLevel(), cs.getTimeStamp()) { - this->setUserField(cs.getInnerLayer()); + this->setHitLayerMask(cs.getHitLayerMask()); this->setFirstTrackletIndex(cs.getFirstTrackletIndex()); this->setSecondTrackletIndex(cs.getSecondTrackletIndex()); - const int innerL = cs.getInnerLayer(); auto& clusters = this->clustersRaw(); - clusters[innerL + 0] = cs.getFirstClusterIndex(); - clusters[innerL + 1] = cs.getSecondClusterIndex(); - clusters[innerL + 2] = cs.getThirdClusterIndex(); + int slot = 0; + const auto hitMask = cs.getHitLayerMask(); + for (int layer = 0; layer < NLayers; ++layer) { + if (hitMask.has(layer)) { + clusters[layer] = cs.getClusters()[slot++]; + } + } } GPUhdDefault() TrackSeed(const TrackSeed&) = default; GPUhdDefault() ~TrackSeed() = default; @@ -129,14 +149,27 @@ class TrackSeed final : public SeedBase GPUhdDefault() TrackSeed& operator=(const TrackSeed&) = default; GPUhdDefault() TrackSeed& operator=(TrackSeed&&) = default; - /// Three-cluster view of the original cell — note: innerL (UserField) is not - /// updated when processNeighbours extends the cluster list leftward. - GPUhd() int getFirstClusterIndex() const { return this->clustersRaw()[this->getUserField()]; } - GPUhd() int getSecondClusterIndex() const { return this->clustersRaw()[this->getUserField() + 1]; } - GPUhd() int getThirdClusterIndex() const { return this->clustersRaw()[this->getUserField() + 2]; } + GPUhd() int getFirstClusterIndex() const { return getClusterBySlot(0); } + GPUhd() int getSecondClusterIndex() const { return getClusterBySlot(1); } + GPUhd() int getThirdClusterIndex() const { return getClusterBySlot(2); } GPUhd() auto& getClusters() { return this->clustersRaw(); } GPUhd() const auto& getClusters() const { return this->clustersRaw(); } GPUhd() int getCluster(int layer) const { return this->clustersRaw()[layer]; } + + private: + GPUhd() int getClusterBySlot(int requestedSlot) const + { + int slot = 0; + const auto hitMask = this->getHitLayerMask(); + for (int layer = 0; layer < NLayers; ++layer) { + if (hitMask.has(layer)) { + if (slot++ == requestedSlot) { + return this->clustersRaw()[layer]; + } + } + } + return constants::UnusedIndex; + } }; } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h index ce7b3e5a87630..c939f39532fdb 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h @@ -43,7 +43,12 @@ enum class IterationStep : uint8_t { using IterationSteps = o2::utils::EnumFlags; struct TrackingParameters { - int CellMinimumLevel() const noexcept { return MinTrackLength - constants::ClustersPerCell + 1; } + int CellMinimumLevel() const noexcept + { + const int minClusters = MinTrackLength - (MaxHoles > 0 ? MaxHoles : 0); + const int effectiveMinClusters = minClusters > constants::ClustersPerCell ? minClusters : constants::ClustersPerCell; + return effectiveMinClusters - constants::ClustersPerCell + 1; + } int NeighboursPerRoad() const noexcept { return NLayers - 3; } int CellsPerRoad() const noexcept { return NLayers - 2; } int TrackletsPerRoad() const noexcept { return NLayers - 1; } @@ -68,6 +73,8 @@ struct TrackingParameters { bool AllowSharingFirstCluster = false; int ClusterSharing = 0; int MinTrackLength = 7; + int MaxHoles = 0; + uint16_t HoleLayerMask = 0; float NSigmaCut = 5; float PVres = 1.e-2f; /// Trackleting cuts diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/LayerMask.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/LayerMask.h new file mode 100644 index 0000000000000..9fe9894b3b457 --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/LayerMask.h @@ -0,0 +1,115 @@ +// Copyright 2019-2026 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 TRACKINGITSU_INCLUDE_LAYERMASK_H_ +#define TRACKINGITSU_INCLUDE_LAYERMASK_H_ + +#include +#include + +#ifndef GPUCA_GPUCODE +#include +#include +#endif + +#include "GPUCommonDef.h" +#include "GPUCommonMath.h" +#include "ITStracking/Constants.h" + +namespace o2::its +{ + +struct LayerMask { + GPUhdDefault() constexpr LayerMask() noexcept = default; + GPUhdDefault() constexpr LayerMask(uint16_t mask) noexcept : mBits{mask} {} + GPUhdDefault() constexpr LayerMask(int layer0, int layer1, int layer2) noexcept + : mBits{static_cast((uint16_t(1) << layer0) | (uint16_t(1) << layer1) | (uint16_t(1) << layer2))} + { + } + GPUhdi() constexpr operator uint16_t() const noexcept { return mBits; } + GPUhdi() constexpr uint16_t value() const noexcept { return mBits; } + GPUhdi() constexpr void set(int layer) noexcept { mBits |= (uint16_t(1) << layer); } + + GPUhdi() LayerMask operator~() const noexcept { return LayerMask{static_cast(~mBits)}; } + GPUhdi() LayerMask operator&(LayerMask other) const noexcept { return LayerMask{static_cast(mBits & other.mBits)}; } + GPUhdi() LayerMask operator|(LayerMask other) const noexcept { return LayerMask{static_cast(mBits | other.mBits)}; } + GPUhdi() LayerMask& operator&=(LayerMask other) noexcept + { + mBits &= other.mBits; + return *this; + } + GPUhdi() LayerMask& operator|=(LayerMask other) noexcept + { + mBits |= other.mBits; + return *this; + } + + GPUhdi() bool empty() const noexcept { return mBits == 0; } + GPUhdi() bool has(int layer) const noexcept { return mBits & (uint16_t(1) << layer); } + GPUhdi() bool isSubsetOf(LayerMask allowed) const noexcept { return (*this & ~allowed).empty(); } + GPUhdi() bool isAllowedHoleMask(int maxHoles, LayerMask allowedHoleMask) const noexcept + { + const int allowedHoles = maxHoles > 0 ? maxHoles : 0; + return count() <= allowedHoles && isSubsetOf(allowedHoleMask); + } + GPUhdi() bool isAllowed(int maxHoles, LayerMask allowedHoleMask) const noexcept + { + return holeMask().isAllowedHoleMask(maxHoles, allowedHoleMask); + } + GPUhdi() int length() const noexcept { return empty() ? 0 : last() - first() + 1; } + GPUhdi() int count() const noexcept { return static_cast(o2::gpu::GPUCommonMath::Popcount(mBits)); } + GPUhdi() int first() const noexcept { return mBits ? static_cast(o2::gpu::GPUCommonMath::Ctz(mBits)) : constants::UnusedIndex; } + GPUhdi() int last() const noexcept { return mBits ? 31 - static_cast(o2::gpu::GPUCommonMath::Clz(mBits)) : constants::UnusedIndex; } + GPUhdi() LayerMask holeMask() const noexcept + { + return empty() ? LayerMask{0} : (span(first(), last()) & ~(*this)); + } + + GPUhdi() int slot(int layer) const noexcept + { + if (!has(layer)) { + return constants::UnusedIndex; + } + const uint32_t lowerLayers = (uint32_t(1) << layer) - 1; + return static_cast(o2::gpu::GPUCommonMath::Popcount(static_cast(mBits) & lowerLayers)); + } + + static GPUhdi() LayerMask span(int fromLayer, int toLayer) noexcept + { + if (fromLayer > toLayer) { + return 0; + } + const uint32_t upper = (uint32_t(1) << (toLayer + 1)) - 1; + const uint32_t lower = (uint32_t(1) << fromLayer) - 1; + return static_cast(upper & ~lower); + } + + static GPUhdi() LayerMask skipped(int fromLayer, int toLayer) noexcept + { + return (toLayer - fromLayer <= 1) ? LayerMask{0} : span(fromLayer + 1, toLayer - 1); + } + +#ifndef GPUCA_GPUCODE + std::string asString() const { return fmt::format("{:016b}", mBits); } +#endif + + private: + uint16_t mBits{0}; +}; + +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +static_assert(sizeof(LayerMask) == sizeof(uint16_t)); +static_assert(alignof(LayerMask) == alignof(uint16_t)); + +} // namespace o2::its + +#endif diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h index d276e27638dbd..950d8c0a9117f 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h @@ -16,6 +16,8 @@ #ifndef O2_ITS_TRACKING_MATHUTILS_H_ #define O2_ITS_TRACKING_MATHUTILS_H_ +#include + #include "CommonConstants/MathConstants.h" #include "ITStracking/Constants.h" #include "MathUtils/Utils.h" diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h index b78540bddfabf..3fef2dc640cbc 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h @@ -32,6 +32,7 @@ #include "ITStracking/ExternalAllocator.h" #include "ITStracking/BoundedAllocator.h" #include "ITStracking/ROFLookupTables.h" +#include "ITStracking/TrackingTopology.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" @@ -66,6 +67,7 @@ struct TimeFrame { using ROFOverlapTableN = ROFOverlapTable; using ROFVertexLookupTableN = ROFVertexLookupTable; using ROFMaskTableN = ROFMaskTable; + using TrackingTopologyN = TrackingTopology; using TrackSeedN = TrackSeed; friend class gpu::TimeFrameGPU; @@ -112,10 +114,10 @@ struct TimeFrame { auto& getMaxRs() { return mMaxR; } float getMinR(int layer) const { return mMinR[layer]; } float getMaxR(int layer) const { return mMaxR[layer]; } - float getMSangle(int layer) const { return mMSangles[layer]; } - auto& getMSangles() { return mMSangles; } - float getPhiCut(int layer) const { return mPhiCuts[layer]; } - auto& getPhiCuts() { return mPhiCuts; } + float getTransitionPhiCut(int transitionId) const { return mTransitionPhiCuts[transitionId]; } + float getTransitionMSAngle(int transitionId) const { return mTransitionMSAngles[transitionId]; } + auto& getTransitionPhiCuts() { return mTransitionPhiCuts; } + auto& getTransitionMSAngles() { return mTransitionMSAngles; } float getPositionResolution(int layer) const { return mPositionResolution[layer]; } auto& getPositionResolutions() { return mPositionResolution; } @@ -135,6 +137,8 @@ struct TimeFrame { const auto& getIndexTableUtils() const { return mIndexTableUtils; } const auto& getROFOverlapTable() const { return mROFOverlapTable; } const auto& getROFOverlapTableView() const { return mROFOverlapTableView; } + const auto& getTrackerTopologies() const { return mTrackerTopologies; } + const auto& getTrackingTopologyView() const { return mTrackingTopologyView; } void setROFOverlapTable(ROFOverlapTableN table) { mROFOverlapTable = std::move(table); @@ -177,7 +181,10 @@ struct TimeFrame { auto& getCellsLabel(int layer) { return mCellLabels[layer]; } bool hasMCinformation() const { return mClusterLabels[0] != nullptr; } - void initialise(const TrackingParameters& trkParam, const int maxLayers = NLayers); + void initVertexingTopology(const TrackingParameters& trkParam); + void initDefaultTrackingTopology(const TrackingParameters& trkParam, const int maxLayers = NLayers); + void initTrackerTopologies(gsl::span trkParams, const int maxLayers = NLayers); + void initialise(const TrackingParameters& trkParam, const int maxLayers = NLayers, const int iteration = constants::UnusedIndex); bool isClusterUsed(int layer, int clusterId) const { return mUsedClusters[layer][clusterId]; } void markUsedCluster(int layer, int clusterId) { mUsedClusters[layer][clusterId] = true; } @@ -193,6 +200,7 @@ struct TimeFrame { auto& getCellsLookupTable() { return mCellsLookupTable; } auto& getCellsNeighbours() { return mCellsNeighbours; } + auto& getCellsNeighboursTopology() { return mCellsNeighboursTopology; } auto& getCellsNeighboursLUT() { return mCellsNeighboursLUT; } auto& getTracks() { return mTracks; } auto& getTracksLabel() { return mTracksLabel; } @@ -273,6 +281,7 @@ struct TimeFrame { bounded_vector mTracks; bounded_vector mTracksLabel; std::vector> mCellsNeighbours; + std::vector> mCellsNeighboursTopology; std::vector> mCellsLookupTable; const o2::base::PropagatorImpl* mPropagatorDevice = nullptr; // Needed only for GPU @@ -292,8 +301,8 @@ struct TimeFrame { bool isBeamPositionOverridden = false; std::array mMinR; std::array mMaxR; - bounded_vector mMSangles; - bounded_vector mPhiCuts; + bounded_vector mTransitionPhiCuts; + bounded_vector mTransitionMSAngles; bounded_vector mPositionResolution; std::array, NLayers> mClusterSize; @@ -319,6 +328,10 @@ struct TimeFrame { IndexTableUtilsN mIndexTableUtils; ROFOverlapTableN mROFOverlapTable; ROFOverlapTableN::View mROFOverlapTableView; + TrackingTopologyN mVertexingTopology; + TrackingTopologyN mDefaultTrackingTopology; + std::vector mTrackerTopologies; + typename TrackingTopologyN::View mTrackingTopologyView; ROFVertexLookupTableN mROFVertexLookupTable; ROFVertexLookupTableN::View mROFVertexLookupTableView; ROFMaskTableN mMultiplicityCutMask; diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h index 885cb0f2b9ca5..d244b39ff9d11 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h @@ -16,8 +16,6 @@ #ifndef O2_ITS_TRACKING_TRACKHELPERS_H_ #define O2_ITS_TRACKING_TRACKHELPERS_H_ -#include - #include "DataFormatsITS/TrackITS.h" #include "ITStracking/Cell.h" #include "ITStracking/Cluster.h" @@ -29,6 +27,16 @@ namespace o2::its::track { +// Prefer 1) longer track 2) sorted in chi2 +GPUhdi() bool isBetter(const o2::its::TrackITS& a, const o2::its::TrackITS& b) +{ + const auto ncla = a.getNumberOfClusters(); + const auto nclb = b.getNumberOfClusters(); + // is a as long as b ? then decide on chi2 + // otherwise prefer longer + return (ncla == nclb) ? (a.getChi2() < b.getChi2()) : ncla > nclb; +} + // Find the populated interior layer closest to the radial midpoint. // If no layer can be found, return constants::UnusedIndex. // Should minimize the sagitta bias. diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h index aa4592c63f404..647403bb6b548 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h @@ -45,7 +45,7 @@ class TrackerTraits virtual ~TrackerTraits() = default; virtual void adoptTimeFrame(TimeFrame* tf) { mTimeFrame = tf; } - virtual void initialiseTimeFrame(const int iteration) { mTimeFrame->initialise(mTrkParams[iteration], mTrkParams[iteration].NLayers); } + virtual void initialiseTimeFrame(const int iteration) { mTimeFrame->initialise(mTrkParams[iteration], mTrkParams[iteration].NLayers, iteration); } virtual void computeLayerTracklets(const int iteration, int iVertex); virtual void computeLayerCells(const int iteration); @@ -53,7 +53,7 @@ class TrackerTraits virtual void findRoads(const int iteration); template - void processNeighbours(int iteration, int iLayer, int iLevel, const bounded_vector& currentCellSeed, const bounded_vector& currentCellId, bounded_vector& updatedCellSeed, bounded_vector& updatedCellId); + void processNeighbours(int iteration, int defaultCellTopologyId, int iLevel, const bounded_vector& currentCellSeed, const bounded_vector& currentCellId, const bounded_vector& currentCellTopologyId, bounded_vector& updatedCellSeed, bounded_vector& updatedCellId, bounded_vector& updatedCellTopologyId); void acceptTracks(int iteration, bounded_vector& tracks, bounded_vector>& firstClusters, bounded_vector>& sharedFirstClusters); void markTracks(int iteration, bounded_vector>& sharedFirstClusters); diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h index 1d997ef12147a..21b4f928d5b73 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h @@ -71,6 +71,8 @@ struct TrackerParamConfig : public o2::conf::ConfigurableParamHelper0, otherwise use code defaults uint8_t startLayerMask[constants::MaxIter] = {}; // mask of start layer for this iteration (if >0) + int maxHolesIter[constants::MaxIter] = {}; // maximum number of missing internal layers allowed in the CA topology for each iteration + uint16_t holeLayerMaskIter[constants::MaxIter] = {}; // layers that may be skipped by the CA topology for each iteration float minPtIterLgt[constants::MaxIter * (MaxTrackLength - MinTrackLength + 1)] = {}; // min.pT for given track length at this iteration, used only if >0, otherwise use code defaults float sysErrY2[7] = {0}; // systematic error^2 in Y per layer float sysErrZ2[7] = {0}; // systematic error^2 in Z per layer diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingTopology.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingTopology.h new file mode 100644 index 0000000000000..2afb67609664f --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingTopology.h @@ -0,0 +1,219 @@ +// Copyright 2019-2026 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 TRACKINGITSU_INCLUDE_TRACKINGTOPOLOGY_H_ +#define TRACKINGITSU_INCLUDE_TRACKINGTOPOLOGY_H_ + +#include +#include +#include +#include + +#ifndef GPUCA_GPUCODE +#include +#include +#include "Framework/Logger.h" +#endif + +#include "CommonDataFormat/RangeReference.h" +#include "GPUCommonDef.h" +#include "GPUCommonMath.h" +#include "ITStracking/LayerMask.h" + +namespace o2::its +{ + +template +class TrackingTopology +{ + public: + using Id = uint8_t; + using Mask = LayerMask; + using Range = o2::dataformats::RangeReference; + static constexpr int MaxTransitions = (NLayers * (NLayers - 1)) / 2; + static constexpr int MaxCells = (NLayers * (NLayers - 1) * (NLayers - 2)) / 6; + static_assert(NLayers < std::numeric_limits::max()); + static_assert(MaxTransitions <= std::numeric_limits::max()); + static_assert(MaxCells <= std::numeric_limits::max()); + + // Describes from which layer to which layer the look-up happens + struct LayerTransition { + Id fromLayer{0}; + Id toLayer{0}; + }; + static_assert(std::is_standard_layout_v); + static_assert(std::is_trivially_copyable_v); + static_assert(sizeof(LayerTransition) == (2 * sizeof(Id))); + + // Describes from which LayerTransition a tracklet is allowed to originate + // and with which LayerTransition this can be combined additionally the hitMasked is cached + struct CellTopology { + Id firstTransition{0}; + Id secondTransition{0}; + Mask hitLayerMask{0}; + }; + static_assert(std::is_standard_layout_v); + static_assert(std::is_trivially_copyable_v); + static_assert(sizeof(CellTopology) == (2 * sizeof(Id)) + sizeof(Mask)); + + // GPU ready view of the underlying LUTs + struct View { + const LayerTransition* transitions{nullptr}; + const CellTopology* cells{nullptr}; + const Range* cellsByFirstTransitionIndex{nullptr}; + const Id* cellsByFirstTransition{nullptr}; + Id nTransitions{0}; + Id nCells{0}; + Id nCellsByFirstTransition{0}; + + GPUhdi() const LayerTransition& getTransition(Id id) const { return transitions[id]; } + GPUhdi() const CellTopology& getCell(Id id) const { return cells[id]; } + GPUhdi() Range getCellsStartingWithTransition(Id transitionId) const { return cellsByFirstTransitionIndex[transitionId]; } + +#ifndef GPUCA_GPUCODE + std::string asString() const + { + std::string out = fmt::format("TrackingTopology: transitions={} cells={}", nTransitions, nCells); + out += "\n transitions:"; + for (Id transitionId = 0; transitionId < nTransitions; ++transitionId) { + const auto& t = transitions[transitionId]; + out += fmt::format("\n {}: {} -> {}", transitionId, t.fromLayer, t.toLayer); + } + out += "\n cells:"; + for (Id cellId = 0; cellId < nCells; ++cellId) { + const auto& c = cells[cellId]; + const auto& first = transitions[c.firstTransition]; + const auto& second = transitions[c.secondTransition]; + out += fmt::format("\n {}: {} -> {} -> {} hitMask={} transitions=({}, {})", cellId, first.fromLayer, first.toLayer, second.toLayer, c.hitLayerMask.asString(), c.firstTransition, c.secondTransition); + } + return out; + } + + void print() const + { + LOGP(info, "{}", asString()); + } +#endif + }; + + void init(int maxLayers, int maxHoles, Mask holeLayerMask) + { + clear(); + mMaxLayers = o2::gpu::CAMath::Max(0, o2::gpu::CAMath::Min(maxLayers, NLayers)); + mMaxHoles = o2::gpu::CAMath::Max(maxHoles, 0); + mHoleLayerMask = holeLayerMask; + for (int fromLayer = 0; fromLayer < mMaxLayers; ++fromLayer) { + for (int toLayer = fromLayer + 1; toLayer < mMaxLayers; ++toLayer) { + if (Mask::skipped(fromLayer, toLayer).isAllowedHoleMask(mMaxHoles, mHoleLayerMask)) { + mTransitions[mNTransitions++] = LayerTransition{static_cast(fromLayer), static_cast(toLayer)}; + } + } + } + + for (Id firstId = 0; firstId < mNTransitions; ++firstId) { + const auto& first = mTransitions[firstId]; + for (Id secondId = 0; secondId < mNTransitions; ++secondId) { + const auto& second = mTransitions[secondId]; + if (first.toLayer != second.fromLayer) { + continue; + } + const Mask hitMask{first.fromLayer, first.toLayer, second.toLayer}; + if (hitMask.isAllowed(mMaxHoles, mHoleLayerMask)) { + mCells[mNCells++] = CellTopology{firstId, secondId, hitMask}; + } + } + } + + fillCellsByTransition(); + } + + View getView() const + { + return View{mTransitions.data(), + mCells.data(), + mCellsByFirstTransitionIndex.data(), + mCellsByFirstTransition.data(), + mNTransitions, + mNCells, + mNCellsByFirstTransition}; + } + + View getDeviceView(const LayerTransition* deviceTransitions, + const CellTopology* deviceCells, + const Range* deviceCellsByFirstTransitionIndex, + const Id* deviceCellsByFirstTransition) const + { + return View{deviceTransitions, + deviceCells, + deviceCellsByFirstTransitionIndex, + deviceCellsByFirstTransition, + mNTransitions, + mNCells, + mNCellsByFirstTransition}; + } + + const auto& getTransitions() const noexcept { return mTransitions; } + const auto& getCells() const noexcept { return mCells; } + const auto& getCellsByFirstTransitionIndex() const noexcept { return mCellsByFirstTransitionIndex; } + const auto& getCellsByFirstTransition() const noexcept { return mCellsByFirstTransition; } + Id getNTransitions() const noexcept { return mNTransitions; } + Id getNCells() const noexcept { return mNCells; } + Id getNCellsByFirstTransition() const noexcept { return mNCellsByFirstTransition; } + + private: + void clear() + { + mNTransitions = 0; + mNCells = 0; + mNCellsByFirstTransition = 0; + mTransitions.fill({}); + mCells.fill({}); + mCellsByFirstTransitionIndex.fill(Range{0, 0}); + mCellsByFirstTransition.fill(0); + } + + void fillCellsByTransition() + { + std::array counts{}; + for (Id cellId = 0; cellId < mNCells; ++cellId) { + ++counts[mCells[cellId].firstTransition]; + } + + Id offset = 0; + for (Id transitionId = 0; transitionId < mNTransitions; ++transitionId) { + mCellsByFirstTransitionIndex[transitionId].setFirstEntry(offset); + mCellsByFirstTransitionIndex[transitionId].setEntries(counts[transitionId]); + offset += counts[transitionId]; + } + + std::array cursor{}; + for (Id cellId = 0; cellId < mNCells; ++cellId) { + const Id transitionId = mCells[cellId].firstTransition; + mCellsByFirstTransition[mCellsByFirstTransitionIndex[transitionId].getFirstEntry() + cursor[transitionId]++] = cellId; + } + mNCellsByFirstTransition = offset; + } + + int mMaxLayers{0}; + int mMaxHoles{0}; + Mask mHoleLayerMask{0}; + Id mNTransitions{0}; + Id mNCells{0}; + Id mNCellsByFirstTransition{0}; + std::array mTransitions{}; + std::array mCells{}; + std::array mCellsByFirstTransitionIndex{}; + std::array mCellsByFirstTransition{}; +}; + +} // namespace o2::its + +#endif diff --git a/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx b/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx index c425d467a8061..0087da0a85ac2 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx @@ -24,8 +24,8 @@ using namespace o2::its; std::string TrackingParameters::asString() const { - std::string str = std::format("NZb:{} NPhB:{} PerVtx:{} DropFail:{} ClSh:{} TtklMinPt:{:.2f} MinCl:{}", - ZBins, PhiBins, PerPrimaryVertexProcessing, DropTFUponFailure, ClusterSharing, TrackletMinPt, MinTrackLength); + std::string str = std::format("NZb:{} NPhB:{} PerVtx:{} DropFail:{} ClSh:{} TtklMinPt:{:.2f} MinCl:{} MaxHoles:{} HoleMask:{:#x}", + ZBins, PhiBins, PerPrimaryVertexProcessing, DropTFUponFailure, ClusterSharing, TrackletMinPt, MinTrackLength, MaxHoles, HoleLayerMask); bool first = true; for (int il = NLayers; il >= MinTrackLength; il--) { int slot = NLayers - il; @@ -204,6 +204,11 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode p.SaveTimeBenchmarks = tc.saveTimeBenchmarks; p.FataliseUponFailure = tc.fataliseUponFailure; p.AllowSharingFirstCluster = tc.allowSharingFirstCluster; + const auto iter = &p - trackParams.data(); + if (iter < constants::MaxIter) { + p.MaxHoles = tc.maxHolesIter[iter]; + p.HoleLayerMask = tc.holeLayerMaskIter[iter]; + } if (tc.useMatCorrTGeo) { p.CorrType = o2::base::PropagatorImpl::MatCorrType::USEMatCorrTGeo; diff --git a/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx b/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx index fc99bf0f35403..8375004cbfbad 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx @@ -241,8 +241,32 @@ void TimeFrame::prepareClusters(const TrackingParameters& trkParam, con } template -void TimeFrame::initialise(const TrackingParameters& trkParam, const int maxLayers) +void TimeFrame::initVertexingTopology(const TrackingParameters& trkParam) { + mVertexingTopology.init(3, trkParam.MaxHoles, trkParam.HoleLayerMask); +} + +template +void TimeFrame::initDefaultTrackingTopology(const TrackingParameters& trkParam, const int maxLayers) +{ + mDefaultTrackingTopology.init(maxLayers, trkParam.MaxHoles, trkParam.HoleLayerMask); +} + +template +void TimeFrame::initTrackerTopologies(gsl::span trkParams, const int maxLayers) +{ + mTrackerTopologies.resize(trkParams.size()); + for (size_t iteration = 0; iteration < trkParams.size(); ++iteration) { + const int iterationMaxLayers = std::min(maxLayers, trkParams[iteration].NLayers); + mTrackerTopologies[iteration].init(iterationMaxLayers, trkParams[iteration].MaxHoles, trkParams[iteration].HoleLayerMask); + } +} + +template +void TimeFrame::initialise(const TrackingParameters& trkParam, const int maxLayers, const int iteration) +{ + mTrackingTopologyView = iteration != constants::UnusedIndex ? mTrackerTopologies[iteration].getView() : (maxLayers == 3 ? mVertexingTopology.getView() : mDefaultTrackingTopology.getView()); + if (trkParam.PassFlags[IterationStep::FirstPass]) { deepVectorClear(mTracks); deepVectorClear(mTracksLabel); @@ -253,14 +277,6 @@ void TimeFrame::initialise(const TrackingParameters& trkParam, const in deepVectorClear(mPrimaryVerticesLabels); } clearResizeBoundedVector(mLinesLabels, getNrof(1), mMemoryPool.get()); - clearResizeBoundedVector(mCells, trkParam.CellsPerRoad(), mMemoryPool.get()); - clearResizeBoundedVector(mCellsLookupTable, trkParam.CellsPerRoad() - 1, mMemoryPool.get()); - clearResizeBoundedVector(mCellsNeighbours, trkParam.CellsPerRoad() - 1, mMemoryPool.get()); - clearResizeBoundedVector(mCellsNeighboursLUT, trkParam.CellsPerRoad() - 1, mMemoryPool.get()); - clearResizeBoundedVector(mCellLabels, trkParam.CellsPerRoad(), mMemoryPool.get()); - clearResizeBoundedVector(mTracklets, std::min(trkParam.TrackletsPerRoad(), maxLayers - 1), mMemoryPool.get()); - clearResizeBoundedVector(mTrackletLabels, trkParam.TrackletsPerRoad(), mMemoryPool.get()); - clearResizeBoundedVector(mTrackletsLookupTable, trkParam.TrackletsPerRoad(), mMemoryPool.get()); mIndexTableUtils.setTrackingParameters(trkParam); clearResizeBoundedVector(mPositionResolution, trkParam.NLayers, mMemoryPool.get()); clearResizeBoundedVector(mBogusClusters, trkParam.NLayers, mMemoryPool.get()); @@ -289,6 +305,17 @@ void TimeFrame::initialise(const TrackingParameters& trkParam, const in mMinR.fill(std::numeric_limits::max()); mMaxR.fill(std::numeric_limits::min()); } + clearResizeBoundedVector(mCells, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mCellsLookupTable, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mCellsNeighbours, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mCellsNeighboursTopology, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mCellsNeighboursLUT, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mCellLabels, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mTracklets, mTrackingTopologyView.nTransitions, mMemoryPool.get()); + clearResizeBoundedVector(mTrackletLabels, mTrackingTopologyView.nTransitions, mMemoryPool.get()); + clearResizeBoundedVector(mTrackletsLookupTable, mTrackingTopologyView.nTransitions, mMemoryPool.get()); + clearResizeBoundedVector(mTransitionPhiCuts, mTrackingTopologyView.nTransitions, mMemoryPool.get()); + clearResizeBoundedVector(mTransitionMSAngles, mTrackingTopologyView.nTransitions, mMemoryPool.get()); mNTrackletsPerROF.resize(2); for (auto& v : mNTrackletsPerROF) { v = bounded_vector(getNrof(1) + 1, 0, mMemoryPool.get()); @@ -304,42 +331,48 @@ void TimeFrame::initialise(const TrackingParameters& trkParam, const in } } - mMSangles.resize(trkParam.NLayers); - mPhiCuts.resize(mClusters.size() - 1, 0.f); - float oneOverR{0.001f * 0.3f * std::abs(mBz) / trkParam.TrackletMinPt}; + // estimate MS per layer + std::array msAngles{}; for (unsigned int iLayer{0}; iLayer < NLayers; ++iLayer) { - mMSangles[iLayer] = math_utils::MSangle(0.14f, trkParam.TrackletMinPt, trkParam.LayerxX0[iLayer]); + msAngles[iLayer] = math_utils::MSangle(0.14f, trkParam.TrackletMinPt, trkParam.LayerxX0[iLayer]); mPositionResolution[iLayer] = o2::gpu::CAMath::Sqrt((0.5f * (trkParam.SystErrorZ2[iLayer] + trkParam.SystErrorY2[iLayer])) + (trkParam.LayerResolution[iLayer] * trkParam.LayerResolution[iLayer])); - if (iLayer < mClusters.size() - 1) { - const float& r1 = trkParam.LayerRadii[iLayer]; - const float& r2 = trkParam.LayerRadii[iLayer + 1]; - oneOverR = (0.5 * oneOverR >= 1.f / r2) ? (2.f / r2) - o2::constants::math::Almost0 : oneOverR; - const float res1 = o2::gpu::CAMath::Hypot(trkParam.PVres, mPositionResolution[iLayer]); - const float res2 = o2::gpu::CAMath::Hypot(trkParam.PVres, mPositionResolution[iLayer + 1]); - const float cosTheta1half = o2::gpu::CAMath::Sqrt(1.f - math_utils::Sq(0.5f * r1 * oneOverR)); - const float cosTheta2half = o2::gpu::CAMath::Sqrt(1.f - math_utils::Sq(0.5f * r2 * oneOverR)); - float x = (r2 * cosTheta1half) - (r1 * cosTheta2half); - float delta = o2::gpu::CAMath::Sqrt(1.f / (1.f - 0.25f * math_utils::Sq(x * oneOverR)) * (math_utils::Sq((0.25f * r1 * r2 * math_utils::Sq(oneOverR) / cosTheta2half) + cosTheta1half) * math_utils::Sq(res1) + math_utils::Sq((0.25f * r1 * r2 * math_utils::Sq(oneOverR) / cosTheta1half) + cosTheta2half) * math_utils::Sq(res2))); - /// the expression std::asin(0.5f * x * oneOverR) is equivalent to std::aCos(0.5f * r1 * oneOverR) - std::acos(0.5 * r2 * oneOverR) - mPhiCuts[iLayer] = std::min(o2::gpu::CAMath::ASin(0.5f * x * oneOverR) + 2.f * mMSangles[iLayer] + delta, o2::constants::math::PI * 0.5f); - } } - for (int iLayer{0}; iLayer < std::min((int)mTracklets.size(), maxLayers); ++iLayer) { - deepVectorClear(mTracklets[iLayer]); - deepVectorClear(mTrackletLabels[iLayer]); - if (iLayer < (int)mCells.size()) { - deepVectorClear(mCells[iLayer]); - deepVectorClear(mTrackletsLookupTable[iLayer]); - mTrackletsLookupTable[iLayer].resize(mClusters[iLayer + 1].size() + 1, 0); - deepVectorClear(mCellLabels[iLayer]); + // for each transition calculate the phi-cuts + integrated MS + float oneOverR{0.001f * 0.3f * std::abs(mBz) / trkParam.TrackletMinPt}; + for (int transitionId{0}; transitionId < (int)mTracklets.size(); ++transitionId) { + const auto& transition = mTrackingTopologyView.getTransition(transitionId); + float ms2 = 0.; + for (int layer = transition.fromLayer; layer < transition.toLayer; ++layer) { + ms2 += math_utils::Sq(msAngles[layer]); } + mTransitionMSAngles[transitionId] = o2::gpu::CAMath::Sqrt(ms2); + const float& r1 = trkParam.LayerRadii[transition.fromLayer]; + const float& r2 = trkParam.LayerRadii[transition.toLayer]; + oneOverR = (0.5 * oneOverR >= 1.f / r2) ? (2.f / r2) - o2::constants::math::Almost0 : oneOverR; + const float res1 = o2::gpu::CAMath::Hypot(trkParam.PVres, mPositionResolution[transition.fromLayer]); + const float res2 = o2::gpu::CAMath::Hypot(trkParam.PVres, mPositionResolution[transition.toLayer]); + const float cosTheta1half = o2::gpu::CAMath::Sqrt(1.f - math_utils::Sq(0.5f * r1 * oneOverR)); + const float cosTheta2half = o2::gpu::CAMath::Sqrt(1.f - math_utils::Sq(0.5f * r2 * oneOverR)); + float x = (r2 * cosTheta1half) - (r1 * cosTheta2half); + float delta = o2::gpu::CAMath::Sqrt(1.f / (1.f - 0.25f * math_utils::Sq(x * oneOverR)) * (math_utils::Sq((0.25f * r1 * r2 * math_utils::Sq(oneOverR) / cosTheta2half) + cosTheta1half) * math_utils::Sq(res1) + math_utils::Sq((0.25f * r1 * r2 * math_utils::Sq(oneOverR) / cosTheta1half) + cosTheta2half) * math_utils::Sq(res2))); + /// the expression std::asin(0.5f * x * oneOverR) is equivalent to std::aCos(0.5f * r1 * oneOverR) - std::acos(0.5 * r2 * oneOverR) + mTransitionPhiCuts[transitionId] = o2::gpu::CAMath::Min(o2::gpu::CAMath::ASin(0.5f * x * oneOverR) + 2.f * mTransitionMSAngles[transitionId] + delta, o2::constants::math::PI * 0.5f); + + // some cleanup + deepVectorClear(mTracklets[transitionId]); + deepVectorClear(mTrackletLabels[transitionId]); + deepVectorClear(mTrackletsLookupTable[transitionId]); + mTrackletsLookupTable[transitionId].resize(mClusters[transition.fromLayer].size() + 1, 0); + } - if (iLayer < (int)mCells.size() - 1) { - deepVectorClear(mCellsLookupTable[iLayer]); - deepVectorClear(mCellsNeighbours[iLayer]); - deepVectorClear(mCellsNeighboursLUT[iLayer]); - } + for (int cellId{0}; cellId < (int)mCells.size(); ++cellId) { + deepVectorClear(mCells[cellId]); + deepVectorClear(mCellsLookupTable[cellId]); + deepVectorClear(mCellsNeighbours[cellId]); + deepVectorClear(mCellsNeighboursTopology[cellId]); + deepVectorClear(mCellsNeighboursLUT[cellId]); + deepVectorClear(mCellLabels[cellId]); } } @@ -356,6 +389,9 @@ unsigned long TimeFrame::getArtefactsMemory() const for (const auto& cellsN : mCellsNeighbours) { size += sizeof(int) * cellsN.size(); } + for (const auto& cellsN : mCellsNeighboursTopology) { + size += sizeof(int) * cellsN.size(); + } return size; } @@ -401,8 +437,8 @@ void TimeFrame::setMemoryPool(std::shared_ptr po initContainers(mNTrackletsPerClusterSum); initContainers(mNClustersPerROF); initVector(mPrimaryVertices); - initVector(mMSangles); - initVector(mPhiCuts); + initVector(mTransitionPhiCuts); + initVector(mTransitionMSAngles); initVector(mPositionResolution); initContainers(mClusterSize); initVector(mPValphaX); @@ -442,6 +478,7 @@ void TimeFrame::wipe() deepVectorClear(mTracklets); deepVectorClear(mCells); deepVectorClear(mCellsNeighbours); + deepVectorClear(mCellsNeighboursTopology); deepVectorClear(mCellsLookupTable); deepVectorClear(mPrimaryVertices); deepVectorClear(mTrackletsLookupTable); @@ -449,8 +486,8 @@ void TimeFrame::wipe() deepVectorClear(mNTrackletsPerCluster); deepVectorClear(mNTrackletsPerClusterSum); deepVectorClear(mNClustersPerROF); - deepVectorClear(mMSangles); - deepVectorClear(mPhiCuts); + deepVectorClear(mTransitionPhiCuts); + deepVectorClear(mTransitionMSAngles); deepVectorClear(mPositionResolution); deepVectorClear(mClusterSize); deepVectorClear(mPValphaX); diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx index 19cae4b70f158..9fef067559e8a 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx @@ -15,25 +15,23 @@ #include #include -#include #include #include #include -#include +#include -#include "CommonConstants/MathConstants.h" #include "DetectorsBase/Propagator.h" #include "GPUCommonMath.h" #include "ITStracking/BoundedAllocator.h" #include "ITStracking/Cell.h" #include "ITStracking/Constants.h" #include "ITStracking/IndexTableUtils.h" +#include "ITStracking/LayerMask.h" #include "ITStracking/ROFLookupTables.h" #include "ITStracking/TrackerTraits.h" #include "ITStracking/TrackHelpers.h" #include "ITStracking/Tracklet.h" -#include "ReconstructionDataFormats/Track.h" namespace o2::its { @@ -47,23 +45,23 @@ struct PassMode { template void TrackerTraits::computeLayerTracklets(const int iteration, int iVertex) { - for (int iLayer = 0; iLayer < mTrkParams[iteration].TrackletsPerRoad(); ++iLayer) { - mTimeFrame->getTracklets()[iLayer].clear(); - mTimeFrame->getTrackletsLabel(iLayer).clear(); - if (iLayer > 0) { - std::fill(mTimeFrame->getTrackletsLookupTable()[iLayer - 1].begin(), mTimeFrame->getTrackletsLookupTable()[iLayer - 1].end(), 0); - } + const auto topology = mTimeFrame->getTrackingTopologyView(); + for (int transitionId = 0; transitionId < topology.nTransitions; ++transitionId) { + mTimeFrame->getTracklets()[transitionId].clear(); + mTimeFrame->getTrackletsLabel(transitionId).clear(); + std::fill(mTimeFrame->getTrackletsLookupTable()[transitionId].begin(), mTimeFrame->getTrackletsLookupTable()[transitionId].end(), 0); } const Vertex diamondVert(mTrkParams[iteration].Diamond, mTrkParams[iteration].DiamondCov, 1, 1.f); gsl::span diamondSpan(&diamondVert, 1); mTaskArena->execute([&] { - auto forTracklets = [&](auto Tag, int iLayer, int pivotROF, int base, int& offset) -> int { - if (!mTimeFrame->getROFMaskView().isROFEnabled(iLayer, pivotROF)) { + auto forTracklets = [&](auto Tag, int transitionId, int pivotROF, int base, int& offset) -> int { + const auto& transition = topology.getTransition(transitionId); + if (!mTimeFrame->getROFMaskView().isROFEnabled(transition.fromLayer, pivotROF)) { return 0; } - gsl::span primaryVertices = mTrkParams[iteration].UseDiamond ? diamondSpan : mTimeFrame->getPrimaryVertices(iLayer, pivotROF); + gsl::span primaryVertices = mTrkParams[iteration].UseDiamond ? diamondSpan : mTimeFrame->getPrimaryVertices(transition.fromLayer, pivotROF); if (primaryVertices.empty()) { return 0; } @@ -73,46 +71,46 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer return 0; } - // does this layer have any overlap with the next layer - const auto& rofOverlap = mTimeFrame->getROFOverlapTableView().getOverlap(iLayer, iLayer + 1, pivotROF); + const auto& rofOverlap = mTimeFrame->getROFOverlapTableView().getOverlap(transition.fromLayer, transition.toLayer, pivotROF); if (!rofOverlap.getEntries()) { return 0; } int localCount = 0; - auto& tracklets = mTimeFrame->getTracklets()[iLayer]; - auto layer0 = mTimeFrame->getClustersOnLayer(pivotROF, iLayer); + auto& tracklets = mTimeFrame->getTracklets()[transitionId]; + auto layer0 = mTimeFrame->getClustersOnLayer(pivotROF, transition.fromLayer); if (layer0.empty()) { return 0; } - const float meanDeltaR = mTrkParams[iteration].LayerRadii[iLayer + 1] - mTrkParams[iteration].LayerRadii[iLayer]; + const float meanDeltaR = mTrkParams[iteration].LayerRadii[transition.toLayer] - mTrkParams[iteration].LayerRadii[transition.fromLayer]; + const float phiCut = mTimeFrame->getTransitionPhiCut(transitionId); + const float msAngle = mTimeFrame->getTransitionMSAngle(transitionId); for (int iCluster = 0; iCluster < int(layer0.size()); ++iCluster) { const Cluster& currentCluster = layer0[iCluster]; - const int currentSortedIndex = mTimeFrame->getSortedIndex(pivotROF, iLayer, iCluster); - if (mTimeFrame->isClusterUsed(iLayer, currentCluster.clusterId)) { + const int currentSortedIndex = mTimeFrame->getSortedIndex(pivotROF, transition.fromLayer, iCluster); + if (mTimeFrame->isClusterUsed(transition.fromLayer, currentCluster.clusterId)) { continue; } const float inverseR0 = 1.f / currentCluster.radius; for (int iV = startVtx; iV < endVtx; ++iV) { const auto& pv = primaryVertices[iV]; - if (!mTimeFrame->getROFVertexLookupTableView().isVertexCompatible(iLayer, pivotROF, pv)) { + if (!mTimeFrame->getROFVertexLookupTableView().isVertexCompatible(transition.fromLayer, pivotROF, pv)) { continue; } if (pv.isFlagSet(Vertex::Flags::UPCMode) != mTrkParams[iteration].PassFlags[IterationStep::SelectUPCVertices]) { continue; } - const float resolution = o2::gpu::CAMath::Sqrt(math_utils::Sq(mTimeFrame->getPositionResolution(iLayer)) + math_utils::Sq(mTrkParams[iteration].PVres) / float(pv.getNContributors())); + const float resolution = o2::gpu::CAMath::Sqrt(math_utils::Sq(mTimeFrame->getPositionResolution(transition.fromLayer)) + math_utils::Sq(mTrkParams[iteration].PVres) / float(pv.getNContributors())); const float tanLambda = (currentCluster.zCoordinate - pv.getZ()) * inverseR0; - const float zAtRmin = tanLambda * (mTimeFrame->getMinR(iLayer + 1) - currentCluster.radius) + currentCluster.zCoordinate; - const float zAtRmax = tanLambda * (mTimeFrame->getMaxR(iLayer + 1) - currentCluster.radius) + currentCluster.zCoordinate; + const float zAtRmin = tanLambda * (mTimeFrame->getMinR(transition.toLayer) - currentCluster.radius) + currentCluster.zCoordinate; + const float zAtRmax = tanLambda * (mTimeFrame->getMaxR(transition.toLayer) - currentCluster.radius) + currentCluster.zCoordinate; const float sqInvDeltaZ0 = 1.f / (math_utils::Sq(currentCluster.zCoordinate - pv.getZ()) + constants::Tolerance); - const float sigmaZ = o2::gpu::CAMath::Sqrt( - math_utils::Sq(resolution) * math_utils::Sq(tanLambda) * ((math_utils::Sq(inverseR0) + sqInvDeltaZ0) * math_utils::Sq(meanDeltaR) + 1.f) + math_utils::Sq(meanDeltaR * mTimeFrame->getMSangle(iLayer))); - const auto bins = o2::its::getBinsRect(currentCluster, iLayer + 1, zAtRmin, zAtRmax, - sigmaZ * mTrkParams[iteration].NSigmaCut, mTimeFrame->getPhiCut(iLayer), + const float sigmaZ = o2::gpu::CAMath::Sqrt((math_utils::Sq(resolution) * math_utils::Sq(tanLambda) * ((math_utils::Sq(inverseR0) + sqInvDeltaZ0) * math_utils::Sq(meanDeltaR) + 1.f)) + math_utils::Sq(meanDeltaR * msAngle)); + const auto bins = o2::its::getBinsRect(currentCluster, transition.toLayer, zAtRmin, zAtRmax, + sigmaZ * mTrkParams[iteration].NSigmaCut, phiCut, mTimeFrame->getIndexTableUtils()); if (bins.x < 0) { continue; @@ -123,18 +121,18 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer } for (int targetROF = rofOverlap.getFirstEntry(); targetROF < rofOverlap.getEntriesBound(); ++targetROF) { - if (!mTimeFrame->getROFMaskView().isROFEnabled(iLayer + 1, targetROF)) { + if (!mTimeFrame->getROFMaskView().isROFEnabled(transition.toLayer, targetROF)) { continue; } - auto layer1 = mTimeFrame->getClustersOnLayer(targetROF, iLayer + 1); + auto layer1 = mTimeFrame->getClustersOnLayer(targetROF, transition.toLayer); if (layer1.empty()) { continue; } - const auto ts = mTimeFrame->getROFOverlapTableView().getTimeStamp(iLayer, pivotROF, iLayer + 1, targetROF); + const auto ts = mTimeFrame->getROFOverlapTableView().getTimeStamp(transition.fromLayer, pivotROF, transition.toLayer, targetROF); if (!ts.isCompatible(pv.getTimeStamp())) { continue; } - const auto& targetIndexTable = mTimeFrame->getIndexTable(targetROF, iLayer + 1); + const auto& targetIndexTable = mTimeFrame->getIndexTable(targetROF, transition.toLayer); const int zBinRange = (bins.z - bins.x) + 1; for (int iPhi = 0; iPhi < phiBinsNum; ++iPhi) { const int iPhiBin = (bins.y + iPhi) % mTrkParams[iteration].PhiBins; @@ -147,22 +145,22 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer break; } const Cluster& nextCluster = layer1[iNext]; - if (mTimeFrame->isClusterUsed(iLayer + 1, nextCluster.clusterId)) { + if (mTimeFrame->isClusterUsed(transition.toLayer, nextCluster.clusterId)) { continue; } const float deltaZ = o2::gpu::CAMath::Abs((tanLambda * (nextCluster.radius - currentCluster.radius)) + currentCluster.zCoordinate - nextCluster.zCoordinate); if (deltaZ / sigmaZ < mTrkParams[iteration].NSigmaCut && - math_utils::isPhiDifferenceBelow(currentCluster.phi, nextCluster.phi, mTimeFrame->getPhiCut(iLayer))) { + math_utils::isPhiDifferenceBelow(currentCluster.phi, nextCluster.phi, phiCut)) { const float phi{o2::gpu::CAMath::ATan2(currentCluster.yCoordinate - nextCluster.yCoordinate, currentCluster.xCoordinate - nextCluster.xCoordinate)}; const float tanL = (currentCluster.zCoordinate - nextCluster.zCoordinate) / (currentCluster.radius - nextCluster.radius); if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { - tracklets.emplace_back(currentSortedIndex, mTimeFrame->getSortedIndex(targetROF, iLayer + 1, iNext), tanL, phi, ts); + tracklets.emplace_back(currentSortedIndex, mTimeFrame->getSortedIndex(targetROF, transition.toLayer, iNext), tanL, phi, ts); } else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) { ++localCount; } else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) { const int idx = base + offset++; - tracklets[idx] = Tracklet(currentSortedIndex, mTimeFrame->getSortedIndex(targetROF, iLayer + 1, iNext), tanL, phi, ts); + tracklets[idx] = Tracklet(currentSortedIndex, mTimeFrame->getSortedIndex(targetROF, transition.toLayer, iNext), tanL, phi, ts); } } } @@ -175,22 +173,24 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer int dummy{0}; if (mTaskArena->max_concurrency() <= 1) { - for (int iLayer{0}; iLayer < mTrkParams[iteration].TrackletsPerRoad(); ++iLayer) { - const int startROF = 0, endROF = mTimeFrame->getROFOverlapTableView().getLayer(iLayer).mNROFsTF; + for (int transitionId{0}; transitionId < topology.nTransitions; ++transitionId) { + const int fromLayer = topology.getTransition(transitionId).fromLayer; + const int startROF = 0, endROF = mTimeFrame->getROFOverlapTableView().getLayer(fromLayer).mNROFsTF; for (int pivotROF{startROF}; pivotROF < endROF; ++pivotROF) { - forTracklets(PassMode::OnePass{}, iLayer, pivotROF, 0, dummy); + forTracklets(PassMode::OnePass{}, transitionId, pivotROF, 0, dummy); } } } else { - tbb::parallel_for(0, mTrkParams[iteration].TrackletsPerRoad(), [&](const int iLayer) { - const int startROF = 0, endROF = mTimeFrame->getROFOverlapTableView().getLayer(iLayer).mNROFsTF; + tbb::parallel_for(0, static_cast(topology.nTransitions), [&](const int transitionId) { + const int fromLayer = topology.getTransition(transitionId).fromLayer; + const int startROF = 0, endROF = mTimeFrame->getROFOverlapTableView().getLayer(fromLayer).mNROFsTF; bounded_vector perROFCount((endROF - startROF) + 1, mMemoryPool.get()); tbb::parallel_for(startROF, endROF, [&](const int pivotROF) { - perROFCount[pivotROF - startROF] = forTracklets(PassMode::TwoPassCount{}, iLayer, pivotROF, 0, dummy); + perROFCount[pivotROF - startROF] = forTracklets(PassMode::TwoPassCount{}, transitionId, pivotROF, 0, dummy); }); std::exclusive_scan(perROFCount.begin(), perROFCount.end(), perROFCount.begin(), 0); const int nTracklets = perROFCount.back(); - mTimeFrame->getTracklets()[iLayer].resize(nTracklets); + mTimeFrame->getTracklets()[transitionId].resize(nTracklets); if (nTracklets == 0) { return; } @@ -200,38 +200,37 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer return; } int localIdx = 0; - forTracklets(PassMode::TwoPassInsert{}, iLayer, pivotROF, baseIdx, localIdx); + forTracklets(PassMode::TwoPassInsert{}, transitionId, pivotROF, baseIdx, localIdx); }); }); } - tbb::parallel_for(0, mTrkParams[iteration].TrackletsPerRoad(), [&](const int iLayer) { + tbb::parallel_for(0, static_cast(topology.nTransitions), [&](const int transitionId) { /// Sort tracklets & remove duplicates // duplicates can exist simply since we evaluate per vertex - auto& trkl{mTimeFrame->getTracklets()[iLayer]}; + auto& trkl{mTimeFrame->getTracklets()[transitionId]}; std::sort(trkl.begin(), trkl.end()); trkl.erase(std::unique(trkl.begin(), trkl.end()), trkl.end()); trkl.shrink_to_fit(); - if (iLayer > 0) { /// recalculate lut - auto& lut{mTimeFrame->getTrackletsLookupTable()[iLayer - 1]}; - if (!trkl.empty()) { - for (const auto& tkl : trkl) { - lut[tkl.firstClusterIndex + 1]++; - } - std::inclusive_scan(lut.begin(), lut.end(), lut.begin()); + auto& lut{mTimeFrame->getTrackletsLookupTable()[transitionId]}; + if (!trkl.empty()) { + for (const auto& tkl : trkl) { + lut[tkl.firstClusterIndex + 1]++; } + std::inclusive_scan(lut.begin(), lut.end(), lut.begin()); } }); /// Create tracklets labels if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].CreateArtefactLabels) { - tbb::parallel_for(0, mTrkParams[iteration].TrackletsPerRoad(), [&](const int iLayer) { - for (auto& trk : mTimeFrame->getTracklets()[iLayer]) { + tbb::parallel_for(0, static_cast(topology.nTransitions), [&](const int transitionId) { + const auto& transition = topology.getTransition(transitionId); + for (auto& trk : mTimeFrame->getTracklets()[transitionId]) { MCCompLabel label; - int currentId{mTimeFrame->getClusters()[iLayer][trk.firstClusterIndex].clusterId}; - int nextId{mTimeFrame->getClusters()[iLayer + 1][trk.secondClusterIndex].clusterId}; - for (const auto& lab1 : mTimeFrame->getClusterLabels(iLayer, currentId)) { - for (const auto& lab2 : mTimeFrame->getClusterLabels(iLayer + 1, nextId)) { + int currentId{mTimeFrame->getClusters()[transition.fromLayer][trk.firstClusterIndex].clusterId}; + int nextId{mTimeFrame->getClusters()[transition.toLayer][trk.secondClusterIndex].clusterId}; + for (const auto& lab1 : mTimeFrame->getClusterLabels(transition.fromLayer, currentId)) { + for (const auto& lab2 : mTimeFrame->getClusterLabels(transition.toLayer, nextId)) { if (lab1 == lab2 && lab1.isValid()) { label = lab1; break; @@ -241,7 +240,7 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer break; } } - mTimeFrame->getTrackletsLabel(iLayer).emplace_back(label); + mTimeFrame->getTrackletsLabel(transitionId).emplace_back(label); } }); } @@ -251,26 +250,28 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iVer template void TrackerTraits::computeLayerCells(const int iteration) { - for (int iLayer = 0; iLayer < mTrkParams[iteration].CellsPerRoad(); ++iLayer) { - deepVectorClear(mTimeFrame->getCells()[iLayer]); - if (iLayer > 0) { - deepVectorClear(mTimeFrame->getCellsLookupTable()[iLayer - 1]); - } + const auto topology = mTimeFrame->getTrackingTopologyView(); + for (int cellTopologyId = 0; cellTopologyId < topology.nCells; ++cellTopologyId) { + deepVectorClear(mTimeFrame->getCells()[cellTopologyId]); + deepVectorClear(mTimeFrame->getCellsLookupTable()[cellTopologyId]); if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].CreateArtefactLabels) { - deepVectorClear(mTimeFrame->getCellsLabel(iLayer)); + deepVectorClear(mTimeFrame->getCellsLabel(cellTopologyId)); } } mTaskArena->execute([&] { - auto forTrackletCells = [&](auto Tag, int iLayer, bounded_vector& layerCells, int iTracklet, int offset = 0) -> int { - const Tracklet& currentTracklet{mTimeFrame->getTracklets()[iLayer][iTracklet]}; + auto forTrackletCells = [&](auto Tag, int cellTopologyId, bounded_vector& layerCells, int iTracklet, int offset = 0) -> int { + const auto& cellTopology = topology.getCell(cellTopologyId); + const auto& firstTransition = topology.getTransition(cellTopology.firstTransition); + const auto& secondTransition = topology.getTransition(cellTopology.secondTransition); + const Tracklet& currentTracklet{mTimeFrame->getTracklets()[cellTopology.firstTransition][iTracklet]}; const int nextLayerClusterIndex{currentTracklet.secondClusterIndex}; - const int nextLayerFirstTrackletIndex{mTimeFrame->getTrackletsLookupTable()[iLayer][nextLayerClusterIndex]}; - const int nextLayerLastTrackletIndex{mTimeFrame->getTrackletsLookupTable()[iLayer][nextLayerClusterIndex + 1]}; + const int nextLayerFirstTrackletIndex{mTimeFrame->getTrackletsLookupTable()[cellTopology.secondTransition][nextLayerClusterIndex]}; + const int nextLayerLastTrackletIndex{mTimeFrame->getTrackletsLookupTable()[cellTopology.secondTransition][nextLayerClusterIndex + 1]}; int foundCells{0}; for (int iNextTracklet{nextLayerFirstTrackletIndex}; iNextTracklet < nextLayerLastTrackletIndex; ++iNextTracklet) { - const Tracklet& nextTracklet{mTimeFrame->getTracklets()[iLayer + 1][iNextTracklet]}; - if (mTimeFrame->getTracklets()[iLayer + 1][iNextTracklet].firstClusterIndex != nextLayerClusterIndex) { + const Tracklet& nextTracklet{mTimeFrame->getTracklets()[cellTopology.secondTransition][iNextTracklet]}; + if (nextTracklet.firstClusterIndex != nextLayerClusterIndex) { break; } if (!currentTracklet.getTimeStamp().isCompatible(nextTracklet.getTimeStamp())) { @@ -282,18 +283,20 @@ void TrackerTraits::computeLayerCells(const int iteration) /// Track seed preparation. Clusters are numbered progressively from the innermost going outward. const int clusId[3]{ - mTimeFrame->getClusters()[iLayer][currentTracklet.firstClusterIndex].clusterId, - mTimeFrame->getClusters()[iLayer + 1][nextTracklet.firstClusterIndex].clusterId, - mTimeFrame->getClusters()[iLayer + 2][nextTracklet.secondClusterIndex].clusterId}; - const auto& cluster1_glo = mTimeFrame->getUnsortedClusters()[iLayer][clusId[0]]; - const auto& cluster2_glo = mTimeFrame->getUnsortedClusters()[iLayer + 1][clusId[1]]; - const auto& cluster3_tf = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer + 2)[clusId[2]]; + mTimeFrame->getClusters()[firstTransition.fromLayer][currentTracklet.firstClusterIndex].clusterId, + mTimeFrame->getClusters()[firstTransition.toLayer][nextTracklet.firstClusterIndex].clusterId, + mTimeFrame->getClusters()[secondTransition.toLayer][nextTracklet.secondClusterIndex].clusterId}; + const int hitLayers[3]{firstTransition.fromLayer, firstTransition.toLayer, secondTransition.toLayer}; + const auto& cluster1_glo = mTimeFrame->getUnsortedClusters()[firstTransition.fromLayer][clusId[0]]; + const auto& cluster2_glo = mTimeFrame->getUnsortedClusters()[firstTransition.toLayer][clusId[1]]; + const auto& cluster3_tf = mTimeFrame->getTrackingFrameInfoOnLayer(secondTransition.toLayer)[clusId[2]]; auto track{o2::its::track::buildTrackSeed(cluster1_glo, cluster2_glo, cluster3_tf, mBz)}; float chi2{0.f}; bool good{false}; for (int iC{2}; iC--;) { - const TrackingFrameInfo& trackingHit = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer + iC)[clusId[iC]]; + const int hitLayer = hitLayers[iC]; + const TrackingFrameInfo& trackingHit = mTimeFrame->getTrackingFrameInfoOnLayer(hitLayer)[clusId[iC]]; if (!track.rotate(trackingHit.alphaTrackingFrame)) { break; @@ -303,7 +306,7 @@ void TrackerTraits::computeLayerCells(const int iteration) break; } - if (!track.correctForMaterial(mTrkParams[iteration].LayerxX0[iLayer + iC], mTrkParams[iteration].LayerxX0[iLayer + iC] * constants::Radl * constants::Rho, true)) { + if (!track.correctForMaterial(mTrkParams[iteration].LayerxX0[hitLayer], mTrkParams[iteration].LayerxX0[hitLayer] * constants::Radl * constants::Rho, true)) { break; } @@ -323,12 +326,13 @@ void TrackerTraits::computeLayerCells(const int iteration) TimeEstBC ts = currentTracklet.getTimeStamp(); ts += nextTracklet.getTimeStamp(); if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { - layerCells.emplace_back(iLayer, clusId[0], clusId[1], clusId[2], iTracklet, iNextTracklet, track, chi2, ts); + layerCells.emplace_back(cellTopology.hitLayerMask, clusId[0], clusId[1], clusId[2], iTracklet, iNextTracklet, track, chi2, ts); ++foundCells; } else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) { ++foundCells; } else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) { - layerCells[offset++] = CellSeed(iLayer, clusId[0], clusId[1], clusId[2], iTracklet, iNextTracklet, track, chi2, ts); + layerCells[offset++] = CellSeed(cellTopology.hitLayerMask, clusId[0], clusId[1], clusId[2], iTracklet, iNextTracklet, track, chi2, ts); + ++foundCells; } else { static_assert(false, "Unknown mode!"); } @@ -338,39 +342,32 @@ void TrackerTraits::computeLayerCells(const int iteration) return foundCells; }; - for (int iLayer = 0; iLayer < mTrkParams[iteration].CellsPerRoad(); ++iLayer) { - if (mTimeFrame->getTracklets()[iLayer + 1].empty() || - mTimeFrame->getTracklets()[iLayer].empty()) { - if (iLayer < mTrkParams[iteration].TrackletsPerRoad()) { - deepVectorClear(mTimeFrame->getTracklets()[iLayer]); - deepVectorClear(mTimeFrame->getTrackletsLabel(iLayer)); - } + for (int cellTopologyId = 0; cellTopologyId < topology.nCells; ++cellTopologyId) { + const auto& cellTopology = topology.getCell(cellTopologyId); + if (mTimeFrame->getTracklets()[cellTopology.firstTransition].empty() || + mTimeFrame->getTracklets()[cellTopology.secondTransition].empty()) { continue; } - auto& layerCells = mTimeFrame->getCells()[iLayer]; - const int currentLayerTrackletsNum{static_cast(mTimeFrame->getTracklets()[iLayer].size())}; + auto& layerCells = mTimeFrame->getCells()[cellTopologyId]; + const int currentLayerTrackletsNum{static_cast(mTimeFrame->getTracklets()[cellTopology.firstTransition].size())}; bounded_vector perTrackletCount(currentLayerTrackletsNum + 1, 0, mMemoryPool.get()); if (mTaskArena->max_concurrency() <= 1) { for (int iTracklet{0}; iTracklet < currentLayerTrackletsNum; ++iTracklet) { - perTrackletCount[iTracklet] = forTrackletCells(PassMode::OnePass{}, iLayer, layerCells, iTracklet); + perTrackletCount[iTracklet] = forTrackletCells(PassMode::OnePass{}, cellTopologyId, layerCells, iTracklet); } std::exclusive_scan(perTrackletCount.begin(), perTrackletCount.end(), perTrackletCount.begin(), 0); } else { tbb::parallel_for(0, currentLayerTrackletsNum, [&](const int iTracklet) { - perTrackletCount[iTracklet] = forTrackletCells(PassMode::TwoPassCount{}, iLayer, layerCells, iTracklet); + perTrackletCount[iTracklet] = forTrackletCells(PassMode::TwoPassCount{}, cellTopologyId, layerCells, iTracklet); }); std::exclusive_scan(perTrackletCount.begin(), perTrackletCount.end(), perTrackletCount.begin(), 0); auto totalCells{perTrackletCount.back()}; if (totalCells == 0) { - if (iLayer > 0) { - auto& lut = mTimeFrame->getCellsLookupTable()[iLayer - 1]; - lut.resize(currentLayerTrackletsNum + 1); - std::fill(lut.begin(), lut.end(), 0); - } - deepVectorClear(mTimeFrame->getTracklets()[iLayer]); - deepVectorClear(mTimeFrame->getTrackletsLabel(iLayer)); + auto& lut = mTimeFrame->getCellsLookupTable()[cellTopologyId]; + lut.resize(currentLayerTrackletsNum + 1); + std::fill(lut.begin(), lut.end(), 0); continue; } layerCells.resize(totalCells); @@ -380,181 +377,184 @@ void TrackerTraits::computeLayerCells(const int iteration) if (offset == perTrackletCount[iTracklet + 1]) { return; } - forTrackletCells(PassMode::TwoPassInsert{}, iLayer, layerCells, iTracklet, offset); + forTrackletCells(PassMode::TwoPassInsert{}, cellTopologyId, layerCells, iTracklet, offset); }); } - if (iLayer > 0) { - auto& lut = mTimeFrame->getCellsLookupTable()[iLayer - 1]; - lut.resize(currentLayerTrackletsNum + 1); - std::copy_n(perTrackletCount.begin(), currentLayerTrackletsNum + 1, lut.begin()); - } + auto& lut = mTimeFrame->getCellsLookupTable()[cellTopologyId]; + lut.resize(currentLayerTrackletsNum + 1); + std::copy_n(perTrackletCount.begin(), currentLayerTrackletsNum + 1, lut.begin()); if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].CreateArtefactLabels) { - auto& labels = mTimeFrame->getCellsLabel(iLayer); + auto& labels = mTimeFrame->getCellsLabel(cellTopologyId); labels.reserve(layerCells.size()); for (const auto& cell : layerCells) { - MCCompLabel currentLab{mTimeFrame->getTrackletsLabel(iLayer)[cell.getFirstTrackletIndex()]}; - MCCompLabel nextLab{mTimeFrame->getTrackletsLabel(iLayer + 1)[cell.getSecondTrackletIndex()]}; + MCCompLabel currentLab{mTimeFrame->getTrackletsLabel(cellTopology.firstTransition)[cell.getFirstTrackletIndex()]}; + MCCompLabel nextLab{mTimeFrame->getTrackletsLabel(cellTopology.secondTransition)[cell.getSecondTrackletIndex()]}; labels.emplace_back(currentLab == nextLab ? currentLab : MCCompLabel()); } } - - // Once layer i cells are built and labelled, the corresponding tracklet artefacts are no longer needed. - deepVectorClear(mTimeFrame->getTracklets()[iLayer]); - deepVectorClear(mTimeFrame->getTrackletsLabel(iLayer)); } }); - // Clear the trailing tracklet artefacts that are not consumed as the first leg of a cell. - for (int iLayer = mTrkParams[iteration].CellsPerRoad(); iLayer < mTrkParams[iteration].TrackletsPerRoad(); ++iLayer) { - deepVectorClear(mTimeFrame->getTracklets()[iLayer]); - deepVectorClear(mTimeFrame->getTrackletsLabel(iLayer)); + for (int transitionId = 0; transitionId < topology.nTransitions; ++transitionId) { + deepVectorClear(mTimeFrame->getTracklets()[transitionId]); + deepVectorClear(mTimeFrame->getTrackletsLabel(transitionId)); } } template void TrackerTraits::findCellsNeighbours(const int iteration) { - struct Neighbor { - int cell{-1}, nextCell{-1}, level{-1}; - }; - + const auto topology = mTimeFrame->getTrackingTopologyView(); mTaskArena->execute([&] { - for (int iLayer{0}; iLayer < mTrkParams[iteration].NeighboursPerRoad(); ++iLayer) { - deepVectorClear(mTimeFrame->getCellsNeighbours()[iLayer]); - deepVectorClear(mTimeFrame->getCellsNeighboursLUT()[iLayer]); - if (mTimeFrame->getCells()[iLayer + 1].empty() || - mTimeFrame->getCellsLookupTable()[iLayer].empty()) { - continue; - } + std::vector> cellsNeighboursByTarget; + cellsNeighboursByTarget.reserve(topology.nCells); + for (int cellTopologyId{0}; cellTopologyId < topology.nCells; ++cellTopologyId) { + deepVectorClear(mTimeFrame->getCellsNeighbours()[cellTopologyId]); + deepVectorClear(mTimeFrame->getCellsNeighboursTopology()[cellTopologyId]); + deepVectorClear(mTimeFrame->getCellsNeighboursLUT()[cellTopologyId]); + cellsNeighboursByTarget.emplace_back(mMemoryPool.get()); + } - int nCells{static_cast(mTimeFrame->getCells()[iLayer].size())}; - bounded_vector cellsNeighbours(mMemoryPool.get()); - - auto forCellNeighbour = [&](auto Tag, int iCell, int offset = 0) -> int { - const auto& currentCellSeed{mTimeFrame->getCells()[iLayer][iCell]}; - const int nextLayerTrackletIndex{currentCellSeed.getSecondTrackletIndex()}; - const int nextLayerFirstCellIndex{mTimeFrame->getCellsLookupTable()[iLayer][nextLayerTrackletIndex]}; - const int nextLayerLastCellIndex{mTimeFrame->getCellsLookupTable()[iLayer][nextLayerTrackletIndex + 1]}; - int foundNextCells{0}; - for (int iNextCell{nextLayerFirstCellIndex}; iNextCell < nextLayerLastCellIndex; ++iNextCell) { - auto nextCellSeed{mTimeFrame->getCells()[iLayer + 1][iNextCell]}; /// copy - if (nextCellSeed.getFirstTrackletIndex() != nextLayerTrackletIndex || !currentCellSeed.getTimeStamp().isCompatible(nextCellSeed.getTimeStamp())) { - break; - } + for (int outerLayer{0}; outerLayer < NLayers; ++outerLayer) { + for (int cellTopologyId{0}; cellTopologyId < topology.nCells; ++cellTopologyId) { + const auto& cellTopology = topology.getCell(cellTopologyId); + if (cellTopology.hitLayerMask.last() != outerLayer || + mTimeFrame->getCells()[cellTopologyId].empty()) { + continue; + } + const auto successors = topology.getCellsStartingWithTransition(cellTopology.secondTransition); + if (!successors.getEntries()) { + continue; + } - if (!nextCellSeed.rotate(currentCellSeed.getAlpha()) || - !nextCellSeed.propagateTo(currentCellSeed.getX(), getBz())) { - continue; - } + tbb::enumerable_thread_specific> sourceNeighbours([&]() { return bounded_vector{mMemoryPool.get()}; }); + tbb::parallel_for(0, static_cast(mTimeFrame->getCells()[cellTopologyId].size()), [&](const int iCell) { + auto& localNeighbours = sourceNeighbours.local(); + const auto& currentCellSeed{mTimeFrame->getCells()[cellTopologyId][iCell]}; + const int nextLayerTrackletIndex{currentCellSeed.getSecondTrackletIndex()}; + for (int iSuccessor{0}; iSuccessor < successors.getEntries(); ++iSuccessor) { + const int nextCellTopologyId = topology.cellsByFirstTransition[successors.getFirstEntry() + iSuccessor]; + if (mTimeFrame->getCells()[nextCellTopologyId].empty() || + mTimeFrame->getCellsLookupTable()[nextCellTopologyId].empty()) { + continue; + } + const auto& nextCellLUT = mTimeFrame->getCellsLookupTable()[nextCellTopologyId]; + if (nextLayerTrackletIndex + 1 >= static_cast(nextCellLUT.size())) { + continue; + } + const int nextLayerFirstCellIndex{nextCellLUT[nextLayerTrackletIndex]}; + const int nextLayerLastCellIndex{nextCellLUT[nextLayerTrackletIndex + 1]}; + for (int iNextCell{nextLayerFirstCellIndex}; iNextCell < nextLayerLastCellIndex; ++iNextCell) { + const auto& nextCellSeedRef{mTimeFrame->getCells()[nextCellTopologyId][iNextCell]}; + if (nextCellSeedRef.getFirstTrackletIndex() != nextLayerTrackletIndex || !currentCellSeed.getTimeStamp().isCompatible(nextCellSeedRef.getTimeStamp())) { + break; + } - float chi2 = currentCellSeed.getPredictedChi2(nextCellSeed); /// TODO: switch to the chi2 wrt cluster to avoid correlation - if (chi2 > mTrkParams[iteration].MaxChi2ClusterAttachment) { - continue; - } + auto nextCellSeed{mTimeFrame->getCells()[nextCellTopologyId][iNextCell]}; /// copy + if (!nextCellSeed.rotate(currentCellSeed.getAlpha()) || + !nextCellSeed.propagateTo(currentCellSeed.getX(), getBz())) { + continue; + } - if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { - cellsNeighbours.emplace_back(iCell, iNextCell, currentCellSeed.getLevel() + 1); - } else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) { - ++foundNextCells; - } else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) { - cellsNeighbours[offset++] = {iCell, iNextCell, currentCellSeed.getLevel() + 1}; - } else { - static_assert(false, "Unknown mode!"); - } - } - return foundNextCells; - }; + float chi2 = currentCellSeed.getPredictedChi2(nextCellSeed); + if (chi2 > mTrkParams[iteration].MaxChi2ClusterAttachment) { + continue; + } - if (mTaskArena->max_concurrency() <= 1) { - for (int iCell{0}; iCell < nCells; ++iCell) { - forCellNeighbour(PassMode::OnePass{}, iCell); - } - } else { - bounded_vector perCellCount(nCells + 1, 0, mMemoryPool.get()); - tbb::parallel_for(0, nCells, [&](const int iCell) { - perCellCount[iCell] = forCellNeighbour(PassMode::TwoPassCount{}, iCell); + const int nextLevel = currentCellSeed.getLevel() + 1; + localNeighbours.emplace_back(cellTopologyId, iCell, nextCellTopologyId, iNextCell, nextLevel); + } + } }); - std::exclusive_scan(perCellCount.begin(), perCellCount.end(), perCellCount.begin(), 0); - int totalCellNeighbours = perCellCount.back(); - if (totalCellNeighbours == 0) { - deepVectorClear(mTimeFrame->getCellsNeighbours()[iLayer]); - continue; + bounded_vector count(topology.nCells, 0, mMemoryPool.get()); + for (const auto& localNeighbours : sourceNeighbours) { + for (const auto& neigh : localNeighbours) { + ++count[neigh.nextCellTopology]; + } } - cellsNeighbours.resize(totalCellNeighbours); - - tbb::parallel_for(0, nCells, [&](const int iCell) { - int offset = perCellCount[iCell]; - if (offset == perCellCount[iCell + 1]) { - return; + for (size_t i{0}; i < topology.nCells; ++i) { + cellsNeighboursByTarget[i].reserve(count[i]); + } + for (const auto& localNeighbours : sourceNeighbours) { + for (const auto& neigh : localNeighbours) { + cellsNeighboursByTarget[neigh.nextCellTopology].emplace_back(neigh); + if (neigh.level > mTimeFrame->getCells()[neigh.nextCellTopology][neigh.nextCell].getLevel()) { + mTimeFrame->getCells()[neigh.nextCellTopology][neigh.nextCell].setLevel(neigh.level); + } } - forCellNeighbour(PassMode::TwoPassInsert{}, iCell, offset); - }); + } } + } + for (int cellTopologyId{0}; cellTopologyId < topology.nCells; ++cellTopologyId) { + auto& cellsNeighbours = cellsNeighboursByTarget[cellTopologyId]; if (cellsNeighbours.empty()) { continue; } - tbb::parallel_sort(cellsNeighbours.begin(), cellsNeighbours.end(), [](const auto& a, const auto& b) { + std::sort(cellsNeighbours.begin(), cellsNeighbours.end(), [](const auto& a, const auto& b) { return a.nextCell < b.nextCell; }); - auto& cellsNeighbourLUT = mTimeFrame->getCellsNeighboursLUT()[iLayer]; - cellsNeighbourLUT.assign(mTimeFrame->getCells()[iLayer + 1].size(), 0); + auto& cellsNeighbourLUT = mTimeFrame->getCellsNeighboursLUT()[cellTopologyId]; + cellsNeighbourLUT.assign(mTimeFrame->getCells()[cellTopologyId].size(), 0); for (const auto& neigh : cellsNeighbours) { ++cellsNeighbourLUT[neigh.nextCell]; } std::inclusive_scan(cellsNeighbourLUT.begin(), cellsNeighbourLUT.end(), cellsNeighbourLUT.begin()); - mTimeFrame->getCellsNeighbours()[iLayer].reserve(cellsNeighbours.size()); - std::ranges::transform(cellsNeighbours, std::back_inserter(mTimeFrame->getCellsNeighbours()[iLayer]), [](const auto& neigh) { return neigh.cell; }); - - for (auto it = cellsNeighbours.begin(); it != cellsNeighbours.end();) { - int cellIdx = it->nextCell; - int maxLvl = it->level; - while (++it != cellsNeighbours.end() && it->nextCell == cellIdx) { - maxLvl = std::max(maxLvl, it->level); - } - mTimeFrame->getCells()[iLayer + 1][cellIdx].setLevel(maxLvl); - } + mTimeFrame->getCellsNeighbours()[cellTopologyId].reserve(cellsNeighbours.size()); + mTimeFrame->getCellsNeighboursTopology()[cellTopologyId].reserve(cellsNeighbours.size()); + std::ranges::transform(cellsNeighbours, std::back_inserter(mTimeFrame->getCellsNeighbours()[cellTopologyId]), [](const auto& neigh) { return neigh.cell; }); + std::ranges::transform(cellsNeighbours, std::back_inserter(mTimeFrame->getCellsNeighboursTopology()[cellTopologyId]), [](const auto& neigh) { return neigh.cellTopology; }); + } - // clear cells LUT - deepVectorClear(mTimeFrame->getCellsLookupTable()[iLayer]); + // clean up LUTs + for (auto& cellLUT : mTimeFrame->getCellsLookupTable()) { + deepVectorClear(cellLUT); } }); } template template -void TrackerTraits::processNeighbours(int iteration, int iLayer, int iLevel, const bounded_vector& currentCellSeed, const bounded_vector& currentCellId, bounded_vector& updatedCellSeeds, bounded_vector& updatedCellsIds) +void TrackerTraits::processNeighbours(int iteration, int defaultCellTopologyId, int iLevel, const bounded_vector& currentCellSeed, const bounded_vector& currentCellId, const bounded_vector& currentCellTopologyId, bounded_vector& updatedCellSeeds, bounded_vector& updatedCellsIds, bounded_vector& updatedCellsTopologyIds) { auto propagator = o2::base::Propagator::Instance(); mTaskArena->execute([&] { auto forCellNeighbours = [&](auto Tag, int iCell, int offset = 0) -> int { const auto& currentCell{currentCellSeed[iCell]}; + const int cellTopologyId = currentCellTopologyId.empty() ? defaultCellTopologyId : currentCellTopologyId[iCell]; if constexpr (decltype(Tag)::value != PassMode::TwoPassInsert::value) { if (currentCell.getLevel() != iLevel) { return 0; } - if (currentCellId.empty() && (mTimeFrame->isClusterUsed(iLayer, currentCell.getFirstClusterIndex()) || - mTimeFrame->isClusterUsed(iLayer + 1, currentCell.getSecondClusterIndex()) || - mTimeFrame->isClusterUsed(iLayer + 2, currentCell.getThirdClusterIndex()))) { - return 0; /// this we do only on the first iteration, hence the check on currentCellId + if (currentCellId.empty()) { + for (int layer = 0; layer < NLayers; ++layer) { + const int clusterIndex = currentCell.getCluster(layer); + if (clusterIndex != constants::UnusedIndex && mTimeFrame->isClusterUsed(layer, clusterIndex)) { + return 0; /// this we do only on the first iteration, hence the check on currentCellId + } + } } } const int cellId = currentCellId.empty() ? iCell : currentCellId[iCell]; - const int startNeighbourId{cellId ? mTimeFrame->getCellsNeighboursLUT()[iLayer - 1][cellId - 1] : 0}; - const int endNeighbourId{mTimeFrame->getCellsNeighboursLUT()[iLayer - 1][cellId]}; + if (cellTopologyId < 0 || mTimeFrame->getCellsNeighboursLUT()[cellTopologyId].empty()) { + return 0; + } + const int startNeighbourId{cellId ? mTimeFrame->getCellsNeighboursLUT()[cellTopologyId][cellId - 1] : 0}; + const int endNeighbourId{mTimeFrame->getCellsNeighboursLUT()[cellTopologyId][cellId]}; int foundSeeds{0}; for (int iNeighbourCell{startNeighbourId}; iNeighbourCell < endNeighbourId; ++iNeighbourCell) { - const int neighbourCellId = mTimeFrame->getCellsNeighbours()[iLayer - 1][iNeighbourCell]; - const auto& neighbourCell = mTimeFrame->getCells()[iLayer - 1][neighbourCellId]; + const int neighbourCellTopologyId = mTimeFrame->getCellsNeighboursTopology()[cellTopologyId][iNeighbourCell]; + const int neighbourCellId = mTimeFrame->getCellsNeighbours()[cellTopologyId][iNeighbourCell]; + const auto& neighbourCell = mTimeFrame->getCells()[neighbourCellTopologyId][neighbourCellId]; if (neighbourCell.getSecondTrackletIndex() != currentCell.getFirstTrackletIndex()) { continue; } @@ -564,7 +564,9 @@ void TrackerTraits::processNeighbours(int iteration, int iLayer, int iL if (currentCell.getLevel() - 1 != neighbourCell.getLevel()) { continue; } - if (mTimeFrame->isClusterUsed(iLayer - 1, neighbourCell.getFirstClusterIndex())) { + const int neighbourLayer = neighbourCell.getInnerLayer(); + const int neighbourCluster = neighbourCell.getFirstClusterIndex(); + if (mTimeFrame->isClusterUsed(neighbourLayer, neighbourCluster)) { continue; } @@ -572,7 +574,7 @@ void TrackerTraits::processNeighbours(int iteration, int iLayer, int iL TrackSeedN seed{currentCell}; seed.getTimeStamp() = currentCell.getTimeStamp(); seed.getTimeStamp() += neighbourCell.getTimeStamp(); - const auto& trHit = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer - 1)[neighbourCell.getFirstClusterIndex()]; + const auto& trHit = mTimeFrame->getTrackingFrameInfoOnLayer(neighbourLayer)[neighbourCluster]; if (!seed.rotate(trHit.alphaTrackingFrame)) { continue; @@ -583,7 +585,7 @@ void TrackerTraits::processNeighbours(int iteration, int iLayer, int iL } if (mTrkParams[iteration].CorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { - if (!seed.correctForMaterial(mTrkParams[iteration].LayerxX0[iLayer - 1], mTrkParams[iteration].LayerxX0[iLayer - 1] * constants::Radl * constants::Rho, true)) { + if (!seed.correctForMaterial(mTrkParams[iteration].LayerxX0[neighbourLayer], mTrkParams[iteration].LayerxX0[neighbourLayer] * constants::Radl * constants::Rho, true)) { continue; } } @@ -598,7 +600,10 @@ void TrackerTraits::processNeighbours(int iteration, int iLayer, int iL } if constexpr (decltype(Tag)::value != PassMode::TwoPassCount::value) { - seed.getClusters()[iLayer - 1] = neighbourCell.getFirstClusterIndex(); + seed.getClusters()[neighbourLayer] = neighbourCluster; + auto mask = seed.getHitLayerMask(); + mask.set(neighbourLayer); + seed.setHitLayerMask(mask); seed.setLevel(neighbourCell.getLevel()); seed.setFirstTrackletIndex(neighbourCell.getFirstTrackletIndex()); seed.setSecondTrackletIndex(neighbourCell.getSecondTrackletIndex()); @@ -607,11 +612,13 @@ void TrackerTraits::processNeighbours(int iteration, int iLayer, int iL if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { updatedCellSeeds.push_back(seed); updatedCellsIds.push_back(neighbourCellId); + updatedCellsTopologyIds.push_back(neighbourCellTopologyId); } else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) { ++foundSeeds; } else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) { updatedCellSeeds[offset] = seed; - updatedCellsIds[offset++] = neighbourCellId; + updatedCellsIds[offset] = neighbourCellId; + updatedCellsTopologyIds[offset++] = neighbourCellTopologyId; } else { static_assert(false, "Unknown mode!"); } @@ -637,6 +644,7 @@ void TrackerTraits::processNeighbours(int iteration, int iLayer, int iL } updatedCellSeeds.resize(totalNeighbours); updatedCellsIds.resize(totalNeighbours); + updatedCellsTopologyIds.resize(totalNeighbours); tbb::parallel_for(0, nCells, [&](const int iCell) { int offset = perCellCount[iCell]; @@ -663,33 +671,42 @@ void TrackerTraits::findRoads(const int iteration) tfInfos[iLayer] = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer).data(); unsortedClusters[iLayer] = mTimeFrame->getUnsortedClusters()[iLayer].data(); } + const auto topology = mTimeFrame->getTrackingTopologyView(); for (int startLevel{mTrkParams[iteration].CellsPerRoad()}; startLevel >= mTrkParams[iteration].CellMinimumLevel(); --startLevel) { auto seedFilter = [&](const auto& seed) { - return seed.getQ2Pt() <= 1.e3 && seed.getChi2() <= mTrkParams[iteration].MaxChi2NDF * ((startLevel + 2) * 2 - 5); + return seed.getHitLayerMask().isAllowed(mTrkParams[iteration].MaxHoles, mTrkParams[iteration].HoleLayerMask) && + seed.getHitLayerMask().length() >= mTrkParams[iteration].MinTrackLength && + seed.getQ2Pt() <= 1.e3 && seed.getChi2() <= mTrkParams[iteration].MaxChi2NDF * ((startLevel + 2) * 2 - 5); }; bounded_vector trackSeeds(mMemoryPool.get()); - for (int startLayer{mTrkParams[iteration].NeighboursPerRoad()}; startLayer >= startLevel - 1; --startLayer) { - if ((mTrkParams[iteration].StartLayerMask & (1 << (startLayer + 2))) == 0) { + for (int startCellTopologyId{0}; startCellTopologyId < topology.nCells; ++startCellTopologyId) { + const int startLayer = topology.getCell(startCellTopologyId).hitLayerMask.last(); + if ((mTrkParams[iteration].StartLayerMask & (1 << startLayer)) == 0 || + mTimeFrame->getCells()[startCellTopologyId].empty()) { continue; } bounded_vector lastCellId(mMemoryPool.get()), updatedCellId(mMemoryPool.get()); + bounded_vector lastCellTopologyId(mMemoryPool.get()), updatedCellTopologyId(mMemoryPool.get()); bounded_vector lastCellSeed(mMemoryPool.get()), updatedCellSeed(mMemoryPool.get()); - processNeighbours(iteration, startLayer, startLevel, mTimeFrame->getCells()[startLayer], lastCellId, updatedCellSeed, updatedCellId); + processNeighbours(iteration, startCellTopologyId, startLevel, mTimeFrame->getCells()[startCellTopologyId], lastCellId, lastCellTopologyId, updatedCellSeed, updatedCellId, updatedCellTopologyId); int level = startLevel; - for (int iLayer{startLayer - 1}; iLayer > 0 && level > 2; --iLayer) { + while (level > 2 && !updatedCellSeed.empty()) { lastCellSeed.swap(updatedCellSeed); lastCellId.swap(updatedCellId); + lastCellTopologyId.swap(updatedCellTopologyId); deepVectorClear(updatedCellSeed); /// tame the memory peaks deepVectorClear(updatedCellId); /// tame the memory peaks - processNeighbours(iteration, iLayer, --level, lastCellSeed, lastCellId, updatedCellSeed, updatedCellId); + deepVectorClear(updatedCellTopologyId); + processNeighbours(iteration, constants::UnusedIndex, --level, lastCellSeed, lastCellId, lastCellTopologyId, updatedCellSeed, updatedCellId, updatedCellTopologyId); } - deepVectorClear(lastCellId); /// tame the memory peaks - deepVectorClear(lastCellSeed); /// tame the memory peaks + deepVectorClear(lastCellId); /// tame the memory peaks + deepVectorClear(lastCellTopologyId); /// tame the memory peaks + deepVectorClear(lastCellSeed); /// tame the memory peaks if (!updatedCellSeed.empty()) { trackSeeds.reserve(trackSeeds.size() + std::count_if(updatedCellSeed.begin(), updatedCellSeed.end(), seedFilter)); @@ -767,7 +784,7 @@ void TrackerTraits::findRoads(const int iteration) }); std::sort(tracks.begin(), tracks.end(), [](const auto& a, const auto& b) { - return a.getChi2() < b.getChi2(); + return track::isBetter(a, b); }); acceptTracks(iteration, tracks, firstClusters, sharedFirstClusters); @@ -778,6 +795,8 @@ void TrackerTraits::findRoads(const int iteration) template void TrackerTraits::acceptTracks(int iteration, bounded_vector& tracks, bounded_vector>& firstClusters, bounded_vector>& sharedFirstClusters) { + auto& trks = mTimeFrame->getTracks(); + trks.reserve(trks.size() + tracks.size()); const float smallestROFHalf = mTimeFrame->getROFOverlapTableView().getClockLayer().mROFLength * 0.5f; for (auto& track : tracks) { int nShared = 0; @@ -837,7 +856,7 @@ void TrackerTraits::acceptTracks(int iteration, bounded_vectorgetTracks().emplace_back(track); + trks.emplace_back(track); if (mTrkParams[iteration].AllowSharingFirstCluster) { firstClusters[firstLayer].push_back(firstCluster); @@ -897,13 +916,13 @@ void TrackerTraits::setNThreads(int n, std::shared_ptr } template class TrackerTraits<7>; -template void TrackerTraits<7>::processNeighbours(int, int, int, const bounded_vector&, const bounded_vector&, bounded_vector>&, bounded_vector&); -template void TrackerTraits<7>::processNeighbours>(int, int, int, const bounded_vector>&, const bounded_vector&, bounded_vector>&, bounded_vector&); +template void TrackerTraits<7>::processNeighbours(int, int, int, const bounded_vector&, const bounded_vector&, const bounded_vector&, bounded_vector>&, bounded_vector&, bounded_vector&); +template void TrackerTraits<7>::processNeighbours>(int, int, int, const bounded_vector>&, const bounded_vector&, const bounded_vector&, bounded_vector>&, bounded_vector&, bounded_vector&); // ALICE3 upgrade #ifdef ENABLE_UPGRADES template class TrackerTraits<11>; -template void TrackerTraits<11>::processNeighbours(int, int, int, const bounded_vector&, const bounded_vector&, bounded_vector>&, bounded_vector&); -template void TrackerTraits<11>::processNeighbours>(int, int, int, const bounded_vector>&, const bounded_vector&, bounded_vector>&, bounded_vector&); +template void TrackerTraits<11>::processNeighbours(int, int, int, const bounded_vector&, const bounded_vector&, const bounded_vector&, bounded_vector>&, bounded_vector&, bounded_vector&); +template void TrackerTraits<11>::processNeighbours>(int, int, int, const bounded_vector>&, const bounded_vector&, const bounded_vector&, bounded_vector>&, bounded_vector&, bounded_vector&); #endif } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx index f745d671419af..7f10419d63fea 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx @@ -54,6 +54,12 @@ void ITSTrackingInterface::initialise() LOGP(info, "Initializing tracker in {} phase reconstruction with {} passes for tracking and {}/{} for vertexing", TrackingMode::toString(mMode), trackParams.size(), o2::its::VertexerParamConfig::Instance().nIterations, vertParams.size()); mTracker->setParameters(trackParams); mVertexer->setParameters(vertParams); + TrackingParameters vertexTrackingParams; + mTimeFrame->initVertexingTopology(vertexTrackingParams); + if (!trackParams.empty()) { + mTimeFrame->initDefaultTrackingTopology(trackParams[0], NLayers); + mTimeFrame->initTrackerTopologies(gsl::span(trackParams.data(), trackParams.size())); + } if (mMode == TrackingMode::Cosmics) { mRunVertexer = false; diff --git a/Detectors/ITSMFT/ITS/tracking/test/CMakeLists.txt b/Detectors/ITSMFT/ITS/tracking/test/CMakeLists.txt index 063583b4cfa1b..f8fce10b78602 100644 --- a/Detectors/ITSMFT/ITS/tracking/test/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/tracking/test/CMakeLists.txt @@ -20,3 +20,9 @@ o2_add_test(roflookuptables COMPONENT_NAME its-tracking LABELS "its;tracking" PUBLIC_LINK_LIBRARIES O2::ITStracking) + +o2_add_test(trackingtopology + SOURCES testTrackingTopology.cxx + COMPONENT_NAME its-tracking + LABELS "its;tracking" + PUBLIC_LINK_LIBRARIES O2::ITStracking) diff --git a/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx b/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx index dd98a75efca7c..9626e42efd547 100644 --- a/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx +++ b/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx @@ -559,6 +559,7 @@ BOOST_AUTO_TEST_CASE(rofvertex_basic) vertices.push_back(vert1); table.update(vertices.data(), vertices.size()); const auto view = table.getView(); + view.printAll(); } BOOST_AUTO_TEST_CASE(rofvertex_init_with_vertices) diff --git a/Detectors/ITSMFT/ITS/tracking/test/testTrackingTopology.cxx b/Detectors/ITSMFT/ITS/tracking/test/testTrackingTopology.cxx new file mode 100644 index 0000000000000..4944d00b15fea --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/test/testTrackingTopology.cxx @@ -0,0 +1,119 @@ +// Copyright 2019-2026 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 +#define BOOST_TEST_MODULE ITS TrackingTopology +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include "ITStracking/TrackingTopology.h" + +/// -------- Tests -------- +BOOST_AUTO_TEST_CASE(layermask_holes_and_length) +{ + using o2::its::LayerMask; + + const LayerMask layer3Hole{0x77}; // layers 0,1,2,4,5,6 + BOOST_CHECK_EQUAL(layer3Hole.count(), 6); + BOOST_CHECK_EQUAL(layer3Hole.length(), 7); + BOOST_CHECK_EQUAL(layer3Hole.holeMask().value(), 0x08); + BOOST_CHECK(layer3Hole.isAllowed(1, 0x08)); + BOOST_CHECK(!layer3Hole.isAllowed(0, 0x08)); + + const LayerMask missingLeadingLayer0{0x7e}; // layers 1..6 + BOOST_CHECK_EQUAL(missingLeadingLayer0.count(), 6); + BOOST_CHECK_EQUAL(missingLeadingLayer0.length(), 6); + BOOST_CHECK_EQUAL(missingLeadingLayer0.holeMask().value(), 0x00); + + const LayerMask missingTrailingLayer6{0x3f}; // layers 0..5 + BOOST_CHECK_EQUAL(missingTrailingLayer6.count(), 6); + BOOST_CHECK_EQUAL(missingTrailingLayer6.length(), 6); + BOOST_CHECK_EQUAL(missingTrailingLayer6.holeMask().value(), 0x00); +} + +BOOST_AUTO_TEST_CASE(layermask_topological_length_counts_internal_holes) +{ + using o2::its::LayerMask; + + BOOST_CHECK_GE(LayerMask{0x7f}.length(), 7); // 7 clusters + BOOST_CHECK_GE(LayerMask{0x77}.length(), 7); // 6 clusters + layer-3 hole + BOOST_CHECK_LT(LayerMask{0x7e}.length(), 7); // missing leading layer + BOOST_CHECK_LT(LayerMask{0x3f}.length(), 7); // missing trailing layer +} + +BOOST_AUTO_TEST_CASE(trackingtopology_basic) +{ + o2::its::TrackingTopology<4> topo; + topo.init(4, 0, 0); + const auto view = topo.getView(); + view.print(); + + BOOST_CHECK_EQUAL(view.nTransitions, 3); + for (int i{0}; i < 3; ++i) { + const auto& tra = view.getTransition(i); + BOOST_CHECK_EQUAL(tra.fromLayer, i); + BOOST_CHECK_EQUAL(tra.toLayer, i + 1); + } + + BOOST_CHECK_EQUAL(view.nCells, 2); + for (int i{0}; i < 2; ++i) { + const auto& cell = view.getCell(i); + BOOST_CHECK_EQUAL(cell.firstTransition, i); + BOOST_CHECK_EQUAL(cell.secondTransition, i + 1); + } +} + +BOOST_AUTO_TEST_CASE(trackingtopology_single_allowed_hole) +{ + o2::its::TrackingTopology<5> topo; + topo.init(5, 1, 1 << 2); + const auto view = topo.getView(); + view.print(); + + BOOST_CHECK_EQUAL(view.nTransitions, 5); + BOOST_CHECK_EQUAL(view.nCells, 5); + + bool hasHoleTransition = false; + for (int i{0}; i < view.nTransitions; ++i) { + const auto& transition = view.getTransition(i); + hasHoleTransition |= transition.fromLayer == 1 && transition.toLayer == 3; + BOOST_CHECK(o2::its::LayerMask::skipped(transition.fromLayer, transition.toLayer).isAllowedHoleMask(1, 1 << 2)); + } + BOOST_CHECK(hasHoleTransition); + + bool hasHoleCell = false; + for (int i{0}; i < view.nCells; ++i) { + const auto& cell = view.getCell(i); + hasHoleCell |= cell.hitLayerMask.value() == 0x0b; // layers 0,1,3 + BOOST_CHECK(cell.hitLayerMask.isAllowed(1, 1 << 2)); + } + BOOST_CHECK(hasHoleCell); +} + +BOOST_AUTO_TEST_CASE(trackingtopology_rejects_wrong_hole_layer) +{ + o2::its::TrackingTopology<5> topo; + topo.init(5, 1, 1 << 2); + const auto view = topo.getView(); + view.print(); + + for (int i{0}; i < view.nTransitions; ++i) { + const auto& transition = view.getTransition(i); + BOOST_CHECK(!(transition.fromLayer == 0 && transition.toLayer == 2)); + BOOST_CHECK(!(transition.fromLayer == 2 && transition.toLayer == 4)); + } + + for (int i{0}; i < view.nCells; ++i) { + const auto& cell = view.getCell(i); + BOOST_CHECK(cell.hitLayerMask.holeMask().isSubsetOf(1 << 2)); + } +} From 3108ae1cdc66c36a6cdaa2d7788b33fac4fe3579 Mon Sep 17 00:00:00 2001 From: shahoian Date: Fri, 15 May 2026 10:54:31 +0200 Subject: [PATCH 077/102] Fix trigger input alias for the raw-tf-dump workflow --- prodtests/full-system-test/dpl-workflow.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prodtests/full-system-test/dpl-workflow.sh b/prodtests/full-system-test/dpl-workflow.sh index 3619cc38a0c09..445cf7a5b5a02 100755 --- a/prodtests/full-system-test/dpl-workflow.sh +++ b/prodtests/full-system-test/dpl-workflow.sh @@ -565,7 +565,7 @@ if [[ $CTFINPUT == 0 && $DIGITINPUT == 0 ]]; then [[ -z ${PROXY_INSPEC} ]] && define_raw_inputs CONFIG_RAWTFDUMP="--dataspec \"${PROXY_INSPEC}\" --output-dir \"${RAWTF_DIR:-$CTF_DIR}\" --meta-output-dir \"${EPN2EOS_METAFILES_DIR}\" --max-dump-rate ${RAWTF_DUMPRATE:-0.1} " CONFIG_RAWTFDUMP+=" --min-file-size ${RAWTF_MINSIZE:-$CTF_MINSIZE} --max-tf-per-file ${RAWTF_MAX_PER_FILE:-$CTF_MAX_PER_FILE} --mute-warn-period ${RAWTF_MUTE_PERIOD:-200} --max-warn ${RAWTF_MAX_WARN:-5} " - [[ -n ${DPL_RAWTFDUMP_TRIGGER:-} ]] && CONFIG_RAWTFDUMP+=" --triggerspec \"${DPL_RAWTFDUMP_TRIGGER}\" " + [[ -n ${DPL_RAWTFDUMP_TRIGGER:-} ]] && CONFIG_RAWTFDUMP+=" --triggerspec \"DMPTRG:${DPL_RAWTFDUMP_TRIGGER}\" " add_W o2-raw-tf-dump-workflow "$CONFIG_RAWTFDUMP" fi From 52b0ff0754be9d9725d3df7b8e4541d26f9d273b Mon Sep 17 00:00:00 2001 From: shahoian Date: Fri, 15 May 2026 12:16:39 +0200 Subject: [PATCH 078/102] Configure TPCFLPCMVSpec output accorging to the triggerPerFlp flag --- .../include/TPCWorkflow/TPCFLPCMVSpec.h | 22 ++++++++++--------- Detectors/TPC/workflow/src/tpc-flp-cmv.cxx | 6 +++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h index bb7f9632c6308..d86356234a1c2 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h @@ -37,14 +37,13 @@ namespace o2::tpc class TPCFLPCMVDevice : public o2::framework::Task { public: - TPCFLPCMVDevice(const int lane, const std::vector& crus, const int nTFsBuffer) - : mLane{lane}, mCRUs{crus}, mNTFsBuffer{nTFsBuffer} {} + TPCFLPCMVDevice(const int lane, const std::vector& crus, const bool triggerPerFlp, const int nTFsBuffer) + : mLane{lane}, mCRUs{crus}, mTriggerPerFLP{triggerPerFlp}, mNTFsBuffer{nTFsBuffer} {} void init(o2::framework::InitContext& ic) final { mDumpCMVs = ic.options().get("dump-cmvs-flp"); mEnableTrigger = ic.options().get("trigger"); - mTriggerPerFLP = ic.options().get("trigger-per-flp"); mTriggerThresholdCMV = ic.options().get("trigger-threshold-cmv"); mTriggerThresholdMeanMax = ic.options().get("trigger-threshold-cmvMeanMax"); mTriggerThresholdMeanMin = ic.options().get("trigger-threshold-cmvMeanMin"); @@ -200,7 +199,7 @@ class TPCFLPCMVDevice : public o2::framework::Task } }; -o2::framework::DataProcessorSpec getTPCFLPCMVSpec(const int ilane, const std::vector& crus, const int nTFsBuffer = 1) +o2::framework::DataProcessorSpec getTPCFLPCMVSpec(const int ilane, const std::vector& crus, const bool triggerPerFlp, const int nTFsBuffer = 1) { std::vector outputSpecs; std::vector inputSpecs; @@ -217,22 +216,25 @@ o2::framework::DataProcessorSpec getTPCFLPCMVSpec(const int ilane, const std::ve // Outputs to TPCDistributeCMVSpec outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup(), subSpec}, o2::framework::Lifetime::Sporadic); outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo(), subSpec}, o2::framework::Lifetime::Sporadic); - } - // Single per-FLP trigger output, subspec keyed on the first CRU - const header::DataHeader::SubSpecificationType trigSubSpec{crus.front() << 7}; - outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVTrigger(), trigSubSpec}, o2::framework::Lifetime::Timeframe); + if (!triggerPerFlp) { + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVTrigger(), subSpec}, o2::framework::Lifetime::Timeframe); + } + } + if (triggerPerFlp) { // Single per-FLP trigger output, subspec keyed on the first CRU + const header::DataHeader::SubSpecificationType trigSubSpec{crus.front() << 7}; + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVTrigger(), trigSubSpec}, o2::framework::Lifetime::Timeframe); + } const auto id = fmt::format("tpc-flp-cmv-{:02}", ilane); return o2::framework::DataProcessorSpec{ id.data(), inputSpecs, outputSpecs, - o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(ilane, crus, nTFsBuffer)}, + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(ilane, crus, triggerPerFlp, nTFsBuffer)}, o2::framework::Options{ {"dump-cmvs-flp", o2::framework::VariantType::Bool, false, {"Dump CMVs to file"}}, {"trigger", o2::framework::VariantType::Bool, false, {"Enable CMV trigger evaluation"}}, - {"trigger-per-flp", o2::framework::VariantType::Bool, false, {"Aggregate triggers of CRUs on FLP to a single trigger"}}, {"trigger-threshold-cmv", o2::framework::VariantType::Float, -10.f, {"CMV threshold: sequence starts when value drops below this (ADC units)"}}, {"trigger-threshold-cmvMeanMax", o2::framework::VariantType::Float, -40.f, {"Upper bound on trigger-sequence mean CMV value"}}, {"trigger-threshold-cmvMeanMin", o2::framework::VariantType::Float, -80.f, {"Lower bound on trigger-sequence mean CMV value"}}, diff --git a/Detectors/TPC/workflow/src/tpc-flp-cmv.cxx b/Detectors/TPC/workflow/src/tpc-flp-cmv.cxx index f41fe5b8fbd15..b7734c5d0b24f 100644 --- a/Detectors/TPC/workflow/src/tpc-flp-cmv.cxx +++ b/Detectors/TPC/workflow/src/tpc-flp-cmv.cxx @@ -32,6 +32,7 @@ void customize(std::vector& workflowOptions) {"time-lanes", VariantType::Int, 1, {"Number of parallel processing lanes (timeframes are split per device)"}}, {"crus", VariantType::String, cruDefault.c_str(), {"List of CRUs, comma separated ranges, e.g. 0-3,7,9-15"}}, {"n-TFs-buffer", VariantType::Int, 1, {"Buffer n-TFs before sending output"}}, + {"trigger-per-flp", VariantType::Bool, false, {"Aggregate triggers of CRUs on FLP to a single trigger"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; std::swap(workflowOptions, options); @@ -48,6 +49,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) const auto nLanes = std::min(static_cast(config.options().get("lanes")), nCRUs); const auto time_lanes = static_cast(config.options().get("time-lanes")); const auto crusPerLane = nCRUs / nLanes + ((nCRUs % nLanes) != 0); + const bool triggerPerFLP = config.options().get("trigger-per-flp"); const int nTFsBuffer = config.options().get("n-TFs-buffer"); o2::conf::ConfigurableParam::updateFromFile(config.options().get("configFile")); @@ -65,8 +67,8 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) } const auto last = std::min(tpcCRUs.end(), first + crusPerLane); const std::vector rangeCRUs(first, last); - workflow.emplace_back(timePipeline(getTPCFLPCMVSpec(ilane, rangeCRUs, nTFsBuffer), time_lanes)); + workflow.emplace_back(timePipeline(getTPCFLPCMVSpec(ilane, rangeCRUs, triggerPerFLP, nTFsBuffer), time_lanes)); } return workflow; -} \ No newline at end of file +} From 3e65dc02af3ed1ed1a0be40b20854766817cd6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=20Ku=C4=8Dera?= <26327373+vkucera@users.noreply.github.com> Date: Wed, 18 Feb 2026 19:11:28 +0100 Subject: [PATCH 079/102] BinningPolicy: Fix bug in getAllBinsCount --- Framework/Core/include/Framework/BinningPolicy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/Core/include/Framework/BinningPolicy.h b/Framework/Core/include/Framework/BinningPolicy.h index 3e41302c920f8..f4ea6885192e4 100644 --- a/Framework/Core/include/Framework/BinningPolicy.h +++ b/Framework/Core/include/Framework/BinningPolicy.h @@ -188,7 +188,7 @@ struct BinningPolicyBase { if constexpr (N == 2) { return getXBinsCount() * getYBinsCount(); } - if constexpr (N == 2) { + if constexpr (N == 3) { return getXBinsCount() * getYBinsCount() * getZBinsCount(); } return -1; From a0409929ccd01c66c23d08d1dd268025c10f133c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=20Ku=C4=8Dera?= <26327373+vkucera@users.noreply.github.com> Date: Fri, 24 Apr 2026 14:19:01 +0200 Subject: [PATCH 080/102] Update Clang-Tidy config file --- .clang-tidy | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index da768906bcc32..080c35ba31df3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,3 +1,42 @@ +--- +Checks: + - -* + - modernize-avoid-bind + - modernize-deprecated-headers + - modernize-make-shared + - modernize-raw-string-literal + - modernize-redundant-void-arg + - modernize-replace-auto-ptr + - modernize-replace-random-shuffle + - modernize-shrink-to-fit + - modernize-unary-static-assert + - modernize-use-equals-default + - modernize-use-noexcept + - modernize-use-nullptr + - modernize-use-override + - modernize-use-transparent-functors + - modernize-use-uncaught-exceptions + - readability-braces-around-statements + - -clang-diagnostic-vla-cxx-extension CheckOptions: - - key: CheckPathRegex - value: '.*/O2/.*' + # Naming conventions + readability-identifier-naming.ClassCase: CamelCase + readability-identifier-naming.ClassMemberPrefix: m + readability-identifier-naming.ConceptCase: CamelCase + readability-identifier-naming.ConstexprVariableCase: CamelCase + readability-identifier-naming.EnumCase: CamelCase + readability-identifier-naming.EnumConstantCase: CamelCase + readability-identifier-naming.EnumConstantIgnoredRegexp: "^k?[A-Z][a-zA-Z0-9_]*$" # Allow "k" prefix and non-trailing underscores in PDG names. + readability-identifier-naming.FunctionCase: camelBack + readability-identifier-naming.MacroDefinitionCase: UPPER_CASE + readability-identifier-naming.MacroDefinitionIgnoredRegexp: "^[A-Z][A-Z0-9_]*_$" # Allow the trailing underscore in header guards. + readability-identifier-naming.MemberCase: camelBack + readability-identifier-naming.NamespaceCase: lower_case + readability-identifier-naming.ParameterCase: camelBack + readability-identifier-naming.StructCase: CamelCase + readability-identifier-naming.TemplateParameterCase: CamelCase + readability-identifier-naming.TypeAliasCase: CamelCase + readability-identifier-naming.TypedefCase: CamelCase + readability-identifier-naming.TypeTemplateParameterCase: CamelCase + readability-identifier-naming.VariableCase: camelBack +... From ebf41956da11035737a898b6e38da224b9547b30 Mon Sep 17 00:00:00 2001 From: shahoian Date: Fri, 15 May 2026 17:26:20 +0200 Subject: [PATCH 081/102] Disable ensureContinuousROF when calibration data is requested By default avoid ROFs modifications in ensureContinuousROF when the --enable-calib-data is active: calibration runs do not respect declared number of ROFs and inject bogus orbits. There is still a possibility to enforce ensureContinuousROF with eventual rectifyDigits/Clusters in case the calibration data is requested via --enforce-continuous-rof-with-calib (beware that this may lead to a desynchronization between the calibration data container and ROFs/digits). Also, for backward compatibility, option --disable-rectify-continuous-rof will disable the calling of rectifyDigits/Clusters after eventual ensureContinuousROF. --- .../include/ITSMFTWorkflow/STFDecoderSpec.h | 2 + .../common/workflow/src/STFDecoderSpec.cxx | 42 ++++++++++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h index 1739822d121cf..8c16759e16726 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h @@ -92,6 +92,8 @@ class STFDecoder : public Task bool mUseClusterDictionary = true; bool mVerifyDecoder = false; bool mDumpFrom1stPipeline = false; + bool mRunEnsureContinuousROF = true; + bool mDisableRectifyContinuousROF = false; int mDumpOnError = 0; int mNThreads = 1; int mVerbosity = 0; diff --git a/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx index cc7200a1120a6..79372160c6ade 100644 --- a/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx @@ -63,6 +63,7 @@ STFDecoder::STFDecoder(const STFDecoderInp& inp, std::shared_ptr void STFDecoder::init(InitContext& ic) { + int lane = ic.services().get().inputTimesliceId; o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); try { auto v0 = o2::utils::Str::tokenize(mInputSpec, ':'); @@ -137,6 +138,31 @@ void STFDecoder::init(InitContext& ic) LOG(error) << "non-std::exception was thrown in decoder configuration"; throw; } + if (mDoCalibData) { + std::string warnMsg; + bool enforceEnsureContinuousROFinCalib = ic.options().get("enforce-continuous-rof-with-calib"); + if (ic.options().get("enforce-continuous-rof-with-calib")) { + warnMsg = "Calibration data requested but the ensureContinuousROF is explicitly enforced!"; + } else { + mRunEnsureContinuousROF = false; + warnMsg = "Calibration data requested, disabling ensureContinuousROF!"; + } + if (lane == 0) { + LOGP(alarm, "{}", warnMsg); + } else { + LOGP(info, "{}", warnMsg); + } + } + + mDisableRectifyContinuousROF = ic.options().get("disable-rectify-continuous-rof"); + if (mDisableRectifyContinuousROF && mRunEnsureContinuousROF) { + std::string warnMsg = "Rectification of clusters/digits is explicitly disabled after the ensureContinuousROF!"; + if (lane == 0) { + LOGP(alarm, "{}", warnMsg); + } else { + LOGP(info, "{}", warnMsg); + } + } if (mDoClusters) { mClusterer = std::make_unique(); @@ -258,8 +284,8 @@ void STFDecoder::run(ProcessingContext& pc) } } if (mDoDigits) { - std::vector expDigRofVec(nROFsTF); - if (ensureContinuousROF(digROFVec, expDigRofVec, iLayer, nROFsTF, "digits")) { + std::vector expDigRofVec; + if (ensureContinuousROF(digROFVec, expDigRofVec, iLayer, nROFsTF, "digits") && !mDisableRectifyContinuousROF) { auto oldNDig = digVec.size(); rectifyDigits(expDigRofVec, digVec); LOGP(warn, "Rectified {} digits out of original {} on layer {} following ensureContinuousROF", digVec.size(), oldNDig, iLayer); @@ -271,12 +297,12 @@ void STFDecoder::run(ProcessingContext& pc) pc.outputs().snapshot(Output{orig, "GBTCALIB", iLayer}, calVec); mEstNCalib[iLayer] = std::max(mEstNCalib[iLayer], size_t(calVec.size() * 1.2)); } - LOG(debug) << mSelfName << " Decoded " << digVec.size() << " Digits in " << digROFVec.size() << " ROFs" << ((mDoStaggering) ? std::format(" on layer {}", iLayer) : ""); + LOG(debug) << mSelfName << " Decoded " << digVec.size() << " Digits in " << expDigRofVec.size() << " ROFs" << ((mDoStaggering) ? std::format(" on layer {}", iLayer) : ""); } if (mDoClusters) { // we are not obliged to create vectors which are not requested, but other devices might not know the options of this one - std::vector expClusRofVec(nROFsTF); - if (ensureContinuousROF(clusROFVec, expClusRofVec, iLayer, nROFsTF, "clusters")) { + std::vector expClusRofVec; + if (ensureContinuousROF(clusROFVec, expClusRofVec, iLayer, nROFsTF, "clusters") && !mDisableRectifyContinuousROF) { auto oldNClus = clusCompVec.size(), oldNPatt = clusPattVec.size(); rectifyClusters(expClusRofVec, clusCompVec, clusPattVec); LOGP(warn, "Rectified {} clusters and {} patterns out of original {} and {} on layer {} following ensureContinuousROF", clusCompVec.size(), clusPattVec.size(), oldNClus, oldNPatt, iLayer); @@ -426,6 +452,10 @@ void STFDecoder::reset() template bool STFDecoder::ensureContinuousROF(const std::vector& rofVec, std::vector& expROFVec, int lr, int nROFsTF, const char* name) { + if (!mRunEnsureContinuousROF) { + expROFVec = rofVec; + return false; + } const auto& par = AlpideParam::Instance(); // ensure that the rof output is continuous // we will preserve the digits/clusters as they are but the stray ROFs will be removed (leaving their clusters/digits unaddressed). @@ -621,6 +651,8 @@ DataProcessorSpec getSTFDecoderSpec(const STFDecoderInp& inp) {"unmute-extra-lanes", VariantType::Bool, false, {"allow extra lanes to be as verbose as 1st one"}}, {"allow-empty-rofs", VariantType::Bool, false, {"record ROFs w/o any hit"}}, {"ignore-noise-map", VariantType::Bool, false, {"do not mask pixels flagged in the noise map"}}, + {"enforce-continuous-rof-with-calib", VariantType::Bool, false, {"enforce ensureContinuousROF call even when calibration data is requested (not recommended)"}}, + {"disable-rectify-continuous-rof", VariantType::Bool, false, {"do not rectify clusters and digits after ensureContinuousROF (not recommended)"}}, {"accept-rof-rampup-data", VariantType::Bool, false, {"do not discard data during ROF ramp up"}}, {"rof-length-error-freq", VariantType::Float, 60.f, {"do not report ROF length error more frequently than this value, disable if negative"}}, {"ignore-cluster-dictionary", VariantType::Bool, false, {"do not use cluster dictionary, always store explicit patterns"}}}}; From 2487a80694a80d39ed1beb4c149911cf99fa474f Mon Sep 17 00:00:00 2001 From: shahor02 Date: Sun, 17 May 2026 21:19:36 +0200 Subject: [PATCH 082/102] Repair raw tf part counters / headers (#15405) * Repair raw tf part counters / headers Can be disabled by --ignore-repair-headers. * By default do not store FLP/DISTSUBTIMEFRAME in the rawTF * Fix payloadIndex, ignore writing DistSTF by default, extra verbose output * Ignore stored DistSTF by default --- .../include/TFReaderDD/SubTimeFrameFile.h | 18 ++++- .../TFReaderDD/SubTimeFrameFileReader.h | 11 ++- .../Raw/TFReaderDD/src/RawTFDumpSpec.cxx | 21 +++++- .../TFReaderDD/src/SubTimeFrameFileReader.cxx | 73 ++++++++++++++++--- Detectors/Raw/TFReaderDD/src/TFReaderSpec.cxx | 13 +++- Detectors/Raw/TFReaderDD/src/TFReaderSpec.h | 2 + .../Raw/TFReaderDD/src/tf-reader-workflow.cxx | 2 +- 7 files changed, 117 insertions(+), 23 deletions(-) diff --git a/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFile.h b/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFile.h index 340027642b74c..eeabf8e8d4117 100644 --- a/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFile.h +++ b/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFile.h @@ -21,6 +21,8 @@ #include #include +#include "Framework/DataSpecUtils.h" +#include "Framework/OutputSpec.h" #include "Framework/Logger.h" namespace o2 @@ -151,13 +153,13 @@ struct SubTimeFrameFileMeta { /// std::uint64_t mWriteTimeMs; - auto getTimePoint() + auto getTimePoint() const { using namespace std::chrono; return time_point{milliseconds{mWriteTimeMs}}; } - std::string getTimeString() + std::string getTimeString() const { using namespace std::chrono; std::time_t lTime = system_clock::to_time_t(getTimePoint()); @@ -167,6 +169,11 @@ struct SubTimeFrameFileMeta { return lTimeStream.str(); } + const std::string info() const + { + return fmt::format("Size in file: {} Time: {} Version: {}", mStfSizeInFile, getTimeString(), mStfFileVersion); + } + SubTimeFrameFileMeta(const std::uint64_t pStfSize) : SubTimeFrameFileMeta() { @@ -220,6 +227,11 @@ struct SubTimeFrameFileDataIndex { static_assert(sizeof(DataIndexElem) == 48, "DataIndexElem changed -> Binary compatibility is lost!"); } + + const std::string info() const + { + return fmt::format("DH: {} Cnt:{} Size:{} Offset:{}", o2::framework::DataSpecUtils::describe(o2::framework::OutputSpec{mDataOrigin, mDataDescription, mSubSpecification}), mDataBlockCnt, mSize, mOffset); + } }; SubTimeFrameFileDataIndex() = default; @@ -240,6 +252,8 @@ struct SubTimeFrameFileDataIndex { return sizeof(o2::header::DataHeader) + (sizeof(DataIndexElem) * mDataIndex.size()); } + const std::vector& getDataIndex() const { return mDataIndex; } + friend std::ostream& operator<<(std::ostream& pStream, const SubTimeFrameFileDataIndex& pIndex); private: diff --git a/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFileReader.h b/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFileReader.h index 3b926e0a79206..2b7d2b7ab8e74 100644 --- a/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFileReader.h +++ b/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFileReader.h @@ -46,11 +46,11 @@ class SubTimeFrameFileReader public: SubTimeFrameFileReader() = delete; - SubTimeFrameFileReader(const std::string& pFileName, o2::detectors::DetID::mask_t detMask); + SubTimeFrameFileReader(const std::string& pFileName, o2::detectors::DetID::mask_t detMask, int verb, bool sup0xccdb, bool repaireHeaders, bool rejectDistSTF); ~SubTimeFrameFileReader(); /// Read a single TF from the file - std::unique_ptr read(fair::mq::Device* device, const std::vector& outputRoutes, const std::string& rawChannel, size_t slice, bool sup0xccdb, int verbosity); + std::unique_ptr read(fair::mq::Device* device, const std::vector& outputRoutes, const std::string& rawChannel, size_t slice); /// Tell the current position of the file inline std::uint64_t position() const { return mFileMapOffset; } @@ -76,6 +76,13 @@ class SubTimeFrameFileReader std::uint64_t mFileMapOffset = 0; std::uint64_t mFileSize = 0; + int mVerbosity = 0; + bool mSup0xccdb = true; + bool mRepaireHeaders = true; + bool mRejectDistSTF = true; + + const std::string describeHeader(const o2::header::DataHeader& hd, bool full = false) const; + // helper to make sure written chunks are buffered, only allow pointers template ::value>> diff --git a/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx index 64c39fa7ef75a..03bd26ae0deb9 100644 --- a/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx +++ b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx @@ -97,6 +97,7 @@ class RawTFDump : public Task bool mCreateRunEnvDir = true; bool mAcceptCurrentTF = false; bool mRejectDEADBEEF = false; + bool mRejectDistSTF = true; int mVerbose = 0; std::vector mTFOrbits{}; // 1st orbits of TF accumulated in current file o2::framework::DataTakingContext mDataTakingContext{}; @@ -185,6 +186,7 @@ void RawTFDump::init(InitContext& ic) mWriteTF = false; mStoreMetaFile = false; } + mRejectDistSTF = !ic.options().get("include-dist-stf"); mRejectDEADBEEF = !ic.options().get("include-deadbeef"); mCreateRunEnvDir = !ic.options().get("ignore-partition-run-dir"); mMinFileSize = ic.options().get("min-file-size"); @@ -253,6 +255,7 @@ void RawTFDump::run(ProcessingContext& pc) try { size_t lTFSizeInFile = getTFSizeInFile(); SubTimeFrameFileMeta lTFFileMeta(lTFSizeInFile); + lTFFileMeta.mWriteTimeMs = mTimingInfo.creation; mFile << lTFFileMeta; // Write DataHeader + SubTimeFrameFileMeta mFile << mTFDataIndex; // Write DataHeader + SubTimeFrameFileDataIndex @@ -263,6 +266,10 @@ void RawTFDump::run(ProcessingContext& pc) const auto& dataPtr = mTFData[lEntry + part]; DataHeader hdToWrite = *reinterpret_cast(dataPtr.first); // make a local DataHeader copy to clear flagsNextHeader bit hdToWrite.flagsNextHeader = 0; + hdToWrite.splitPayloadIndex = part; + if (mVerbose > 2) { + LOGP(info, "Writing part:{}/{} of {} | TFCounter:{} part{}/{}", part, lCnt, DataSpecUtils::describe(OutputSpec{hdToWrite.dataOrigin, hdToWrite.dataDescription, hdToWrite.subSpecification}), hdToWrite.firstTForbit, hdToWrite.splitPayloadIndex, hdToWrite.splitPayloadParts); + } buffered_write(reinterpret_cast(&hdToWrite), sizeof(DataHeader)); buffered_write(dataPtr.second, hdToWrite.payloadSize); } @@ -517,7 +524,11 @@ void RawTFDump::prepareTFForWriting(ProcessingContext& pc) LOGP(error, "Failed to extract header"); continue; } - if (dh->subSpecification == 0xdeadbeef && mRejectDEADBEEF) { + if ((dh->subSpecification == 0xdeadbeef && mRejectDEADBEEF) || + (dh->dataOrigin == o2::header::gDataOriginFLP && dh->dataDescription == o2::header::gDataDescriptionDISTSTF && mRejectDistSTF)) { + if (mVerbose > 2) { + LOGP(info, "Rejecting {}", DataSpecUtils::describe(OutputSpec{dh->dataOrigin, dh->dataDescription, dh->subSpecification})); + } continue; } const auto lHdrDataSize = sizeof(DataHeader) + dh->payloadSize; @@ -531,9 +542,10 @@ void RawTFDump::prepareTFForWriting(ProcessingContext& pc) lCnt++; mTFData.push_back({ref.header, ref.payload}); if (mVerbose > 2) { - LOGP(info, "{}, part: {} of {}, payload {}, 1stTFOrbit: {} TF: {}", + const auto* dph = DataRefUtils::getHeader(ref); + LOGP(info, "{}, part: {} of {}, payload {}, 1stTFOrbit: {} TF: {}, creation: {} | counter:{} size:{} entry:{}", DataSpecUtils::describe(OutputSpec{dh->dataOrigin, dh->dataDescription, dh->subSpecification}), - dh->splitPayloadIndex, dh->splitPayloadParts, dh->payloadSize, dh->firstTForbit, dh->tfCounter); + dh->splitPayloadIndex, dh->splitPayloadParts, dh->payloadSize, dh->firstTForbit, dh->tfCounter, dph ? dph->creation : -1UL, lCnt, lSize, lEntry); } } @@ -548,7 +560,7 @@ void RawTFDump::prepareTFForWriting(ProcessingContext& pc) OutputSpec spec{eq.mDataOrigin, eq.mDataDescription, eq.mSubSpecification}; if (mVerbose > 1) { - LOGP(info, "{} : {} parts of size {} | offset: {}", DataSpecUtils::describe(spec), lCnt, lSize, lCurrOff); + LOGP(info, "{} : {} parts of size {} entry {}| offset: {}", DataSpecUtils::describe(spec), lCnt, lSize, lEntry, lCurrOff); } mTFDataIndex.AddStfElement(eq, lCnt, lCurrOff, lSize); lCurrOff += lSize; @@ -577,6 +589,7 @@ DataProcessorSpec getRawTFDumpSpec(const std::string& inpconfig, const std::stri AlgorithmSpec{adaptFromTask(trigger)}, Options{ {"include-deadbeef", VariantType::Bool, false, {"Include DPL-generated 0xdeadbeef subspecs for missing data"}}, + {"include-dist-stf", VariantType::Bool, false, {"Include FLP/DISTSUBTIMEFRAME input"}}, {"exclude-trigger-specs", VariantType::String, "", {"Ignore trigger seen in these inputs of triggerspec"}}, {"max-dump-rate", VariantType::Float, 0.f, {"%-age of TFs to dump. W/o external trigger: random(>0) or periodic(<0) rejection, with: max limit"}}, {"rate-est-conf-limit", VariantType::Float, 0.05f, {"quantile for the lowest rate estimate confidence limit"}}, diff --git a/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx b/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx index 5f862dffe512f..c8bc6ff374ead 100644 --- a/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx +++ b/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx @@ -45,8 +45,8 @@ namespace o2f = o2::framework; /// SubTimeFrameFileReader //////////////////////////////////////////////////////////////////////////////// -SubTimeFrameFileReader::SubTimeFrameFileReader(const std::string& pFileName, o2::detectors::DetID::mask_t detMask) - : mFileName(pFileName) +SubTimeFrameFileReader::SubTimeFrameFileReader(const std::string& pFileName, o2::detectors::DetID::mask_t detMask, int verb, bool sup0xccdb, bool repaireHeaders, bool rejectDistSTF) + : mFileName(pFileName), mVerbosity(verb), mSup0xccdb(sup0xccdb), mRepaireHeaders(repaireHeaders), mRejectDistSTF(rejectDistSTF) { mFileMap.open(mFileName); if (!mFileMap.is_open()) { @@ -178,13 +178,21 @@ Stack SubTimeFrameFileReader::getHeaderStack(std::size_t& pOrigsize) return Stack(lStackMem); } +const std::string SubTimeFrameFileReader::describeHeader(const o2::header::DataHeader& hd, bool full) const +{ + std::string res = fmt::format("{}", o2f::DataSpecUtils::describe(o2::framework::OutputSpec{hd.dataOrigin, hd.dataDescription, hd.subSpecification})); + if (full) { + res += fmt::format(" part:{}/{} sz:{} TF:{} Orb:{} Run:{}", hd.splitPayloadIndex, hd.splitPayloadParts, hd.payloadSize, hd.tfCounter, hd.firstTForbit, hd.runNumber); + } + return res; +} + std::uint32_t sRunNumber = 0; // TODO: add id to files metadata std::uint32_t sFirstTForbit = 0; // TODO: add id to files metadata std::uint64_t sCreationTime = 0; std::mutex stfMtx; -std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* device, const std::vector& outputRoutes, - const std::string& rawChannel, size_t slice, bool sup0xccdb, int verbosity) +std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* device, const std::vector& outputRoutes, const std::string& rawChannel, size_t slice) { std::unique_ptr messagesPerRoute = std::make_unique(); auto& msgMap = *messagesPerRoute.get(); @@ -252,10 +260,15 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* return nullptr; } lStfMetaDataHdr = o2::header::DataHeader::Get(lMetaHdrStack.first()); - LOGP(debug, "read filemeta, pos = {}, size = {}", position(), sizeof(SubTimeFrameFileMeta)); + if (mVerbosity > 0) { + LOGP(info, "read filemeta, pos = {}, size = {}", position(), sizeof(SubTimeFrameFileMeta)); + } if (!read_advance(&lStfFileMeta, sizeof(SubTimeFrameFileMeta))) { return nullptr; } + if (mVerbosity > 0) { + LOGP(info, "TFMeta : {}", lStfFileMeta.info()); + } if (lStfFileMeta.mWriteTimeMs == 0 && creationFallBack != 0) { if (!creation0Notified) { creation0Notified = true; @@ -319,6 +332,7 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* std::int64_t lLeftToRead = lStfDataSize; STFHeader stfHeader{tfID, -1u, -1u}; + DataHeader prevHeader; // read pairs while (lLeftToRead > 0) { // allocate and read the Headers @@ -335,6 +349,25 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* return nullptr; } DataHeader locDataHeader(*lDataHeader); + + if (mRepaireHeaders) { + if (locDataHeader == prevHeader) { + if (prevHeader.tfCounter == locDataHeader.tfCounter && (prevHeader.splitPayloadIndex + 1) != locDataHeader.splitPayloadIndex) { + if (mVerbosity > 3) { + LOGP(warn, "Repairing wrong part index for {} to {}", describeHeader(locDataHeader, true), (prevHeader.splitPayloadIndex + 1) % prevHeader.splitPayloadParts); + } + locDataHeader.splitPayloadIndex = (++prevHeader.splitPayloadIndex) % prevHeader.splitPayloadParts; + } + } else { // new header + if (locDataHeader.splitPayloadIndex != 0) { + if (mVerbosity > 2) { + LOGP(warn, "Repairing wrong part index for new {} to {}", describeHeader(locDataHeader, true), (prevHeader.splitPayloadIndex + 1) % prevHeader.splitPayloadParts); + } + locDataHeader.splitPayloadIndex = 0; + } + } + prevHeader = locDataHeader; + } // sanity check if (int(locDataHeader.firstTForbit) == -1) { if (!negativeOrbitNotified) { @@ -350,6 +383,18 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* } locDataHeader.runNumber = runNumberFallBack; } + const std::uint64_t lDataSize = locDataHeader.payloadSize; + + if (locDataHeader.dataOrigin == o2::header::gDataOriginFLP && locDataHeader.dataDescription == o2::header::gDataDescriptionDISTSTF && mRejectDistSTF) { + if (mVerbosity > 0) { + LOGP(warn, "Ignoring stored {}", describeHeader(locDataHeader)); + } + if (!ignore_nbytes(lDataSize)) { + return nullptr; + } + lLeftToRead -= (lDataHeaderStackSize + lDataSize); // update the counter + continue; + } o2::header::Stack headerStack{locDataHeader, o2f::DataProcessingHeader{tfID, 1, lStfFileMeta.mWriteTimeMs}}; if (stfHeader.runNumber == -1) { stfHeader.id = locDataHeader.tfCounter; @@ -359,8 +404,6 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* sRunNumber = stfHeader.runNumber; sFirstTForbit = stfHeader.firstOrbit; } - - const std::uint64_t lDataSize = locDataHeader.payloadSize; // do we accept these data? auto detOrigStatus = mDetOrigMap.find(locDataHeader.dataOrigin); if (detOrigStatus != mDetOrigMap.end() && !detOrigStatus->second) { // this is a detector data and we don't want to read it @@ -403,10 +446,10 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* if (!read_advance(lDataMsg->GetData(), lDataSize)) { return nullptr; } - if (verbosity > 0) { - if (verbosity > 1 || locDataHeader.splitPayloadIndex == 0) { + if (mVerbosity > 0) { + if (mVerbosity > 1 || locDataHeader.splitPayloadIndex == 0) { printStack(headerStack); - if (o2::raw::RDHUtils::checkRDH(lDataMsg->GetData()) && verbosity > 2) { + if (o2::raw::RDHUtils::checkRDH(lDataMsg->GetData()) && mVerbosity > 2) { o2::raw::RDHUtils::printRDH(lDataMsg->GetData()); } } @@ -414,6 +457,9 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* #ifdef _RUN_TIMING_MEASUREMENT_ addPartSW.Start(false); #endif + if (mVerbosity > 2) { + LOGP(info, "addPart {} to {} | HdrSize:{} DataSize:{}", describeHeader(locDataHeader, true), fmqChannel, lHdrStackMsg->GetSize(), lDataMsg->GetSize()); + } addPart(std::move(lHdrStackMsg), std::move(lDataMsg), fmqChannel); #ifdef _RUN_TIMING_MEASUREMENT_ addPartSW.Stop(); @@ -435,7 +481,7 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* } unsigned stfSS[2] = {0, 0xccdb}; - for (int iss = 0; iss < (sup0xccdb ? 1 : 2); iss++) { + for (int iss = 0; iss < (mSup0xccdb ? 1 : 2); iss++) { o2::header::DataHeader stfDistDataHeader(o2::header::gDataDescriptionDISTSTF, o2::header::gDataOriginFLP, stfSS[iss], sizeof(STFHeader), 0, 1); stfDistDataHeader.payloadSerializationMethod = o2::header::gSerializationMethodNone; stfDistDataHeader.firstTForbit = stfHeader.firstOrbit; @@ -445,7 +491,7 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* if (!fmqChannel.empty()) { // no output channel auto fmqFactory = device->GetChannel(fmqChannel, 0).Transport(); o2::header::Stack headerStackSTF{stfDistDataHeader, o2f::DataProcessingHeader{tfID, 1, lStfFileMeta.mWriteTimeMs}}; - if (verbosity > 0) { + if (mVerbosity > 0) { printStack(headerStackSTF); } auto hdMessageSTF = fmqFactory->CreateMessage(headerStackSTF.size(), fair::mq::Alignment{64}); @@ -455,6 +501,9 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* #ifdef _RUN_TIMING_MEASUREMENT_ addPartSW.Start(false); #endif + if (mVerbosity > 2) { + LOGP(info, "addPart forced {} to {} | HdrSize:{} DataSize:{}", describeHeader(stfDistDataHeader, true), fmqChannel, hdMessageSTF->GetSize(), plMessageSTF->GetSize()); + } addPart(std::move(hdMessageSTF), std::move(plMessageSTF), fmqChannel); #ifdef _RUN_TIMING_MEASUREMENT_ addPartSW.Stop(); diff --git a/Detectors/Raw/TFReaderDD/src/TFReaderSpec.cxx b/Detectors/Raw/TFReaderDD/src/TFReaderSpec.cxx index 919e76083f595..d0de5fb893e3d 100644 --- a/Detectors/Raw/TFReaderDD/src/TFReaderSpec.cxx +++ b/Detectors/Raw/TFReaderDD/src/TFReaderSpec.cxx @@ -118,6 +118,9 @@ void TFReaderSpec::init(o2f::InitContext& ic) mInput.maxTFsPerFile = mInput.maxTFsPerFile > 0 ? mInput.maxTFsPerFile : 0x7fffffff; mInput.maxTFCache = std::max(1, ic.options().get("max-cached-tf")); mInput.maxFileCache = std::max(1, ic.options().get("max-cached-files")); + mInput.repairHeaders = !ic.options().get("ignore-repair-headers"); + mInput.rejectDistSTF = !ic.options().get("read-dist-stf"); + if (!mInput.fileRunTimeSpans.empty()) { loadRunTimeSpans(mInput.fileRunTimeSpans); } @@ -263,7 +266,11 @@ void TFReaderSpec::run(o2f::ProcessingContext& ctx) setTimingInfo(*tfPtr.get()); size_t nparts = 0, dataSize = 0; if (mInput.sendDummyForMissing) { + int cntAck = 0; for (auto& msgIt : *tfPtr.get()) { // complete with empty output for the specs which were requested but were not seen in the data + if (mInput.verbosity > 0) { + LOGP(info, "acknowledgeOutput {}", cntAck++); + } acknowledgeOutput(*msgIt.second.get(), true); } addMissingParts(*tfPtr.get()); @@ -409,7 +416,7 @@ void TFReaderSpec::TFBuilder() } LOG(info) << "Processing file " << tfFileName; - SubTimeFrameFileReader reader(tfFileName, mInput.detMask); + SubTimeFrameFileReader reader(tfFileName, mInput.detMask, mInput.verbosity, mInput.sup0xccdb, mInput.repairHeaders, mInput.rejectDistSTF); size_t locID = 0; // try { @@ -421,7 +428,7 @@ void TFReaderSpec::TFBuilder() std::this_thread::sleep_for(sleepTime); continue; } - auto tf = reader.read(mDevice, mOutputRoutes, mInput.rawChannelConfig, mAccTFCounter, mInput.sup0xccdb, mInput.verbosity); + auto tf = reader.read(mDevice, mOutputRoutes, mInput.rawChannelConfig, mAccTFCounter); bool acceptTF = true; if (tf) { if (mRunTimeRanges.size()) { @@ -675,6 +682,8 @@ o2f::DataProcessorSpec o2::rawdd::getTFReaderSpec(o2::rawdd::TFReaderInp& rinp) } spec.options.emplace_back(o2f::ConfigParamSpec{"select-tf-ids", o2f::VariantType::String, "", {"comma-separated list TF IDs to inject (from cumulative counter of TFs seen)"}}); spec.options.emplace_back(o2f::ConfigParamSpec{"fetch-failure-threshold", o2f::VariantType::Float, 0.f, {"Fatil if too many failures( >0: fraction, <0: abs number, 0: no threshold)"}}); + spec.options.emplace_back(o2f::ConfigParamSpec{"ignore-repair-headers", o2f::VariantType::Bool, false, {"do not check/repair headers"}}); + spec.options.emplace_back(o2f::ConfigParamSpec{"read-dist-stf", o2f::VariantType::Bool, false, {"do not ignore stored FLP/DISTSUBTIMEFRAME (will clash with injected one)"}}); spec.options.emplace_back(o2f::ConfigParamSpec{"max-tf", o2f::VariantType::Int, -1, {"max TF ID to process (<= 0 : infinite)"}}); spec.options.emplace_back(o2f::ConfigParamSpec{"max-tf-per-file", o2f::VariantType::Int, -1, {"max TFs to process per raw-tf file (<= 0 : infinite)"}}); spec.options.emplace_back(o2f::ConfigParamSpec{"max-cached-tf", o2f::VariantType::Int, 3, {"max TFs to cache in memory"}}); diff --git a/Detectors/Raw/TFReaderDD/src/TFReaderSpec.h b/Detectors/Raw/TFReaderDD/src/TFReaderSpec.h index 2c1c62ecbb414..6ecce0d032c06 100644 --- a/Detectors/Raw/TFReaderDD/src/TFReaderSpec.h +++ b/Detectors/Raw/TFReaderDD/src/TFReaderSpec.h @@ -49,6 +49,8 @@ struct TFReaderInp { bool sendDummyForMissing = true; bool sup0xccdb = false; bool invertIRFramesSelection = false; + bool repairHeaders = true; + bool rejectDistSTF = true; std::vector hdVec; std::vector tfIDs{}; }; diff --git a/Detectors/Raw/TFReaderDD/src/tf-reader-workflow.cxx b/Detectors/Raw/TFReaderDD/src/tf-reader-workflow.cxx index b424353531de7..a29b4dadfdb25 100644 --- a/Detectors/Raw/TFReaderDD/src/tf-reader-workflow.cxx +++ b/Detectors/Raw/TFReaderDD/src/tf-reader-workflow.cxx @@ -34,7 +34,7 @@ void customize(std::vector& workflowOptions) options.push_back(ConfigParamSpec{"copy-dir", VariantType::String, "/tmp/", {"copy base directory for remote files"}}); options.push_back(ConfigParamSpec{"tf-file-regex", VariantType::String, ".+\\.tf$", {"regex string to identify TF files"}}); options.push_back(ConfigParamSpec{"remote-regex", VariantType::String, "^(alien://|)/alice/data/.+", {"regex string to identify remote files"}}); // Use "^/eos/aliceo2/.+" for direct EOS access - options.push_back(ConfigParamSpec{"tf-reader-verbosity", VariantType::Int, 0, {"verbosity level (1 or 2: check RDH, print DH/DPH for 1st or all slices, >2 print RDH)"}}); + options.push_back(ConfigParamSpec{"tf-reader-verbosity", VariantType::Int, 0, {"verbosity level (1 or 2: check RDH, print DH/DPH for 1st or all slices, >2 print RDH), report repairs"}}); options.push_back(ConfigParamSpec{"raw-channel-config", VariantType::String, "", {"optional raw FMQ channel for non-DPL output"}}); options.push_back(ConfigParamSpec{"send-diststf-0xccdb", VariantType::Bool, false, {"send explicit FLP/DISTSUBTIMEFRAME/0xccdb output"}}); options.push_back(ConfigParamSpec{"disable-dummy-output", VariantType::Bool, false, {"Disable sending empty output if corresponding data is not found in the data"}}); From 231abca7cb99fb989328cf4ee782bc104dbaa7ec Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Mon, 11 May 2026 23:18:56 +0200 Subject: [PATCH 083/102] DPL: auto register CCDB path options ConfigurableCCDBPath only needed if you want to override the value directly. --- Framework/Core/include/Framework/AnalysisTask.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Framework/Core/include/Framework/AnalysisTask.h b/Framework/Core/include/Framework/AnalysisTask.h index fbd523c7b0c37..1b25727874749 100644 --- a/Framework/Core/include/Framework/AnalysisTask.h +++ b/Framework/Core/include/Framework/AnalysisTask.h @@ -16,6 +16,7 @@ #include "Framework/AlgorithmSpec.h" #include "Framework/CallbackService.h" #include "Framework/ConfigContext.h" +#include "Framework/ConfigParamsHelper.h" #include "Framework/ControlService.h" #include "Framework/DataProcessorSpec.h" #include "Framework/Expressions.h" @@ -557,6 +558,17 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args) LOG(warn) << "Task " << name_str << " has no inputs"; } + // Auto-register default ccdb: path options from subscribed timestamped-table inputs. + // This allows tasks to accept --ccdb:fXxx overrides without requiring an explicit + // ConfigurableCCDBPath<> member for every column in the subscribed table. + for (auto& input : inputs) { + for (auto& meta : input.metadata) { + if (meta.name.starts_with("ccdb:") && meta.name != "ccdb:") { + ConfigParamsHelper::addOptionIfMissing(options, meta); + } + } + } + homogeneous_apply_refs_sized([&outputs, &hash](auto& element) { return analysis_task_parsers::appendOutput(outputs, element, hash); }, *task.get()); auto requiredServices = CommonServices::defaultServices(); From 2f52be1afa9d96fc02148b3fee7eb73fb1ef7661 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Tue, 19 May 2026 11:33:06 +0200 Subject: [PATCH 084/102] DPL MCP: support multiple workflows Rather than having to specify a single workflow in the MCP configuration, allow it to connect to any running workflow. --- .../scripts/dpl-mcp-server/dpl_mcp_server.py | 351 +++++++++++------- 1 file changed, 223 insertions(+), 128 deletions(-) diff --git a/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py b/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py index 3900a646632a1..dca5058b01dcd 100644 --- a/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py +++ b/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py @@ -14,19 +14,21 @@ Bridges the DPL driver /status WebSocket endpoint to MCP tools so that an AI assistant (e.g. Claude) can inspect and monitor a running DPL workflow. +Supports multiple concurrent workflows. Use the ``connect`` tool to attach +to a running topology by port or PID, then pass the returned workflow name +to every other tool. + Usage ----- - python3 dpl_mcp_server.py --port 8080 - python3 dpl_mcp_server.py --pid 12345 # port derived as 8080 + pid % 30000 - DPL_STATUS_PORT=8080 python3 dpl_mcp_server.py + python3 dpl_mcp_server.py -Wire protocol (client → driver) +Wire protocol (client -> driver) -------------------------------- {"cmd":"list_metrics","device":""} {"cmd":"subscribe","device":"","metrics":["m1","m2"]} {"cmd":"unsubscribe","device":"","metrics":["m1"]} -Wire protocol (driver → client) +Wire protocol (driver -> client) -------------------------------- {"type":"snapshot","devices":[{"name","pid","active","streamingState","deviceState"},...]} {"type":"update","device":,"name":"","metrics":{}} @@ -35,80 +37,115 @@ from __future__ import annotations -import argparse import asyncio import json -import os -import sys from typing import Any import websockets from mcp.server.fastmcp import FastMCP + # --------------------------------------------------------------------------- -# Global connection state (all access from the single asyncio event loop) +# Per-workflow connection state # --------------------------------------------------------------------------- -_port: int = 8080 -_ws: Any = None -_reader_task: asyncio.Task | None = None -_snapshot: dict = {} -_updates: list[dict] = [] -_logs: list[dict] = [] -_metrics_lists: dict[str, list[str]] = {} - - -async def _ensure_connected() -> None: - """Connect (or reconnect) to the driver's /status WebSocket.""" - global _ws, _reader_task - - # Check liveness of existing connection. - if _ws is not None: +class WorkflowConnection: + """Holds WebSocket connection and buffered state for one DPL workflow.""" + + def __init__(self, port: int, name: str): + self.port = port + self.name = name + self.ws: Any = None + self.reader_task: asyncio.Task | None = None + self.snapshot: dict = {} + self.updates: list[dict] = [] + self.logs: list[dict] = [] + self.metrics_lists: dict[str, list[str]] = {} + + async def ensure_connected(self) -> None: + """Connect (or reconnect) to the driver's /status WebSocket.""" + if self.ws is not None: + try: + pong = await asyncio.wait_for(self.ws.ping(), timeout=2.0) + await pong + return + except Exception: + old_ws = self.ws + self.ws = None + if self.reader_task is not None and not self.reader_task.done(): + self.reader_task.cancel() + try: + await self.reader_task + except (asyncio.CancelledError, Exception): + pass + self.reader_task = None + try: + await old_ws.close() + except Exception: + pass + + url = f"ws://localhost:{self.port}/status" + self.ws = await websockets.connect(url, subprotocols=["dpl"]) + if self.reader_task is None or self.reader_task.done(): + self.reader_task = asyncio.create_task(self._reader()) + + async def _reader(self) -> None: + """Background task: read frames from the driver and buffer them.""" try: - pong = await asyncio.wait_for(_ws.ping(), timeout=2.0) - await pong - return + async for raw in self.ws: + try: + msg = json.loads(raw) + except json.JSONDecodeError: + continue + t = msg.get("type") + if t == "snapshot": + self.snapshot = msg + self.metrics_lists.clear() + elif t == "update": + self.updates.append(msg) + elif t == "log": + self.logs.append(msg) + elif t == "metrics_list": + device = msg.get("device", "") + self.metrics_lists[device] = msg.get("metrics", []) except Exception: - _ws = None - if _reader_task is not None and not _reader_task.done(): - _reader_task.cancel() - _reader_task = None - - url = f"ws://localhost:{_port}/status" - _ws = await websockets.connect(url, subprotocols=["dpl"]) - if _reader_task is None or _reader_task.done(): - _reader_task = asyncio.create_task(_reader()) - - -async def _reader() -> None: - """Background task: read frames from the driver and buffer them.""" - global _ws, _snapshot, _updates, _logs, _metrics_lists - try: - async for raw in _ws: + pass + finally: + self.ws = None + + async def send(self, obj: dict) -> None: + await self.ensure_connected() + await self.ws.send(json.dumps(obj, separators=(",", ":"))) + + async def close(self) -> None: + ws = self.ws + self.ws = None + if self.reader_task is not None and not self.reader_task.done(): + self.reader_task.cancel() try: - msg = json.loads(raw) - except json.JSONDecodeError: - continue - t = msg.get("type") - if t == "snapshot": - _snapshot = msg - # Clear stale metric lists from a previous driver instance. - _metrics_lists.clear() - elif t == "update": - _updates.append(msg) - elif t == "log": - _logs.append(msg) - elif t == "metrics_list": - device = msg.get("device", "") - _metrics_lists[device] = msg.get("metrics", []) - except Exception: - pass - finally: - _ws = None - - -async def _send(obj: dict) -> None: - await _ensure_connected() - await _ws.send(json.dumps(obj, separators=(",", ":"))) + await self.reader_task + except (asyncio.CancelledError, Exception): + pass + self.reader_task = None + if ws is not None: + await ws.close() + + +# --------------------------------------------------------------------------- +# Workflow registry +# --------------------------------------------------------------------------- +_workflows: dict[str, WorkflowConnection] = {} + + +def _get(workflow: str) -> WorkflowConnection: + """Look up a workflow by name, raising a clear error if not found.""" + conn = _workflows.get(workflow) + if conn is None: + available = ", ".join(_workflows.keys()) if _workflows else "(none)" + raise ValueError( + f"No workflow named '{workflow}'. Connected workflows: {available}. " + f"Use the connect tool first." + ) + return conn # --------------------------------------------------------------------------- @@ -118,16 +155,81 @@ async def _send(obj: dict) -> None: @mcp.tool() -async def list_devices() -> str: +async def connect(port: int = 0, pid: int = 0, name: str = "") -> str: + """Connect to a running DPL workflow. + + Provide either ``port`` (the driver's WebSocket port) or ``pid`` (the + driver PID, port derived as 8080 + pid % 30000). An optional ``name`` + gives the workflow a human-friendly label; if omitted the port number is + used. + + Args: + port: TCP port of the DPL driver status WebSocket. + pid: PID of the DPL driver process (alternative to port). + name: Optional human-friendly name for this workflow. + """ + if pid: + port = 8080 + pid % 30000 + if not port: + return "Provide either port or pid." + + wf_name = name or str(port) + if wf_name in _workflows: + old = _workflows[wf_name] + await old.close() + + conn = WorkflowConnection(port, wf_name) + await conn.ensure_connected() + _workflows[wf_name] = conn + + devices = conn.snapshot.get("devices", []) + return ( + f"Connected to workflow '{wf_name}' on port {port} " + f"({len(devices)} device(s))." + ) + + +@mcp.tool() +async def disconnect(workflow: str) -> str: + """Disconnect from a DPL workflow and release its resources. + + Args: + workflow: Workflow name as returned by connect. + """ + conn = _get(workflow) + await conn.close() + del _workflows[workflow] + return f"Disconnected from workflow '{workflow}'." + + +@mcp.tool() +async def list_workflows() -> str: + """List all currently connected DPL workflows.""" + if not _workflows: + return "No workflows connected. Use the connect tool first." + lines = [] + for wf_name, conn in _workflows.items(): + n = len(conn.snapshot.get("devices", [])) + status = "connected" if conn.ws is not None else "disconnected" + lines.append(f"{wf_name}: port={conn.port} devices={n} status={status}") + return "\n".join(lines) + + +@mcp.tool() +async def list_devices(workflow: str) -> str: """List all DPL devices with their current status. Returns each device's name, PID, active flag, streaming state, and device state as reported by the driver snapshot. + + Args: + workflow: Workflow name as returned by connect. """ - await _ensure_connected() - if not _snapshot: - return "No snapshot received yet — the driver may still be starting." - devices = _snapshot.get("devices", []) + conn = _get(workflow) + await conn.ensure_connected() + if not conn.snapshot: + return "No snapshot received yet -- the driver may still be starting." + devices = conn.snapshot.get("devices", []) if not devices: return "No devices in snapshot." lines = [] @@ -140,7 +242,7 @@ async def list_devices() -> str: @mcp.tool() -async def list_metrics(device: str) -> str: +async def list_metrics(workflow: str, device: str) -> str: """List the available numeric metrics for a DPL device. Sends a list_metrics command to the driver and waits up to 3 seconds for @@ -148,15 +250,16 @@ async def list_metrics(device: str) -> str: and enum metrics are excluded. Args: + workflow: Workflow name as returned by connect. device: Device name exactly as shown by list_devices. """ - # Remove any stale cached result so we can detect the fresh reply. - _metrics_lists.pop(device, None) - await _send({"cmd": "list_metrics", "device": device}) + conn = _get(workflow) + conn.metrics_lists.pop(device, None) + await conn.send({"cmd": "list_metrics", "device": device}) for _ in range(60): # up to 3 s await asyncio.sleep(0.05) - if device in _metrics_lists: - names = _metrics_lists[device] + if device in conn.metrics_lists: + names = conn.metrics_lists[device] if not names: return f"Device '{device}' has no numeric metrics yet." return f"{len(names)} metric(s): " + ", ".join(names) @@ -164,7 +267,7 @@ async def list_metrics(device: str) -> str: @mcp.tool() -async def subscribe(device: str, metrics: list[str]) -> str: +async def subscribe(workflow: str, device: str, metrics: list[str]) -> str: """Subscribe to one or more metrics for a DPL device. After subscribing, the driver will push update frames for the device @@ -172,60 +275,70 @@ async def subscribe(device: str, metrics: list[str]) -> str: the buffer. Args: + workflow: Workflow name as returned by connect. device: Device name exactly as shown by list_devices. metrics: List of metric names to subscribe to (from list_metrics). """ - await _send({"cmd": "subscribe", "device": device, "metrics": metrics}) + conn = _get(workflow) + await conn.send({"cmd": "subscribe", "device": device, "metrics": metrics}) return f"Subscribed to {len(metrics)} metric(s) for '{device}': {', '.join(metrics)}" @mcp.tool() -async def unsubscribe(device: str, metrics: list[str]) -> str: +async def unsubscribe(workflow: str, device: str, metrics: list[str]) -> str: """Stop receiving updates for specific metrics of a DPL device. Args: + workflow: Workflow name as returned by connect. device: Device name exactly as shown by list_devices. metrics: List of metric names to unsubscribe from. """ - await _send({"cmd": "unsubscribe", "device": device, "metrics": metrics}) + conn = _get(workflow) + await conn.send({"cmd": "unsubscribe", "device": device, "metrics": metrics}) return f"Unsubscribed from {len(metrics)} metric(s) for '{device}'." @mcp.tool() -async def subscribe_logs(device: str) -> str: +async def subscribe_logs(workflow: str, device: str) -> str: """Subscribe to log output for a DPL device. After subscribing, new log lines from the device will be buffered and can be retrieved with get_logs(). Args: + workflow: Workflow name as returned by connect. device: Device name exactly as shown by list_devices. """ - await _send({"cmd": "subscribe_logs", "device": device}) + conn = _get(workflow) + await conn.send({"cmd": "subscribe_logs", "device": device}) return f"Subscribed to logs for '{device}'." @mcp.tool() -async def unsubscribe_logs(device: str) -> str: +async def unsubscribe_logs(workflow: str, device: str) -> str: """Stop receiving log output for a DPL device. Args: + workflow: Workflow name as returned by connect. device: Device name exactly as shown by list_devices. """ - await _send({"cmd": "unsubscribe_logs", "device": device}) + conn = _get(workflow) + await conn.send({"cmd": "unsubscribe_logs", "device": device}) return f"Unsubscribed from logs for '{device}'." @mcp.tool() -async def get_logs(max_lines: int = 100) -> str: +async def get_logs(workflow: str, max_lines: int = 100) -> str: """Drain and return buffered log lines received since the last call. Args: + workflow: Workflow name as returned by connect. max_lines: Maximum number of log lines to return (default 100). """ - await _ensure_connected() - batch = _logs[:max_lines] - del _logs[:max_lines] + conn = _get(workflow) + await conn.ensure_connected() + batch = conn.logs[:max_lines] + del conn.logs[:max_lines] if not batch: return "No buffered log lines." lines = [] @@ -238,17 +351,21 @@ async def get_logs(max_lines: int = 100) -> str: @mcp.tool() -async def start_devices() -> str: +async def start_devices(workflow: str) -> str: """Resume all stopped DPL devices (send SIGCONT). Use this when the workflow was started with -s (all devices paused). + + Args: + workflow: Workflow name as returned by connect. """ - await _send({"cmd": "start_devices"}) + conn = _get(workflow) + await conn.send({"cmd": "start_devices"}) return "Sent SIGCONT to all active devices." @mcp.tool() -async def enable_signpost(device: str, streams: list[str]) -> str: +async def enable_signpost(workflow: str, device: str, streams: list[str]) -> str: """Enable one or more signpost log streams for a DPL device. Signpost streams produce detailed trace output visible in the device logs. @@ -259,27 +376,31 @@ async def enable_signpost(device: str, streams: list[str]) -> str: ch.cern.aliceo2.data_processor_context, ch.cern.aliceo2.stream_context. Args: + workflow: Workflow name as returned by connect. device: Device name as shown by list_devices, or "" for the driver. streams: List of full signpost log names to enable. """ - await _send({"cmd": "enable_signpost", "device": device, "streams": streams}) + conn = _get(workflow) + await conn.send({"cmd": "enable_signpost", "device": device, "streams": streams}) return f"Enabled {len(streams)} signpost stream(s) for '{device or 'driver'}': {', '.join(streams)}" @mcp.tool() -async def disable_signpost(device: str, streams: list[str]) -> str: +async def disable_signpost(workflow: str, device: str, streams: list[str]) -> str: """Disable one or more signpost log streams for a DPL device. Args: + workflow: Workflow name as returned by connect. device: Device name as shown by list_devices, or "" for the driver. streams: List of full signpost log names to disable. """ - await _send({"cmd": "disable_signpost", "device": device, "streams": streams}) + conn = _get(workflow) + await conn.send({"cmd": "disable_signpost", "device": device, "streams": streams}) return f"Disabled {len(streams)} signpost stream(s) for '{device or 'driver'}': {', '.join(streams)}" @mcp.tool() -async def get_updates(max_updates: int = 50) -> str: +async def get_updates(workflow: str, max_updates: int = 50) -> str: """Drain and return buffered metric update frames received since the last call. Each frame contains the latest values of all subscribed metrics that @@ -287,11 +408,13 @@ async def get_updates(max_updates: int = 50) -> str: time-ordered view of metric evolution. Args: + workflow: Workflow name as returned by connect. max_updates: Maximum number of update frames to return (default 50). """ - await _ensure_connected() - batch = _updates[:max_updates] - del _updates[:max_updates] + conn = _get(workflow) + await conn.ensure_connected() + batch = conn.updates[:max_updates] + del conn.updates[:max_updates] if not batch: return "No buffered updates." lines = [] @@ -310,34 +433,6 @@ async def get_updates(max_updates: int = 50) -> str: # Entry point # --------------------------------------------------------------------------- def main() -> None: - global _port - - parser = argparse.ArgumentParser( - description="DPL status MCP server — expose DPL driver metrics via MCP tools" - ) - group = parser.add_mutually_exclusive_group() - group.add_argument( - "--port", - type=int, - default=None, - help="TCP port of the DPL driver status WebSocket (default: 8080 or DPL_STATUS_PORT env var)", - ) - group.add_argument( - "--pid", - type=int, - default=None, - help="PID of the DPL driver process; port is derived as 8080 + pid %% 30000", - ) - args = parser.parse_args() - - if args.pid is not None: - _port = 8080 + args.pid % 30000 - elif args.port is not None: - _port = args.port - elif "DPL_STATUS_PORT" in os.environ: - _port = int(os.environ["DPL_STATUS_PORT"]) - # else leave _port at the default 8080 - mcp.run() From 3ee5c9fe4f3e8b26cab7c37198fab29582d6019b Mon Sep 17 00:00:00 2001 From: mcoquet642 <74600025+mcoquet642@users.noreply.github.com> Date: Tue, 19 May 2026 22:05:49 +0200 Subject: [PATCH 085/102] [MFT] Fixing number of links per RU/zone in mapping (#15410) Co-authored-by: Maurice Coquet --- .../ITSMFTReconstruction/ChipMappingMFT.h | 17 ++++++++--------- .../reconstruction/src/ChipMappingMFT.cxx | 6 +++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingMFT.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingMFT.h index eee9bdbb6a4dc..63d37a25ffbc9 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingMFT.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingMFT.h @@ -73,16 +73,15 @@ class ChipMappingMFT ///< total number of RUs static constexpr Int_t getNRUs() { return NRUs; } - ///< get FEEId of the RU (software id of the RU), read via given link + ///< get software id of the RU, from first 8 bits of FEEID (HW id of RU) uint8_t FEEId2RUSW(uint16_t hw) const { return mFEEId2RUSW[hw & 0xff]; } - ///< get HW id of the RU (software id of the RU) + ///< get FEEID, from software id of the RU and link number uint16_t RUSW2FEEId(uint16_t sw, uint16_t linkID = 0) const { return ((linkID << 8) + mRUInfo[sw].idHW); } ///< compose FEEid for given stave (ru) relative to layer and link, see documentation in the constructor uint16_t composeFEEId(uint16_t layer, uint16_t ruOnLayer, uint16_t link) const { - // only one link is used // ruOnLayer is 0, 1, 2, 3 for half = 0 // 4, 5, 6, 7 1 auto dhalf = std::div(ruOnLayer, 4); @@ -114,7 +113,7 @@ class ChipMappingMFT face = (feeID >> 2) & 0x1; } - ///< get info on sw RU + ///< get info on sw RU corresponding to given FEEID const RUInfo* getRUInfoFEEId(Int_t feeID) const { return &mRUInfo[FEEId2RUSW(feeID)]; } ///< get number of chips served by single cable on given RU type @@ -123,13 +122,13 @@ class ChipMappingMFT return ((0x1 << 7) + (cableHW & 0x1f)); } - ///< convert HW cable ID to its position on the ActiveLanes word in the GBT.header for given RU type + ///< convert HW cable ID to its position on the ActiveLanes word in the GBT.header for given RU type (note: this position is equal to the HW cable ID) uint8_t cableHW2Pos(uint8_t ruType, uint8_t hwid) const { return mCableHW2Pos[ruType][hwid]; } ///< convert HW cable ID to SW ID for give RU type uint8_t cableHW2SW(uint8_t ruType, uint8_t hwid) const { return hwid < mCableHW2SW[ruType].size() ? mCableHW2SW[ruType][hwid] : 0xff; } - ///< convert cable iterator ID to its position on the ActiveLanes word in the GBT.header for given RU type + ///< convert cable iterator ID (i.e. chipOnModule) to its position on the ActiveLanes word in the GBT.header for given RU type (note: this position is equal to the HW cable ID) uint8_t cablePos(uint8_t ruType, uint8_t id) const { return mCablePos[ruType][id]; } ///< get chipID on module from chip global SW ID, cable SW ID and stave (RU) info @@ -139,7 +138,7 @@ class ChipMappingMFT return 0xffff; } - ///< get chip global SW ID from chipID on module, cable SW ID and stave (RU) info + ///< get chip global SW ID from cable HW ID and stave (RU) info (note: chOnModuleHW is unused) uint16_t getGlobalChipID(uint16_t chOnModuleHW, int cableHW, const RUInfo& ruInfo) const { auto chipOnRU = cableHW2SW(ruInfo.ruType, cableHW); @@ -393,11 +392,11 @@ class ChipMappingMFT private: Int_t invalid() const; - static constexpr Int_t NRUs = NLayers * NZonesPerLayer; + static constexpr Int_t NRUs = NLayers * NZonesPerLayer; // 10 layers * 8 zones per layer static constexpr Int_t NModules = 280; static constexpr Int_t NChipsInfo = 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 16 + 17 + 18 + 19 + 14; static constexpr Int_t NChipsPerCable = 1; - static constexpr Int_t NLinks = 1; + static constexpr Int_t NLinks = 3; static constexpr Int_t NConnectors = 5; static constexpr Int_t NMaxChipsPerLadder = 5; static constexpr Int_t NRUCables = 25; diff --git a/Detectors/ITSMFT/common/reconstruction/src/ChipMappingMFT.cxx b/Detectors/ITSMFT/common/reconstruction/src/ChipMappingMFT.cxx index de2358469e894..b79c529bef803 100644 --- a/Detectors/ITSMFT/common/reconstruction/src/ChipMappingMFT.cxx +++ b/Detectors/ITSMFT/common/reconstruction/src/ChipMappingMFT.cxx @@ -1624,7 +1624,7 @@ ChipMappingMFT::ChipMappingMFT() { // init chips info - uint32_t maxRUHW = composeFEEId(NLayers - 1, NZonesPerLayer - 1, NLinks - 1); // Max possible FEE ID + uint32_t maxRUHW = composeFEEId(NLayers - 1, NZonesPerLayer - 1, 0); // Max possible RU HW ID (first 8 bits of max FEEID, while link stored in 9th and 10th bit of FEEID) mFEEId2RUSW.resize(maxRUHW + 1, 0xff); int curLayer = -1, curZone = -1, curHalf = -1; @@ -1698,8 +1698,8 @@ ChipMappingMFT::ChipMappingMFT() auto& ruInfo = mRUInfo[ctrRU]; ruInfo.idSW = ctrRU++; - // map FEEIds (RU read out by at most 3 GBT links) to SW ID - ruInfo.idHW = composeFEEId(iLayer, iZone, 0); // FEEId for link 0 + // map RU HW ID (RU read out by at most 3 GBT links) to SW ID + ruInfo.idHW = composeFEEId(iLayer, iZone, 0); // RU HW ID (first 8 bits of FEEID) mFEEId2RUSW[ruInfo.idHW] = ruInfo.idSW; ruInfo.layer = iLayer; ruInfo.ruType = ZoneRUType[iZone % 4][iLayer / 2]; From 2e26434c440f559711b7a4e0aa576f0ab5ded134 Mon Sep 17 00:00:00 2001 From: shahoian Date: Tue, 19 May 2026 17:00:48 +0200 Subject: [PATCH 086/102] Fixes for ROFs downscaling for ITS tracking --- .../ITSMFT/ITS/tracking/src/FastMultEst.cxx | 86 +++++-------------- 1 file changed, 21 insertions(+), 65 deletions(-) diff --git a/Detectors/ITSMFT/ITS/tracking/src/FastMultEst.cxx b/Detectors/ITSMFT/ITS/tracking/src/FastMultEst.cxx index cb831d7db71d0..cfbfdd8a9150e 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/FastMultEst.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/FastMultEst.cxx @@ -168,85 +168,41 @@ int FastMultEst::selectROFs(const std::array(rofs, clus, doStaggering, multLayer); + const int selectionLayer = multEstConf.isMultCutRequested() ? std::clamp(multEstConf.cutMultClusLayer, 0, NLayers - 1) : overlapView.getClock(); + const auto multCounts = buildMultiplicityCounts(rofs, clus, doStaggering, selectionLayer); const int selectionRofCount = doStaggering ? static_cast(rofs[selectionLayer].size()) : static_cast(rofs[0].size()); sel.resetMask(); lastRandomSeed = gRandom->GetSeed(); const o2::InteractionRecord tfStartIR{0, firstTForbit}; - - if (!trig.empty()) { + // mask ROFs which are not good from the multiplicity selection (if any) POV + struct ROFStatus { + int entry = 0, priority = 0; + }; + std::vector selROFs; + selROFs.reserve(selectionRofCount); + bool selmult = multEstConf.isMultCutRequested(); + for (int selectionRof = 0; selectionRof < selectionRofCount; ++selectionRof) { + selROFs.emplace_back(selectionRof, (selmult && !multEstConf.isPassingMultCut(process(multCounts[selectionRof]))) ? -1 : 0); + } + if (!trig.empty() && multEstConf.preferTriggered) { const auto& selectionLayerTiming = overlapView.getLayer(selectionLayer); - const auto& multLayerTiming = overlapView.getLayer(multLayer); - for (const auto& trigger : trig) { const int selectionRof = findROFForIR(trigger.ir, tfStartIR, selectionLayerTiming); - if (selectionRof < 0) { - continue; - } - if (multEstConf.cutRandomFraction > 0.f && gRandom->Rndm() < multEstConf.cutRandomFraction) { - continue; - } - if (multEstConf.isMultCutRequested()) { - const int triggerMultRof = doStaggering ? findROFForIR(trigger.ir, tfStartIR, multLayerTiming) : selectionRof; - if (triggerMultRof < 0 || triggerMultRof >= static_cast(multCounts.size())) { - continue; - } - if (!multEstConf.isPassingMultCut(process(multCounts[triggerMultRof]))) { - continue; - } - } - enableCompatibleROFs(selectionLayer, selectionRof, overlapView, sel); - } - } else { - LOGP(info, "FastMultEst received no physics/TRD triggers, falling back to ROF-driven filtering on layer {}", selectionLayer); - for (int selectionRof = 0; selectionRof < selectionRofCount; ++selectionRof) { - if (multEstConf.isMultCutRequested()) { - bool passes = false; - if (!doStaggering || selectionLayer == multLayer) { - if (selectionRof < static_cast(multCounts.size())) { - passes = multEstConf.isPassingMultCut(process(multCounts[selectionRof])); - } - } else { - const auto& overlap = overlapView.getOverlap(selectionLayer, multLayer, selectionRof); - for (int rof = overlap.getFirstEntry(); rof < overlap.getEntriesBound(); ++rof) { - if (rof < static_cast(multCounts.size())) { - if (multEstConf.isPassingMultCut(process(multCounts[rof]))) { - passes = true; - break; - } - } - } - } - if (!passes) { - continue; - } - } - if (multEstConf.cutRandomFraction > 0.f && gRandom->Rndm() < multEstConf.cutRandomFraction) { + if (selectionRof < 0 || selROFs[selectionRof].priority < 0) { continue; } - enableCompatibleROFs(selectionLayer, selectionRof, overlapView, sel); + selROFs[selectionRof].priority++; // increment trigger counter } + sort(selROFs.begin(), selROFs.end(), [](const ROFStatus& a, const ROFStatus& b) { return a.priority > b.priority; }); // order in number of triggers, masked will go to the end } - - const auto selView = sel.getView(); int nsel = 0; - for (int irof = 0; irof < selectionRofCount; ++irof) { - nsel += selView.isROFEnabled(selectionLayer, irof); - } - - if (!trig.empty() && multEstConf.preferTriggered) { - LOGP(debug, "FastMultEst preferTriggered is ignored in trigger-driven mask mode"); + for (auto& rof : selROFs) { + if (rof.priority >= 0 && (multEstConf.cutRandomFraction <= 0.f || (gRandom->Rndm() > multEstConf.cutRandomFraction))) { + enableCompatibleROFs(selectionLayer, rof.entry, overlapView, sel); + nsel++; + } } - LOGP(debug, "NSel = {} of {} rofs on layer {} Seeds: before {} after {}", nsel, selectionRofCount, selectionLayer, lastRandomSeed, gRandom->GetSeed()); - return nsel; } From f8f5d1eb6c74aa9d5840407a082bef9af3f56555 Mon Sep 17 00:00:00 2001 From: Martin Eide <43970264+mrtineide@users.noreply.github.com> Date: Wed, 20 May 2026 14:06:56 +0200 Subject: [PATCH 087/102] Add token for the test CCDB instance Before the test instance did not need a token. This has changed, which broke tests that relied on hardcoded HTTP, and not HTTPS. Now the test instance redirects to HTTPS and we need a token. --- CCDB/src/CcdbApi.cxx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CCDB/src/CcdbApi.cxx b/CCDB/src/CcdbApi.cxx index 42bc13904bf61..93a79ad56c477 100644 --- a/CCDB/src/CcdbApi.cxx +++ b/CCDB/src/CcdbApi.cxx @@ -213,8 +213,7 @@ void CcdbApi::init(std::string const& host) snapshotReport += ')'; } - mNeedAlienToken = (host.find("https://") != std::string::npos) || (host.find("alice-ccdb.cern.ch") != std::string::npos); - + mNeedAlienToken = (host.find("https://") != std::string::npos) || (host.find("alice-ccdb.cern.ch") != std::string::npos) || (host.find("ccdb-test.cern.ch") != std::string::npos); // Set the curl timeout. It can be forced with an env var or it has different defaults based on the deployment mode. if (getenv("ALICEO2_CCDB_CURL_TIMEOUT_DOWNLOAD")) { auto timeout = atoi(getenv("ALICEO2_CCDB_CURL_TIMEOUT_DOWNLOAD")); From 084c3660c7bce5e8fa011fa0fee40a32d0d86f84 Mon Sep 17 00:00:00 2001 From: Matthias Kleiner Date: Wed, 20 May 2026 15:23:30 +0200 Subject: [PATCH 088/102] TPC: add option for disabling corrections - add option for using static corrections instead of main corrections --- .../calibration/src/CorrectionMapsLoader.cxx | 13 +++- .../calibration/src/CorrectionMapsOptions.cxx | 4 +- Detectors/TPC/workflow/src/TPCScalerSpec.cxx | 63 ++++++++++--------- .../CorrectionMapsHelper.cxx | 43 ++++++++++++- .../CorrectionMapsTypes.h | 14 +++-- 5 files changed, 96 insertions(+), 41 deletions(-) diff --git a/Detectors/TPC/calibration/src/CorrectionMapsLoader.cxx b/Detectors/TPC/calibration/src/CorrectionMapsLoader.cxx index 9569e0eb8abd2..c8bdfa0f99350 100644 --- a/Detectors/TPC/calibration/src/CorrectionMapsLoader.cxx +++ b/Detectors/TPC/calibration/src/CorrectionMapsLoader.cxx @@ -28,8 +28,13 @@ using namespace o2::framework; void CorrectionMapsLoader::extractCCDBInputs(ProcessingContext& pc, float tpcScaler) { pc.inputs().get("tpcCorrPar"); - pc.inputs().get("tpcCorrMap"); - pc.inputs().get("tpcCorrMapRef"); + const auto lumiMode = getLumiScaleMode(); + if (lumiMode != LumiScaleMode::NoCorrection && lumiMode != LumiScaleMode::StaticMapOnly) { + pc.inputs().get("tpcCorrMap"); + } + if (lumiMode != LumiScaleMode::NoCorrection) { + pc.inputs().get("tpcCorrMapRef"); + } const int maxDumRep = 5; int dumRep = 0; o2::ctp::LumiInfo lumiObj; @@ -97,6 +102,10 @@ void CorrectionMapsLoader::requestCCDBInputs(std::vector& inputs, con // for MC corrections addInput(inputs, {"tpcCorrMap", "TPC", "CorrMap", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalCorrMapMC), {}, 1)}); // time-dependent addInput(inputs, {"tpcCorrMapRef", "TPC", "CorrMapRef", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalCorrDerivMapMC), {}, 1)}); // time-dependent + } else if (gloOpts.lumiMode == LumiScaleMode::NoCorrection) { + // no correction maps needed — a dummy map is created at runtime + } else if (gloOpts.lumiMode == LumiScaleMode::StaticMapOnly) { + addInput(inputs, {"tpcCorrMapRef", "TPC", "CorrMapRef", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalCorrMapRef), {}, 0)}); // load once } else { LOG(fatal) << "Correction mode unknown! Choose either 0 (default) or 1 (derivative map) for flag corrmap-lumi-mode."; } diff --git a/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx b/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx index 604b7c680385b..45c3771db57bf 100644 --- a/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx +++ b/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx @@ -21,13 +21,13 @@ CorrectionMapsGloOpts CorrectionMapsOptions::parseGlobalOptions(const o2::framew { CorrectionMapsGloOpts tpcopt; auto lumiTypeVal = opts.get("lumi-type"); - if (lumiTypeVal < -1 || lumiTypeVal > 2) { + if (lumiTypeVal < static_cast(LumiScaleType::Unset) || lumiTypeVal >= static_cast(LumiScaleType::Count)) { LOGP(fatal, "Invalid lumi-type value: {}", lumiTypeVal); } tpcopt.lumiType = static_cast(lumiTypeVal); auto lumiModeVal = opts.get("corrmap-lumi-mode"); - if (lumiModeVal < -1 || lumiModeVal > 2) { + if (lumiModeVal < static_cast(LumiScaleMode::Unset) || lumiModeVal >= static_cast(LumiScaleMode::Count)) { LOGP(fatal, "Invalid corrmap-lumi-mode value: {}", lumiModeVal); } tpcopt.lumiMode = static_cast(lumiModeVal); diff --git a/Detectors/TPC/workflow/src/TPCScalerSpec.cxx b/Detectors/TPC/workflow/src/TPCScalerSpec.cxx index 8e2a78d69757b..1df192dd5ec00 100644 --- a/Detectors/TPC/workflow/src/TPCScalerSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCScalerSpec.cxx @@ -183,40 +183,45 @@ class TPCScalerSpec : public Task void buildMap(ProcessingContext& pc) { - // reference map - auto* corrMap = mTPCCorrMapsLoader.getCorrMap(); - - // // new correction map + const auto lumiMode = mTPCCorrMapsLoader.getLumiScaleMode(); o2::gpu::TPCFastTransform finalMap; - finalMap.cloneFromObject(*corrMap, nullptr); - finalMap.setApplyCorrectionOn(); - - const auto* corrMapRef = mTPCCorrMapsLoader.getCorrMapRef(); - const float lumiScale = mTPCCorrMapsLoader.getLumiScale(); std::vector> additionalCorrections; - // if standard scaling is used: map(lumi) = (mean_map - ref_map) * lumiScale + ref_map - if (mTPCCorrMapsLoader.getLumiScaleMode() == LumiScaleMode::Linear) { - const std::vector> step0{{&(corrMapRef->getCorrection()), -1.f}}; - // finalMap = (mean_map - finalMap) - TPCFastSpaceChargeCorrectionHelper::instance()->mergeCorrections(finalMap.getCorrection(), 1, step0, true); - - // finalMap = finalMap * lumiScale + ref_map - const std::vector> step1{{&(corrMapRef->getCorrection()), 1.f}}; - TPCFastSpaceChargeCorrectionHelper::instance()->mergeCorrections(finalMap.getCorrection(), lumiScale, step1, true); - - } else if (mTPCCorrMapsLoader.getLumiScaleMode() == LumiScaleMode::DerivativeMap || mTPCCorrMapsLoader.getLumiScaleMode() == LumiScaleMode::DerivativeMapMC) { - additionalCorrections.emplace_back(&(corrMapRef->getCorrection()), lumiScale); - } + if (lumiMode == LumiScaleMode::NoCorrection) { + std::unique_ptr dummy(TPCFastTransformHelperO2::instance()->create(0)); + finalMap.cloneFromObject(*dummy, nullptr); + finalMap.setApplyCorrectionOff(); + } else { + auto* corrMap = mTPCCorrMapsLoader.getCorrMap(); + const auto* corrMapRef = mTPCCorrMapsLoader.getCorrMapRef(); + finalMap.cloneFromObject(lumiMode == LumiScaleMode::StaticMapOnly && corrMapRef ? *corrMapRef : *corrMap, nullptr); + finalMap.setApplyCorrectionOn(); + + const float lumiScale = mTPCCorrMapsLoader.getLumiScale(); + + // if standard scaling is used: map(lumi) = (mean_map - ref_map) * lumiScale + ref_map + if (lumiMode == LumiScaleMode::Linear) { + const std::vector> step0{{&(corrMapRef->getCorrection()), -1.f}}; + // finalMap = (mean_map - finalMap) + TPCFastSpaceChargeCorrectionHelper::instance()->mergeCorrections(finalMap.getCorrection(), 1, step0, true); + + // finalMap = finalMap * lumiScale + ref_map + const std::vector> step1{{&(corrMapRef->getCorrection()), 1.f}}; + TPCFastSpaceChargeCorrectionHelper::instance()->mergeCorrections(finalMap.getCorrection(), lumiScale, step1, true); + + } else if (lumiMode == LumiScaleMode::DerivativeMap || lumiMode == LumiScaleMode::DerivativeMapMC) { + additionalCorrections.emplace_back(&(corrMapRef->getCorrection()), lumiScale); + } - // if mshape map valid - if (!mTPCCorrMapsLoader.isCorrMapMShapeDummy()) { - LOGP(info, "Adding M-shape correction to the final map with scaling factor {}", mMShapeScalingFac); - additionalCorrections.emplace_back(&(mTPCCorrMapsLoader.getCorrMapMShape()->getCorrection()), 1.f); - } + // if mshape map valid + if (!mTPCCorrMapsLoader.isCorrMapMShapeDummy()) { + LOGP(info, "Adding M-shape correction to the final map with scaling factor {}", mMShapeScalingFac); + additionalCorrections.emplace_back(&(mTPCCorrMapsLoader.getCorrMapMShape()->getCorrection()), 1.f); + } - if (!additionalCorrections.empty()) { - TPCFastSpaceChargeCorrectionHelper::instance()->mergeCorrections(finalMap.getCorrection(), 1, additionalCorrections, true); + if (!additionalCorrections.empty()) { + TPCFastSpaceChargeCorrectionHelper::instance()->mergeCorrections(finalMap.getCorrection(), 1, additionalCorrections, true); + } } Output corrMapOutput{header::gDataOriginTPC, "TPCCORRMAP", 0}; diff --git a/GPU/TPCFastTransformation/CorrectionMapsHelper.cxx b/GPU/TPCFastTransformation/CorrectionMapsHelper.cxx index 7f7deddafe1c8..4bfedc117dec7 100644 --- a/GPU/TPCFastTransformation/CorrectionMapsHelper.cxx +++ b/GPU/TPCFastTransformation/CorrectionMapsHelper.cxx @@ -38,6 +38,10 @@ void CorrectionMapsHelper::setCorrMapMShape(std::unique_ptr&& void CorrectionMapsHelper::updateLumiScale(bool report) { if (!canUseCorrections()) { + if (mLumiScaleMode != LumiScaleMode::NoCorrection) { + LOGP(warning, "Negative meanLumi={} detected, switching to NoCorrection mode for backward compatibility", mMeanLumi); + mLumiScaleMode = LumiScaleMode::NoCorrection; + } mLumiScale = -1.f; } else if ((mLumiScaleMode == LumiScaleMode::DerivativeMap) || (mLumiScaleMode == LumiScaleMode::DerivativeMapMC)) { mLumiScale = mMeanLumiRef ? (mInstLumi - mMeanLumi) / mMeanLumiRef : 0.f; @@ -54,7 +58,40 @@ void CorrectionMapsHelper::updateLumiScale(bool report) //________________________________________________________ void CorrectionMapsHelper::reportScaling() { - LOGP(info, "Map scaling update: LumiScaleType={} instLumi(CTP)={} instLumi(scaling)={} meanLumiRef={}, meanLumi={} -> LumiScale={} lumiScaleMode={}, M-Shape map valid: {}, M-Shape default: {}", - mLumiScaleType == LumiScaleType::NoScaling ? "NoScaling" : (mLumiScaleType == LumiScaleType::CTPLumi ? "LumiCTP" : "TPCScaler"), getInstLumiCTP(), getInstLumi(), getMeanLumiRef(), getMeanLumi(), getLumiScale(), - mLumiScaleMode == LumiScaleMode::Linear ? "Linear" : "Derivative", (mCorrMapMShape != nullptr), isCorrMapMShapeDummy()); + auto lumiTypeName = [](LumiScaleType t) { + switch (t) { + case LumiScaleType::NoScaling: + return "NoScaling"; + case LumiScaleType::CTPLumi: + return "CTPLumi"; + case LumiScaleType::TPCScaler: + return "TPCScaler"; + default: + return "Unknown"; + } + }; + + const bool mshapeValid = (mCorrMapMShape != nullptr) && !isCorrMapMShapeDummy(); + + if (mLumiScaleMode == LumiScaleMode::NoCorrection) { + LOGP(info, "Map scaling update: mode=NoCorrection (corrections disabled, dummy map in use)"); + } else if (mLumiScaleMode == LumiScaleMode::StaticMapOnly) { + LOGP(info, "Map scaling update: mode=StaticMapOnly (static reference map, no lumi scaling), M-Shape correction: {}", mshapeValid ? "applied" : "not applied"); + } else { + auto lumiModeName = [](LumiScaleMode m) { + switch (m) { + case LumiScaleMode::Linear: + return "Linear"; + case LumiScaleMode::DerivativeMap: + return "DerivativeMap"; + case LumiScaleMode::DerivativeMapMC: + return "DerivativeMapMC"; + default: + return "Unknown"; + } + }; + LOGP(info, "Map scaling update: LumiScaleType={} instLumi(CTP)={} instLumi(scaling)={} meanLumiRef={} meanLumi={} -> LumiScale={} lumiScaleMode={}, M-Shape correction: {}", + lumiTypeName(mLumiScaleType), getInstLumiCTP(), getInstLumi(), getMeanLumiRef(), getMeanLumi(), getLumiScale(), + lumiModeName(mLumiScaleMode), mshapeValid ? "applied" : "not applied"); + } } diff --git a/GPU/TPCFastTransformation/CorrectionMapsTypes.h b/GPU/TPCFastTransformation/CorrectionMapsTypes.h index e239b668ab751..092a2927ebe3e 100644 --- a/GPU/TPCFastTransformation/CorrectionMapsTypes.h +++ b/GPU/TPCFastTransformation/CorrectionMapsTypes.h @@ -22,14 +22,18 @@ enum class LumiScaleType : int { Unset = -1, ///< init value NoScaling = 0, ///< no scaling, use map as is CTPLumi = 1, ///< use CTP luminosity for scaling - TPCScaler = 2 ///< use TPC scaler for scaling + TPCScaler = 2, ///< use TPC scaler for scaling + Count ///< sentinel - keep last }; enum class LumiScaleMode : int { - Unset = -1, ///< init value - Linear = 0, ///< map(lumi) = (mean_map - referenceMap) * lumiScale + referenceMap - DerivativeMap = 1, ///< map(lumi) = mean_map + lumiScale * (derivativeMap) where derivativeMap = (mean_map_A - mean_map_B) - DerivativeMapMC = 2 ///< same DerivativeMap, but for MC + Unset = -1, ///< init value + Linear = 0, ///< map(lumi) = (mean_map - referenceMap) * lumiScale + referenceMap + DerivativeMap = 1, ///< map(lumi) = mean_map + lumiScale * (derivativeMap) where derivativeMap = (mean_map_A - mean_map_B) + DerivativeMapMC = 2, ///< same DerivativeMap, but for MC + NoCorrection = 3, ///< no corrections at all + StaticMapOnly = 4, ///< use only static map instead of main map + Count ///< sentinel - keep last }; struct CorrectionMapsGloOpts { From aa96c1a9fd301f775c2ab3a04d49fe890c03782d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Jacazio?= Date: Thu, 21 May 2026 12:00:34 +0200 Subject: [PATCH 089/102] IOTOF: align geometry to specs (#15414) - add macro to draw geometry - streamline setup of IOTOF active layers --- .../ALICE3/IOTOF/macros/CMakeLists.txt | 3 + .../ALICE3/IOTOF/macros/drawTOFGeometry.C | 90 +++++++++++++++++++ .../ALICE3/IOTOF/simulation/src/Detector.cxx | 29 ++++-- .../ALICE3/IOTOF/simulation/src/Layer.cxx | 53 ++++++++--- 4 files changed, 156 insertions(+), 19 deletions(-) create mode 100644 Detectors/Upgrades/ALICE3/IOTOF/macros/drawTOFGeometry.C diff --git a/Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt index b2f1857186c0b..41b800ed114b4 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt @@ -11,3 +11,6 @@ o2_add_test_root_macro(defineIOTOFGeo.C LABELS alice3) + +o2_add_test_root_macro(drawTOFGeometry.C + LABELS alice3) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/macros/drawTOFGeometry.C b/Detectors/Upgrades/ALICE3/IOTOF/macros/drawTOFGeometry.C new file mode 100644 index 0000000000000..4e58fb54fbf6e --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/macros/drawTOFGeometry.C @@ -0,0 +1,90 @@ +// 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 "IOTOFBase/GeometryTGeo.h" +#include "IOTOFSimulation/Layer.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +void ensureMedium(const char* name, int id, double a, double z, double density) +{ + if (!gGeoManager->GetMedium(name)) { + auto* mat = new TGeoMaterial(name, a, z, density); + new TGeoMedium(name, id, mat); + } +} + +void prepareMinimalMedia() +{ + ensureMedium("VACUUM$", 0, 1., 1., 1.e-16); + ensureMedium("TF3_AIR$", 1, 14.61, 7.3, 1.20479e-3); + ensureMedium("TF3_SILICON$", 3, 28.086, 14., 2.33); +} +} // namespace + +void drawTOFGeometry(double x2x0 = 0.02, + double sensorThickness = 0.005, + bool checkOverlaps = true, + double overlapToleranceCm = 0.01) +{ + gStyle->SetOptStat(0); + + if (gGeoManager) { + delete gGeoManager; + } + + auto* geo = new TGeoManager("IOTOFGeomFromLayer", "Geometry built from Layer.h classes"); + prepareMinimalMedia(); + + auto* top = geo->MakeBox("TOP", geo->GetMedium("VACUUM$"), 1200., 1200., 1200.); + geo->SetTopVolume(top); + + auto* mother = new TGeoVolumeAssembly("IOTOFMacroVol"); + top->AddNode(mother, 1, new TGeoTranslation(0., 0., 0.)); + + // Build using the same classes and createLayer() used by detector geometry code. + o2::iotof::ITOFLayer itof(o2::iotof::GeometryTGeo::getITOFLayerPattern(), + 21.f, 0.f, 129.f, 0.f, x2x0, + o2::iotof::Layer::kBarrelSegmented, + 24, 5.42, 3.0, 10, sensorThickness); + + o2::iotof::OTOFLayer otof(o2::iotof::GeometryTGeo::getOTOFLayerPattern(), + 92.f, 0.f, 680.f, 0.f, x2x0, + o2::iotof::Layer::kBarrelSegmented, + 62, 9.74, 5.0, 54, sensorThickness); + + itof.createLayer(mother); + otof.createLayer(mother); + + geo->CloseGeometry(); + + std::cout << "Built geometry from Layer.h classes with x2x0=" << x2x0 + << " and sensorThickness=" << sensorThickness << " cm\n"; + std::cout << "ITOF sensitive volumes: " << o2::iotof::ITOFLayer::mRegister.size() << "\n"; + std::cout << "OTOF sensitive volumes: " << o2::iotof::OTOFLayer::mRegister.size() << "\n"; + + if (checkOverlaps) { + std::cout << "Checking overlaps with tolerance=" << overlapToleranceCm << " cm\n"; + geo->CheckOverlaps(overlapToleranceCm); + geo->PrintOverlaps(); + } + + top->Draw("ogl"); +} diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx index bed8cbfd6dfac..ab9a68bd401ec 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx @@ -200,28 +200,47 @@ void Detector::defineSensitiveVolumes() TGeoManager* geoManager = gGeoManager; TGeoVolume* v; - // The names of the IOTOF sensitive volumes have the format: IOTOFLayer(0...mLayers.size()-1) auto& iotofPars = IOTOFBaseParam::Instance(); - if (iotofPars.enableInnerTOF) { + const bool itof = iotofPars.enableInnerTOF; + const bool otof = iotofPars.enableOuterTOF; + bool ftof = iotofPars.enableForwardTOF; + bool btof = iotofPars.enableBackwardTOF; + const std::string pattern = iotofPars.detectorPattern; + if (pattern == "") { + LOG(info) << "Default pattern"; + } else if (pattern == "v3b") { + ftof = false; + btof = false; + } else if (pattern == "v3b1a") { + } else if (pattern == "v3b1b") { + } else if (pattern == "v3b2a") { + } else if (pattern == "v3b2b") { + } else if (pattern == "v3b3") { + } else { + LOG(fatal) << "IOTOF layer pattern " << pattern << " not recognized, exiting"; + } + + // The names of the IOTOF sensitive volumes have the format: IOTOFLayer(0...mLayers.size()-1) + if (itof) { for (const std::string& itofSensor : ITOFLayer::mRegister) { v = geoManager->GetVolume(itofSensor.c_str()); LOGP(info, "Adding IOTOF Sensitive Volume {}", v->GetName()); AddSensitiveVolume(v); } } - if (iotofPars.enableOuterTOF) { + if (otof) { for (const std::string& otofSensor : OTOFLayer::mRegister) { v = geoManager->GetVolume(otofSensor.c_str()); LOGP(info, "Adding IOTOF Sensitive Volume {}", v->GetName()); AddSensitiveVolume(v); } } - if (iotofPars.enableForwardTOF) { + if (ftof) { v = geoManager->GetVolume(GeometryTGeo::getFTOFSensorPattern()); LOGP(info, "Adding IOTOF Sensitive Volume {}", v->GetName()); AddSensitiveVolume(v); } - if (iotofPars.enableBackwardTOF) { + if (btof) { v = geoManager->GetVolume(GeometryTGeo::getBTOFSensorPattern()); LOGP(info, "Adding IOTOF Sensitive Volume {}", v->GetName()); AddSensitiveVolume(v); diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx index 627fb599ff8ae..f2e42e1bce172 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx @@ -296,12 +296,24 @@ void OTOFLayer::createLayer(TGeoVolume* motherVolume) case kBarrelSegmented: { // First we create the volume for the whole layer, which will be used as mother volume for the segments const double avgRadius = 0.5 * (mInnerRadius + mOuterRadius); - const double staveSizeX = mStaves.second; // cm - const double staveSizeY = mOuterRadius - mInnerRadius; // cm - const double staveSizeZ = mZLength; // cm - const double deltaForTilt = 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); // we increase the size of the layer to account for the tilt of the staves - const double radiusMax = std::sqrt(avgRadius * avgRadius + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY + avgRadius * 2. * deltaForTilt); // we increase the outer radius to account for the tilt of the staves - const double radiusMin = std::sqrt(avgRadius * avgRadius + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY - avgRadius * 2. * deltaForTilt); // we decrease the inner radius to account for the tilt of the staves + const double staveSizeX = mStaves.second; // cm, tangential stave size + const double staveSizeY = mOuterRadius - mInnerRadius; // cm, radial stave size + const double staveSizeZ = mZLength; // cm + + // Build the mother layer tube from the exact inscribed/outscribed radii of a tilted stave rectangle. + const double alpha = mTiltAngle * TMath::DegToRad(); + const double u0 = -avgRadius * std::cos(alpha); + const double v0 = avgRadius * std::sin(alpha); + const double uClamped = std::max(-0.5 * staveSizeY, std::min(0.5 * staveSizeY, u0)); + const double vClamped = std::max(-0.5 * staveSizeX, std::min(0.5 * staveSizeX, v0)); + const double radiusMin = std::hypot(uClamped - u0, vClamped - v0); + + const double uCorners[4] = {-0.5 * staveSizeY, 0.5 * staveSizeY, 0.5 * staveSizeY, -0.5 * staveSizeY}; + const double vCorners[4] = {-0.5 * staveSizeX, -0.5 * staveSizeX, 0.5 * staveSizeX, 0.5 * staveSizeX}; + double radiusMax = 0.0; + for (int i = 0; i < 4; ++i) { + radiusMax = std::max(radiusMax, std::hypot(uCorners[i] - u0, vCorners[i] - v0)); + } TGeoTube* layer = new TGeoTube(radiusMin, radiusMax, mZLength / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); setLayerStyle(layerVol); @@ -312,10 +324,21 @@ void OTOFLayer::createLayer(TGeoVolume* motherVolume) setStaveStyle(staveVol); // Now we create the volume for a single module (sensor + chip) - const int modulesPerStaveX = 1; // we assume that each stave is divided in 2 modules along the x direction - const double moduleSizeX = staveSizeX / modulesPerStaveX; // cm - const double moduleSizeY = staveSizeY; // cm - const double moduleSizeZ = staveSizeZ / mModulesPerStave; // cm + // oTOF V2 is a 2xN matrix of modules per stave with overlap along z. + const int modulesPerStaveX = 2; + if (mModulesPerStave % modulesPerStaveX != 0) { + LOG(fatal) << "Invalid oTOF module layout: total modules per stave " << mModulesPerStave + << " is not divisible by modulesPerStaveX=" << modulesPerStaveX; + } + const int modulesPerStaveZ = mModulesPerStave / modulesPerStaveX; + const double moduleOverlapZ = 0.7; // cm, 7 mm longitudinal overlap from oTOF V2 specs + const double moduleSizeX = staveSizeX / modulesPerStaveX; + const double moduleSizeY = staveSizeY; + const double moduleSizeZ = (staveSizeZ + (modulesPerStaveZ - 1) * moduleOverlapZ) / modulesPerStaveZ; + const double modulePitchZ = moduleSizeZ - moduleOverlapZ; + if (modulePitchZ <= 0.0) { + LOG(fatal) << "Invalid oTOF module overlap " << moduleOverlapZ << " cm for module size " << moduleSizeZ << " cm"; + } TGeoBBox* module = new TGeoBBox(moduleSizeX * 0.5, moduleSizeY * 0.5, moduleSizeZ * 0.5); TGeoVolume* moduleVol = new TGeoVolume(moduleName, module, medAir); setModuleStyle(moduleVol); @@ -363,10 +386,12 @@ void OTOFLayer::createLayer(TGeoVolume* motherVolume) // Now we build a stave from modules for (int i = 0; i < modulesPerStaveX; ++i) { - for (int j = 0; j < mModulesPerStave; ++j) { - LOGP(info, "oTOF: Creating module {}/{} for stave {}/{}", i + 1, modulesPerStaveX, j + 1, mModulesPerStave); - auto* translation = new TGeoTranslation((i + 0.5) * moduleSizeX - 0.5 * staveSizeX, 0, (j + 0.5) * moduleSizeZ - 0.5 * staveSizeZ); - staveVol->AddNode(moduleVol, 1 + i * mModulesPerStave + j, translation); + for (int j = 0; j < modulesPerStaveZ; ++j) { + LOGP(info, "oTOF: Creating module {}/{} for stave {}/{}", i + 1, modulesPerStaveX, j + 1, modulesPerStaveZ); + const double tx = (i + 0.5) * moduleSizeX - 0.5 * staveSizeX; + const double tz = -0.5 * staveSizeZ + 0.5 * moduleSizeZ + j * modulePitchZ; + auto* translation = new TGeoTranslation(tx, 0, tz); + staveVol->AddNode(moduleVol, 1 + i * modulesPerStaveZ + j, translation); } } From f017263d5da7a08e808cbb061139195cd2bf4a31 Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Thu, 21 May 2026 12:18:14 +0200 Subject: [PATCH 090/102] ALICE3: factor GPU tracking into dynamically loaded CUDA/HIP backend (#15420) --- .../reconstruction/CMakeLists.txt | 33 +-- ...lAllocator.cxx => GPUExternalAllocator.cu} | 37 +-- .../workflow/CMakeLists.txt | 34 +++ .../TrackerSpec.h | 10 + .../TrackerSpecImpl.h | 226 ++++++++++++++++ .../workflow/src/TrackerSpec.cxx | 250 +++--------------- .../workflow/src/TrackerSpecGPU.cxx | 28 ++ 7 files changed, 342 insertions(+), 276 deletions(-) rename Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/{GPUExternalAllocator.cxx => GPUExternalAllocator.cu} (81%) create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h create mode 100644 Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpecGPU.cxx diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt index 8805c1885b079..1dfcb7a22f725 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt @@ -13,24 +13,9 @@ 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 @@ -48,26 +33,10 @@ o2_add_library(ALICE3GlobalReconstruction O2::TRKReconstruction O2::TRKSimulation nlohmann_json::nlohmann_json - ${alice3GlobalRecoGpuTargets} ${actsTarget} PRIVATE_LINK_LIBRARIES O2::Steer - 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() + TBB::tbb) if(Acts_FOUND) target_compile_definitions(${targetName} PUBLIC O2_WITH_ACTS) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cu similarity index 81% rename from Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cxx rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cu index df2a2c30b037a..c7b5f1cee50f5 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cu @@ -9,11 +9,9 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#if defined(TRK_HAS_CUDA_TRACKING) +#define GPUCA_GPUCODE_HOSTONLY + #include -#elif defined(TRK_HAS_HIP_TRACKING) -#include -#endif #include "ALICE3GlobalReconstruction/GPUExternalAllocator.h" @@ -23,21 +21,12 @@ 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 @@ -147,26 +136,14 @@ void GPUExternalAllocator::releaseAll() 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; } @@ -176,21 +153,11 @@ void GPUExternalAllocator::freeAllocation(void* ptr, AllocationSpace space) 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) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt index be6add9c03483..6a4994e11467b 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt @@ -24,8 +24,42 @@ o2_add_library(ALICE3GlobalReconstructionWorkflow O2::TRKBase O2::TRKSimulation O2::ALICE3GlobalReconstruction + O2::CommonUtils nlohmann_json::nlohmann_json) +if(CUDA_ENABLED OR HIP_ENABLED) + target_compile_definitions(${targetName} PUBLIC TRK_HAS_GPU_TRACKING) +endif() + +if(CUDA_ENABLED) + find_package(CUDAToolkit REQUIRED) + target_compile_definitions(${targetName} PUBLIC TRK_HAS_CUDA_TRACKING) + o2_add_library(ALICE3GlobalReconstructionWorkflowCUDA + TARGETVARNAME cudaTargetName + SOURCES src/TrackerSpecGPU.cxx + ../reconstruction/src/TimeFrameGPU.cxx + ../reconstruction/src/GPUExternalAllocator.cu + PUBLIC_LINK_LIBRARIES + O2::ALICE3GlobalReconstructionWorkflow + O2::ITStrackingCUDA + PRIVATE_LINK_LIBRARIES + CUDA::cudart) + target_include_directories(${cudaTargetName} PRIVATE ${CUDAToolkit_INCLUDE_DIRS}) +endif() + +if(HIP_ENABLED) + target_compile_definitions(${targetName} PUBLIC TRK_HAS_HIP_TRACKING) + o2_add_hipified_library(ALICE3GlobalReconstructionWorkflowHIP + SOURCES src/TrackerSpecGPU.cxx + ../reconstruction/src/TimeFrameGPU.cxx + ../reconstruction/src/GPUExternalAllocator.cu + PUBLIC_LINK_LIBRARIES + O2::ALICE3GlobalReconstructionWorkflow + O2::ITStrackingHIP + PRIVATE_LINK_LIBRARIES + hip::host) +endif() + o2_add_executable(reco-workflow SOURCES src/alice3-global-reconstruction-workflow.cxx COMPONENT_NAME alice3-global-reconstruction diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h index 006bb4cbf5260..c1e7e051fb3f1 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h @@ -32,6 +32,10 @@ #include +#include +#include +#include + namespace o2::trk { class TrackerDPL : public framework::Task @@ -48,10 +52,15 @@ class TrackerDPL : public framework::Task void endOfStream(framework::EndOfStreamContext& ec) final; // void finaliseCCDB(framework::ConcreteDataMatcher& matcher, void* obj) final; void stop() final; + template + void runTracking(framework::ProcessingContext& pc, TimeFrameT& timeFrame, TrackerTraitsT& trackerTraits); + const std::shared_ptr& getGPUAllocator() const noexcept { return mGPUAllocator; } + void setGPUAllocator(std::shared_ptr allocator) { mGPUAllocator = std::move(allocator); } private: void updateTimeDependentParams(framework::ProcessingContext& pc); std::vector createTrackingParamsFromConfig(); + void runGPUTracking(framework::ProcessingContext& pc); // std::unique_ptr mRecChain = nullptr; // std::unique_ptr mChainITS = nullptr; // std::shared_ptr mGGCCDBRequest; @@ -61,6 +70,7 @@ class TrackerDPL : public framework::Task std::shared_ptr mMemoryPool; std::shared_ptr mGPUAllocator; std::shared_ptr mTaskArena; + std::vector mTrackingParams; nlohmann::json mHitRecoConfig; nlohmann::json mClusterRecoConfig; TStopwatch mTimer; diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h new file mode 100644 index 0000000000000..f6221e485f369 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h @@ -0,0 +1,226 @@ +// 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_TRK_TRACKERSPECIMPL_H +#define O2_TRK_TRACKERSPECIMPL_H + +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" + +#include "CommonDataFormat/IRFrame.h" +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" +#include "DetectorsBase/GeometryManager.h" +#include "Field/MagFieldParam.h" +#include "Field/MagneticField.h" +#include "Framework/ControlService.h" +#include "ITStracking/Tracker.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCEventHeader.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "TRKBase/GeometryTGeo.h" +#include "TRKSimulation/Hit.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace o2::trk +{ + +template +void TrackerDPL::runTracking(framework::ProcessingContext& pc, TimeFrameT& timeFrame, TrackerTraitsT& trackerTraits) +{ + o2::its::Tracker<11> itsTracker(&trackerTraits); + timeFrame.setMemoryPool(mMemoryPool); + trackerTraits.setMemoryPool(mMemoryPool); + trackerTraits.setNThreads(mTaskArena->max_concurrency(), mTaskArena); + trackerTraits.adoptTimeFrame(static_cast*>(&timeFrame)); + itsTracker.adoptTimeFrame(timeFrame); + trackerTraits.updateTrackingParameters(mTrackingParams); + timeFrame.initTrackerTopologies(mTrackingParams, 11); + + 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(); + } + } + + timeFrame.deriveAndInitTiming(layerROFs); + + const float yPlaneMLOT = 0.0010f; + nRofs = timeFrame.loadROFrameData(layerROFs, layerClusters, layerPatterns, mIsMC ? &layerLabels : nullptr, yPlaneMLOT); + timeFrame.addTruthSeedingVertices(); + } + + const auto trackingLoopStart = std::chrono::steady_clock::now(); + for (size_t iter{0}; iter < mTrackingParams.size(); ++iter) { + LOGP(info, "{}", mTrackingParams[iter].asString()); + trackerTraits.initialiseTimeFrame(iter); + trackerTraits.computeLayerTracklets(iter, -1); + LOGP(info, "Number of tracklets in iteration {}: {}", iter, timeFrame.getNumberOfTracklets()); + trackerTraits.computeLayerCells(iter); + LOGP(info, "Number of cells in iteration {}: {}", iter, timeFrame.getNumberOfCells()); + trackerTraits.findCellsNeighbours(iter); + LOGP(info, "Number of cell neighbours in iteration {}: {}", iter, timeFrame.getNumberOfNeighbours()); + 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); + + if (mIsMC) { + itsTracker.computeTracksMClabels(); + } + + const auto& tracks = timeFrame.getTracks(); + const auto& labels = timeFrame.getTracksLabel(); + std::vector allTracks(tracks.begin(), tracks.end()); + std::vector allLabels; + + int totalTracks = allTracks.size(); + int goodTracks = 0; + int fakeTracks = 0; + + if (mIsMC) { + allLabels.assign(labels.begin(), labels.end()); + for (const auto& label : allLabels) { + if (label.isFake()) { + ++fakeTracks; + } else { + ++goodTracks; + } + } + } + + LOGP(info, "=== Tracking Summary ==="); + LOGP(info, "Total tracks reconstructed: {}", totalTracks); + 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); + + 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, "TRK pushed {} tracks in {} ROFs and {} IR frames{}", + allTracks.size(), allTrackROFs.size(), irFrames.size(), + mIsMC ? " (with MC labels)" : ""); + + timeFrame.wipe(); +} + +} // namespace o2::trk + +#endif // O2_TRK_TRACKERSPECIMPL_H diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index 9fb2899ab3ef5..6f9f5561a5ef6 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -17,6 +17,7 @@ #include #include +#include "CommonUtils/DLLoaderBase.h" #include "CommonDataFormat/IRFrame.h" #include "DataFormatsTRK/Cluster.h" #include "DataFormatsTRK/ROFRecord.h" @@ -36,12 +37,8 @@ #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 "ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h" #include #ifdef O2_WITH_ACTS @@ -58,6 +55,18 @@ namespace trk { using Vertex = o2::dataformats::Vertex>; +namespace +{ +class ALICE3TrackingBackendLoader : public o2::utils::DLLoaderBase +{ + O2DLLoaderDef(ALICE3TrackingBackendLoader) +}; + +O2DLLoaderImpl(ALICE3TrackingBackendLoader) + + constexpr const char* kGPUBackendFunction = "runALICE3GPUTracking"; +} // namespace + TrackerDPL::TrackerDPL(std::shared_ptr gr, bool isMC, const std::string& hitRecoConfigFileName, @@ -249,220 +258,20 @@ void TrackerDPL::run(ProcessingContext& pc) mTaskArena = std::make_shared(1); /// TODO: make it configurable } - auto trackingParams = createTrackingParamsFromConfig(); + mTrackingParams = createTrackingParamsFromConfig(); auto cput = mTimer.CpuTime(); auto realt = mTimer.RealTime(); mTimer.Start(false); 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 - - auto runTracking = [&](auto& timeFrame, auto& trackerTraits) { - o2::its::Tracker<11> itsTracker(&trackerTraits); - timeFrame.setMemoryPool(mMemoryPool); - 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(); - } - } - - timeFrame.deriveAndInitTiming(layerROFs); - - const float yPlaneMLOT = 0.0010f; - nRofs = timeFrame.loadROFrameData(layerROFs, layerClusters, layerPatterns, mIsMC ? &layerLabels : nullptr, yPlaneMLOT); - timeFrame.addTruthSeedingVertices(); - } - - const auto trackingLoopStart = std::chrono::steady_clock::now(); - for (size_t iter{0}; iter < trackingParams.size(); ++iter) { - LOGP(info, "{}", trackingParams[iter].asString()); - trackerTraits.initialiseTimeFrame(iter); - trackerTraits.computeLayerTracklets(iter, -1); - LOGP(info, "Number of tracklets in iteration {}: {}", iter, timeFrame.getNumberOfTracklets()); - trackerTraits.computeLayerCells(iter); - LOGP(info, "Number of cells in iteration {}: {}", iter, timeFrame.getNumberOfCells()); - trackerTraits.findCellsNeighbours(iter); - LOGP(info, "Number of cell neighbours in iteration {}: {}", iter, timeFrame.getNumberOfNeighbours()); - 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); - - if (mIsMC) { - itsTracker.computeTracksMClabels(); - } - - const auto& tracks = timeFrame.getTracks(); - const auto& labels = timeFrame.getTracksLabel(); - std::vector allTracks(tracks.begin(), tracks.end()); - std::vector allLabels; - - int totalTracks = allTracks.size(); - int goodTracks = 0; - int fakeTracks = 0; - - if (mIsMC) { - allLabels.assign(labels.begin(), labels.end()); - for (const auto& label : allLabels) { - if (label.isFake()) { - ++fakeTracks; - } else { - ++goodTracks; - } - } - } - - LOGP(info, "=== Tracking Summary ==="); - LOGP(info, "Total tracks reconstructed: {}", totalTracks); - 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); - - 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, "TRK pushed {} tracks in {} ROFs and {} IR frames{}", - allTracks.size(), allTrackROFs.size(), irFrames.size(), - mIsMC ? " (with MC labels)" : ""); - - timeFrame.wipe(); - }; - -#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 - { + runGPUTracking(pc); + } else { o2::trk::TimeFrame<11> timeFrame; o2::its::TrackerTraits<11> itsTrackerTraits; - runTracking(timeFrame, itsTrackerTraits); + runTracking(pc, timeFrame, itsTrackerTraits); } pc.services().get().endOfStream(); @@ -472,6 +281,29 @@ 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::runGPUTracking(ProcessingContext& pc) +{ + auto& loader = ALICE3TrackingBackendLoader::Instance(); + switch (mDeviceType) { + case o2::gpu::gpudatatypes::DeviceType::CUDA: +#ifdef TRK_HAS_CUDA_TRACKING + loader.executeFunctionAlias("O2ALICE3GlobalReconstructionWorkflowCUDA", kGPUBackendFunction, this, &pc); + return; +#else + LOGP(fatal, "CUDA TRK GPU tracking was requested but this build has no CUDA TRK GPU tracking backend"); +#endif + case o2::gpu::gpudatatypes::DeviceType::HIP: +#ifdef TRK_HAS_HIP_TRACKING + loader.executeFunctionAlias("O2ALICE3GlobalReconstructionWorkflowHIP", kGPUBackendFunction, this, &pc); + return; +#else + LOGP(fatal, "HIP TRK GPU tracking was requested but this build has no HIP TRK GPU tracking backend"); +#endif + default: + LOGP(fatal, "Unsupported TRK GPU device type {}", static_cast(mDeviceType)); + } +} + 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); diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpecGPU.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpecGPU.cxx new file mode 100644 index 0000000000000..ea98ab3f852e5 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpecGPU.cxx @@ -0,0 +1,28 @@ +// 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/GPUExternalAllocator.h" +#include "ALICE3GlobalReconstruction/TimeFrameGPU.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h" +#include "ITStrackingGPU/TrackerTraitsGPU.h" + +extern "C" int runALICE3GPUTracking(o2::trk::TrackerDPL* tracker, o2::framework::ProcessingContext* pc) +{ + o2::trk::TimeFrameGPU<11> timeFrame; + o2::its::TrackerTraitsGPU<11> itsTrackerTraits; + if (!tracker->getGPUAllocator()) { + tracker->setGPUAllocator(std::make_shared()); + } + timeFrame.setFrameworkAllocator(tracker->getGPUAllocator().get()); + tracker->runTracking(*pc, timeFrame, itsTrackerTraits); + return 0; +} From c7d5958f58ac9ac4295c8e79f9578f74ddd72bf7 Mon Sep 17 00:00:00 2001 From: Fabrizio Chinu <91954233+fchinu@users.noreply.github.com> Date: Thu, 21 May 2026 13:36:01 +0200 Subject: [PATCH 091/102] ITS: add selections on tracks sharing clusters (#15406) * ITS: add selections on tracks sharing clusters * Refactor selection function, return simple int instead of reference to int * Use isPhiDifferenceBelow, avoid variable narrowing * Improve memory management for selection of tracks with shared clusters --- .../ITS/include/DataFormatsITS/TrackITS.h | 8 ++- .../tracking/GPU/cuda/TrackerTraitsGPU.cxx | 4 +- .../include/ITStracking/Configuration.h | 7 ++- .../include/ITStracking/TrackerTraits.h | 4 +- .../include/ITStracking/TrackingConfigParam.h | 5 ++ .../ITSMFT/ITS/tracking/src/Configuration.cxx | 3 + .../ITSMFT/ITS/tracking/src/TrackerTraits.cxx | 61 ++++++++++++------- 7 files changed, 63 insertions(+), 29 deletions(-) diff --git a/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h index a06395e76afff..89f6416c6e177 100644 --- a/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h +++ b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h @@ -192,7 +192,13 @@ class TrackITSExt : public TrackITS getClusterRefs().setEntries(ncl); } - GPUhdi() const int& getClusterIndex(int lr) const { return mIndex[lr]; } + GPUhdi() const int getClusterIndex(int lr) const { return mIndex[lr]; } + + GPUh() const int getFirstLayerClusterIndex() const + { + int firstLayer = getFirstClusterLayer(); + return getClusterIndex(firstLayer); + } GPUhdi() void setExternalClusterIndex(int layer, int idx, bool newCluster = false) { diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx index f1812c9f6f764..32c46e2ea55d2 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx @@ -387,10 +387,10 @@ void TrackerTraitsGPU::findRoads(const int iteration) mTimeFrameGPU->downloadTrackITSExtDevice(); auto& tracks = mTimeFrameGPU->getTrackITSExt(); - this->acceptTracks(iteration, tracks, firstClusters, sharedFirstClusters); + this->acceptTracks(iteration, tracks, firstClusters); mTimeFrameGPU->loadUsedClustersDevice(); } - this->markTracks(iteration, sharedFirstClusters); + this->markTracks(iteration); // wipe the artefact memory mTimeFrameGPU->popMemoryStack(iteration); }; diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h index c939f39532fdb..852c5ecd24633 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h @@ -70,7 +70,6 @@ struct TrackingParameters { float DiamondCov[6] = {25.e-6f, 0.f, 0.f, 25.e-6f, 0.f, 36.f}; /// General parameters - bool AllowSharingFirstCluster = false; int ClusterSharing = 0; int MinTrackLength = 7; int MaxHoles = 0; @@ -98,6 +97,12 @@ struct TrackingParameters { bool PrintMemory = false; // print allocator usage in epilog report size_t MaxMemory = std::numeric_limits::max(); bool DropTFUponFailure = false; + + // Selections on tracks sharing clusters + bool AllowSharingFirstCluster = false; + float SharedClusterMaxDeltaPhi = 0.05f; // For tracks sharing clusters, maximum allowed delta phi at the cluster position + float SharedClusterMaxDeltaEta = 0.03f; // For tracks sharing clusters, maximum allowed delta eta at the cluster position + bool SharedClusterOppositeSign = false; // For tracks sharing clusters, require opposite sign of the tracklets }; struct VertexingParameters { diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h index 647403bb6b548..f536e86fe95d5 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h @@ -55,8 +55,8 @@ class TrackerTraits template void processNeighbours(int iteration, int defaultCellTopologyId, int iLevel, const bounded_vector& currentCellSeed, const bounded_vector& currentCellId, const bounded_vector& currentCellTopologyId, bounded_vector& updatedCellSeed, bounded_vector& updatedCellId, bounded_vector& updatedCellTopologyId); - void acceptTracks(int iteration, bounded_vector& tracks, bounded_vector>& firstClusters, bounded_vector>& sharedFirstClusters); - void markTracks(int iteration, bounded_vector>& sharedFirstClusters); + void acceptTracks(int iteration, bounded_vector& tracks, bounded_vector>& firstClusters); + void markTracks(int iteration); void updateTrackingParameters(const std::vector& trkPars) { diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h index 21b4f928d5b73..69aa3c5fdaf06 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h @@ -102,7 +102,12 @@ struct TrackerParamConfig : public o2::conf::ConfigurableParamHelper::max(); bool dropTFUponFailure = false; bool fataliseUponFailure = true; // granular management of the fatalisation in async mode + + // Selections on tracks sharing clusters bool allowSharingFirstCluster = false; // allow first cluster sharing among tracks + float sharedClusterMaxDeltaPhi = 0.05f; // Maximum allowed delta phi at the cluster position + float sharedClusterMaxDeltaEta = 0.03f; // Maximum allowed delta eta at the cluster position + bool sharedClusterOppositeSign = false; // Require opposite sign of the tracklets O2ParamDef(TrackerParamConfig, "ITSCATrackerParam"); }; diff --git a/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx b/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx index 0087da0a85ac2..0bf383c996a68 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx @@ -204,6 +204,9 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode p.SaveTimeBenchmarks = tc.saveTimeBenchmarks; p.FataliseUponFailure = tc.fataliseUponFailure; p.AllowSharingFirstCluster = tc.allowSharingFirstCluster; + p.SharedClusterMaxDeltaPhi = tc.sharedClusterMaxDeltaPhi; + p.SharedClusterMaxDeltaEta = tc.sharedClusterMaxDeltaEta; + p.SharedClusterOppositeSign = tc.sharedClusterOppositeSign; const auto iter = &p - trackParams.data(); if (iter < constants::MaxIter) { p.MaxHoles = tc.maxHolesIter[iter]; diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx index 9fef067559e8a..3432b60162002 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx @@ -661,9 +661,7 @@ template void TrackerTraits::findRoads(const int iteration) { bounded_vector> firstClusters(mTrkParams[iteration].NLayers, bounded_vector(mMemoryPool.get()), mMemoryPool.get()); - bounded_vector> sharedFirstClusters(mTrkParams[iteration].NLayers, bounded_vector(mMemoryPool.get()), mMemoryPool.get()); firstClusters.resize(mTrkParams[iteration].NLayers); - sharedFirstClusters.resize(mTrkParams[iteration].NLayers); const auto propagator = o2::base::Propagator::Instance(); const TrackingFrameInfo* tfInfos[NLayers]{}; const Cluster* unsortedClusters[NLayers]{}; @@ -787,13 +785,13 @@ void TrackerTraits::findRoads(const int iteration) return track::isBetter(a, b); }); - acceptTracks(iteration, tracks, firstClusters, sharedFirstClusters); + acceptTracks(iteration, tracks, firstClusters); } - markTracks(iteration, sharedFirstClusters); + markTracks(iteration); } template -void TrackerTraits::acceptTracks(int iteration, bounded_vector& tracks, bounded_vector>& firstClusters, bounded_vector>& sharedFirstClusters) +void TrackerTraits::acceptTracks(int iteration, bounded_vector& tracks, bounded_vector>& firstClusters) { auto& trks = mTimeFrame->getTracks(); trks.reserve(trks.size() + tracks.size()); @@ -860,34 +858,51 @@ void TrackerTraits::acceptTracks(int iteration, bounded_vector -void TrackerTraits::markTracks(int iteration, bounded_vector>& sharedFirstClusters) +void TrackerTraits::markTracks(int iteration) { if (mTrkParams[iteration].AllowSharingFirstCluster) { /// Now we have to set the shared cluster flag - for (int iLayer{0}; iLayer < mTrkParams[iteration].NLayers; ++iLayer) { - std::sort(sharedFirstClusters[iLayer].begin(), sharedFirstClusters[iLayer].end()); - } + auto& tracks = mTimeFrame->getTracks(); - for (auto& track : mTimeFrame->getTracks()) { - int firstLayer{mTrkParams[iteration].NLayers}, firstCluster{constants::UnusedIndex}; - for (int iLayer{0}; iLayer < mTrkParams[iteration].NLayers; ++iLayer) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - firstLayer = iLayer; - firstCluster = track.getClusterIndex(iLayer); - break; + bounded_vector fclusSort(tracks.size(), mMemoryPool.get()); + std::iota(fclusSort.begin(), fclusSort.end(), 0); + std::sort(fclusSort.begin(), fclusSort.end(), [&tracks](int a, int b) { + return tracks[a].getFirstLayerClusterIndex() < tracks[b].getFirstLayerClusterIndex(); + }); + + auto areTracksSelected = [this, iteration](const TrackITSExt& t1, const TrackITSExt& t2) { + const auto t1FirstLayer{t1.getFirstClusterLayer()}, t2FirstLayer{t2.getFirstClusterLayer()}; + if (t1FirstLayer != t2FirstLayer) { + return false; + } + if (mTimeFrame->getClusterROF(t1FirstLayer, t1.getClusterIndex(t1FirstLayer)) != mTimeFrame->getClusterROF(t2FirstLayer, t2.getClusterIndex(t2FirstLayer))) { + return false; + } + if (!math_utils::isPhiDifferenceBelow(t1.getPhi(), t2.getPhi(), mTrkParams[iteration].SharedClusterMaxDeltaPhi)) { + return false; + } + if (std::abs(t1.getEta() - t2.getEta()) > mTrkParams[iteration].SharedClusterMaxDeltaEta) { + return false; } - if (std::binary_search(sharedFirstClusters[firstLayer].begin(), sharedFirstClusters[firstLayer].end(), firstCluster)) { - track.setSharedClusters(); + if (mTrkParams[iteration].SharedClusterOppositeSign && t1.getSign() == t2.getSign()) { + return false; + } + return true; + }; + + for (int i{0}; i < static_cast(fclusSort.size()); ++i) { + auto& track = tracks[fclusSort[i]]; + for (int j{i + 1}; j < static_cast(fclusSort.size()) && tracks[fclusSort[j]].getFirstLayerClusterIndex() == track.getFirstLayerClusterIndex(); ++j) { + auto& track2 = tracks[fclusSort[j]]; + if (areTracksSelected(track, track2)) { + track.setSharedClusters(); + track2.setSharedClusters(); + } } } } From 8c2216f6a5eb93d433f0a834d5c410efd2e98904 Mon Sep 17 00:00:00 2001 From: shahoian Date: Thu, 21 May 2026 18:04:09 +0200 Subject: [PATCH 092/102] Update CorrectionMapsOptions help hints --- Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx b/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx index 45c3771db57bf..5518d680420ca 100644 --- a/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx +++ b/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx @@ -45,7 +45,7 @@ void CorrectionMapsOptions::addGlobalOptions(std::vector& optio { // these are options which should be added at the workflow level, since they modify the inputs of the devices addOption(options, ConfigParamSpec{"lumi-type", o2::framework::VariantType::Int, 0, {"1 = use CTP lumi for TPC correction scaling, 2 = use TPC scalers for TPC correction scaling"}}); - addOption(options, ConfigParamSpec{"corrmap-lumi-mode", o2::framework::VariantType::Int, 0, {"scaling mode: (default) 0 = static + scale * full; 1 = full + scale * derivative; 2 = full + scale * derivative (for MC)"}}); + addOption(options, ConfigParamSpec{"corrmap-lumi-mode", o2::framework::VariantType::Int, 0, {"scaling mode: (default) 0 = static + scale * full; 1 = full + scale * derivative; 2 = full + scale * derivative (for MC); 3 = no correction; 4 = static only"}}); addOption(options, ConfigParamSpec{"enable-M-shape-correction", o2::framework::VariantType::Bool, false, {"Enable M-shape distortion correction"}}); addOption(options, ConfigParamSpec{"disable-ctp-lumi-request", o2::framework::VariantType::Bool, false, {"do not request CTP lumi (regardless what is used for corrections)"}}); addOption(options, ConfigParamSpec{"disable-lumi-type-consistency-check", o2::framework::VariantType::Bool, false, {"disable check of selected CTP or IDC scaling source being consistent with the map"}}); From 86a999e410d7959a8a74c9ac16b42e828795c20d Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Fri, 22 May 2026 19:39:53 +0200 Subject: [PATCH 093/102] DPL MCP: allow connecting to a running Hyperloop test --- .../scripts/dpl-mcp-server/dpl_mcp_server.py | 59 +++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py b/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py index dca5058b01dcd..ed457b8a57d9d 100644 --- a/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py +++ b/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py @@ -39,7 +39,9 @@ import asyncio import json +import os from typing import Any +from urllib.parse import urlparse import websockets from mcp.server.fastmcp import FastMCP @@ -51,9 +53,10 @@ class WorkflowConnection: """Holds WebSocket connection and buffered state for one DPL workflow.""" - def __init__(self, port: int, name: str): - self.port = port + def __init__(self, *, url: str, name: str, extra_headers: dict[str, str] | None = None): + self.url = url self.name = name + self.extra_headers = extra_headers or {} self.ws: Any = None self.reader_task: asyncio.Task | None = None self.snapshot: dict = {} @@ -83,8 +86,11 @@ async def ensure_connected(self) -> None: except Exception: pass - url = f"ws://localhost:{self.port}/status" - self.ws = await websockets.connect(url, subprotocols=["dpl"]) + self.ws = await websockets.connect( + self.url, + subprotocols=["dpl"], + additional_headers=self.extra_headers if self.extra_headers else None, + ) if self.reader_task is None or self.reader_task.done(): self.reader_task = asyncio.create_task(self._reader()) @@ -178,7 +184,8 @@ async def connect(port: int = 0, pid: int = 0, name: str = "") -> str: old = _workflows[wf_name] await old.close() - conn = WorkflowConnection(port, wf_name) + url = f"ws://localhost:{port}/status" + conn = WorkflowConnection(url=url, name=wf_name) await conn.ensure_connected() _workflows[wf_name] = conn @@ -189,6 +196,48 @@ async def connect(port: int = 0, pid: int = 0, name: str = "") -> str: ) +@mcp.tool() +async def connect_hyperloop(url: str, name: str = "", token: str = "") -> str: + """Connect to a DPL workflow running on Hyperloop via the remote proxy. + + Accepts a URL like: + https://alimonitor.cern.ch/train-workdir/remote-gui/remote_proxy.html?/ + + and remaps it to the local WebSocket proxy endpoint. + + Args: + url: The remote_proxy.html URL from alimonitor. + name: Optional human-friendly name for this workflow. + token: Hyperloop auth token. Falls back to HYPERLOOP_TOKEN env var. + """ + token = token or os.environ.get("HYPERLOOP_TOKEN", "") + if not token: + return "No token provided and HYPERLOOP_TOKEN environment variable is not set." + + parsed = urlparse(url) + path_suffix = parsed.query # everything after '?' + if not path_suffix: + return f"Cannot parse token/port from URL: {url}" + + ws_url = f"ws://localhost:8888/remote-mcp/o2/{path_suffix}/status" + wf_name = name or path_suffix.split("/")[-1] + + if wf_name in _workflows: + old = _workflows[wf_name] + await old.close() + + headers = {"Authorization": f"Bearer {token}"} + conn = WorkflowConnection(url=ws_url, name=wf_name, extra_headers=headers) + await conn.ensure_connected() + _workflows[wf_name] = conn + + devices = conn.snapshot.get("devices", []) + return ( + f"Connected to Hyperloop workflow '{wf_name}' via {ws_url} " + f"({len(devices)} device(s))." + ) + + @mcp.tool() async def disconnect(workflow: str) -> str: """Disconnect from a DPL workflow and release its resources. From d13ebbbc141dc9229dcbe3ec69905b167b3d5075 Mon Sep 17 00:00:00 2001 From: shahoian Date: Sat, 23 May 2026 00:39:54 +0200 Subject: [PATCH 094/102] Fix output file name for checkResid streamers --- .../GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx index 01ec999fce1eb..6a1915791a911 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx @@ -132,7 +132,7 @@ void CheckResidSpec::init(InitContext& ic) int maxLanes = ic.services().get().maxInputTimeslices; std::string nm = params.outname; if (maxLanes > 1) { - o2::conf::ConfigurableParam::updateFromString(fmt::format("checkresid.outname={}_{}", nm, lane)); + o2::conf::ConfigurableParam::updateFromString(fmt::format("checkresid.outname={}_t{}", nm, lane)); } if (mDraw) { mFillHistos = true; @@ -173,8 +173,7 @@ void CheckResidSpec::init(InitContext& ic) mNThreads = 1; #endif if (mFillTree) { - nm += ".root"; - mDBGOut = std::make_unique(nm.c_str(), "recreate"); + mDBGOut = std::make_unique(fmt::format("{}.root", params.outname).c_str(), "recreate"); } } From c36617d3be5c39397aa9373f8b49b07d9e8fd453 Mon Sep 17 00:00:00 2001 From: Matthias Kleiner Date: Tue, 19 May 2026 12:09:06 +0200 Subject: [PATCH 095/102] TPC timeseries: make data requests conditional on input sources - do not request PV and FT0 in TPC-only mode - do not request TPC clusters if TPC is not in input sources - make time series work without any track input --- Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index ac3ff15fd3a29..0c0ae72056318 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -1825,15 +1825,16 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter, auto dataRequest = std::make_shared(); bool useMC = false; GTrackID::mask_t srcTracks = GTrackID::getSourcesMask("TPC,ITS,ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF") & src; - srcTracks.set(GTrackID::TPC); // TPC must be always there dataRequest->requestTracks(srcTracks, useMC); - dataRequest->requestClusters(GTrackID::getSourcesMask("TPC"), useMC); + if (src[GTrackID::TPC]) { + dataRequest->requestClusters(GTrackID::getSourcesMask("TPC"), useMC); + } bool tpcOnly = srcTracks == GTrackID::getSourcesMask("TPC"); - if (!tpcOnly) { + if (srcTracks.any() && !tpcOnly) { dataRequest->requestFT0RecPoints(useMC); + dataRequest->requestPrimaryVertices(useMC); } - dataRequest->requestPrimaryVertices(useMC); const bool enableAskMatLUT = matType == o2::base::Propagator::MatCorrType::USEMatCorrLUT; auto ccdbRequest = std::make_shared(!disableWriter, // orbitResetTime From a4bd6bc0c69b2dd18b1073390a8b062002d8e360 Mon Sep 17 00:00:00 2001 From: Matthias Kleiner Date: Thu, 23 Apr 2026 16:04:23 +0200 Subject: [PATCH 096/102] TPC: move nthreads to local option --- .../include/TPCWorkflow/TPCFourierTransformAggregatorSpec.h | 5 ++++- .../TPC/workflow/src/tpc-fouriertransform-aggregator.cxx | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFourierTransformAggregatorSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFourierTransformAggregatorSpec.h index 35f51dd489115..7facee78fb3d6 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFourierTransformAggregatorSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFourierTransformAggregatorSpec.h @@ -64,6 +64,8 @@ class TPCFourierTransformAggregatorSpec : public o2::framework::Task mLengthIDCScalerSeconds = ic.options().get("tpcScalerLengthS"); mDisableScaler = ic.options().get("disable-scaler"); mEnableFFTCCDB = ic.options().get("enable-fft-CCDB"); + int nthreads = ic.options().get("nthreads"); + TPCFourierTransformAggregatorSpec::IDCFType::setNThreads(nthreads); resizeBuffer(mInputLanes); } @@ -448,7 +450,8 @@ DataProcessorSpec getTPCFourierTransformAggregatorSpec(const unsigned int rangeI {"dump-coefficients-agg", VariantType::Bool, false, {"Dump fourier coefficients to file"}}, {"tpcScalerLengthS", VariantType::Float, 300.f, {"Length of the TPC scalers in seconds"}}, {"disable-scaler", VariantType::Bool, false, {"Disable creation of IDC scaler"}}, - {"enable-fft-CCDB", VariantType::Bool, false, {"Enable writing of FFT coefficients to CCDB"}}}}; + {"enable-fft-CCDB", VariantType::Bool, false, {"Enable writing of FFT coefficients to CCDB"}}, + {"nthreads", VariantType::Int, 1, {"Number of threads which will be used during the calculation of the fourier coefficients."}}}}; } } // namespace o2::tpc diff --git a/Detectors/TPC/workflow/src/tpc-fouriertransform-aggregator.cxx b/Detectors/TPC/workflow/src/tpc-fouriertransform-aggregator.cxx index b0f09e02e627b..2f66a144251f1 100644 --- a/Detectors/TPC/workflow/src/tpc-fouriertransform-aggregator.cxx +++ b/Detectors/TPC/workflow/src/tpc-fouriertransform-aggregator.cxx @@ -26,7 +26,6 @@ void customize(std::vector& workflowOptions) std::vector options{ {"rangeIDC", VariantType::Int, 200, {"Number of 1D-IDCs which will be used for the calculation of the fourier coefficients. TODO ALREADY SET IN ABERAGEGROUP"}}, {"nFourierCoeff", VariantType::Int, 60, {"Number of fourier coefficients (real+imag) which will be stored in the CCDB. The maximum can be 'rangeIDC + 2'."}}, - {"nthreads", VariantType::Int, 1, {"Number of threads which will be used during the calculation of the fourier coefficients."}}, {"inputLanes", VariantType::Int, 2, {"Number of expected input lanes."}}, {"sendOutput", VariantType::Bool, false, {"send fourier coefficients"}}, {"use-naive-fft", VariantType::Bool, false, {"using naive fourier transform (true) or FFTW (false)"}}, @@ -51,8 +50,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) const bool processSACs = config.options().get("process-SACs"); const auto rangeIDC = static_cast(config.options().get("rangeIDC")); const auto nFourierCoeff = std::clamp(static_cast(config.options().get("nFourierCoeff")), static_cast(0), rangeIDC + 2); - const auto nthreadsFourier = static_cast(config.options().get("nthreads")); - TPCFourierTransformAggregatorSpec::IDCFType::setNThreads(nthreadsFourier); TPCFourierTransformAggregatorSpec::IDCFType::setFFT(!fft); const auto inputLanes = config.options().get("inputLanes"); WorkflowSpec workflow{getTPCFourierTransformAggregatorSpec(rangeIDC, nFourierCoeff, sendOutput, processSACs, inputLanes)}; From 5b4b32f9f0a761711c4e9448877f62f6cd0c2f78 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Sun, 24 May 2026 00:04:36 +0200 Subject: [PATCH 097/102] DPL: add debug information for rate limiting --- Framework/Core/src/ControlWebSocketHandler.cxx | 9 +++++++++ Framework/Core/src/DevicesManager.cxx | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/Framework/Core/src/ControlWebSocketHandler.cxx b/Framework/Core/src/ControlWebSocketHandler.cxx index 35528a1d6dfec..8be91c0e22fc3 100644 --- a/Framework/Core/src/ControlWebSocketHandler.cxx +++ b/Framework/Core/src/ControlWebSocketHandler.cxx @@ -14,10 +14,13 @@ #include "StatusWebSocketHandler.h" #include "Framework/DeviceMetricsHelper.h" #include "Framework/ServiceMetricsInfo.h" +#include "Framework/Signpost.h" #include #include "Framework/Logger.h" #include "Framework/DeviceConfigInfo.h" +O2_DECLARE_DYNAMIC_LOG(rate_limiting); + namespace o2::framework { void ControlWebSocketHandler::frame(char const* frame, size_t s) @@ -74,6 +77,10 @@ void ControlWebSocketHandler::endChunk() if (!didProcessMetric) { return; } + O2_SIGNPOST_ID_GENERATE(sid, rate_limiting); + O2_SIGNPOST_START(rate_limiting, sid, "endChunk", + "Processing metrics from device %d (had new metric: %d)", + mIndex, (int)didHaveNewMetric); size_t timestamp = (uv_hrtime() - mContext.driver->startTime) / 1000000 + mContext.driver->startTimeMsFromEpoch; assert(mContext.metrics); assert(mContext.infos); @@ -91,6 +98,8 @@ void ControlWebSocketHandler::endChunk() for (auto& metricsInfo : *mContext.metrics) { std::fill(metricsInfo.changed.begin(), metricsInfo.changed.end(), false); } + O2_SIGNPOST_END(rate_limiting, sid, "endChunk", + "Done processing metrics from device %d", mIndex); } void ControlWebSocketHandler::headers(std::map const& headers) diff --git a/Framework/Core/src/DevicesManager.cxx b/Framework/Core/src/DevicesManager.cxx index e6fa2c2c61ae6..b427e72ca781d 100644 --- a/Framework/Core/src/DevicesManager.cxx +++ b/Framework/Core/src/DevicesManager.cxx @@ -13,12 +13,19 @@ #include "Framework/RuntimeError.h" #include "Framework/Logger.h" #include "Framework/DeviceController.h" +#include "Framework/Signpost.h" + +O2_DECLARE_DYNAMIC_LOG(devices_manager); namespace o2::framework { void DevicesManager::queueMessage(char const* target, char const* message) { + O2_SIGNPOST_ID_GENERATE(sid, devices_manager); + O2_SIGNPOST_EVENT_EMIT(devices_manager, sid, "queue", + "Queuing message for %{public}s: %{public}s", + target, message); for (int di = 0; di < specs.size(); ++di) { if (specs[di].id == target) { messages.push_back({di, message}); @@ -44,6 +51,10 @@ void DevicesManager::flush() LOGP(info, "Controller for {} now available.", specs[handle.ref.index].id); notifiedAvailable = true; } + O2_SIGNPOST_ID_GENERATE(sid, devices_manager); + O2_SIGNPOST_EVENT_EMIT(devices_manager, sid, "flush", + "Flushing message to %{public}s: %{public}s", + specs[handle.ref.index].id.c_str(), handle.message.c_str()); controller->write(handle.message.c_str(), handle.message.size()); } From 19b82ba383c8000a3d1be23cc15d4cb0c36f83b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Jacazio?= Date: Mon, 25 May 2026 09:14:46 +0200 Subject: [PATCH 098/102] [ALICE3] TRK: fix ACTS clusterer compilation (#15352) Comment out the processing of digMC2ROFs if condition. --- .../ALICE3/TRK/reconstruction/src/ClustererACTS.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx index 2dbf56ae610e3..30ab503b7e250 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx @@ -387,10 +387,10 @@ void ClustererACTS::process(gsl::span digits, 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); - } - } + // 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); + // } + // } } From 8936d29ea8fdee5085990361442be87c809ab99c Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Mon, 25 May 2026 10:01:47 +0200 Subject: [PATCH 099/102] DPL: fix mismatched type in signpost --- Framework/Core/src/ControlWebSocketHandler.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/Core/src/ControlWebSocketHandler.cxx b/Framework/Core/src/ControlWebSocketHandler.cxx index 8be91c0e22fc3..8d2f85b034364 100644 --- a/Framework/Core/src/ControlWebSocketHandler.cxx +++ b/Framework/Core/src/ControlWebSocketHandler.cxx @@ -79,7 +79,7 @@ void ControlWebSocketHandler::endChunk() } O2_SIGNPOST_ID_GENERATE(sid, rate_limiting); O2_SIGNPOST_START(rate_limiting, sid, "endChunk", - "Processing metrics from device %d (had new metric: %d)", + "Processing metrics from device %zu (had new metric: %d)", mIndex, (int)didHaveNewMetric); size_t timestamp = (uv_hrtime() - mContext.driver->startTime) / 1000000 + mContext.driver->startTimeMsFromEpoch; assert(mContext.metrics); @@ -99,7 +99,7 @@ void ControlWebSocketHandler::endChunk() std::fill(metricsInfo.changed.begin(), metricsInfo.changed.end(), false); } O2_SIGNPOST_END(rate_limiting, sid, "endChunk", - "Done processing metrics from device %d", mIndex); + "Done processing metrics from device %zu", mIndex); } void ControlWebSocketHandler::headers(std::map const& headers) From 440a899dee1673762ec344cad73735ecf44ad57d Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Mon, 25 May 2026 09:33:52 +0200 Subject: [PATCH 100/102] DPL: reduce number of spurious warnings --- Framework/Core/src/DataProcessingDevice.cxx | 22 +++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Framework/Core/src/DataProcessingDevice.cxx b/Framework/Core/src/DataProcessingDevice.cxx index be25133158072..b45a48c28f691 100644 --- a/Framework/Core/src/DataProcessingDevice.cxx +++ b/Framework/Core/src/DataProcessingDevice.cxx @@ -1420,14 +1420,24 @@ void DataProcessingDevice::Run() } return missingInfo.empty() ? std::string(" (policy: ") + spec.resourcePolicy.name + ")" : " -" + missingInfo; }; + auto const timeSinceLastScheduled = lastSched ? uv_now(state.loop) - lastSched : 0; if (schedulingStats.numberOfUnscheduledSinceLastScheduled >= schedulingStats.nextWarnAt) { auto const missingStr = buildMissingInfo(); - O2_SIGNPOST_EVENT_EMIT_WARN(scheduling, sid, "Run", - "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.", - streamRef.index, - schedulingStats.numberOfUnscheduledSinceLastScheduled.load(), - schedInfo.c_str(), - missingStr.c_str()); + if (timeSinceLastScheduled >= 50) { + O2_SIGNPOST_EVENT_EMIT_WARN(scheduling, sid, "Run", + "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.", + streamRef.index, + schedulingStats.numberOfUnscheduledSinceLastScheduled.load(), + schedInfo.c_str(), + missingStr.c_str()); + } else { + O2_SIGNPOST_EVENT_EMIT(scheduling, sid, "Run", + "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.", + streamRef.index, + schedulingStats.numberOfUnscheduledSinceLastScheduled.load(), + schedInfo.c_str(), + missingStr.c_str()); + } schedulingStats.nextWarnAt = schedulingStats.nextWarnAt * 2; } else { auto const missingStr = buildMissingInfo(); From 67473780483d90c743df4d8092abfa6ae596e097 Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Sat, 16 May 2026 20:23:13 +0200 Subject: [PATCH 101/102] ITS: adjust printing and use mask Signed-off-by: Felix Schlepper --- .../ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx | 3 +-- .../include/ITStracking/Configuration.h | 7 ++++--- .../ITSMFT/ITS/tracking/src/Configuration.cxx | 18 ++++++++++++++---- .../ITSMFT/ITS/tracking/src/TrackerTraits.cxx | 5 ++--- .../workflow/src/TrackerSpec.cxx | 4 ++-- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx index 32c46e2ea55d2..141d558712e6d 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx @@ -309,8 +309,7 @@ void TrackerTraitsGPU::findRoads(const int iteration) bounded_vector> trackSeeds(this->getMemoryPool().get()); for (int startCellTopologyId{0}; startCellTopologyId < hostTopology.nCells; ++startCellTopologyId) { const int startLayer = hostTopology.getCell(startCellTopologyId).hitLayerMask.last(); - if ((this->mTrkParams[iteration].StartLayerMask & (1 << startLayer)) == 0 || - mTimeFrameGPU->getNCells()[startCellTopologyId] == 0) { + if (!(this->mTrkParams[iteration].StartLayerMask.has(startLayer)) || mTimeFrameGPU->getNCells()[startCellTopologyId] == 0) { continue; } processNeighboursHandler(startLevel, diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h index 852c5ecd24633..275752854665b 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h @@ -26,6 +26,7 @@ #include "CommonUtils/EnumFlags.h" #include "DetectorsBase/Propagator.h" #include "ITStracking/Constants.h" +#include "ITStracking/LayerMask.h" namespace o2::its { @@ -70,10 +71,9 @@ struct TrackingParameters { float DiamondCov[6] = {25.e-6f, 0.f, 0.f, 25.e-6f, 0.f, 36.f}; /// General parameters - int ClusterSharing = 0; int MinTrackLength = 7; int MaxHoles = 0; - uint16_t HoleLayerMask = 0; + LayerMask HoleLayerMask = 0; float NSigmaCut = 5; float PVres = 1.e-2f; /// Trackleting cuts @@ -86,7 +86,7 @@ struct TrackingParameters { float MaxChi2NDF = 30.f; int ReseedIfShorter = 6; // reseed for the final fit track with the length shorter than this std::vector MinPt = {0.f, 0.f, 0.f, 0.f}; - uint16_t StartLayerMask = 0x7F; + LayerMask StartLayerMask = 0x7F; bool RepeatRefitOut = false; // repeat outward refit using inward refit as a seed bool ShiftRefToCluster = true; // TrackFit: after update shift the linearization reference to cluster bool PerPrimaryVertexProcessing = false; @@ -103,6 +103,7 @@ struct TrackingParameters { float SharedClusterMaxDeltaPhi = 0.05f; // For tracks sharing clusters, maximum allowed delta phi at the cluster position float SharedClusterMaxDeltaEta = 0.03f; // For tracks sharing clusters, maximum allowed delta eta at the cluster position bool SharedClusterOppositeSign = false; // For tracks sharing clusters, require opposite sign of the tracklets + int SharedMaxClusters = 0; // Maximal allowed shared clusters (excluding first cluster) }; struct VertexingParameters { diff --git a/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx b/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx index 0bf383c996a68..f07a8f3394c05 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx @@ -24,8 +24,9 @@ using namespace o2::its; std::string TrackingParameters::asString() const { - std::string str = std::format("NZb:{} NPhB:{} PerVtx:{} DropFail:{} ClSh:{} TtklMinPt:{:.2f} MinCl:{} MaxHoles:{} HoleMask:{:#x}", - ZBins, PhiBins, PerPrimaryVertexProcessing, DropTFUponFailure, ClusterSharing, TrackletMinPt, MinTrackLength, MaxHoles, HoleLayerMask); + std::string str = std::format("NZb:{} NPhB:{} PerVtx:{} DropFail:{} TtklMinPt:{:.2f} MinCl:{}", ZBins, PhiBins, PerPrimaryVertexProcessing, DropTFUponFailure, TrackletMinPt, MinTrackLength); + auto isSet = [](auto e) { return e >= 0; }; + auto isAnySet = [&isSet](auto v) { return !v.empty() && std::any_of(v.begin(), v.end(), isSet); }; bool first = true; for (int il = NLayers; il >= MinTrackLength; il--) { int slot = NLayers - il; @@ -37,18 +38,27 @@ std::string TrackingParameters::asString() const str += std::format("L{}:{:.2f} ", il, MinPt[slot]); } } - if (!SystErrorY2.empty() || !SystErrorZ2.empty()) { + if (isAnySet(SystErrorY2) || isAnySet(SystErrorZ2)) { str += " SystErrY/Z:"; for (size_t i = 0; i < SystErrorY2.size(); i++) { str += std::format("{:.2e}/{:.2e} ", SystErrorY2[i], SystErrorZ2[i]); } } - if (!AddTimeError.empty()) { + if (isAnySet(AddTimeError)) { str += " AddTimeError:"; for (unsigned int i : AddTimeError) { str += std::format("{} ", i); } } + if (SharedMaxClusters) { + str += std::format(" ShaMaxCls:{} ", SharedMaxClusters); + } + if (AllowSharingFirstCluster) { + str += std::format(" ShaClsDPhi:{} ShaClsDEta:{} ShaClsSign:{}", SharedClusterMaxDeltaPhi, SharedClusterMaxDeltaEta, SharedClusterOppositeSign); + } + if (MaxHoles) { + str += std::format(" MaxHoles:{} HoleMask:{}", MaxHoles, HoleLayerMask.asString()); + } if (std::numeric_limits::max() != MaxMemory) { str += std::format(" MemLimit {:.2f} GB", double(MaxMemory) / constants::GB); } diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx index 3432b60162002..c4439dc74d29e 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx @@ -681,8 +681,7 @@ void TrackerTraits::findRoads(const int iteration) bounded_vector trackSeeds(mMemoryPool.get()); for (int startCellTopologyId{0}; startCellTopologyId < topology.nCells; ++startCellTopologyId) { const int startLayer = topology.getCell(startCellTopologyId).hitLayerMask.last(); - if ((mTrkParams[iteration].StartLayerMask & (1 << startLayer)) == 0 || - mTimeFrame->getCells()[startCellTopologyId].empty()) { + if (!(mTrkParams[iteration].StartLayerMask.has(startLayer)) || mTimeFrame->getCells()[startCellTopologyId].empty()) { continue; } @@ -814,7 +813,7 @@ void TrackerTraits::acceptTracks(int iteration, bounded_vector mTrkParams[iteration].ClusterSharing) { + if (nShared - int(isFirstShared && mTrkParams[iteration].AllowSharingFirstCluster) > mTrkParams[iteration].SharedMaxClusters) { continue; } diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index 6f9f5561a5ef6..070466ea8711d 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -113,8 +113,8 @@ std::vector TrackerDPL::createTrackingParamsFromCon if (paramConfig.contains("PhiBins")) { params.PhiBins = paramConfig["PhiBins"].get(); } - if (paramConfig.contains("ClusterSharing")) { - params.ClusterSharing = paramConfig["ClusterSharing"].get(); + if (paramConfig.contains("SharedMaxClusters")) { + params.SharedMaxClusters = paramConfig["SharedMaxClusters"].get(); } if (paramConfig.contains("MinTrackLength")) { params.MinTrackLength = paramConfig["MinTrackLength"].get(); From ac48d72874fbf15ad87af0d0a17313d60f3bf716 Mon Sep 17 00:00:00 2001 From: Felix Schlepper Date: Fri, 22 May 2026 20:15:05 +0200 Subject: [PATCH 102/102] ITS: Remove redundant 'const' from getter methods the redunant const from #15406 qualifiers lead to warnings: ``` O2/GPU/GPUTracking/Standalone/../../../DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h:195:12: warning: type qualifiers ignored on function return type [-Wignored-qualifiers] 195 | GPUhdi() const int getClusterIndex(int lr) const { return mIndex[lr]; } | ^~~~~ /O2/GPU/GPUTracking/Standalone/../../../DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h:197:10: warning: type qualifiers ignored on function return type [-Wignored-qualifiers] 197 | GPUh() const int getFirstLayerClusterIndex() const | ^~~~~ ``` --- .../Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h index 89f6416c6e177..20fb7c63ebacd 100644 --- a/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h +++ b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h @@ -192,12 +192,11 @@ class TrackITSExt : public TrackITS getClusterRefs().setEntries(ncl); } - GPUhdi() const int getClusterIndex(int lr) const { return mIndex[lr]; } + GPUhdi() int getClusterIndex(int lr) const { return mIndex[lr]; } - GPUh() const int getFirstLayerClusterIndex() const + GPUh() int getFirstLayerClusterIndex() const { - int firstLayer = getFirstClusterLayer(); - return getClusterIndex(firstLayer); + return getClusterIndex(getFirstClusterLayer()); } GPUhdi() void setExternalClusterIndex(int layer, int idx, bool newCluster = false)