diff --git a/DataFormats/simulation/include/SimulationDataFormat/DigitizationContext.h b/DataFormats/simulation/include/SimulationDataFormat/DigitizationContext.h index de8d89e6b1b72..4149b32683060 100644 --- a/DataFormats/simulation/include/SimulationDataFormat/DigitizationContext.h +++ b/DataFormats/simulation/include/SimulationDataFormat/DigitizationContext.h @@ -113,6 +113,11 @@ class DigitizationContext /// Check collision parts for vertex consistency. bool checkVertexCompatibility(bool verbose = false) const; + /// retrieves collision context for a single timeframe-id (which may be needed by simulation) + /// (Only copies collision context without QED information. This can be added to the result with the fillQED method + /// in a second step. As a pre-condition, one should have called finalizeTimeframeStructure) + DigitizationContext extractSingleTimeframe(int timeframeid, std::vector const& sources_to_offset); + /// function reading the hits from a chain (previously initialized with initSimChains /// The hits pointer will be initialized (what to we do about ownership??) template @@ -128,8 +133,9 @@ class DigitizationContext // apply collision number cuts and potential relabeling of eventID void applyMaxCollisionFilter(long startOrbit, long orbitsPerTF, int maxColl); - // finalize timeframe structure (fixes the indices in mTimeFrameStartIndex) - void finalizeTimeframeStructure(long startOrbit, long orbitsPerTF); + /// finalize timeframe structure (fixes the indices in mTimeFrameStartIndex) + // returns the number of timeframes + int finalizeTimeframeStructure(long startOrbit, long orbitsPerTF); // Sample and fix interaction vertices (according to some distribution). Makes sure that same event ids // have to have same vertex, as well as event ids associated to same collision. @@ -173,7 +179,7 @@ class DigitizationContext // for each collision we may record/fix the interaction vertex (to be used in event generation) std::vector> mInteractionVertices; - // the collision records _with_ QED interleaved; + // the collision records **with** QED interleaved; std::vector mEventRecordsWithQED; std::vector> mEventPartsWithQED; diff --git a/DataFormats/simulation/src/DigitizationContext.cxx b/DataFormats/simulation/src/DigitizationContext.cxx index f3f40d77042a5..ba1fda53e179b 100644 --- a/DataFormats/simulation/src/DigitizationContext.cxx +++ b/DataFormats/simulation/src/DigitizationContext.cxx @@ -19,6 +19,7 @@ #include // for iota #include #include +#include using namespace o2::steer; @@ -196,10 +197,52 @@ o2::parameters::GRPObject const& DigitizationContext::getGRP() const void DigitizationContext::saveToFile(std::string_view filename) const { + // checks if the path content of filename exists ... otherwise it is created before creating the ROOT file + auto ensure_path_exists = [](std::string_view filename) { + try { + // Extract the directory path from the filename + std::filesystem::path file_path(filename); + std::filesystem::path dir_path = file_path.parent_path(); + + // Check if the directory path is empty (which means filename was just a name without path) + if (dir_path.empty()) { + // nothing to do + return true; + } + + // Create directories if they do not exist + if (!std::filesystem::exists(dir_path)) { + if (std::filesystem::create_directories(dir_path)) { + // std::cout << "Directories created successfully: " << dir_path.string() << std::endl; + return true; + } else { + std::cerr << "Failed to create directories: " << dir_path.string() << std::endl; + return false; + } + } + return true; + } catch (const std::filesystem::filesystem_error& ex) { + std::cerr << "Filesystem error: " << ex.what() << std::endl; + return false; + } catch (const std::exception& ex) { + std::cerr << "General error: " << ex.what() << std::endl; + return false; + } + }; + + if (!ensure_path_exists(filename)) { + LOG(error) << "Filename contains path component which could not be created"; + return; + } + TFile file(filename.data(), "RECREATE"); - auto cl = TClass::GetClass(typeid(*this)); - file.WriteObjectAny(this, cl, "DigitizationContext"); - file.Close(); + if (file.IsOpen()) { + auto cl = TClass::GetClass(typeid(*this)); + file.WriteObjectAny(this, cl, "DigitizationContext"); + file.Close(); + } else { + LOG(error) << "Could not write to file " << filename.data(); + } } DigitizationContext* DigitizationContext::loadFromFile(std::string_view filename) @@ -391,13 +434,15 @@ void DigitizationContext::applyMaxCollisionFilter(long startOrbit, long orbitsPe mEventParts = newparts; } -void DigitizationContext::finalizeTimeframeStructure(long startOrbit, long orbitsPerTF) +int DigitizationContext::finalizeTimeframeStructure(long startOrbit, long orbitsPerTF) { mTimeFrameStartIndex = getTimeFrameBoundaries(mEventRecords, startOrbit, orbitsPerTF); LOG(info) << "Fixed " << mTimeFrameStartIndex.size() << " timeframes "; for (auto p : mTimeFrameStartIndex) { LOG(info) << p.first << " " << p.second; } + + return mTimeFrameStartIndex.size(); } std::unordered_map DigitizationContext::getCollisionIndicesForSource(int source) const @@ -483,3 +528,55 @@ void DigitizationContext::sampleInteractionVertices(o2::dataformats::MeanVertexO } } } + +DigitizationContext DigitizationContext::extractSingleTimeframe(int timeframeid, std::vector const& sources_to_offset) +{ + DigitizationContext r; // make a return object + if (mTimeFrameStartIndex.size() == 0) { + LOG(error) << "No timeframe structure determined; Returning empty object. Please call ::finalizeTimeframeStructure before calling this function"; + return r; + } + r.mSimPrefixes = mSimPrefixes; + r.mMuBC = mMuBC; + try { + auto startend = mTimeFrameStartIndex.at(timeframeid); + + auto startindex = startend.first; + auto endindex = startend.second; + + std::copy(mEventRecords.begin() + startindex, mEventRecords.begin() + endindex, std::back_inserter(r.mEventRecords)); + std::copy(mEventParts.begin() + startindex, mEventParts.begin() + endindex, std::back_inserter(r.mEventParts)); + if (mInteractionVertices.size() > endindex) { + std::copy(mInteractionVertices.begin() + startindex, mInteractionVertices.begin() + endindex, std::back_inserter(r.mInteractionVertices)); + } + + // let's assume we want to fix the ids for source = source_id + // Then we find the first index that has this source_id and take the corresponding number + // as offset. Thereafter we subtract this offset from all known event parts. + auto perform_offsetting = [&r](int source_id) { + auto indices_for_source = r.getCollisionIndicesForSource(source_id); + int minvalue = std::numeric_limits::max(); + for (auto& p : indices_for_source) { + if (p.first < minvalue) { + minvalue = p.first; + } + } + // now fix them + for (auto& p : indices_for_source) { + auto index_into_mEventParts = p.second; + for (auto& part : r.mEventParts[index_into_mEventParts]) { + if (part.sourceID == source_id) { + part.entryID -= minvalue; + } + } + } + }; + for (auto source_id : sources_to_offset) { + perform_offsetting(source_id); + } + + } catch (std::exception) { + LOG(warn) << "No such timeframe id in collision context. Returing empty object"; + } + return r; +} diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackInfoExt.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackInfoExt.h index 754c08388abdb..b988eddfa861f 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackInfoExt.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackInfoExt.h @@ -27,6 +27,7 @@ namespace dataformats struct TrackInfoExt { o2::track::TrackParCov track; DCA dca{}; + DCA dcaTPC{}; VtxTrackIndex gid; MatchInfoTOF infoTOF; float ttime = 0; diff --git a/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx b/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx index 378d2b9dcfacc..1e605e308f4ab 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx @@ -364,10 +364,30 @@ void TrackingStudySpec::process(o2::globaltracking::RecoContainer& recoData) continue; } { + o2::dataformats::DCA dcaTPC; + dcaTPC.set(-999.f, -999.f); + if (tpcTr) { + if (is == GTrackID::TPC) { + dcaTPC = dca; + } else { + o2::track::TrackParCov tmpTPC(*tpcTr); + if (iv < nv - 1 && is == GTrackID::TPC && tpcTr && !tpcTr->hasBothSidesClusters()) { // for unconstrained TPC tracks correct track Z + float corz = vdrit * (tpcTr->getTime0() * mTPCTBinMUS - pvvec[iv].getTimeStamp().getTimeStamp()); + if (tpcTr->hasASideClustersOnly()) { + corz = -corz; // A-side + } + tmpTPC.setZ(tmpTPC.getZ() + corz); + } + if (!prop->propagateToDCA(iv == nv - 1 ? vtxDummy : pvvec[iv], tmpTPC, prop->getNominalBz(), 2., o2::base::PropagatorF::MatCorrType::USEMatCorrLUT, &dcaTPC)) { + dcaTPC.set(-999.f, -999.f); + } + } + } auto& trcExt = trcExtVec.emplace_back(); recoData.getTrackTime(vid, trcExt.ttime, trcExt.ttimeE); trcExt.track = trc; trcExt.dca = dca; + trcExt.dcaTPC = dcaTPC; trcExt.gid = vid; trcExt.xmin = xmin; auto gidRefs = recoData.getSingleDetectorRefs(vid); diff --git a/Framework/Core/include/Framework/DataRefUtils.h b/Framework/Core/include/Framework/DataRefUtils.h index c63b06357b8ed..2839860965357 100644 --- a/Framework/Core/include/Framework/DataRefUtils.h +++ b/Framework/Core/include/Framework/DataRefUtils.h @@ -122,7 +122,7 @@ struct DataRefUtils { // object only depends on the state at serialization of the original object. However, // all objects created during deserialization are new and must be owned by the collection // to avoid memory leak. So we call SetOwner if it is available for the type. - if constexpr (has_root_setowner::value) { + if constexpr (requires(T t) { t.SetOwner(true); }) { result->SetOwner(true); } }); diff --git a/Framework/Core/include/Framework/TypeTraits.h b/Framework/Core/include/Framework/TypeTraits.h index 19ca548835cdd..faa9055de3280 100644 --- a/Framework/Core/include/Framework/TypeTraits.h +++ b/Framework/Core/include/Framework/TypeTraits.h @@ -147,22 +147,5 @@ class has_root_dictionary::value>::ty { }; -// Detect whether a class is a ROOT class implementing SetOwner -// This member detector idiom is implemented using SFINAE idiom to look for -// a 'SetOwner()' method. -template -struct has_root_setowner : std::false_type { -}; - -template -struct has_root_setowner< - T, - std::conditional_t< - false, - class_member_checker< - decltype(std::declval().SetOwner(true))>, - void>> : public std::true_type { -}; - } // namespace o2::framework #endif // FRAMEWORK_TYPETRAITS_H diff --git a/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx b/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx index 1acbb99973b43..537c3cf63a628 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx +++ b/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx @@ -216,8 +216,9 @@ int32_t GPUReconstructionCPU::RunChains() timerTotal.Start(); if (mProcessingSettings.doublePipeline) { - if (EnqueuePipeline()) { - return 1; + int32_t retVal = EnqueuePipeline(); + if (retVal) { + return retVal; } } else { if (mThreadId != GetThread()) { diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index be843b01610e8..224e7c720c334 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -89,7 +89,7 @@ AddOptionRTC(extraClusterErrorSplitPadSharedSingleY2, float, 0.03f, "", 0, "Addi AddOptionRTC(extraClusterErrorFactorSplitPadSharedSingleY2, float, 3.0f, "", 0, "Multiplicative extra cluster error for Y2 if splitpad, shared, or single set") AddOptionRTC(extraClusterErrorSplitTimeSharedSingleZ2, float, 0.03f, "", 0, "Additive extra cluster error for Z2 if splittime, shared, or single set") AddOptionRTC(extraClusterErrorFactorSplitTimeSharedSingleZ2, float, 3.0f, "", 0, "Multiplicative extra cluster error for Z2 if splittime, shared, or single set") -AddOptionArray(errorsCECrossing, float, 5, (0.f, 0.f, 0.f, 0.f, 0.f), "", 0, "Extra errors to add to track when crossing CE, depending on addErrorsCECrossing") // BUG: CUDA cannot yet hand AddOptionArrayRTC +AddOptionArray(errorsCECrossing, float, 5, (0.f, 0.f, 0.f, 0.f, 0.f), "", 0, "Extra errors to add to track when crossing CE, depending on addErrorsCECrossing") // BUG: CUDA cannot yet handle AddOptionArrayRTC AddOptionRTC(globalTrackingYRangeUpper, float, 0.85f, "", 0, "Inner portion of y-range in slice that is not used in searching for global track candidates") AddOptionRTC(globalTrackingYRangeLower, float, 0.85f, "", 0, "Inner portion of y-range in slice that is not used in searching for global track candidates") AddOptionRTC(trackFollowingYFactor, float, 4.f, "", 0, "Weight of y residual vs z residual in tracklet constructor") diff --git a/GPU/GPUTracking/Global/GPUChainTrackingDebugAndProfiling.cxx b/GPU/GPUTracking/Global/GPUChainTrackingDebugAndProfiling.cxx index 7a4aa73ae13d1..f8a64e9d4faaa 100644 --- a/GPU/GPUTracking/Global/GPUChainTrackingDebugAndProfiling.cxx +++ b/GPU/GPUTracking/Global/GPUChainTrackingDebugAndProfiling.cxx @@ -302,7 +302,7 @@ void GPUChainTracking::SanityCheck() void GPUChainTracking::RunTPCClusterFilter(o2::tpc::ClusterNativeAccess* clusters, std::function allocator, bool applyClusterCuts) { GPUTPCClusterFilter clusterFilter(*clusters); - o2::tpc::ClusterNative* outputBuffer; + o2::tpc::ClusterNative* outputBuffer = nullptr; for (int32_t iPhase = 0; iPhase < 2; iPhase++) { uint32_t countTotal = 0; for (uint32_t iSector = 0; iSector < GPUCA_NSLICES; iSector++) { diff --git a/GPU/Workflow/src/GPUWorkflowSpec.cxx b/GPU/Workflow/src/GPUWorkflowSpec.cxx index f16082cb6c0da..4549d895c26b9 100644 --- a/GPU/Workflow/src/GPUWorkflowSpec.cxx +++ b/GPU/Workflow/src/GPUWorkflowSpec.cxx @@ -851,7 +851,7 @@ void GPURecoWorkflowSpec::run(ProcessingContext& pc) } createEmptyOutput = !mConfParam->partialOutputForNonFatalErrors; } else { - throw std::runtime_error("tracker returned error code " + std::to_string(retVal)); + throw std::runtime_error("GPU Reconstruction error: error code " + std::to_string(retVal)); } } diff --git a/Steer/src/CollisionContextTool.cxx b/Steer/src/CollisionContextTool.cxx index f94fde22ef8ac..af2f607b88774 100644 --- a/Steer/src/CollisionContextTool.cxx +++ b/Steer/src/CollisionContextTool.cxx @@ -55,6 +55,8 @@ struct Options { bool genVertices = false; // whether to assign vertices to collisions std::string configKeyValues = ""; // string to init config key values long timestamp = -1; // timestamp for CCDB queries + std::string individualTFextraction = ""; // triggers extraction of individuel timeframe components when non-null + // format is path prefix }; enum class InteractionLockMode { @@ -200,7 +202,10 @@ bool parseOptions(int argc, char* argv[], Options& optvalues) "timeframeID", bpo::value(&optvalues.tfid)->default_value(0), "Timeframe id of the first timeframe int this context. Allows to generate contexts for different start orbits")( "first-orbit", bpo::value(&optvalues.firstFractionalOrbit)->default_value(0), "First (fractional) orbit in the run (HBFUtils.firstOrbit + BC from decimal)")( "maxCollsPerTF", bpo::value(&optvalues.maxCollsPerTF)->default_value(-1), "Maximal number of MC collisions to put into one timeframe. By default no constraint.")( - "noEmptyTF", bpo::bool_switch(&optvalues.noEmptyTF), "Enforce to have at least one collision")("configKeyValues", bpo::value(&optvalues.configKeyValues)->default_value(""), "Semicolon separated key=value strings (e.g.: 'TPC.gasDensity=1;...')")("with-vertices", "Assign vertices to collisions.")("timestamp", bpo::value(&optvalues.timestamp)->default_value(-1L), "Timestamp for CCDB queries / anchoring"); + "noEmptyTF", bpo::bool_switch(&optvalues.noEmptyTF), "Enforce to have at least one collision")( + "configKeyValues", bpo::value(&optvalues.configKeyValues)->default_value(""), "Semicolon separated key=value strings (e.g.: 'TPC.gasDensity=1;...')")("with-vertices", "Assign vertices to collisions.")("timestamp", bpo::value(&optvalues.timestamp)->default_value(-1L), "Timestamp for CCDB queries / anchoring")( + "extract-per-timeframe", bpo::value(&optvalues.individualTFextraction)->default_value(""), + "Extract individual timeframe contexts. Format required: time_frame_prefix[:comma_separated_list_of_signals_to_offset]"); options.add_options()("help,h", "Produce help message."); @@ -283,6 +288,8 @@ int main(int argc, char* argv[]) } }; + auto orbitstart = options.firstOrbit + options.tfid * options.orbitsPerTF; + for (int id = 0; id < ispecs.size(); ++id) { auto mode = ispecs[id].syncmode; if (mode == InteractionLockMode::NOLOCK) { @@ -291,7 +298,6 @@ int main(int argc, char* argv[]) if (!options.bcpatternfile.empty()) { setBCFillingHelper(sampler, options.bcpatternfile); } - auto orbitstart = options.firstOrbit + options.tfid * options.orbitsPerTF; o2::InteractionTimeRecord record; // this loop makes sure that the first collision is within the range of orbits asked (if noEmptyTF is enabled) do { @@ -439,9 +445,9 @@ int main(int argc, char* argv[]) digicontext.setSimPrefixes(prefixes); // apply max collision per timeframe filters + reindexing of event id (linearisation and compactification) - digicontext.applyMaxCollisionFilter(options.tfid * options.orbitsPerTF, options.orbitsPerTF, options.maxCollsPerTF); + digicontext.applyMaxCollisionFilter(orbitstart, options.orbitsPerTF, options.maxCollsPerTF); - digicontext.finalizeTimeframeStructure(options.tfid * options.orbitsPerTF, options.orbitsPerTF); + auto numTimeFrames = digicontext.finalizeTimeframeStructure(orbitstart, options.orbitsPerTF); if (options.genVertices) { // TODO: offer option taking meanVertex directly from CCDB ! "GLO/Calib/MeanVertex" @@ -466,5 +472,72 @@ int main(int argc, char* argv[]) } digicontext.saveToFile(options.outfilename); + // extract individual timeframes + if (options.individualTFextraction.size() > 0) { + // we are asked to extract individual timeframe components + + LOG(info) << "Extracting individual timeframe collision contexts"; + // extract prefix path to store these collision contexts + // Function to check the pattern and extract tokens from b + auto check_and_extract_tokens = [](const std::string& input, std::vector& tokens) { + // the regular expression pattern for expected input format + const std::regex pattern(R"(^([a-zA-Z0-9]+)(:([a-zA-Z0-9]+(,[a-zA-Z0-9]+)*))?$)"); + std::smatch matches; + + // Check if the input matches the pattern + if (std::regex_match(input, matches, pattern)) { + // Clear any existing tokens in the vector + tokens.clear(); + + // matches[1] contains the part before the colon which we save first + tokens.push_back(matches[1].str()); + // matches[2] contains the comma-separated list + std::string b = matches[2].str(); + std::regex token_pattern(R"([a-zA-Z0-9]+)"); + auto tokens_begin = std::sregex_iterator(b.begin(), b.end(), token_pattern); + auto tokens_end = std::sregex_iterator(); + + // Iterate over the tokens and add them to the vector + for (std::sregex_iterator i = tokens_begin; i != tokens_end; ++i) { + tokens.push_back((*i).str()); + } + return true; + } + LOG(error) << "Argument for --extract-per-timeframe does not match specification"; + return false; + }; + + std::vector tokens; + if (check_and_extract_tokens(options.individualTFextraction, tokens)) { + auto path_prefix = tokens[0]; + std::vector sources_to_offset{}; + + LOG(info) << "PREFIX is " << path_prefix; + + for (int i = 1; i < tokens.size(); ++i) { + LOG(info) << "Offsetting " << tokens[i]; + sources_to_offset.push_back(digicontext.findSimPrefix(tokens[i])); + } + + // now we are ready to loop over all timeframes + for (int tf_id = 0; tf_id < numTimeFrames; ++tf_id) { + auto copy = digicontext.extractSingleTimeframe(tf_id, sources_to_offset); + + // each individual case gets QED interactions injected + // This should probably be done inside the extraction itself + if (digicontext.isQEDProvided()) { + auto qedSpec = parseInteractionSpec(options.qedInteraction, ispecs, options.useexistingkinematics); + copy.fillQED(qedSpec.name, qedSpec.mcnumberasked, qedSpec.interactionRate); + } + + std::stringstream str; + str << path_prefix << (tf_id + 1) << "/collisioncontext.root"; + copy.saveToFile(str.str()); + LOG(info) << "----"; + copy.printCollisionSummary(options.qedInteraction.size() > 0); + } + } + } + return 0; } diff --git a/prodtests/full-system-test/aggregator-workflow.sh b/prodtests/full-system-test/aggregator-workflow.sh index 4e5f6f2a4c8ad..4c20e901a2978 100755 --- a/prodtests/full-system-test/aggregator-workflow.sh +++ b/prodtests/full-system-test/aggregator-workflow.sh @@ -295,7 +295,8 @@ fi # TPC IDCs and SAC crus="0-359" # to be used with $AGGREGATOR_TASKS == TPC_IDCBOTH_SAC or ALL -lanesFactorize=10 +lanesFactorize=${O2_TPC_IDC_FACTORIZE_NLANES:-10} +threadFactorize=${O2_TPC_IDC_FACTORIZE_NTHREADS:-8} nTFs=$((1000 * 128 / ${NHBPERTF})) nTFs_SAC=$((1000 * 128 / ${NHBPERTF})) nBuffer=$((100 * 128 / ${NHBPERTF})) @@ -309,7 +310,7 @@ if [[ "${DISABLE_IDC_PAD_MAP_WRITING:-}" == 1 ]]; then TPC_WRITING_PAD_STATUS_MA if ! workflow_has_parameter CALIB_LOCAL_INTEGRATED_AGGREGATOR; then if [[ $CALIB_TPC_IDC == 1 ]] && [[ $AGGREGATOR_TASKS == TPC_IDCBOTH_SAC || $AGGREGATOR_TASKS == ALL ]]; then add_W o2-tpc-idc-distribute "--crus ${crus} --timeframes ${nTFs} --output-lanes ${lanesFactorize} --send-precise-timestamp true --condition-tf-per-query ${nTFs} --n-TFs-buffer ${nBuffer}" - add_W o2-tpc-idc-factorize "--n-TFs-buffer ${nBuffer} --input-lanes ${lanesFactorize} --crus ${crus} --timeframes ${nTFs} --nthreads-grouping 8 --nthreads-IDC-factorization 8 --sendOutputFFT true --enable-CCDB-output true --enablePadStatusMap true ${TPC_WRITING_PAD_STATUS_MAP} --use-precise-timestamp true $IDC_DELTA" "TPCIDCGroupParam.groupPadsSectorEdges=32211" + add_W o2-tpc-idc-factorize "--n-TFs-buffer ${nBuffer} --input-lanes ${lanesFactorize} --crus ${crus} --timeframes ${nTFs} --nthreads-grouping ${threadFactorize} --nthreads-IDC-factorization ${threadFactorize} --sendOutputFFT true --enable-CCDB-output true --enablePadStatusMap true ${TPC_WRITING_PAD_STATUS_MAP} --use-precise-timestamp true $IDC_DELTA" "TPCIDCGroupParam.groupPadsSectorEdges=32211" add_W o2-tpc-idc-ft-aggregator "--rangeIDC 200 --inputLanes ${lanesFactorize} --nFourierCoeff 40 --nthreads 8" fi if [[ $CALIB_TPC_SAC == 1 ]] && [[ $AGGREGATOR_TASKS == TPC_IDCBOTH_SAC || $AGGREGATOR_TASKS == ALL ]]; then