From a03223aa5e558f9b9cd1fbe054e02b40cc857fc9 Mon Sep 17 00:00:00 2001 From: shahoian Date: Tue, 29 Sep 2026 21:32:48 +0200 Subject: [PATCH 01/18] Support per-layer ITS clusters and ROFs in TPC-ITS matching Extend MatchTPCITS to work with ITS clusters provided either as a single (monolithic) input or per layer, with layer-dependent ROF length and bias (staggered readout): - ITS clusters, sizes, cluster ROFRecords and MC labels are stored per layer, clusters are addressed by the composed ID (layer<<28)+index_in_layer (all in slot 0 for the monolithic input) - all ITS ROF timings (per-layer lengths and biases in BC and mus, the clock layer defining the ITS tracks ROFs granularity) are derived from the DPLAlpideParam object set via setAlpideParam; the setITSROFrameLength... and setITSTimeBiasInBC setters are removed - interaction candidates are related to the cluster ROFs of every AfterBurner layer within the optional abROFMarginMUS margin (allowing up to 2 compatible ROFs per layer) and are cut at the last clock-layer cluster ROF - AfterBurner reworked for CPU efficiency: unused clusters are filtered and (chip,Z)-sorted once per TF into per (layer, ROF) blocks, built only for the ROFs referenced by candidates with seeds; ITSChipClustersRefs is replaced by thread-local per-layer views (compact Y,Z,id,chip cluster info) refreshed only when the processed group of candidates changes ROFs - tpcits-match-workflow can request the per-layer ITS clusters input (through RecoContainer::setITSPerLayer) Co-Authored-By: Claude Fable 5 --- .../include/GlobalTracking/MatchTPCITS.h | 185 +++-- .../GlobalTracking/MatchTPCITSParams.h | 1 + Detectors/GlobalTracking/src/MatchTPCITS.cxx | 665 ++++++++++++------ .../TPCITSMatchingSpec.h | 2 +- .../src/TPCITSMatchingSpec.cxx | 14 +- .../src/tpcits-match-workflow.cxx | 2 +- 6 files changed, 598 insertions(+), 271 deletions(-) diff --git a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h index e736f0c9c8a42..51ef4e1f67e29 100644 --- a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h +++ b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h @@ -34,6 +34,7 @@ #include "ReconstructionDataFormats/GlobalTrackID.h" #include "MathUtils/Primitive2D.h" #include "CommonDataFormat/EvIndex.h" +#include #include "CommonDataFormat/InteractionRecord.h" #include "CommonDataFormat/RangeReference.h" #include "CommonDataFormat/BunchFilling.h" @@ -42,6 +43,7 @@ #include "CommonUtils/TreeStreamRedirector.h" #include "DataFormatsITSMFT/Cluster.h" #include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITS/TrackITS.h" #include "DataFormatsFT0/RecPoints.h" #include "FT0Reconstruction/InteractionTag.h" @@ -107,6 +109,17 @@ constexpr int MinusOne = -1; constexpr int MinusTen = -10; constexpr int Validated = -2; +///< ITS clusters are referred to by the composed index (layer << ITSClusLayerShift) + index_in_layer. +///< With a single ITS clusters input (not per layer) all clusters are in the layer slot 0. +constexpr int ITSClusLayerShift = 28; +constexpr int ITSClusIndexMask = (0x1 << ITSClusLayerShift) - 1; +constexpr int composeITSClusID(int lr, int idx) { return (lr << ITSClusLayerShift) + idx; } +constexpr int ITSClusID2Layer(int id) { return id >> ITSClusLayerShift; } +constexpr int ITSClusID2Index(int id) { return id & ITSClusIndexMask; } + +///< per-layer status of ITS clusters (e.g. for the AfterBurner) +using ITSClusStatus = std::array, o2::its::RecoGeomHelper::getNLayers()>; + ///< flags to tell the status of TPC-ITS tracks comparison enum TrackRejFlag : int { Accept = 0, @@ -264,25 +277,25 @@ struct TPCABSeed { { return lowestLayer < o2::its::RecoGeomHelper::getNLayers() ? firstInLr[lowestLayer] : -1; } - bool checkLinkHasUsedClusters(int linkID, const std::vector& clStatus) const + bool checkLinkHasUsedClusters(int linkID, const ITSClusStatus& clStatus) const { // check if some clusters used by the link or its parents are forbidden (already used by validatet track) while (linkID > MinusOne) { const auto& link = getLink(linkID); - if (link.clID > MinusOne && clStatus[link.clID] != MinusOne) { + if (link.clID > MinusOne && clStatus[ITSClusID2Layer(link.clID)][ITSClusID2Index(link.clID)] != MinusOne) { return true; } linkID = link.parentID; } return false; } - void flagLinkUsedClusters(int linkID, std::vector& clStatus) const + void flagLinkUsedClusters(int linkID, ITSClusStatus& clStatus) const { // check if some clusters used by the link or its parents are forbidden (already used by validated track) while (linkID > MinusOne) { const auto& link = getLink(linkID); if (link.clID > MinusOne) { - clStatus[link.clID] = MinusTen; + clStatus[ITSClusID2Layer(link.clID)][ITSClusID2Index(link.clID)] = MinusTen; } linkID = link.parentID; } @@ -293,40 +306,59 @@ struct TPCABSeed { struct InteractionCandidate : public o2::InteractionRecord { o2::math_utils::Bracketf_t tBracket; // interaction time - int rofITS; // corresponding ITS ROF entry (in the ROFRecord vectors) + int rofITS; // corresponding ITS clock-layer cluster ROF entry (in the ROFRecord vectors) uint32_t flag; // origin, etc. o2::dataformats::RangeReference seedsRef; // references to AB seeds - InteractionCandidate(const o2::InteractionRecord& ir, float t, float dt, int rof, uint32_t f = 0) : o2::InteractionRecord(ir), tBracket(t - dt, t + dt), rofITS(rof), flag(f) {} -}; - -struct ITSChipClustersRefs { - ///< contaner for sorted cluster indices for certain time window (usually ROF) and reference on the start and N clusters - ///< for every chip - using ClusRange = o2::dataformats::RangeReference; - std::vector clusterID; // indices of sorted clusters - -#ifndef ENABLE_UPGRADES - std::array chipRefs; // offset and number of clusters in each chip - ITSChipClustersRefs(int nclIni = 50000) - { - clusterID.reserve(nclIni); - } -#else - std::vector chipRefs; // offset and number of clusters in each chip - ITSChipClustersRefs(int nchips = o2::its::RecoGeomHelper::getNChips(), int nclIni = 50000) + std::array rofLr; // per ITS layer: cluster ROF entry compatible with the candidate time, -1: none + uint8_t rofNextLr = 0; // bit lr set: the candidate time is compatible also with the ROF rofLr[lr]+1 of the layer + InteractionCandidate(const o2::InteractionRecord& ir, float t, float dt, int rof, uint32_t f = 0) : o2::InteractionRecord(ir), tBracket(t - dt, t + dt), rofITS(rof), flag(f) { - clusterID.reserve(nclIni); - chipRefs.resize(nchips, ClusRange()); + rofLr.fill(MinusOne); } -#endif +}; + +struct ABClusterInfo { + ///< compact info on an ITS cluster usable by the AfterBurner + float y = 0.f, z = 0.f; ///< Y, Z of the cluster in the tracking frame of its sensor + int id = MinusOne; ///< composed cluster ID, see composeITSClusID + int chip = -1; ///< global chip (sensor) ID +}; +struct ABLayerClusters { + ///< clusters of one ITS layer usable by the AfterBurner (not attached to ITS tracks), stored in per-ROF + ///< blocks sorted in (chip, Z). Only the blocks (ROFs) referenced by some interaction candidate are built. + using ClusRange = o2::dataformats::RangeReference; + std::vector rofTimes; ///< time brackets of all cluster ROFs of the layer + std::vector rofRefs; ///< block of every ROF in the clus vector, firstEntry < 0 if the block was not built + std::vector clus; ///< clusters of the built blocks + std::vector needROF; ///< scratch buffer: blocks to build for the current TF void clear() { - clusterID.clear(); - std::memset(chipRefs.data(), 0, chipRefs.size() * sizeof(ClusRange)); // reset chip->cluster references + rofTimes.clear(); + rofRefs.clear(); + clus.clear(); + needROF.clear(); } - size_t sizeInternal() const { return sizeof(int) * clusterID.size(); } - size_t capInternal() const { return sizeof(int) * clusterID.capacity(); } + size_t sizeInternal() const { return sizeof(ABClusterInfo) * clus.size() + sizeof(o2::math_utils::Bracketf_t) * rofTimes.size() + sizeof(ClusRange) * rofRefs.size() + needROF.size(); } + size_t capInternal() const { return sizeof(ABClusterInfo) * clus.capacity() + sizeof(o2::math_utils::Bracketf_t) * rofTimes.capacity() + sizeof(ClusRange) * rofRefs.capacity() + needROF.capacity(); } +}; + +struct ABLayerView { + ///< thread-local view of the ABLayerClusters block(s) covering the time of the interaction candidate being processed + using ClusRange = o2::dataformats::RangeReference; + int rofKey = -2; ///< 1st ROF of the loaded block(s): -1: no compatible ROF, -2: nothing loaded yet + int nROFsKey = 0; ///< number of consecutive ROFs loaded (1 or 2) + int chipOffs = 0; ///< global ID of the 1st chip of the layer + int nData = 0; ///< number of clusters in the view + const ABClusterInfo* data = nullptr; ///< clusters of the loaded block(s), sorted in (chip, Z) + std::vector chipRefs; ///< cluster range in data for every chip of the layer + std::vector touchedChips; ///< chips with non-empty chipRefs, for fast reset + std::vector merged; ///< buffer to merge 2 blocks when the candidate time is compatible with 2 ROFs +}; + +struct ABThreadClusterViews { + ///< per-thread views of the AB clusters of all layers + std::array layers; }; class MatchTPCITS @@ -340,6 +372,7 @@ class MatchTPCITS using Params = o2::globaltracking::MatchTPCITSParams; using MatCorrType = o2::base::Propagator::MatCorrType; using VDTriplet = o2::dataformats::Triplet; + using AlpParamITS = o2::itsmft::DPLAlpideParam; MatchTPCITS(); // std::unique_ptr to forward declared type needs constructor / destructor in .cxx ~MatchTPCITS(); @@ -348,6 +381,7 @@ class MatchTPCITS static constexpr int MaxLadderCand = 2 * MaxUpDnLadders + 1; // max ladders to check for matching clusters static constexpr int MaxSeedsPerLayer = 50; // TODO static constexpr int NITSLayers = o2::its::RecoGeomHelper::getNLayers(); + static_assert(NITSLayers == AlpParamITS::getNLayers(), "ITS layers count mismatch between geometry helper and DPLAlpideParam"); ///< perform matching for provided input #if !defined(__CINT__) && !defined(__MAKECINT__) && !defined(__ROOTCLING__) && !defined(__CLING__) void run(const o2::globaltracking::RecoContainer& inp, @@ -394,7 +428,13 @@ class MatchTPCITS void setNHBPerTF(int n) { mNHBPerTF = n; } ///< ITS readout mode - void setITSTriggered(bool v) { mITSTriggered = v; } + void setITSTriggered(bool v) + { + mITSTriggered = v; + if (mAlpParams) { // the ROF length depends on the readout mode + setAlpideParam(mAlpParams); + } + } bool isITSTriggered() const { return mITSTriggered; } void setUseFT0(bool v) { mUseFT0 = v; } @@ -403,14 +443,29 @@ class MatchTPCITS void setUseBCFilling(bool v) { mUseBCFilling = v; } bool getUseBCFilling() const { return mUseBCFilling; } - ///< set ITS ROFrame duration in microseconds - void setITSROFrameLengthMUS(float fums); - ///< set ITS ROFrame duration in BC (continuous mode only) - void setITSROFrameLengthInBC(int nbc); - - void setITSTimeBiasInBC(int n); - int getITSTimeBiasInBC() const { return mITSTimeBiasInBC; } - float getITSTimeBiasMUS() const { return mITSTimeBiasMUS; } + ///< set ITS Alpide parameters: all per-layer ITS ROF lengths, biases and the clock layer + ///< are derived from them, no other ITS timing setter is needed. Can be called in any order + ///< wrt setITSTriggered(). + void setAlpideParam(const AlpParamITS* p); + const AlpParamITS* getAlpideParam() const { return mAlpParams; } + + ///< layer whose ROF defines the granularity of the ITS tracks ROFRecords (0 if all layers share the same ROF) + int getITSClockLayer() const { return mITSClockLayer; } + + ///< per-layer ITS ROF length and bias, always defined for all NITSLayers + int getITSROFrameLengthInBC(int lr) const { return mITSROFrameLengthInBC[lr]; } + float getITSROFrameLengthMUS(int lr) const { return mITSROFrameLengthMUS[lr]; } + float getITSROFrameLengthMUSInv(int lr) const { return mITSROFrameLengthMUSInv[lr]; } + float getITSTimeResMUS(int lr) const { return mITSTimeResMUS[lr]; } + int getITSTimeBiasInBC(int lr) const { return mITSTimeBiasInBC[lr]; } + float getITSTimeBiasMUS(int lr) const { return mITSTimeBiasMUS[lr]; } + + ///< time bracket (wrt TF start) of the ROF starting at nBC of the given layer + BracketF getITSROFTimeBracket(long nBC, int lr) const + { + float tMin = (nBC + mITSTimeBiasInBC[lr]) * o2::constants::lhc::LHCBunchSpacingMUS; + return {tMin, tMin + mITSROFrameLengthMUS[lr]}; + } // ==================== >> DPL-driven input >> ======================= void setITSDictionary(const o2::itsmft::TopologyDictionary* d) { mITSDict = d; } @@ -493,9 +548,8 @@ class MatchTPCITS int prepareTPCTracksAfterBurner(); int addTPCSeed(const o2::track::TrackParCov& _tr, float t0, float terr, o2::dataformats::GlobalTrackID srcGID, int tpcID); - int preselectChipClusters(std::vector& clVecOut, const ClusRange& clRange, const ITSChipClustersRefs& itsChipClRefs, + int preselectChipClusters(std::vector& clVecOut, const ClusRange& clRange, const ABLayerView& clView, float trackY, float trackZ, float tolerY, float tolerZ) const; - void fillClustersForAfterBurner(int rofStart, int nROFs, ITSChipClustersRefs& itsChipClRefs); void flagUsedITSClusters(const o2::its::TrackITS& track); void doMatching(int sec); @@ -533,7 +587,7 @@ class MatchTPCITS ///< convert time to ITS ROFrame units in case of continuous ITS readout int time2ITSROFrameCont(float t) const { - int rof = (t - mITSTimeBiasMUS) * mITSROFrameLengthMUSInv; + int rof = (t - mITSTimeBiasMUS[mITSClockLayer]) * mITSROFrameLengthMUSInv[mITSClockLayer]; if (rof < 0) { rof = 0; } @@ -544,7 +598,7 @@ class MatchTPCITS ///< convert time to ITS ROFrame units in case of triggered ITS readout int time2ITSROFrameTrig(float t, int start) const { - t -= mITSTimeBiasMUS; + t -= mITSTimeBiasMUS[mITSClockLayer]; while (start < int(mITSROFTimes.size())) { if (mITSROFTimes[start].getMax() > t) { return start; @@ -575,10 +629,17 @@ class MatchTPCITS return delta > toler ? rejFlag : (delta < -toler ? -rejFlag : Accept); } + const ITSCluster& getITSCluster(int composedID) const + { + return mITSClustersArray[ITSClusID2Layer(composedID)][ITSClusID2Index(composedID)]; + } + // ========================= AFTERBURNER ========================= int prepareABSeeds(); - void processABSeed(int sid, const ITSChipClustersRefs& itsChipClRefs, uint8_t tID); - int followABSeed(const o2::track::TrackParCov& seed, const ITSChipClustersRefs& itsChipClRefs, int seedID, int lrID, TPCABSeed& ABSeed); + void prepareABClusters(); + void updateABLayerView(ABLayerView& view, int lr, int rof, int nROFs) const; + void processABSeed(int sid, const ABThreadClusterViews& itsClViews, uint8_t tID); + int followABSeed(const o2::track::TrackParCov& seed, const ABLayerView& clView, int seedID, int lrID, TPCABSeed& ABSeed); int registerABTrackLink(TPCABSeed& ABSeed, const o2::track::TrackParCov& trc, int clID, int parentID, int lr, int laddID, float chi2Cl); bool isBetter(float chi2A, float chi2B) { return chi2A < chi2B; } // RS FIMXE TODO void accountForOverlapsAB(int lrSeed); @@ -600,6 +661,7 @@ class MatchTPCITS ///========== Parameters to be set externally, e.g. from CCDB ==================== const Params* mParams = nullptr; const o2::ft0::InteractionTag* mFT0Params = nullptr; + const AlpParamITS* mAlpParams = nullptr; ///< ITS Alpide parameters, set externally MatCorrType mUseMatCorrFlag = MatCorrType::USEMatCorrTGeo; bool mUseBCFilling = false; ///< use BC filling for candidates validation @@ -618,12 +680,15 @@ class MatchTPCITS ///< assigned time0 and its track Z position (converted from mTPCTimeEdgeZSafeMargin) float mTPCTimeEdgeTSafeMargin = 0.f; float mTPCExtConstrainedNSigmaInv = 0.f; // inverse for NSigmas for TPC time-interval from external constraint time sigma - int mITSROFrameLengthInBC = 0; ///< ITS RO frame in BC (for ITS cont. mode only) - float mITSROFrameLengthMUS = -1.; ///< ITS RO frame in \mus - float mITSTimeResMUS = -1.; ///< nominal ITS time resolution derived from ROF - float mITSROFrameLengthMUSInv = -1.; ///< ITS RO frame in \mus inverse - int mITSTimeBiasInBC = 0; ///< ITS RO frame shift in BCs, i.e. t_i = (I_ROF*mITSROFrameLengthInBC + mITSTimeBiasInBC)*BCLength_MUS - float mITSTimeBiasMUS = 0.; ///< ITS RO frame shift in \mus, i.e. t_i = (I_ROF*mITSROFrameLengthInBC)*BCLength_MUS + mITSTimeBiasMUS + ///< ITS ROF timings, always filled for all NITSLayers, also when all layers share the same ROF. + ///< t_i(lr) = (I_ROF*mITSROFrameLengthInBC[lr] + mITSTimeBiasInBC[lr])*BCLength_MUS + int mITSClockLayer = 0; ///< layer defining the ITS tracks ROFRecords granularity + std::array mITSROFrameLengthInBC{}; ///< ITS RO frame in BC per layer (cont. mode only) + std::array mITSROFrameLengthMUS{}; ///< ITS RO frame in \mus per layer + std::array mITSROFrameLengthMUSInv{}; ///< inverse ITS RO frame in \mus per layer + std::array mITSTimeResMUS{}; ///< nominal ITS time resolution derived from per-layer ROF + std::array mITSTimeBiasInBC{}; ///< ITS RO frame shift in BC per layer + std::array mITSTimeBiasMUS{}; ///< ITS RO frame shift in \mus per layer float mTPCVDrift = -1.; ///< TPC drift speed in cm/microseconds float mTPCVDriftInv = -1.; ///< inverse TPC nominal drift speed in cm/microseconds float mTPCDriftTimeOffset = 0; ///< drift time offset in mus @@ -662,15 +727,14 @@ class MatchTPCITS gsl::span mITSTrackROFRec; ///< input ITS tracks ROFRecord span gsl::span mITSTracksArray; ///< input ITS tracks span gsl::span mITSTrackClusIdx; ///< input ITS track cluster indices span - std::vector mITSClustersArray; ///< ITS clusters created in loadInput - std::vector mITSClusterSizes; ///< ITS cluster sizes created in loadInput - - gsl::span mITSClusterROFRec; ///< input ITS clusters ROFRecord span + std::array, NITSLayers> mITSClustersArray{}; ///< ITS clusters created in loadInput + std::array, NITSLayers> mITSClusterSizes{}; ///< ITS cluster sizes created in loadInput + std::array, NITSLayers> mITSClusterROFRec; ///< input ITS clusters ROFRecord span + int mNITSClusters = 0; gsl::span mFITInfo; ///< optional input FIT info span gsl::span mTPCRefitterShMap; ///< externally set TPC clusters sharing map gsl::span mTPCRefitterOccMap; ///< externally set TPC clusters occupancy map - const o2::itsmft::TopologyDictionary* mITSDict{nullptr}; // cluster patterns dictionary #ifdef ENABLE_UPGRADES const o2::its3::TopologyDictionary* mIT3Dict{nullptr}; // cluster patterns dictionary @@ -678,7 +742,7 @@ class MatchTPCITS const o2::tpc::ClusterNativeAccess* mTPCClusterIdxStruct = nullptr; ///< struct holding the TPC cluster indices - const o2::dataformats::MCTruthContainer* mITSClsLabels = nullptr; ///< input ITS Cluster MC labels + std::array*, NITSLayers> mITSClsLabels{}; ///< input ITS Cluster MC labels gsl::span mITSTrkLabels; ///< input ITS Track MC labels gsl::span mTPCTrkLabels; ///< input TPC Track MC labels /// <<<----- @@ -711,7 +775,12 @@ class MatchTPCITS ///< indices of selected track entries in mTPCWork (for tracks selected by AfterBurner) std::vector mTPCABIndexCache; std::vector mABWinnersIDs; - std::vector mABClusterLinkIndex; ///< index of 1st ABClusterLink for every cluster used by AfterBurner, -1: unused, -10: used by external ITS tracks + ///< per storage-slot status of ITS clusters wrt AfterBurner: -1: free, -10: used by an external ITS track or a validated AB track. + ///< The slot is the layer for per-layer clusters input, 0 for the monolithic one; only the slots used by the AfterBurner are booked + ITSClusStatus mABClusterStatus; + std::array mABLayerClusters; ///< per (layer, ROF) blocks of AB-usable clusters; filled for the AB layers and the ROF times also for the clock layer + std::array mABChipsBounds{}; ///< the layer lr owns the global chip IDs [mABChipsBounds[lr], mABChipsBounds[lr+1]) + float mABROFMarginMUS = 0.f; ///< effective margin for candidate time to ITS ROF matching: abROFMarginMUS clamped to below half of the shortest AB layer ROF LinksPoolMT mABLinksPool; ///< per sector indices of TPC track entry in mTPCWork diff --git a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITSParams.h b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITSParams.h index 3ec189deff54b..2dfa5e6012428 100644 --- a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITSParams.h +++ b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITSParams.h @@ -85,6 +85,7 @@ struct MatchTPCITSParams : public o2::conf::ConfigurableParamHelperrunAfterBurner) { // only used in AfterBurner mRGHelper.init(mParams->lowestLayerAB); // prepare helper for TPC track / ITS clusters matching } + { + const auto* geomITS = o2::its::GeometryTGeo::Instance(); + for (int lr = 0; lr < NITSLayers; lr++) { + mABChipsBounds[lr] = geomITS->getFirstChipIndex(lr); + } + mABChipsBounds[NITSLayers] = geomITS->getLastChipIndex(NITSLayers - 1) + 1; + } clear(); @@ -262,30 +276,53 @@ void MatchTPCITS::init() //______________________________________________ void MatchTPCITS::updateTimeDependentParams() { - ///< update parameters depending on time (once per TF) - auto& elParam = o2::tpc::ParameterElectronics::Instance(); - auto& detParam = o2::tpc::ParameterDetector::Instance(); - mTPCTBinMUS = elParam.ZbinWidth; - mTPCTBinNS = mTPCTBinMUS * 1e3; - mTPCZMax = detParam.TPClength; - mTPCTBinMUSInv = 1. / mTPCTBinMUS; - assert(mITSROFrameLengthMUS > 0.0f); + ///< update parameters depending on time (once per TF or beginning of run) + mBz = o2::base::Propagator::Instance()->getNominalBz(); + mFieldON = std::abs(mBz) > 0.01; + + static bool initOnceDone = false; + if (!initOnceDone) { + initOnceDone = true; + if (mParams->runAfterBurner) { + // margin for matching interaction candidate times to per-layer ITS ROFs in the AfterBurner: must stay + // below half of the shortest AB layer ROF length to guarantee at most 2 compatible ROFs per layer + float margin = std::max(0.f, mParams->abROFMarginMUS), marginMax = mITSROFrameLengthMUS[mParams->lowestLayerAB]; + for (int lr = mParams->lowestLayerAB + 1; lr < NITSLayers; lr++) { + if (mITSROFrameLengthMUS[lr] < marginMax) { + marginMax = mITSROFrameLengthMUS[lr]; + } + } + marginMax *= 0.45f; + if (margin > marginMax) { + if (mABROFMarginMUS != marginMax) { + LOGP(warn, "abROFMarginMUS={} exceeds 45% of the shortest AfterBurner layer ROF length, clamping to {}", margin, marginMax); + } + margin = marginMax; + } + mABROFMarginMUS = margin; + } + auto& elParam = o2::tpc::ParameterElectronics::Instance(); + auto& detParam = o2::tpc::ParameterDetector::Instance(); + mTPCTBinMUS = elParam.ZbinWidth; + mTPCTBinNS = mTPCTBinMUS * 1e3; + mTPCZMax = detParam.TPClength; + mTPCTBinMUSInv = 1. / mTPCTBinMUS; + assert(mITSROFrameLengthMUS[mITSClockLayer] > 0.0f); + + o2::math_utils::Point3D p0(90., 1., 1), p1(90., 100., 100.); + auto matbd = o2::base::Propagator::Instance()->getMatBudget(mParams->matCorr, p0, p1); + mTPCmeanX0Inv = matbd.meanX2X0 / matbd.length; + } mTPCBin2Z = mTPCTBinMUS * mTPCVDrift; mZ2TPCBin = 1. / mTPCBin2Z; mTPCVDriftInv = 1. / mTPCVDrift; mNTPCBinsFullDrift = mTPCZMax * mZ2TPCBin; mTPCTimeEdgeTSafeMargin = z2TPCBin(mParams->safeMarginTPCTimeEdge); mTPCExtConstrainedNSigmaInv = 1.f / mParams->tpcExtConstrainedNSigma; - mBz = o2::base::Propagator::Instance()->getNominalBz(); - mFieldON = std::abs(mBz) > 0.01; mMinTPCTrackPtInv = (mFieldON && mParams->minTPCTrackR > 0) ? 1. / std::abs(mParams->minTPCTrackR * mBz * o2::constants::math::B2C) : 999.; mMinITSTrackPtInv = (mFieldON && mParams->minITSTrackR > 0) ? 1. / std::abs(mParams->minITSTrackR * mBz * o2::constants::math::B2C) : 999.; - o2::math_utils::Point3D p0(90., 1., 1), p1(90., 100., 100.); - auto matbd = o2::base::Propagator::Instance()->getMatBudget(mParams->matCorr, p0, p1); - mTPCmeanX0Inv = matbd.meanX2X0 / matbd.length; - const auto& trackTune = TrackTuneParams::Instance(); float scale = mLumiCTP; if (scale < 0.f) { @@ -645,57 +682,83 @@ bool MatchTPCITS::prepareITSData() const auto& inp = *mRecoCont; // ITS clusters - mITSClusterROFRec = inp.getITSClustersROFRecords(); - const auto clusITS = inp.getITSClusters(); - if (mITSClusterROFRec.empty() || clusITS.empty()) { - LOG(info) << "No ITS clusters"; - return false; - } - const auto patterns = inp.getITSClustersPatterns(); - auto pattIt = patterns.begin(); - mITSClustersArray.reserve(clusITS.size()); + int nClLr = inp.getITSPerLayer() ? NITSLayers : 1; + mNITSClusters = 0; + for (int lr = 0; lr < nClLr; lr++) { + mITSClusterROFRec[lr] = inp.getITSClustersROFRecords(lr); + const auto clusITS = inp.getITSClusters(lr); + mNITSClusters += clusITS.size(); + const auto patterns = inp.getITSClustersPatterns(lr); + auto pattIt = patterns.begin(); + mITSClustersArray[lr].reserve(clusITS.size()); #ifdef ENABLE_UPGRADES - bool withITS3 = o2::GlobalParams::Instance().withITS3; - if (withITS3) { - o2::its3::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mIT3Dict); - } else { - o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mITSDict); - } + bool withITS3 = o2::GlobalParams::Instance().withITS3; + if (withITS3) { + o2::its3::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray[lr], mIT3Dict); + } else { + o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray[lr], mITSDict); + } #else - o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mITSDict); + o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray[lr], mITSDict); #endif - - // ITS clusters sizes - mITSClusterSizes.reserve(clusITS.size()); - auto pattIt2 = patterns.begin(); - for (auto& clus : clusITS) { - auto pattID = clus.getPatternID(); - unsigned int npix; + // ITS clusters sizes + mITSClusterSizes[lr].reserve(clusITS.size()); + auto pattIt2 = patterns.begin(); + for (auto& clus : clusITS) { + auto pattID = clus.getPatternID(); + unsigned int npix; #ifdef ENABLE_UPGRADES - auto ib = o2::its3::constants::detID::isDetITS3(clus.getChipID()); - if ((pattID == o2::itsmft::CompCluster::InvalidPatternID) || ((withITS3) ? mIT3Dict->isGroup(pattID, ib) : mITSDict->isGroup(pattID))) { // braces guarantee evaluation order + auto ib = o2::its3::constants::detID::isDetITS3(clus.getChipID()); + if ((pattID == o2::itsmft::CompCluster::InvalidPatternID) || ((withITS3) ? mIT3Dict->isGroup(pattID, ib) : mITSDict->isGroup(pattID))) { // braces guarantee evaluation order #else - if (pattID == o2::itsmft::CompCluster::InvalidPatternID || mITSDict->isGroup(pattID)) { + if (pattID == o2::itsmft::CompCluster::InvalidPatternID || mITSDict->isGroup(pattID)) { #endif - o2::itsmft::ClusterPattern patt; - patt.acquirePattern(pattIt2); - npix = patt.getNPixels(); - } else { -#ifdef ENABLE_UPGRADES - if (withITS3) { - npix = mIT3Dict->getNpixels(pattID, ib); + o2::itsmft::ClusterPattern patt; + patt.acquirePattern(pattIt2); + npix = patt.getNPixels(); } else { - npix = mITSDict->getNpixels(pattID); - } +#ifdef ENABLE_UPGRADES + if (withITS3) { + npix = mIT3Dict->getNpixels(pattID, ib); + } else { + npix = mITSDict->getNpixels(pattID); + } #else - npix = mITSDict->getNpixels(pattID); + npix = mITSDict->getNpixels(pattID); #endif + } + mITSClusterSizes[lr].push_back(std::clamp(npix, 0u, 255u)); + } + if (mMCTruthON) { + mITSClsLabels[lr] = inp.getITSClustersMCLabels(lr); + } + } + if (!mNITSClusters) { + LOGP(warn, "No ITS clusters"); + return false; + } + // time brackets of the cluster ROFs: the clock-layer ones relate interaction candidates to ITS ROFs, + // the AfterBurner layers ones select the clusters compatible with the candidate time + for (int lr = 0; lr < NITSLayers; lr++) { + auto& abClus = mABLayerClusters[lr]; + abClus.clear(); + if (lr != mITSClockLayer && !(mParams->runAfterBurner && lr >= mParams->lowestLayerAB)) { + continue; + } + const auto& rofs = mITSClusterROFRec[inp.getITSPerLayer() ? lr : 0]; + abClus.rofTimes.reserve(rofs.size()); + for (const auto& rofRec : rofs) { + abClus.rofTimes.push_back(getITSROFTimeBracket(rofRec.getBCData().differenceInBC(mStartIR), lr)); } - mITSClusterSizes.push_back(std::clamp(npix, 0u, 255u)); } - if (mMCTruthON) { - mITSClsLabels = inp.getITSClustersMCLabels(); + // the AfterBurner uses only layers from mParams->lowestLayerAB, with single ITS clusters input all of them are in the slot 0 + if (inp.getITSPerLayer()) { + for (int lr = mParams->lowestLayerAB; lr < NITSLayers; lr++) { + mABClusterStatus[lr].resize(mITSClustersArray[lr].size(), MinusOne); + } + } else { + mABClusterStatus[0].resize(mITSClustersArray[0].size(), MinusOne); } // ITS tracks @@ -708,10 +771,6 @@ bool MatchTPCITS::prepareITSData() int nROFs = mITSTrackROFRec.size(); mITSWork.reserve(mITSTracksArray.size()); - // total N ITS clusters in TF - const auto& lastClROF = mITSClusterROFRec.back(); - int nITSClus = lastClROF.getFirstEntry() + lastClROF.getNEntries(); - mABClusterLinkIndex.resize(nITSClus, MinusOne); for (int sec = o2::constants::math::NSectors; sec--;) { mITSTimeStart[sec].resize(nROFs, -1); // start of ITS work tracks in every sector } @@ -730,10 +789,10 @@ bool MatchTPCITS::prepareITSData() } break; } - float tMin = nBC * o2::constants::lhc::LHCBunchSpacingMUS + mITSTimeBiasMUS; - float tMax = (nBC + mITSROFrameLengthInBC) * o2::constants::lhc::LHCBunchSpacingMUS + mITSTimeBiasMUS; + auto tBracket = getITSROFTimeBracket(nBC, mITSClockLayer); + float tMin = tBracket.getMin(), tMax = tBracket.getMax(); if (!mITSTriggered) { - size_t irofCont = nBC / mITSROFrameLengthInBC; + size_t irofCont = nBC / mITSROFrameLengthInBC[mITSClockLayer]; if (mITSTrackROFContMapping.size() <= irofCont) { // there might be gaps in the non-empty rofs, this will map continuous ROFs index to non empty ones mITSTrackROFContMapping.resize((1 + irofCont / 128) * 128, 0); } @@ -834,7 +893,7 @@ bool MatchTPCITS::prepareITSData() mMatchRecordsITS.reserve(mITSWork.size() * mParams->maxMatchCandidates); mTimer[SWPrepITS].Stop(); - return nITSClus > 0; + return mNITSClusters > 0; } //_____________________________________________________ @@ -1569,7 +1628,7 @@ void MatchTPCITS::fillCalibDebug(int ifit, int iTPC, const o2::dataformats::Trac (*mDBGOut) << "refit" << "multTPC=" << mltTPC << "multITSTr=" << mITSTrackROFRec[tITS.roFrame].getNEntries() - << "multITSCl=" << mITSClusterROFRec[tITS.roFrame].getNEntries() + << "multITSCl=" << mNITSClusters << "tf=" << mTFCount << "\n"; } #endif @@ -1599,8 +1658,8 @@ bool MatchTPCITS::refitTrackTPCITS(int slot, int iTPC, int& iITS, pmr::vector mITSTimeResMUS && tTPC.constraint != TrackLocTPC::Constrained) { - timeErr = mITSTimeResMUS; // chose smallest error + if (timeErr > mITSTimeResMUS[mITSClockLayer] && tTPC.constraint != TrackLocTPC::Constrained) { + timeErr = mITSTimeResMUS[mITSClockLayer]; // chose smallest error deltaT = tTPC.constraint == TrackLocTPC::ASide ? tITS.tBracket.mean() - tTPC.time0 : tTPC.time0 - tITS.tBracket.mean(); } timeErr += mParams->globalTimeExtraErrorMUS; @@ -1638,7 +1697,7 @@ bool MatchTPCITS::refitTrackTPCITS(int slot, int iTPC, int& iITS, pmr::vectorgetSensorRefAlpha(clus.getSensorID()), x = clus.getX(); if (!trfit.rotate(alpha) || // note: here we also calculate the L,T integral (in the inward direction, but this is irrelevant) @@ -1763,7 +1822,7 @@ bool MatchTPCITS::refitABTrack(int iITSAB, const TPCABSeed& seed, pmr::vectorgetSensorRefAlpha(clus.getSensorID()), x = clus.getX(); if (!tracOut.rotate(alpha, refLin, propagator->getNominalBz()) || // note: here we also calculate the L,T integral @@ -1921,10 +1980,15 @@ int MatchTPCITS::prepareABSeeds() //______________________________________________ int MatchTPCITS::prepareInteractionTimes() { - // guess interaction times from various sources and relate with ITS rofs + // Guess interaction times from various sources and relate them to the ITS cluster ROFs of the clock layer. + // For every candidate define also the per-layer cluster ROFs compatible with its time within the + // mABROFMarginMUS margin: they define the clusters the AfterBurner will check for this candidate. const float ft0Uncertainty = 0.5e-3; - int nITSROFs = mITSROFTimes.size(); - if (mFITInfo.size()) { + const auto& clockROFTimes = mABLayerClusters[mITSClockLayer].rofTimes; + int nClockROFs = clockROFTimes.size(); + int lowestAB = mParams->runAfterBurner ? mParams->lowestLayerAB : NITSLayers; + std::array ptrLr{}; // monotonic pointers on the per-layer ROFs (candidates are time-ordered) + if (mFITInfo.size() && nClockROFs) { int rof = 0; for (const auto& ft : mFITInfo) { if (!mFT0Params->isSelected(ft)) { @@ -1934,22 +1998,33 @@ int MatchTPCITS::prepareInteractionTimes() if (fitTime < 0) { // should not happen continue; } + while (rof < nClockROFs && clockROFTimes[rof].getMax() + mABROFMarginMUS < fitTime) { + rof++; + } + if (rof >= nClockROFs) { // no candidates beyond the last cluster ROF of the clock layer + break; + } if (size_t(fitTime) >= mInteractionMUSLUT.size()) { mInteractionMUSLUT.resize(size_t(fitTime) + 1, -1); } if (mInteractionMUSLUT[fitTime] < 0) { mInteractionMUSLUT[fitTime] = mInteractions.size(); } - for (; rof < nITSROFs; rof++) { - if (mITSROFTimes[rof] < fitTime) { - continue; + auto& intCand = mInteractions.emplace_back(ft.getInteractionRecord(), fitTime, ft0Uncertainty, rof, o2::detectors::DetID::FT0); + for (int lr = lowestAB; lr < NITSLayers; lr++) { // relate the candidate time to compatible ROFs of the AB layers + const auto& rofTimes = mABLayerClusters[lr].rofTimes; + int nROFsLr = (int)rofTimes.size(); + int& ptr = ptrLr[lr]; + while (ptr < nROFsLr && rofTimes[ptr].getMax() + mABROFMarginMUS < fitTime) { + ptr++; + } + if (ptr < nROFsLr && fitTime >= rofTimes[ptr].getMin() - mABROFMarginMUS) { + intCand.rofLr[lr] = ptr; + if (ptr + 1 < nROFsLr && fitTime >= rofTimes[ptr + 1].getMin() - mABROFMarginMUS) { + intCand.rofNextLr |= 0x1 << lr; // compatible also with the next ROF of the layer + } } - break; - } - if (rof >= nITSROFs) { - break; } - mInteractions.emplace_back(ft.getInteractionRecord(), fitTime, ft0Uncertainty, rof, o2::detectors::DetID::FT0); } } int ent = 0; @@ -1963,6 +2038,164 @@ int MatchTPCITS::prepareInteractionTimes() return mInteractions.size(); } +//______________________________________________ +void MatchTPCITS::prepareABClusters() +{ + // Build per (layer, ROF) blocks of the clusters usable by the AfterBurner (i.e. not attached to ITS + // tracks), sorted in (chip, Z). Only the blocks referenced by interaction candidates with seeds are built. + const bool perLayer = mRecoCont->getITSPerLayer(); + const int lowestAB = mParams->lowestLayerAB; + for (int lr = lowestAB; lr < NITSLayers; lr++) { + auto& abClus = mABLayerClusters[lr]; + abClus.needROF.assign(abClus.rofTimes.size(), 0); + abClus.rofRefs.assign(abClus.rofTimes.size(), ClusRange(-1, 0)); + } + for (const auto& intCand : mInteractions) { // mark the blocks to build + if (!intCand.seedsRef.getEntries()) { + continue; + } + for (int lr = lowestAB; lr < NITSLayers; lr++) { + int rof = intCand.rofLr[lr]; + if (rof < 0) { + continue; + } + auto& need = mABLayerClusters[lr].needROF; + need[rof] = 1; + if ((intCand.rofNextLr >> lr) & 0x1) { + need[rof + 1] = 1; + } + } + } + struct ABBlock { + int lr = 0, rof = 0, nCl = 0, offs = 0; + }; + std::vector blocks; + for (int lr = lowestAB; lr < NITSLayers; lr++) { + const auto& need = mABLayerClusters[lr].needROF; + for (int rof = 0; rof < (int)need.size(); rof++) { + if (need[rof]) { + blocks.push_back({lr, rof}); + } + } + } + if (blocks.empty()) { + return; + } + int nBlocks = blocks.size(); + // count AB-usable clusters of every block +#ifdef WITH_OPENMP +#pragma omp parallel for schedule(dynamic) num_threads(mNThreads) +#endif + for (int ib = 0; ib < nBlocks; ib++) { + auto& blk = blocks[ib]; + int slot = perLayer ? blk.lr : 0; + const auto& rofRec = mITSClusterROFRec[slot][blk.rof]; + const auto& status = mABClusterStatus[slot]; + int last = rofRec.getFirstEntry() + rofRec.getNEntries(), nCl = 0; + if (perLayer) { + for (int icl = rofRec.getFirstEntry(); icl < last; icl++) { + if (status[icl] != MinusTen) { + nCl++; + } + } + } else { // select only the clusters of the block layer + const auto& clusArr = mITSClustersArray[0]; + int chipMin = mABChipsBounds[blk.lr], chipMax = mABChipsBounds[blk.lr + 1]; + for (int icl = rofRec.getFirstEntry(); icl < last; icl++) { + int chip = clusArr[icl].getSensorID(); + if (chip >= chipMin && chip < chipMax && status[icl] != MinusTen) { + nCl++; + } + } + } + blk.nCl = nCl; + } + // assign per-layer storage offsets + std::array layerSizes{}; + for (auto& blk : blocks) { + blk.offs = layerSizes[blk.lr]; + layerSizes[blk.lr] += blk.nCl; + } + for (int lr = lowestAB; lr < NITSLayers; lr++) { + mABLayerClusters[lr].clus.resize(layerSizes[lr]); + } + // fill and sort the blocks +#ifdef WITH_OPENMP +#pragma omp parallel for schedule(dynamic) num_threads(mNThreads) +#endif + for (int ib = 0; ib < nBlocks; ib++) { + const auto& blk = blocks[ib]; + auto& abClus = mABLayerClusters[blk.lr]; + int slot = perLayer ? blk.lr : 0; + const auto& rofRec = mITSClusterROFRec[slot][blk.rof]; + const auto& status = mABClusterStatus[slot]; + const auto& clusArr = mITSClustersArray[slot]; + int last = rofRec.getFirstEntry() + rofRec.getNEntries(); + int chipMin = mABChipsBounds[blk.lr], chipMax = mABChipsBounds[blk.lr + 1]; + ABClusterInfo* dst = abClus.clus.data() + blk.offs; + int nCl = 0; + for (int icl = rofRec.getFirstEntry(); icl < last; icl++) { + const auto& cls = clusArr[icl]; + int chip = cls.getSensorID(); + if ((perLayer || (chip >= chipMin && chip < chipMax)) && status[icl] != MinusTen) { + assert(chip >= chipMin && chip < chipMax); // clusters of a per-layer slot must belong to its layer + dst[nCl++] = {cls.getY(), cls.getZ(), composeITSClusID(slot, icl), chip}; + } + } + assert(nCl == blk.nCl); + std::sort(dst, dst + nCl, [](const ABClusterInfo& a, const ABClusterInfo& b) { return a.chip < b.chip || (a.chip == b.chip && a.z < b.z); }); + abClus.rofRefs[blk.rof].set(blk.offs, nCl); + } +} + +//______________________________________________ +void MatchTPCITS::updateABLayerView(ABLayerView& view, int lr, int rof, int nROFs) const +{ + // Point the thread-local view to the AB clusters block(s) of the layer covering the requested ROF(s), + // refreshing the chip->clusters references. Does nothing if the requested block(s) are already loaded. + if (view.rofKey == rof && view.nROFsKey == nROFs) { + return; + } + for (int chip : view.touchedChips) { // reset previously registered references + view.chipRefs[chip].setEntries(0); + } + view.touchedChips.clear(); + view.rofKey = rof; + view.nROFsKey = nROFs; + view.data = nullptr; + view.nData = 0; + if (rof < 0) { // no ROF of this layer is compatible with the candidate time + return; + } + const auto& abClus = mABLayerClusters[lr]; + const auto& ref0 = abClus.rofRefs[rof]; + assert(ref0.getFirstEntry() >= 0); // the block must have been built in prepareABClusters + if (nROFs == 1) { + view.data = abClus.clus.data() + ref0.getFirstEntry(); + view.nData = ref0.getEntries(); + } else { // merge the 2 blocks, each sorted in (chip, Z) + const auto& ref1 = abClus.rofRefs[rof + 1]; + assert(ref1.getFirstEntry() >= 0); + view.merged.resize(ref0.getEntries() + ref1.getEntries()); + std::merge(abClus.clus.begin() + ref0.getFirstEntry(), abClus.clus.begin() + ref0.getEntriesBound(), + abClus.clus.begin() + ref1.getFirstEntry(), abClus.clus.begin() + ref1.getEntriesBound(), + view.merged.begin(), [](const ABClusterInfo& a, const ABClusterInfo& b) { return a.chip < b.chip || (a.chip == b.chip && a.z < b.z); }); + view.data = view.merged.data(); + view.nData = (int)view.merged.size(); + } + int icl = 0; + while (icl < view.nData) { // register the cluster ranges of the chips + int chip = view.data[icl].chip, jcl = icl + 1; + while (jcl < view.nData && view.data[jcl].chip == chip) { + jcl++; + } + int chipLoc = chip - view.chipOffs; + view.chipRefs[chipLoc].set(icl, jcl - icl); + view.touchedChips.push_back(chipLoc); + icl = jcl; + } +} + //______________________________________________ bool MatchTPCITS::runAfterBurner(pmr::vector& matchedTracks, pmr::vector& matchLabels, pmr::vector& ABTrackletLabels, pmr::vector& ABTrackletClusterIDs, pmr::vector& ABTrackletRefs, pmr::vector>& calib) @@ -1977,33 +2210,77 @@ bool MatchTPCITS::runAfterBurner(pmr::vector& matc return false; } mTimer[SWABMatch].Start(false); - - std::vector itsChipClRefsBuff(mNThreads); -#ifdef ENABLE_UPGRADES - // with upgrades the datatype changed, hence we need to initialize - // each element individually - std::generate(itsChipClRefsBuff.begin(), itsChipClRefsBuff.end(), []() { - return ITSChipClustersRefs(o2::its::GeometryTGeo::Instance()->getNumberOfChips()); - }); -#endif - -#ifdef WITH_OPENMP -#pragma omp parallel for schedule(dynamic) num_threads(mNThreads) -#endif + prepareABClusters(); // build the blocks of AB-usable clusters for the (layer, ROF)s referenced by the candidates + + // Group together consecutive candidates with seeds sharing the same per-layer ROFs: within a group the + // thread-local cluster views stay valid, so they are (re)loaded at most once per group and layer + int lowestAB = mParams->lowestLayerAB; + std::vector seededIC; + std::vector> icGroups; // ranges (1st, last entry) in seededIC + seededIC.reserve(nIntCand); for (int ic = 0; ic < nIntCand; ic++) { const auto& intCand = mInteractions[ic]; - LOGP(debug, "cand T {} Entries: {} : {} : {} | ITS ROF: {}", intCand.tBracket.mean(), intCand.seedsRef.getEntries(), intCand.seedsRef.getFirstEntry(), intCand.seedsRef.getEntriesBound(), intCand.rofITS); if (!intCand.seedsRef.getEntries()) { continue; } + bool anyROF = false; + for (int lr = lowestAB; lr < NITSLayers; lr++) { + if (intCand.rofLr[lr] >= 0) { + anyROF = true; + break; + } + } + if (!anyROF) { // no AB layer has clusters at the candidate time: discard the seeds of this candidate + for (int is = intCand.seedsRef.getFirstEntry(); is < intCand.seedsRef.getEntriesBound(); is++) { + mTPCABSeeds[is].disable(); + } + continue; + } + bool sameROFs = false; + if (!seededIC.empty()) { + const auto& prevCand = mInteractions[seededIC.back()]; + sameROFs = prevCand.rofNextLr == intCand.rofNextLr; + for (int lr = lowestAB; sameROFs && lr < NITSLayers; lr++) { + sameROFs = prevCand.rofLr[lr] == intCand.rofLr[lr]; + } + } + if (sameROFs) { + icGroups.back().second = (int)seededIC.size(); + } else { + icGroups.emplace_back((int)seededIC.size(), (int)seededIC.size()); + } + seededIC.push_back(ic); + } + + std::vector itsClViewsBuff(mNThreads); + for (auto& views : itsClViewsBuff) { // book the chip->clusters references of the AB layers + for (int lr = lowestAB; lr < NITSLayers; lr++) { + auto& view = views.layers[lr]; + view.chipOffs = mABChipsBounds[lr]; + view.chipRefs.resize(mABChipsBounds[lr + 1] - mABChipsBounds[lr], ClusRange(0, 0)); + } + } + +#ifdef WITH_OPENMP +#pragma omp parallel for schedule(dynamic) num_threads(mNThreads) +#endif + for (int ig = 0; ig < (int)icGroups.size(); ig++) { #ifdef WITH_OPENMP uint8_t tid = (uint8_t)omp_get_thread_num(); #else uint8_t tid = 0; #endif - fillClustersForAfterBurner(intCand.rofITS, 1, itsChipClRefsBuff[tid]); // RS FIXME account for possibility of filling 2 ROFs - for (int is = intCand.seedsRef.getFirstEntry(); is < intCand.seedsRef.getEntriesBound(); is++) { // loop over all seeds of this interaction candidate - processABSeed(is, itsChipClRefsBuff[tid], tid); + auto& views = itsClViewsBuff[tid]; + const auto& groupCand = mInteractions[seededIC[icGroups[ig].first]]; // all candidates of the group share the same ROFs + for (int lr = lowestAB; lr < NITSLayers; lr++) { + updateABLayerView(views.layers[lr], lr, groupCand.rofLr[lr], (groupCand.rofNextLr >> lr) & 0x1 ? 2 : 1); + } + for (int j = icGroups[ig].first; j <= icGroups[ig].second; j++) { + const auto& intCand = mInteractions[seededIC[j]]; + LOGP(debug, "cand T {} Entries: {} : {} : {} | ITS clock ROF: {}", intCand.tBracket.mean(), intCand.seedsRef.getEntries(), intCand.seedsRef.getFirstEntry(), intCand.seedsRef.getEntriesBound(), intCand.rofITS); + for (int is = intCand.seedsRef.getFirstEntry(); is < intCand.seedsRef.getEntriesBound(); is++) { // loop over all seeds of this interaction candidate + processABSeed(is, views, tid); + } } } mTimer[SWABMatch].Stop(); @@ -2039,13 +2316,13 @@ bool MatchTPCITS::runAfterBurner(pmr::vector& matc continue; } auto bestID = ABSeed.getBestLinkID(); - if (ABSeed.checkLinkHasUsedClusters(bestID, mABClusterLinkIndex)) { + if (ABSeed.checkLinkHasUsedClusters(bestID, mABClusterStatus)) { ABSeed.setNeedAlternative(); // flag for later processing // RSTMP LOG(info) << "Iter: " << iter << " seed has used clusters " << i << "[" << candAB[i].seedID << "/" << candAB[i].chi2 << "]" << " last lr: " << int(ABSeed.lowestLayer) << " Ncont: " << int(link.nContLayers);; continue; } ABSeed.validate(bestID); - ABSeed.flagLinkUsedClusters(bestID, mABClusterLinkIndex); + ABSeed.flagLinkUsedClusters(bestID, mABClusterStatus); mABWinnersIDs.push_back(tTPC.matchID = candAB[i].seedID); mNABRefsClus += ABSeed.getNLayers(); nwin++; @@ -2079,8 +2356,8 @@ void MatchTPCITS::refitABWinners(pmr::vector& matc } std::map labelOccurence; - auto accountClusterLabel = [&labelOccurence, itsClLabs = mITSClsLabels](int clID) { - auto labels = itsClLabs->getLabels(clID); + auto accountClusterLabel = [&labelOccurence, this](int clID) { + auto labels = mITSClsLabels[ITSClusID2Layer(clID)]->getLabels(ITSClusID2Index(clID)); for (auto lab : labels) { // check all labels of the cluster if (lab.isSet()) { labelOccurence[lab]++; @@ -2099,7 +2376,7 @@ void MatchTPCITS::refitABWinners(pmr::vector& matc ABTrackletClusterIDs.push_back(winL.clID); ncl++; clref.pattern |= 0x1 << winL.layerID; - clref.setClusterSize(winL.layerID, mITSClusterSizes[winL.clID]); + clref.setClusterSize(winL.layerID, mITSClusterSizes[ITSClusID2Layer(winL.clID)][ITSClusID2Index(winL.clID)]); if (mMCTruthON) { accountClusterLabel(winL.clID); } @@ -2139,13 +2416,13 @@ void MatchTPCITS::refitABWinners(pmr::vector& matc } //______________________________________________ -void MatchTPCITS::processABSeed(int sid, const ITSChipClustersRefs& itsChipClRefs, uint8_t tID) +void MatchTPCITS::processABSeed(int sid, const ABThreadClusterViews& itsClViews, uint8_t tID) { // prepare matching hypothesis tree for given seed auto& ABSeed = mTPCABSeeds[sid]; ABSeed.threadID = tID; ABSeed.linksEntry = mABLinksPool.threadPool[tID].size(); - followABSeed(ABSeed.track, itsChipClRefs, MinusTen, NITSLayers - 1, ABSeed); // check matches on outermost layer + followABSeed(ABSeed.track, itsClViews.layers[NITSLayers - 1], MinusTen, NITSLayers - 1, ABSeed); // check matches on outermost layer for (int ilr = NITSLayers - 1; ilr > mParams->lowestLayerAB; ilr--) { int nextLinkID = ABSeed.firstInLr[ilr]; if (nextLinkID < 0) { @@ -2158,7 +2435,7 @@ void MatchTPCITS::processABSeed(int sid, const ITSChipClustersRefs& itsChipClRef continue; } int next2nextLinkID = seedLink.nextOnLr; // fetch now since the seedLink may change due to the relocation - followABSeed(seedLink, itsChipClRefs, nextLinkID, ilr - 1, ABSeed); // check matches on the next layer + followABSeed(seedLink, itsClViews.layers[ilr - 1], nextLinkID, ilr - 1, ABSeed); // check matches on the next layer nextLinkID = next2nextLinkID; } } @@ -2185,9 +2462,11 @@ void MatchTPCITS::processABSeed(int sid, const ITSChipClustersRefs& itsChipClRef } //______________________________________________ -int MatchTPCITS::followABSeed(const o2::track::TrackParCov& seed, const ITSChipClustersRefs& itsChipClRefs, int seedID, int lrID, TPCABSeed& ABSeed) +int MatchTPCITS::followABSeed(const o2::track::TrackParCov& seed, const ABLayerView& clView, int seedID, int lrID, TPCABSeed& ABSeed) { - + if (!clView.nData) { // no AB-usable clusters on this layer at the interaction candidate time + return 0; + } auto propagator = o2::base::Propagator::Instance(); float xTgt; const auto& lr = mRGHelper.layers[lrID]; @@ -2248,7 +2527,7 @@ int MatchTPCITS::followABSeed(const o2::track::TrackParCov& seed, const ITSChipC if (lad.chips[chipID].zRange.isOutside(zCross, mParams->nABSigmaZ * errZ)) { continue; } - const auto& clRange = itsChipClRefs.chipRefs[lad.chips[chipID].id]; + const auto& clRange = clView.chipRefs[lad.chips[chipID].id - clView.chipOffs]; if (!clRange.getEntries()) { LOG(debug) << "No clusters in chip range"; continue; @@ -2257,7 +2536,7 @@ int MatchTPCITS::followABSeed(const o2::track::TrackParCov& seed, const ITSChipC float errYcalp = errY * (csa * chipC.csAlp + sna * chipC.snAlp); // sigY_rotate(from alpha0 to alpha1) = sigY * cos(alpha1 - alpha0); float tolerZ = errZ * mParams->nABSigmaZ, tolerY = errYcalp * mParams->nABSigmaY; float yTrack = -xCross * chipC.snAlp + yCross * chipC.csAlp; // track-chip crossing Y in chip frame - if (!preselectChipClusters(chipSelClusters, clRange, itsChipClRefs, yTrack, zCross, tolerY, tolerZ)) { // select candidate clusters for this chip + if (!preselectChipClusters(chipSelClusters, clRange, clView, yTrack, zCross, tolerY, tolerZ)) { // select candidate clusters for this chip LOG(debug) << "No compatible clusters found"; continue; } @@ -2270,7 +2549,7 @@ int MatchTPCITS::followABSeed(const o2::track::TrackParCov& seed, const ITSChipC } for (auto clID : chipSelClusters) { - const auto& cls = mITSClustersArray[clID]; + const auto& cls = getITSCluster(clID); auto chi2 = trcLC.getPredictedChi2(cls); if (chi2 > mParams->cutABTrack2ClChi2) { continue; @@ -2470,79 +2749,44 @@ float MatchTPCITS::correctTPCTrack(o2::track::TrackParCov& trc, const TrackLocTP } //______________________________________________ -void MatchTPCITS::fillClustersForAfterBurner(int rofStart, int nROFs, ITSChipClustersRefs& itsChipClRefs) -{ - // Prepare unused clusters of given ROFs range for matching in the afterburner - // Note: normally only 1 ROF needs to be filled (nROFs==1 ) unless we want - // to account for interaction on the boundary of 2 rofs, which then may contribute to both ROFs. - int first = mITSClusterROFRec[rofStart].getFirstEntry(), last = first; - for (int ir = nROFs; ir--;) { - last += mITSClusterROFRec[rofStart + ir].getNEntries(); - } - itsChipClRefs.clear(); - auto& idxSort = itsChipClRefs.clusterID; - for (int icl = first; icl < last; icl++) { - if (mABClusterLinkIndex[icl] != MinusTen) { // clusters with MinusOne are used in main matching - idxSort.push_back(icl); - } - } - // sort in chip, Z - const auto& clusArr = mITSClustersArray; - std::sort(idxSort.begin(), idxSort.end(), [&clusArr](int i, int j) { - const auto &clI = clusArr[i], &clJ = clusArr[j]; - if (clI.getSensorID() < clJ.getSensorID()) { - return true; - } - if (clI.getSensorID() == clJ.getSensorID()) { - return clI.getZ() < clJ.getZ(); - } - return false; - }); - - int ncl = idxSort.size(); - int lastSens = -1, nClInSens = 0; - ClusRange* chipClRefs = nullptr; - for (int icl = 0; icl < ncl; icl++) { - const auto& clus = mITSClustersArray[idxSort[icl]]; - int sens = clus.getSensorID(); - if (sens != lastSens) { - if (chipClRefs) { // finalize chip reference - chipClRefs->setEntries(nClInSens); - nClInSens = 0; - } - chipClRefs = &itsChipClRefs.chipRefs[(lastSens = sens)]; - chipClRefs->setFirstEntry(icl); - } - nClInSens++; - } - if (chipClRefs) { - chipClRefs->setEntries(nClInSens); // finalize last chip reference - } -} - -//______________________________________________ -void MatchTPCITS::setITSTimeBiasInBC(int n) -{ - mITSTimeBiasInBC = n; - mITSTimeBiasMUS = mITSTimeBiasInBC * o2::constants::lhc::LHCBunchSpacingNS * 1e-3; -} - -//______________________________________________ -void MatchTPCITS::setITSROFrameLengthMUS(float fums) -{ - mITSROFrameLengthMUS = fums; - mITSTimeResMUS = mITSROFrameLengthMUS / std::sqrt(12.f); - mITSROFrameLengthMUSInv = 1. / mITSROFrameLengthMUS; - mITSROFrameLengthInBC = std::max(1, int(mITSROFrameLengthMUS / (o2::constants::lhc::LHCBunchSpacingNS * 1e-3))); -} - -//______________________________________________ -void MatchTPCITS::setITSROFrameLengthInBC(int nbc) +void MatchTPCITS::setAlpideParam(const AlpParamITS* p) { - mITSROFrameLengthInBC = nbc; - mITSROFrameLengthMUS = nbc * o2::constants::lhc::LHCBunchSpacingNS * 1e-3; - mITSTimeResMUS = mITSROFrameLengthMUS / std::sqrt(12.f); - mITSROFrameLengthMUSInv = 1. / mITSROFrameLengthMUS; + ///< derive all ITS ROF timings from the Alpide parameters. The per-layer arrays are always + ///< filled for all NITSLayers, also when every layer shares the same ROF length and bias. + if (!p) { + LOG(fatal) << "ITS Alpide parameters pointer is null"; + } + mAlpParams = p; + constexpr float BCLenMUS = o2::constants::lhc::LHCBunchSpacingMUS; + unsigned int maxNROFsPerOrbit = 0; + mITSClockLayer = 0; + for (int lr = 0; lr < NITSLayers; lr++) { + if (mITSTriggered) { // in the triggered mode all layers are read out together + mITSROFrameLengthMUS[lr] = p->roFrameLengthTrig * 1e-3; + mITSROFrameLengthInBC[lr] = std::max(1, int(mITSROFrameLengthMUS[lr] / BCLenMUS)); + } else { + mITSROFrameLengthInBC[lr] = p->getROFLengthInBC(lr); + mITSROFrameLengthMUS[lr] = mITSROFrameLengthInBC[lr] * BCLenMUS; + } + mITSROFrameLengthMUSInv[lr] = 1.f / mITSROFrameLengthMUS[lr]; + mITSTimeResMUS[lr] = mITSROFrameLengthMUS[lr] / std::sqrt(12.f); + mITSTimeBiasInBC[lr] = p->getROFBiasInBC(lr); + mITSTimeBiasMUS[lr] = mITSTimeBiasInBC[lr] * BCLenMUS; + // the ITS tracks ROFRecords are defined by the layer with the largest number of ROFs per orbit, + // see o2::its::ITSTrackingInterface (o2::its::ROFOverlapView::getClock()). If all layers have the + // same timing, this is the layer 0. + unsigned int nROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / mITSROFrameLengthInBC[lr]; + if (nROFsPerOrbit > maxNROFsPerOrbit) { + maxNROFsPerOrbit = nROFsPerOrbit; + mITSClockLayer = lr; + } + } + std::string rep; + for (int lr = 0; lr < NITSLayers; lr++) { + rep += fmt::format(" L{}:{}/{}", lr, mITSROFrameLengthInBC[lr], mITSTimeBiasInBC[lr]); + } + LOGP(info, "ITS {} readout, per-layer ROFLength/Bias in BC:{} | clock layer {}", + mITSTriggered ? "triggered" : "continuous", rep, mITSClockLayer); } //___________________________________________________________________ @@ -2655,35 +2899,37 @@ void MatchTPCITS::flagUsedITSClusters(const o2::its::TrackITS& track) // flag clusters used by this track int clEntry = track.getFirstClusterEntry(); for (int icl = track.getNumberOfClusters(); icl--;) { - mABClusterLinkIndex[mITSTrackClusIdx[clEntry++]] = MinusTen; + const int clID = mITSTrackClusIdx[clEntry++]; // composed ID: (layer << ITSClusLayerShift) + index_in_layer + auto& clStatus = mABClusterStatus[ITSClusID2Layer(clID)]; + if (!clStatus.empty()) { // layers not used by the AfterBurner are not booked + clStatus[ITSClusID2Index(clID)] = MinusTen; + } } } //__________________________________________________________ -int MatchTPCITS::preselectChipClusters(std::vector& clVecOut, const ClusRange& clRange, const ITSChipClustersRefs& itsChipClRefs, +int MatchTPCITS::preselectChipClusters(std::vector& clVecOut, const ClusRange& clRange, const ABLayerView& clView, float trackY, float trackZ, float tolerY, float tolerZ) const { clVecOut.clear(); int icID = clRange.getFirstEntry(); for (int icl = clRange.getEntries(); icl--;) { // note: clusters within a chip are sorted in Z - int clID = itsChipClRefs.clusterID[icID++]; // so, we go in clusterID increasing direction - const auto& cls = mITSClustersArray[clID]; - float dz = cls.getZ() - trackZ; - LOG(debug) << "cl" << icl << '/' << clID << " " - << " dZ: " << dz << " [" << tolerZ << "| dY: " << trackY - cls.getY() << " [" << tolerY << "]"; + const auto& cls = clView.data[icID++]; + float dz = cls.z - trackZ; + LOG(debug) << "cl" << icl << '/' << cls.id << " " + << " dZ: " << dz << " [" << tolerZ << "| dY: " << trackY - cls.y << " [" << tolerY << "]"; if (dz > tolerZ) { - float clsZ = cls.getZ(); - LOG(debug) << "Skip the rest since " << trackZ << " < " << clsZ << "\n"; + LOG(debug) << "Skip the rest since " << trackZ << " < " << cls.z << "\n"; break; } else if (dz < -tolerZ) { - LOG(debug) << "Skip cluster dz=" << dz << " Ztr=" << trackZ << " zCl=" << cls.getZ(); + LOG(debug) << "Skip cluster dz=" << dz << " Ztr=" << trackZ << " zCl=" << cls.z; continue; } - if (fabs(trackY - cls.getY()) > tolerY) { - LOG(debug) << "Skip cluster dy= " << trackY - cls.getY() << " Ytr=" << trackY << " yCl=" << cls.getY(); + if (fabs(trackY - cls.y) > tolerY) { + LOG(debug) << "Skip cluster dy= " << trackY - cls.y << " Ytr=" << trackY << " yCl=" << cls.y; continue; } - clVecOut.push_back(clID); + clVecOut.push_back(cls.id); } return clVecOut.size(); } @@ -2735,12 +2981,24 @@ void MatchTPCITS::reportSizes(pmr::vector& matched LOGP(info, "Size SHM, calib : size {:9} cap {:9}", siz, cap); } { - siz = mITSClustersArray.size() * sizeof(ITSCluster); - cap = mITSClustersArray.capacity() * sizeof(ITSCluster); + siz = cap = 0; + for (const auto& clusV : mITSClustersArray) { + siz += clusV.size() * sizeof(ITSCluster); + cap += clusV.capacity() * sizeof(ITSCluster); + } sizTot += siz; capTot += cap; LOGP(info, "Size RSS, mITSClustersArray : size {:9} cap {:9}", siz, cap); // + siz = cap = 0; + for (const auto& abClus : mABLayerClusters) { + siz += abClus.sizeInternal(); + cap += abClus.capInternal(); + } + sizTot += siz; + capTot += cap; + LOGP(info, "Size RSS, mABLayerClusters : size {:9} cap {:9}", siz, cap); + // siz = mMatchRecordsTPC.size() * sizeof(MatchRecord); cap = mMatchRecordsTPC.capacity() * sizeof(MatchRecord); sizTot += siz; @@ -2803,11 +3061,14 @@ void MatchTPCITS::reportSizes(pmr::vector& matched capTot += cap; LOGP(info, "Size RSS, mABWinnersIDs : size {:9} cap {:9}", siz, cap); // - siz = mABClusterLinkIndex.size() * sizeof(int); - cap = mABClusterLinkIndex.capacity() * sizeof(int); + siz = cap = 0; + for (const auto& clStatus : mABClusterStatus) { + siz += clStatus.size() * sizeof(int); + cap += clStatus.capacity() * sizeof(int); + } sizTot += siz; capTot += cap; - LOGP(info, "Size RSS, mABClusterLinkIndex : size {:9} cap {:9}", siz, cap); + LOGP(info, "Size RSS, mABClusterStatus : size {:9} cap {:9}", siz, cap); // for (int is = 0; is < o2::constants::math::NSectors; is++) { siz += mTPCSectIndexCache[is].size() * sizeof(int); @@ -2951,7 +3212,7 @@ void MatchTPCITS::fillTPCITSmatchTree(int itsID, int tpcID, int rejFlag, float c << "rejFlag=" << rejFlag << "multTPC=" << mltTPC << "multITSTr=" << mITSTrackROFRec[trackITS.roFrame].getNEntries() - << "multITSCl=" << mITSClusterROFRec[trackITS.roFrame].getNEntries() + << "multITSCl=" << mITSClusterROFRec[mRecoCont->getITSPerLayer() ? mITSClockLayer : 0][trackITS.roFrame].getNEntries() << "\n"; mTimer[SWDBG].Stop(); @@ -2986,7 +3247,7 @@ void MatchTPCITS::dumpWinnerMatches() (*mDBGOut) << "matchWin" << "multTPC=" << mltTPC << "multITSTr=" << mITSTrackROFRec[tITS.roFrame].getNEntries() - << "multITSCl=" << mITSClusterROFRec[tITS.roFrame].getNEntries() + << "multITSCl=" << mITSClusterROFRec[mRecoCont->getITSPerLayer() ? mITSClockLayer : 0][tITS.roFrame].getNEntries() << "\n"; } mTimer[SWDBG].Stop(); diff --git a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TPCITSMatchingSpec.h b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TPCITSMatchingSpec.h index 56240fd2c8f98..775e7b8c676f2 100644 --- a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TPCITSMatchingSpec.h +++ b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TPCITSMatchingSpec.h @@ -23,7 +23,7 @@ namespace o2 namespace globaltracking { /// create a processor spec -framework::DataProcessorSpec getTPCITSMatchingSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useFT0, bool calib, bool skipTPCOnly, bool useGeom, bool useMC, bool requestCTPLumi); +framework::DataProcessorSpec getTPCITSMatchingSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useFT0, bool calib, bool skipTPCOnly, bool useGeom, bool useMC, bool requestCTPLumi, bool itsStag); } // namespace globaltracking } // namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx index 7f63b61e02be0..90a02fd6fc493 100644 --- a/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx @@ -210,15 +210,10 @@ void TPCITSMatchingDPL::updateTimeDependentParams(ProcessingContext& pc) pc.inputs().get("MatchParam"); // Note: ITS/CLUSDICT and ITS/ALPIDEPARAM are requested/loaded by the recocontainer - const auto& alpParams = o2::itsmft::DPLAlpideParam::Instance(); - if (mMatching.isITSTriggered()) { - mMatching.setITSROFrameLengthMUS(alpParams.roFrameLengthTrig / 1.e3); // ITS ROFrame duration in \mus - } else { - mMatching.setITSROFrameLengthInBC(alpParams.roFrameLengthInBC); // ITS ROFrame duration in \mus - } - mMatching.setITSTimeBiasInBC(alpParams.roFrameBiasInBC); - mMatching.setSkipTPCOnly(mSkipTPCOnly); + // all per-layer ITS ROF lengths and biases are derived in MatchTPCITS::setAlpideParam() mMatching.setITSTriggered(!o2::base::GRPGeomHelper::instance().getGRPECS()->isDetContinuousReadOut(o2::detectors::DetID::ITS)); + mMatching.setAlpideParam(&o2::itsmft::DPLAlpideParam::Instance()); + mMatching.setSkipTPCOnly(mSkipTPCOnly); mMatching.setNHBPerTF(o2::base::GRPGeomHelper::instance().getGRPECS()->getNHBFPerTF()); mMatching.setMCTruthOn(mUseMC); mMatching.setUseFT0(mUseFT0); @@ -250,10 +245,11 @@ void TPCITSMatchingDPL::updateTimeDependentParams(ProcessingContext& pc) } } -DataProcessorSpec getTPCITSMatchingSpec(GTrackID::mask_t src, bool useFT0, bool calib, bool skipTPCOnly, bool useGeom, bool useMC, bool requestCTPLumi) +DataProcessorSpec getTPCITSMatchingSpec(GTrackID::mask_t src, bool useFT0, bool calib, bool skipTPCOnly, bool useGeom, bool useMC, bool requestCTPLumi, bool itsStag) { std::vector outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); if ((src & GTrackID::getSourcesMask("TPC-TRD,TPC-TOF,TPC-TRD-TOF")).any()) { // preliminary stage of extended workflow ? dataRequest->setMatchingInputStrict(); } diff --git a/Detectors/GlobalTrackingWorkflow/src/tpcits-match-workflow.cxx b/Detectors/GlobalTrackingWorkflow/src/tpcits-match-workflow.cxx index 79ca13430ccd9..05c3f15d6df51 100644 --- a/Detectors/GlobalTrackingWorkflow/src/tpcits-match-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/tpcits-match-workflow.cxx @@ -96,7 +96,7 @@ WorkflowSpec defineDataProcessing(o2::framework::ConfigContext const& configcont if (!configcontext.options().get("disable-root-input")) { specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt)); } - specs.emplace_back(o2::globaltracking::getTPCITSMatchingSpec(srcL, useFT0, calib, !GID::includesSource(GID::TPC, src), useGeom, useMC, sclOpt.requestCTPLumi)); + specs.emplace_back(o2::globaltracking::getTPCITSMatchingSpec(srcL, useFT0, calib, !GID::includesSource(GID::TPC, src), useGeom, useMC, sclOpt.requestCTPLumi, doStag)); if (!configcontext.options().get("disable-root-output")) { specs.emplace_back(o2::globaltracking::getTrackWriterTPCITSSpec(useMC)); From 8d8b21fdd2893986558381b13ce68a23147e768a Mon Sep 17 00:00:00 2001 From: shahoian Date: Tue, 29 Sep 2026 22:48:53 +0200 Subject: [PATCH 02/18] Use per-track ITS time stamps instead of ROF duration in TPC-ITS matching MatchTPCITS previously assigned every ITS track the full clock-layer ROF duration as its time bracket. TrackITS now carries a per-track TimeStamp (BC since TF start, symmetric error, ROF bias already applied), which is typically much narrower than the ROF, especially in staggered running. - prepareITSData builds the per-track bracket from getTimeStamp(), widened by the new itsTimeStampMarginBC margin (BC, both edges), falling back to the nominal ROF bracket when the time stamp is invalid (legacy input) - mITSROFTimes is extended to the envelope of the nominal ROF bracket and the actual per-track brackets, keeping the TPC-side and triggered-mode ITS ROF entry caches conservative - mITSMaxROFOverhangMUS tracks how far track brackets extend past their ROF end in the current TF; doMatching's continuous-mode entry lookup is shifted by this amount so no compatible track is skipped - sorting by bracket min time still cannot mix tracks of different ROFs (the tracker guarantees the raw lower edge stays within the assigned ROF, and the margin shifts all tracks alike), so the existing mITSTimeStart assignment and the tBracket-based break/continue gates in doMatching remain valid without a LUT rebuild; the long-dead RejectOnTgl ROF-skip code (whose precondition never held with mixed layers) is removed - refitTrackTPCITS derives the fallback ITS time error from the track's own bracket (delta()/sqrt(12)) instead of the nominal clock-layer ROF resolution Net effect: most TPC x ITS pairs are now rejected by the cheap bracket overlap check in doMatching before the sqrt/kinematic comparisons, with no new containers and a single extra float member. Co-Authored-By: Claude Sonnet 5 --- .../include/GlobalTracking/MatchTPCITS.h | 1 + .../GlobalTracking/MatchTPCITSParams.h | 1 + Detectors/GlobalTracking/src/MatchTPCITS.cxx | 64 +++++++++++-------- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h index 51ef4e1f67e29..e1c3982a23c08 100644 --- a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h +++ b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h @@ -781,6 +781,7 @@ class MatchTPCITS std::array mABLayerClusters; ///< per (layer, ROF) blocks of AB-usable clusters; filled for the AB layers and the ROF times also for the clock layer std::array mABChipsBounds{}; ///< the layer lr owns the global chip IDs [mABChipsBounds[lr], mABChipsBounds[lr+1]) float mABROFMarginMUS = 0.f; ///< effective margin for candidate time to ITS ROF matching: abROFMarginMUS clamped to below half of the shortest AB layer ROF + float mITSMaxROFOverhangMUS = 0.f; ///< max excess of ITS track time brackets over the end of their clock-layer ROF in the current TF LinksPoolMT mABLinksPool; ///< per sector indices of TPC track entry in mTPCWork diff --git a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITSParams.h b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITSParams.h index 2dfa5e6012428..a45b8e135a20a 100644 --- a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITSParams.h +++ b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITSParams.h @@ -86,6 +86,7 @@ struct MatchTPCITSParams : public o2::conf::ConfigurableParamHelperitsTimeStampMarginBC); for (int irof = 0; irof < nROFs; irof++) { const auto& rofRec = mITSTrackROFRec[irof]; @@ -799,7 +801,7 @@ bool MatchTPCITS::prepareITSData() mITSTrackROFContMapping[irofCont] = irof; } - mITSROFTimes.emplace_back(tMin, tMax); // ITS ROF min/max time + mITSROFTimes.emplace_back(tMin, tMax); // nominal ITS ROF min/max time, to be extended to the envelope of the per-track time brackets for (int sec = o2::constants::math::NSectors; sec--;) { // start of sector's tracks for this ROF mITSTimeStart[sec][irof] = mITSSectIndexCache[sec].size(); // The sorting does not affect this @@ -817,9 +819,31 @@ bool MatchTPCITS::prepareITSData() if (std::abs(trcOrig.getQ2Pt()) > mMinITSTrackPtInv) { continue; } + // per-track time bracket from the tracker time stamp (bias-corrected BC since the TF start), + // widened by the itsTimeStampMarginBC safety margin; the nominal ROF bracket is used as a + // fallback when the time stamp is invalid (legacy input) + float tMinTrc = tMin, tMaxTrc = tMax; + const auto& tstamp = trcOrig.getTimeStamp(); + if (tstamp.getTimeStampError() > 0.f) { + // the tracker guarantees the raw lower edge of the time stamp to be within the assigned clock-layer ROF + assert(tstamp.getTimeStamp() - tstamp.getTimeStampError() >= nBC + mITSTimeBiasInBC[mITSClockLayer] - 0.5f); + float errBC = tstamp.getTimeStampError() + trackTimeMarginBC; + tMinTrc = (tstamp.getTimeStamp() - errBC) * o2::constants::lhc::LHCBunchSpacingMUS; + tMaxTrc = (tstamp.getTimeStamp() + errBC) * o2::constants::lhc::LHCBunchSpacingMUS; + auto& rofEnv = mITSROFTimes.back(); // extend the ROF envelope used by the TPC-side and triggered-mode entry caches + if (tMinTrc < rofEnv.getMin()) { + rofEnv.setMin(tMinTrc); + } + if (tMaxTrc > rofEnv.getMax()) { + rofEnv.setMax(tMaxTrc); + if (tMaxTrc - tMax > mITSMaxROFOverhangMUS) { + mITSMaxROFOverhangMUS = tMaxTrc - tMax; // max excess of the track brackets over their ROF end + } + } + } int nWorkTracks = mITSWork.size(); // working copy of outer track param - auto& trc = mITSWork.emplace_back(TrackLocITS{trcOrig.getParamOut(), {tMin, tMax}, it, irof, MinusOne}); + auto& trc = mITSWork.emplace_back(TrackLocITS{trcOrig.getParamOut(), {tMinTrc, tMaxTrc}, it, irof, MinusOne}); if (!trc.rotate(o2::math_utils::angle2Alpha(trc.getPhiPos()))) { mITSWork.pop_back(); // discard failed track continue; @@ -869,8 +893,10 @@ bool MatchTPCITS::prepareITSData() } } - // sort tracks in each sector according to their min time, then tgl - // RSTODO: sorting in tgl will be dangerous once the tracks with different time uncertaincies will be added + // Sort tracks in each sector according to their bracket min time (tgl serves only as a deterministic tie-break). + // Since the raw lower edge of every track time stamp is guaranteed to be within its clock-layer ROF and the + // safety margin shifts all tracks alike, the sorting cannot mix tracks of different ROFs, hence the + // mITSTimeStart entries assigned at the filling stage above remain valid. for (int sec = o2::constants::math::NSectors; sec--;) { auto& indexCache = mITSSectIndexCache[sec]; if (mParams->verbosity > 0) { @@ -946,7 +972,9 @@ void MatchTPCITS::doMatching(int sec) // estimate ITS 1st ROframe bin this track may match to: TPC track are sorted according to their // timeMax, hence the timeMax - MaxmNTPCBinsFullDrift are non-decreasing auto tmn = trefTPC.tBracket.getMax() - maxTDriftSafe; - itsROBin = mITSTriggered ? time2ITSROFrameTrig(tmn, itsROBin) : time2ITSROFrameCont(tmn); + // in continuous mode the lookup time is decreased by the max excess of the ITS track brackets over their + // ROF end, since the mITSTimeStart binning is in ROF units while the brackets may extend beyond the ROF + itsROBin = mITSTriggered ? time2ITSROFrameTrig(tmn, itsROBin) : time2ITSROFrameCont(tmn - mITSMaxROFOverhangMUS); if (itsROBin >= int(timeStartITS.size())) { // time of TPC track exceeds the max time of ITS in the cache break; @@ -1001,27 +1029,6 @@ void MatchTPCITS::doMatching(int sec) fillTPCITSmatchTree(cacheITS[iits], cacheTPC[itpc], rejFlag, chi2, timeCorr); } #endif - /* - // RS: this might be dangerous for ITS tracks with different time coverages. - if (rejFlag == RejectOnTgl) { - // ITS tracks in each ROFrame are ordered in Tgl, hence if this check failed on Tgl check - // (i.e. tgl_its>tgl_tpc+tolerance), then all other ITS tracks in this ROFrame will also have tgl too large. - // Jump on the 1st ITS track of the next ROFrame - int rof = trefITS.roFrame; - bool stop = false; - do { - if (++rof >= int(timeStartITS.size())) { - stop = true; - break; // no more ITS ROFrames in cache - } - iits = timeStartITS[rof] - 1; // next track to be checked -1 - } while (iits <= timeStartITS[trefITS.roFrame]); // skip empty bins - if (stop) { - break; - } - continue; - } - */ if (rejFlag != Accept) { continue; } @@ -1658,8 +1665,9 @@ bool MatchTPCITS::refitTrackTPCITS(int slot, int iTPC, int& iITS, pmr::vector mITSTimeResMUS[mITSClockLayer] && tTPC.constraint != TrackLocTPC::Constrained) { - timeErr = mITSTimeResMUS[mITSClockLayer]; // chose smallest error + float itsTimeRes = tITS.tBracket.delta() / std::sqrt(12.f); // ITS time resolution from the track time-stamp bracket (uniform distribution) + if (timeErr > itsTimeRes && tTPC.constraint != TrackLocTPC::Constrained) { + timeErr = itsTimeRes; // chose smallest error deltaT = tTPC.constraint == TrackLocTPC::ASide ? tITS.tBracket.mean() - tTPC.time0 : tTPC.time0 - tITS.tBracket.mean(); } timeErr += mParams->globalTimeExtraErrorMUS; From 14725630f2f9b018c68901c86e2890bddb7b78a0 Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 16:45:21 +0200 Subject: [PATCH 03/18] Encode the ITS layer into the per-layer ITS track cluster references With the per-layer (staggered readout) ITS clusters input the cluster index kept by TrackITS is local to its layer, so the layer must be encoded into the stored reference for the consumers to be able to find the cluster. MatchTPCITS already decoded the ITS/TRACKCLSID entries as such composed IDs, but the tracker pushed the bare per-layer index, which was correct only for the layer 0. - ITSTrackingInterface::run composes the stored reference as (layer << ClusLayerShift) + index_in_layer; with the monolithic clusters input the layer slot is 0 and the composed ID stays equal to the flat index, so the non-staggered output is unchanged - the ID composition/decomposition and the max number of separately provided ITS/MFT cluster layers move from MatchTPCITS.h to the new lightweight DataFormatsITSMFT/ClusterID.h, so that both the producer and the (many) consumers can use them without pulling in GlobalTracking; RecoContainer.h includes it and keeps MaxITSLayers/MaxMFTLayers as aliases - the layer field is shifted by 27 rather than 28 bits, so that it can accommodate the MFT layers too: the bit 31 is unusable, since the negative values of the composed ID are reserved for the "no cluster" flags - the unused TrackITSExt::setClusterIndex, carrying its own hardcoded copy of the composition (and writing to the packed slot while getClusterIndex reads the layer slot), is removed Co-Authored-By: Claude Opus 5 --- .../DataFormatsGlobalTracking/RecoContainer.h | 5 ++- .../ITS/include/DataFormatsITS/TrackITS.h | 7 --- .../include/DataFormatsITSMFT/ClusterID.h | 43 +++++++++++++++++++ .../include/GlobalTracking/MatchTPCITS.h | 17 +++----- Detectors/GlobalTracking/src/MatchTPCITS.cxx | 12 +++--- .../ITS/tracking/src/TrackingInterface.cxx | 5 ++- 6 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/ClusterID.h diff --git a/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/RecoContainer.h b/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/RecoContainer.h index db072ef3a421e..5d91c63258e57 100644 --- a/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/RecoContainer.h +++ b/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/RecoContainer.h @@ -27,6 +27,7 @@ #include "SimulationDataFormat/MCTruthContainer.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" #include "DataFormatsCTP/LumiInfo.h" +#include "DataFormatsITSMFT/ClusterID.h" #include #include @@ -188,8 +189,8 @@ namespace globaltracking { // max number of layers for which the ITS/MFT clusters, ROF records and patterns can be provided separately -constexpr int MaxITSLayers = 7; -constexpr int MaxMFTLayers = 10; +constexpr int MaxITSLayers = o2::itsmft::MaxITSClusLayers; +constexpr int MaxMFTLayers = o2::itsmft::MaxMFTClusLayers; // helper class to request DPL input data from the processor specs definition struct DataRequest { diff --git a/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h index 9b63509cc9424..2f026fce7a588 100644 --- a/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h +++ b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h @@ -221,13 +221,6 @@ class TrackITSExt : public TrackITS GPUhdDefault() TrackITSExt(const TrackITSExt& t) = default; - void setClusterIndex(int l, int i) - { - int ncl = getNumberOfClusters(); - mIndex[ncl++] = (l << 28) + i; - getClusterRefs().setEntries(ncl); - } - GPUhdi() int getClusterIndex(int lr) const { return mIndex[lr]; } GPUh() int getFirstLayerClusterIndex() const diff --git a/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/ClusterID.h b/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/ClusterID.h new file mode 100644 index 0000000000000..a8d1da83533ce --- /dev/null +++ b/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/ClusterID.h @@ -0,0 +1,43 @@ +// 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 ClusterID.h +/// \brief Composition/decomposition of the ITS/MFT cluster IDs referring to per-layer cluster arrays +/// \author ruben.shahoyan@cern.ch + +#ifndef ALICEO2_ITSMFT_CLUSTERID_H +#define ALICEO2_ITSMFT_CLUSTERID_H + +namespace o2::itsmft +{ + +// max number of layers for which the ITS/MFT clusters, ROF records and patterns can be provided separately +constexpr int MaxITSClusLayers = 7; +constexpr int MaxMFTClusLayers = 10; +constexpr int MaxClusLayers = MaxITSClusLayers > MaxMFTClusLayers ? MaxITSClusLayers : MaxMFTClusLayers; + +///< With the per-layer (staggered readout) ITS/MFT clusters input the clusters are referred to by the +///< composed ID (layer << ClusLayerShift) + index_in_layer. With a single (monolithic) clusters input +///< all clusters sit in the layer slot 0, hence the composed ID coincides with the flat cluster index +///< and the same decoding works for both cases. +///< Note: the bit 31 is excluded from the layer field, since the negative values of the composed ID +///< are reserved for the "no cluster" flags. +constexpr int ClusLayerShift = 27; +constexpr int ClusIndexMask = (0x1 << ClusLayerShift) - 1; +static_assert((1 << (31 - ClusLayerShift)) >= MaxClusLayers, "ClusLayerShift leaves no room for the layer ID"); + +constexpr int composeClusID(int lr, int idx) { return (lr << ClusLayerShift) + idx; } +constexpr int clusID2Layer(int id) { return id >> ClusLayerShift; } +constexpr int clusID2Index(int id) { return id & ClusIndexMask; } + +} // namespace o2::itsmft + +#endif diff --git a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h index e1c3982a23c08..88d176d2f29bb 100644 --- a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h +++ b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h @@ -55,6 +55,7 @@ #include "GlobalTracking/MatchTPCITSParams.h" #include "DataFormatsITSMFT/TopologyDictionary.h" #include "DataFormatsITSMFT/TrkClusRef.h" +#include "DataFormatsITSMFT/ClusterID.h" #include "ITSMFTReconstruction/ChipMappingITS.h" #include "TPCFastTransformPOD.h" #if !defined(__CINT__) && !defined(__MAKECINT__) && !defined(__ROOTCLING__) && !defined(__CLING__) @@ -109,14 +110,6 @@ constexpr int MinusOne = -1; constexpr int MinusTen = -10; constexpr int Validated = -2; -///< ITS clusters are referred to by the composed index (layer << ITSClusLayerShift) + index_in_layer. -///< With a single ITS clusters input (not per layer) all clusters are in the layer slot 0. -constexpr int ITSClusLayerShift = 28; -constexpr int ITSClusIndexMask = (0x1 << ITSClusLayerShift) - 1; -constexpr int composeITSClusID(int lr, int idx) { return (lr << ITSClusLayerShift) + idx; } -constexpr int ITSClusID2Layer(int id) { return id >> ITSClusLayerShift; } -constexpr int ITSClusID2Index(int id) { return id & ITSClusIndexMask; } - ///< per-layer status of ITS clusters (e.g. for the AfterBurner) using ITSClusStatus = std::array, o2::its::RecoGeomHelper::getNLayers()>; @@ -282,7 +275,7 @@ struct TPCABSeed { // check if some clusters used by the link or its parents are forbidden (already used by validatet track) while (linkID > MinusOne) { const auto& link = getLink(linkID); - if (link.clID > MinusOne && clStatus[ITSClusID2Layer(link.clID)][ITSClusID2Index(link.clID)] != MinusOne) { + if (link.clID > MinusOne && clStatus[o2::itsmft::clusID2Layer(link.clID)][o2::itsmft::clusID2Index(link.clID)] != MinusOne) { return true; } linkID = link.parentID; @@ -295,7 +288,7 @@ struct TPCABSeed { while (linkID > MinusOne) { const auto& link = getLink(linkID); if (link.clID > MinusOne) { - clStatus[ITSClusID2Layer(link.clID)][ITSClusID2Index(link.clID)] = MinusTen; + clStatus[o2::itsmft::clusID2Layer(link.clID)][o2::itsmft::clusID2Index(link.clID)] = MinusTen; } linkID = link.parentID; } @@ -320,7 +313,7 @@ struct InteractionCandidate : public o2::InteractionRecord { struct ABClusterInfo { ///< compact info on an ITS cluster usable by the AfterBurner float y = 0.f, z = 0.f; ///< Y, Z of the cluster in the tracking frame of its sensor - int id = MinusOne; ///< composed cluster ID, see composeITSClusID + int id = MinusOne; ///< composed cluster ID, see o2::itsmft::composeClusID int chip = -1; ///< global chip (sensor) ID }; @@ -631,7 +624,7 @@ class MatchTPCITS const ITSCluster& getITSCluster(int composedID) const { - return mITSClustersArray[ITSClusID2Layer(composedID)][ITSClusID2Index(composedID)]; + return mITSClustersArray[o2::itsmft::clusID2Layer(composedID)][o2::itsmft::clusID2Index(composedID)]; } // ========================= AFTERBURNER ========================= diff --git a/Detectors/GlobalTracking/src/MatchTPCITS.cxx b/Detectors/GlobalTracking/src/MatchTPCITS.cxx index ec46a5a0cd5f1..6d2a8fcb691c3 100644 --- a/Detectors/GlobalTracking/src/MatchTPCITS.cxx +++ b/Detectors/GlobalTracking/src/MatchTPCITS.cxx @@ -2147,7 +2147,7 @@ void MatchTPCITS::prepareABClusters() int chip = cls.getSensorID(); if ((perLayer || (chip >= chipMin && chip < chipMax)) && status[icl] != MinusTen) { assert(chip >= chipMin && chip < chipMax); // clusters of a per-layer slot must belong to its layer - dst[nCl++] = {cls.getY(), cls.getZ(), composeITSClusID(slot, icl), chip}; + dst[nCl++] = {cls.getY(), cls.getZ(), o2::itsmft::composeClusID(slot, icl), chip}; } } assert(nCl == blk.nCl); @@ -2365,7 +2365,7 @@ void MatchTPCITS::refitABWinners(pmr::vector& matc std::map labelOccurence; auto accountClusterLabel = [&labelOccurence, this](int clID) { - auto labels = mITSClsLabels[ITSClusID2Layer(clID)]->getLabels(ITSClusID2Index(clID)); + auto labels = mITSClsLabels[o2::itsmft::clusID2Layer(clID)]->getLabels(o2::itsmft::clusID2Index(clID)); for (auto lab : labels) { // check all labels of the cluster if (lab.isSet()) { labelOccurence[lab]++; @@ -2384,7 +2384,7 @@ void MatchTPCITS::refitABWinners(pmr::vector& matc ABTrackletClusterIDs.push_back(winL.clID); ncl++; clref.pattern |= 0x1 << winL.layerID; - clref.setClusterSize(winL.layerID, mITSClusterSizes[ITSClusID2Layer(winL.clID)][ITSClusID2Index(winL.clID)]); + clref.setClusterSize(winL.layerID, mITSClusterSizes[o2::itsmft::clusID2Layer(winL.clID)][o2::itsmft::clusID2Index(winL.clID)]); if (mMCTruthON) { accountClusterLabel(winL.clID); } @@ -2907,10 +2907,10 @@ void MatchTPCITS::flagUsedITSClusters(const o2::its::TrackITS& track) // flag clusters used by this track int clEntry = track.getFirstClusterEntry(); for (int icl = track.getNumberOfClusters(); icl--;) { - const int clID = mITSTrackClusIdx[clEntry++]; // composed ID: (layer << ITSClusLayerShift) + index_in_layer - auto& clStatus = mABClusterStatus[ITSClusID2Layer(clID)]; + const int clID = mITSTrackClusIdx[clEntry++]; // composed ID: (layer << o2::itsmft::ClusLayerShift) + index_in_layer + auto& clStatus = mABClusterStatus[o2::itsmft::clusID2Layer(clID)]; if (!clStatus.empty()) { // layers not used by the AfterBurner are not booked - clStatus[ITSClusID2Index(clID)] = MinusTen; + clStatus[o2::itsmft::clusID2Index(clID)] = MinusTen; } } } diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx index a0e8d708cffa2..732a68208732a 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx @@ -26,6 +26,7 @@ #include "ITSMFTTracking/ITSTrackingConfigParam.h" #include "ITStracking/TrackingInterface.h" +#include "DataFormatsITSMFT/ClusterID.h" #include "DataFormatsITSMFT/ROFRecord.h" #include "DataFormatsITSMFT/PhysTrigger.h" #include "DataFormatsTRD/TriggerRecord.h" @@ -333,7 +334,9 @@ void ITSTrackingInterface::run(framework::ProcessingContext& pc) auto clid = trc.getClusterIndex(ic); if (clid >= 0) { trc.setClusterSize(ic, mTimeFrame->getClusterSize((mDoStaggering) ? ic : 0, clid)); - allClusIdx.push_back(clid); + // with the per-layer clusters input the index is local to the layer, hence the layer must be + // encoded into the stored reference; with the monolithic input the composed ID is just the index + allClusIdx.push_back(o2::itsmft::composeClusID((mDoStaggering) ? ic : 0, clid)); nclf++; } } From fb9931a9998a7ec42c89939471fe16f0caa1ccd3 Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:28:34 +0200 Subject: [PATCH 04/18] Fix asserts checking N layers in staggered R/O helpers --- .../ITSMFT/common/include/DataFormatsITSMFT/DPLAlpideParam.h | 5 +++++ .../GlobalTracking/include/GlobalTracking/MatchTPCITS.h | 1 + 2 files changed, 6 insertions(+) diff --git a/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/DPLAlpideParam.h b/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/DPLAlpideParam.h index a06ba0745edbd..714d156dd5f80 100644 --- a/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/DPLAlpideParam.h +++ b/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/DPLAlpideParam.h @@ -16,6 +16,7 @@ #include "CommonUtils/ConfigurableParam.h" #include "CommonUtils/ConfigurableParamHelper.h" #include "CommonConstants/LHCConstants.h" +#include "DataFormatsITSMFT/ClusterID.h" #include namespace o2 @@ -76,6 +77,10 @@ struct DPLAlpideParam : public o2::conf::ConfigurableParamHelper Date: Wed, 30 Sep 2026 20:40:10 +0200 Subject: [PATCH 05/18] Add the ClustersPerLayer helper for the per-layer ITS/MFT clusters Stores the clusters of all layers in a single vector with the per-layer offsets and gives access by the composed ID of ClusterID.h, so that the same code works for the monolithic and the per-layer input. Detector agnostic: the filling is left to the caller, since the spacepoint conversion needs the detector geometry and dictionary. Co-Authored-By: Claude Opus 5 --- .../DataFormatsITSMFT/ClustersPerLayer.h | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/ClustersPerLayer.h diff --git a/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/ClustersPerLayer.h b/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/ClustersPerLayer.h new file mode 100644 index 0000000000000..77e97618a00a1 --- /dev/null +++ b/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/ClustersPerLayer.h @@ -0,0 +1,109 @@ +// 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 ClustersPerLayer.h +/// \brief Container of the ITS/MFT clusters addressed by the composed (layer,index) ID +/// \author ruben.shahoyan@cern.ch + +#ifndef ALICEO2_ITSMFT_CLUSTERSPERLAYER_H +#define ALICEO2_ITSMFT_CLUSTERSPERLAYER_H + +#include "DataFormatsITSMFT/ClusterID.h" +#include "Framework/Logger.h" +#include +#include + +namespace o2::itsmft +{ + +///< Container of the ITS/MFT clusters supplied either as a single (monolithic) array or per layer +///< (staggered readout). The clusters of all layers are kept in a single vector, layer by layer, +///< with the per-layer starting offsets recorded, so that a cluster referred to by the composed ID +///< (layer << ClusLayerShift) + index_in_layer can be looked up directly. With the monolithic input +///< only the layer slot 0 is filled and the composed ID coincides with the flat cluster index, hence +///< the same lookup works for both cases. +///< +///< The conversion of the compact clusters to the stored objects is detector specific (and needs the +///< geometry), so it is left to the caller: the container only records where each layer starts. +///< Expected usage (nLr == 1 for the monolithic input): +///< cont.init(nLr); +///< for (int lr = 0; lr < nLr; lr++) { +///< cont.beginLayer(lr); +///< auto pattIt = recoData.getITSClustersPatterns(lr).begin(); +///< o2::its::ioutils::convertCompactClusters(recoData.getITSClusters(lr), pattIt, cont.getClusters(), dict); +///< } +///< cont.finalize(); +///< after which cont[composedID] gives the cluster referred to by a track cluster reference. +template +class ClustersPerLayer +{ + public: + ///< prepare for filling nLr layer slots, discarding the previous content + void init(int nLr) + { + if (nLr < 1 || nLr > MaxClusLayers) { + LOGP(fatal, "Clusters container cannot be initialized for {} layers, must be within 1:{}", nLr, MaxClusLayers); + } + mClusters.clear(); + mLrFirst.fill(0); + mNLayers = nLr; + } + + ///< record the start of the layer lr data, the layers must be filled in the increasing order + void beginLayer(int lr) + { + if (lr < 0 || lr >= mNLayers) { + LOGP(fatal, "Clusters container was initialized for {} layers, cannot fill the layer {}", mNLayers, lr); + } + mLrFirst[lr] = int(mClusters.size()); + } + + ///< to be called once all the layers were filled + void finalize() + { + for (int lr = mNLayers; lr <= MaxClusLayers; lr++) { // the slots above the filled ones are empty + mLrFirst[lr] = int(mClusters.size()); + } + } + + void clear() + { + mClusters.clear(); + mLrFirst.fill(0); + mNLayers = 1; + } + + ///< the clusters of all layers, to be appended to by the caller between beginLayer and finalize + auto& getClusters() { return mClusters; } + const auto& getClusters() const { return mClusters; } + + auto getNLayers() const { return mNLayers; } + auto getFirstIndex(int lr) const { return mLrFirst[lr]; } + auto getNClusters(int lr) const { return mLrFirst[lr + 1] - mLrFirst[lr]; } + auto size() const { return mClusters.size(); } + bool empty() const { return mClusters.empty(); } + + ///< flat index of the cluster referred to by its composed ID + int flatIndex(int composedID) const + { + return mLrFirst[clusID2Layer(composedID)] + clusID2Index(composedID); + } + const T& operator[](int composedID) const { return mClusters[flatIndex(composedID)]; } + + private: + std::vector mClusters{}; ///< clusters of all layers, layer by layer + std::array mLrFirst{}; ///< 1st cluster of every layer, + the total in the last slot + int mNLayers = 1; ///< number of filled layer slots +}; + +} // namespace o2::itsmft + +#endif From 6bbe08b9901f1484d2c248fd629eae327e4c7cdd Mon Sep 17 00:00:00 2001 From: shahoian Date: Thu, 1 Oct 2026 13:29:27 +0200 Subject: [PATCH 06/18] Fix ITSMFT cluster reader in staggered mode --- Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx index 5e8ee4b99c092..b22469535bd03 100644 --- a/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx @@ -33,7 +33,7 @@ namespace itsmft { template -ClusterReader::ClusterReader(bool useMC, bool doStag, bool usePatterns, bool triggerOut) : mUseMC(useMC), mUsePatterns(usePatterns), mTriggerOut(triggerOut), mDetName(Origin.as()), mDetNameLC(mDetName) +ClusterReader::ClusterReader(bool useMC, bool doStag, bool usePatterns, bool triggerOut) : mUseMC(useMC), mDoStaggering(doStag), mUsePatterns(usePatterns), mTriggerOut(triggerOut), mDetName(Origin.as()), mDetNameLC(mDetName) { std::transform(mDetNameLC.begin(), mDetNameLC.end(), mDetNameLC.begin(), ::tolower); if (doStag) { From b05bde3fc3d6a368b0820960895d34f1d8000fb4 Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:10 +0200 Subject: [PATCH 07/18] Support per-layer ITS clusters in strangeness-tracking-workflow The parallel cluster sizes vector is indexed via ClustersPerLayer::flatIndex so that it cannot diverge from the cluster container offsets. --- .../StrangenessTrackingSpec.h | 2 +- .../src/StrangenessTrackingSpec.cxx | 3 +- .../src/strangeness-tracking-workflow.cxx | 3 +- .../StrangenessTracking/StrangenessTracker.h | 14 ++++--- .../src/StrangenessTracker.cxx | 38 +++++++++++-------- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/StrangenessTrackingSpec.h b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/StrangenessTrackingSpec.h index bbc2cdc80995e..48ba7ad8416e7 100644 --- a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/StrangenessTrackingSpec.h +++ b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/StrangenessTrackingSpec.h @@ -60,7 +60,7 @@ class StrangenessTrackerSpec : public framework::Task std::unique_ptr mGRP = nullptr; }; -o2::framework::DataProcessorSpec getStrangenessTrackerSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useMC, bool useGeom); +o2::framework::DataProcessorSpec getStrangenessTrackerSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useMC, bool useGeom, bool itsStag); o2::framework::WorkflowSpec getWorkflow(bool upstreamClusters = false, bool upstreamV0s = false); } // namespace strangeness_tracking diff --git a/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx index c438f869773a5..ec158a1749398 100644 --- a/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx @@ -154,10 +154,11 @@ void StrangenessTrackerSpec::endOfStream(framework::EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getStrangenessTrackerSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useMC, bool useGeom) +DataProcessorSpec getStrangenessTrackerSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useMC, bool useGeom, bool itsStag) { // ITS auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); dataRequest->requestITSClusters(useMC); dataRequest->requestTracks(src, useMC); dataRequest->requestPrimaryVertices(useMC); diff --git a/Detectors/GlobalTrackingWorkflow/src/strangeness-tracking-workflow.cxx b/Detectors/GlobalTrackingWorkflow/src/strangeness-tracking-workflow.cxx index 8c42871ac05bf..4d47cc15e1306 100644 --- a/Detectors/GlobalTrackingWorkflow/src/strangeness-tracking-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/strangeness-tracking-workflow.cxx @@ -64,13 +64,14 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto useRootInput = !configcontext.options().get("disable-root-input"); auto disableRootOut = configcontext.options().get("disable-root-output"); auto useGeom = configcontext.options().get("use-full-geometry"); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); o2::conf::ConfigurableParam::writeINI("o2strangeness_tracking_workflow_configuration.ini"); GID::mask_t itsSource = GID::getSourceMask(GID::ITS); // ITS tracks and clusters WorkflowSpec specs; - specs.emplace_back(o2::strangeness_tracking::getStrangenessTrackerSpec(itsSource, useMC, useGeom)); + specs.emplace_back(o2::strangeness_tracking::getStrangenessTrackerSpec(itsSource, useMC, useGeom, doStag)); o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, itsSource, itsSource, itsSource, useMC, itsSource); o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, useMC); // P-vertex is always needed o2::globaltracking::InputHelper::addInputSpecsSVertex(configcontext, specs); // S-vertex is always needed diff --git a/Detectors/Vertexing/StrangenessTracking/include/StrangenessTracking/StrangenessTracker.h b/Detectors/Vertexing/StrangenessTracking/include/StrangenessTracking/StrangenessTracker.h index b30be4dd081e3..dcfcdde95da6c 100644 --- a/Detectors/Vertexing/StrangenessTracking/include/StrangenessTracking/StrangenessTracker.h +++ b/Detectors/Vertexing/StrangenessTracking/include/StrangenessTracking/StrangenessTracker.h @@ -19,6 +19,7 @@ #include #include "DataFormatsITSMFT/TopologyDictionary.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "StrangenessTracking/IndexTableUtils.h" #include "StrangenessTracking/StrangenessTrackingConfigParam.h" #include "ReconstructionDataFormats/PID.h" @@ -66,6 +67,7 @@ class StrangenessTracker using PID = o2::track::PID; using TrackITS = o2::its::TrackITS; using ITSCluster = o2::BaseCluster; + using ITSClusters = o2::itsmft::ClustersPerLayer; using V0 = o2::dataformats::V0; using V0Index = o2::dataformats::V0Index; using Cascade = o2::dataformats::Cascade; @@ -247,12 +249,12 @@ class StrangenessTracker auto firstClus = itsTrack.getFirstClusterEntry(); auto ncl = itsTrack.getNumberOfClusters(); for (int icl = 0; icl < ncl; icl++) { - outVec.push_back(mInputClusterSizes[mInputITSidxs[firstClus + icl]]); + outVec.push_back(mInputClusterSizes[mInputITSclusters.flatIndex(mInputITSidxs[firstClus + icl])]); } return outVec; }; - void getClusterSizesITS(std::vector& clusSizeVec, const gsl::span ITSclus, gsl::span::iterator& pattIt, const o2::itsmft::TopologyDictionary* mdict) + void getClusterSizesITS(std::vector& clusSizeVec, int offs, const gsl::span ITSclus, gsl::span::iterator& pattIt, const o2::itsmft::TopologyDictionary* mdict) { for (unsigned int iClus{0}; iClus < ITSclus.size(); ++iClus) { auto& clus = ITSclus[iClus]; @@ -268,13 +270,13 @@ class StrangenessTracker npix = mdict->getNpixels(pattID); patt = mdict->getPattern(pattID); } - clusSizeVec[iClus] = npix; + clusSizeVec[offs + iClus] = npix; } // LOG(info) << " Patt Npixel: " << pattVec[0].getNPixels(); } #ifdef ENABLE_UPGRADES - void getClusterSizesIT3(std::vector& clusSizeVec, const gsl::span ITSclus, gsl::span::iterator& pattIt, const o2::its3::TopologyDictionary* mdict) + void getClusterSizesIT3(std::vector& clusSizeVec, int offs, const gsl::span ITSclus, gsl::span::iterator& pattIt, const o2::its3::TopologyDictionary* mdict) { for (unsigned int iClus{0}; iClus < ITSclus.size(); ++iClus) { auto& clus = ITSclus[iClus]; @@ -291,7 +293,7 @@ class StrangenessTracker npix = mdict->getNpixels(pattID, ib); patt = mdict->getPattern(pattID, ib); } - clusSizeVec[iClus] = npix; + clusSizeVec[offs + iClus] = npix; } } #endif @@ -326,7 +328,7 @@ class StrangenessTracker std::vector mITSvtxBrackets; // time brackets for ITS tracks std::vector mTracksIdxTable; // index table for ITS tracks std::vector mInputClusterSizes; // input cluster sizes - std::vector mInputITSclusters; // input ITS clusters + ITSClusters mInputITSclusters; // input ITS clusters, by composed (layer,index) ID gsl::span mInputITSidxs; // input ITS track-cluster indexes gsl::span mInputV0tracks; // input V0 of decay daughters gsl::span mInputV0Indices; // input V0 indices of decay daughters diff --git a/Detectors/Vertexing/StrangenessTracking/src/StrangenessTracker.cxx b/Detectors/Vertexing/StrangenessTracking/src/StrangenessTracker.cxx index 00f903ea42de9..1b7fe53cc7fea 100644 --- a/Detectors/Vertexing/StrangenessTracking/src/StrangenessTracker.cxx +++ b/Detectors/Vertexing/StrangenessTracking/src/StrangenessTracker.cxx @@ -42,24 +42,32 @@ bool StrangenessTracker::loadData(const o2::globaltracking::RecoContainer& recoD mInputITStracks = recoData.getITSTracks(); mInputITSidxs = recoData.getITSTracksClusterRefs(); - auto compClus = recoData.getITSClusters(); - auto clusPatt = recoData.getITSClustersPatterns(); - auto pattIt = clusPatt.begin(); - auto pattIt2 = clusPatt.begin(); - mInputITSclusters.reserve(compClus.size()); - mInputClusterSizes.resize(compClus.size()); + int nLr = recoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mInputITSclusters.init(nLr); + mInputClusterSizes.clear(); + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mInputITSclusters.beginLayer(lr); + auto compClus = recoData.getITSClusters(lr); + auto clusPatt = recoData.getITSClustersPatterns(lr); + auto pattIt = clusPatt.begin(); + auto pattIt2 = clusPatt.begin(); + int offs = (int)mInputClusterSizes.size(); + mInputITSclusters.getClusters().reserve(offs + compClus.size()); + mInputClusterSizes.resize(offs + compClus.size()); #ifdef ENABLE_UPGRADES - if (o2::GlobalParams::Instance().withITS3) { - o2::its3::ioutils::convertCompactClusters(compClus, pattIt, mInputITSclusters, mIT3Dict); - getClusterSizesIT3(mInputClusterSizes, compClus, pattIt2, mIT3Dict); - } else { - o2::its::ioutils::convertCompactClusters(compClus, pattIt, mInputITSclusters, mITSDict); - getClusterSizesITS(mInputClusterSizes, compClus, pattIt2, mITSDict); - } + if (o2::GlobalParams::Instance().withITS3) { + o2::its3::ioutils::convertCompactClusters(compClus, pattIt, mInputITSclusters.getClusters(), mIT3Dict); + getClusterSizesIT3(mInputClusterSizes, offs, compClus, pattIt2, mIT3Dict); + } else { + o2::its::ioutils::convertCompactClusters(compClus, pattIt, mInputITSclusters.getClusters(), mITSDict); + getClusterSizesITS(mInputClusterSizes, offs, compClus, pattIt2, mITSDict); + } #else - o2::its::ioutils::convertCompactClusters(compClus, pattIt, mInputITSclusters, mITSDict); - getClusterSizesITS(mInputClusterSizes, compClus, pattIt2, mITSDict); + o2::its::ioutils::convertCompactClusters(compClus, pattIt, mInputITSclusters.getClusters(), mITSDict); + getClusterSizesITS(mInputClusterSizes, offs, compClus, pattIt2, mITSDict); #endif + } + mInputITSclusters.finalize(); mITSvtxBrackets.resize(mInputITStracks.size()); for (int i = 0; i < mInputITStracks.size(); i++) { From b26eb7a2a619e7c7ad99a7ab065a91d3eaa63eb6 Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:11 +0200 Subject: [PATCH 08/18] Support per-layer ITS clusters in secondary-vertexing-workflow --- .../include/GlobalTrackingWorkflow/SecondaryVertexingSpec.h | 2 +- .../GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx | 3 ++- .../src/secondary-vertexing-workflow.cxx | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/SecondaryVertexingSpec.h b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/SecondaryVertexingSpec.h index 9211a9cb0c264..1efb8d4216cd3 100644 --- a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/SecondaryVertexingSpec.h +++ b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/SecondaryVertexingSpec.h @@ -25,7 +25,7 @@ namespace vertexing { /// create a processor spec -o2::framework::DataProcessorSpec getSecondaryVertexingSpec(o2::dataformats::GlobalTrackID::mask_t src, bool enableCasc, bool enable3body, bool enableStrangenesTracking, bool enableCCDBParams, bool useMC, bool useGeom); +o2::framework::DataProcessorSpec getSecondaryVertexingSpec(o2::dataformats::GlobalTrackID::mask_t src, bool enableCasc, bool enable3body, bool enableStrangenesTracking, bool enableCCDBParams, bool useMC, bool useGeom, bool itsStag); } // namespace vertexing } // namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx index 3a9de8662f4b5..406b780e28a15 100644 --- a/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx @@ -245,7 +245,7 @@ void SecondaryVertexingSpec::updateTimeDependentParams(ProcessingContext& pc) } DataProcessorSpec getSecondaryVertexingSpec(GTrackID::mask_t src, bool enableCasc, bool enable3body, bool enableStrangenesTracking, bool enableCCDBParams, - bool useMC, bool useGeom) + bool useMC, bool useGeom, bool itsStag) { std::vector outputs; Options opts{ @@ -255,6 +255,7 @@ DataProcessorSpec getSecondaryVertexingSpec(GTrackID::mask_t src, bool enableCas if (enableCCDBParams) { dataRequest->inputs.emplace_back("SVParam", "GLO", "SVPARAM", 0, Lifetime::Condition, ccdbParamSpec("GLO/Config/SVertexerParam")); } + dataRequest->setITSPerLayer(itsStag); GTrackID::mask_t srcClus{}; if (enableStrangenesTracking) { src |= (srcClus = GTrackID::getSourceMask(GTrackID::ITS)); diff --git a/Detectors/GlobalTrackingWorkflow/src/secondary-vertexing-workflow.cxx b/Detectors/GlobalTrackingWorkflow/src/secondary-vertexing-workflow.cxx index 937c995626ef5..85483337e35ab 100644 --- a/Detectors/GlobalTrackingWorkflow/src/secondary-vertexing-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/secondary-vertexing-workflow.cxx @@ -88,6 +88,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto enable3body = !configcontext.options().get("disable-3body-finder"); auto enableStrTr = !configcontext.options().get("disable-strangeness-tracker"); auto useGeom = configcontext.options().get("use-full-geometry"); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); GID::mask_t src = allowedSources & GID::getSourcesMask(configcontext.options().get("vertexing-sources")); GID::mask_t dummy, srcClus = GID::includesDet(DetID::TOF, src) ? GID::getSourceMask(GID::TOF) : dummy; // eventually, TPC clusters will be needed for refit @@ -104,7 +105,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (!configcontext.options().get("disable-root-input")) { specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt)); } - specs.emplace_back(o2::vertexing::getSecondaryVertexingSpec(src, enableCasc, enable3body, enableStrTr, enableCCDBParams, useMC, useGeom)); + specs.emplace_back(o2::vertexing::getSecondaryVertexingSpec(src, enableCasc, enable3body, enableStrTr, enableCCDBParams, useMC, useGeom, doStag)); // only TOF clusters are needed if TOF is involved, no clusters MC needed WorkflowSpec inputspecs; From c40c61d56f792fc2c276e12c960af363d43cedf3 Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:11 +0200 Subject: [PATCH 09/18] Support per-layer ITS clusters in cosmics-match-workflow --- .../include/GlobalTracking/MatchCosmics.h | 3 ++- Detectors/GlobalTracking/src/MatchCosmics.cxx | 23 +++++++++++-------- .../CosmicsMatchingSpec.h | 2 +- .../src/CosmicsMatchingSpec.cxx | 3 ++- .../src/cosmics-match-workflow.cxx | 3 ++- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/Detectors/GlobalTracking/include/GlobalTracking/MatchCosmics.h b/Detectors/GlobalTracking/include/GlobalTracking/MatchCosmics.h index 896351f7baa06..28cf1ab846412 100644 --- a/Detectors/GlobalTracking/include/GlobalTracking/MatchCosmics.h +++ b/Detectors/GlobalTracking/include/GlobalTracking/MatchCosmics.h @@ -23,6 +23,7 @@ #include "ReconstructionDataFormats/GlobalTrackAccessor.h" #include "ReconstructionDataFormats/BaseCluster.h" #include "DataFormatsITSMFT/TopologyDictionary.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "SimulationDataFormat/MCCompLabel.h" #include "GlobalTracking/MatchCosmicsParams.h" #include "CommonUtils/TreeStreamRedirector.h" @@ -134,7 +135,7 @@ class MatchCosmics bool validateMatch(int partner0); void selectWinners(); void refitWinners(const o2::globaltracking::RecoContainer& data); - std::vector> prepareITSClusters(const o2::globaltracking::RecoContainer& data) const; + o2::itsmft::ClustersPerLayer> prepareITSClusters(const o2::globaltracking::RecoContainer& data) const; std::vector mSeeds; std::vector mRecords; diff --git a/Detectors/GlobalTracking/src/MatchCosmics.cxx b/Detectors/GlobalTracking/src/MatchCosmics.cxx index 53c87d39974c7..d3dacf86a128a 100644 --- a/Detectors/GlobalTracking/src/MatchCosmics.cxx +++ b/Detectors/GlobalTracking/src/MatchCosmics.cxx @@ -622,17 +622,22 @@ void MatchCosmics::init() } //________________________________________________________ -std::vector> MatchCosmics::prepareITSClusters(const o2::globaltracking::RecoContainer& data) const +o2::itsmft::ClustersPerLayer> MatchCosmics::prepareITSClusters(const o2::globaltracking::RecoContainer& data) const { - std::vector> itscl; - const auto& clusITS = data.getITSClusters(); - if (clusITS.size()) { - const auto& patterns = data.getITSClustersPatterns(); - itscl.reserve(clusITS.size()); - auto pattIt = patterns.begin(); - o2::its::ioutils::convertCompactClusters(clusITS, pattIt, itscl, mITSDict); + o2::itsmft::ClustersPerLayer> itscl; + int nLr = data.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + itscl.init(nLr); + for (int lr = 0; lr < nLr; lr++) { + itscl.beginLayer(lr); + const auto& clusITS = data.getITSClusters(lr); + if (clusITS.size()) { + auto pattIt = data.getITSClustersPatterns(lr).begin(); + itscl.getClusters().reserve(itscl.size() + clusITS.size()); + o2::its::ioutils::convertCompactClusters(clusITS, pattIt, itscl.getClusters(), mITSDict); + } } - return std::move(itscl); + itscl.finalize(); + return itscl; } //______________________________________________ diff --git a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/CosmicsMatchingSpec.h b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/CosmicsMatchingSpec.h index ed1b0aa1c105d..1b1d9c494dc6c 100644 --- a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/CosmicsMatchingSpec.h +++ b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/CosmicsMatchingSpec.h @@ -24,7 +24,7 @@ namespace globaltracking { /// create a processor spec -framework::DataProcessorSpec getCosmicsMatchingSpec(o2::dataformats::GlobalTrackID::mask_t src, bool usePV, bool useMC); +framework::DataProcessorSpec getCosmicsMatchingSpec(o2::dataformats::GlobalTrackID::mask_t src, bool usePV, bool useMC, bool itsStag); } // namespace globaltracking } // namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx index 338bb86bd8033..20941d8333e27 100644 --- a/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx @@ -183,7 +183,7 @@ void CosmicsMatchingSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getCosmicsMatchingSpec(GTrackID::mask_t src, bool usePV, bool useMC) +DataProcessorSpec getCosmicsMatchingSpec(GTrackID::mask_t src, bool usePV, bool useMC, bool itsStag) { std::vector outputs; Options opts{ @@ -191,6 +191,7 @@ DataProcessorSpec getCosmicsMatchingSpec(GTrackID::mask_t src, bool usePV, bool {"debug-tree-flags", VariantType::Int, 0, {"DebugFlagTypes bit-pattern for debug tree"}}}; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); dataRequest->requestTracks(src, useMC); dataRequest->requestClusters(src, false); // no MC labels for clusters needed for refit only diff --git a/Detectors/GlobalTrackingWorkflow/src/cosmics-match-workflow.cxx b/Detectors/GlobalTrackingWorkflow/src/cosmics-match-workflow.cxx index 46cfd641b3165..67e2fd6cdc4cd 100644 --- a/Detectors/GlobalTrackingWorkflow/src/cosmics-match-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/cosmics-match-workflow.cxx @@ -88,6 +88,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) o2::conf::ConfigurableParam::writeINI("o2match-cosmics-workflow_configuration.ini"); auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); auto useMC = !configcontext.options().get("disable-mc"); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); auto disableRootOut = configcontext.options().get("disable-root-output"); GID::mask_t src = alowedSources & GID::getSourcesMask(configcontext.options().get("track-sources")); @@ -110,7 +111,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt)); } bool usePV = configcontext.options().get("use-pv-info"); - specs.emplace_back(o2::globaltracking::getCosmicsMatchingSpec(src, usePV, useMC)); + specs.emplace_back(o2::globaltracking::getCosmicsMatchingSpec(src, usePV, useMC, doStag)); o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, src, src, src, useMC, dummy); // clusters MC is not needed if (usePV) { From 95b10aa6219e8fec8109e4cb8fc3765e68736973 Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:21 +0200 Subject: [PATCH 10/18] Support per-layer ITS clusters in trd-tracking-workflow --- .../TRDWorkflow/TRDGlobalTrackingSpec.h | 7 +++-- .../workflow/src/TRDGlobalTrackingSpec.cxx | 29 +++++++++++-------- .../workflow/src/trd-tracking-workflow.cxx | 3 +- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h b/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h index 244a3bc0a9a8c..8f83b98c4f075 100644 --- a/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h +++ b/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h @@ -23,6 +23,7 @@ #include "GPUTRDRecoParam.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "DataFormatsTRD/TrackTRD.h" #include "DataFormatsTRD/TrackTriggerRecord.h" #include "DataFormatsTRD/PID.h" @@ -51,6 +52,8 @@ namespace trd class TRDGlobalTracking : public o2::framework::Task { public: + using ITSClusters = o2::itsmft::ClustersPerLayer>; + TRDGlobalTracking(bool useMC, bool withPID, PIDPolicy policy, std::shared_ptr dataRequest, std::shared_ptr gr, o2::dataformats::GlobalTrackID::mask_t src, bool trigRecFilterActive, bool strict, bool requestCTPLumi) : mUseMC(useMC), mWithPID(withPID), mDataRequest(dataRequest), mGGCCDBRequest(gr), mTrkMask(src), mTrigRecFilter(trigRecFilterActive), mStrict(strict), mPolicy(policy), mRequestCTPLumi(requestCTPLumi) {} ~TRDGlobalTracking() override = default; @@ -99,7 +102,7 @@ class TRDGlobalTracking : public o2::framework::Task gsl::span mITSABRefsArray; ///< input ITS-TPC Afterburner ITS tracklets references gsl::span mITSTrackClusIdx; ///< input ITS track cluster indices span gsl::span mITSABTrackClusIdx; ///< input ITSAB track cluster indices span - std::vector> mITSClustersArray; ///< ITS clusters created in run() method from compact clusters + ITSClusters mITSClustersArray; ///< ITS clusters created in run() from compact clusters, by composed ID const o2::itsmft::TopologyDictionary* mITSDict = nullptr; ///< ITS cluster patterns dictionary #ifdef ENABLE_UPGRADES const o2::its3::TopologyDictionary* mIT3Dict = nullptr; ///< IT3 cluster patterns dictionary @@ -114,7 +117,7 @@ class TRDGlobalTracking : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getTRDGlobalTrackingSpec(bool useMC, o2::dataformats::GlobalTrackID::mask_t src, bool trigRecFilterActive, bool strict /* = false*/, bool withPID /* = false*/, PIDPolicy policy /* = PIDPolicy::DEFAULT*/, bool requestCTPLumi); +framework::DataProcessorSpec getTRDGlobalTrackingSpec(bool useMC, o2::dataformats::GlobalTrackID::mask_t src, bool trigRecFilterActive, bool strict /* = false*/, bool withPID /* = false*/, PIDPolicy policy /* = PIDPolicy::DEFAULT*/, bool requestCTPLumi, bool itsStag); } // namespace trd } // namespace o2 diff --git a/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx b/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx index 2539d09eb509b..4cbe5ca7a3609 100644 --- a/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx +++ b/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx @@ -331,20 +331,24 @@ void TRDGlobalTracking::run(ProcessingContext& pc) mITSTrackClusIdx = inputTracks.getITSTracksClusterRefs(); mITSABRefsArray = inputTracks.getITSABRefs(); mITSABTrackClusIdx = inputTracks.getITSABClusterRefs(); - const auto clusITS = inputTracks.getITSClusters(); - const auto patterns = inputTracks.getITSClustersPatterns(); - auto pattIt = patterns.begin(); - mITSClustersArray.clear(); - mITSClustersArray.reserve(clusITS.size()); + int nLr = mDataRequest->getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mITSClustersArray.init(nLr); + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mITSClustersArray.beginLayer(lr); + const auto clusITS = inputTracks.getITSClusters(lr); + auto pattIt = inputTracks.getITSClustersPatterns(lr).begin(); + mITSClustersArray.getClusters().reserve(mITSClustersArray.size() + clusITS.size()); #ifdef ENABLE_UPGRADES - if (o2::GlobalParams::Instance().withITS3) { - o2::its3::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mIT3Dict); - } else { - o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mITSDict); - } + if (o2::GlobalParams::Instance().withITS3) { + o2::its3::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray.getClusters(), mIT3Dict); + } else { + o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray.getClusters(), mITSDict); + } #else - o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mITSDict); + o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray.getClusters(), mITSDict); #endif + } + mITSClustersArray.finalize(); } LOGF(info, "There are %i tracklets in total from %i trigger records", mChainTracking->mIOPtrs.nTRDTracklets, mChainTracking->mIOPtrs.nTRDTriggerRecords); @@ -971,11 +975,12 @@ void TRDGlobalTracking::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getTRDGlobalTrackingSpec(bool useMC, GTrackID::mask_t src, bool trigRecFilterActive, bool strict, bool withPID, PIDPolicy policy, bool requestCTPLumi) +DataProcessorSpec getTRDGlobalTrackingSpec(bool useMC, GTrackID::mask_t src, bool trigRecFilterActive, bool strict, bool withPID, PIDPolicy policy, bool requestCTPLumi, bool itsStag) { std::vector outputs; uint32_t ss = o2::globaltracking::getSubSpec(strict ? o2::globaltracking::MatchingType::Strict : o2::globaltracking::MatchingType::Standard); std::shared_ptr dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); if (strict) { dataRequest->setMatchingInputStrict(); } diff --git a/Detectors/TRD/workflow/src/trd-tracking-workflow.cxx b/Detectors/TRD/workflow/src/trd-tracking-workflow.cxx index bc0da73c09dd5..e5db74716cbd3 100644 --- a/Detectors/TRD/workflow/src/trd-tracking-workflow.cxx +++ b/Detectors/TRD/workflow/src/trd-tracking-workflow.cxx @@ -89,6 +89,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto pulseHeight = configcontext.options().get("enable-ph"); auto digitsSpec = configcontext.options().get("trd-digits-spec"); auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); bool rootInput = !configcontext.options().get("disable-root-input"); GTrackID::mask_t srcTRD = allowedSources & GTrackID::getSourcesMask(configcontext.options().get("track-sources")); if (strict && (srcTRD & ~GTrackID::getSourcesMask("TPC")).any()) { @@ -118,7 +119,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (!configcontext.options().get("disable-root-input")) { specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt)); } - specs.emplace_back(o2::trd::getTRDGlobalTrackingSpec(useMC, srcTRD, trigRecFilterActive, strict, pid, policy, sclOpt.requestCTPLumi)); + specs.emplace_back(o2::trd::getTRDGlobalTrackingSpec(useMC, srcTRD, trigRecFilterActive, strict, pid, policy, sclOpt.requestCTPLumi, doStag)); if (vdexb || gain) { specs.emplace_back(o2::trd::getTRDTrackBasedCalibSpec(srcTRD, vdexb, gain)); } From e50629e536f4eef3167e3099754b34e026140066 Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:21 +0200 Subject: [PATCH 11/18] Support per-layer ITS clusters in tpc-interpolation-workflow --- .../TPCInterpolationSpec.h | 2 +- .../src/TPCInterpolationSpec.cxx | 3 ++- .../src/tpc-interpolation-workflow.cxx | 3 ++- .../include/SpacePoints/TrackInterpolation.h | 4 +++- .../SpacePoints/src/TrackInterpolation.cxx | 19 ++++++++++++------- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCInterpolationSpec.h b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCInterpolationSpec.h index 83dbb1bd0f5fe..bb3ebef84032c 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCInterpolationSpec.h +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCInterpolationSpec.h @@ -68,7 +68,7 @@ class TPCInterpolationDPL : public Task /// create a processor spec framework::DataProcessorSpec getTPCInterpolationSpec(o2::dataformats::GlobalTrackID::mask_t srcCls, o2::dataformats::GlobalTrackID::mask_t srcVtx, o2::dataformats::GlobalTrackID::mask_t srcTrk, - o2::dataformats::GlobalTrackID::mask_t srcTrkMap, bool useMC, bool processITSTPConly, bool sendTrackData, bool debugOutput, bool extDetResid); + o2::dataformats::GlobalTrackID::mask_t srcTrkMap, bool useMC, bool processITSTPConly, bool sendTrackData, bool debugOutput, bool extDetResid, bool itsStag); } // namespace tpc } // namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx index fe2677416edc2..2af349be4fd37 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx @@ -159,9 +159,10 @@ void TPCInterpolationDPL::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getTPCInterpolationSpec(GTrackID::mask_t srcCls, GTrackID::mask_t srcVtx, GTrackID::mask_t srcTrk, GTrackID::mask_t srcTrkMap, bool useMC, bool processITSTPConly, bool sendTrackData, bool debugOutput, bool extDetResid) +DataProcessorSpec getTPCInterpolationSpec(GTrackID::mask_t srcCls, GTrackID::mask_t srcVtx, GTrackID::mask_t srcTrk, GTrackID::mask_t srcTrkMap, bool useMC, bool processITSTPConly, bool sendTrackData, bool debugOutput, bool extDetResid, bool itsStag) { auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); std::vector outputs; if (useMC) { diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/tpc-interpolation-workflow.cxx b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/tpc-interpolation-workflow.cxx index 65a79a8635a49..e8b6aaa07eaba 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/tpc-interpolation-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/tpc-interpolation-workflow.cxx @@ -105,11 +105,12 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) o2::conf::ConfigurableParam::writeINI("o2tpcinterpolation-workflow_configuration.ini"); auto useMC = !configcontext.options().get("disable-mc"); useMC = false; // force disabling MC as long as it is not implemented + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); auto sendTrackData = configcontext.options().get("send-track-data"); auto debugOutput = configcontext.options().get("debug-output"); auto extDetResid = !configcontext.options().get("skip-ext-det-residuals"); - specs.emplace_back(o2::tpc::getTPCInterpolationSpec(srcClusters, srcVtx, srcTracks, srcTracksMap, useMC, processITSTPConly, sendTrackData, debugOutput, extDetResid)); + specs.emplace_back(o2::tpc::getTPCInterpolationSpec(srcClusters, srcVtx, srcTracks, srcTracksMap, useMC, processITSTPConly, sendTrackData, debugOutput, extDetResid, doStag)); if (!configcontext.options().get("disable-root-output")) { specs.emplace_back(o2::tpc::getTPCResidualWriterSpec(sendTrackData, debugOutput)); } diff --git a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackInterpolation.h b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackInterpolation.h index 99e85cf19101a..aa7b71510eebe 100644 --- a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackInterpolation.h +++ b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackInterpolation.h @@ -39,6 +39,7 @@ #include "TPCReconstruction/TPCFastTransformHelperO2.h" #include "DetectorsBase/Propagator.h" #include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "GPUTRDRecoParam.h" #include "TRDBase/Geometry.h" #include "CommonUtils/TreeStreamRedirector.h" @@ -245,6 +246,7 @@ class TrackInterpolation { public: using MatCorrType = o2::base::Propagator::MatCorrType; + using ITSClusters = o2::itsmft::ClustersPerLayer>; /// Default constructor TrackInterpolation() = default; @@ -468,7 +470,7 @@ class TrackInterpolation // ITS specific input only needed for debugging gsl::span mITSTrackClusIdx; ///< input ITS track cluster indices span - std::vector> mITSClustersArray; ///< ITS clusters created in run() method from compact clusters + ITSClusters mITSClustersArray; ///< ITS clusters created in run() from compact clusters, by composed ID std::vector mITSRefitSeedID; ///< seed ID first using refitted ITS track const o2::itsmft::TopologyDictionary* mITSDict = nullptr; ///< cluster patterns dictionary diff --git a/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx b/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx index 571bc00a48763..a3600666ced51 100644 --- a/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx +++ b/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx @@ -351,13 +351,18 @@ void TrackInterpolation::process() return; } mITSTrackClusIdx = mRecoCont->getITSTracksClusterRefs(); - const auto clusITS = mRecoCont->getITSClusters(); - const auto patterns = mRecoCont->getITSClustersPatterns(); - auto pattIt = patterns.begin(); - mITSClustersArray.clear(); - mITSClustersArray.reserve(clusITS.size()); - LOGP(info, "We have {} ITS clusters and the number of patterns is {}", clusITS.size(), patterns.size()); - o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mITSDict); + int nLr = mRecoCont->getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mITSClustersArray.init(nLr); + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mITSClustersArray.beginLayer(lr); + const auto clusITS = mRecoCont->getITSClusters(lr); + const auto patterns = mRecoCont->getITSClustersPatterns(lr); + auto pattIt = patterns.begin(); + mITSClustersArray.getClusters().reserve(mITSClustersArray.size() + clusITS.size()); + LOGP(info, "We have {} ITS clusters and the number of patterns is {} on the layer slot {}", clusITS.size(), patterns.size(), lr); + o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray.getClusters(), mITSDict); + } + mITSClustersArray.finalize(); } // In case we have more input tracks available than are required per TF From d82a9c3c5ed37d2b434fa1327fa96968d34e9031 Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:21 +0200 Subject: [PATCH 12/18] Support per-layer ITS clusters in barrel-alignment-workflow Cluster-overlap bookkeeping in AlignableDetectorITS moves to a per-layer ROF loop; this is the natural granularity anyway since row-side chip overlaps are always within a single layer. Note: this whole module will be superseded by the new MP-2 based package. --- .../AlignmentWorkflow/BarrelAlignmentSpec.h | 2 +- .../Workflow/src/BarrelAlignmentSpec.cxx | 3 +- .../src/barrel-alignment-workflow.cxx | 3 +- .../include/Align/AlignableDetectorITS.h | 7 +- Detectors/Align/src/AlignableDetectorITS.cxx | 190 +++++++++--------- 5 files changed, 108 insertions(+), 97 deletions(-) diff --git a/Detectors/Align/Workflow/include/AlignmentWorkflow/BarrelAlignmentSpec.h b/Detectors/Align/Workflow/include/AlignmentWorkflow/BarrelAlignmentSpec.h index 9793b8609874f..a6ff06dfa570d 100644 --- a/Detectors/Align/Workflow/include/AlignmentWorkflow/BarrelAlignmentSpec.h +++ b/Detectors/Align/Workflow/include/AlignmentWorkflow/BarrelAlignmentSpec.h @@ -27,7 +27,7 @@ namespace align /// create a processor spec framework::DataProcessorSpec getBarrelAlignmentSpec(o2::dataformats::GlobalTrackID::mask_t srcMP, o2::dataformats::GlobalTrackID::mask_t src, - o2::detectors::DetID::mask_t dets, o2::detectors::DetID::mask_t skipDetClusters, bool enableCosmic, int postproc, bool useMC); + o2::detectors::DetID::mask_t dets, o2::detectors::DetID::mask_t skipDetClusters, bool enableCosmic, int postproc, bool useMC, bool itsStag); } // namespace align } // namespace o2 diff --git a/Detectors/Align/Workflow/src/BarrelAlignmentSpec.cxx b/Detectors/Align/Workflow/src/BarrelAlignmentSpec.cxx index 3e42c19f535f8..06762b926346f 100644 --- a/Detectors/Align/Workflow/src/BarrelAlignmentSpec.cxx +++ b/Detectors/Align/Workflow/src/BarrelAlignmentSpec.cxx @@ -357,10 +357,11 @@ void BarrelAlignmentSpec::endOfStream(EndOfStreamContext& ec) mDBGOut.reset(); } -DataProcessorSpec getBarrelAlignmentSpec(GTrackID::mask_t srcMP, GTrackID::mask_t src, DetID::mask_t dets, DetID::mask_t skipDetClusters, bool enableCosmic, int postprocess, bool useMC) +DataProcessorSpec getBarrelAlignmentSpec(GTrackID::mask_t srcMP, GTrackID::mask_t src, DetID::mask_t dets, DetID::mask_t skipDetClusters, bool enableCosmic, int postprocess, bool useMC, bool itsStag) { std::vector outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); bool loadTPCCalib = false; Options opts{ ConfigParamSpec{"apply-xor", o2::framework::VariantType::Bool, false, {"flip the 8-th bit of slope and position (for processing TRD CTFs from 2021 pilot beam)"}}, diff --git a/Detectors/Align/Workflow/src/barrel-alignment-workflow.cxx b/Detectors/Align/Workflow/src/barrel-alignment-workflow.cxx index 276c7a4e82601..ba8bfcff9f0cd 100644 --- a/Detectors/Align/Workflow/src/barrel-alignment-workflow.cxx +++ b/Detectors/Align/Workflow/src/barrel-alignment-workflow.cxx @@ -95,6 +95,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) bool loadTPCTracks = configcontext.options().get("enable-tpc-tracks"); bool enableCosmic = configcontext.options().get("enable-cosmic"); bool useMC = configcontext.options().get("enable-mc"); + bool doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); DetID::mask_t dets = allowedDets & DetID::getMask(configcontext.options().get("detectors")); DetID::mask_t skipDetClusters; // optionally skip automatically loaded clusters @@ -154,7 +155,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt)); } - specs.emplace_back(o2::align::getBarrelAlignmentSpec(srcMP, src, dets, skipDetClusters, enableCosmic, postprocess, useMC)); + specs.emplace_back(o2::align::getBarrelAlignmentSpec(srcMP, src, dets, skipDetClusters, enableCosmic, postprocess, useMC, doStag)); // RS FIXME: check which clusters are really needed if (!postprocess) { GID::mask_t dummy; diff --git a/Detectors/Align/include/Align/AlignableDetectorITS.h b/Detectors/Align/include/Align/AlignableDetectorITS.h index 5f1816d8368b8..d7ae3a899a74e 100644 --- a/Detectors/Align/include/Align/AlignableDetectorITS.h +++ b/Detectors/Align/include/Align/AlignableDetectorITS.h @@ -22,6 +22,7 @@ #include "ReconstructionDataFormats/TrackParametrizationWithError.h" #include "ReconstructionDataFormats/BaseCluster.h" #include "ITSMFTReconstruction/ChipMappingITS.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" namespace o2 { @@ -76,9 +77,9 @@ class AlignableDetectorITS : public AlignableDetector // protected: // - std::vector mITSClustersArray; - std::vector mOverlapCandidateID; // pool of indices for potentially overlapping clusters - std::vector mOverlapClusRef; // 1st entry in mOverlapCandidateID for the overlapping cluster indices of each cluster + o2::itsmft::ClustersPerLayer mITSClustersArray; + std::vector mOverlapCandidateID; // pool of composed IDs of the potentially overlapping clusters + std::vector mOverlapClusRef; // 1st entry in mOverlapCandidateID for the overlapping clusters of each cluster, indexed by the flat cluster index std::vector mOverlaps; const o2::itsmft::TopologyDictionary* mITSDict{nullptr}; // cluster patterns dictionary // diff --git a/Detectors/Align/src/AlignableDetectorITS.cxx b/Detectors/Align/src/AlignableDetectorITS.cxx index 7387ae4620bf3..1528102369ec8 100644 --- a/Detectors/Align/src/AlignableDetectorITS.cxx +++ b/Detectors/Align/src/AlignableDetectorITS.cxx @@ -219,7 +219,7 @@ int AlignableDetectorITS::processPoints(GIndex gid, int npntCut, bool inv) if (clus.getCount()) { // there is an overlap, find best matching cluster nOverlaps--; - int bestClusID = -1, clusIDtoCheck = mOverlapClusRef[clusIDs[icl]]; + int bestClusID = -1, clusIDtoCheck = mOverlapClusRef[mITSClustersArray.flatIndex(clusIDs[icl])]; float bestChi2 = algConf.ITSOverlapMaxChi2; auto trPropOvl = trcProp; for (int iov = 0; iov < clus.getCount(); iov++) { @@ -256,18 +256,11 @@ bool AlignableDetectorITS::prepareDetectorData() // prepare TF data for processing: convert clusters const auto& algConf = AlignConfig::Instance(); auto recoData = mController->getRecoContainer(); - const auto clusITS = recoData->getITSClusters(); - const auto clusITSROF = recoData->getITSClustersROFRecords(); - const auto patterns = recoData->getITSClustersPatterns(); - auto pattIt = patterns.begin(); - mITSClustersArray.clear(); - mITSClustersArray.reserve(clusITS.size()); + int nLr = recoData->getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mITSClustersArray.init(nLr); if (algConf.ITSOverlapMargin > 0) { mOverlapClusRef.clear(); - mOverlapClusRef.resize(clusITS.size(), -1); - mOverlapCandidateID.clear(); - mOverlapCandidateID.reserve(clusITS.size()); } static std::vector edgeClusters; int ROFCount = 0; @@ -278,103 +271,118 @@ bool AlignableDetectorITS::prepareDetectorData() }; std::array chipROFStart{}; // fill only for clusters with overlaps - for (const auto& rof : clusITSROF) { - int maxic = rof.getFirstEntry() + rof.getNEntries(); - edgeClusters.clear(); - for (int ic = rof.getFirstEntry(); ic < maxic; ic++) { - const auto& c = clusITS[ic]; - int16_t sensID = c.getSensorID(); - auto* sensor = getSensor(sensID); - double sigmaY2, sigmaZ2, sigmaYZ = 0, locXYZC[3], traXYZ[3]; - auto pattItCopy = pattIt; - auto locXYZ = o2::its::ioutils::extractClusterDataA(c, pattIt, mITSDict, sigmaY2, sigmaZ2); // local ideal coordinates - const auto& matAlg = sensor->getMatrixClAlg(); // local alignment matrix !!! RS FIXME - matAlg.LocalToMaster(locXYZ.data(), locXYZC); // aligned point in the local frame - const auto& mat = sensor->getMatrixT2L(); // RS FIXME check if correct - mat.MasterToLocal(locXYZC, traXYZ); - auto& cl3d = mITSClustersArray.emplace_back(sensID, traXYZ[0], traXYZ[1], traXYZ[2], sigmaY2, sigmaZ2, sigmaYZ); // local --> tracking + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mITSClustersArray.beginLayer(lr); + const auto clusITS = recoData->getITSClusters(lr); + const auto clusITSROF = recoData->getITSClustersROFRecords(lr); + const auto patterns = recoData->getITSClustersPatterns(lr); + auto pattIt = patterns.begin(); + const int clOffs = mITSClustersArray.getFirstIndex(lr); // flat index of the 1st cluster of this layer slot + mITSClustersArray.getClusters().reserve(clOffs + clusITS.size()); + if (algConf.ITSOverlapMargin > 0) { + mOverlapClusRef.resize(clOffs + clusITS.size(), -1); + mOverlapCandidateID.reserve(clOffs + clusITS.size()); + } + + for (const auto& rof : clusITSROF) { + int maxic = rof.getFirstEntry() + rof.getNEntries(); + edgeClusters.clear(); + for (int ic = rof.getFirstEntry(); ic < maxic; ic++) { + const auto& c = clusITS[ic]; + int16_t sensID = c.getSensorID(); + auto* sensor = getSensor(sensID); + double sigmaY2, sigmaZ2, sigmaYZ = 0, locXYZC[3], traXYZ[3]; + auto pattItCopy = pattIt; + auto locXYZ = o2::its::ioutils::extractClusterDataA(c, pattIt, mITSDict, sigmaY2, sigmaZ2); // local ideal coordinates + const auto& matAlg = sensor->getMatrixClAlg(); // local alignment matrix !!! RS FIXME + matAlg.LocalToMaster(locXYZ.data(), locXYZC); // aligned point in the local frame + const auto& mat = sensor->getMatrixT2L(); // RS FIXME check if correct + mat.MasterToLocal(locXYZC, traXYZ); + auto& cl3d = mITSClustersArray.getClusters().emplace_back(sensID, traXYZ[0], traXYZ[1], traXYZ[2], sigmaY2, sigmaZ2, sigmaYZ); // local --> tracking - if (algConf.ITSOverlapMargin > 0) { - // fill chips overlaps info for clusters whose center is within of the algConf.ITSOverlapMargin distance from the chip min or max row edge - // but the pixel closest to this edge has distance of at least algConf.ITSOverlapEdgeRows from the edge - int row = 0, col = 0; - o2::itsmft::SegmentationAlpide::localToDetectorUnchecked(locXYZ[0], locXYZ[2], row, col); - int drow = row < o2::itsmft::SegmentationAlpide::NRows / 2 ? row : o2::itsmft::SegmentationAlpide::NRows - row - 1; // distance to the edge - if (drow * o2::itsmft::SegmentationAlpide::PitchRow < algConf.ITSOverlapMargin) { // rough check is passed, check if the edge cluster is indeed good - cl3d.setBit(row < o2::itsmft::SegmentationAlpide::NRows / 2 ? EdgeFlags::LowRow : EdgeFlags::HighRow); // flag that this is an edge cluster and indicate the low/high row side - // check if it is not too close to the edge (to be biased) - if (algConf.ITSOverlapEdgeRows > 0) { // is there a restriction? - auto pattID = c.getPatternID(); - drow = c.getRow(); - if (pattID != itsmft::CompCluster::InvalidPatternID) { - if (!mITSDict->isGroup(pattID)) { - const auto& patt = mITSDict->getPattern(pattID); // reference pixel is min row/col corner + if (algConf.ITSOverlapMargin > 0) { + // fill chips overlaps info for clusters whose center is within of the algConf.ITSOverlapMargin distance from the chip min or max row edge + // but the pixel closest to this edge has distance of at least algConf.ITSOverlapEdgeRows from the edge + int row = 0, col = 0; + o2::itsmft::SegmentationAlpide::localToDetectorUnchecked(locXYZ[0], locXYZ[2], row, col); + int drow = row < o2::itsmft::SegmentationAlpide::NRows / 2 ? row : o2::itsmft::SegmentationAlpide::NRows - row - 1; // distance to the edge + if (drow * o2::itsmft::SegmentationAlpide::PitchRow < algConf.ITSOverlapMargin) { // rough check is passed, check if the edge cluster is indeed good + cl3d.setBit(row < o2::itsmft::SegmentationAlpide::NRows / 2 ? EdgeFlags::LowRow : EdgeFlags::HighRow); // flag that this is an edge cluster and indicate the low/high row side + // check if it is not too close to the edge (to be biased) + if (algConf.ITSOverlapEdgeRows > 0) { // is there a restriction? + auto pattID = c.getPatternID(); + drow = c.getRow(); + if (pattID != itsmft::CompCluster::InvalidPatternID) { + if (!mITSDict->isGroup(pattID)) { + const auto& patt = mITSDict->getPattern(pattID); // reference pixel is min row/col corner + if (row > o2::itsmft::SegmentationAlpide::NRows / 2) { + drow = o2::itsmft::SegmentationAlpide::NRows - 1 - (drow + patt.getRowSpan() - 1); + } + } else { // group: reference pixel is the one containing the COG + o2::itsmft::ClusterPattern patt(pattItCopy); + drow = row < o2::itsmft::SegmentationAlpide::NRows / 2 ? drow - patt.getRowSpan() / 2 : o2::itsmft::SegmentationAlpide::NRows - 1 - (drow + patt.getRowSpan() / 2 - 1); + } + } else { + o2::itsmft::ClusterPattern patt(pattItCopy); // reference pixel is min row/col corner if (row > o2::itsmft::SegmentationAlpide::NRows / 2) { drow = o2::itsmft::SegmentationAlpide::NRows - 1 - (drow + patt.getRowSpan() - 1); } - } else { // group: reference pixel is the one containing the COG - o2::itsmft::ClusterPattern patt(pattItCopy); - drow = row < o2::itsmft::SegmentationAlpide::NRows / 2 ? drow - patt.getRowSpan() / 2 : o2::itsmft::SegmentationAlpide::NRows - 1 - (drow + patt.getRowSpan() / 2 - 1); } - } else { - o2::itsmft::ClusterPattern patt(pattItCopy); // reference pixel is min row/col corner - if (row > o2::itsmft::SegmentationAlpide::NRows / 2) { - drow = o2::itsmft::SegmentationAlpide::NRows - 1 - (drow + patt.getRowSpan() - 1); + if (drow < algConf.ITSOverlapEdgeRows) { // too close to the edge, flag this + cl3d.setBit(EdgeFlags::Biased); } } - if (drow < algConf.ITSOverlapEdgeRows) { // too close to the edge, flag this - cl3d.setBit(EdgeFlags::Biased); - } - } - if (!cl3d.isBitSet(EdgeFlags::Biased)) { - if (chipROFStart[sensID].rofCount != ROFCount) { // remember 1st entry - chipROFStart[sensID].rofCount = ROFCount; - chipROFStart[sensID].chipFirstEntry = edgeClusters.size(); // remember 1st entry of edge cluster for this chip + if (!cl3d.isBitSet(EdgeFlags::Biased)) { + if (chipROFStart[sensID].rofCount != ROFCount) { // remember 1st entry + chipROFStart[sensID].rofCount = ROFCount; + chipROFStart[sensID].chipFirstEntry = edgeClusters.size(); // remember 1st entry of edge cluster for this chip + } + edgeClusters.push_back(clOffs + ic); } - edgeClusters.push_back(ic); } } - } - } // clusters of ROF - // relate edge clusters of ROF to each other - int prevSensID = -1; - for (auto ic : edgeClusters) { - auto& cl = mITSClustersArray[ic]; - int sensID = cl.getSensorID(); - auto ovl = mOverlaps[sensID]; - int ovlCount = 0; - for (int ir = 0; ir < OVL::NSides; ir++) { - if (ovl.rowSide[ir] == OVL::NONE) { // no overlap from this row side - continue; - } - int chipOvl = ovl.rowSide[ir]; // look for overlaps with this chip - // are there clusters with overlaps on chipOvl? - if (chipROFStart[chipOvl].rofCount == ROFCount) { - auto oClusID = edgeClusters[chipROFStart[chipOvl].chipFirstEntry]; - while (oClusID < int(mITSClustersArray.size())) { - auto oClus = mITSClustersArray[oClusID]; - if (oClus.getSensorID() != sensID) { - break; // no more clusters on the overlapping chip - } - if (oClus.isBitSet(ovl.rowSideOverlap[ir]) && // make sure that the edge cluster is on the right side of the row - !oClus.isBitSet(EdgeFlags::Biased) && // not too close to the edge - std::abs(oClus.getZ() - cl.getZ()) < algConf.ITSOverlapMaxDZ) { // apply fiducial cut on Z distance of 2 clusters - // register overlaping cluster - if (!ovlCount) { // 1st overlap - mOverlapClusRef[ic] = mOverlapCandidateID.size(); + } // clusters of ROF + // relate edge clusters of ROF to each other + int prevSensID = -1; + for (auto ic : edgeClusters) { // ic is the flat cluster index here + auto& cl = mITSClustersArray.getClusters()[ic]; + int sensID = cl.getSensorID(); + auto ovl = mOverlaps[sensID]; + int ovlCount = 0; + for (int ir = 0; ir < OVL::NSides; ir++) { + if (ovl.rowSide[ir] == OVL::NONE) { // no overlap from this row side + continue; + } + int chipOvl = ovl.rowSide[ir]; // look for overlaps with this chip + // are there clusters with overlaps on chipOvl? + if (chipROFStart[chipOvl].rofCount == ROFCount) { + auto oClusID = edgeClusters[chipROFStart[chipOvl].chipFirstEntry]; + while (oClusID < int(mITSClustersArray.size())) { + auto oClus = mITSClustersArray.getClusters()[oClusID]; + if (oClus.getSensorID() != sensID) { + break; // no more clusters on the overlapping chip } - mOverlapCandidateID.push_back(oClusID); - ovlCount++; + if (oClus.isBitSet(ovl.rowSideOverlap[ir]) && // make sure that the edge cluster is on the right side of the row + !oClus.isBitSet(EdgeFlags::Biased) && // not too close to the edge + std::abs(oClus.getZ() - cl.getZ()) < algConf.ITSOverlapMaxDZ) { // apply fiducial cut on Z distance of 2 clusters + // register overlaping cluster + if (!ovlCount) { // 1st overlap + mOverlapClusRef[ic] = mOverlapCandidateID.size(); + } + mOverlapCandidateID.push_back(o2::itsmft::composeClusID(lr, oClusID - clOffs)); // store the composed ID + ovlCount++; + } + oClusID++; } - oClusID++; } } + cl.setCount(std::min(127, ovlCount)); } - cl.setCount(std::min(127, ovlCount)); - } - ROFCount++; - } // loop over ROFs + ROFCount++; + } // loop over ROFs + } // loop over the layer slots + mITSClustersArray.finalize(); return true; } From fcbb158a0c85e863269fb2445ffb9b7528609ffa Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:35 +0200 Subject: [PATCH 13/18] Support per-layer ITS clusters in check-resid-workfow and trackMCStudy-workflow TrackMCStudy's ITS occupancy counting sums over the layer slots, each with its own ROF cursor advanced by DPLAlpideParam::getROFLengthInBC/ getROFBiasInBC(layer). --- .../GlobalTrackingStudy/CheckResidSpec.h | 2 +- .../GlobalTrackingStudy/TrackMCStudy.h | 2 +- .../study/src/CheckResidSpec.cxx | 24 +++-- .../study/src/TrackMCStudy.cxx | 95 +++++++++++-------- .../study/src/check-resid-workflow.cxx | 3 +- .../study/src/trackMCStudy-workflow.cxx | 3 +- 6 files changed, 78 insertions(+), 51 deletions(-) diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h index 3cae8e94b8e68..f9a2c7cdab912 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, bool postProcOnly); +o2::framework::DataProcessorSpec getCheckResidSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool drawOnly, bool postProcOnly, bool itsStag); } // namespace o2::checkresid diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudy.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudy.h index 18ce3dbf8ab87..097b4600bcc52 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudy.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudy.h @@ -20,7 +20,7 @@ namespace o2::trackstudy { /// create a processor spec -o2::framework::DataProcessorSpec getTrackMCStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool checkSV); +o2::framework::DataProcessorSpec getTrackMCStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool checkSV, bool itsStag); } // namespace o2::trackstudy diff --git a/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx index 93249946c9011..12546e81b0c9c 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx @@ -17,6 +17,7 @@ #include #include "DataFormatsGlobalTracking/RecoContainer.h" #include "DataFormatsITSMFT/TrkClusRef.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" #include "ReconstructionDataFormats/TrackTPCITS.h" #include "ReconstructionDataFormats/GlobalTrackID.h" @@ -70,6 +71,7 @@ using PVertex = o2::dataformats::PrimaryVertex; using V2TRef = o2::dataformats::VtxTrackRef; using VTIndex = o2::dataformats::VtxTrackIndex; using GTrackID = o2::dataformats::GlobalTrackID; +using ITSClusters = o2::itsmft::ClustersPerLayer>; using timeEst = o2::dataformats::TimeStampWithError; class CheckResidSpec final : public Task @@ -101,7 +103,7 @@ class CheckResidSpec final : public Task bool mMeanVertexUpdated = false; float mITSROFrameLengthMUS = 0.f; o2::dataformats::MeanVertexObject mMeanVtx{}; - std::vector> mITSClustersArray; ///< ITS clusters created in run() method from compact clusters + ITSClusters mITSClustersArray; ///< ITS clusters from compact clusters, by composed ID const o2::itsmft::TopologyDictionary* mITSDict = nullptr; ///< cluster patterns dictionary o2::vertexing::PVertexer mVertexer; std::shared_ptr mDataRequest; @@ -240,14 +242,17 @@ void CheckResidSpec::process() const auto itsTracks = mRecoData->getITSTracks(); // const auto itsLbls = mRecoData->getITSTracksMCLabels(); const auto itsClRefs = mRecoData->getITSTracksClusterRefs(); - const auto clusITS = mRecoData->getITSClusters(); - const auto patterns = mRecoData->getITSClustersPatterns(); const auto& params = o2::checkresid::CheckResidConfig::Instance(); - auto pattIt = patterns.begin(); - mITSClustersArray.clear(); - mITSClustersArray.reserve(clusITS.size()); - - o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mITSDict); + int nLr = mDataRequest->getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mITSClustersArray.init(nLr); + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mITSClustersArray.beginLayer(lr); + const auto clusITS = mRecoData->getITSClusters(lr); + auto pattIt = mRecoData->getITSClustersPatterns(lr).begin(); + mITSClustersArray.getClusters().reserve(mITSClustersArray.size() + clusITS.size()); + o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray.getClusters(), mITSDict); + } + mITSClustersArray.finalize(); auto pvvec = mRecoData->getPrimaryVertices(); auto trackIndex = mRecoData->getPrimaryVertexMatchedTracks(); // Global ID's for associated tracks @@ -990,10 +995,11 @@ void CheckResidSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) } } -DataProcessorSpec getCheckResidSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool drawOnly, bool postProcOnly) +DataProcessorSpec getCheckResidSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool drawOnly, bool postProcOnly, bool itsStag) { std::vector outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); if (!drawOnly && !postProcOnly) { bool useMC = false; dataRequest->requestTracks(srcTracks, useMC); diff --git a/Detectors/GlobalTrackingWorkflow/study/src/TrackMCStudy.cxx b/Detectors/GlobalTrackingWorkflow/study/src/TrackMCStudy.cxx index 70cdf67099d76..ec70cfc9e2115 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/TrackMCStudy.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/TrackMCStudy.cxx @@ -33,6 +33,7 @@ #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" #include "FT0Reconstruction/InteractionTag.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsBase/GRPGeomHelper.h" @@ -80,6 +81,7 @@ using VTIndex = o2::dataformats::VtxTrackIndex; using VTIndexV = std::pair; using GTrackID = o2::dataformats::GlobalTrackID; using TBracket = o2::math_utils::Bracketf_t; +using ITSClusters = o2::itsmft::ClustersPerLayer>; using timeEst = o2::dataformats::TimeStampWithError; @@ -121,7 +123,7 @@ class TrackMCStudy final : public Task std::vector mIntBC; ///< interaction global BC wrt TF start std::vector mTPCOcc; ///< TPC occupancy for this interaction time std::vector mITSOcc; //< N ITS clusters in the ROF containing collision - std::vector> mITSClustersArray; ///< ITS clusters created in run() method from compact clusters + ITSClusters mITSClustersArray; ///< ITS clusters from compact clusters, by composed ID const o2::itsmft::TopologyDictionary* mITSDict = nullptr; ///< cluster patterns dictionary bool mCheckSV = false; //< check SV binding (apart from prongs availability) @@ -321,10 +323,18 @@ void TrackMCStudy::process(const o2::globaltracking::RecoContainer& recoData) { const auto* digconst = mcReader.getDigitizationContext(); const auto& mcEvRecords = digconst->getEventRecords(false); - int ITSTimeBias = o2::itsmft::DPLAlpideParam::Instance().roFrameBiasInBC; - int ITSROFLen = o2::itsmft::DPLAlpideParam::Instance().roFrameLengthInBC; - unsigned int rofCount = 0; - const auto ITSClusROFRec = recoData.getITSClustersROFRecords(); + // in the staggered readout every layer has its own ROF length, bias and ROFRecords, hence the + // occupancy is summed over the layer slots, each with its own (monotonic) ROF cursor + const int nLrOcc = recoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + const auto& alpParITS = o2::itsmft::DPLAlpideParam::Instance(); + std::array, o2::globaltracking::MaxITSLayers> ITSClusROFRec{}; + std::array ITSTimeBias{}, ITSROFLen{}; + std::array rofCount{}; + for (int lr = 0; lr < nLrOcc; lr++) { + ITSClusROFRec[lr] = recoData.getITSClustersROFRecords(lr); + ITSTimeBias[lr] = alpParITS.getROFBiasInBC(lr); + ITSROFLen[lr] = alpParITS.getROFLengthInBC(lr); + } for (const auto& mcIR : mcEvRecords) { long tbc = mcIR.differenceInBC(recoData.startIR); auto& mcVtx = mMCVtVec.emplace_back(); @@ -335,18 +345,21 @@ void TrackMCStudy::process(const o2::globaltracking::RecoContainer& recoData) mTPCOcc.push_back(occBin < 0 ? mTBinClOcc[0] : (occBin >= mTBinClOcc.size() ? mTBinClOcc.back() : mTBinClOcc[occBin])); // fill ITS occupancy long gbc = mcIR.toLong(); - while (rofCount < ITSClusROFRec.size()) { - long rofbcMin = ITSClusROFRec[rofCount].getBCData().toLong() + ITSTimeBias, rofbcMax = rofbcMin + ITSROFLen; - if (gbc < rofbcMin) { // IRs and ROFs are sorted, so this IR is prior of all ROFs - mITSOcc.push_back(0); - } else if (gbc < rofbcMax) { - mITSOcc.push_back(ITSClusROFRec[rofCount].getNEntries()); - } else { - rofCount++; // test next ROF - continue; + int itsOcc = 0; + for (int lr = 0; lr < nLrOcc; lr++) { + const auto& rofs = ITSClusROFRec[lr]; + while (rofCount[lr] < rofs.size()) { + long rofbcMin = rofs[rofCount[lr]].getBCData().toLong() + ITSTimeBias[lr], rofbcMax = rofbcMin + ITSROFLen[lr]; + if (gbc < rofbcMin) { // IRs and ROFs are sorted, so this IR is prior of all remaining ROFs of this layer + break; + } else if (gbc < rofbcMax) { + itsOcc += rofs[rofCount[lr]].getNEntries(); + break; + } + rofCount[lr]++; // test next ROF } - break; } + mITSOcc.push_back(itsOcc); // 0 if the IR is before the 1st or after the last ROF of every layer if (mNTPCOccBinLengthInv > 0.f) { mcVtx.occTPCV.resize(params.nOccBinsDrift); int grp = TMath::Max(1, TMath::Nint(params.nTBPerOccBin * mNTPCOccBinLengthInv)); @@ -362,9 +375,6 @@ void TrackMCStudy::process(const o2::globaltracking::RecoContainer& recoData) mcVtx.occTPCV[ib] = smb; } } - if (rofCount >= ITSClusROFRec.size()) { - mITSOcc.push_back(0); // IR after the last ROF - } } } // collect interesting MC particle (tracks and parents) @@ -940,18 +950,21 @@ void TrackMCStudy::fillMCClusterInfo(const o2::globaltracking::RecoContainer& re } } // fill ITS cluster info - const auto* mcITSClusters = recoData.getITSClustersMCLabels(); - const auto& ITSClusters = recoData.getITSClusters(); - for (unsigned int icl = 0; icl < ITSClusters.size(); icl++) { - const auto labels = mcITSClusters->getLabels(icl); - for (const auto& lbl : labels) { - auto entry = mSelMCTracks.find(lbl); - if (entry == mSelMCTracks.end()) { // not selected - continue; + int nLrCl = recoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + for (int lr = 0; lr < nLrCl; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + const auto* mcITSClusters = recoData.getITSClustersMCLabels(lr); + const auto& ITSClusters = recoData.getITSClusters(lr); + for (unsigned int icl = 0; icl < ITSClusters.size(); icl++) { + const auto labels = mcITSClusters->getLabels(icl); + for (const auto& lbl : labels) { + auto entry = mSelMCTracks.find(lbl); + if (entry == mSelMCTracks.end()) { // not selected + continue; + } + auto& mctr = entry->second.mcTrackInfo; + mctr.nITSCl++; + mctr.pattITSCl |= 0x1 << o2::itsmft::ChipMappingITS::getLayer(ITSClusters[icl].getChipID()); } - auto& mctr = entry->second.mcTrackInfo; - mctr.nITSCl++; - mctr.pattITSCl |= 0x1 << o2::itsmft::ChipMappingITS::getLayer(ITSClusters[icl].getChipID()); } } @@ -1306,17 +1319,22 @@ void TrackMCStudy::processITSTracks(const o2::globaltracking::RecoContainer& rec const auto itsTracks = recoData.getITSTracks(); const auto itsLbls = recoData.getITSTracksMCLabels(); const auto itsClRefs = recoData.getITSTracksClusterRefs(); - const auto clusITS = recoData.getITSClusters(); - const auto patterns = recoData.getITSClustersPatterns(); const auto& params = o2::trackstudy::TrackMCStudyConfig::Instance(); - auto pattIt = patterns.begin(); - mITSClustersArray.clear(); - mITSClustersArray.reserve(clusITS.size()); - - o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mITSDict); + int nLr = recoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mITSClustersArray.init(nLr); + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mITSClustersArray.beginLayer(lr); + const auto clusITS = recoData.getITSClusters(lr); + const auto patterns = recoData.getITSClustersPatterns(lr); + auto pattIt = patterns.begin(); + mITSClustersArray.getClusters().reserve(mITSClustersArray.size() + clusITS.size()); + o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray.getClusters(), mITSDict); + LOGP(info, "We have {} ITS clusters and the number of patterns is {} on the layer slot {}", clusITS.size(), patterns.size(), lr); + } + mITSClustersArray.finalize(); auto geom = o2::its::GeometryTGeo::Instance(); int ntr = itsLbls.size(); - LOGP(info, "We have {} ITS clusters and the number of patterns is {}, ITSdict:{} NMCLabels: {}", clusITS.size(), patterns.size(), mITSDict != nullptr, itsLbls.size()); + LOGP(info, "In total {} ITS clusters, ITSdict:{} NMCLabels: {}", mITSClustersArray.size(), mITSDict != nullptr, itsLbls.size()); std::vector evord(ntr); std::iota(evord.begin(), evord.end(), 0); @@ -1376,7 +1394,7 @@ void TrackMCStudy::processITSTracks(const o2::globaltracking::RecoContainer& rec } } -DataProcessorSpec getTrackMCStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool checkSV) +DataProcessorSpec getTrackMCStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool checkSV, bool itsStag) { std::vector outputs; Options opts{ @@ -1387,6 +1405,7 @@ DataProcessorSpec getTrackMCStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask {"max-tpc-dcaz", VariantType::Float, 2.f, {"Cut on TPC dcaZ"}}, {"min-x-prop", VariantType::Float, 6.f, {"track should be propagated to this X at least"}}}; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); bool useMC = true; dataRequest->requestTracks(srcTracks, useMC); dataRequest->requestClusters(srcClusters, useMC); diff --git a/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx b/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx index 0791d72474ad3..ca1c12e3217a3 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx @@ -59,6 +59,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) bool drawOnly = configcontext.options().get("draw-external-only"); bool postProcOnly = configcontext.options().get("postproc-external-only"); + bool doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); 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"); @@ -75,7 +76,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) allowedSourcesTrc = {}; allowedSourcesClus = {}; } - specs.emplace_back(o2::checkresid::getCheckResidSpec(srcTrc, srcCls, drawOnly, postProcOnly)); + specs.emplace_back(o2::checkresid::getCheckResidSpec(srcTrc, srcCls, drawOnly, postProcOnly, doStag)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit if (!drawOnly) { diff --git a/Detectors/GlobalTrackingWorkflow/study/src/trackMCStudy-workflow.cxx b/Detectors/GlobalTrackingWorkflow/study/src/trackMCStudy-workflow.cxx index 07eb271f66580..4b6212d01b018 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/trackMCStudy-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/trackMCStudy-workflow.cxx @@ -60,6 +60,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) WorkflowSpec specs; auto useMC = !configcontext.options().get("disable-mc"); auto checkSV = !configcontext.options().get("ignore-sv-check"); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); if (!useMC) { throw std::runtime_error("MC cannot be disabled for this workflow"); } @@ -86,7 +87,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt)); } - specs.emplace_back(o2::trackstudy::getTrackMCStudySpec(srcTrc, srcCls, checkSV)); + specs.emplace_back(o2::trackstudy::getTrackMCStudySpec(srcTrc, srcCls, checkSV, doStag)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); From 7c76602b424d0ac53fc160d926bcb49d89c3e1c2 Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:35 +0200 Subject: [PATCH 14/18] Support per-layer ITS clusters in its3-alignment-workflow --- .../include/ITS3Align/AlignmentSpec.h | 2 +- .../ITS3/alignment/src/AlignmentSpec.cxx | 105 ++++++++++-------- .../ITS3/alignment/src/alignment-workflow.cxx | 3 +- 3 files changed, 59 insertions(+), 51 deletions(-) diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentSpec.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentSpec.h index d171454bc4794..ddf450beabd01 100644 --- a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentSpec.h +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentSpec.h @@ -29,7 +29,7 @@ enum class OutputOpt : uint8_t { }; using OutputEnum = utils::EnumFlags; -o2::framework::DataProcessorSpec getAlignmentSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, bool withPV, bool withITS3, OutputEnum out); +o2::framework::DataProcessorSpec getAlignmentSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, bool withPV, bool withITS3, OutputEnum out, bool itsStag); } // namespace o2::its3::align #endif diff --git a/Detectors/Upgrades/ITS3/alignment/src/AlignmentSpec.cxx b/Detectors/Upgrades/ITS3/alignment/src/AlignmentSpec.cxx index edba6718ab268..142dd55579dde 100644 --- a/Detectors/Upgrades/ITS3/alignment/src/AlignmentSpec.cxx +++ b/Detectors/Upgrades/ITS3/alignment/src/AlignmentSpec.cxx @@ -31,6 +31,7 @@ #include "Framework/Task.h" #include "ITSBase/GeometryTGeo.h" #include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsBase/Propagator.h" #include "DetectorsBase/GRPGeomHelper.h" @@ -124,7 +125,7 @@ class AlignmentSpec final : public Task bool prepareITSTrack(int iTrk, const o2::its::TrackITS& itsTrack, Track& resTrack); // prepare ITS measuremnt points - void prepareMeasurments(std::span clusters, std::span pattIt); + void prepareMeasurments(); // build track to vertex association void buildT2V(); @@ -143,7 +144,7 @@ class AlignmentSpec final : public Task const o2::its3::TopologyDictionary* mIT3Dict{nullptr}; o2::globaltracking::RecoContainer* mRecoData = nullptr; std::unique_ptr mcReader; - std::vector mITSTrackingInfo; + o2::itsmft::ClustersPerLayer mITSTrackingInfo; // addressed by the composed (layer,index) ID std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; std::unique_ptr mHierarchy; // tree-hiearchy @@ -194,13 +195,11 @@ void AlignmentSpec::process() const auto bz = prop->getNominalBz(); const auto itsTracks = mRecoData->getITSTracks(); const auto itsClRefs = mRecoData->getITSTracksClusterRefs(); - const auto clusITS = mRecoData->getITSClusters(); - const auto patterns = mRecoData->getITSClustersPatterns(); std::span mcLbls; if (mUseMC) { mcLbls = mRecoData->getITSTracksMCLabels(); } - prepareMeasurments(clusITS, patterns); + prepareMeasurments(); if (mWithPV) { buildT2V(); @@ -731,53 +730,60 @@ bool AlignmentSpec::prepareITSTrack(int iTrk, const o2::its::TrackITS& itsTrack, return true; } -void AlignmentSpec::prepareMeasurments(std::span clusters, std::span patterns) +void AlignmentSpec::prepareMeasurments() { - LOGP(info, "Preparing {} measurments", clusters.size()); auto geom = its::GeometryTGeo::Instance(); geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G)); - mITSTrackingInfo.clear(); - mITSTrackingInfo.reserve(clusters.size()); - auto pattIt = patterns.begin(); - for (const auto& cls : clusters) { - const auto sens = cls.getSensorID(); - const auto lay = geom->getLayer(sens); - double sigmaY2{0}, sigmaZ2{0}; - math_utils::Point3D locXYZ; - if (mIsITS3) { - locXYZ = o2::its3::ioutils::extractClusterData(cls, pattIt, mIT3Dict, sigmaY2, sigmaZ2); - } else { - locXYZ = o2::its::ioutils::extractClusterData(cls, pattIt, mITSDict, sigmaY2, sigmaZ2); - } - sigmaY2 += mParams->extraClsErrY[lay] * mParams->extraClsErrY[lay]; - sigmaZ2 += mParams->extraClsErrZ[lay] * mParams->extraClsErrZ[lay]; - // Transformation to the local --> global - const auto gloXYZ = geom->getMatrixL2G(sens) * locXYZ; - // Inverse transformation to the local --> tracking - auto trkXYZf = geom->getMatrixT2L(sens) ^ locXYZ; - o2::math_utils::Point3D trkXYZ; - trkXYZ.SetCoordinates(trkXYZf.X(), trkXYZf.Y(), trkXYZf.Z()); - // Tracking alpha angle - // We want that each cluster rotates its tracking frame to the clusters phi - // that way the track linearization around the measurement is less biases to the arc - // this means automatically that the measurement on the arc is at 0 for the curved layers - double alpha = geom->getSensorRefAlpha(sens); - double x = trkXYZ.x(); - if (mIsITS3 && constants::detID::isDetITS3(sens)) { - trkXYZ.SetY(0.f); - // alpha&x always have to be defined wrt to the global Z axis! - x = std::hypot(gloXYZ.x(), gloXYZ.y()); - trkXYZ.SetX(x); - alpha = std::atan2(gloXYZ.y(), gloXYZ.x()); - auto chip = constants::detID::getSensorID(sens); - sigmaY2 += mParams->extraClsErrY[chip] * mParams->extraClsErrY[chip]; - sigmaZ2 += mParams->extraClsErrZ[chip] * mParams->extraClsErrZ[chip]; + int nLr = mRecoData->getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mITSTrackingInfo.init(nLr); + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mITSTrackingInfo.beginLayer(lr); + const auto clusters = mRecoData->getITSClusters(lr); + const auto patterns = mRecoData->getITSClustersPatterns(lr); + LOGP(info, "Preparing {} measurments of the layer slot {}", clusters.size(), lr); + mITSTrackingInfo.getClusters().reserve(mITSTrackingInfo.size() + clusters.size()); + auto pattIt = patterns.begin(); + for (const auto& cls : clusters) { + const auto sens = cls.getSensorID(); + const auto lay = geom->getLayer(sens); + double sigmaY2{0}, sigmaZ2{0}; + math_utils::Point3D locXYZ; + if (mIsITS3) { + locXYZ = o2::its3::ioutils::extractClusterData(cls, pattIt, mIT3Dict, sigmaY2, sigmaZ2); + } else { + locXYZ = o2::its::ioutils::extractClusterData(cls, pattIt, mITSDict, sigmaY2, sigmaZ2); + } + sigmaY2 += mParams->extraClsErrY[lay] * mParams->extraClsErrY[lay]; + sigmaZ2 += mParams->extraClsErrZ[lay] * mParams->extraClsErrZ[lay]; + // Transformation to the local --> global + const auto gloXYZ = geom->getMatrixL2G(sens) * locXYZ; + // Inverse transformation to the local --> tracking + auto trkXYZf = geom->getMatrixT2L(sens) ^ locXYZ; + o2::math_utils::Point3D trkXYZ; + trkXYZ.SetCoordinates(trkXYZf.X(), trkXYZf.Y(), trkXYZf.Z()); + // Tracking alpha angle + // We want that each cluster rotates its tracking frame to the clusters phi + // that way the track linearization around the measurement is less biases to the arc + // this means automatically that the measurement on the arc is at 0 for the curved layers + double alpha = geom->getSensorRefAlpha(sens); + double x = trkXYZ.x(); + if (mIsITS3 && constants::detID::isDetITS3(sens)) { + trkXYZ.SetY(0.f); + // alpha&x always have to be defined wrt to the global Z axis! + x = std::hypot(gloXYZ.x(), gloXYZ.y()); + trkXYZ.SetX(x); + alpha = std::atan2(gloXYZ.y(), gloXYZ.x()); + auto chip = constants::detID::getSensorID(sens); + sigmaY2 += mParams->extraClsErrY[chip] * mParams->extraClsErrY[chip]; + sigmaZ2 += mParams->extraClsErrZ[chip] * mParams->extraClsErrZ[chip]; + } + math_utils::bringToPMPid(alpha); + mITSTrackingInfo.getClusters().emplace_back(sens, lay, x, alpha, + std::array{trkXYZ.y(), trkXYZ.z()}, + std::array{sigmaY2, 0., sigmaZ2}); } - math_utils::bringToPMPid(alpha); - mITSTrackingInfo.emplace_back(sens, lay, x, alpha, - std::array{trkXYZ.y(), trkXYZ.z()}, - std::array{sigmaY2, 0., sigmaZ2}); - } + } // loop over the layer slots + mITSTrackingInfo.finalize(); } void AlignmentSpec::buildT2V() @@ -941,9 +947,10 @@ void AlignmentSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) } } -DataProcessorSpec getAlignmentSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, bool withPV, bool withITS, OutputEnum out) +DataProcessorSpec getAlignmentSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, bool withPV, bool withITS, OutputEnum out, bool itsStag) { auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); std::shared_ptr ggRequest{nullptr}; if (!out[OutputOpt::MilleRes]) { dataRequest->requestTracks(srcTracks, useMC); diff --git a/Detectors/Upgrades/ITS3/alignment/src/alignment-workflow.cxx b/Detectors/Upgrades/ITS3/alignment/src/alignment-workflow.cxx index fd9495fb3f206..31ab211d6709c 100644 --- a/Detectors/Upgrades/ITS3/alignment/src/alignment-workflow.cxx +++ b/Detectors/Upgrades/ITS3/alignment/src/alignment-workflow.cxx @@ -54,6 +54,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfg) const auto useMC = !cfg.options().get("disable-mc"); const auto withPV = !cfg.options().get("without-pv"); const auto withITS = cfg.options().get("with-its"); + const auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(cfg); const OutputEnum output(cfg.options().get("output")); WorkflowSpec specs; @@ -66,7 +67,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfg) specs.emplace_back(o2::globaltracking::getNoInpDummyOutSpec(0)); } - specs.emplace_back(o2::its3::align::getAlignmentSpec(srcTrc, srcCls, useMC, withPV, withITS, output)); + specs.emplace_back(o2::its3::align::getAlignmentSpec(srcTrc, srcCls, useMC, withPV, withITS, output, doStag)); o2::raw::HBFUtilsInitializer hbfIni(cfg, specs); return std::move(specs); From cdabf3daaee1bbf123f1bcf2803080bc566d232a Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:35 +0200 Subject: [PATCH 15/18] Support per-layer ITS clusters in its3-tracking-study-workflow --- .../include/ITS3TrackingStudy/TrackingStudy.h | 2 +- .../Upgrades/ITS3/study/src/TrackingStudy.cxx | 109 ++++++++++-------- .../src/its3-tracking-study-workflow.cxx | 3 +- 3 files changed, 62 insertions(+), 52 deletions(-) diff --git a/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/TrackingStudy.h b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/TrackingStudy.h index 6d55dfc80aa8a..9d2ee9e1a009b 100644 --- a/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/TrackingStudy.h +++ b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/TrackingStudy.h @@ -18,7 +18,7 @@ namespace o2::its3::study { -o2::framework::DataProcessorSpec getTrackingStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, bool withPV); +o2::framework::DataProcessorSpec getTrackingStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, bool withPV, bool itsStag); } // namespace o2::its3::study diff --git a/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx b/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx index 04ba817fb5187..2dee7727f2054 100644 --- a/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx +++ b/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx @@ -18,6 +18,7 @@ #include "CommonUtils/TreeStreamRedirector.h" #include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" #include "DataFormatsITSMFT/Digit.h" #include "ITSMFTSimulation/Hit.h" @@ -139,7 +140,7 @@ class TrackingStudySpec final : public Task TrackCounter mTrackCounter; using TrackingCluster = align::TrackingCluster; - std::vector mITScl; + o2::itsmft::ClustersPerLayer mITScl; // addressed by the composed (layer,index) ID std::span mITSclRef; const ITS3TrackingStudyParam* mParams{nullptr}; @@ -237,39 +238,43 @@ void TrackingStudySpec::process() void TrackingStudySpec::prepareITSClusters() { - const auto& clusITS = mRecoData.getITSClusters(); - LOGP(info, "Preparing {} measurments", clusITS.size()); - const auto& patterns = mRecoData.getITSClustersPatterns(); - mITScl.reserve(clusITS.size()); - auto pattIt = patterns.begin(); auto geom = its::GeometryTGeo::Instance(); mITSclRef = mRecoData.getITSTracksClusterRefs(); - mITScl.clear(); - mITScl.reserve(clusITS.size()); - for (const auto& cls : clusITS) { - const auto sens = cls.getSensorID(); - float sigmaY2{0}, sigmaZ2{0}; - math_utils::Point3D locXYZ = o2::its3::ioutils::extractClusterData(cls, pattIt, mITSDict, sigmaY2, sigmaZ2); - // Transformation to the local --> global - const auto gloXYZ = geom->getMatrixL2G(sens) * locXYZ; - // Inverse transformation to the local --> tracking - o2::math_utils::Point3D trkXYZ = geom->getMatrixT2L(sens) ^ locXYZ; - // Tracking alpha angle - // We want that each cluster rotates its tracking frame to the clusters phi - // that way the track linearization around the measurement is less biases to the arc - // this means automatically that the measurement on the arc is at 0 for the curved layers - float alpha = geom->getSensorRefAlpha(sens); - if (constants::detID::isDetITS3(sens)) { - trkXYZ.SetY(0.f); - // alpha&x always have to be defined wrt to the global Z axis! - trkXYZ.SetX(std::hypot(gloXYZ.x(), gloXYZ.y())); - alpha = std::atan2(gloXYZ.y(), gloXYZ.x()); + int nLr = mRecoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mITScl.init(nLr); + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mITScl.beginLayer(lr); + const auto& clusITS = mRecoData.getITSClusters(lr); + const auto& patterns = mRecoData.getITSClustersPatterns(lr); + LOGP(info, "Preparing {} measurments of the layer slot {}", clusITS.size(), lr); + mITScl.getClusters().reserve(mITScl.size() + clusITS.size()); + auto pattIt = patterns.begin(); + for (const auto& cls : clusITS) { + const auto sens = cls.getSensorID(); + float sigmaY2{0}, sigmaZ2{0}; + math_utils::Point3D locXYZ = o2::its3::ioutils::extractClusterData(cls, pattIt, mITSDict, sigmaY2, sigmaZ2); + // Transformation to the local --> global + const auto gloXYZ = geom->getMatrixL2G(sens) * locXYZ; + // Inverse transformation to the local --> tracking + o2::math_utils::Point3D trkXYZ = geom->getMatrixT2L(sens) ^ locXYZ; + // Tracking alpha angle + // We want that each cluster rotates its tracking frame to the clusters phi + // that way the track linearization around the measurement is less biases to the arc + // this means automatically that the measurement on the arc is at 0 for the curved layers + float alpha = geom->getSensorRefAlpha(sens); + if (constants::detID::isDetITS3(sens)) { + trkXYZ.SetY(0.f); + // alpha&x always have to be defined wrt to the global Z axis! + trkXYZ.SetX(std::hypot(gloXYZ.x(), gloXYZ.y())); + alpha = std::atan2(gloXYZ.y(), gloXYZ.x()); + } + auto& cl3d = mITScl.getClusters().emplace_back(sens, trkXYZ); + cl3d.setErrors(sigmaY2, sigmaZ2, 0.f); + cl3d.alpha = alpha; + math_utils::detail::bringToPMPi(cl3d.alpha); // alpha is defined on -Pi,Pi } - auto& cl3d = mITScl.emplace_back(sens, trkXYZ); - cl3d.setErrors(sigmaY2, sigmaZ2, 0.f); - cl3d.alpha = alpha; - math_utils::detail::bringToPMPi(cl3d.alpha); // alpha is defined on -Pi,Pi - } + } // loop over the layer slots + mITScl.finalize(); } bool TrackingStudySpec::selectTrack(GTrackID trkID, bool checkMCTruth) const @@ -787,26 +792,29 @@ void TrackingStudySpec::doMCStudy() } } LOGP(info, "** Creating particle/clusters correspondence ... "); - const auto& clusters = mRecoData.getITSClusters(); - const auto& clustersMCLCont = mRecoData.getITSClustersMCLabels(); - for (auto iCluster{0}; iCluster < clusters.size(); ++iCluster) { - auto labs = clustersMCLCont->getLabels(iCluster); - for (auto& lab : labs) { - if (!lab.isValid() || lab.getSourceID() != 0 || !lab.isCorrect()) { - continue; - } - int trackID = 0, evID = 0, srcID = 0; - bool fake = false; - lab.get(trackID, evID, srcID, fake); - auto& cluster = clusters[iCluster]; - auto layer = o2::its::GeometryTGeo::Instance()->getLayer(cluster.getSensorID()); - auto& part = info[{trackID, evID, srcID}]; - part.clusters |= (1 << layer); - if (fake) { - part.fakeClusters |= (1 << layer); + int nLrCl = mRecoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + for (int lr = 0; lr < nLrCl; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + const auto& clusters = mRecoData.getITSClusters(lr); + const auto& clustersMCLCont = mRecoData.getITSClustersMCLabels(lr); + for (auto iCluster{0}; iCluster < clusters.size(); ++iCluster) { + auto labs = clustersMCLCont->getLabels(iCluster); + for (auto& lab : labs) { + if (!lab.isValid() || lab.getSourceID() != 0 || !lab.isCorrect()) { + continue; + } + int trackID = 0, evID = 0, srcID = 0; + bool fake = false; + lab.get(trackID, evID, srcID, fake); + auto& cluster = clusters[iCluster]; + auto layer = o2::its::GeometryTGeo::Instance()->getLayer(cluster.getSensorID()); + auto& part = info[{trackID, evID, srcID}]; + part.clusters |= (1 << layer); + if (fake) { + part.fakeClusters |= (1 << layer); + } } } - } + } // loop over the layer slots LOGP(info, "** Analysing tracks ... "); auto accountLbl = [&](const globaltracking::RecoContainer::GlobalIDSet& contributorsGID, DetID::ID det) { if (contributorsGID[det].isIndexSet()) { @@ -1242,10 +1250,11 @@ void TrackingStudySpec::getImpactParams(const o2::track::TrackParCov& trk, const ip[1] = trk.getZ() + (trk.getTgl() / rp4 * std::asin((f2 * r1) - (f1 * r2))) - z; } -DataProcessorSpec getTrackingStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, bool withPV) +DataProcessorSpec getTrackingStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, bool withPV, bool itsStag) { std::vector outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); dataRequest->requestTracks(srcTracks, useMC); dataRequest->requestIT3Clusters(useMC); diff --git a/Detectors/Upgrades/ITS3/study/src/its3-tracking-study-workflow.cxx b/Detectors/Upgrades/ITS3/study/src/its3-tracking-study-workflow.cxx index c26ce34dea403..f17578b83fe09 100644 --- a/Detectors/Upgrades/ITS3/study/src/its3-tracking-study-workflow.cxx +++ b/Detectors/Upgrades/ITS3/study/src/its3-tracking-study-workflow.cxx @@ -62,6 +62,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); auto useMC = !configcontext.options().get("disable-mc"); auto usePV = !configcontext.options().get("without-pv"); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); GID::mask_t srcTrc = allowedSourcesTrc & GID::getSourcesMask(configcontext.options().get("track-sources")); GID::mask_t srcCls = allowedSourcesClus & GID::getSourcesMask(configcontext.options().get("cluster-sources")); @@ -71,7 +72,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, useMC); } - specs.emplace_back(o2::its3::study::getTrackingStudySpec(srcTrc, srcCls, useMC, usePV)); + specs.emplace_back(o2::its3::study::getTrackingStudySpec(srcTrc, srcCls, useMC, usePV, doStag)); o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); From 2b916645f58959287bd72e6d89dbe1f046c9d54d Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:35 +0200 Subject: [PATCH 16/18] Support per-layer ITS clusters in the event display workflow EveWorkflowHelper only flattens the per-layer clusters; its ROF records were already used solely as an emptiness guard, not for timing. --- .../include/EveWorkflow/EveWorkflowHelper.h | 3 ++- .../Workflow/src/EveWorkflowHelper.cxx | 19 ++++++++++++------- .../Workflow/src/O2DPLDisplay.cxx | 1 + 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/EventVisualisation/Workflow/include/EveWorkflow/EveWorkflowHelper.h b/EventVisualisation/Workflow/include/EveWorkflow/EveWorkflowHelper.h index 6b3ec653c5350..dcbba84a12bec 100644 --- a/EventVisualisation/Workflow/include/EveWorkflow/EveWorkflowHelper.h +++ b/EventVisualisation/Workflow/include/EveWorkflow/EveWorkflowHelper.h @@ -21,6 +21,7 @@ #include "Framework/DataProcessingHeader.h" #include "DataFormatsTRD/TrackTRD.h" #include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "EveWorkflow/EveConfiguration.h" #include "EventVisualisationDataConverter/VisualisationEvent.h" #include "MFTBase/GeometryTGeo.h" @@ -201,7 +202,7 @@ class EveWorkflowHelper std::unordered_map> mPrimaryVertexTriggerGIDs; std::unordered_map mGIDTrackTime; std::vector mItsROFBrackets; - std::vector> mITSClustersArray; + o2::itsmft::ClustersPerLayer> mITSClustersArray; // addressed by the composed (layer,index) ID std::vector> mMFTClustersArray; o2::mft::GeometryTGeo* mMFTGeom; o2::its::GeometryTGeo* mITSGeom; diff --git a/EventVisualisation/Workflow/src/EveWorkflowHelper.cxx b/EventVisualisation/Workflow/src/EveWorkflowHelper.cxx index b4f7655648001..a906cfcd2a412 100644 --- a/EventVisualisation/Workflow/src/EveWorkflowHelper.cxx +++ b/EventVisualisation/Workflow/src/EveWorkflowHelper.cxx @@ -618,14 +618,19 @@ void EveWorkflowHelper::addTrackToEvent(const o2::track::TrackPar& tr, GID gid, void EveWorkflowHelper::prepareITSClusters(const o2::itsmft::TopologyDictionary* dict) { - const auto& ITSClusterROFRec = mRecoCont->getITSClustersROFRecords(); - const auto& clusITS = mRecoCont->getITSClusters(); - if (clusITS.size() && ITSClusterROFRec.size()) { - const auto& patterns = mRecoCont->getITSClustersPatterns(); - auto pattIt = patterns.begin(); - mITSClustersArray.reserve(clusITS.size()); - o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, dict); + int nLr = mRecoCont->getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mITSClustersArray.init(nLr); + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mITSClustersArray.beginLayer(lr); + const auto& ITSClusterROFRec = mRecoCont->getITSClustersROFRecords(lr); + const auto& clusITS = mRecoCont->getITSClusters(lr); + if (clusITS.size() && ITSClusterROFRec.size()) { + auto pattIt = mRecoCont->getITSClustersPatterns(lr).begin(); + mITSClustersArray.getClusters().reserve(mITSClustersArray.size() + clusITS.size()); + o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray.getClusters(), dict); + } } + mITSClustersArray.finalize(); } void EveWorkflowHelper::prepareMFTClusters( diff --git a/EventVisualisation/Workflow/src/O2DPLDisplay.cxx b/EventVisualisation/Workflow/src/O2DPLDisplay.cxx index 946602d8c4802..b062c436e18c5 100644 --- a/EventVisualisation/Workflow/src/O2DPLDisplay.cxx +++ b/EventVisualisation/Workflow/src/O2DPLDisplay.cxx @@ -372,6 +372,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) }; std::shared_ptr dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(cfgc)); dataRequest->requestTracks(srcTrk, useMC); dataRequest->requestClusters(srcCl, useMC); From cb8d8bd519a6888ec7d7d06bbc6cb38f7f349e8c Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:35 +0200 Subject: [PATCH 17/18] Support per-layer ITS clusters in the GPU display workflow --- GPU/Workflow/src/O2GPUDPLDisplay.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/GPU/Workflow/src/O2GPUDPLDisplay.cxx b/GPU/Workflow/src/O2GPUDPLDisplay.cxx index 4788eb207a344..fa68289517e68 100644 --- a/GPU/Workflow/src/O2GPUDPLDisplay.cxx +++ b/GPU/Workflow/src/O2GPUDPLDisplay.cxx @@ -183,6 +183,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) throw std::runtime_error("No input configured"); } std::shared_ptr dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(cfgc)); dataRequest->requestTracks(srcTrk, useMC); dataRequest->requestClusters(srcCl, useMC); From 6cd6ed0ac04d363ac171724b4a91f085a679cbe0 Mon Sep 17 00:00:00 2001 From: shahoian Date: Wed, 30 Sep 2026 20:40:55 +0200 Subject: [PATCH 18/18] Support per-layer ITS clusters in its-standalone-postprocessing-workflow All 7 study classes selectable from this single workflow. AnomalyStudy is ported in full (its nROF is a config parameter and all histograms are per layer, so per-layer ROF counters are the natural granularity). EfficiencyStudy and ITSBeamBackgroundStudy request the per-layer input but refuse to run on it (LOGP fatal): their algorithms rely on assumptions the staggered readout breaks (cluster-ROF index == track-ROF index; chaining consecutive ROFs across the 3 IB layers into one strobe series), and the replacement is an analysis choice for the study to make. Co-Authored-By: Claude Sonnet 5 --- .../studies/include/ITSStudies/AnomalyStudy.h | 2 +- .../studies/include/ITSStudies/AvgClusSize.h | 2 +- .../studies/include/ITSStudies/Efficiency.h | 2 +- .../ITSStudies/ITSBeamBackgroundStudy.h | 2 +- .../studies/include/ITSStudies/PIDStudy.h | 2 +- .../studies/include/ITSStudies/TrackCheck.h | 2 +- .../include/ITSStudies/TrackExtension.h | 2 +- .../studies/src/AnomalyStudy.cxx | 73 ++++++++++--------- .../studies/src/AvgClusSize.cxx | 30 +++++--- .../postprocessing/studies/src/Efficiency.cxx | 11 ++- .../studies/src/ITSBeamBackgroundStudy.cxx | 11 ++- .../postprocessing/studies/src/PIDStudy.cxx | 36 +++++---- .../postprocessing/studies/src/TrackCheck.cxx | 50 ++++++++----- .../studies/src/TrackExtension.cxx | 53 ++++++++------ .../standalone-postprocessing-workflow.cxx | 15 ++-- 15 files changed, 181 insertions(+), 112 deletions(-) diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/AnomalyStudy.h b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/AnomalyStudy.h index 946b5ef08fd48..67ce02593d670 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/AnomalyStudy.h +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/AnomalyStudy.h @@ -22,7 +22,7 @@ namespace study using mask_t = o2::dataformats::GlobalTrackID::mask_t; -o2::framework::DataProcessorSpec getAnomalyStudy(mask_t srcClustersMask, bool useMC); +o2::framework::DataProcessorSpec getAnomalyStudy(mask_t srcClustersMask, bool useMC, bool itsStag); } // namespace study } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/AvgClusSize.h b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/AvgClusSize.h index 766bb1f94ca1f..7658fd6bc8fe9 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/AvgClusSize.h +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/AvgClusSize.h @@ -28,7 +28,7 @@ namespace study using mask_t = o2::dataformats::GlobalTrackID::mask_t; -o2::framework::DataProcessorSpec getAvgClusSizeStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader); +o2::framework::DataProcessorSpec getAvgClusSizeStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader, bool itsStag); } // namespace study } // namespace its } // namespace o2 diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/Efficiency.h b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/Efficiency.h index b6f43bb772390..1c9bb6f7e568a 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/Efficiency.h +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/Efficiency.h @@ -26,7 +26,7 @@ namespace its namespace study { using mask_t = o2::dataformats::GlobalTrackID::mask_t; -o2::framework::DataProcessorSpec getEfficiencyStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader); +o2::framework::DataProcessorSpec getEfficiencyStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader, bool itsStag); float mEtaCuts[2] = {-1.0, 1.0}; float mPtCuts[2] = {0, 10}; /// no cut for B=0 diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/ITSBeamBackgroundStudy.h b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/ITSBeamBackgroundStudy.h index cd96a9df8dee6..9e852b6379dac 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/ITSBeamBackgroundStudy.h +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/ITSBeamBackgroundStudy.h @@ -20,7 +20,7 @@ namespace o2::its::study using mask_t = o2::dataformats::GlobalTrackID::mask_t; -o2::framework::DataProcessorSpec getITSBeamBackgroundStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC); +o2::framework::DataProcessorSpec getITSBeamBackgroundStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, bool itsStag); } // namespace o2::its::study #endif diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/PIDStudy.h b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/PIDStudy.h index 54c7e5f387e90..dadeae0671cfc 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/PIDStudy.h +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/PIDStudy.h @@ -28,7 +28,7 @@ namespace study using mask_t = o2::dataformats::GlobalTrackID::mask_t; -o2::framework::DataProcessorSpec getPIDStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader); +o2::framework::DataProcessorSpec getPIDStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader, bool itsStag); } // namespace study } // namespace its } // namespace o2 diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/TrackCheck.h b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/TrackCheck.h index 406677796a9b3..ec0651bfbd018 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/TrackCheck.h +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/TrackCheck.h @@ -26,7 +26,7 @@ namespace its namespace study { using mask_t = o2::dataformats::GlobalTrackID::mask_t; -o2::framework::DataProcessorSpec getTrackCheckStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader); +o2::framework::DataProcessorSpec getTrackCheckStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader, bool itsStag); } // namespace study } // namespace its } // namespace o2 diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/TrackExtension.h b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/TrackExtension.h index fd5b93b0f9509..af59a762b2207 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/TrackExtension.h +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/TrackExtension.h @@ -24,7 +24,7 @@ class MCKinematicsReader; namespace its::study { using mask_t = o2::dataformats::GlobalTrackID::mask_t; -o2::framework::DataProcessorSpec getTrackExtensionStudy(mask_t srcTracksMask, mask_t srcClustersMask, std::shared_ptr kineReader); +o2::framework::DataProcessorSpec getTrackExtensionStudy(mask_t srcTracksMask, mask_t srcClustersMask, std::shared_ptr kineReader, bool itsStag); } // namespace its::study } // namespace o2 diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/src/AnomalyStudy.cxx b/Detectors/ITSMFT/ITS/postprocessing/studies/src/AnomalyStudy.cxx index c56c2bff2e0d2..5ac3107339f1e 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/src/AnomalyStudy.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/src/AnomalyStudy.cxx @@ -197,41 +197,47 @@ void AnomalyStudy::process(o2::globaltracking::RecoContainer& recoData) auto nROF = o2::its::study::AnomalyStudyParamConfig::Instance().nRofTimeFrames; auto nLayProc = o2::its::study::AnomalyStudyParamConfig::Instance().nLayersToProcess; auto doROFAnalysis = o2::its::study::AnomalyStudyParamConfig::Instance().doROFAnalysis; - int rofCount = 0; - auto clusRofRecords = recoData.getITSClustersROFRecords(); - auto compClus = recoData.getITSClusters(); - auto clusPatt = recoData.getITSClustersPatterns(); - - getClusterPatterns(compClus, clusPatt, *mDict); - - auto pattIt = clusPatt.begin(); - std::vector globalClusters; - o2::its::ioutils::convertCompactClusters(compClus, pattIt, globalClusters, mDict); - int lay, sta, ssta, mod, chipInMod; - for (auto& rofRecord : clusRofRecords) { - auto clustersInRof = rofRecord.getROFData(compClus); - auto patternsInRof = rofRecord.getROFData(mPatterns); - auto locClustersInRof = rofRecord.getROFData(globalClusters); - for (unsigned int clusInd{0}; clusInd < clustersInRof.size(); clusInd++) { - const auto& compClus = clustersInRof[clusInd]; - auto& locClus = locClustersInRof[clusInd]; - auto& clusPattern = patternsInRof[clusInd]; - auto gloC = locClus.getXYZGlo(*mGeom); - mChipMapping.expandChipInfoHW(compClus.getChipID(), lay, sta, ssta, mod, chipInMod); - if (lay >= nLayProc) { - continue; - } - float phi = TMath::ATan2(gloC.Y(), gloC.X()); - mTFvsPhiHist[lay]->Fill(phi, mTFCount); - mTFvsPhiClusSizeHist[lay]->Fill(phi, mTFCount, clusPattern.getNPixels()); - if (doROFAnalysis) { - mROFvsPhiHist[lay]->Fill(phi, (mTFCount - 1) * nROF + rofCount); - mROFvsPhiClusSizeHist[lay]->Fill(phi, (mTFCount - 1) * nROF + rofCount, clusPattern.getNPixels()); + // in the staggered readout the clusters and their ROFs are provided per layer, every layer having its + // own ROF length and numbering; all the filled histograms are per layer, hence each layer slot is + // processed with its own ROF counter + int nLr = recoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + int rofCount = 0; + auto clusRofRecords = recoData.getITSClustersROFRecords(lr); + auto compClus = recoData.getITSClusters(lr); + auto clusPatt = recoData.getITSClustersPatterns(lr); + + getClusterPatterns(compClus, clusPatt, *mDict); + + auto pattIt = clusPatt.begin(); + std::vector globalClusters; + o2::its::ioutils::convertCompactClusters(compClus, pattIt, globalClusters, mDict); + + for (auto& rofRecord : clusRofRecords) { + auto clustersInRof = rofRecord.getROFData(compClus); + auto patternsInRof = rofRecord.getROFData(mPatterns); + auto locClustersInRof = rofRecord.getROFData(globalClusters); + for (unsigned int clusInd{0}; clusInd < clustersInRof.size(); clusInd++) { + const auto& compClus = clustersInRof[clusInd]; + auto& locClus = locClustersInRof[clusInd]; + auto& clusPattern = patternsInRof[clusInd]; + auto gloC = locClus.getXYZGlo(*mGeom); + mChipMapping.expandChipInfoHW(compClus.getChipID(), lay, sta, ssta, mod, chipInMod); + if (lay >= nLayProc) { + continue; + } + float phi = TMath::ATan2(gloC.Y(), gloC.X()); + mTFvsPhiHist[lay]->Fill(phi, mTFCount); + mTFvsPhiClusSizeHist[lay]->Fill(phi, mTFCount, clusPattern.getNPixels()); + if (doROFAnalysis) { + mROFvsPhiHist[lay]->Fill(phi, (mTFCount - 1) * nROF + rofCount); + mROFvsPhiClusSizeHist[lay]->Fill(phi, (mTFCount - 1) * nROF + rofCount, clusPattern.getNPixels()); + } } + ++rofCount; } - ++rofCount; - } + } // loop over the layer slots mStopwatch.Stop(); LOGP(info, "Processed TF: {} in {} s", mTFCount, mStopwatch.RealTime()); } @@ -263,10 +269,11 @@ void AnomalyStudy::getClusterPatterns(gsl::span outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); dataRequest->requestClusters(srcClustersMask, useMC); dataRequest->requestTracks(GTrackID::getSourcesMask(""), useMC); diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/src/AvgClusSize.cxx b/Detectors/ITSMFT/ITS/postprocessing/studies/src/AvgClusSize.cxx index f7efae8677516..aa200c9a041bc 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/src/AvgClusSize.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/src/AvgClusSize.cxx @@ -25,6 +25,7 @@ #include "DataFormatsParameters/GRPObject.h" #include "DataFormatsITS/TrackITS.h" #include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "ReconstructionDataFormats/PrimaryVertex.h" #include "ReconstructionDataFormats/PID.h" @@ -90,7 +91,7 @@ class AvgClusSizeStudy final : public Task float getAverageClusterSize(o2::its::TrackITS*); float calcV0HypoMass(const V0&, PID, PID); void calcAPVars(const V0&, float*, float*); - void getClusterSizes(std::vector&, const gsl::span, gsl::span::iterator&, const o2::itsmft::TopologyDictionary*); + void getClusterSizes(std::vector&, int offs, const gsl::span, gsl::span::iterator&, const o2::itsmft::TopologyDictionary*); void saveHistograms(); void plotHistograms(); void fillEtaBin(float eta, float clusSize, int i); @@ -101,7 +102,7 @@ class AvgClusSizeStudy final : public Task // Data std::shared_ptr mGGCCDBRequest; std::shared_ptr mDataRequest; - std::vector mClusterSizes; + o2::itsmft::ClustersPerLayer mClusterSizes; // addressed by the composed (layer,index) ID gsl::span mInputITSidxs; std::vector mMCTracks; const o2::itsmft::TopologyDictionary* mDict = nullptr; @@ -294,7 +295,7 @@ void AvgClusSizeStudy::run(ProcessingContext& pc) process(recoData); } -void AvgClusSizeStudy::getClusterSizes(std::vector& clusSizeVec, const gsl::span ITSclus, gsl::span::iterator& pattIt, const o2::itsmft::TopologyDictionary* mdict) +void AvgClusSizeStudy::getClusterSizes(std::vector& clusSizeVec, int offs, const gsl::span ITSclus, gsl::span::iterator& pattIt, const o2::itsmft::TopologyDictionary* mdict) { for (unsigned int iClus{0}; iClus < ITSclus.size(); ++iClus) { auto& clus = ITSclus[iClus]; @@ -309,18 +310,26 @@ void AvgClusSizeStudy::getClusterSizes(std::vector& clusSizeVec, const gsl: npix = mdict->getNpixels(pattID); patt = mdict->getPattern(pattID); } - clusSizeVec[iClus] = npix; + clusSizeVec[offs + iClus] = npix; } } void AvgClusSizeStudy::loadData(o2::globaltracking::RecoContainer& recoData) { mInputITSidxs = recoData.getITSTracksClusterRefs(); - auto compClus = recoData.getITSClusters(); - auto clusPatt = recoData.getITSClustersPatterns(); - mClusterSizes.resize(compClus.size()); - auto pattIt = clusPatt.begin(); - getClusterSizes(mClusterSizes, compClus, pattIt, mDict); + int nLr = recoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mClusterSizes.init(nLr); + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mClusterSizes.beginLayer(lr); + auto compClus = recoData.getITSClusters(lr); + auto clusPatt = recoData.getITSClustersPatterns(lr); + auto pattIt = clusPatt.begin(); + auto& sizes = mClusterSizes.getClusters(); + int offs = (int)sizes.size(); + sizes.resize(offs + compClus.size()); + getClusterSizes(sizes, offs, compClus, pattIt, mDict); + } + mClusterSizes.finalize(); } void AvgClusSizeStudy::process(o2::globaltracking::RecoContainer& recoData) @@ -646,10 +655,11 @@ void AvgClusSizeStudy::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) } } -DataProcessorSpec getAvgClusSizeStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader) +DataProcessorSpec getAvgClusSizeStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader, bool itsStag) { std::vector outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); dataRequest->requestTracks(srcTracksMask, useMC); dataRequest->requestClusters(srcClustersMask, useMC); dataRequest->requestSecondaryVertices(useMC); diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/src/Efficiency.cxx b/Detectors/ITSMFT/ITS/postprocessing/studies/src/Efficiency.cxx index f6ee014abdabd..743d954df41a4 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/src/Efficiency.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/src/Efficiency.cxx @@ -558,6 +558,14 @@ void EfficiencyStudy::run(ProcessingContext& pc) void EfficiencyStudy::initialiseRun(o2::globaltracking::RecoContainer& recoData) { LOGP(info, "--------------- initialiseRun"); + if (recoData.getITSPerLayer()) { + // The duplicated-cluster search scans the cluster ROF whose index equals that of the track ROF and + // relies on the two ROF series being the same. With the staggered readout every layer has its own + // ROF length and numbering, while the track ROFs follow the clock layer only, so the ROF of the + // layer to scan has to be found from the track time instead. This requires deciding whether a + // duplicate is searched in a single ROF or in all the ROFs compatible with the track time bracket. + LOGP(fatal, "EfficiencyStudy does not support the per-layer (staggered) ITS clusters input yet"); + } if (mUseMC) { mTracksMCLabels = recoData.getITSTracksMCLabels(); mClustersMCLCont = recoData.getITSClustersMCLabels(); @@ -2837,10 +2845,11 @@ void EfficiencyStudy::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) } } -DataProcessorSpec getEfficiencyStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader) +DataProcessorSpec getEfficiencyStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader, bool itsStag) { std::vector outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); dataRequest->requestTracks(srcTracksMask, useMC); dataRequest->requestClusters(srcClustersMask, useMC); diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/src/ITSBeamBackgroundStudy.cxx b/Detectors/ITSMFT/ITS/postprocessing/studies/src/ITSBeamBackgroundStudy.cxx index fc9e139648672..80742c2dd6503 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/src/ITSBeamBackgroundStudy.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/src/ITSBeamBackgroundStudy.cxx @@ -286,6 +286,14 @@ void ITSBeamBackgroundStudy::finaliseCCDB(ConcreteDataMatcher& matcher, void* ob // Custom area void ITSBeamBackgroundStudy::process(o2::globaltracking::RecoContainer& recoData) { + if (recoData.getITSPerLayer()) { + // The study chains consecutive ROFs (ChipSeenInLastROF/ChipSeenInLast2ROF) into a single time + // series over the 3 inner barrel layers and derives the strobe length from the number of ROFs. + // With the staggered readout each layer has its own ROF length and numbering, so it has to be + // decided whether the 3 layers are followed as independent series (and how the ROF counters and + // the background tagging are then normalized). + LOGP(fatal, "ITSBeamBackgroundStudy does not support the per-layer (staggered) ITS clusters input yet"); + } LOGP(info, "Processing RecoContainer"); Counters->Fill(1); @@ -708,13 +716,14 @@ void ITSBeamBackgroundStudy::getClusterPatterns(gsl::span outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); dataRequest->requestClusters(srcClustersMask, useMC); // dataRequest->requestTracks(GTrackID::getSourcesMask("ZDC"), useMC); diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/src/PIDStudy.cxx b/Detectors/ITSMFT/ITS/postprocessing/studies/src/PIDStudy.cxx index 9a7f6c218cd12..740d4f0a036f5 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/src/PIDStudy.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/src/PIDStudy.cxx @@ -27,6 +27,7 @@ #include "DataFormatsTPC/TrackTPC.h" #include "ReconstructionDataFormats/TrackTPCITS.h" #include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "ReconstructionDataFormats/PrimaryVertex.h" #include "ReconstructionDataFormats/PID.h" @@ -106,7 +107,7 @@ class PIDStudy : public Task // Helper functions void saveOutput(); void updateTimeDependentParams(ProcessingContext& pc); - void getClusterSizes(std::vector&, const gsl::span, gsl::span::iterator&, const o2::itsmft::TopologyDictionary*); + void getClusterSizes(std::vector&, int offs, const gsl::span, gsl::span::iterator&, const o2::itsmft::TopologyDictionary*); std::array getTrackClusterSizes(const TrackITS& track); float computeNSigma(PID pid, TrackTPC& tpcTrack, float resolution); @@ -118,8 +119,8 @@ class PIDStudy : public Task // Data std::shared_ptr mGGCCDBRequest; std::shared_ptr mDataRequest; - std::vector mClusterSizes; - gsl::span mClusters; + o2::itsmft::ClustersPerLayer mClusterSizes; // addressed by the composed (layer,index) ID + std::array, o2::globaltracking::MaxITSLayers> mClusters; // per layer slot gsl::span mInputITSidxs; const o2::itsmft::TopologyDictionary* mDict = nullptr; @@ -155,7 +156,7 @@ void PIDStudy::run(ProcessingContext& pc) process(recoData); } -void PIDStudy::getClusterSizes(std::vector& clusSizeVec, const gsl::span ITSclus, gsl::span::iterator& pattIt, const o2::itsmft::TopologyDictionary* mdict) +void PIDStudy::getClusterSizes(std::vector& clusSizeVec, int offs, const gsl::span ITSclus, gsl::span::iterator& pattIt, const o2::itsmft::TopologyDictionary* mdict) { for (unsigned int iClus{0}; iClus < ITSclus.size(); ++iClus) { auto& clus = ITSclus[iClus]; @@ -170,18 +171,25 @@ void PIDStudy::getClusterSizes(std::vector& clusSizeVec, const gsl::spangetNpixels(pattID); patt = mdict->getPattern(pattID); } - clusSizeVec[iClus] = npix; + clusSizeVec[offs + iClus] = npix; } } void PIDStudy::loadData(o2::globaltracking::RecoContainer& recoData) { mInputITSidxs = recoData.getITSTracksClusterRefs(); - mClusters = recoData.getITSClusters(); - auto clusPatt = recoData.getITSClustersPatterns(); - mClusterSizes.resize(mClusters.size()); - auto pattIt = clusPatt.begin(); - getClusterSizes(mClusterSizes, mClusters, pattIt, mDict); + int nLr = recoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + mClusterSizes.init(nLr); + for (int lr = 0; lr < nLr; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mClusterSizes.beginLayer(lr); + mClusters[lr] = recoData.getITSClusters(lr); + auto pattIt = recoData.getITSClustersPatterns(lr).begin(); + auto& sizes = mClusterSizes.getClusters(); + int offs = (int)sizes.size(); + sizes.resize(offs + mClusters[lr].size()); + getClusterSizes(sizes, offs, mClusters[lr], pattIt, mDict); + } + mClusterSizes.finalize(); } void PIDStudy::process(o2::globaltracking::RecoContainer& recoData) @@ -272,8 +280,9 @@ std::array PIDStudy::getTrackClusterSizes(const TrackITS& track) auto firstClus = track.getFirstClusterEntry(); auto ncl = track.getNumberOfClusters(); for (int icl = 0; icl < ncl; icl++) { - auto& clus = mClusters[mInputITSidxs[firstClus + icl]]; - auto& clSize = mClusterSizes[mInputITSidxs[firstClus + icl]]; + int clID = mInputITSidxs[firstClus + icl]; + auto& clus = mClusters[o2::itsmft::clusID2Layer(clID)][o2::itsmft::clusID2Index(clID)]; + auto& clSize = mClusterSizes[clID]; auto layer = geom->getLayer(clus.getSensorID()); clusSizes[layer] = clSize; } @@ -324,10 +333,11 @@ float PIDStudy::computeNSigma(PID pid, TrackTPC& tpcTrack, float resolution) return nSigma; } -DataProcessorSpec getPIDStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader) +DataProcessorSpec getPIDStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader, bool itsStag) { std::vector outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); dataRequest->requestTracks(srcTracksMask, useMC); dataRequest->requestClusters(srcClustersMask, useMC); diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/src/TrackCheck.cxx b/Detectors/ITSMFT/ITS/postprocessing/studies/src/TrackCheck.cxx index 482f9bf1cc97d..05f21b9897f5e 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/src/TrackCheck.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/src/TrackCheck.cxx @@ -16,6 +16,7 @@ #include "SimulationDataFormat/MCTrack.h" #include "DataFormatsITSMFT/CompCluster.h" #include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "CommonUtils/TreeStreamRedirector.h" #include "Framework/Task.h" @@ -98,9 +99,10 @@ class TrackCheckStudy final : public Task gsl::span mTracksROFRecords; gsl::span mTracks; gsl::span mTracksMCLabels; - gsl::span mClusters; + std::array, o2::globaltracking::MaxITSLayers> mClusters; // per layer slot + int mNLayerSlots = 1; // 1 for the monolithic input gsl::span mInputITSidxs; - const o2::dataformats::MCLabelContainer* mClustersMCLCont; + std::array mClustersMCLCont{}; // per layer slot // Data GTrackID::mask_t mTracksSrc{}; @@ -433,12 +435,18 @@ void TrackCheckStudy::initialiseRun(o2::globaltracking::RecoContainer& recoData) mTracksROFRecords = recoData.getITSTracksROFRecords(); mTracks = recoData.getITSTracks(); mTracksMCLabels = recoData.getITSTracksMCLabels(); - mClusters = recoData.getITSClusters(); - mClustersMCLCont = recoData.getITSClustersMCLabels(); + mNLayerSlots = recoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + size_t nClTot = 0, nClLbl = 0; + for (int lr = 0; lr < mNLayerSlots; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mClusters[lr] = recoData.getITSClusters(lr); + mClustersMCLCont[lr] = recoData.getITSClustersMCLabels(lr); + nClTot += mClusters[lr].size(); + nClLbl += mClustersMCLCont[lr]->getIndexedSize(); + } mInputITSidxs = recoData.getITSTracksClusterRefs(); LOGP(info, "** Found in {} rofs:\n\t- {} clusters with {} labels\n\t- {} tracks with {} labels", - mTracksROFRecords.size(), mClusters.size(), mClustersMCLCont->getIndexedSize(), mTracks.size(), mTracksMCLabels.size()); + mTracksROFRecords.size(), nClTot, nClLbl, mTracks.size(), mTracksMCLabels.size()); LOGP(info, "** Found {} sources from kinematic files", mKineReader->getNSources()); } @@ -469,20 +477,22 @@ void TrackCheckStudy::process() } LOGP(info, "** Creating particle/clusters correspondance ... "); for (auto iSource{0}; iSource < mParticleInfo.size(); ++iSource) { - for (auto iCluster{0}; iCluster < mClusters.size(); ++iCluster) { - auto labs = mClustersMCLCont->getLabels(iCluster); // ideally I can have more than one label per cluster - for (auto& lab : labs) { - if (!lab.isValid()) { - continue; // We want to skip channels related to noise, e.g. sID = 99: QED + for (int lr = 0; lr < mNLayerSlots; lr++) { + for (auto iCluster{0}; iCluster < mClusters[lr].size(); ++iCluster) { + auto labs = mClustersMCLCont[lr]->getLabels(iCluster); // ideally I can have more than one label per cluster + for (auto& lab : labs) { + if (!lab.isValid()) { + continue; // We want to skip channels related to noise, e.g. sID = 99: QED + } + int trackID, evID, srcID; + bool fake; + lab.get(trackID, evID, srcID, fake); + auto& cluster = mClusters[lr][iCluster]; + auto layer = mGeometry->getLayer(cluster.getSensorID()); + mParticleInfo[srcID][evID][trackID].clusters |= (1 << layer); } - int trackID, evID, srcID; - bool fake; - lab.get(trackID, evID, srcID, fake); - auto& cluster = mClusters[iCluster]; - auto layer = mGeometry->getLayer(cluster.getSensorID()); - mParticleInfo[srcID][evID][trackID].clusters |= (1 << layer); } - } + } // loop over the layer slots } LOGP(info, "** Analysing tracks ... "); int unaccounted{0}, good{0}, fakes{0}; @@ -715,7 +725,8 @@ void TrackCheckStudy::process() histLength3FakeNoCl[len - 4][idxPart]->Fill(iLayer); } } - auto labs = mClustersMCLCont->getLabels(mInputITSidxs[firstclu - 1 - iLayer + track.getFirstClusterLayer() + nclu]); + int clIDlbl = mInputITSidxs[firstclu - 1 - iLayer + track.getFirstClusterLayer() + nclu]; + auto labs = mClustersMCLCont[o2::itsmft::clusID2Layer(clIDlbl)]->getLabels(o2::itsmft::clusID2Index(clIDlbl)); for (auto& lab : labs) { if (!lab.isValid()) { @@ -1353,10 +1364,11 @@ void TrackCheckStudy::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) { } -DataProcessorSpec getTrackCheckStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader) +DataProcessorSpec getTrackCheckStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC, std::shared_ptr kineReader, bool itsStag) { std::vector outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); dataRequest->requestTracks(srcTracksMask, useMC); dataRequest->requestClusters(srcClustersMask, useMC); diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/src/TrackExtension.cxx b/Detectors/ITSMFT/ITS/postprocessing/studies/src/TrackExtension.cxx index 6826d441cc815..f5c9d56c3f91f 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/src/TrackExtension.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/src/TrackExtension.cxx @@ -11,6 +11,7 @@ #include "CommonUtils/TreeStreamRedirector.h" #include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsITSMFT/ClustersPerLayer.h" #include "DataFormatsITS/TrackITS.h" #include "DataFormatsITSMFT/CompCluster.h" #include "DetectorsBase/GRPGeomHelper.h" @@ -96,9 +97,10 @@ class TrackExtensionStudy final : public Task gsl::span mTracksROFRecords; gsl::span mTracks; gsl::span mTracksMCLabels; - gsl::span mClusters; + std::array, o2::globaltracking::MaxITSLayers> mClusters; // per layer slot + int mNLayerSlots = 1; // 1 for the monolithic input gsl::span mInputITSidxs; - const o2::dataformats::MCLabelContainer* mClustersMCLCont{}; + std::array mClustersMCLCont{}; // per layer slot GTrackID::mask_t mTracksSrc{}; std::shared_ptr mDataRequest; @@ -253,12 +255,18 @@ void TrackExtensionStudy::run(ProcessingContext& pc) mTracksROFRecords = recoData.getITSTracksROFRecords(); mTracks = recoData.getITSTracks(); mTracksMCLabels = recoData.getITSTracksMCLabels(); - mClusters = recoData.getITSClusters(); - mClustersMCLCont = recoData.getITSClustersMCLabels(); + mNLayerSlots = recoData.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1; + size_t nClTot = 0, nClLbl = 0; + for (int lr = 0; lr < mNLayerSlots; lr++) { // with a single (monolithic) input all clusters are in the layer slot 0 + mClusters[lr] = recoData.getITSClusters(lr); + mClustersMCLCont[lr] = recoData.getITSClustersMCLabels(lr); + nClTot += mClusters[lr].size(); + nClLbl += mClustersMCLCont[lr]->getIndexedSize(); + } mInputITSidxs = recoData.getITSTracksClusterRefs(); LOGP(info, "** Found in {} rofs:\n\t- {} clusters with {} labels\n\t- {} tracks with {} labels", - mTracksROFRecords.size(), mClusters.size(), mClustersMCLCont->getIndexedSize(), mTracks.size(), mTracksMCLabels.size()); + mTracksROFRecords.size(), nClTot, nClLbl, mTracks.size(), mTracksMCLabels.size()); LOGP(info, "** Found {} sources from kinematic files", mKineReader->getNSources()); process(); @@ -293,23 +301,25 @@ void TrackExtensionStudy::process() } LOGP(info, "** Creating particle/clusters correspondance ... "); for (auto iSource{0}; iSource < mParticleInfo.size(); ++iSource) { - for (auto iCluster{0}; iCluster < mClusters.size(); ++iCluster) { - auto labs = mClustersMCLCont->getLabels(iCluster); // ideally I can have more than one label per cluster - for (auto& lab : labs) { - if (!lab.isValid()) { - continue; // We want to skip channels related to noise, e.g. sID = 99: QED - } - int trackID, evID, srcID; - bool fake; - lab.get(trackID, evID, srcID, fake); - auto& cluster = mClusters[iCluster]; - auto layer = mGeometry->getLayer(cluster.getSensorID()); - mParticleInfo[srcID][evID][trackID].clusters |= (1 << layer); - if (fake) { - mParticleInfo[srcID][evID][trackID].fakeClusters |= (1 << layer); + for (int lr = 0; lr < mNLayerSlots; lr++) { + for (auto iCluster{0}; iCluster < mClusters[lr].size(); ++iCluster) { + auto labs = mClustersMCLCont[lr]->getLabels(iCluster); // ideally I can have more than one label per cluster + for (auto& lab : labs) { + if (!lab.isValid()) { + continue; // We want to skip channels related to noise, e.g. sID = 99: QED + } + int trackID, evID, srcID; + bool fake; + lab.get(trackID, evID, srcID, fake); + auto& cluster = mClusters[lr][iCluster]; + auto layer = mGeometry->getLayer(cluster.getSensorID()); + mParticleInfo[srcID][evID][trackID].clusters |= (1 << layer); + if (fake) { + mParticleInfo[srcID][evID][trackID].fakeClusters |= (1 << layer); + } } } - } + } // loop over the layer slots } LOGP(info, "** Analysing tracks ... "); @@ -628,10 +638,11 @@ void TrackExtensionStudy::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) } } -DataProcessorSpec getTrackExtensionStudy(mask_t srcTracksMask, mask_t srcClustersMask, std::shared_ptr kineReader) +DataProcessorSpec getTrackExtensionStudy(mask_t srcTracksMask, mask_t srcClustersMask, std::shared_ptr kineReader, bool itsStag) { std::vector outputs; auto dataRequest = std::make_shared(); + dataRequest->setITSPerLayer(itsStag); dataRequest->requestTracks(srcTracksMask, true); dataRequest->requestClusters(srcClustersMask, true); diff --git a/Detectors/ITSMFT/ITS/postprocessing/workflow/standalone-postprocessing-workflow.cxx b/Detectors/ITSMFT/ITS/postprocessing/workflow/standalone-postprocessing-workflow.cxx index 405e80475bd25..754cb2deac684 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/workflow/standalone-postprocessing-workflow.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/workflow/standalone-postprocessing-workflow.cxx @@ -71,6 +71,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); auto useMC = !configcontext.options().get("disable-mc"); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); std::shared_ptr mcKinematicsReader; if (useMC) { @@ -90,14 +91,14 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) srcTrc = GID::getSourcesMask(configcontext.options().get("track-sources")); srcCls = GID::getSourcesMask(configcontext.options().get("cluster-sources")); o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC, srcCls, srcTrc); - specs.emplace_back(o2::its::study::getAvgClusSizeStudy(srcTrc, srcCls, useMC, mcKinematicsReader)); + specs.emplace_back(o2::its::study::getAvgClusSizeStudy(srcTrc, srcCls, useMC, mcKinematicsReader, doStag)); } if (configcontext.options().get("pid-study")) { anyStudy = true; srcTrc = GID::getSourcesMask(configcontext.options().get("track-sources")); srcCls = GID::getSourcesMask(configcontext.options().get("cluster-sources")); o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC, srcCls, srcTrc); - specs.emplace_back(o2::its::study::getPIDStudy(srcTrc, srcCls, useMC, mcKinematicsReader)); + specs.emplace_back(o2::its::study::getPIDStudy(srcTrc, srcCls, useMC, mcKinematicsReader, doStag)); } if (configcontext.options().get("track-study")) { anyStudy = true; @@ -106,7 +107,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (!configcontext.options().get("input-from-upstream")) { o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC, srcCls, srcTrc); } - specs.emplace_back(o2::its::study::getTrackCheckStudy(GID::getSourcesMask("ITS"), GID::getSourcesMask("ITS"), useMC, mcKinematicsReader)); + specs.emplace_back(o2::its::study::getTrackCheckStudy(GID::getSourcesMask("ITS"), GID::getSourcesMask("ITS"), useMC, mcKinematicsReader, doStag)); } if (configcontext.options().get("anomaly-study")) { anyStudy = true; @@ -114,7 +115,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (!configcontext.options().get("input-from-upstream")) { o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC, srcCls, srcTrc); } - specs.emplace_back(o2::its::study::getAnomalyStudy(srcCls, useMC)); + specs.emplace_back(o2::its::study::getAnomalyStudy(srcCls, useMC, doStag)); } if (configcontext.options().get("its-beambkg-study")) { anyStudy = true; @@ -125,7 +126,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (!configcontext.options().get("input-from-upstream")) { o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC, srcCls, srcTrc); } - specs.emplace_back(o2::its::study::getITSBeamBackgroundStudy(srcTrc, srcCls, useMC)); + specs.emplace_back(o2::its::study::getITSBeamBackgroundStudy(srcTrc, srcCls, useMC, doStag)); } if (configcontext.options().get("track-extension-study")) { if (!useMC) { @@ -135,7 +136,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) srcTrc = GID::getSourcesMask(configcontext.options().get("track-sources")); srcCls = GID::getSourcesMask("ITS"); o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, true, srcCls, srcTrc); - specs.emplace_back(o2::its::study::getTrackExtensionStudy(srcTrc, srcCls, mcKinematicsReader)); + specs.emplace_back(o2::its::study::getTrackExtensionStudy(srcTrc, srcCls, mcKinematicsReader, doStag)); } if (configcontext.options().get("efficiency-study")) { anyStudy = true; @@ -144,7 +145,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (!configcontext.options().get("input-from-upstream")) { o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC, srcCls, srcTrc); } - specs.emplace_back(o2::its::study::getEfficiencyStudy(GID::getSourcesMask("ITS"), GID::getSourcesMask("ITS"), useMC, mcKinematicsReader)); + specs.emplace_back(o2::its::study::getEfficiencyStudy(GID::getSourcesMask("ITS"), GID::getSourcesMask("ITS"), useMC, mcKinematicsReader, doStag)); } if (!anyStudy) { LOGP(info, "No study selected, dryrunning");