diff --git a/DataFormats/Detectors/TPC/include/DataFormatsTPC/ClusterNativeHelper.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/ClusterNativeHelper.h index f35141f4bf849..d712ae7479499 100644 --- a/DataFormats/Detectors/TPC/include/DataFormatsTPC/ClusterNativeHelper.h +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/ClusterNativeHelper.h @@ -76,6 +76,43 @@ struct ClusterNativeBuffer : public ClusterGroupHeader { value_type clusters[0]; }; +// @struct ClusterCountIndex +// Index of cluster counts per {sector,padrow} for the full TPC +// +// This is the header for the transport format of TPC ClusterNative data, +// followed by a linear buffer of clusters. +struct alignas(64) ClusterCountIndex { + unsigned int nClusters[Constants::MAXSECTOR][Constants::MAXGLOBALPADROW]; +}; + +// @struct ClusterCountIndex +// Index of cluster counts per {sector,padrow} coordinate +// TODO: remove or merge with the above +struct alignas(64) ClusterIndexBuffer { + using value_type = ClusterNative; + + unsigned int nClusters[Constants::MAXSECTOR][Constants::MAXGLOBALPADROW]; + + size_t getNClusters() const + { + size_t count = 0; + for (auto sector = 0; sector < Constants::MAXSECTOR; sector++) { + for (auto row = 0; row < Constants::MAXGLOBALPADROW; row++) { + count += nClusters[sector][row]; + } + } + return count; + } + + size_t getFlatSize() const { return sizeof(this) + getNClusters() * sizeof(value_type); } + + const value_type* data() const { return clusters; } + + value_type* data() { return clusters; } + + value_type clusters[0]; +}; + /// @class ClusterNativeHelper utility class for TPC native clusters /// This class supports the following utility functionality for handling of /// TPC ClusterNative data: @@ -185,9 +222,9 @@ class ClusterNativeHelper ClusterNativeAccess& clusterIndex, std::unique_ptr& clusterBuffer, DataArrayType& inputs, CheckFct checkFct = [](auto const&) { return true; }) { - // just use a dummy parameter with empty vectors, the ugly double vector will be removed - // soon. TODO: maybe do in one function with conditional template parameter - std::vector> dummy(inputs.size()); + // just use a dummy parameter with empty vectors + // TODO: maybe do in one function with conditional template parameter + std::vector> dummy; // another default, nothing will be added to the container MCLabelContainer mcBuffer; return fillIndex(clusterIndex, clusterBuffer, mcBuffer, inputs, dummy, checkFct); @@ -196,10 +233,10 @@ class ClusterNativeHelper // Process data for one sector. // This function does not copy any data but sets the corresponding poiters in the index. // Cluster data are provided as a raw buffer of consecutive ClusterNative arrays preceded by ClusterGroupHeader - // MC labels are provided as a span of MCLabelContainers, one per pad row. + // MC labels are provided as a span of MCLabelContainers, one per sector. static int parseSector(const char* buffer, size_t size, gsl::span const& mcinput, // ClusterNativeAccess& clusterIndex, // - const MCLabelContainer* (&clustersMCTruth)[NSectors][NPadRows]); // + const MCLabelContainer* (&clustersMCTruth)[NSectors]); // // Process data for one sector // Helper method receiving raw buffer provided as container @@ -207,7 +244,7 @@ class ClusterNativeHelper template static int parseSector(ContainerT const cont, gsl::span const& mcinput, // ClusterNativeAccess& clusterIndex, // - const MCLabelContainer* (&clustersMCTruth)[NSectors][NPadRows]) // + const MCLabelContainer* (&clustersMCTruth)[NSectors]) // { using T = typename std::remove_pointer::type; static_assert(sizeof(typename T::value_type) == 1, "raw container must be byte-type"); @@ -316,13 +353,37 @@ int ClusterNativeHelper::Reader::fillIndex(ClusterNativeAccess& clusterIndex, std::runtime_error("inconsistent size of MC label array " + std::to_string(mcinputs.size()) + ", expected " + std::to_string(inputs.size())); } memset(&clusterIndex, 0, sizeof(clusterIndex)); - const MCLabelContainer* clustersMCTruth[NSectors][NPadRows] = {}; + if (inputs.size() == 1) { + if (inputs[0].size() >= sizeof(o2::tpc::ClusterCountIndex)) { + // there is only one data block and we can set the index directly from it + const o2::tpc::ClusterCountIndex* hdr = reinterpret_cast(inputs[0].data()); + memcpy((void*)&clusterIndex.nClusters[0][0], hdr, sizeof(*hdr)); + clusterIndex.clustersLinear = reinterpret_cast(inputs[0].data() + sizeof(*hdr)); + clusterIndex.setOffsetPtrs(); + if (mcinputs.size() > 0) { + clusterIndex.clustersMCTruth = mcinputs[0].get(); + } + } + if (sizeof(ClusterCountIndex) + clusterIndex.nClustersTotal * sizeof(ClusterNative) > inputs[0].size()) { + throw std::runtime_error("inconsistent input buffer, expecting size " + std::to_string(sizeof(ClusterCountIndex) + clusterIndex.nClustersTotal * sizeof(ClusterNative)) + " got " + std::to_string(inputs[0].size())); + } + return clusterIndex.nClustersTotal; + } + + // multiple data blocks need to be merged into the single block + const MCLabelContainer* clustersMCTruth[NSectors] = {nullptr}; int result = 0; for (size_t index = 0, end = inputs.size(); index < end; index++) { if (!checkFct(index)) { continue; } - int locres = parseSector(inputs[index], mcinputs[index], clusterIndex, clustersMCTruth); + MCLabelContainer const* labelsptr = nullptr; + int extent = 0; + if (index < mcinputs.size()) { + labelsptr = mcinputs[index].get(); + extent = 1; + } + int locres = parseSector(inputs[index], {labelsptr, extent}, clusterIndex, clustersMCTruth); if (locres < 0) { return locres; } @@ -337,12 +398,13 @@ int ClusterNativeHelper::Reader::fillIndex(ClusterNativeAccess& clusterIndex, clusterIndex.clustersLinear = clusterBuffer.get(); clusterIndex.setOffsetPtrs(); for (unsigned int i = 0; i < NSectors; i++) { + int sectorLabelId = 0; for (unsigned int j = 0; j < NPadRows; j++) { memcpy(&clusterBuffer[clusterIndex.clusterOffset[i][j]], old.clusters[i][j], sizeof(*old.clusters[i][j]) * old.nClusters[i][j]); - if (clustersMCTruth[i][j]) { + if (clustersMCTruth[i]) { mcPresent = true; - for (unsigned int k = 0; k < old.nClusters[i][j]; k++) { - for (auto const& label : clustersMCTruth[i][j]->getLabels(k)) { + for (unsigned int k = 0; k < old.nClusters[i][j]; k++, sectorLabelId++) { + for (auto const& label : clustersMCTruth[i]->getLabels(sectorLabelId)) { mcBuffer.addElement(clusterIndex.clusterOffset[i][j] + k, label); } } diff --git a/DataFormats/Detectors/TPC/src/ClusterNativeHelper.cxx b/DataFormats/Detectors/TPC/src/ClusterNativeHelper.cxx index ddfb3487b2424..ac047133db6ac 100644 --- a/DataFormats/Detectors/TPC/src/ClusterNativeHelper.cxx +++ b/DataFormats/Detectors/TPC/src/ClusterNativeHelper.cxx @@ -168,47 +168,50 @@ int ClusterNativeHelper::Reader::fillIndex(ClusterNativeAccess& clusterIndex, st mSectorRaw[index]->clear(); } } - int result = fillIndex(clusterIndex, clusterBuffer, mcBuffer, mSectorRaw, mSectorMC, [](auto&) { return true; }); - return result; + // after changing this ClusterNative transport format, this functionality needs + // to be adapted + throw std::runtime_error("code path currently not supported"); + //int result = fillIndex(clusterIndex, clusterBuffer, mcBuffer, mSectorRaw, mSectorMC, [](auto&) { return true; }); + //return result; + return 0; } int ClusterNativeHelper::Reader::parseSector(const char* buffer, size_t size, gsl::span const& mcinput, ClusterNativeAccess& clusterIndex, - const MCLabelContainer* (&clustersMCTruth)[Constants::MAXSECTOR][Constants::MAXGLOBALPADROW]) + const MCLabelContainer* (&clustersMCTruth)[Constants::MAXSECTOR]) { - if (!buffer || size == 0) { + if (!buffer || size < sizeof(ClusterCountIndex)) { return 0; } auto mcIterator = mcinput.begin(); - using ClusterGroupParser = o2::algorithm::ForwardParser; - ClusterGroupParser parser; + ClusterCountIndex const& counts = *reinterpret_cast(buffer); + ClusterNative const* clusters = reinterpret_cast(buffer + sizeof(ClusterCountIndex)); size_t numberOfClusters = 0; - parser.parse( - buffer, size, - [](const typename ClusterGroupParser::HeaderType& h) { - // check the header, but in this case there is no validity check - return true; - }, - [](const typename ClusterGroupParser::HeaderType& h) { - // get the size of the frame including payload - // and header and trailer size, e.g. payload size - // from a header member - return h.nClusters * sizeof(ClusterNative) + ClusterGroupParser::totalOffset; - }, - [&](typename ClusterGroupParser::FrameInfo& frame) { - int sector = frame.header->sector; - int padrow = frame.header->globalPadRow; - int nClusters = frame.header->nClusters; - clusterIndex.clusters[sector][padrow] = reinterpret_cast(frame.payload); - clusterIndex.nClusters[sector][padrow] = nClusters; - numberOfClusters += nClusters; + for (int i = 0; i < Constants::MAXSECTOR; i++) { + int nSectorClusters = 0; + for (int j = 0; j < Constants::MAXGLOBALPADROW; j++) { + if (counts.nClusters[i][j] == 0) { + continue; + } + nSectorClusters += counts.nClusters[i][j]; + if ((numberOfClusters + counts.nClusters[i][j]) * sizeof(ClusterNative) + sizeof(ClusterCountIndex) > size) { + throw std::runtime_error("inconsistent buffer size"); + } + clusterIndex.clusters[i][j] = clusters + numberOfClusters; + clusterIndex.nClusters[i][j] = counts.nClusters[i][j]; + numberOfClusters += counts.nClusters[i][j]; + } + if (nSectorClusters > 0) { if (mcIterator != mcinput.end()) { - clustersMCTruth[sector][padrow] = &(*mcIterator); + clustersMCTruth[i] = &(*mcIterator); ++mcIterator; + if (mcIterator != mcinput.end()) { + throw std::runtime_error("can only have one MCLabel block per sector"); + } } + } + } - return true; - }); return numberOfClusters; } @@ -219,6 +222,9 @@ ClusterNativeHelper::TreeWriter::~TreeWriter() void ClusterNativeHelper::TreeWriter::init(const char* filename, const char* treename) { + // after changing this ClusterNative transport format, this functionality needs + // to be adapted + throw std::runtime_error("code path currently not supported"); mFile.reset(TFile::Open(filename, "RECREATE")); if (!mFile) { return; diff --git a/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx index b3db0a0bc7f18..6c31de86d10da 100644 --- a/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx @@ -92,22 +92,17 @@ void TPCITSMatchingDPL::run(ProcessingContext& pc) LOG(ERROR) << "sector header missing on header stack"; throw std::runtime_error("sector header missing on header stack"); } - const int& sector = sectorHeader->sector(); - // the TPCSectorHeader now allows to transport information for more than one sector, - // e.g. for transporting clusters in one single data block. For the moment, the - // implemenation here requires single sectors - if (sector >= o2::tpc::TPCSectorHeader::NSectors) { - throw std::runtime_error("Expecting data for single sectors"); - } - LOG(INFO) << "Reading cluster data for sector " << sector; - if (validSectors.test(sector)) { + int sector = sectorHeader->sector(); + std::bitset sectorMask(sectorHeader->sectorBits); + LOG(INFO) << "Reading TPC cluster data, sector mask is " << sectorMask; + if ((validSectors & sectorMask).any()) { // have already data for this sector, this should not happen in the current // sequential implementation, for parallel path merged at the tracker stage // multiple buffers need to be handled throw std::runtime_error("can only have one data set per sector"); } activeSectors |= sectorHeader->activeSectors; - validSectors.set(sector); + validSectors |= sectorMask; datarefs[sector] = ref; } diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx index df5816fff1ec7..314ccf03b0ae8 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx @@ -67,21 +67,16 @@ void TPCInterpolationDPL::run(ProcessingContext& pc) throw std::runtime_error("sector header missing on header stack"); } const int& sector = sectorHeader->sector(); - // the TPCSectorHeader now allows to transport information for more than one sector, - // e.g. for transporting clusters in one single data block. For the moment, the - // implemenation here requires single sectors - if (sector >= o2::tpc::TPCSectorHeader::NSectors) { - throw std::runtime_error("Expecting data for single sectors"); - } - LOG(INFO) << "Reading cluster data for sector " << sector; - if (validSectors.test(sector)) { + std::bitset sectorMask(sectorHeader->sectorBits); + LOG(INFO) << "Reading TPC cluster data, sector mask is " << sectorMask; + if ((validSectors & sectorMask).any()) { // have already data for this sector, this should not happen in the current // sequential implementation, for parallel path merged at the tracker stage // multiple buffers need to be handled throw std::runtime_error("can only have one data set per sector"); } activeSectors |= sectorHeader->activeSectors; - validSectors.set(sector); + validSectors |= sectorMask; datarefs[sector] = ref; } diff --git a/Detectors/TPC/reconstruction/include/TPCReconstruction/HardwareClusterDecoder.h b/Detectors/TPC/reconstruction/include/TPCReconstruction/HardwareClusterDecoder.h index a6e14e061bea1..e84532380152d 100644 --- a/Detectors/TPC/reconstruction/include/TPCReconstruction/HardwareClusterDecoder.h +++ b/Detectors/TPC/reconstruction/include/TPCReconstruction/HardwareClusterDecoder.h @@ -77,7 +77,7 @@ class HardwareClusterDecoder int decodeClusters(std::vector>& inputClusters, OutputAllocator outputAllocator, const std::vector>* inMCLabels = nullptr, - std::vector>* outMCLabels = nullptr); + o2::dataformats::MCTruthContainer* outMCLabels = nullptr); /// @brief Sort clusters and MC labels in place /// ClusterNative defines the smaller-than relation used in the sorting, with time being the more significant diff --git a/Detectors/TPC/reconstruction/src/HardwareClusterDecoder.cxx b/Detectors/TPC/reconstruction/src/HardwareClusterDecoder.cxx index 070c79ce84be7..bd14d2002e827 100644 --- a/Detectors/TPC/reconstruction/src/HardwareClusterDecoder.cxx +++ b/Detectors/TPC/reconstruction/src/HardwareClusterDecoder.cxx @@ -35,14 +35,21 @@ using namespace o2::dataformats; int HardwareClusterDecoder::decodeClusters(std::vector>& inputClusters, HardwareClusterDecoder::OutputAllocator outputAllocator, const std::vector>* inMCLabels, - std::vector>* outMCLabels) + o2::dataformats::MCTruthContainer* outMCLabels) { if (mIntegrator == nullptr) mIntegrator.reset(new DigitalCurrentClusterIntegrator); - if (!inMCLabels) + // MCLabelContainer does only allow appending new labels, so we need to write to separate + // containers per {sector,padrow} and merge at the end; + std::vector> outMCLabelContainers; + if (!inMCLabels) { outMCLabels = nullptr; - std::vector outputBufferMap; + } + ClusterNative* outputClusterBuffer = nullptr; + // the number of clusters in a {sector,row} int nRowClusters[Constants::MAXSECTOR][Constants::MAXGLOBALPADROW] = {0}; + // offset of first cluster of {sector,row} in the output buffer + size_t clusterOffsets[Constants::MAXSECTOR][Constants::MAXGLOBALPADROW] = {0}; int containerRowCluster[Constants::MAXSECTOR][Constants::MAXGLOBALPADROW] = {0}; Mapper& mapper = Mapper::instance(); int numberOfOutputContainers = 0; @@ -62,13 +69,16 @@ int HardwareClusterDecoder::decodeClusters(std::vectorclusters[nCls]; + ClusterNative& cOut = outputClusterBuffer[clusterOffsets[sector][padRowGlobal] + nCls]; float pad = cIn.getPad(); cOut.setPad(pad); cOut.setTimeFlags(cIn.getTimeLocal() + cont.timeBinOffset, cIn.getFlags()); @@ -78,7 +88,7 @@ int HardwareClusterDecoder::decodeClusters(std::vectorintegrateCluster(sector, padRowGlobal, pad, cIn.getQTot()); if (outMCLabels) { - auto& mcOut = (*outMCLabels)[containerRowCluster[sector][padRowGlobal]]; + auto& mcOut = outMCLabelContainers[containerRowCluster[sector][padRowGlobal]]; for (const auto& element : (*inMCLabels)[i].getLabels(k)) { mcOut.addElement(nCls, element); } @@ -95,40 +105,65 @@ int HardwareClusterDecoder::decodeClusters(std::vectorclusters, outputBufferMap[i]->nClusters, (*outMCLabels)[i]); - } else { - auto* cl = outputBufferMap[i]->clusters; - std::sort(cl, cl + outputBufferMap[i]->nClusters); + for (int i = 0; i < Constants::MAXSECTOR; i++) { + for (int j = 0; j < Constants::MAXGLOBALPADROW; j++) { + if (nRowClusters[i][j] == 0) { + continue; + } + if (outMCLabels) { + sortClustersAndMC(outputClusterBuffer + clusterOffsets[i][j], nRowClusters[i][j], outMCLabelContainers[containerRowCluster[i][j]]); + } else { + auto* cl = outputClusterBuffer + clusterOffsets[i][j]; + std::sort(cl, cl + nRowClusters[i][j]); + } } } } else { //Now we know the size of all output buffers, allocate them - if (outMCLabels) - outMCLabels->resize(numberOfOutputContainers); - size_t rawOutputBufferSize = numberOfOutputContainers * sizeof(ClusterNativeBuffer) + nTotalClusters * sizeof(ClusterNative); + if (outMCLabels) { + outMCLabelContainers.resize(numberOfOutputContainers); + } + size_t rawOutputBufferSize = sizeof(ClusterCountIndex) + nTotalClusters * sizeof(ClusterNative); char* rawOutputBuffer = outputAllocator(rawOutputBufferSize); - char* rawOutputBufferIterator = rawOutputBuffer; + auto& clusterCounts = *(reinterpret_cast(rawOutputBuffer)); + outputClusterBuffer = reinterpret_cast(rawOutputBuffer + sizeof(ClusterCountIndex)); + nTotalClusters = 0; numberOfOutputContainers = 0; for (int i = 0; i < Constants::MAXSECTOR; i++) { for (int j = 0; j < Constants::MAXGLOBALPADROW; j++) { - if (nRowClusters[i][j] == 0) + clusterCounts.nClusters[i][j] = nRowClusters[i][j]; + if (nRowClusters[i][j] == 0) { continue; - outputBufferMap.push_back(reinterpret_cast(rawOutputBufferIterator)); - ClusterNativeBuffer& container = *outputBufferMap.back(); - container.sector = i; - container.globalPadRow = j; - container.nClusters = nRowClusters[i][j]; + } containerRowCluster[i][j] = numberOfOutputContainers++; - rawOutputBufferIterator += container.getFlatSize(); + clusterOffsets[i][j] = nTotalClusters; + nTotalClusters += nRowClusters[i][j]; mIntegrator->initRow(i, j); } } - assert(rawOutputBufferIterator == rawOutputBuffer + rawOutputBufferSize); memset(nRowClusters, 0, sizeof(nRowClusters)); } } + // Finally merge MC label containers into one container following the cluster sequence in the + // output buffer + if (outMCLabels) { + auto& labels = *outMCLabels; + int nCls = 0; + for (int i = 0; i < Constants::MAXSECTOR; i++) { + for (int j = 0; j < Constants::MAXGLOBALPADROW; j++) { + if (nRowClusters[i][j] == 0) { + continue; + } + for (int k = 0, end = outMCLabelContainers[containerRowCluster[i][j]].getIndexedSize(); k < end; k++, nCls++) { + assert(end == nRowClusters[i][j]); + assert(clusterOffsets[i][j] + k == nCls); + for (const auto& element : outMCLabelContainers[containerRowCluster[i][j]].getLabels(k)) { + labels.addElement(nCls, element); + } + } + } + } + } return (0); } diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCSectorCompletionPolicy.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCSectorCompletionPolicy.h index a44b334cc33a5..ae394304dd9df 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCSectorCompletionPolicy.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCSectorCompletionPolicy.h @@ -125,7 +125,8 @@ class TPCSectorCompletionPolicy throw std::runtime_error("TPC sector header missing on header stack"); } activeSectors |= sectorHeader->activeSectors; - validSectors.set(sectorHeader->sector()); + std::bitset sectorMask(sectorHeader->sectorBits); + validSectors |= sectorMask; break; } } diff --git a/Detectors/TPC/workflow/src/CATrackerSpec.cxx b/Detectors/TPC/workflow/src/CATrackerSpec.cxx index 95b0afe403aac..5f80e90a5cb40 100644 --- a/Detectors/TPC/workflow/src/CATrackerSpec.cxx +++ b/Detectors/TPC/workflow/src/CATrackerSpec.cxx @@ -309,9 +309,14 @@ DataProcessorSpec getCATrackerSpec(ca::Config const& specconfig, std::vectorverbosity; // FIXME cleanup almost duplicated code auto& validMcInputs = processAttributes->validMcInputs; - using CachedMCLabelContainer = decltype(std::declval().get>(DataRef{nullptr, nullptr, nullptr})); - std::array mcInputs; - std::array, NSectors> inputs; + using CachedMCLabelContainer = decltype(std::declval().get(DataRef{nullptr, nullptr, nullptr})); + std::vector mcInputs; + std::vector> inputs; + struct InputRef { + DataRef data; + DataRef labels; + }; + std::map inputrefs; o2::gpu::GPUTrackingInOutZS tpcZS; std::vector tpcZSmetaPointers[GPUTrackingInOutZS::NSLICES][GPUTrackingInOutZS::NENDPOINTS]; std::vector tpcZSmetaSizes[GPUTrackingInOutZS::NSLICES][GPUTrackingInOutZS::NENDPOINTS]; @@ -339,28 +344,23 @@ DataProcessorSpec getCATrackerSpec(ca::Config const& specconfig, std::vector= TPCSectorHeader::NSectors) { - throw std::runtime_error("Expecting data for single sectors"); - } - if (validMcInputs.test(sector)) { + std::bitset sectorMask(sectorHeader->sectorBits); + if ((validMcInputs & sectorMask).any()) { // have already data for this sector, this should not happen in the current // sequential implementation, for parallel path merged at the tracker stage // multiple buffers need to be handled throw std::runtime_error("can only have one MC data set per sector"); } + inputrefs[sector].labels = ref; if (caClusterer) { inputDigitsMC[sector] = std::move(pc.inputs().get(ref)); } else { - mcInputs[sector] = std::move(pc.inputs().get>(ref)); } - validMcInputs.set(sector); + validMcInputs |= sectorMask; activeSectors |= sectorHeader->activeSectors; if (verbosity > 1) { LOG(INFO) << "received " << *(ref.spec) << " MC label containers" - << " for sector " << sector // + << " for sectors " << sectorMask // << std::endl // << " mc input status: " << validMcInputs // << std::endl // @@ -371,7 +371,6 @@ DataProcessorSpec getCATrackerSpec(ca::Config const& specconfig, std::vectorvalidInputs; int operation = 0; - std::map datarefs; std::vector filter = { {"check", ConcreteDataTypeMatcher{gDataOriginTPC, "DIGITS"}, Lifetime::Timeframe}, {"check", ConcreteDataTypeMatcher{gDataOriginTPC, "CLUSTERNATIVE"}, Lifetime::Timeframe}, @@ -384,24 +383,18 @@ DataProcessorSpec getCATrackerSpec(ca::Config const& specconfig, std::vectorsector(); if (sector < 0) { - //throw std::runtime_error("lagacy input, custom eos is not expected anymore") continue; } - // the TPCSectorHeader now allows to transport information for more than one sector, - // e.g. for transporting clusters in one single data block. For the moment, the - // implemenation here requires single sectors - if (sector >= TPCSectorHeader::NSectors) { - throw std::runtime_error("Expecting data for single sectors"); - } - if (validInputs.test(sector)) { + std::bitset sectorMask(sectorHeader->sectorBits); + if ((validInputs & sectorMask).any()) { // have already data for this sector, this should not happen in the current // sequential implementation, for parallel path merged at the tracker stage // multiple buffers need to be handled throw std::runtime_error("can only have one cluster data set per sector"); } activeSectors |= sectorHeader->activeSectors; - validInputs.set(sector); - datarefs[sector] = ref; + validInputs |= sectorMask; + inputrefs[sector].data = ref; if (caClusterer && !zsOnTheFly) { inputDigits[sector] = pc.inputs().get>(ref); LOG(INFO) << "GOT SPAN FOR SECTOR " << sector << " -> " << inputDigits[sector].size(); @@ -538,12 +531,20 @@ DataProcessorSpec getCATrackerSpec(ca::Config const& specconfig, std::vector(refentry.second.labels))); + } + inputs.emplace_back(gsl::span(ref.payload, DataRefUtils::getPayloadSize(ref))); printInputLog(ref, "received", sector); } + assert(mcInputs.size() == 0 || mcInputs.size() == inputs.size()); if (verbosity > 0) { // make human readable information from the bitfield std::string bitInfo; @@ -623,6 +624,28 @@ DataProcessorSpec getCATrackerSpec(ca::Config const& specconfig, std::vector>(Output{"", "", 0}))>; + ClusterOutputChunkType* clusterOutput = nullptr; + o2::tpc::TPCSectorHeader clusterOutputSectorHeader{0}; + if (processAttributes->clusterOutputIds.size() > 0) { + if (activeSectors == 0) { + // there is no sector header shipped with the ZS raw data and thus we do not have + // a valid activeSector variable, though it will be needed downstream + // FIXME: check if this can be provided upstream + for (auto const& sector : processAttributes->clusterOutputIds) { + activeSectors |= 0x1 << sector; + } + } + clusterOutputSectorHeader.sectorBits = activeSectors; + // subspecs [0, NSectors - 1] are used to identify sector data, we use NSectors + // to indicate the full TPC + o2::header::DataHeader::SubSpecificationType subspec = NSectors; + clusterOutputSectorHeader.activeSectors = activeSectors; + clusterOutput = &pc.outputs().make>({gDataOriginTPC, "CLUSTERNATIVE", subspec, Lifetime::Timeframe, {clusterOutputSectorHeader}}); + } + GPUInterfaceOutputs outputRegions; size_t bufferSize = 2048ul * 1024 * 1024; // TODO: Just allocated some large buffer for now, should estimate this correctly; auto* bufferCompressedClusters = doOutputCompressedClustersFlat ? &pc.outputs().make>(Output{gDataOriginTPC, "COMPCLUSTERSFLAT", 0}, bufferSize) : nullptr; @@ -672,23 +695,22 @@ DataProcessorSpec getCATrackerSpec(ca::Config const& specconfig, std::vectorclusterOutputIds.size() > 0 && ptrs.clusters == nullptr) { throw std::logic_error("No cluster index object provided by GPU processor"); } - if (processAttributes->clusterOutputIds.size() > 0 && activeSectors == 0) { - // there is no sector header shipped with the ZS raw data and thus we do not have - // a valid activeSector variable, though it will be needed downstream - // FIXME: check if this can be provided upstream - for (auto const& sector : processAttributes->clusterOutputIds) { - activeSectors |= 0x1 << sector; - } - } - for (auto const& sector : processAttributes->clusterOutputIds) { - o2::tpc::TPCSectorHeader header{sector}; - o2::header::DataHeader::SubSpecificationType subspec = sector; - header.activeSectors = activeSectors; - auto& target = pc.outputs().make>({gDataOriginTPC, "CLUSTERNATIVE", subspec, Lifetime::Timeframe, {header}}); - std::vector labels; - ClusterNativeHelper::copySectorData(*ptrs.clusters, sector, target, labels); - if (pc.outputs().isAllowed({gDataOriginTPC, "CLNATIVEMCLBL", subspec})) { - pc.outputs().snapshot({gDataOriginTPC, "CLNATIVEMCLBL", subspec, Lifetime::Timeframe, {header}}, labels); + // previously, clusters have been published individually for the enabled sectors + // clusters are now published as one block, subspec is NSectors + if (clusterOutput != nullptr) { + o2::header::DataHeader::SubSpecificationType subspec = NSectors; + // doing a copy for now, in the future the tracker uses the output buffer directly + auto& target = *clusterOutput; + ClusterNativeAccess const& accessIndex = *ptrs.clusters; + size_t outputSize = accessIndex.nClustersTotal * sizeof(ClusterNative) + sizeof(ClusterCountIndex); + target.resize(outputSize); + ClusterCountIndex* outIndex = reinterpret_cast(target.data()); + ClusterNative* outClusters = reinterpret_cast(target.data() + sizeof(ClusterCountIndex)); + static_assert(sizeof(ClusterCountIndex) == sizeof(accessIndex.nClusters)); + memcpy(outIndex, &accessIndex.nClusters[0][0], sizeof(ClusterCountIndex)); + memcpy(outClusters, accessIndex.clustersLinear, accessIndex.nClustersTotal * sizeof(ClusterNative)); + if (pc.outputs().isAllowed({gDataOriginTPC, "CLNATIVEMCLBL", subspec}) && accessIndex.clustersMCTruth) { + pc.outputs().snapshot({gDataOriginTPC, "CLNATIVEMCLBL", subspec, Lifetime::Timeframe, {clusterOutputSectorHeader}}, *accessIndex.clustersMCTruth); } } @@ -696,7 +718,7 @@ DataProcessorSpec getCATrackerSpec(ca::Config const& specconfig, std::vectorclusterOutputIds.emplace_back(sector); - if (specconfig.processMC) { - outputSpecs.emplace_back(OutputSpec{gDataOriginTPC, "CLNATIVEMCLBL", id, Lifetime::Timeframe}); - } + } + outputSpecs.emplace_back(gDataOriginTPC, "CLUSTERNATIVE", NSectors, Lifetime::Timeframe); + if (specconfig.processMC) { + outputSpecs.emplace_back(OutputSpec{gDataOriginTPC, "CLNATIVEMCLBL", NSectors, Lifetime::Timeframe}); } } return std::move(outputSpecs); diff --git a/Detectors/TPC/workflow/src/ClusterDecoderRawSpec.cxx b/Detectors/TPC/workflow/src/ClusterDecoderRawSpec.cxx index f2bed6bd35941..f43fad7a07bcf 100644 --- a/Detectors/TPC/workflow/src/ClusterDecoderRawSpec.cxx +++ b/Detectors/TPC/workflow/src/ClusterDecoderRawSpec.cxx @@ -165,8 +165,8 @@ DataProcessorSpec getClusterDecoderRawSpec(bool sendMC) outputBuffer = pc.outputs().newChunk(Output{gDataOriginTPC, DataDescription("CLUSTERNATIVE"), fanSpec, Lifetime::Timeframe, std::move(rawHeaderStack)}, size).data(); return outputBuffer; }; - std::vector mcoutList; - decoder->decodeClusters(inputList, outputAllocator, (mcin ? &mcinCopies : nullptr), &mcoutList); + MCLabelContainer mcout; + decoder->decodeClusters(inputList, outputAllocator, (mcin ? &mcinCopies : nullptr), &mcout); // TODO: reestablish the logging messages on the raw buffer // if (verbosity > 1) { @@ -177,12 +177,11 @@ DataProcessorSpec getClusterDecoderRawSpec(bool sendMC) if (DataRefUtils::isValid(mclabelref)) { if (verbosity > 0) { - LOG(INFO) << "sending " << mcoutList.size() << " MC label container(s) with in total " - << std::accumulate(mcoutList.begin(), mcoutList.end(), size_t(0), [](size_t l, auto const& r) { return l + r.getIndexedSize(); }) + LOG(INFO) << "sending " << mcout.getIndexedSize() << " label object(s)" << std::endl; } // serialize the complete list of MC label containers - pc.outputs().snapshot(Output{gDataOriginTPC, DataDescription("CLNATIVEMCLBL"), fanSpec, Lifetime::Timeframe, std::move(mcHeaderStack)}, mcoutList); + pc.outputs().snapshot(Output{gDataOriginTPC, DataDescription("CLNATIVEMCLBL"), fanSpec, Lifetime::Timeframe, std::move(mcHeaderStack)}, mcout); } }; diff --git a/Detectors/TPC/workflow/src/PublisherSpec.cxx b/Detectors/TPC/workflow/src/PublisherSpec.cxx index f65aa638ed856..962e5ad028589 100644 --- a/Detectors/TPC/workflow/src/PublisherSpec.cxx +++ b/Detectors/TPC/workflow/src/PublisherSpec.cxx @@ -24,6 +24,9 @@ #include #include // std::move #include //std::invalid_argument +#include +#include +#include using namespace o2::framework; using namespace o2::header; @@ -43,13 +46,19 @@ DataProcessorSpec createPublisherSpec(PublisherConf const& config, bool propagat throw std::invalid_argument("need TPC sector and output id configuration"); } constexpr static size_t NSectors = o2::tpc::Sector::MAXSECTOR; + enum struct SectorMode { + Sector, // stored in sector branches + Full, // full TPC stored in one branch + }; struct ProcessAttributes { std::vector sectors; std::vector outputIds; + std::vector zeroLengthOutputs; uint64_t activeSectors = 0; std::array, NSectors> readers; bool terminateOnEod = false; bool finished = false; + SectorMode sectorMode = SectorMode::Sector; }; auto initFunction = [config, propagateMC, creator](InitContext& ic) { @@ -61,13 +70,35 @@ DataProcessorSpec createPublisherSpec(PublisherConf const& config, bool propagat auto nofEvents = ic.options().get("nevents"); auto publishingMode = nofEvents == -1 ? RootTreeReader::PublishingMode::Single : RootTreeReader::PublishingMode::Loop; + // do a runtime check if the branch name without sector number suffix is found in the file + // if found the publisher will publish the single data set at one output route and empty + // messages at all the others + auto checkSectorMode = [&filename, &treename, &clbrName]() -> SectorMode { + std::unique_ptr file(TFile::Open(filename.c_str())); + if (file) { + TTree* tree = reinterpret_cast(file->GetObjectChecked(treename.c_str(), "TTree")); + if (tree) { + const auto brlist = tree->GetListOfBranches(); + for (TObject const* entry : *brlist) { + if (clbrName == entry->GetName()) { + return SectorMode::Full; + } + } + } + file->Close(); + } + return SectorMode::Sector; + }; + auto processAttributes = std::make_shared(); { processAttributes->terminateOnEod = ic.options().get("terminate-on-eod"); + processAttributes->sectorMode = checkSectorMode(); auto& sectors = processAttributes->sectors; auto& activeSectors = processAttributes->activeSectors; auto& readers = processAttributes->readers; auto& outputIds = processAttributes->outputIds; + auto& sectorMode = processAttributes->sectorMode; sectors = config.tpcSectors; outputIds = config.outputIds; @@ -87,10 +118,7 @@ DataProcessorSpec createPublisherSpec(PublisherConf const& config, bool propagat // TODO: parallelism on sectors needs to be implemented as selector in the reader // the data is now in parallel branches, as first attempt use an array of readers auto outputId = outputIds.begin(); - for (size_t sector = 0; sector < NSectors; ++sector) { - if ((activeSectors & ((uint64_t)0x1 << sector)) == 0) { - continue; - } + for (auto const& sector : sectors) { o2::header::DataHeader::SubSpecificationType subSpec = *outputId; std::string sectorfile = filename; if (filename.find('%') != std::string::npos) { @@ -98,8 +126,12 @@ DataProcessorSpec createPublisherSpec(PublisherConf const& config, bool propagat snprintf(formattedname.data(), formattedname.size() - 1, filename.c_str(), sector); sectorfile = formattedname.data(); } - std::string clusterbranchname = clbrName + "_" + std::to_string(sector); - std::string mcbranchname = mcbrName + "_" + std::to_string(sector); + std::string clusterbranchname = clbrName; + std::string mcbranchname = mcbrName; + if (sectorMode == SectorMode::Sector) { + clusterbranchname += "_" + std::to_string(sector); + mcbranchname += "_" + std::to_string(sector); + } readers[sector] = creator(treename.c_str(), // tree name sectorfile.c_str(), // input file name nofEvents, // number of entries to publish @@ -108,10 +140,19 @@ DataProcessorSpec createPublisherSpec(PublisherConf const& config, bool propagat clusterbranchname.c_str(), // name of data branch mcbranchname.c_str() // name of mc label branch ); + if (sectorMode == SectorMode::Full) { + break; + } if (++outputId == outputIds.end()) { outputId = outputIds.begin(); } } + if (sectorMode == SectorMode::Full) { + // the slot of the first configured sector is used to publish the full set, all others removed + sectors.resize(1); + // the data will be published at first configured output id, zero-length data on all other output ids + processAttributes->zeroLengthOutputs.assign(++outputId, outputIds.end()); + } } // set up the processing function @@ -120,7 +161,7 @@ DataProcessorSpec createPublisherSpec(PublisherConf const& config, bool propagat // function gets out of scope // FIXME: wanted to use it = sectors.begin() in the variable capture but the iterator // is const and can not be incremented - auto processingFct = [processAttributes, propagateMC](ProcessingContext& pc) { + auto processingFct = [processAttributes, config](ProcessingContext& pc) { if (processAttributes->finished) { return; } @@ -131,6 +172,9 @@ DataProcessorSpec createPublisherSpec(PublisherConf const& config, bool propagat auto& activeSectors = processAttributes->activeSectors; auto& readers = processAttributes->readers; o2::tpc::TPCSectorHeader header{sector}; + if (processAttributes->sectorMode == SectorMode::Full) { + header.sectorBits = activeSectors; + } header.activeSectors = activeSectors; auto& r = *(readers[sector].get()); @@ -149,6 +193,19 @@ DataProcessorSpec createPublisherSpec(PublisherConf const& config, bool propagat processAttributes->finished = true; pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); + } else { + // publish empty events + auto dto = DataSpecUtils::asConcreteDataTypeMatcher(config.dataoutput); + auto mco = DataSpecUtils::asConcreteDataTypeMatcher(config.mcoutput); + o2::tpc::TPCSectorHeader header{0}; + header.sectorBits = 0; + header.activeSectors = processAttributes->activeSectors; + for (auto const& subSpec : processAttributes->zeroLengthOutputs) { + pc.outputs().make({dto.origin, dto.description, subSpec, Lifetime::Timeframe, {header}}); + if (pc.outputs().isAllowed({mco.origin, mco.description, subSpec})) { + pc.outputs().make({mco.origin, mco.description, subSpec, Lifetime::Timeframe, {header}}); + } + } } }; diff --git a/Detectors/TPC/workflow/src/RecoWorkflow.cxx b/Detectors/TPC/workflow/src/RecoWorkflow.cxx index e339e941a42fa..95c57c4a7bdc9 100644 --- a/Detectors/TPC/workflow/src/RecoWorkflow.cxx +++ b/Detectors/TPC/workflow/src/RecoWorkflow.cxx @@ -288,7 +288,8 @@ framework::WorkflowSpec getWorkflow(std::vector const& tpcSectors, std::vec const char* defaultFileName, const char* defaultTreeName, auto&& databranch, - auto&& mcbranch) { + auto&& mcbranch, + bool singleBranch = false) { if (tpcSectors.size() == 0) { throw std::invalid_argument(std::string("writer process configuration needs list of TPC sectors")); } @@ -297,12 +298,17 @@ framework::WorkflowSpec getWorkflow(std::vector const& tpcSectors, std::vec input.binding += std::to_string(laneConfiguration[index]); DataSpecUtils::updateMatchingSubspec(input, laneConfiguration[index]); }; - auto amendBranchDef = [laneConfiguration, amendInput, tpcSectors, getIndex, getName](auto&& def, bool enable = true) { - def.keys = mergeInputs(def.keys, laneConfiguration.size(), amendInput); - // the branch is disabled if set to 0 - def.nofBranches = enable ? tpcSectors.size() : 0; - def.getIndex = getIndex; - def.getName = getName; + auto amendBranchDef = [laneConfiguration, amendInput, tpcSectors, getIndex, getName, singleBranch](auto&& def, bool enableMC = true) { + if (!singleBranch) { + def.keys = mergeInputs(def.keys, laneConfiguration.size(), amendInput); + // the branch is disabled if set to 0 + def.nofBranches = enableMC ? tpcSectors.size() : 0; + def.getIndex = getIndex; + def.getName = getName; + } else { + // instead of the separate sector branches only one is going to be written + def.nofBranches = enableMC ? 1 : 0; + } return std::move(def); }; @@ -354,16 +360,19 @@ framework::WorkflowSpec getWorkflow(std::vector const& tpcSectors, std::vec // // selected by output type 'clusters' if (isEnabled(OutputType::Clusters) && !isEnabled(OutputType::DisableWriter)) { - using MCLabelCollection = std::vector>; + using MCLabelContainer = o2::dataformats::MCTruthContainer; + // if the caClusterer is enabled, only one data set with the full TPC is produced, and the writer + // is configured to write one single branch specs.push_back(makeWriterSpec("tpc-native-cluster-writer", inputType == InputType::Clusters ? "tpc-filtered-native-clusters.root" : "tpc-native-clusters.root", "tpcrec", - BranchDefinition{InputSpec{"data", "TPC", "CLUSTERNATIVE", 0}, + BranchDefinition{InputSpec{"data", ConcreteDataTypeMatcher{"TPC", "CLUSTERNATIVE"}}, "TPCClusterNative", "databranch"}, - BranchDefinition{InputSpec{"mc", "TPC", "CLNATIVEMCLBL", 0}, - "TPCClusterNativeMCTruth", - "mcbranch"})); + BranchDefinition{InputSpec{"mc", ConcreteDataTypeMatcher{"TPC", "CLNATIVEMCLBL"}}, + "TPCClusterNativeMCTruth", + "mcbranch"}, + caClusterer)); } if (zsOnTheFly) {