4040#include < chrono>
4141#include " DataFormatsTPC/PIDResponse.h"
4242#include " DataFormatsITS/TrackITS.h"
43+ #include " DataFormatsTRD/TrackTRD.h"
44+ #include " DataFormatsTRD/Tracklet64.h"
45+ #include " DataFormatsTRD/CalibratedTracklet.h"
4346#include " TROOT.h"
4447#include " ReconstructionDataFormats/MatchInfoTOF.h"
4548#include " DataFormatsTOF/Cluster.h"
@@ -60,6 +63,13 @@ namespace tpc
6063class TPCTimeSeries : public Task
6164{
6265 public:
66+ // / D2: per-track TRD tracklet lookup data
67+ struct TRDTrackletData {
68+ uint8_t trdPattern = 0 ;
69+ uint8_t nTRDTracklets = 0 ;
70+ int trackletIndices[6 ] = {-1 , -1 , -1 , -1 , -1 , -1 };
71+ };
72+
6373 // / \constructor
6474 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) {};
6575
@@ -298,6 +308,38 @@ class TPCTimeSeries : public Task
298308 // find nearest vertex of tracks which have no vertex assigned
299309 findNearesVertex (tracksTPC, vertices);
300310
311+ // D2: build TPC track index → TRD tracklet data map (for unbinned output)
312+ // For each TPC track that has a TRD match, store the TrackTRD tracklet indices
313+ std::unordered_map<unsigned int , TRDTrackletData> tpcToTRDMap;
314+ auto trdTracklets = mTPCOnly ? gsl::span<const o2::trd::Tracklet64>() : recoData.getTRDTracklets ();
315+ auto trdCalibTracklets = mTPCOnly ? gsl::span<const o2::trd::CalibratedTracklet>() : recoData.getTRDCalibratedTracklets ();
316+ if (mUnbinnedWriter && !mTPCOnly ) {
317+ // scan ITS-TPC-TRD tracks
318+ auto itstpctrdTracks = recoData.getITSTPCTRDTracks <o2::trd::TrackTRD>();
319+ for (unsigned int ig = 0 ; ig < itstpctrdTracks.size (); ++ig) {
320+ auto gid = GTrackID (ig, GTrackID::ITSTPCTRD );
321+ auto refTPC = recoData.getTPCContributorGID (gid);
322+ if (!refTPC.isIndexSet ()) {
323+ continue ;
324+ }
325+ auto refTRD = recoData.getSingleDetectorRefs (gid)[GTrackID::TRD ];
326+ if (!refTRD.isIndexSet ()) {
327+ continue ;
328+ }
329+ const auto & trdTrack = recoData.getTrack <o2::trd::TrackTRD>(refTRD);
330+ TRDTrackletData trdData;
331+ for (int iLay = 0 ; iLay < 6 ; ++iLay) {
332+ auto trkltId = trdTrack.getTrackletIndex (iLay);
333+ if (trkltId >= 0 ) {
334+ trdData.trdPattern |= (1 << iLay);
335+ trdData.nTRDTracklets ++;
336+ trdData.trackletIndices [iLay] = trkltId;
337+ }
338+ }
339+ tpcToTRDMap[refTPC] = trdData;
340+ }
341+ }
342+
301343 // getting cluster references for cluster bitmask
302344 if (mUnbinnedWriter ) {
303345 mTPCTrackClIdx = pc.inputs ().get <gsl::span<o2::tpc::TPCClRefElem>>(" trackTPCClRefs" );
@@ -467,7 +509,7 @@ class TPCTimeSeries : public Task
467509 auto myThread = [&](int iThread) {
468510 for (size_t i = iThread; i < loopEnd; i += mNThreads ) {
469511 if (acceptTrack (tracksTPC[i])) {
470- fillDCA (tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters);
512+ fillDCA (tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters, tpcToTRDMap, trdTracklets, trdCalibTracklets );
471513 }
472514 }
473515 };
@@ -484,7 +526,7 @@ class TPCTimeSeries : public Task
484526 auto myThread = [&](int iThread) {
485527 for (size_t i = iThread; i < loopEnd; i += mNThreads ) {
486528 if (acceptTrack (tracksTPC[i])) {
487- fillDCA (tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters);
529+ fillDCA (tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters, tpcToTRDMap, trdTracklets, trdCalibTracklets );
488530 }
489531 }
490532 };
@@ -1122,7 +1164,7 @@ class TPCTimeSeries : public Task
11221164 return isGoodTrack;
11231165 }
11241166
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)
1167+ 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 )
11261168 {
11271169 const auto & trackFull = tracksTPC[iTrk];
11281170 const bool isGoodTrack = checkTrack (trackFull);
@@ -1168,21 +1210,22 @@ class TPCTimeSeries : public Task
11681210 return ;
11691211 }
11701212
1171- const int tglBin = mTglBins * std::abs (trackTmp.getTgl ()) / mMaxTgl + mPhiBins ;
1172- const int phiBin = mPhiBins * trackTmp.getPhi () / o2::constants::math::TwoPI;
1213+ // Saturate bin indices — edge bins act as overflow (Phase 0.2 fix)
1214+ const int tglBin = std::clamp (static_cast <int >(mTglBins * std::abs (trackTmp.getTgl ()) / mMaxTgl ) + mPhiBins ,
1215+ mPhiBins , mPhiBins + mTglBins - 1 );
1216+ const int phiBin = std::clamp (static_cast <int >(mPhiBins * trackTmp.getPhi () / o2::constants::math::TwoPI),
1217+ 0 , mPhiBins - 1 );
11731218
11741219 const int offsQPtBin = mPhiBins + mTglBins ;
1175- const int qPtBin = offsQPtBin + mQPtBins * (trackTmp.getQ2Pt () + mMaxQPt ) / (2 * mMaxQPt );
1220+ const int qPtBin = std::clamp (offsQPtBin + static_cast <int >(mQPtBins * (trackTmp.getQ2Pt () + mMaxQPt ) / (2 * mMaxQPt )),
1221+ offsQPtBin, offsQPtBin + mQPtBins - 1 );
11761222 const int localMult = mNTracksWindow [iTrk];
11771223
11781224 const int offsMult = offsQPtBin + mQPtBins ;
1179- const int multBin = offsMult + mMultBins * localMult / mMultMax ;
1225+ const int multBin = std::clamp (offsMult + static_cast <int >(mMultBins * localMult / mMultMax ),
1226+ offsMult, offsMult + mMultBins - 1 );
11801227 const int nBins = getNBins ();
11811228
1182- if ((phiBin < 0 ) || (phiBin > mPhiBins ) || (tglBin < mPhiBins ) || (tglBin > offsQPtBin) || (qPtBin < offsQPtBin) || (qPtBin > offsMult) || (multBin < offsMult) || (multBin > offsMult + mMultBins )) {
1183- return ;
1184- }
1185-
11861229 float sigmaY2 = 0 ;
11871230 float sigmaZ2 = 0 ;
11881231 const int sector = o2::math_utils::angle2Sector (trackTmp.getPhiPos ());
@@ -1343,6 +1386,30 @@ class TPCTimeSeries : public Task
13431386 const float chi2match_ITSTPC = hasITSTPC ? tracksITSTPC[idxITSTPC.front ()].getChi2Match () : -1 ;
13441387 const int nClITS = idxITSCheck ? tracksITS[idxITSTrack].getNClusters () : -1 ;
13451388 const int chi2ITS = idxITSCheck ? tracksITS[idxITSTrack].getChi2 () : -1 ;
1389+ // D1: ITS cluster sizes (4-bit per layer, mask bit 28 = kSharedClusters)
1390+ const uint32_t itsClusterSizes = idxITSCheck ? (static_cast <uint32_t >(tracksITS[idxITSTrack].getClusterSizes ()) & 0x0FFFFFFFu ) : 0u ;
1391+ const bool itsHasSharedClusters = idxITSCheck ? tracksITS[idxITSTrack].hasSharedClusters () : false ;
1392+ const uint32_t itsPattern = idxITSCheck ? (tracksITS[idxITSTrack].getPattern () & 0x7Fu ) : 0u ;
1393+
1394+ // D2: TRD tracklet data — native objects per layer
1395+ uint8_t trdPattern = 0 ;
1396+ uint8_t nTRDTracklets = 0 ;
1397+ std::vector<o2::trd::Tracklet64> trdTrackletVec (6 );
1398+ std::vector<o2::trd::CalibratedTracklet> trdCalibVec (6 );
1399+ auto itTRD = tpcToTRDMap.find (iTrk);
1400+ if (itTRD != tpcToTRDMap.end ()) {
1401+ const auto & trdData = itTRD->second ;
1402+ trdPattern = trdData.trdPattern ;
1403+ nTRDTracklets = trdData.nTRDTracklets ;
1404+ for (int iLay = 0 ; iLay < 6 ; ++iLay) {
1405+ if (trdData.trackletIndices [iLay] >= 0 ) {
1406+ trdTrackletVec[iLay] = trdTracklets[trdData.trackletIndices [iLay]];
1407+ if (trdData.trackletIndices [iLay] < static_cast <int >(trdCalibTracklets.size ())) {
1408+ trdCalibVec[iLay] = trdCalibTracklets[trdData.trackletIndices [iLay]];
1409+ }
1410+ }
1411+ }
1412+ }
13461413 int typeSide = 2 ; // A- and C-Side cluster
13471414 if (trackFull.hasASideClustersOnly ()) {
13481415 typeSide = 0 ;
@@ -1477,6 +1544,14 @@ class TPCTimeSeries : public Task
14771544 << " mX_ITS=" << mx_ITS
14781545 << " nClITS=" << nClITS
14791546 << " chi2ITS=" << chi2ITS
1547+ << " itsClusterSizes=" << itsClusterSizes
1548+ << " itsHasSharedClusters=" << itsHasSharedClusters
1549+ << " itsPattern=" << itsPattern
1550+ // D2: TRD tracklet data
1551+ << " trdPattern=" << trdPattern
1552+ << " nTRDTracklets=" << nTRDTracklets
1553+ << " trdTracklets=" << trdTrackletVec
1554+ << " trdCalibTracklets=" << trdCalibVec
14801555 << " chi2match_ITSTPC=" << chi2match_ITSTPC
14811556 << " PID=" << trkOrig.getPID ().getID ()
14821557 // TPC cov at vertex (without vertex constrained)
@@ -1669,6 +1744,7 @@ class TPCTimeSeries : public Task
16691744
16701745 std::unordered_map<int , int > nContributors_ITS; // ITS: vertex ID -> n contributors
16711746 std::unordered_map<int , int > nContributors_ITSTPC; // ITS-TPC (and ITS-TPC-TRD, ITS-TPC-TOF, ITS-TPC-TRD-TOF): vertex ID -> n contributors
1747+ std::unordered_map<int , int > nContributors_TRD; // ITS-TPC-TRD (and ITS-TPC-TRD-TOF): vertex ID -> n TRD-matched PV contributors
16721748
16731749 // loop over collisions
16741750 if (!vertices.empty ()) {
@@ -1689,6 +1765,10 @@ class TPCTimeSeries : public Task
16891765 if (refITSTPC.isIndexSet ()) {
16901766 indicesITSTPC_vtx[refITSTPC] = vID;
16911767 ++nContributors_ITSTPC[vID];
1768+ // count TRD-matched PV contributors
1769+ if (source == TrkSrc::ITSTPCTRD || source == TrkSrc::ITSTPCTRDTOF ) {
1770+ ++nContributors_TRD[vID];
1771+ }
16921772 } else {
16931773 ++nContributors_ITS[vID];
16941774 }
@@ -1750,6 +1830,17 @@ class TPCTimeSeries : public Task
17501830 mBufferDCA .vertexY_ITSTPC_RMS .front () = avgVtxITSTPC[1 ].getStdDev ();
17511831 mBufferDCA .vertexZ_ITSTPC_RMS .front () = avgVtxITSTPC[2 ].getStdDev ();
17521832
1833+ // TRD matching fraction (summed over all vertices in this TF)
1834+ int sumITSTPCBased = 0 ;
1835+ int sumWithTRD = 0 ;
1836+ for (int ivtx = 0 ; ivtx < vertices.size (); ++ivtx) {
1837+ sumITSTPCBased += nContributors_ITSTPC[ivtx];
1838+ sumWithTRD += nContributors_TRD[ivtx];
1839+ }
1840+ mBufferDCA .nITSTPCBasedPVContributors .front () = sumITSTPCBased;
1841+ mBufferDCA .nITSTPCWithTRDPVContributors .front () = sumWithTRD;
1842+ mBufferDCA .fracTRD .front () = (sumITSTPCBased > 0 ) ? static_cast <float >(sumWithTRD) / sumITSTPCBased : std::nanf (" " );
1843+
17531844 // quantiles and truncated mean
17541845 RobustAverage avg (vertices.size (), false );
17551846 for (const auto & vtx : vertices) {
@@ -1839,6 +1930,10 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter,
18391930 if (src[GTrackID::TPC ]) {
18401931 dataRequest->requestClusters (GTrackID::getSourcesMask (" TPC" ), useMC);
18411932 }
1933+ // D2: request TRD tracklets for tracks with TRD contribution
1934+ if (srcTracks[GTrackID::ITSTPCTRD ] || srcTracks[GTrackID::ITSTPCTRDTOF ]) {
1935+ dataRequest->requestTRDTracklets (useMC);
1936+ }
18421937
18431938 bool tpcOnly = srcTracks == GTrackID::getSourcesMask (" TPC" );
18441939 if (srcTracks.any () && !tpcOnly) {
0 commit comments