1919#include " Framework/ConfigParamRegistry.h"
2020#include " Framework/DataProcessorSpec.h"
2121#include " Framework/ControlService.h"
22+ #include " Framework/CCDBParamSpec.h"
2223#include " TPCWorkflow/ProcessingHelpers.h"
2324#include " TPCBase/Mapper.h"
2425#include " DetectorsBase/GRPGeomHelper.h"
4041#include < chrono>
4142#include " DataFormatsTPC/PIDResponse.h"
4243#include " DataFormatsITS/TrackITS.h"
44+ #include " DataFormatsTRD/TrackTRD.h"
45+ #include " DataFormatsTRD/Tracklet64.h"
46+ #include " DataFormatsTRD/CalibratedTracklet.h"
4347#include " TROOT.h"
4448#include " ReconstructionDataFormats/MatchInfoTOF.h"
4549#include " DataFormatsTOF/Cluster.h"
4650#include " DataFormatsFT0/RecPoints.h"
4751#include " TPCCalibration/PressureTemperatureHelper.h"
52+ #include " TPCBaseRecSim/CDBTypes.h"
53+ #include " TPCCalibration/SectorEdgeFluctuations.h"
4854
4955using namespace o2 ::globaltracking;
5056using GTrackID = o2::dataformats::GlobalTrackID;
@@ -60,6 +66,13 @@ namespace tpc
6066class TPCTimeSeries : public Task
6167{
6268 public:
69+ // / D2: per-track TRD tracklet lookup data
70+ struct TRDTrackletData {
71+ uint8_t trdPattern = 0 ;
72+ uint8_t nTRDTracklets = 0 ;
73+ int trackletIndices[6 ] = {-1 , -1 , -1 , -1 , -1 , -1 };
74+ };
75+
6376 // / \constructor
6477 TPCTimeSeries (std::shared_ptr<o2::base::GRPGeomRequest> req, const bool disableWriter, const o2::base::Propagator::MatCorrType matType, const bool enableUnbinnedWriter, const bool tpcOnly, std::shared_ptr<o2::globaltracking::DataRequest> dr) : mCCDBRequest (req), mDisableWriter (disableWriter), mMatType (matType), mUnbinnedWriter (enableUnbinnedWriter), mTPCOnly (tpcOnly), mDataRequest (dr) {};
6578
@@ -132,12 +145,14 @@ class TPCTimeSeries : public Task
132145 mVDrift = mTPCVDriftHelper .getVDriftObject ().getVDrift ();
133146 LOGP (info, " Updated reference drift velocity to: {}" , mVDrift );
134147 }
148+ pc.inputs ().get <TTree*>(" tpcSecFlucInfo" );
135149 mBufferDCA .mVDrift = mVDrift ;
136150
137151 const int nBins = getNBins ();
138152
139153 mTimeMS = o2::base::GRPGeomHelper::instance ().getOrbitResetTimeMS () + processing_helpers::getFirstTForbit (pc) * o2::constants::lhc::LHCOrbitMUS / 1000 ;
140154 mRun = processing_helpers::getRunNumber (pc);
155+ mBufferDCA .mSecEdgeFlucCorr = mSecEdgeFlucInfo .getSectorsAtTime (mRun , static_cast <long >(mTimeMS ));
141156 mBufferDCA .mTemperature = mPTHelper .getMeanTemperature (mTimeMS );
142157 mBufferDCA .mPressure = mPTHelper .getPressure (mTimeMS );
143158
@@ -298,6 +313,38 @@ class TPCTimeSeries : public Task
298313 // find nearest vertex of tracks which have no vertex assigned
299314 findNearesVertex (tracksTPC, vertices);
300315
316+ // D2: build TPC track index → TRD tracklet data map (for unbinned output)
317+ // For each TPC track that has a TRD match, store the TrackTRD tracklet indices
318+ std::unordered_map<unsigned int , TRDTrackletData> tpcToTRDMap;
319+ auto trdTracklets = mTPCOnly ? gsl::span<const o2::trd::Tracklet64>() : recoData.getTRDTracklets ();
320+ auto trdCalibTracklets = mTPCOnly ? gsl::span<const o2::trd::CalibratedTracklet>() : recoData.getTRDCalibratedTracklets ();
321+ if (mUnbinnedWriter && !mTPCOnly ) {
322+ // scan ITS-TPC-TRD tracks
323+ auto itstpctrdTracks = recoData.getITSTPCTRDTracks <o2::trd::TrackTRD>();
324+ for (unsigned int ig = 0 ; ig < itstpctrdTracks.size (); ++ig) {
325+ auto gid = GTrackID (ig, GTrackID::ITSTPCTRD );
326+ auto refTPC = recoData.getTPCContributorGID (gid);
327+ if (!refTPC.isIndexSet ()) {
328+ continue ;
329+ }
330+ auto refTRD = recoData.getSingleDetectorRefs (gid)[GTrackID::TRD ];
331+ if (!refTRD.isIndexSet ()) {
332+ continue ;
333+ }
334+ const auto & trdTrack = recoData.getTrack <o2::trd::TrackTRD>(refTRD);
335+ TRDTrackletData trdData;
336+ for (int iLay = 0 ; iLay < 6 ; ++iLay) {
337+ auto trkltId = trdTrack.getTrackletIndex (iLay);
338+ if (trkltId >= 0 ) {
339+ trdData.trdPattern |= (1 << iLay);
340+ trdData.nTRDTracklets ++;
341+ trdData.trackletIndices [iLay] = trkltId;
342+ }
343+ }
344+ tpcToTRDMap[refTPC] = trdData;
345+ }
346+ }
347+
301348 // getting cluster references for cluster bitmask
302349 if (mUnbinnedWriter ) {
303350 mTPCTrackClIdx = pc.inputs ().get <gsl::span<o2::tpc::TPCClRefElem>>(" trackTPCClRefs" );
@@ -467,7 +514,7 @@ class TPCTimeSeries : public Task
467514 auto myThread = [&](int iThread) {
468515 for (size_t i = iThread; i < loopEnd; i += mNThreads ) {
469516 if (acceptTrack (tracksTPC[i])) {
470- fillDCA (tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters);
517+ fillDCA (tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters, tpcToTRDMap, trdTracklets, trdCalibTracklets );
471518 }
472519 }
473520 };
@@ -484,7 +531,7 @@ class TPCTimeSeries : public Task
484531 auto myThread = [&](int iThread) {
485532 for (size_t i = iThread; i < loopEnd; i += mNThreads ) {
486533 if (acceptTrack (tracksTPC[i])) {
487- fillDCA (tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters);
534+ fillDCA (tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters, tpcToTRDMap, trdTracklets, trdCalibTracklets );
488535 }
489536 }
490537 };
@@ -875,6 +922,11 @@ class TPCTimeSeries : public Task
875922 mTPCVDriftHelper .accountCCDBInputs (matcher, obj);
876923 mPTHelper .accountCCDBInputs (matcher, obj);
877924 o2::base::GRPGeomHelper::instance ().finaliseCCDB (matcher, obj);
925+ if (matcher == ConcreteDataMatcher (o2::header::gDataOriginTPC , " InfoMapSecFluc" , 0 )) {
926+ LOGP (info, " Updating TPC sector edge fluctuation info" );
927+ mSecEdgeFlucInfo .setFromTree (*((TTree*)obj));
928+ LOGP (info, " Loaded sector edge fluctuation information with {} intervals for {} runs" , mSecEdgeFlucInfo .size (), mSecEdgeFlucInfo .getNRuns ());
929+ }
878930 }
879931
880932 private:
@@ -1112,6 +1164,7 @@ class TPCTimeSeries : public Task
11121164 int mRun {}; // /< run number
11131165 int mMaxOccupancyHistBins {912 }; // /< maximum number of occupancy bins
11141166 PressureTemperatureHelper mPTHelper ; // /< helper to extract pressure and temperature from CCDB
1167+ o2::tpc::SectorEdgeFluctuations mSecEdgeFlucInfo ; // /< definition of sector edge fluctuation distortion map scaling
11151168
11161169 // / check if track passes coarse cuts
11171170 bool acceptTrack (const TrackTPC& track) const { return std::abs (track.getTgl ()) < mMaxTgl ; }
@@ -1122,7 +1175,7 @@ class TPCTimeSeries : public Task
11221175 return isGoodTrack;
11231176 }
11241177
1125- void fillDCA (const gsl::span<const TrackTPC> tracksTPC, const gsl::span<const o2::dataformats::TrackTPCITS> tracksITSTPC, const gsl::span<const o2::dataformats::PrimaryVertex> vertices, const int iTrk, const int iThread, const std::unordered_map<unsigned int , std::array<int , 2 >>& indicesITSTPC, const gsl::span<const o2::its::TrackITS> tracksITS, const std::vector<std::tuple<int , float , float , o2::track::TrackLTIntegral, double , float , unsigned int , unsigned short >>& idxTPCTrackToTOFCluster, const gsl::span<const o2::tof::Cluster> tofClusters)
1178+ void fillDCA (const gsl::span<const TrackTPC> tracksTPC, const gsl::span<const o2::dataformats::TrackTPCITS> tracksITSTPC, const gsl::span<const o2::dataformats::PrimaryVertex> vertices, const int iTrk, const int iThread, const std::unordered_map<unsigned int , std::array<int , 2 >>& indicesITSTPC, const gsl::span<const o2::its::TrackITS> tracksITS, const std::vector<std::tuple<int , float , float , o2::track::TrackLTIntegral, double , float , unsigned int , unsigned short >>& idxTPCTrackToTOFCluster, const gsl::span<const o2::tof::Cluster> tofClusters, const std::unordered_map< unsigned int , TRDTrackletData>& tpcToTRDMap, const gsl::span< const o2::trd::Tracklet64> trdTracklets, const gsl::span< const o2::trd::CalibratedTracklet> trdCalibTracklets )
11261179 {
11271180 const auto & trackFull = tracksTPC[iTrk];
11281181 const bool isGoodTrack = checkTrack (trackFull);
@@ -1168,21 +1221,22 @@ class TPCTimeSeries : public Task
11681221 return ;
11691222 }
11701223
1171- const int tglBin = mTglBins * std::abs (trackTmp.getTgl ()) / mMaxTgl + mPhiBins ;
1172- const int phiBin = mPhiBins * trackTmp.getPhi () / o2::constants::math::TwoPI;
1224+ // Saturate bin indices — edge bins act as overflow (Phase 0.2 fix)
1225+ const int tglBin = std::clamp (static_cast <int >(mTglBins * std::abs (trackTmp.getTgl ()) / mMaxTgl ) + mPhiBins ,
1226+ mPhiBins , mPhiBins + mTglBins - 1 );
1227+ const int phiBin = std::clamp (static_cast <int >(mPhiBins * trackTmp.getPhi () / o2::constants::math::TwoPI),
1228+ 0 , mPhiBins - 1 );
11731229
11741230 const int offsQPtBin = mPhiBins + mTglBins ;
1175- const int qPtBin = offsQPtBin + mQPtBins * (trackTmp.getQ2Pt () + mMaxQPt ) / (2 * mMaxQPt );
1231+ const int qPtBin = std::clamp (offsQPtBin + static_cast <int >(mQPtBins * (trackTmp.getQ2Pt () + mMaxQPt ) / (2 * mMaxQPt )),
1232+ offsQPtBin, offsQPtBin + mQPtBins - 1 );
11761233 const int localMult = mNTracksWindow [iTrk];
11771234
11781235 const int offsMult = offsQPtBin + mQPtBins ;
1179- const int multBin = offsMult + mMultBins * localMult / mMultMax ;
1236+ const int multBin = std::clamp (offsMult + static_cast <int >(mMultBins * localMult / mMultMax ),
1237+ offsMult, offsMult + mMultBins - 1 );
11801238 const int nBins = getNBins ();
11811239
1182- if ((phiBin < 0 ) || (phiBin > mPhiBins ) || (tglBin < mPhiBins ) || (tglBin > offsQPtBin) || (qPtBin < offsQPtBin) || (qPtBin > offsMult) || (multBin < offsMult) || (multBin > offsMult + mMultBins )) {
1183- return ;
1184- }
1185-
11861240 float sigmaY2 = 0 ;
11871241 float sigmaZ2 = 0 ;
11881242 const int sector = o2::math_utils::angle2Sector (trackTmp.getPhiPos ());
@@ -1343,6 +1397,30 @@ class TPCTimeSeries : public Task
13431397 const float chi2match_ITSTPC = hasITSTPC ? tracksITSTPC[idxITSTPC.front ()].getChi2Match () : -1 ;
13441398 const int nClITS = idxITSCheck ? tracksITS[idxITSTrack].getNClusters () : -1 ;
13451399 const int chi2ITS = idxITSCheck ? tracksITS[idxITSTrack].getChi2 () : -1 ;
1400+ // D1: ITS cluster sizes (4-bit per layer, mask bit 28 = kSharedClusters)
1401+ const uint32_t itsClusterSizes = idxITSCheck ? (static_cast <uint32_t >(tracksITS[idxITSTrack].getClusterSizes ()) & 0x0FFFFFFFu ) : 0u ;
1402+ const bool itsHasSharedClusters = idxITSCheck ? tracksITS[idxITSTrack].hasSharedClusters () : false ;
1403+ const uint32_t itsPattern = idxITSCheck ? (tracksITS[idxITSTrack].getPattern () & 0x7Fu ) : 0u ;
1404+
1405+ // D2: TRD tracklet data — native objects per layer
1406+ uint8_t trdPattern = 0 ;
1407+ uint8_t nTRDTracklets = 0 ;
1408+ std::vector<o2::trd::Tracklet64> trdTrackletVec (6 );
1409+ std::vector<o2::trd::CalibratedTracklet> trdCalibVec (6 );
1410+ auto itTRD = tpcToTRDMap.find (iTrk);
1411+ if (itTRD != tpcToTRDMap.end ()) {
1412+ const auto & trdData = itTRD->second ;
1413+ trdPattern = trdData.trdPattern ;
1414+ nTRDTracklets = trdData.nTRDTracklets ;
1415+ for (int iLay = 0 ; iLay < 6 ; ++iLay) {
1416+ if (trdData.trackletIndices [iLay] >= 0 ) {
1417+ trdTrackletVec[iLay] = trdTracklets[trdData.trackletIndices [iLay]];
1418+ if (trdData.trackletIndices [iLay] < static_cast <int >(trdCalibTracklets.size ())) {
1419+ trdCalibVec[iLay] = trdCalibTracklets[trdData.trackletIndices [iLay]];
1420+ }
1421+ }
1422+ }
1423+ }
13461424 int typeSide = 2 ; // A- and C-Side cluster
13471425 if (trackFull.hasASideClustersOnly ()) {
13481426 typeSide = 0 ;
@@ -1477,6 +1555,14 @@ class TPCTimeSeries : public Task
14771555 << " mX_ITS=" << mx_ITS
14781556 << " nClITS=" << nClITS
14791557 << " chi2ITS=" << chi2ITS
1558+ << " itsClusterSizes=" << itsClusterSizes
1559+ << " itsHasSharedClusters=" << itsHasSharedClusters
1560+ << " itsPattern=" << itsPattern
1561+ // D2: TRD tracklet data
1562+ << " trdPattern=" << trdPattern
1563+ << " nTRDTracklets=" << nTRDTracklets
1564+ << " trdTracklets=" << trdTrackletVec
1565+ << " trdCalibTracklets=" << trdCalibVec
14801566 << " chi2match_ITSTPC=" << chi2match_ITSTPC
14811567 << " PID=" << trkOrig.getPID ().getID ()
14821568 // TPC cov at vertex (without vertex constrained)
@@ -1669,6 +1755,7 @@ class TPCTimeSeries : public Task
16691755
16701756 std::unordered_map<int , int > nContributors_ITS; // ITS: vertex ID -> n contributors
16711757 std::unordered_map<int , int > nContributors_ITSTPC; // ITS-TPC (and ITS-TPC-TRD, ITS-TPC-TOF, ITS-TPC-TRD-TOF): vertex ID -> n contributors
1758+ std::unordered_map<int , int > nContributors_TRD; // ITS-TPC-TRD (and ITS-TPC-TRD-TOF): vertex ID -> n TRD-matched PV contributors
16721759
16731760 // loop over collisions
16741761 if (!vertices.empty ()) {
@@ -1689,6 +1776,10 @@ class TPCTimeSeries : public Task
16891776 if (refITSTPC.isIndexSet ()) {
16901777 indicesITSTPC_vtx[refITSTPC] = vID;
16911778 ++nContributors_ITSTPC[vID];
1779+ // count TRD-matched PV contributors
1780+ if (source == TrkSrc::ITSTPCTRD || source == TrkSrc::ITSTPCTRDTOF ) {
1781+ ++nContributors_TRD[vID];
1782+ }
16921783 } else {
16931784 ++nContributors_ITS[vID];
16941785 }
@@ -1750,6 +1841,17 @@ class TPCTimeSeries : public Task
17501841 mBufferDCA .vertexY_ITSTPC_RMS .front () = avgVtxITSTPC[1 ].getStdDev ();
17511842 mBufferDCA .vertexZ_ITSTPC_RMS .front () = avgVtxITSTPC[2 ].getStdDev ();
17521843
1844+ // TRD matching fraction (summed over all vertices in this TF)
1845+ int sumITSTPCBased = 0 ;
1846+ int sumWithTRD = 0 ;
1847+ for (int ivtx = 0 ; ivtx < vertices.size (); ++ivtx) {
1848+ sumITSTPCBased += nContributors_ITSTPC[ivtx];
1849+ sumWithTRD += nContributors_TRD[ivtx];
1850+ }
1851+ mBufferDCA .nITSTPCBasedPVContributors .front () = sumITSTPCBased;
1852+ mBufferDCA .nITSTPCWithTRDPVContributors .front () = sumWithTRD;
1853+ mBufferDCA .fracTRD .front () = (sumITSTPCBased > 0 ) ? static_cast <float >(sumWithTRD) / sumITSTPCBased : std::nanf (" " );
1854+
17531855 // quantiles and truncated mean
17541856 RobustAverage avg (vertices.size (), false );
17551857 for (const auto & vtx : vertices) {
@@ -1839,6 +1941,10 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter,
18391941 if (src[GTrackID::TPC ]) {
18401942 dataRequest->requestClusters (GTrackID::getSourcesMask (" TPC" ), useMC);
18411943 }
1944+ // D2: request TRD tracklets for tracks with TRD contribution
1945+ if (srcTracks[GTrackID::ITSTPCTRD ] || srcTracks[GTrackID::ITSTPCTRDTOF ]) {
1946+ dataRequest->requestTRDTracklets (useMC);
1947+ }
18421948
18431949 bool tpcOnly = srcTracks == GTrackID::getSourcesMask (" TPC" );
18441950 if (srcTracks.any () && !tpcOnly) {
@@ -1859,6 +1965,8 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter,
18591965
18601966 o2::tpc::VDriftHelper::requestCCDBInputs (dataRequest->inputs );
18611967 PressureTemperatureHelper::requestCCDBInputs (dataRequest->inputs );
1968+ dataRequest->inputs .emplace_back (" tpcSecFlucInfo" , o2::header::gDataOriginTPC , " InfoMapSecFluc" , 0 , Lifetime::Condition, ccdbParamSpec (CDBTypeMap.at (CDBType::CalSecEdgeInfo), {}, 1 ));
1969+
18621970 std::vector<OutputSpec> outputs;
18631971 outputs.emplace_back (o2::header::gDataOriginTPC , getDataDescriptionTimeSeries (), 0 , Lifetime::Sporadic);
18641972 if (!disableWriter) {
0 commit comments