diff --git a/.clang-tidy b/.clang-tidy index da768906bcc32..080c35ba31df3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,3 +1,42 @@ +--- +Checks: + - -* + - modernize-avoid-bind + - modernize-deprecated-headers + - modernize-make-shared + - modernize-raw-string-literal + - modernize-redundant-void-arg + - modernize-replace-auto-ptr + - modernize-replace-random-shuffle + - modernize-shrink-to-fit + - modernize-unary-static-assert + - modernize-use-equals-default + - modernize-use-noexcept + - modernize-use-nullptr + - modernize-use-override + - modernize-use-transparent-functors + - modernize-use-uncaught-exceptions + - readability-braces-around-statements + - -clang-diagnostic-vla-cxx-extension CheckOptions: - - key: CheckPathRegex - value: '.*/O2/.*' + # Naming conventions + readability-identifier-naming.ClassCase: CamelCase + readability-identifier-naming.ClassMemberPrefix: m + readability-identifier-naming.ConceptCase: CamelCase + readability-identifier-naming.ConstexprVariableCase: CamelCase + readability-identifier-naming.EnumCase: CamelCase + readability-identifier-naming.EnumConstantCase: CamelCase + readability-identifier-naming.EnumConstantIgnoredRegexp: "^k?[A-Z][a-zA-Z0-9_]*$" # Allow "k" prefix and non-trailing underscores in PDG names. + readability-identifier-naming.FunctionCase: camelBack + readability-identifier-naming.MacroDefinitionCase: UPPER_CASE + readability-identifier-naming.MacroDefinitionIgnoredRegexp: "^[A-Z][A-Z0-9_]*_$" # Allow the trailing underscore in header guards. + readability-identifier-naming.MemberCase: camelBack + readability-identifier-naming.NamespaceCase: lower_case + readability-identifier-naming.ParameterCase: camelBack + readability-identifier-naming.StructCase: CamelCase + readability-identifier-naming.TemplateParameterCase: CamelCase + readability-identifier-naming.TypeAliasCase: CamelCase + readability-identifier-naming.TypedefCase: CamelCase + readability-identifier-naming.TypeTemplateParameterCase: CamelCase + readability-identifier-naming.VariableCase: camelBack +... diff --git a/.github/workflows/async-list-label.yml b/.github/workflows/async-list-label.yml deleted file mode 100644 index e0b4185c563b7..0000000000000 --- a/.github/workflows/async-list-label.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Collect and print async labels - -'on': - pull_request_target: - types: - - opened - - reopened - branches: - - dev - -permissions: {} - -jobs: - list_async_labels: - name: Collect and print async labels - uses: alisw/ali-bot/.github/workflows/async-list-label.yml@master - permissions: - pull-requests: write # to update labels diff --git a/CCDB/include/CCDB/BasicCCDBManager.h b/CCDB/include/CCDB/BasicCCDBManager.h index fd0fe7aa6d05b..6ad645a0a893e 100644 --- a/CCDB/include/CCDB/BasicCCDBManager.h +++ b/CCDB/include/CCDB/BasicCCDBManager.h @@ -55,6 +55,7 @@ class CCDBManagerInstance long endvalidity = -1; long cacheValidFrom = 0; // time for which the object was cached long cacheValidUntil = -1; // object is guaranteed to be valid till this time (modulo new updates) + size_t size = 0; size_t minSize = -1ULL; size_t maxSize = 0; int queries = 0; @@ -64,8 +65,8 @@ class CCDBManagerInstance bool isValid(long ts) { return ts < endvalidity && ts >= startvalidity; } bool isCacheValid(long ts) { - LOGP(debug, "isCacheValid : {} : {} : {} --> {}", cacheValidFrom, ts, cacheValidUntil, ts < cacheValidUntil && ts >= cacheValidFrom); - return ts < cacheValidUntil && ts >= cacheValidFrom; + LOGP(debug, "isCacheValid : {} : {} : {} --> {}", cacheValidFrom, ts, cacheValidUntil, isValid(ts)); + return ts < cacheValidUntil && isValid(ts); } void clear() { @@ -229,6 +230,7 @@ class CCDBManagerInstance long mCreatedNotBefore = 0; // lower limit for object creation timestamp (TimeMachine mode) - If-Not-Before HTTP header long mTimerMS = 0; // timer for queries size_t mFetchedSize = 0; // total fetched size + size_t mRequestedSize = 0; // total requested size (fetched + served from cache) int mQueries = 0; // total number of object queries int mFetches = 0; // total number of succesful fetches from CCDB int mFailures = 0; // total number of failed fetches @@ -258,6 +260,7 @@ T* CCDBManagerInstance::getForTimeStamp(std::string const& path, long timestamp, if (sh != mHeaders.end()) { size_t s = atol(sh->second.c_str()); mFetchedSize += s; + mRequestedSize += s; } } @@ -272,6 +275,7 @@ T* CCDBManagerInstance::getForTimeStamp(std::string const& path, long timestamp, if (headers) { *headers = cached.cacheOfHeaders; } + mRequestedSize += cached.size; return reinterpret_cast(cached.noCleanupPtr ? cached.noCleanupPtr : cached.objPtr.get()); } ptr = mCCDBAccessor.retrieveFromTFileAny(path, mMetaData, timestamp, &mHeaders, cached.uuid, @@ -318,6 +322,8 @@ T* CCDBManagerInstance::getForTimeStamp(std::string const& path, long timestamp, if (sh != mHeaders.end()) { size_t s = atol(sh->second.c_str()); mFetchedSize += s; + mRequestedSize += s; + cached.size = s; cached.minSize = std::min(s, cached.minSize); cached.maxSize = std::max(s, cached.minSize); } @@ -342,12 +348,14 @@ T* CCDBManagerInstance::getForTimeStamp(std::string const& path, long timestamp, } auto end = std::chrono::system_clock::now(); mTimerMS += std::chrono::duration_cast(end - start).count(); - auto *ref = o2::framework::ServiceRegistryRef::globalDeviceRef(); + auto* ref = o2::framework::ServiceRegistryRef::globalDeviceRef(); if (ref && ref->active()) { auto& stats = ref->get(); stats.updateStats({(int)o2::framework::ProcessingStatsId::CCDB_CACHE_HIT, o2::framework::DataProcessingStats::Op::Set, (int64_t)mQueries - mFailures - mFetches}); stats.updateStats({(int)o2::framework::ProcessingStatsId::CCDB_CACHE_MISS, o2::framework::DataProcessingStats::Op::Set, (int64_t)mFetches}); stats.updateStats({(int)o2::framework::ProcessingStatsId::CCDB_CACHE_FAILURE, o2::framework::DataProcessingStats::Op::Set, (int64_t)mFailures}); + stats.updateStats({(int)o2::framework::ProcessingStatsId::CCDB_CACHE_FETCHED_BYTES, o2::framework::DataProcessingStats::Op::Set, (int64_t)mFetchedSize}); + stats.updateStats({(int)o2::framework::ProcessingStatsId::CCDB_CACHE_REQUESTED_BYTES, o2::framework::DataProcessingStats::Op::Set, (int64_t)mRequestedSize}); } return ptr; } diff --git a/CCDB/src/BasicCCDBManager.cxx b/CCDB/src/BasicCCDBManager.cxx index d55fdad960d3a..53055d0d1231d 100644 --- a/CCDB/src/BasicCCDBManager.cxx +++ b/CCDB/src/BasicCCDBManager.cxx @@ -101,7 +101,7 @@ std::pair CCDBManagerInstance::getRunDuration(int runnumber, b std::string CCDBManagerInstance::getSummaryString() const { - std::string res = fmt::format("{} queries, {} bytes", mQueries, fmt::group_digits(mFetchedSize)); + std::string res = fmt::format("{} queries, {} fetched / {} requested bytes", mQueries, fmt::group_digits(mFetchedSize), fmt::group_digits(mRequestedSize)); if (mCachingEnabled) { res += fmt::format(" for {} objects", mCache.size()); } diff --git a/CCDB/src/CcdbApi.cxx b/CCDB/src/CcdbApi.cxx index 42bc13904bf61..93a79ad56c477 100644 --- a/CCDB/src/CcdbApi.cxx +++ b/CCDB/src/CcdbApi.cxx @@ -213,8 +213,7 @@ void CcdbApi::init(std::string const& host) snapshotReport += ')'; } - mNeedAlienToken = (host.find("https://") != std::string::npos) || (host.find("alice-ccdb.cern.ch") != std::string::npos); - + mNeedAlienToken = (host.find("https://") != std::string::npos) || (host.find("alice-ccdb.cern.ch") != std::string::npos) || (host.find("ccdb-test.cern.ch") != std::string::npos); // Set the curl timeout. It can be forced with an env var or it has different defaults based on the deployment mode. if (getenv("ALICEO2_CCDB_CURL_TIMEOUT_DOWNLOAD")) { auto timeout = atoi(getenv("ALICEO2_CCDB_CURL_TIMEOUT_DOWNLOAD")); diff --git a/CODEOWNERS b/CODEOWNERS index 369a7cf8a8463..f54738e2ce4e3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -73,7 +73,7 @@ /Detectors/TPC @davidrohr @wiechula @shahor02 /Detectors/TRD @f3sch @bazinski @wille10 /Detectors/Upgrades @mconcas -/Detectors/Upgrades/ALICE3 @mconcas @njacazio +/Detectors/Upgrades/ALICE3 @mconcas @njacazio @fcolamar /Detectors/Upgrades/ITS3 @fgrosa @arossi81 @mconcas @f3sch /Detectors/ZDC @coppedis @cortesep /Detectors/CTF @shahor02 diff --git a/Common/Constants/include/CommonConstants/PhysicsConstants.h b/Common/Constants/include/CommonConstants/PhysicsConstants.h index 46aeff98d6033..e95b4343a63d4 100644 --- a/Common/Constants/include/CommonConstants/PhysicsConstants.h +++ b/Common/Constants/include/CommonConstants/PhysicsConstants.h @@ -92,7 +92,9 @@ enum Pdg { kHyperHelium4 = 1010020040, kHyperHelium5 = 1010020050, kHyperHelium4Sigma = 1110020040, - kLambda1520_Py = 102134 + kLambda1520_Py = 102134, + kK1_1270_0 = 10313, + kK1_1270Plus = 10323 }; /// \brief Declarations of masses for additional particles @@ -158,6 +160,8 @@ constexpr double MassHyperHelium4 = 3.921728; constexpr double MassHyperHelium5 = 4.839961; constexpr double MassHyperHelium4Sigma = 3.995; constexpr double MassLambda1520_Py = 1.5195; +constexpr double MassK1_1270_0 = 1.253; +constexpr double MassK1_1270Plus = 1.272; /// \brief Declarations of masses for particles in ROOT PDG_t constexpr double MassDown = 0.00467; diff --git a/Common/Constants/include/CommonConstants/make_pdg_header.py b/Common/Constants/include/CommonConstants/make_pdg_header.py index f83c44bb401db..ccea1863771f3 100755 --- a/Common/Constants/include/CommonConstants/make_pdg_header.py +++ b/Common/Constants/include/CommonConstants/make_pdg_header.py @@ -151,6 +151,8 @@ class Pdg(Enum): kHyperHelium5 = 1010020050 kHyperHelium4Sigma = 1110020040 kLambda1520_Py = 102134 # PYTHIA code different from PDG + kK1_1270_0 = 10313 + kK1_1270Plus = 10323 dbPdg = o2.O2DatabasePDG diff --git a/Common/Field/src/MagFieldFast.cxx b/Common/Field/src/MagFieldFast.cxx index 02ef9c153d189..9735d0c711fa3 100644 --- a/Common/Field/src/MagFieldFast.cxx +++ b/Common/Field/src/MagFieldFast.cxx @@ -16,12 +16,10 @@ #include "Field/MagFieldFast.h" #include -#ifndef GPUCA_GPUCODE_DEVICE #include #include #include using namespace std; -#endif using namespace o2::field; diff --git a/Common/MathUtils/include/MathUtils/Cartesian.h b/Common/MathUtils/include/MathUtils/Cartesian.h index 9b917707835a6..d7e421ecd965b 100644 --- a/Common/MathUtils/include/MathUtils/Cartesian.h +++ b/Common/MathUtils/include/MathUtils/Cartesian.h @@ -284,25 +284,15 @@ GPUdi() SMatrix> Similarity(const SMatrix +struct is_forced_trivially_copyable; -/// Defining Point3D explicitly as trivially copyable -/// -/// std::is_trivially_copyable> fails because the class -/// implements a copy constructor, although it does not much more than the default copy -/// constructor. We need Point3D to fulfill the condition in order to make types -/// inheriting from it or using it as member can be safely detected as messageable. -/// -/// We believe that Point3D is messageable and explicitly specialize the type trait. -/// There is a unit test for checking trivial copy -/// This is a workaround, we will also make suggestions to fix the cause in ROOT itself -/// TODO: delete once it is fixed in ROOT template -struct is_trivially_copyable> : std::true_type { +struct is_forced_trivially_copyable> : std::true_type { }; -} // namespace std +} // namespace o2::framework #endif // Disable for GPU #endif diff --git a/Common/MathUtils/include/MathUtils/detail/Bracket.h b/Common/MathUtils/include/MathUtils/detail/Bracket.h index 25d0dbd1d0a6e..2da6949c4a6f8 100644 --- a/Common/MathUtils/include/MathUtils/detail/Bracket.h +++ b/Common/MathUtils/include/MathUtils/detail/Bracket.h @@ -17,7 +17,7 @@ #define ALICEO2_BRACKET_H #include -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE #include #include #endif @@ -76,7 +76,7 @@ class Bracket Relation isOutside(T t, T tErr) const; Relation isOutside(T t) const; -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE std::string asString() const; #endif @@ -247,7 +247,7 @@ inline typename Bracket::Relation Bracket::isOutside(T t) const return t < mMin ? Below : (t > mMax ? Above : Inside); } -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE template std::string Bracket::asString() const { diff --git a/Common/MathUtils/test/testCartesian.cxx b/Common/MathUtils/test/testCartesian.cxx index ec04c34670fc3..9f2b4c912007e 100644 --- a/Common/MathUtils/test/testCartesian.cxx +++ b/Common/MathUtils/test/testCartesian.cxx @@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE(Cartesian_test) BOOST_AUTO_TEST_CASE(Point3D_messageable) { using ElementType = math_utils::Point3D; - static_assert(std::is_trivially_copyable::value == true); + static_assert(o2::framework::is_forced_trivially_copyable::value == true); std::vector pts(10); auto makeElement = [](int idx) { return ElementType{idx, idx + 10, idx + 20}; diff --git a/Common/Utils/include/CommonUtils/EnumFlags.h b/Common/Utils/include/CommonUtils/EnumFlags.h index e7481c903e666..5032b6c0f1483 100644 --- a/Common/Utils/include/CommonUtils/EnumFlags.h +++ b/Common/Utils/include/CommonUtils/EnumFlags.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,9 @@ #include #include +#ifndef GPUCA_GPUCODE #include "CommonUtils/StringUtils.h" +#endif namespace o2::utils { @@ -55,6 +58,7 @@ concept EnumFlagHelper = requires { // This is very much inspired by much more extensive libraries like magic_enum. // Inspiration by its c++20 version (https://github.com/fix8mt/conjure_enum). // NOTE: Cannot detect if bit values past the underlying type are defined. +#ifndef GPUCA_GPUCODE template struct FlagsHelper final { using U = std::underlying_type_t; @@ -317,10 +321,12 @@ struct FlagsHelper final { return false; } }; +#endif } // namespace details::enum_flags // Require an enum to fullfil what one would except from a bitset. +#ifndef GPUCA_GPUCODE template concept EnumFlag = requires { // range checks @@ -332,6 +338,10 @@ concept EnumFlag = requires { requires !details::enum_flags::FlagsHelper::hasNone(); // added automatically requires !details::enum_flags::FlagsHelper::hasAll(); // added automatically }; +#else +template +concept EnumFlag = details::enum_flags::EnumFlagHelper; +#endif /** * \brief Class to aggregate and manage enum-based on-off flags. @@ -358,7 +368,9 @@ template class EnumFlags { static constexpr int DefaultBase{2}; +#ifndef GPUCA_GPUCODE using H = details::enum_flags::FlagsHelper; +#endif using U = std::underlying_type_t; U mBits{0}; @@ -388,18 +400,21 @@ class EnumFlags // Initialize with a list of flags. constexpr EnumFlags(std::initializer_list flags) noexcept { - std::for_each(flags.begin(), flags.end(), [this](const E f) noexcept { mBits |= to_bit(f); }); + for (const E f : flags) { + mBits |= to_bit(f); + } } +#ifndef GPUCA_GPUCODE // Init from a string. // explicit EnumFlags(const std::string& str, int base = DefaultBase) { set(str, base); } - // Destructor. - constexpr ~EnumFlags() = default; +#endif - static constexpr U None{0}; // Represents no flags set. + static constexpr U None{0}; // Represents no flags set. +#ifndef GPUCA_GPUCODE static constexpr U All{H::MaxRep}; // Represents all flags set. // Return list of all enum values @@ -432,6 +447,7 @@ class EnumFlags throw; } } +#endif // Returns the raw bitset value. [[nodiscard]] constexpr auto value() const noexcept { @@ -493,6 +509,7 @@ class EnumFlags } // Checks if all flags are set. +#ifndef GPUCA_GPUCODE [[nodiscard]] constexpr bool all() const noexcept { return mBits == All; @@ -537,6 +554,7 @@ class EnumFlags } return oss.str(); } +#endif // Checks if any flag is set (Boolean context). [[nodiscard]] constexpr explicit operator bool() const noexcept @@ -645,6 +663,7 @@ class EnumFlags } // Serializes the flag set to a string. +#ifndef GPUCA_GPUCODE [[nodiscard]] std::string serialize() const { return std::to_string(mBits); @@ -659,6 +678,7 @@ class EnumFlags } mBits = static_cast(v); } +#endif // Counts the number of set bits (active flags). [[nodiscard]] constexpr size_t count() const noexcept @@ -686,6 +706,7 @@ class EnumFlags private: // Set implementation, bits was zeroed before. +#ifndef GPUCA_GPUCODE void setImpl(const std::string& s, int base = 2) { // Helper to check if character is valid for given base @@ -782,14 +803,17 @@ class EnumFlags throw std::invalid_argument("Cannot parse string!"); } } +#endif }; +#ifndef GPUCA_GPUCODE template std::ostream& operator<<(std::ostream& os, const EnumFlags& f) { os << f.pstring(true); return os; } +#endif } // namespace o2::utils diff --git a/Common/Utils/include/CommonUtils/NameConf.h b/Common/Utils/include/CommonUtils/NameConf.h index fb10f929c9782..8d4c0a2c1c4f8 100644 --- a/Common/Utils/include/CommonUtils/NameConf.h +++ b/Common/Utils/include/CommonUtils/NameConf.h @@ -100,6 +100,9 @@ class NameConf : public o2::conf::ConfigurableParamHelper // CTF Dictionary static std::string getCTFDictFileName(); + // O2 Raw TF Filename + static std::string getRawTFFileName(uint32_t run, uint32_t orb, uint32_t id, const std::string& host, const std::string_view prefix = "o2_rawtf_dump"); + // Default CCDB server static std::string getCCDBServer(); diff --git a/Common/Utils/src/DebugStreamer.cxx b/Common/Utils/src/DebugStreamer.cxx index 46ff9be83d415..3a5401ca9b382 100644 --- a/Common/Utils/src/DebugStreamer.cxx +++ b/Common/Utils/src/DebugStreamer.cxx @@ -10,18 +10,16 @@ // or submit itself to any jurisdiction. #include "CommonUtils/DebugStreamer.h" -#if !defined(GPUCA_GPUCODE) && !defined(GPUCA_STANDALONE) #include #include #include "TROOT.h" #include "TKey.h" #include #include "Framework/Logger.h" -#endif O2ParamImpl(o2::utils::ParameterDebugStreamer); -#if !defined(GPUCA_GPUCODE) && !defined(GPUCA_STANDALONE) && defined(DEBUG_STREAMER) +#if defined(DEBUG_STREAMER) o2::utils::DebugStreamer::DebugStreamer() { diff --git a/Common/Utils/src/NameConf.cxx b/Common/Utils/src/NameConf.cxx index 45646284a878b..48cefacaf14c7 100644 --- a/Common/Utils/src/NameConf.cxx +++ b/Common/Utils/src/NameConf.cxx @@ -95,6 +95,11 @@ std::string NameConf::getCTFFileName(uint32_t run, uint32_t orb, uint32_t id, co return o2::utils::Str::concat_string(prefix, '_', fmt::format("run{:08d}_orbit{:010d}_tf{:010d}_{}", run, orb, id, host), ".root"); } +std::string NameConf::getRawTFFileName(uint32_t run, uint32_t orb, uint32_t id, const std::string& host, const std::string_view prefix) +{ + return o2::utils::Str::concat_string(prefix, '_', fmt::format("run{:08d}_orbit{:010d}_tf{:010d}_{}", run, orb, id, host), ".tf"); +} + std::string NameConf::getCTFDictFileName() { return o2::utils::Str::concat_string(CTFDICT, ".root"); diff --git a/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/ChannelData.h b/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/ChannelData.h index 9b3d6ec805604..dfe41525af480 100644 --- a/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/ChannelData.h +++ b/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/ChannelData.h @@ -76,8 +76,8 @@ struct ChannelData { void print() const; void printLog() const; [[nodiscard]] uint8_t getChannelID() const { return ChId; } - [[nodiscard]] uint16_t getTime() const { return CFDTime; } - [[nodiscard]] uint16_t getAmp() const { return QTCAmpl; } + [[nodiscard]] int16_t getTime() const { return CFDTime; } + [[nodiscard]] int16_t getAmp() const { return QTCAmpl; } bool operator==(ChannelData const& other) const { diff --git a/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/EventsPerBc.h b/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/EventsPerBc.h index 9fcd1318914bd..632eac342fdc9 100644 --- a/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/EventsPerBc.h +++ b/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/EventsPerBc.h @@ -14,11 +14,24 @@ #include "CommonConstants/LHCConstants.h" #include +#include +#include namespace o2::ft0 { struct EventsPerBc { std::array histogram; + + std::unique_ptr toTH1F(const char* name = "eventsPerBc") const + { + constexpr int N = o2::constants::lhc::LHCMaxBunches; + auto h = std::make_unique(name, name, N, 0, N); + for (int i = 0; i < N; ++i) { + h->SetBinContent(i + 1, histogram[i]); + } + return h; + } + ClassDefNV(EventsPerBc, 1); }; } // namespace o2::ft0 diff --git a/DataFormats/Detectors/FIT/FV0/include/DataFormatsFV0/ChannelData.h b/DataFormats/Detectors/FIT/FV0/include/DataFormatsFV0/ChannelData.h index 054b336510c4f..29447dfa04202 100644 --- a/DataFormats/Detectors/FIT/FV0/include/DataFormatsFV0/ChannelData.h +++ b/DataFormats/Detectors/FIT/FV0/include/DataFormatsFV0/ChannelData.h @@ -76,8 +76,8 @@ struct ChannelData { void print() const; void printLog() const; [[nodiscard]] uint8_t getChannelID() const { return ChId; } - [[nodiscard]] uint16_t getTime() const { return CFDTime; } - [[nodiscard]] uint16_t getAmp() const { return QTCAmpl; } + [[nodiscard]] int16_t getTime() const { return CFDTime; } + [[nodiscard]] int16_t getAmp() const { return QTCAmpl; } bool operator==(ChannelData const& other) const { diff --git a/DataFormats/Detectors/GlobalTracking/src/RecoContainer.cxx b/DataFormats/Detectors/GlobalTracking/src/RecoContainer.cxx index dd206ffe3b70d..277466fb2e969 100644 --- a/DataFormats/Detectors/GlobalTracking/src/RecoContainer.cxx +++ b/DataFormats/Detectors/GlobalTracking/src/RecoContainer.cxx @@ -34,7 +34,7 @@ #include "ReconstructionDataFormats/TrackMCHMID.h" #include "DataFormatsITSMFT/TrkClusRef.h" #include "DataFormatsITSMFT/TopologyDictionary.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" // FIXME: ideally, the data formats definition should be independent of the framework // collectData is using the input of ProcessingContext to extract the first valid // header and the TF orbit from it diff --git a/DataFormats/Detectors/ITSMFT/ITS/CMakeLists.txt b/DataFormats/Detectors/ITSMFT/ITS/CMakeLists.txt index 5a353881e27ba..f05979d749fc0 100644 --- a/DataFormats/Detectors/ITSMFT/ITS/CMakeLists.txt +++ b/DataFormats/Detectors/ITSMFT/ITS/CMakeLists.txt @@ -11,8 +11,12 @@ o2_add_library(DataFormatsITS SOURCES src/TrackITS.cxx + src/TimeEstBC.cxx PUBLIC_LINK_LIBRARIES O2::ReconstructionDataFormats + O2::SimulationDataFormat O2::DataFormatsITSMFT) o2_target_root_dictionary(DataFormatsITS - HEADERS include/DataFormatsITS/TrackITS.h) + HEADERS include/DataFormatsITS/TrackITS.h + include/DataFormatsITS/Vertex.h + include/DataFormatsITS/TimeEstBC.h) diff --git a/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TimeEstBC.h b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TimeEstBC.h new file mode 100644 index 0000000000000..695d9aff42858 --- /dev/null +++ b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TimeEstBC.h @@ -0,0 +1,103 @@ +// Copyright 2019-2026 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. + +#ifndef O2_TRACKINGITS_TIMEESTBC_H_ +#define O2_TRACKINGITS_TIMEESTBC_H_ + +#include +#include +#include "CommonDataFormat/TimeStamp.h" +#include "GPUCommonRtypes.h" +#include "GPUCommonDef.h" +#include "GPUCommonMath.h" + +namespace o2::its +{ +// Time estimates are given in BC +// error needs to cover maximum 1 orbit +using TimeStampType = uint32_t; +using TimeStampErrorType = uint16_t; +// this is an symmetric time error [t0-tE, t0+tE] +using TimeStamp = o2::dataformats::TimeStampWithError; +// this is an asymmetric time interval [t0, t0+tE] used for internal calculations +class TimeEstBC : public o2::dataformats::TimeStampWithError +{ + using Base = o2::dataformats::TimeStampWithError; + + public: + GPUhdDefault() TimeEstBC() = default; + GPUhdi() TimeEstBC(TimeStampType t, TimeStampErrorType e) : Base(t, e) {} + + // convert to symmetric center+-half representation + GPUhdi() its::TimeStamp makeSymmetrical() const noexcept + { + const auto start = static_cast(this->getTimeStamp()); + const float half = (float)this->getTimeStampError() / 2.f; + return {start + half, half}; + } + + // check if timestamps overlap within their interval + GPUhdi() bool isCompatible(const TimeEstBC& o) const noexcept + { + return this->upper() > o.lower() && o.upper() > this->lower(); + } + + // check if this time interval is fully contained within o + GPUhdi() bool isContainedIn(const TimeEstBC& o) const noexcept + { + return this->lower() >= o.lower() && this->upper() <= o.upper(); + } + + GPUhdi() TimeEstBC& operator+=(const TimeEstBC& o) noexcept + { + add(o); + return *this; + } + + GPUhdi() TimeEstBC operator+(const TimeEstBC& o) const noexcept + { + TimeEstBC res = *this; + res += o; + return res; + } + + // upper bound of interval t0+tE + GPUhdi() TimeStampType upper() const noexcept + { + TimeStampType t = this->getTimeStamp(); + TimeStampType e = this->getTimeStampError(); + constexpr TimeStampType max = std::numeric_limits::max(); + return (t > (max - e)) ? max : t + e; + } + + // lower bound of interval t0 + GPUhdi() TimeStampType lower() const noexcept + { + return this->getTimeStamp(); + } + + private: + // intersect with the other timestamp + // this assumes already that both overlap + GPUhdi() void add(const TimeEstBC& o) noexcept + { + const TimeStampType lo = o2::gpu::CAMath::Max(this->lower(), o.lower()); + const TimeStampType hi = o2::gpu::CAMath::Min(this->upper(), o.upper()); + this->setTimeStamp(lo); + this->setTimeStampError(static_cast(hi - lo)); + } + + ClassDefNV(TimeEstBC, 1); +}; + +} // namespace o2::its + +#endif diff --git a/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h index 06d4fba51bd54..20fb7c63ebacd 100644 --- a/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h +++ b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/TrackITS.h @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -16,11 +16,12 @@ #ifndef ALICEO2_ITS_TRACKITS_H #define ALICEO2_ITS_TRACKITS_H -#include +#include #include "GPUCommonDef.h" #include "ReconstructionDataFormats/Track.h" #include "CommonDataFormat/RangeReference.h" +#include "DataFormatsITS/TimeEstBC.h" namespace o2 { @@ -35,8 +36,7 @@ namespace its class TrackITS : public o2::track::TrackParCov { enum UserBits { - kNextROF = 1 << 28, - kSharedClusters = 1 << 29 + kSharedClusters = 1 << 28 }; using Cluster = o2::itsmft::Cluster; @@ -93,6 +93,9 @@ class TrackITS : public o2::track::TrackParCov bool isBetter(const TrackITS& best, float maxChi2) const; + GPUhdi() auto& getTimeStamp() { return mTime; } + GPUhdi() const auto& getTimeStamp() const { return mTime; } + GPUhdi() o2::track::TrackParCov& getParamIn() { return *this; } GPUhdi() const o2::track::TrackParCov& getParamIn() const { return *this; } @@ -122,8 +125,6 @@ class TrackITS : public o2::track::TrackParCov } int getNFakeClusters() const; - void setNextROFbit(bool toggle = true) { mClusterSizes = toggle ? (mClusterSizes | kNextROF) : (mClusterSizes & ~kNextROF); } - bool hasHitInNextROF() const { return mClusterSizes & kNextROF; } void setSharedClusters(bool toggle = true) { mClusterSizes = toggle ? (mClusterSizes | kSharedClusters) : (mClusterSizes & ~kSharedClusters); } bool hasSharedClusters() const { return mClusterSizes & kSharedClusters; } @@ -157,9 +158,10 @@ class TrackITS : public o2::track::TrackParCov ClusRefs mClusRef; ///< references on clusters float mChi2 = 0.; ///< Chi2 for this track uint32_t mPattern = 0; ///< layers pattern - unsigned int mClusterSizes = 0u; + uint32_t mClusterSizes = 0u; ///< 4bit packed cluster sizes + TimeStamp mTime; ///< track time stamp with error in BC since start of TF, symmetrical - ClassDefNV(TrackITS, 6); + ClassDefNV(TrackITS, 7); }; class TrackITSExt : public TrackITS @@ -169,15 +171,13 @@ class TrackITSExt : public TrackITS static constexpr int MaxClusters = 16; /// Prepare for overlaps and new detector configurations using TrackITS::TrackITS; // inherit base constructors - GPUh() TrackITSExt(o2::track::TrackParCov&& parCov, short ncl, float chi2, - o2::track::TrackParCov&& outer, std::array cls) + GPUh() TrackITSExt(o2::track::TrackParCov&& parCov, short ncl, float chi2, o2::track::TrackParCov&& outer, std::array cls) : TrackITS(parCov, chi2, outer), mIndex{cls} { setNumberOfClusters(ncl); } - GPUh() TrackITSExt(o2::track::TrackParCov& parCov, short ncl, float chi2, std::uint32_t rof, - o2::track::TrackParCov& outer, std::array cls) + GPUh() TrackITSExt(o2::track::TrackParCov& parCov, short ncl, float chi2, std::uint32_t rof, o2::track::TrackParCov& outer, std::array cls) : TrackITS(parCov, chi2, outer), mIndex{cls} { setNumberOfClusters(ncl); @@ -192,7 +192,12 @@ class TrackITSExt : public TrackITS getClusterRefs().setEntries(ncl); } - GPUhdi() const int& getClusterIndex(int lr) const { return mIndex[lr]; } + GPUhdi() int getClusterIndex(int lr) const { return mIndex[lr]; } + + GPUh() int getFirstLayerClusterIndex() const + { + return getClusterIndex(getFirstClusterLayer()); + } GPUhdi() void setExternalClusterIndex(int layer, int idx, bool newCluster = false) { @@ -210,9 +215,36 @@ class TrackITSExt : public TrackITS return mIndex; } +#ifndef GPUCA_GPUCODE + // build order-independent hash via the external cluster idx (unique within a TF) for the selected layers + // cluster indices are either sorted inward or outward + size_t hash(uint16_t layerMask = 0xFFFF, bool inward = true) const noexcept + { + size_t h1 = 0, h2 = 0; + int from = (int)getLastClusterLayer(), to = -1, step = -1; + if (inward) { + from = (int)getFirstClusterLayer(); + to = MaxClusters; + step = 1; + } + // clusters are stored continously but they do not necesarrily correspond to the layers + for (int layer = from, slot{0}; layer != to; layer += step) { + if (hasHitOnLayer(layer)) { + int idx = mIndex[slot++]; + if (layerMask & (uint16_t(1) << layer)) { + size_t v = std::hash{}(idx); + h1 ^= v; + h2 += v * 0x9e3779b97f4a7c15ULL; // boost's hash_combine + } + } + } + return h1 ^ (h2 << 1); + } +#endif + private: std::array mIndex = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; ///< Indices of associated clusters - ClassDefNV(TrackITSExt, 2); + ClassDefNV(TrackITSExt, 3); }; } // namespace its } // namespace o2 diff --git a/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/Vertex.h b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/Vertex.h new file mode 100644 index 0000000000000..1e4ed03b753eb --- /dev/null +++ b/DataFormats/Detectors/ITSMFT/ITS/include/DataFormatsITS/Vertex.h @@ -0,0 +1,42 @@ +// Copyright 2019-2026 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. + +#ifndef O2_TRACKINGITS_VERTEX_H_ +#define O2_TRACKINGITS_VERTEX_H_ + +#include "GPUCommonDef.h" +#ifndef GPUCA_GPUCODE_DEVICE +#include +#endif +#include "ReconstructionDataFormats/Vertex.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "DataFormatsITS/TimeEstBC.h" + +namespace o2::its +{ +// NOTE: this uses the internal asymmetrical time reprenstation! +using Vertex = o2::dataformats::Vertex; +using VertexLabel = std::pair; +} // namespace o2::its + +#ifndef GPUCA_GPUCODE_DEVICE +/// Defining ITS Vertex explicitly as messageable +namespace o2::framework +{ +template +struct is_messageable; +template <> +struct is_messageable> : std::true_type { +}; +} // namespace o2::framework +#endif + +#endif diff --git a/DataFormats/Detectors/ITSMFT/ITS/src/DataFormatsITSLinkDef.h b/DataFormats/Detectors/ITSMFT/ITS/src/DataFormatsITSLinkDef.h index 91a71847148fb..a0d5b25c65b70 100644 --- a/DataFormats/Detectors/ITSMFT/ITS/src/DataFormatsITSLinkDef.h +++ b/DataFormats/Detectors/ITSMFT/ITS/src/DataFormatsITSLinkDef.h @@ -14,7 +14,14 @@ #pragma link off all globals; #pragma link off all classes; #pragma link off all functions; + #pragma link C++ class o2::its::TrackITS + ; #pragma link C++ class std::vector < o2::its::TrackITS> + ; +#pragma link C++ class o2::its::TimeEstBC + ; +#pragma link C++ class std::vector < o2::its::TimeEstBC> + ; + +#pragma link C++ class o2::dataformats::Vertex < o2::its::TimeEstBC> + ; +#pragma link C++ class std::vector < o2::dataformats::Vertex < o2::its::TimeEstBC>> + ; + #endif diff --git a/DataFormats/Detectors/ITSMFT/ITS/src/TimeEstBC.cxx b/DataFormats/Detectors/ITSMFT/ITS/src/TimeEstBC.cxx new file mode 100644 index 0000000000000..3af299cf74d25 --- /dev/null +++ b/DataFormats/Detectors/ITSMFT/ITS/src/TimeEstBC.cxx @@ -0,0 +1,13 @@ +// Copyright 2019-2026 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. + +#include "DataFormatsITS/TimeEstBC.h" +ClassImp(o2::its::TimeEstBC); \ No newline at end of file diff --git a/DataFormats/Detectors/ITSMFT/common/CMakeLists.txt b/DataFormats/Detectors/ITSMFT/common/CMakeLists.txt index 96d376526a1a4..a619f8ad0081d 100644 --- a/DataFormats/Detectors/ITSMFT/common/CMakeLists.txt +++ b/DataFormats/Detectors/ITSMFT/common/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# Copyright 2019-2026 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. # @@ -20,13 +20,18 @@ o2_add_library(DataFormatsITSMFT src/TopologyDictionary.cxx src/TimeDeadMap.cxx src/CTF.cxx + src/DPLAlpideParam.cxx + src/DPLAlpideParamInitializer.cxx PUBLIC_LINK_LIBRARIES O2::ITSMFTBase + O2::DetectorsCommonDataFormats O2::ReconstructionDataFormats + O2::CommonUtils Microsoft.GSL::GSL) o2_target_root_dictionary(DataFormatsITSMFT HEADERS include/DataFormatsITSMFT/ROFRecord.h include/DataFormatsITSMFT/Digit.h + include/DataFormatsITSMFT/DPLAlpideParam.h include/DataFormatsITSMFT/GBTCalibData.h include/DataFormatsITSMFT/NoiseMap.h include/DataFormatsITSMFT/TimeDeadMap.h diff --git a/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/CTF.h b/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/CTF.h index 314523aa878ba..0510b6df5225c 100644 --- a/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/CTF.h +++ b/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/CTF.h @@ -36,7 +36,9 @@ struct CTFHeader : public o2::ctf::CTFDictHeader { uint32_t nPatternBytes = 0; /// number of bytes for explict patterns uint32_t firstOrbit = 0; /// 1st orbit of TF uint16_t firstBC = 0; /// 1st BC of TF - ClassDefNV(CTFHeader, 2); + uint8_t maxStreams = 1; /// Number of streams per TF (== NLayers for staggered ITS/MFT readout, 1 for non-staggered one) + uint8_t streamID = 0; /// ID of the stream (0:maxStreams-1) + ClassDefNV(CTFHeader, 3); }; /// Compressed but not yet entropy-encoded clusters diff --git a/Detectors/ITSMFT/common/base/include/ITSMFTBase/DPLAlpideParam.h b/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/DPLAlpideParam.h similarity index 81% rename from Detectors/ITSMFT/common/base/include/ITSMFTBase/DPLAlpideParam.h rename to DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/DPLAlpideParam.h index de39bed299634..a06ba0745edbd 100644 --- a/Detectors/ITSMFT/common/base/include/ITSMFTBase/DPLAlpideParam.h +++ b/DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/DPLAlpideParam.h @@ -9,8 +9,8 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef ALICEO2_ITSMFTDPLBASEPARAM_H_ -#define ALICEO2_ITSMFTDPLBASEPARAM_H_ +#ifndef ALICEO2_ITSMFTALPIDEPARAM_H_ +#define ALICEO2_ITSMFTALPIDEPARAM_H_ #include "DetectorsCommonDataFormats/DetID.h" #include "CommonUtils/ConfigurableParam.h" @@ -46,24 +46,10 @@ struct DPLAlpideParam : public o2::conf::ConfigurableParamHelper + +namespace o2 +{ +namespace framework +{ +class ConfigParamSpec; +class ConfigContext; +} // namespace framework +namespace itsmft +{ + +struct DPLAlpideParamInitializer { + static constexpr char stagITSOpt[] = "enable-its-staggering"; + static constexpr char stagMFTOpt[] = "enable-mft-staggering"; + static constexpr bool stagDef = false; + + // DPL workflow options for staggering + static void addConfigOption(std::vector& opts); + static void addITSConfigOption(std::vector& opts); + static bool isITSStaggeringEnabled(o2::framework::ConfigContext const& cfgc); + static void addMFTConfigOption(std::vector& opts); + static bool isMFTStaggeringEnabled(o2::framework::ConfigContext const& cfgc); +}; + +} // namespace itsmft +} // namespace o2 + +#endif diff --git a/Detectors/ITSMFT/common/base/src/DPLAlpideParam.cxx b/DataFormats/Detectors/ITSMFT/common/src/DPLAlpideParam.cxx similarity index 82% rename from Detectors/ITSMFT/common/base/src/DPLAlpideParam.cxx rename to DataFormats/Detectors/ITSMFT/common/src/DPLAlpideParam.cxx index 1cb9bdf997d68..205f8a008a661 100644 --- a/Detectors/ITSMFT/common/base/src/DPLAlpideParam.cxx +++ b/DataFormats/Detectors/ITSMFT/common/src/DPLAlpideParam.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -9,15 +9,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" -namespace o2 -{ -namespace itsmft +namespace o2::itsmft { // this makes sure that the constructor of the parameters is statically called // so that these params are part of the parameter database static auto& sAlpideParamITS = o2::itsmft::DPLAlpideParam::Instance(); static auto& sAlpideParamMFT = o2::itsmft::DPLAlpideParam::Instance(); -} // namespace itsmft -} // namespace o2 +} // namespace o2::itsmft diff --git a/DataFormats/Detectors/ITSMFT/common/src/DPLAlpideParamInitializer.cxx b/DataFormats/Detectors/ITSMFT/common/src/DPLAlpideParamInitializer.cxx new file mode 100644 index 0000000000000..715ec5d90b813 --- /dev/null +++ b/DataFormats/Detectors/ITSMFT/common/src/DPLAlpideParamInitializer.cxx @@ -0,0 +1,46 @@ +// Copyright 2019-2026 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. + +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" +#include "Framework/ConfigParamsHelper.h" +#include "Framework/ConfigParamSpec.h" +#include "Framework/ConfigContext.h" + +namespace o2::itsmft +{ + +void DPLAlpideParamInitializer::addConfigOption(std::vector& opts) +{ + addITSConfigOption(opts); + addMFTConfigOption(opts); +} + +void DPLAlpideParamInitializer::addITSConfigOption(std::vector& opts) +{ + o2::framework::ConfigParamsHelper::addOptionIfMissing(opts, {stagITSOpt, o2::framework::VariantType::Bool, stagDef, {"enable per layer ITS in&out-put for staggered readout"}}); +} + +void DPLAlpideParamInitializer::addMFTConfigOption(std::vector& opts) +{ + o2::framework::ConfigParamsHelper::addOptionIfMissing(opts, {stagMFTOpt, o2::framework::VariantType::Bool, stagDef, {"enable per layer MFT in&out-put for staggered readout"}}); +} + +bool DPLAlpideParamInitializer::isITSStaggeringEnabled(const o2::framework::ConfigContext& cfgc) +{ + return cfgc.options().get(stagITSOpt); +} + +bool DPLAlpideParamInitializer::isMFTStaggeringEnabled(const o2::framework::ConfigContext& cfgc) +{ + return cfgc.options().get(stagMFTOpt); +} + +} // namespace o2::itsmft diff --git a/DataFormats/Detectors/ITSMFT/common/src/ITSMFTDataFormatsLinkDef.h b/DataFormats/Detectors/ITSMFT/common/src/ITSMFTDataFormatsLinkDef.h index fc67fdf028436..1b1918b46c9d4 100644 --- a/DataFormats/Detectors/ITSMFT/common/src/ITSMFTDataFormatsLinkDef.h +++ b/DataFormats/Detectors/ITSMFT/common/src/ITSMFTDataFormatsLinkDef.h @@ -15,6 +15,11 @@ #pragma link off all classes; #pragma link off all functions; +#pragma link C++ class o2::itsmft::DPLAlpideParam < o2::detectors::DetID::ITS> + ; +#pragma link C++ class o2::itsmft::DPLAlpideParam < o2::detectors::DetID::MFT> + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::itsmft::DPLAlpideParam < o2::detectors::DetID::ITS>> + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::itsmft::DPLAlpideParam < o2::detectors::DetID::MFT>> + ; + #pragma link C++ class o2::itsmft::Digit + ; #pragma link C++ class o2::itsmft::NoiseMap + ; #pragma link C++ class o2::itsmft::TimeDeadMap + ; diff --git a/DataFormats/Detectors/TPC/include/DataFormatsTPC/CMV.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/CMV.h new file mode 100644 index 0000000000000..8195b3e39c689 --- /dev/null +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/CMV.h @@ -0,0 +1,124 @@ +// 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 CMV.h +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief Common mode values data format definition + +/// The data is sent by the CRU as 256+16 bit words. The CMV data layout is as follows: +/// - 256-bit Header: [version:8][packetID:8][errorCode:8][magicWord:8][heartbeatOrbit:32][heartbeatBC:16][padding:176] +/// - 16-bit CMV value: [sign:1][I8F7:15] where bit 15 is the sign (1=positive, 0=negative) and the lower 15 bits are a fixed point I8F7 value (8 integer bits, 7 fractional bits) +/// Float conversion: sign ? (value & 0x7FFF) / 128.0 : -(value & 0x7FFF) / 128.0 + +#ifndef ALICEO2_DATAFORMATSTPC_CMV_H +#define ALICEO2_DATAFORMATSTPC_CMV_H + +#include +#include + +namespace o2::tpc::cmv +{ + +static constexpr uint32_t NTimeBinsPerPacket = 3564; ///< number of time bins (covering 8 heartbeats) +static constexpr uint32_t NPacketsPerTFPerCRU = 4; ///< 4 packets per timeframe +static constexpr uint32_t NTimeBinsPerTF = NTimeBinsPerPacket * NPacketsPerTFPerCRU; ///< maximum number of timebins per timeframe (14256) + +/// Data padding: NTimeBinsPerPacket * sizeof(Data) = 3564 * 2 = 7128 bytes +static constexpr uint32_t DataSizeBytes = NTimeBinsPerPacket * sizeof(uint16_t); ///< 7128 bytes +static constexpr uint32_t DataPaddingBytes = (32 - (DataSizeBytes % 32)) % 32; ///< 8 bytes + +/// Header definition of the CMVs +struct Header { + static constexpr uint8_t MagicWord = 0xDC; + union { + uint64_t word0 = 0; ///< bits 0 - 63 + struct { + uint8_t version : 8; ///< version + uint8_t packetID : 8; ///< packet id + uint8_t errorCode : 8; ///< errors + uint8_t magicWord : 8; ///< magic word + uint32_t heartbeatOrbit : 32; ///< first heart beat timing of the package + }; + }; + union { + uint64_t word1 = 0; ///< bits 64 - 127 + struct { + uint16_t heartbeatBC : 16; ///< first BC id of the package + uint16_t unused1 : 16; ///< reserved + uint32_t unused2 : 32; ///< reserved + }; + }; + union { + uint64_t word3 = 0; ///< bits 128 - 191 + struct { + uint64_t unused3 : 64; ///< reserved + }; + }; + union { + uint64_t word4 = 0; ///< bits 192 - 255 + struct { + uint64_t unused4 : 64; ///< reserved + }; + }; +}; + +/// CMV single data container +struct Data { + uint16_t cmv{0}; ///< 16-bit signed fixed point value: bit 15 = sign (1=positive, 0=negative), bits 14-0 = I8F7 magnitude + + uint16_t getCMV() const { return cmv; } ///< raw 16-bit integer representation + void setCMV(uint16_t value) { cmv = value; } ///< set raw 16-bit integer representation + + // Decode to float: sign-magnitude with 7 fractional bits, range ±255.992 + float getCMVFloat() const + { + const bool positive = (cmv >> 15) & 1; // bit 15: sign (1=positive, 0=negative) + const float magnitude = (cmv & 0x7FFF) / 128.f; // lower 15 bits, shift right by 7 (divide by 2^7) + return positive ? magnitude : -magnitude; + } + + // Encode from float: truncates magnitude to 15 bits, range ±255.992 + void setCMVFloat(float value) + { + const bool positive = (value >= 0.f); + const uint16_t magnitude = static_cast( + std::lround(std::abs(value) * 128.f)) & + 0x7FFF; + cmv = (positive ? 0x8000 : 0x0000) | magnitude; + } +}; + +/// CMV full data container: one packet carries NTimeBinsPerPacket CMV values followed by padding +/// Layout: Header (32 bytes) + Data[NTimeBinsPerPacket] (7128 bytes) + padding (8 bytes) = 7168 bytes total (224 * 32 = 7168) +/// The padding bytes at the end of the data array are rubbish/unused and must not be interpreted as CMV values +struct Container { + Header header; ///< CMV data header + Data data[NTimeBinsPerPacket]; ///< data values + uint8_t padding[DataPaddingBytes]{}; ///< trailing padding to align data to 32-byte boundary + + // Header and data accessors + const Header& getHeader() const { return header; } + Header& getHeader() { return header; } + + const Data* getData() const { return data; } + Data* getData() { return data; } + + // Per timebin CMV accessors + uint16_t getCMV(uint32_t timeBin) const { return data[timeBin].getCMV(); } + void setCMV(uint32_t timeBin, uint16_t value) { data[timeBin].setCMV(value); } + + float getCMVFloat(uint32_t timeBin) const { return data[timeBin].getCMVFloat(); } + void setCMVFloat(uint32_t timeBin, float value) { data[timeBin].setCMVFloat(value); } +}; + +} // namespace o2::tpc::cmv + +#endif diff --git a/DataFormats/Detectors/TPC/include/DataFormatsTPC/CalibdEdxCorrection.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/CalibdEdxCorrection.h index 024d6189593e9..f5088959edcf8 100644 --- a/DataFormats/Detectors/TPC/include/DataFormatsTPC/CalibdEdxCorrection.h +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/CalibdEdxCorrection.h @@ -42,14 +42,12 @@ class CalibdEdxCorrection static constexpr int FitSize = 288; ///< Number of fitted corrections static constexpr int ParamSize = 8; ///< Number of params per fit -#if !defined(GPUCA_ALIGPUCODE) +#if !defined(GPUCA_GPUCODE) CalibdEdxCorrection() { clear(); } CalibdEdxCorrection(std::string_view fileName) { loadFromFile(fileName); } -#else - CalibdEdxCorrection() = default; #endif ~CalibdEdxCorrection() = default; diff --git a/DataFormats/Detectors/TPC/include/DataFormatsTPC/Constants.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/Constants.h index 6f6201b7de8df..0ddf7281be866 100644 --- a/DataFormats/Detectors/TPC/include/DataFormatsTPC/Constants.h +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/Constants.h @@ -28,7 +28,7 @@ namespace constants constexpr int MAXSECTOR = 36; // the number of global pad rows -#if defined(GPUCA_STANDALONE) && !defined(GPUCA_O2_LIB) && !defined(GPUCA_TPC_GEOMETRY_O2) +#if defined(GPUCA_STANDALONE) && defined(GPUCA_RUN2) constexpr int MAXGLOBALPADROW = 159; // Number of pad rows in Run 2, used for GPU TPC tests with Run 2 data #else constexpr int MAXGLOBALPADROW = 152; // Correct number of pad rows in Run 3 diff --git a/DataFormats/Detectors/TPC/src/CalibdEdxCorrection.cxx b/DataFormats/Detectors/TPC/src/CalibdEdxCorrection.cxx index 152feacb41937..635b0e0817d6e 100644 --- a/DataFormats/Detectors/TPC/src/CalibdEdxCorrection.cxx +++ b/DataFormats/Detectors/TPC/src/CalibdEdxCorrection.cxx @@ -14,6 +14,7 @@ #include #include +#ifndef GPUCA_STANDALONE // o2 includes #include "Framework/Logger.h" #include "DataFormatsTPC/Defs.h" @@ -21,6 +22,7 @@ // root includes #include "TFile.h" +#endif using namespace o2::tpc; @@ -37,6 +39,8 @@ void CalibdEdxCorrection::clear() mDims = -1; } +#ifndef GPUCA_STANDALONE + void CalibdEdxCorrection::writeToFile(std::string_view fileName, std::string_view objName) const { std::unique_ptr file(TFile::Open(fileName.data(), "recreate")); @@ -181,3 +185,5 @@ void CalibdEdxCorrection::setUnity() } mDims = 0; } + +#endif // GPUCA_STANDALONE diff --git a/DataFormats/Detectors/TRD/include/DataFormatsTRD/TrackTRD.h b/DataFormats/Detectors/TRD/include/DataFormatsTRD/TrackTRD.h index 9dcbde05fb743..b2182636d4591 100644 --- a/DataFormats/Detectors/TRD/include/DataFormatsTRD/TrackTRD.h +++ b/DataFormats/Detectors/TRD/include/DataFormatsTRD/TrackTRD.h @@ -16,6 +16,7 @@ #define O2_DATAFORMATS_TRACK_TRD_H #include "GPUTRDTrack.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" namespace o2 { diff --git a/DataFormats/Detectors/TRD/src/Tracklet64.cxx b/DataFormats/Detectors/TRD/src/Tracklet64.cxx index d7b63cae45354..0458dad92e678 100644 --- a/DataFormats/Detectors/TRD/src/Tracklet64.cxx +++ b/DataFormats/Detectors/TRD/src/Tracklet64.cxx @@ -25,7 +25,6 @@ void Tracklet64::print() const HelperMethods::getSector(getDetector()), HelperMethods::getStack(getDetector()), HelperMethods::getLayer(getDetector()), getROB(), getMCM(), getPadRow(), getPadCol(), getPosition(), getSlope(), getPID(), getQ0(), getQ1(), getQ2(), getFormat()); } -#ifndef GPUCA_GPUCODE_DEVICE void Tracklet64::printStream(std::ostream& stream) const { stream << "Tracklet64 : 0x" << std::hex << getTrackletWord(); @@ -50,7 +49,5 @@ bool operator<(const Tracklet64& lhs, const Tracklet64& rhs) (lhs.getDetector() == rhs.getDetector() && lhs.getROB() == rhs.getROB() && lhs.getMCM() == rhs.getMCM() && lhs.getPadRow() == rhs.getPadRow() && lhs.getPadCol() < rhs.getPadCol()); } -#endif // GPUCA_GPUCODE_DEVICE - } // namespace trd } // namespace o2 diff --git a/DataFormats/Detectors/ZDC/include/DataFormatsZDC/FEEConfig.h b/DataFormats/Detectors/ZDC/include/DataFormatsZDC/FEEConfig.h deleted file mode 100644 index b084507b84519..0000000000000 --- a/DataFormats/Detectors/ZDC/include/DataFormatsZDC/FEEConfig.h +++ /dev/null @@ -1,52 +0,0 @@ -// 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. -// -// DataFormats/Detectors/ZDC/include/DataFormatsZDC/RawEventData.h - -#include "ZDCBase/Constants.h" - -#ifndef ALICEO2_ZDC_FEECONFIG_H -#define ALICEO2_ZDC_FEECONFIG_H - -/// \file FEEConfig.h -/// \brief ZDC FEE configuration -/// \author pietro.cortese@cern.ch - -namespace o2 -{ -namespace zdc -{ - -struct FEEFillingMap { - uint64_t filling[56]; -}; - -struct FEEConfigMap { - uint32_t address[5 * NChPerModule + 3] = {0, 1, 2, 3, - 4, 5, 6, 7, - 8, 9, 10, 11, - 12, 13, 14, 15, - 16, 17, 18, 19, - 76, 77, 78}; - uint64_t delay_sample[NChPerModule] = {6, 6, 6, 6}; // 4 bits - uint64_t delay_coarse[NChPerModule] = {200, 200, 200, 200}; // 8 bits - uint64_t threshold_level[NChPerModule] = {10, 10, 10, 10}; // 12 bits - uint64_t difference_delta[NChPerModule] = {4, 4, 4, 4}; // 3 bits - uint64_t masking_difference[NChPerModule] = {0x00ff00, 0x00ff00, 0x00ff00, 0x00ff00}; // 24 bits - uint64_t masking_alicet = 0x00000010; // 32 bits - uint64_t masking_autot = 0xf; // 4 bits - uint64_t masking_readout = 0xf; // 4 bits -}; - -} // namespace zdc -} // namespace o2 - -#endif diff --git a/DataFormats/Headers/include/Headers/DataHeader.h b/DataFormats/Headers/include/Headers/DataHeader.h index b44f41c5d3cb3..dbcdb8e0bba89 100644 --- a/DataFormats/Headers/include/Headers/DataHeader.h +++ b/DataFormats/Headers/include/Headers/DataHeader.h @@ -600,6 +600,7 @@ constexpr o2::header::DataDescription gDataDescriptionClusters{"CLUSTERS"}; constexpr o2::header::DataDescription gDataDescriptionTracks{"TRACKS"}; constexpr o2::header::DataDescription gDataDescriptionConfig{"CONFIGURATION"}; constexpr o2::header::DataDescription gDataDescriptionInfo{"INFORMATION"}; +constexpr o2::header::DataDescription gDataDescriptionEos{"EOS"}; constexpr o2::header::DataDescription gDataDescriptionROOTStreamers{"ROOT STREAMERS"}; constexpr o2::header::DataDescription gDataDescriptionDISTSTF{"DISTSUBTIMEFRAME"}; /// @} // end of doxygen group diff --git a/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertex.h b/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertex.h index 5343d26ec5ce5..62de31d335e80 100644 --- a/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertex.h +++ b/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertex.h @@ -42,7 +42,7 @@ class PrimaryVertex : public Vertex> float getZMAD() const { return mZMAD; } void setZMAD(float v) { mZMAD = v; } -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE void print() const; std::string asString() const; #endif @@ -56,7 +56,7 @@ class PrimaryVertex : public Vertex> ClassDefNV(PrimaryVertex, 2); }; -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE std::ostream& operator<<(std::ostream& os, const o2::dataformats::PrimaryVertex& v); #endif diff --git a/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertexExt.h b/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertexExt.h index bf47ed03f3b39..a228984f2ae5d 100644 --- a/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertexExt.h +++ b/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertexExt.h @@ -36,7 +36,7 @@ struct PrimaryVertexExt : public PrimaryVertex { int getNSrcA(int i) const { return nSrcA[i]; } int getNSrcAU(int i) const { return nSrcAU[i]; } -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE void print() const; std::string asString() const; #endif @@ -44,7 +44,7 @@ struct PrimaryVertexExt : public PrimaryVertex { ClassDefNV(PrimaryVertexExt, 6); }; -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE std::ostream& operator<<(std::ostream& os, const o2::dataformats::PrimaryVertexExt& v); #endif diff --git a/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrizationWithError.h b/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrizationWithError.h index 7f7e1e33144b1..436dc42cff749 100644 --- a/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrizationWithError.h +++ b/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrizationWithError.h @@ -83,7 +83,7 @@ class TrackParametrizationWithError : public TrackParametrization GPUd() void print() const; GPUd() void printHexadecimal(); -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE bool toFwdTrackParCov(TrackParCovFwd& t) const; std::string asString() const; std::string asStringHexadecimal(); diff --git a/DataFormats/Reconstruction/include/ReconstructionDataFormats/Vertex.h b/DataFormats/Reconstruction/include/ReconstructionDataFormats/Vertex.h index cb1c9d5d87c7f..588a23d25a000 100644 --- a/DataFormats/Reconstruction/include/ReconstructionDataFormats/Vertex.h +++ b/DataFormats/Reconstruction/include/ReconstructionDataFormats/Vertex.h @@ -152,7 +152,10 @@ class Vertex : public VertexBase std::string asString() const; #endif - GPUd() ushort getNContributors() const { return mNContributors; } + GPUd() ushort getNContributors() const + { + return mNContributors; + } GPUd() void setNContributors(ushort v) { mNContributors = v; } GPUd() void addContributor() { mNContributors++; } @@ -184,12 +187,26 @@ namespace detail { template concept Streamable = requires(std::ostream& os, const T& a) { - { os << a } -> std::same_as; + { + os << a + } + -> std::same_as; }; template concept HasFormattableTimeStamp = requires(const T& t) { - { fmt::format("{}", t.getTimeStamp()) } -> std::convertible_to; + { + fmt::format("{}", t.getTimeStamp()) + } + -> std::convertible_to; +}; + +template +concept HasFormattableTimeStampWithError = requires(const T& t) { + { + fmt::format("{}+-{}", t.getTimeStamp(), t.getTimeStampError()) + } + -> std::convertible_to; }; } // namespace detail @@ -201,6 +218,8 @@ inline std::string Vertex::asString() const std::ostringstream oss; oss << mTimeStamp; return oss.str(); + } else if constexpr (detail::HasFormattableTimeStampWithError) { + return fmt::format("{}+-{}", mTimeStamp.getTimeStamp(), mTimeStamp.getTimeStampError()); } else if constexpr (detail::HasFormattableTimeStamp) { return fmt::format("{}", mTimeStamp.getTimeStamp()); } else { diff --git a/DataFormats/Reconstruction/src/DCA.cxx b/DataFormats/Reconstruction/src/DCA.cxx index 9bb324c8df3a9..dd7c959add253 100644 --- a/DataFormats/Reconstruction/src/DCA.cxx +++ b/DataFormats/Reconstruction/src/DCA.cxx @@ -20,20 +20,16 @@ namespace o2 namespace dataformats { -#ifndef GPUCA_GPUCODE_DEVICE std::ostream& operator<<(std::ostream& os, const o2::dataformats::DCA& d) { // stream itself os << "DCA YZ {" << d.getY() << ", " << d.getZ() << "} Cov {" << d.getSigmaY2() << ", " << d.getSigmaYZ() << ", " << d.getSigmaZ2() << "}"; return os; } -#endif void DCA::print() const { -#ifndef GPUCA_GPUCODE_DEVICE std::cout << *this << '\n'; -#endif } } // namespace dataformats diff --git a/DataFormats/Reconstruction/src/Decay3Body.cxx b/DataFormats/Reconstruction/src/Decay3Body.cxx index aa071cea675cd..eb1b7ea1fd57d 100644 --- a/DataFormats/Reconstruction/src/Decay3Body.cxx +++ b/DataFormats/Reconstruction/src/Decay3Body.cxx @@ -16,15 +16,14 @@ using namespace o2::dataformats; Decay3Body::Decay3Body(const std::array& xyz, const std::array& pxyz, const std::array& covxyz, const Track& tr0, const Track& tr1, const Track& tr2, o2::track::PID pid) : mProngs{tr0, tr1, tr2} { - std::array cov{}, cov1{}, cov2{}; - tr0.getCovXYZPxPyPzGlo(cov); + std::array cov{}, cov0{}, cov1{}, cov2{}; + tr0.getCovXYZPxPyPzGlo(cov0); tr1.getCovXYZPxPyPzGlo(cov1); tr2.getCovXYZPxPyPzGlo(cov2); - for (int i = 0; i < 21; i++) { - cov[i] += cov1[i] + cov2[i]; - } + constexpr int MomInd[6] = {9, 13, 14, 18, 19, 20}; // cov matrix elements for momentum component for (int i = 0; i < 6; i++) { cov[i] = covxyz[i]; + cov[MomInd[i]] = cov0[MomInd[i]] + cov1[MomInd[i]] + cov2[MomInd[i]]; } this->set(xyz, pxyz, cov, tr0.getCharge() + tr1.getCharge() + tr2.getCharge(), true, pid); } diff --git a/DataFormats/Reconstruction/src/PrimaryVertex.cxx b/DataFormats/Reconstruction/src/PrimaryVertex.cxx index f1b1a8ff01181..f6f589fcb038a 100644 --- a/DataFormats/Reconstruction/src/PrimaryVertex.cxx +++ b/DataFormats/Reconstruction/src/PrimaryVertex.cxx @@ -19,8 +19,6 @@ namespace o2 namespace dataformats { -#ifndef GPUCA_ALIGPUCODE - std::string PrimaryVertex::asString() const { auto str = o2::utils::Str::concat_string(VertexBase::asString(), @@ -44,7 +42,5 @@ void PrimaryVertex::print() const std::cout << *this << std::endl; } -#endif - } // namespace dataformats } // namespace o2 diff --git a/DataFormats/Reconstruction/src/PrimaryVertexExt.cxx b/DataFormats/Reconstruction/src/PrimaryVertexExt.cxx index 6065f04a3bc1a..31f0eff191591 100644 --- a/DataFormats/Reconstruction/src/PrimaryVertexExt.cxx +++ b/DataFormats/Reconstruction/src/PrimaryVertexExt.cxx @@ -18,8 +18,6 @@ namespace o2 { namespace dataformats { - -#ifndef GPUCA_ALIGPUCODE using GTrackID = o2::dataformats::GlobalTrackID; std::string PrimaryVertexExt::asString() const @@ -45,7 +43,5 @@ void PrimaryVertexExt::print() const std::cout << *this << std::endl; } -#endif - } // namespace dataformats } // namespace o2 diff --git a/DataFormats/Reconstruction/src/Vertex.cxx b/DataFormats/Reconstruction/src/Vertex.cxx index 85145683ddd97..9294aeb655803 100644 --- a/DataFormats/Reconstruction/src/Vertex.cxx +++ b/DataFormats/Reconstruction/src/Vertex.cxx @@ -20,8 +20,6 @@ namespace o2 namespace dataformats { -#ifndef GPUCA_GPUCODE_DEVICE -#ifndef GPUCA_NO_FMT std::string VertexBase::asString() const { return fmt::format("Vtx {{{:+.4e},{:+.4e},{:+.4e}}} Cov.:{{{{{:.3e}..}},{{{:.3e},{:.3e}..}},{{{:.3e},{:.3e},{:.3e}}}}}", @@ -39,7 +37,6 @@ void VertexBase::print() const { std::cout << *this << std::endl; } -#endif bool VertexBase::operator==(const VertexBase& other) const { @@ -54,8 +51,6 @@ bool VertexBase::operator==(const VertexBase& other) const return true; } -#endif - template class o2::dataformats::Vertex>; template class o2::dataformats::Vertex>; diff --git a/DataFormats/common/include/CommonDataFormat/InteractionRecord.h b/DataFormats/common/include/CommonDataFormat/InteractionRecord.h index e99f338a16343..c22b24ad03aac 100644 --- a/DataFormats/common/include/CommonDataFormat/InteractionRecord.h +++ b/DataFormats/common/include/CommonDataFormat/InteractionRecord.h @@ -15,7 +15,7 @@ #define ALICEO2_INTERACTIONRECORD_H #include "GPUCommonRtypes.h" -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE #include #include #endif diff --git a/DataFormats/common/include/CommonDataFormat/TFIDInfo.h b/DataFormats/common/include/CommonDataFormat/TFIDInfo.h index 2a41a4e725781..9628b38b95fa3 100644 --- a/DataFormats/common/include/CommonDataFormat/TFIDInfo.h +++ b/DataFormats/common/include/CommonDataFormat/TFIDInfo.h @@ -29,6 +29,7 @@ struct TFIDInfo { // helper info to patch DataHeader uint32_t runNumber = -1U; uint32_t startTime = -1U; // same as timeslot uint64_t creation = -1UL; + bool discard = false; bool isDummy() { return tfCounter == -1U; } void fill(uint32_t firstTForbit_, uint32_t tfCounter_, uint32_t runNumber_, uint32_t startTime_, uint64_t creation_) @@ -38,9 +39,10 @@ struct TFIDInfo { // helper info to patch DataHeader runNumber = runNumber_; startTime = startTime_; creation = creation_; + discard = (firstTForbit < tfCounter) || firstTForbit == -1U || creation == -1; } - ClassDefNV(TFIDInfo, 2); + ClassDefNV(TFIDInfo, 3); }; } // namespace dataformats } // namespace o2 diff --git a/DataFormats/common/include/CommonDataFormat/TimeStamp.h b/DataFormats/common/include/CommonDataFormat/TimeStamp.h index 56a71414c6b86..709af221c28f8 100644 --- a/DataFormats/common/include/CommonDataFormat/TimeStamp.h +++ b/DataFormats/common/include/CommonDataFormat/TimeStamp.h @@ -27,10 +27,10 @@ class TimeStamp public: GPUhdDefault() TimeStamp() = default; GPUhdDefault() ~TimeStamp() = default; - GPUdi() TimeStamp(T time) { mTimeStamp = time; } + GPUhdi() TimeStamp(T time) { mTimeStamp = time; } GPUhdi() T getTimeStamp() const { return mTimeStamp; } - GPUdi() void setTimeStamp(T t) { mTimeStamp = t; } - GPUdi() bool operator==(const TimeStamp& t) const { return mTimeStamp == t.mTimeStamp; } + GPUhdi() void setTimeStamp(T t) { mTimeStamp = t; } + GPUhdi() bool operator==(const TimeStamp& t) const { return mTimeStamp == t.mTimeStamp; } private: T mTimeStamp = 0; @@ -41,11 +41,11 @@ template class TimeStampWithError : public TimeStamp { public: - GPUdDefault() TimeStampWithError() = default; - GPUd() TimeStampWithError(T t, E te) : TimeStamp(t), mTimeStampError(te) {} - GPUdi() E getTimeStampError() const { return mTimeStampError; } - GPUdi() E getTimeStampError2() const { return mTimeStampError * mTimeStampError; } - GPUdi() void setTimeStampError(E te) { mTimeStampError = te; } + GPUhdDefault() TimeStampWithError() = default; + GPUhd() TimeStampWithError(T t, E te) : TimeStamp(t), mTimeStampError(te) {} + GPUhdi() E getTimeStampError() const { return mTimeStampError; } + GPUhdi() E getTimeStampError2() const { return mTimeStampError * mTimeStampError; } + GPUhdi() void setTimeStampError(E te) { mTimeStampError = te; } private: E mTimeStampError = 0; diff --git a/DataFormats/common/src/InteractionRecord.cxx b/DataFormats/common/src/InteractionRecord.cxx index f15c8c8e85328..213ed600e1c57 100644 --- a/DataFormats/common/src/InteractionRecord.cxx +++ b/DataFormats/common/src/InteractionRecord.cxx @@ -15,8 +15,6 @@ namespace o2 { -#ifndef GPUCA_ALIGPUCODE - std::string InteractionRecord::asString() const { return isDummy() ? std::string{"NotSet"} : fmt::format("BCid: {:4d} Orbit: {:6d}", bc, orbit); @@ -49,6 +47,4 @@ void InteractionTimeRecord::print() const std::cout << (*this) << std::endl; } -#endif - } // namespace o2 diff --git a/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h b/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h index 23dc30119aa7a..ef259e5322bb8 100644 --- a/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h +++ b/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h @@ -524,6 +524,48 @@ inline void O2DatabasePDG::addALICEParticles(TDatabasePDG* db) db->AddParticle("f2_1525", "f2_1525", 1.525, kFALSE, 0.073, 0, "Resonance", ionCode); } + // when using hadronic rescattering in Pythia8 + ionCode = 9000221; + if (!db->GetParticle(ionCode)) { + db->AddParticle("f0_500", "f0_500", 0.500, kFALSE, 0.350, 0.0, "Resonance", ionCode); + } + ionCode = -100313; + if (!db->GetParticle(ionCode)) { + db->AddParticle("K*(1410)bar0", "K*(1410)bar0", 1.414, kFALSE, 0.232, 0, "Resonance", ionCode); + } + ionCode = 100313; + if (!db->GetParticle(ionCode)) { + db->AddParticle("K*(1410)0", "K*(1410)0", 1.414, kFALSE, 0.232, 0, "Resonance", ionCode); + } + ionCode = 100323; + if (!db->GetParticle(ionCode)) { + db->AddParticle("K*(1410)+", "K*(1410)+", 1.414, kFALSE, 0.232, +1, "Resonance", ionCode); + } + ionCode = -100323; + if (!db->GetParticle(ionCode)) { + db->AddParticle("K*(1410)-", "K*(1410)-", 1.414, kFALSE, 0.232, -1, "Resonance", ionCode); + } + ionCode = 100211; + if (!db->GetParticle(ionCode)) { + db->AddParticle("pi(1300)+", "pi(1300)+", 1.165, kFALSE, 0.400, +1, "Resonance", ionCode); + } + ionCode = -100211; + if (!db->GetParticle(ionCode)) { + db->AddParticle("pi(1300)-", "pi(1300)-", 1.165, kFALSE, 0.400, -1, "Resonance", ionCode); + } + ionCode = 202112; + if (!db->GetParticle(ionCode)) { + db->AddParticle("n(1440)0", "n(1440)0", 1.358, kFALSE, 0.350, 0, "Ion", ionCode); + } + ionCode = -202212; + if (!db->GetParticle(ionCode)) { + db->AddParticle("p(1440)bar-", "p(1440)bar-", 1.793, kFALSE, 0.350, -1, "Ion", ionCode); + } + ionCode = 202212; + if (!db->GetParticle(ionCode)) { + db->AddParticle("p(1440)+", "p(1440)+", 1.793, kFALSE, 0.350, 1, "Ion", ionCode); + } + // Xi-/+ (1820) ionCode = 123314; if (!db->GetParticle(ionCode)) { diff --git a/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h b/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h index 588cd575ee7f5..02f1b2582d74b 100644 --- a/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h +++ b/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h @@ -48,6 +48,14 @@ using DataRequest = o2::globaltracking::DataRequest; namespace o2::aodproducer { +/// helper struct to keep mapping of colIndex to MC labels and bunch crossing +struct MCColInfo { + int colIndex; + int sourceID; + int eventID; + int64_t bc; // global bunch crossing +}; + /// A structure or container to organize bunch crossing data of a timeframe /// and to facilitate fast lookup and search within bunch crossings. class BunchCrossings @@ -241,6 +249,7 @@ class AODProducerWorkflowDPL : public Task bool mThinTracks{false}; bool mPropTracks{false}; bool mPropMuons{false}; + bool mStoreAllMFTCov{false}; float mTrackQCKeepGlobalTracks{false}; float mTrackQCRetainOnlydEdx{false}; float mTrackQCFraction{0.00}; @@ -283,6 +292,7 @@ class AODProducerWorkflowDPL : public Task TString mAnchorPass{""}; TString mAnchorProd{""}; TString mRecoPass{""}; + std::string mAODParent{""}; // link to possible parent AOD file (MC embedding,...) TString mUser{"aliprod"}; // who created this AOD (aliprod, alidaq, individual users) TStopwatch mTimer; bool mEMCselectLeading{false}; @@ -538,8 +548,8 @@ class AODProducerWorkflowDPL : public Task template void addToTRDsExtra(const o2::globaltracking::RecoContainer& recoData, TRDsExtraCursorType& trdExtraCursor, const GIndex& trkIdx, int trkTableIdx); - template - void addToMFTTracksTable(mftTracksCursorType& mftTracksCursor, AmbigMFTTracksCursorType& ambigMFTTracksCursor, + template + void addToMFTTracksTable(mftTracksCursorType& mftTracksCursor, mftTracksCovCursorType& mftTracksCovCursor, AmbigMFTTracksCursorType& ambigMFTTracksCursor, GIndex trackID, const o2::globaltracking::RecoContainer& data, int collisionID, std::uint64_t collisionBC, const std::map& bcsMap); @@ -660,7 +670,7 @@ class AODProducerWorkflowDPL : public Task const gsl::span& primVer2TRefs, const gsl::span& GIndices, const o2::globaltracking::RecoContainer& data, - const std::vector>& mcColToEvSrc); + const std::vector& mcColToEvSrc); template void fillMCTrackLabelsTable(MCTrackLabelCursorType& mcTrackLabelCursor, diff --git a/Detectors/AOD/src/AODProducerWorkflowSpec.cxx b/Detectors/AOD/src/AODProducerWorkflowSpec.cxx index fcb419d6c441b..8365628f1644b 100644 --- a/Detectors/AOD/src/AODProducerWorkflowSpec.cxx +++ b/Detectors/AOD/src/AODProducerWorkflowSpec.cxx @@ -60,7 +60,7 @@ #include "GlobalTracking/MatchGlobalFwd.h" #include "MCHTracking/TrackExtrap.h" #include "MCHTracking/TrackParam.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsVertexing/PVertexerParams.h" #include "ReconstructionDataFormats/GlobalFwdTrack.h" #include "ReconstructionDataFormats/GlobalTrackID.h" @@ -507,8 +507,8 @@ void AODProducerWorkflowDPL::addToTRDsExtra(const o2::globaltracking::RecoContai trdExtraCursor(trkTableIdx, q0s, q1s, q2s, q0sCor, q1sCor, q2sCor, ttgls, tphis); } -template -void AODProducerWorkflowDPL::addToMFTTracksTable(mftTracksCursorType& mftTracksCursor, AmbigMFTTracksCursorType& ambigMFTTracksCursor, +template +void AODProducerWorkflowDPL::addToMFTTracksTable(mftTracksCursorType& mftTracksCursor, mftTracksCovCursorType& mftTracksCovCursor, AmbigMFTTracksCursorType& ambigMFTTracksCursor, GIndex trackID, const o2::globaltracking::RecoContainer& data, int collisionID, std::uint64_t collisionBC, const std::map& bcsMap) { @@ -543,6 +543,30 @@ void AODProducerWorkflowDPL::addToMFTTracksTable(mftTracksCursorType& mftTracksC truncateFloatFraction(track.getTrackChi2(), mTrackChi2), truncateFloatFraction(trackTime, mTrackTime), truncateFloatFraction(trackTimeRes, mTrackTimeError)); + if (mStoreAllMFTCov) { + float sX = TMath::Sqrt(track.getSigma2X()); + float sY = TMath::Sqrt(track.getSigma2Y()); + float sPhi = TMath::Sqrt(track.getSigma2Phi()); + float sTgl = TMath::Sqrt(track.getSigma2Tanl()); + float sQ2Pt = TMath::Sqrt(track.getSigma2InvQPt()); + + mftTracksCovCursor(mTableTrMFTID, + truncateFloatFraction(sX, mTrackCovDiag), + truncateFloatFraction(sY, mTrackCovDiag), + truncateFloatFraction(sPhi, mTrackCovDiag), + truncateFloatFraction(sTgl, mTrackCovDiag), + truncateFloatFraction(sQ2Pt, mTrackCovDiag), + (Char_t)(128. * track.getCovariances()(0, 1) / (sX * sY)), + (Char_t)(128. * track.getCovariances()(0, 2) / (sPhi * sX)), + (Char_t)(128. * track.getCovariances()(1, 2) / (sPhi * sY)), + (Char_t)(128. * track.getCovariances()(0, 3) / (sTgl * sX)), + (Char_t)(128. * track.getCovariances()(1, 3) / (sTgl * sY)), + (Char_t)(128. * track.getCovariances()(2, 3) / (sTgl * sPhi)), + (Char_t)(128. * track.getCovariances()(0, 4) / (sQ2Pt * sX)), + (Char_t)(128. * track.getCovariances()(1, 4) / (sQ2Pt * sY)), + (Char_t)(128. * track.getCovariances()(2, 4) / (sQ2Pt * sPhi)), + (Char_t)(128. * track.getCovariances()(3, 4) / (sQ2Pt * sTgl))); + } if (needBCSlice) { ambigMFTTracksCursor(mTableTrMFTID, bcSlice); } @@ -579,10 +603,13 @@ void AODProducerWorkflowDPL::fillTrackTablesPerCollision(int collisionID, int nToReserve = end - start; // + last index for a given table if (src == GIndex::Source::MFT) { mftTracksCursor.reserve(nToReserve + mftTracksCursor.lastIndex()); + if (mStoreAllMFTCov) { + mftTracksCovCursor.reserve(nToReserve + mftTracksCovCursor.lastIndex()); + } } else if (src == GIndex::Source::MCH || src == GIndex::Source::MFTMCH || src == GIndex::Source::MCHMID) { fwdTracksCursor.reserve(nToReserve + fwdTracksCursor.lastIndex()); fwdTracksCovCursor.reserve(nToReserve + fwdTracksCovCursor.lastIndex()); - if (src == GIndex::Source::MFTMCH) { + if (!mStoreAllMFTCov && src == GIndex::Source::MFTMCH) { mftTracksCovCursor.reserve(nToReserve + mftTracksCovCursor.lastIndex()); } } else { @@ -597,7 +624,7 @@ void AODProducerWorkflowDPL::fillTrackTablesPerCollision(int collisionID, if (trackIndex.isAmbiguous() && mGIDToTableMFTID.find(trackIndex) != mGIDToTableMFTID.end()) { // was it already stored ? continue; } - addToMFTTracksTable(mftTracksCursor, ambigMFTTracksCursor, trackIndex, data, collisionID, collisionBC, bcsMap); + addToMFTTracksTable(mftTracksCursor, mftTracksCovCursor, ambigMFTTracksCursor, trackIndex, data, collisionID, collisionBC, bcsMap); mGIDToTableMFTID.emplace(trackIndex, mTableTrMFTID); mTableTrMFTID++; } else if (src == GIndex::Source::MCH || src == GIndex::Source::MFTMCH || src == GIndex::Source::MCHMID) { // FwdTracks tracks are treated separately since they are stored in a different table @@ -929,22 +956,24 @@ void AODProducerWorkflowDPL::addToFwdTracksTable(FwdTracksCursorType& fwdTracksC float sX = TMath::Sqrt(mfttrack.getSigma2X()), sY = TMath::Sqrt(mfttrack.getSigma2Y()), sPhi = TMath::Sqrt(mfttrack.getSigma2Phi()), sTgl = TMath::Sqrt(mfttrack.getSigma2Tanl()), sQ2Pt = TMath::Sqrt(mfttrack.getSigma2InvQPt()); - mftTracksCovCursor(fwdInfo.matchmfttrackid, - truncateFloatFraction(sX, mTrackCovDiag), - truncateFloatFraction(sY, mTrackCovDiag), - truncateFloatFraction(sPhi, mTrackCovDiag), - truncateFloatFraction(sTgl, mTrackCovDiag), - truncateFloatFraction(sQ2Pt, mTrackCovDiag), - (Char_t)(128. * mfttrack.getCovariances()(0, 1) / (sX * sY)), - (Char_t)(128. * mfttrack.getCovariances()(0, 2) / (sPhi * sX)), - (Char_t)(128. * mfttrack.getCovariances()(1, 2) / (sPhi * sY)), - (Char_t)(128. * mfttrack.getCovariances()(0, 3) / (sTgl * sX)), - (Char_t)(128. * mfttrack.getCovariances()(1, 3) / (sTgl * sY)), - (Char_t)(128. * mfttrack.getCovariances()(2, 3) / (sTgl * sPhi)), - (Char_t)(128. * mfttrack.getCovariances()(0, 4) / (sQ2Pt * sX)), - (Char_t)(128. * mfttrack.getCovariances()(1, 4) / (sQ2Pt * sY)), - (Char_t)(128. * mfttrack.getCovariances()(2, 4) / (sQ2Pt * sPhi)), - (Char_t)(128. * mfttrack.getCovariances()(3, 4) / (sQ2Pt * sTgl))); + if (!mStoreAllMFTCov) { + mftTracksCovCursor(fwdInfo.matchmfttrackid, + truncateFloatFraction(sX, mTrackCovDiag), + truncateFloatFraction(sY, mTrackCovDiag), + truncateFloatFraction(sPhi, mTrackCovDiag), + truncateFloatFraction(sTgl, mTrackCovDiag), + truncateFloatFraction(sQ2Pt, mTrackCovDiag), + (Char_t)(128. * mfttrack.getCovariances()(0, 1) / (sX * sY)), + (Char_t)(128. * mfttrack.getCovariances()(0, 2) / (sPhi * sX)), + (Char_t)(128. * mfttrack.getCovariances()(1, 2) / (sPhi * sY)), + (Char_t)(128. * mfttrack.getCovariances()(0, 3) / (sTgl * sX)), + (Char_t)(128. * mfttrack.getCovariances()(1, 3) / (sTgl * sY)), + (Char_t)(128. * mfttrack.getCovariances()(2, 3) / (sTgl * sPhi)), + (Char_t)(128. * mfttrack.getCovariances()(0, 4) / (sQ2Pt * sX)), + (Char_t)(128. * mfttrack.getCovariances()(1, 4) / (sQ2Pt * sY)), + (Char_t)(128. * mfttrack.getCovariances()(2, 4) / (sQ2Pt * sPhi)), + (Char_t)(128. * mfttrack.getCovariances()(3, 4) / (sQ2Pt * sTgl))); + } } std::uint64_t bcOfTimeRef; @@ -1084,13 +1113,13 @@ void AODProducerWorkflowDPL::fillMCParticlesTable(o2::steer::MCKinematicsReader& const gsl::span& primVer2TRefs, const gsl::span& GIndices, const o2::globaltracking::RecoContainer& data, - const std::vector>& mcColToEvSrc) + const std::vector& mcColToEvSrc) { int NSources = 0; int NEvents = 0; for (auto& p : mcColToEvSrc) { - NSources = std::max(p[1], NSources); - NEvents = std::max(p[2], NEvents); + NSources = std::max(p.sourceID, NSources); + NEvents = std::max(p.eventID, NEvents); } NSources++; // 0 - indexed NEvents++; @@ -1166,9 +1195,9 @@ void AODProducerWorkflowDPL::fillMCParticlesTable(o2::steer::MCKinematicsReader& size_t offset = 0; for (auto& colInfo : mcColToEvSrc) { // loop over " <-> combined MC col. ID" key pairs - int event = colInfo[2]; - int source = colInfo[1]; - int mcColId = colInfo[0]; + int event = colInfo.eventID; + int source = colInfo.sourceID; + int mcColId = colInfo.colIndex; std::vector const& mcParticles = mcReader.getTracks(source, event); LOG(debug) << "Event=" << event << " source=" << source << " collision=" << mcColId; auto& preselect = mToStore[source][event]; @@ -1818,6 +1847,7 @@ void AODProducerWorkflowDPL::init(InitContext& ic) mAnchorProd = ic.options().get("anchor-prod"); mUser = ic.options().get("created-by"); mRecoPass = ic.options().get("reco-pass"); + mAODParent = ic.options().get("aod-parent"); mTFNumber = ic.options().get("aod-timeframe-id"); mRecoOnly = ic.options().get("reco-mctracks-only"); mTruncate = ic.options().get("enable-truncation"); @@ -1829,6 +1859,7 @@ void AODProducerWorkflowDPL::init(InitContext& ic) mPropTracks = ic.options().get("propagate-tracks"); mMaxPropXiu = ic.options().get("propagate-tracks-max-xiu"); mPropMuons = ic.options().get("propagate-muons"); + mStoreAllMFTCov = ic.options().get("store-all-mft-cov"); if (auto s = ic.options().get("with-streamers"); !s.empty()) { mStreamerFlags.set(s); if (mStreamerFlags) { @@ -2178,10 +2209,8 @@ void AODProducerWorkflowDPL::run(ProcessingContext& pc) zdcChannelsT); } - // keep track event/source id for each mc-collision - // using map and not unordered_map to ensure - // correct ordering when iterating over container elements - std::vector> mcColToEvSrc; + // keep track of event_id + source_id + bc for each mc-collision + std::vector mcColToEvSrc; if (mUseMC) { using namespace o2::aodmchelpers; @@ -2254,13 +2283,13 @@ void AODProducerWorkflowDPL::run(ProcessingContext& pc) 0, sourceID); } - mcColToEvSrc.emplace_back(std::vector{iCol, sourceID, eventID}); // point background and injected signal events to one collision + mcColToEvSrc.emplace_back(MCColInfo{iCol, sourceID, eventID, globalBC}); // point background and injected signal events to one collision } } } std::sort(mcColToEvSrc.begin(), mcColToEvSrc.end(), - [](const std::vector& left, const std::vector& right) { return (left[0] < right[0]); }); + [](const MCColInfo& left, const MCColInfo& right) { return (left.colIndex < right.colIndex); }); // vector of FDD amplitudes int16_t aFDDAmplitudesA[8] = {0u}, aFDDAmplitudesC[8] = {0u}; @@ -2359,16 +2388,46 @@ void AODProducerWorkflowDPL::run(ProcessingContext& pc) } if (mUseMC) { - // filling MC collision labels + // Fill MC collision labels using information from the primary vertexer. mcColLabelsCursor.reserve(primVerLabels.size()); - for (auto& label : primVerLabels) { - auto it = std::find_if(mcColToEvSrc.begin(), mcColToEvSrc.end(), - [&label](const auto& mcColInfo) { return mcColInfo[1] == label.getSourceID() && mcColInfo[2] == label.getEventID(); }); + for (size_t ivert = 0; ivert < primVerLabels.size(); ++ivert) { + const auto& label = primVerLabels[ivert]; + + // Collect all MC collision candidates matching this (sourceID, eventID) label. + // In the non-embedding case there is exactly one candidate. In the embedding + // case the same (sourceID, eventID) pair can appear in multiple collisions, + // so we need to disambiguate. + std::vector> candidates; // (colIndex, bc) + for (const auto& colInfo : mcColToEvSrc) { + if (colInfo.sourceID == label.getSourceID() && + colInfo.eventID == label.getEventID()) { + candidates.emplace_back(colInfo.colIndex, colInfo.bc); + } + } + int32_t mcCollisionID = -1; - if (it != mcColToEvSrc.end()) { - mcCollisionID = it->at(0); + if (candidates.size() == 1) { + mcCollisionID = candidates[0].first; + } else if (candidates.size() > 1) { + // Disambiguate by BC: pick the MCCollision whose BC is closest + // to the reconstructed collision's BC. + // TODO: Consider a complementary strategy using the MC labels of tracks + // associated to the primary vertex, and/or by allowing the primary + // vertexer to return multiple MC collision labels per vertex. + const auto& timeStamp = primVertices[ivert].getTimeStamp(); + const double interactionTime = timeStamp.getTimeStamp() * 1E3; // us -> ns + const auto recoBC = relativeTime_to_GlobalBC(interactionTime); + int64_t bestDiff = std::numeric_limits::max(); + for (const auto& [colIndex, bc] : candidates) { + const auto bcDiff = std::abs(static_cast(bc) - static_cast(recoBC)); + if (bcDiff < bestDiff) { + bestDiff = bcDiff; + mcCollisionID = colIndex; + } + } } - uint16_t mcMask = 0; // todo: set mask using normalized weights? + + uint16_t mcMask = 0; // TODO: set mask using normalised weights mcColLabelsCursor(mcCollisionID, mcMask); } } @@ -2615,7 +2674,7 @@ void AODProducerWorkflowDPL::run(ProcessingContext& pc) pc.outputs().snapshot(Output{"AMD", "AODMetadataVals", 0}, mMetaDataVals); pc.outputs().snapshot(Output{"TFN", "TFNumber", 0}, tfNumber); - pc.outputs().snapshot(Output{"TFF", "TFFilename", 0}, ""); + pc.outputs().snapshot(Output{"TFF", "TFFilename", 0}, mAODParent); mTimer.Stop(); } @@ -3485,6 +3544,7 @@ DataProcessorSpec getAODProducerWorkflowSpec(GID::mask_t src, bool enableSV, boo ConfigParamSpec{"anchor-pass", VariantType::String, "", {"AnchorPassName"}}, ConfigParamSpec{"anchor-prod", VariantType::String, "", {"AnchorProduction"}}, ConfigParamSpec{"reco-pass", VariantType::String, "", {"RecoPassName"}}, + ConfigParamSpec{"aod-parent", VariantType::String, "", {"Parent AOD file name (if any)"}}, ConfigParamSpec{"created-by", VariantType::String, "", {"Who created this AO2D"}}, ConfigParamSpec{"nthreads", VariantType::Int, std::max(1, int(std::thread::hardware_concurrency() / 2)), {"Number of threads"}}, ConfigParamSpec{"reco-mctracks-only", VariantType::Int, 0, {"Store only reconstructed MC tracks and their mothers/daughters. 0 -- off, != 0 -- on"}}, @@ -3494,6 +3554,7 @@ DataProcessorSpec getAODProducerWorkflowSpec(GID::mask_t src, bool enableSV, boo ConfigParamSpec{"propagate-tracks-max-xiu", VariantType::Float, 5.0f, {"Propagate tracks to IP if X_IU smaller than this value (and if propagate tracks enabled)"}}, ConfigParamSpec{"hepmc-update", VariantType::String, "always", {"When to update HepMC Aux tables: always - force update, never - never update, all - if all keys are present, any - when any key is present (not valid yet)"}}, ConfigParamSpec{"propagate-muons", VariantType::Bool, false, {"Propagate muons to IP"}}, + ConfigParamSpec{"store-all-mft-cov", VariantType::Bool, false, {"Store covariance matrices for all MFT tracks"}}, ConfigParamSpec{"thin-tracks", VariantType::Bool, false, {"Produce thinned track tables"}}, ConfigParamSpec{"trackqc-keepglobaltracks", VariantType::Bool, false, {"Always keep TrackQA for global tracks"}}, ConfigParamSpec{"trackqc-retainonlydedx", VariantType::Bool, false, {"Keep only dEdx information, zero out everything else"}}, diff --git a/Detectors/AOD/src/aod-producer-workflow.cxx b/Detectors/AOD/src/aod-producer-workflow.cxx index f6bfaae170bbd..d75694f3bd512 100644 --- a/Detectors/AOD/src/aod-producer-workflow.cxx +++ b/Detectors/AOD/src/aod-producer-workflow.cxx @@ -18,6 +18,7 @@ #include "DetectorsRaw/HBFUtilsInitializer.h" #include "Framework/CallbacksPolicy.h" #include "DetectorsBase/DPLWorkflowUtils.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -43,6 +44,7 @@ void customize(std::vector& workflowOptions) {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}, {"combine-source-devices", o2::framework::VariantType::Bool, false, {"merge DPL source devices"}}, {"ctpconfig-run-independent", o2::framework::VariantType::Bool, false, {"Use CTP config w/o runNumber tag"}}}; + o2::itsmft::DPLAlpideParamInitializer::addConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } diff --git a/Detectors/Align/Workflow/include/AlignmentWorkflow/BarrelAlignmentSpec.h b/Detectors/Align/Workflow/include/AlignmentWorkflow/BarrelAlignmentSpec.h index 197ace2bd9d20..9793b8609874f 100644 --- a/Detectors/Align/Workflow/include/AlignmentWorkflow/BarrelAlignmentSpec.h +++ b/Detectors/Align/Workflow/include/AlignmentWorkflow/BarrelAlignmentSpec.h @@ -21,17 +21,13 @@ using namespace o2::framework; namespace o2 { -namespace tpc -{ -struct CorrectionMapsLoaderGloOpts; -} 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, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts); + o2::detectors::DetID::mask_t dets, o2::detectors::DetID::mask_t skipDetClusters, bool enableCosmic, int postproc, bool useMC); } // namespace align } // namespace o2 diff --git a/Detectors/Align/Workflow/src/BarrelAlignmentSpec.cxx b/Detectors/Align/Workflow/src/BarrelAlignmentSpec.cxx index d4ab53c8181ce..3e42c19f535f8 100644 --- a/Detectors/Align/Workflow/src/BarrelAlignmentSpec.cxx +++ b/Detectors/Align/Workflow/src/BarrelAlignmentSpec.cxx @@ -36,7 +36,7 @@ #include "TRDBase/TrackletTransformer.h" #include "CommonUtils/TreeStreamRedirector.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCFastTransformPOD.h" #include "GPUO2ExternalUser.h" #include "GPUO2InterfaceUtils.h" #include "GPUParam.h" @@ -64,7 +64,7 @@ #include "DataFormatsTPC/ClusterNative.h" #include "DataFormatsTPC/WorkflowHelper.h" #include "ITSBase/GeometryTGeo.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" */ using namespace o2::framework; @@ -86,14 +86,9 @@ class BarrelAlignmentSpec : public Task CheckConstaints = 0x1 << 1, GenPedeFiles = 0x1 << 2, LabelPedeResults = 0x1 << 3 }; - BarrelAlignmentSpec(GTrackID::mask_t srcMP, std::shared_ptr dr, std::shared_ptr ggrec, const o2::tpc::CorrectionMapsLoaderGloOpts& tpcOpt, + BarrelAlignmentSpec(GTrackID::mask_t srcMP, std::shared_ptr dr, std::shared_ptr ggrec, DetID::mask_t detmask, bool cosmic, int postprocess, bool useMC, bool loadTPCCalib) - : mDataRequest(dr), mGRPGeomRequest(ggrec), mMPsrc{srcMP}, mDetMask{detmask}, mCosmic(cosmic), mPostProcessing(postprocess), mUseMC(useMC), mLoadTPCCalib(loadTPCCalib) - { - mTPCCorrMapsLoader.setLumiScaleType(tpcOpt.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(tpcOpt.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(tpcOpt.checkCTPIDCconsistency); - } + : mDataRequest(dr), mGRPGeomRequest(ggrec), mMPsrc{srcMP}, mDetMask{detmask}, mCosmic(cosmic), mPostProcessing(postprocess), mUseMC(useMC), mLoadTPCCalib(loadTPCCalib) {} ~BarrelAlignmentSpec() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -122,7 +117,8 @@ class BarrelAlignmentSpec : public Task std::unique_ptr mTPCParam; o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps{}; // TStopwatch mTimer; @@ -184,9 +180,6 @@ void BarrelAlignmentSpec::init(InitContext& ic) } mIgnoreCCDBAlignment = ic.options().get("ignore-ccdb-alignment"); if (!mPostProcessing) { - if (mLoadTPCCalib) { - mTPCCorrMapsLoader.init(ic); - } if (GTrackID::includesDet(DetID::TRD, mMPsrc)) { mTRDTransformer.reset(new o2::trd::TrackletTransformer); if (ic.options().get("apply-xor")) { @@ -272,13 +265,10 @@ void BarrelAlignmentSpec::updateTimeDependentParams(ProcessingContext& pc) } mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); - bool updateMaps = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - mController->setTPCCorrMaps(&mTPCCorrMapsLoader); + + auto const& raw = pc.inputs().get("corrMap"); + mTPCCorrMaps = &o2::gpu::TPCFastTransformPOD::get(raw); + mController->setTPCCorrMaps(mTPCCorrMaps); if (mTPCVDriftHelper.isUpdated()) { LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, @@ -286,10 +276,6 @@ void BarrelAlignmentSpec::updateTimeDependentParams(ProcessingContext& pc) mTPCVDriftHelper.getSourceName()); mController->setTPCVDrift(mTPCVDriftHelper.getVDriftObject()); mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); } } } @@ -314,9 +300,6 @@ void BarrelAlignmentSpec::finaliseCCDB(o2::framework::ConcreteDataMatcher& match return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } } void BarrelAlignmentSpec::run(ProcessingContext& pc) @@ -374,7 +357,7 @@ 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, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts) +DataProcessorSpec getBarrelAlignmentSpec(GTrackID::mask_t srcMP, GTrackID::mask_t src, DetID::mask_t dets, DetID::mask_t skipDetClusters, bool enableCosmic, int postprocess, bool useMC) { std::vector outputs; auto dataRequest = std::make_shared(); @@ -399,7 +382,7 @@ DataProcessorSpec getBarrelAlignmentSpec(GTrackID::mask_t srcMP, GTrackID::mask_ } if (src[DetID::TPC] && !skipDetClusters[DetID::TPC]) { o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); loadTPCCalib = true; } } @@ -417,7 +400,7 @@ DataProcessorSpec getBarrelAlignmentSpec(GTrackID::mask_t srcMP, GTrackID::mask_ "barrel-alignment", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(srcMP, dataRequest, ccdbRequest, sclOpts, dets, enableCosmic, postprocess, useMC, loadTPCCalib)}, + AlgorithmSpec{adaptFromTask(srcMP, dataRequest, ccdbRequest, dets, enableCosmic, postprocess, useMC, loadTPCCalib)}, opts}; } diff --git a/Detectors/Align/Workflow/src/barrel-alignment-workflow.cxx b/Detectors/Align/Workflow/src/barrel-alignment-workflow.cxx index 8df479ba39260..07224702b1be1 100644 --- a/Detectors/Align/Workflow/src/barrel-alignment-workflow.cxx +++ b/Detectors/Align/Workflow/src/barrel-alignment-workflow.cxx @@ -20,13 +20,14 @@ #include "TPCReaderWorkflow/ClusterReaderSpec.h" #include "TPCWorkflow/ClusterSharingMapSpec.h" #include "TPCWorkflow/TPCScalerSpec.h" -#include "TPCCalibration/CorrectionMapsLoader.h" #include "TOFWorkflowIO/ClusterReaderSpec.h" #include "TOFWorkflowIO/TOFMatchedReaderSpec.h" #include "TOFWorkflowIO/ClusterReaderSpec.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DetectorsCommonDataFormats/DetID.h" #include "GlobalTrackingWorkflowReaders/TrackTPCITSReaderSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" +#include "TPCCalibration/CorrectionMapsOptions.h" #include "Algorithm/RangeTokenizer.h" #include "DetectorsRaw/HBFUtilsInitializer.h" @@ -59,7 +60,8 @@ void customize(std::vector& workflowOptions) {"enable-cosmic", VariantType::Bool, false, {"enable cosmic tracks)"}}, {"postprocessing", VariantType::Int, 0, {"postprocessing bits: 1 - extract alignment objects, 2 - check constraints, 4 - print mpParams/Constraints, 8 - relabel pede results"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -102,7 +104,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (dets[DetID::TPC]) { loadTPCClusters = loadTPCTracks = true; } - auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); if (!postprocess) { // this part is needed only if the data should be read if (GID::includesDet(DetID::ITS, src)) { src |= GID::getSourceMask(GID::ITS); @@ -148,11 +150,11 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) o2::conf::ConfigurableParam::writeINI("o2_barrel_alignment_configuration.ini"); } - if (sclOpt.needTPCScalersWorkflow() && !configcontext.options().get("disable-root-input")) { - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == 2, sclOpt.enableMShapeCorrection)); + if (!configcontext.options().get("disable-root-input")) { + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpt.enableMShapeCorrection, sclOpt)); } - specs.emplace_back(o2::align::getBarrelAlignmentSpec(srcMP, src, dets, skipDetClusters, enableCosmic, postprocess, useMC, sclOpt)); + specs.emplace_back(o2::align::getBarrelAlignmentSpec(srcMP, src, dets, skipDetClusters, enableCosmic, postprocess, useMC)); // RS FIXME: check which clusters are really needed if (!postprocess) { GID::mask_t dummy; diff --git a/Detectors/Align/include/Align/Controller.h b/Detectors/Align/include/Align/Controller.h index 90abf2025d1c3..a9c4eac734535 100644 --- a/Detectors/Align/include/Align/Controller.h +++ b/Detectors/Align/include/Align/Controller.h @@ -30,7 +30,7 @@ #include "ReconstructionDataFormats/PrimaryVertex.h" #include "ReconstructionDataFormats/TrackCosmics.h" #include "DataFormatsTPC/VDriftCorrFact.h" -#include "CorrectionMapsHelper.h" +#include "TPCFastTransformPOD.h" #include "Align/Millepede2Record.h" #include "Align/ResidualsController.h" @@ -272,8 +272,8 @@ class Controller final : public TObject bool getAllowAfterburnerTracks() const { return mAllowAfterburnerTracks; } void setTPCVDrift(const o2::tpc::VDriftCorrFact& v); - void setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph); - o2::gpu::CorrectionMapsHelper* getTPCCorrMaps() { return mTPCCorrMapsHelper; } + void setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph); + const o2::gpu::TPCFastTransformPOD* getTPCCorrMaps() { return mTPCCorrMaps; } const o2::tpc::VDriftCorrFact& getTPCVDrift() const { return mTPCDrift; } int getInstanceID() const { return mInstanceID; } @@ -359,7 +359,7 @@ class Controller final : public TObject bool mUseRecoOCDB = true; // flag to preload reco-time calib objects o2::tpc::VDriftCorrFact mTPCDrift{}; - o2::gpu::CorrectionMapsHelper* mTPCCorrMapsHelper = nullptr; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps = nullptr; // static const int sSkipLayers[kNLrSkip]; // detector layers for which we don't need module matrices diff --git a/Detectors/Align/src/Controller.cxx b/Detectors/Align/src/Controller.cxx index 5cfbbf9f3a4ae..5f55d07893d33 100644 --- a/Detectors/Align/src/Controller.cxx +++ b/Detectors/Align/src/Controller.cxx @@ -1882,9 +1882,9 @@ void Controller::setTPCVDrift(const o2::tpc::VDriftCorrFact& v) } //______________________________________________ -void Controller::setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph) +void Controller::setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph) { - mTPCCorrMapsHelper = maph; + mTPCCorrMaps = maph; } } // namespace align diff --git a/Detectors/Base/src/MatLayerCyl.cxx b/Detectors/Base/src/MatLayerCyl.cxx index 29bed111b3584..2efe60235b895 100644 --- a/Detectors/Base/src/MatLayerCyl.cxx +++ b/Detectors/Base/src/MatLayerCyl.cxx @@ -83,7 +83,7 @@ void MatLayerCyl::initSegmentation(float rMin, float rMax, float zHalfSpan, int // int offs = 0; - o2::gpu::resizeArray(mPhiBin2Slice, 0, nphi, reinterpret_cast(mFlatBufferPtr + offs)); + o2::gpu::FlatObject::FlatObject::resizeArray(mPhiBin2Slice, 0, nphi, reinterpret_cast(mFlatBufferPtr + offs)); mNPhiSlices = mNPhiBins = nphi; for (int i = nphi; i--;) { @@ -92,10 +92,10 @@ void MatLayerCyl::initSegmentation(float rMin, float rMax, float zHalfSpan, int offs = alignSize(offs + nphi * sizeof(short), getBufferAlignmentBytes()); // account for alignment - o2::gpu::resizeArray(mSliceCos, 0, nphi, reinterpret_cast(mFlatBufferPtr + offs)); // in the beginning nslice = nphi + o2::gpu::FlatObject::resizeArray(mSliceCos, 0, nphi, reinterpret_cast(mFlatBufferPtr + offs)); // in the beginning nslice = nphi offs = alignSize(offs + nphi * sizeof(float), getBufferAlignmentBytes()); // account for alignment - o2::gpu::resizeArray(mSliceSin, 0, nphi, reinterpret_cast(mFlatBufferPtr + offs)); // in the beginning nslice = nphi + o2::gpu::FlatObject::resizeArray(mSliceSin, 0, nphi, reinterpret_cast(mFlatBufferPtr + offs)); // in the beginning nslice = nphi offs = alignSize(offs + nphi * sizeof(float), getBufferAlignmentBytes()); // account for alignment for (int i = nphi; i--;) { @@ -103,7 +103,7 @@ void MatLayerCyl::initSegmentation(float rMin, float rMax, float zHalfSpan, int mSliceSin[i] = o2::math_utils::sin(getPhiBinMin(i)); } - o2::gpu::resizeArray(mCells, 0, getNCells(), reinterpret_cast(mFlatBufferPtr + offs)); + o2::gpu::FlatObject::resizeArray(mCells, 0, getNCells(), reinterpret_cast(mFlatBufferPtr + offs)); mConstructionMask = InProgress; } @@ -245,10 +245,10 @@ void MatLayerCyl::optimizePhiSlices(float maxRelDiff) // mSliceCos pointer does not change, but sliceSin needs to be relocated auto offs = alignSize(newSl * sizeof(float), getBufferAlignmentBytes()); char* dst = ((char*)mSliceCos) + offs; // account for alignment - o2::gpu::resizeArray(mSliceSin, getNPhiBins(), newSl, reinterpret_cast(dst)); + o2::gpu::FlatObject::resizeArray(mSliceSin, getNPhiBins(), newSl, reinterpret_cast(dst)); // adjust mCells array dst = ((char*)mSliceSin) + offs; // account for alignment - o2::gpu::resizeArray(mCells, getNPhiBins() * getNZBins(), newSl * getNZBins(), reinterpret_cast(dst)); + o2::gpu::FlatObject::resizeArray(mCells, getNPhiBins() * getNZBins(), newSl * getNZBins(), reinterpret_cast(dst)); mFlatBufferSize = estimateFlatBufferSize(); LOG(info) << "Updated Nslices = " << getNPhiSlices(); } @@ -313,7 +313,7 @@ void MatLayerCyl::flatten(char* newPtr) // make object flat: move all content to single internally allocated buffer assert(mConstructionMask == InProgress); fixPointers(mFlatBufferPtr, newPtr); - auto old = o2::gpu::resizeArray(mFlatBufferPtr, getFlatBufferSize(), getFlatBufferSize(), newPtr); + auto old = o2::gpu::FlatObject::resizeArray(mFlatBufferPtr, getFlatBufferSize(), getFlatBufferSize(), newPtr); delete[] old; mFlatBufferContainer = nullptr; mConstructionMask = Constructed; diff --git a/Detectors/Base/src/MatLayerCylSet.cxx b/Detectors/Base/src/MatLayerCylSet.cxx index 1d904ed01e699..c390c8d617326 100644 --- a/Detectors/Base/src/MatLayerCylSet.cxx +++ b/Detectors/Base/src/MatLayerCylSet.cxx @@ -39,7 +39,7 @@ void MatLayerCylSet::addLayer(float rmin, float rmax, float zmax, float dz, floa if (!nlr) { // book local storage auto sz = sizeof(MatLayerCylSetLayout); - o2::gpu::resizeArray(mFlatBufferContainer, 0, sz); + o2::gpu::FlatObject::resizeArray(mFlatBufferContainer, 0, sz); mFlatBufferPtr = mFlatBufferContainer; mFlatBufferSize = sz; //--------------???? @@ -53,7 +53,7 @@ void MatLayerCylSet::addLayer(float rmin, float rmax, float zmax, float dz, floa LOG(fatal) << "new layer overlaps with layer " << il; } } - auto* oldLayers = o2::gpu::resizeArray(get()->mLayers, nlr, nlr + 1); + auto* oldLayers = o2::gpu::FlatObject::resizeArray(get()->mLayers, nlr, nlr + 1); // dynamyc buffers of old layers were used in new ones, detach them for (int i = nlr; i--;) { oldLayers[i].clearInternalBufferPtr(); @@ -98,8 +98,8 @@ void MatLayerCylSet::finalizeStructures() assert(mConstructionMask == InProgress); int nlr = getNLayers(); int nR2Int = 2 * (nlr + 1); - o2::gpu::resizeArray(get()->mR2Intervals, 0, nR2Int); - o2::gpu::resizeArray(get()->mInterval2LrID, 0, nR2Int); + o2::gpu::FlatObject::resizeArray(get()->mR2Intervals, 0, nR2Int); + o2::gpu::FlatObject::resizeArray(get()->mInterval2LrID, 0, nR2Int); get()->mR2Intervals[0] = get()->mRMin2; get()->mR2Intervals[1] = get()->mRMax2; get()->mInterval2LrID[0] = 0; @@ -116,8 +116,8 @@ void MatLayerCylSet::finalizeStructures() get()->mInterval2LrID[nRIntervals] = i; get()->mR2Intervals[++nRIntervals] = lr.getRMax2(); } - delete[] o2::gpu::resizeArray(get()->mInterval2LrID, nR2Int, nRIntervals); // rebook with precise size - delete[] o2::gpu::resizeArray(get()->mR2Intervals, nR2Int, ++nRIntervals); // rebook with precise size + delete[] o2::gpu::FlatObject::resizeArray(get()->mInterval2LrID, nR2Int, nRIntervals); // rebook with precise size + delete[] o2::gpu::FlatObject::resizeArray(get()->mR2Intervals, nR2Int, ++nRIntervals); // rebook with precise size // } @@ -508,14 +508,14 @@ void MatLayerCylSet::flatten() int sz = estimateFlatBufferSize(); // create new internal buffer with total size and copy data - delete[] o2::gpu::resizeArray(mFlatBufferContainer, mFlatBufferSize, sz); + delete[] o2::gpu::FlatObject::resizeArray(mFlatBufferContainer, mFlatBufferSize, sz); mFlatBufferPtr = mFlatBufferContainer; mFlatBufferSize = sz; int nLr = getNLayers(); auto offs = alignSize(sizeof(MatLayerCylSetLayout), getBufferAlignmentBytes()); // account for the alignment // move array of layer pointers to the flat array - auto* oldLayers = o2::gpu::resizeArray(get()->mLayers, nLr, nLr, (MatLayerCyl*)(mFlatBufferPtr + offs)); + auto* oldLayers = o2::gpu::FlatObject::resizeArray(get()->mLayers, nLr, nLr, (MatLayerCyl*)(mFlatBufferPtr + offs)); // dynamyc buffers of old layers were used in new ones, detach them for (int i = nLr; i--;) { oldLayers[i].clearInternalBufferPtr(); @@ -524,11 +524,11 @@ void MatLayerCylSet::flatten() offs = alignSize(offs + nLr * sizeof(MatLayerCyl), MatLayerCyl::getClassAlignmentBytes()); // account for the alignment // move array of R2 boundaries to the flat array - delete[] o2::gpu::resizeArray(get()->mR2Intervals, nLr + 1, nLr + 1, (float*)(mFlatBufferPtr + offs)); + delete[] o2::gpu::FlatObject::resizeArray(get()->mR2Intervals, nLr + 1, nLr + 1, (float*)(mFlatBufferPtr + offs)); offs = alignSize(offs + (nLr + 1) * sizeof(float), getBufferAlignmentBytes()); // account for the alignment // move array of R2 boundaries to the flat array - delete[] o2::gpu::resizeArray(get()->mInterval2LrID, nLr, nLr, (int*)(mFlatBufferPtr + offs)); + delete[] o2::gpu::FlatObject::resizeArray(get()->mInterval2LrID, nLr, nLr, (int*)(mFlatBufferPtr + offs)); offs = alignSize(offs + nLr * sizeof(int), getBufferAlignmentBytes()); // account for the alignment for (int il = 0; il < nLr; il++) { diff --git a/Detectors/Base/src/TFIDInfoHelper.cxx b/Detectors/Base/src/TFIDInfoHelper.cxx index f0bd8c0f15a30..6eac1cc18c81a 100644 --- a/Detectors/Base/src/TFIDInfoHelper.cxx +++ b/Detectors/Base/src/TFIDInfoHelper.cxx @@ -23,15 +23,9 @@ void o2::base::TFIDInfoHelper::fillTFIDInfo(ProcessingContext& pc, o2::dataforma { const auto& tinfo = pc.services().get(); static int errCount = 0; - if (tinfo.firstTForbit == -1U || tinfo.creation == -1) { - if (errCount++ < 5) { - LOGP(warn, "Ignoring dummy input with orbit {} and creation time {} in fillTFIDInfo", tinfo.firstTForbit, tinfo.creation); - } - return; + ti.fill(tinfo.firstTForbit, tinfo.tfCounter, tinfo.runNumber, tinfo.timeslice, tinfo.creation); + if (ti.discard && errCount++ < 5) { + LOGP(warn, "Bad input with orbit {}, TFcounter {} and creation time {} in fillTFIDInfo", tinfo.firstTForbit, tinfo.tfCounter, tinfo.creation); } - ti.firstTForbit = tinfo.firstTForbit; - ti.tfCounter = tinfo.tfCounter; - ti.runNumber = tinfo.runNumber; - ti.startTime = tinfo.timeslice; - ti.creation = tinfo.creation; + return; } diff --git a/Detectors/Base/test/buildMatBudLUT.C b/Detectors/Base/test/buildMatBudLUT.C index 14f6d078aee90..860fcbd5da940 100644 --- a/Detectors/Base/test/buildMatBudLUT.C +++ b/Detectors/Base/test/buildMatBudLUT.C @@ -23,8 +23,6 @@ #include #endif -#ifndef GPUCA_ALIGPUCODE // this part is invisible on GPU version - o2::base::MatLayerCylSet mbLUT; bool testMBLUT(const std::string& lutFile = "matbud.root"); @@ -399,5 +397,3 @@ void configLayers() lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); } while (lrData.back().rMax < 500); } - -#endif //!_COMPILED_ON_GPU_ diff --git a/Detectors/Base/test/testMatBudLUT.cxx b/Detectors/Base/test/testMatBudLUT.cxx index 4daa992368dba..33c3498995c90 100644 --- a/Detectors/Base/test/testMatBudLUT.cxx +++ b/Detectors/Base/test/testMatBudLUT.cxx @@ -21,16 +21,12 @@ namespace o2 { BOOST_AUTO_TEST_CASE(MatBudLUT) { -#ifndef GPUCA_ALIGPUCODE // this part is unvisible on GPU version - // using process specific geometry names in order // to avoid race/conditions with other tests accessing geometry std::string geomPrefix("matBudGeom"); std::string matBudFile("matbud"); matBudFile += std::to_string(getpid()) + ".root"; BOOST_CHECK(buildMatBudLUT(2, 20, matBudFile, geomPrefix + std::to_string(getpid()), "align-geom.mDetectors=none")); // generate LUT - BOOST_CHECK(testMBLUT(matBudFile)); // test LUT manipulations - -#endif //!GPUCA_ALIGPUCODE + BOOST_CHECK(testMBLUT(matBudFile)); // test LUT manipulations } } // namespace o2 diff --git a/Detectors/CTF/test/test_ctf_io_itsmft.cxx b/Detectors/CTF/test/test_ctf_io_itsmft.cxx index 13cbdf7745961..7f2ff8ce9f340 100644 --- a/Detectors/CTF/test/test_ctf_io_itsmft.cxx +++ b/Detectors/CTF/test/test_ctf_io_itsmft.cxx @@ -81,7 +81,7 @@ BOOST_DATA_TEST_CASE(CompressedClustersTest, boost_data::make(ANSVersions), ansV sw.Start(); std::vector vec; { - CTFCoder coder(o2::ctf::CTFCoderBase::OpType::Encoder, o2::detectors::DetID::ITS); + CTFCoder coder(o2::ctf::CTFCoderBase::OpType::Encoder, false); coder.setANSVersion(ansVersion); coder.encode(vec, rofRecVec, cclusVec, pattVec, pattIdConverter, 0); // compress } @@ -120,7 +120,7 @@ BOOST_DATA_TEST_CASE(CompressedClustersTest, boost_data::make(ANSVersions), ansV sw.Start(); const auto ctfImage = o2::itsmft::CTF::getImage(vec.data()); { - CTFCoder coder(o2::ctf::CTFCoderBase::OpType::Decoder, o2::detectors::DetID::ITS); + CTFCoder coder(o2::ctf::CTFCoderBase::OpType::Decoder, false); coder.decode(ctfImage, rofRecVecD, cclusVecD, pattVecD, nullptr, clPattLookup); // decompress } sw.Stop(); diff --git a/Detectors/CTF/workflow/include/CTFWorkflow/CTFReaderSpec.h b/Detectors/CTF/workflow/include/CTFWorkflow/CTFReaderSpec.h index 081e6cf4d968a..51f2fca2c8303 100644 --- a/Detectors/CTF/workflow/include/CTFWorkflow/CTFReaderSpec.h +++ b/Detectors/CTF/workflow/include/CTFWorkflow/CTFReaderSpec.h @@ -27,6 +27,7 @@ struct CTFReaderInp { std::string inpdata{}; o2::detectors::DetID::mask_t detMask = o2::detectors::DetID::FullMask; std::string copyCmd{}; + std::string copyDir{}; std::string tffileRegex{}; std::string remoteRegex{}; std::string metricChannel{}; @@ -50,6 +51,8 @@ struct CTFReaderInp { int tfRateLimit = -999; size_t minSHM = 0; bool shuffle{false}; + bool doITSStaggering = false; + bool doMFTStaggering = false; }; /// create a processor spec diff --git a/Detectors/CTF/workflow/include/CTFWorkflow/CTFWriterSpec.h b/Detectors/CTF/workflow/include/CTFWorkflow/CTFWriterSpec.h index 5eb6d65e26cec..12ad483d90881 100644 --- a/Detectors/CTF/workflow/include/CTFWorkflow/CTFWriterSpec.h +++ b/Detectors/CTF/workflow/include/CTFWorkflow/CTFWriterSpec.h @@ -15,16 +15,23 @@ #define O2_CTFWRITER_SPEC #include "Framework/DataProcessorSpec.h" -#include "Framework/Task.h" #include "DetectorsCommonDataFormats/DetID.h" namespace o2 { namespace ctf { +struct CTFWriterInp { + o2::detectors::DetID::mask_t detMask = o2::detectors::DetID::FullMask; + int verbosity = 0; + int reportInterval = 200; + std::string outType = ""; + bool doITSStaggering = false; + bool doMFTStaggering = false; +}; /// create a processor spec -framework::DataProcessorSpec getCTFWriterSpec(o2::detectors::DetID::mask_t dets, const std::string& outType, int verbosity, int reportInterval); +framework::DataProcessorSpec getCTFWriterSpec(const o2::ctf::CTFWriterInp& inp); } // namespace ctf } // namespace o2 diff --git a/Detectors/CTF/workflow/src/CTFReaderSpec.cxx b/Detectors/CTF/workflow/src/CTFReaderSpec.cxx index 4100ebb37c61d..9fba8a220be55 100644 --- a/Detectors/CTF/workflow/src/CTFReaderSpec.cxx +++ b/Detectors/CTF/workflow/src/CTFReaderSpec.cxx @@ -35,6 +35,7 @@ #include "CommonUtils/NameConf.h" #include "DetectorsCommonDataFormats/CTFHeader.h" #include "Headers/STFHeader.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITSMFT/CTF.h" #include "DataFormatsTPC/CTF.h" #include "DataFormatsTRD/CTF.h" @@ -170,7 +171,7 @@ void CTFReaderSpec::init(InitContext& ic) mInput.maxTFsPerFile = ic.options().get("max-tf-per-file"); mInput.maxTFsPerFile = mInput.maxTFsPerFile > 0 ? mInput.maxTFsPerFile : 0x7fffffff; mRunning = true; - mFileFetcher = std::make_unique(mInput.inpdata, mInput.tffileRegex, mInput.remoteRegex, mInput.copyCmd); + mFileFetcher = std::make_unique(mInput.inpdata, mInput.tffileRegex, mInput.remoteRegex, mInput.copyCmd, mInput.copyDir); mFileFetcher->setMaxFilesInQueue(mInput.maxFileCache); mFileFetcher->setMaxLoops(mInput.maxLoops); mFileFetcher->setFailThreshold(ic.options().get("fetch-failure-threshold")); @@ -188,6 +189,48 @@ void CTFReaderSpec::init(InitContext& ic) } } +///_______________________________________ +template <> +void CTFReaderSpec::processDetector(DetID det, const CTFHeader& ctfHeader, ProcessingContext& pc) const +{ + if (mInput.detMask[det]) { + std::string lbl = det.getName(); + int nLayers = 1; + if (det == DetID::ITS) { + nLayers = mInput.doITSStaggering ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + } else if (det == DetID::MFT) { + nLayers = mInput.doMFTStaggering ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + } else { + LOGP(fatal, "This specialization is define only for ITS and MFT detectors, {} provided", det.getName()); + } + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + auto& bufVec = pc.outputs().make>({lbl, mInput.subspec * 100 + iLayer}, ctfHeader.detectors[det] ? sizeof(o2::itsmft::CTF) : 0); + if (ctfHeader.detectors[det]) { + auto brName = nLayers == 1 ? lbl : fmt::format("{}_{}", lbl, iLayer); + o2::itsmft::CTF::readFromTree(bufVec, *(mCTFTree.get()), brName, mCurrTreeEntry); + } else if (!mInput.allowMissingDetectors) { + throw std::runtime_error(fmt::format("Requested detector {} is missing in the CTF", lbl)); + } + } + } +} + +///_______________________________________ +template +void CTFReaderSpec::processDetector(DetID det, const CTFHeader& ctfHeader, ProcessingContext& pc) const +{ + if (mInput.detMask[det]) { + const auto lbl = det.getName(); + auto& bufVec = pc.outputs().make>({lbl, mInput.subspec}, ctfHeader.detectors[det] ? sizeof(C) : 0); + if (ctfHeader.detectors[det]) { + C::readFromTree(bufVec, *(mCTFTree.get()), lbl, mCurrTreeEntry); + } else if (!mInput.allowMissingDetectors) { + throw std::runtime_error(fmt::format("Requested detector {} is missing in the CTF", lbl)); + } + // setMessageHeader(pc, ctfHeader, lbl); + } +} + void CTFReaderSpec::runTimeRangesToIRFrameSelector(const o2::framework::TimingInfo& timingInfo) { // convert entries in the runTimeRanges to IRFrameSelector, if needed, convert time to orbit @@ -562,22 +605,6 @@ void CTFReaderSpec::setMessageHeader(ProcessingContext& pc, const CTFHeader& ctf dph->creation = ctfHeader.creationTime; } -///_______________________________________ -template -void CTFReaderSpec::processDetector(DetID det, const CTFHeader& ctfHeader, ProcessingContext& pc) const -{ - if (mInput.detMask[det]) { - const auto lbl = det.getName(); - auto& bufVec = pc.outputs().make>({lbl, mInput.subspec}, ctfHeader.detectors[det] ? sizeof(C) : 0); - if (ctfHeader.detectors[det]) { - C::readFromTree(bufVec, *(mCTFTree.get()), lbl, mCurrTreeEntry); - } else if (!mInput.allowMissingDetectors) { - throw std::runtime_error(fmt::format("Requested detector {} is missing in the CTF", lbl)); - } - // setMessageHeader(pc, ctfHeader, lbl); - } -} - ///_______________________________________ void CTFReaderSpec::tryToFixCTFHeader(CTFHeader& ctfHeader) const { @@ -636,7 +663,19 @@ DataProcessorSpec getCTFReaderSpec(const CTFReaderInp& inp) for (auto id = DetID::First; id <= DetID::Last; id++) { if (inp.detMask[id]) { DetID det(id); - outputs.emplace_back(OutputLabel{det.getName()}, det.getDataOrigin(), "CTFDATA", inp.subspec, Lifetime::Timeframe); + if (det == DetID::ITS) { + uint32_t nLayers = inp.doITSStaggering ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + for (uint32_t iLayer = 0; iLayer < nLayers; iLayer++) { + outputs.emplace_back(OutputLabel{det.getName()}, det.getDataOrigin(), "CTFDATA", inp.subspec * 100 + iLayer, Lifetime::Timeframe); + } + } else if (det == DetID::MFT) { + uint32_t nLayers = inp.doMFTStaggering ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + for (uint32_t iLayer = 0; iLayer < nLayers; iLayer++) { + outputs.emplace_back(OutputLabel{det.getName()}, det.getDataOrigin(), "CTFDATA", inp.subspec * 100 + iLayer, Lifetime::Timeframe); + } + } else { + outputs.emplace_back(OutputLabel{det.getName()}, det.getDataOrigin(), "CTFDATA", inp.subspec, Lifetime::Timeframe); + } } } if (!inp.fileIRFrames.empty() || !inp.fileRunTimeSpans.empty()) { diff --git a/Detectors/CTF/workflow/src/CTFWriterSpec.cxx b/Detectors/CTF/workflow/src/CTFWriterSpec.cxx index ba4542969a712..f175bf4c2e5d3 100644 --- a/Detectors/CTF/workflow/src/CTFWriterSpec.cxx +++ b/Detectors/CTF/workflow/src/CTFWriterSpec.cxx @@ -12,11 +12,10 @@ /// @file CTFWriterSpec.cxx #include "Framework/Logger.h" -#include "Framework/ControlService.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/InputSpec.h" +#include "Framework/Task.h" #include "Framework/RawDeviceService.h" -#include "Framework/CommonServices.h" #include "Framework/DataTakingContext.h" #include "Framework/TimingInfo.h" #include @@ -29,6 +28,7 @@ #include "DetectorsCommonDataFormats/EncodedBlocks.h" #include "DetectorsCommonDataFormats/FileMetaData.h" #include "CommonUtils/StringUtils.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITSMFT/CTF.h" #include "DataFormatsTPC/CTF.h" #include "DataFormatsTRD/CTF.h" @@ -94,17 +94,19 @@ size_t appendToTree(TTree& tree, const std::string brname, T& ptr) using DetID = o2::detectors::DetID; using FTrans = o2::rans::DenseHistogram; -class CTFWriterSpec : public o2::framework::Task +class CTFWriterSpec final : public o2::framework::Task { public: CTFWriterSpec() = delete; - CTFWriterSpec(DetID::mask_t dm, const std::string& outType, int verbosity, int reportInterval); + CTFWriterSpec(const o2::ctf::CTFWriterInp&); ~CTFWriterSpec() final { finalize(); } void init(o2::framework::InitContext& ic) final; void run(o2::framework::ProcessingContext& pc) final; void endOfStream(o2::framework::EndOfStreamContext& ec) final { finalize(); } void stop() final { finalize(); } - bool isPresent(DetID id) const { return mDets[id]; } + bool isPresent(DetID id) const { return mInput.detMask[id]; } + + static std::string getBinding(const std::string& name, int spec) { return fmt::format("{}_{}", name, spec); } private: void updateTimeDependentParams(ProcessingContext& pc); @@ -121,7 +123,7 @@ class CTFWriterSpec : public o2::framework::Task void removeLockFile(); void finalize(); - DetID::mask_t mDets; // detectors + CTFWriterInp mInput; bool mFinalized = false; bool mWriteCTF = true; bool mCreateDict = false; @@ -130,8 +132,6 @@ class CTFWriterSpec : public o2::framework::Task bool mRejectCurrentTF = false; bool mFallBackDirUsed = false; bool mFallBackDirProvided = false; - int mReportInterval = -1; - int mVerbosity = 0; int mSaveDictAfter = 0; // if positive and mWriteCTF==true, save dictionary after each mSaveDictAfter TFs processed uint32_t mPrevDictTimeStamp = 0; // timestamp of the previously stored dictionary uint32_t mDictTimeStamp = 0; // timestamp of the currently stored dictionary @@ -155,7 +155,6 @@ class CTFWriterSpec : public o2::framework::Task std::vector mTFOrbits{}; // 1st orbits of TF accumulated in current file o2::framework::DataTakingContext mDataTakingContext{}; o2::framework::TimingInfo mTimingInfo{}; - std::string mOutputType{}; // RS FIXME once global/local options clash is solved, --output-type will become device option std::string mDictDir{}; std::string mCTFDir{}; std::string mHostName{}; @@ -190,8 +189,8 @@ class CTFWriterSpec : public o2::framework::Task const std::string CTFWriterSpec::TMPFileEnding{".part"}; //___________________________________________________________________ -CTFWriterSpec::CTFWriterSpec(DetID::mask_t dm, const std::string& outType, int verbosity, int reportInterval) - : mDets(dm), mOutputType(outType), mReportInterval(reportInterval), mVerbosity(verbosity) +CTFWriterSpec::CTFWriterSpec(const o2::ctf::CTFWriterInp& inp) + : mInput(inp) { std::for_each(mIsSaturatedFrequencyTable.begin(), mIsSaturatedFrequencyTable.end(), [](auto& bitset) { bitset.reset(); }); mTimer.Stop(); @@ -202,7 +201,7 @@ CTFWriterSpec::CTFWriterSpec(DetID::mask_t dm, const std::string& outType, int v void CTFWriterSpec::init(InitContext& ic) { // auto outmode = ic.options().get("output-type"); // RS FIXME once global/local options clash is solved, --output-type will become device option - auto outmode = mOutputType; + auto outmode = mInput.outType; if (outmode == "ctf") { mWriteCTF = true; mCreateDict = false; @@ -301,71 +300,82 @@ size_t CTFWriterSpec::processDet(o2::framework::ProcessingContext& pc, DetID det { static bool warnedEmpty = false; size_t sz = 0; - if (!isPresent(det) || !pc.inputs().isValid(det.getName())) { + + if (!isPresent(det) || !pc.inputs().isValid(getBinding(det.getName(), 0))) { mSizeReport += fmt::format(" {}:N/A", det.getName()); return sz; } - auto ctfBuffer = pc.inputs().get>(det.getName()); - const o2::ctf::BufferType* bdata = ctfBuffer.data(); - if (bdata) { - if (warnedEmpty) { - throw std::runtime_error(fmt::format("Non-empty input was seen at {}-th TF after empty one for {}, this will lead to misalignment of detectors in CTF", mNCTF, det.getName())); - } - const auto ctfImage = C::getImage(bdata); - ctfImage.print(o2::utils::Str::concat_string(det.getName(), ": "), mVerbosity); - if (mWriteCTF && !mRejectCurrentTF) { - sz = ctfImage.appendToTree(*tree, det.getName()); - header.detectors.set(det); - } else { - sz = ctfBuffer.size(); - } - if (mCreateDict) { - if (mFreqsAccumulation[det].empty()) { - mFreqsAccumulation[det].resize(C::getNBlocks()); - mFreqsMetaData[det].resize(C::getNBlocks()); + + uint32_t nLayers = 1; + if (det == DetID::ITS) { + nLayers = mInput.doITSStaggering ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + } else if (det == DetID::MFT) { + nLayers = mInput.doMFTStaggering ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + } + for (uint32_t iLayer = 0; iLayer < nLayers; iLayer++) { + auto binding = getBinding(det.getName(), iLayer); + auto ctfBuffer = pc.inputs().get>(binding); + const o2::ctf::BufferType* bdata = ctfBuffer.data(); + if (bdata) { + if (warnedEmpty) { + throw std::runtime_error(fmt::format("Non-empty input was seen at {}-th TF after empty one for {}, this will lead to misalignment of detectors in CTF", mNCTF, det.getName())); } - if (!mHeaders[det]) { // store 1st header - mHeaders[det] = ctfImage.cloneHeader(); - auto& hb = *static_cast(mHeaders[det].get()); - hb.det = det; + const auto ctfImage = C::getImage(bdata); + ctfImage.print(o2::utils::Str::concat_string(binding, ": "), mInput.verbosity); + if (mWriteCTF && !mRejectCurrentTF) { + sz += ctfImage.appendToTree(*tree, nLayers > 1 ? binding : det.getName()); + header.detectors.set(det); + } else { + sz += ctfBuffer.size(); } - for (int ib = 0; ib < C::getNBlocks(); ib++) { - if (!mIsSaturatedFrequencyTable[det][ib]) { - const auto& bl = ctfImage.getBlock(ib); - if (bl.getNDict()) { - auto freq = mFreqsAccumulation[det][ib]; - auto& mdSave = mFreqsMetaData[det][ib]; - const auto& md = ctfImage.getMetadata(ib); - if ([&, this]() { - try { - freq.addFrequencies(bl.getDict(), bl.getDict() + bl.getNDict(), md.min); - } catch (const std::overflow_error& e) { - LOGP(warning, "unable to add frequency table for {}, block {} due to overflow", det.getName(), ib); - mIsSaturatedFrequencyTable[det][ib] = true; - return false; - } - return true; - }()) { - auto newProbBits = static_cast(o2::rans::compat::computeRenormingPrecision(countNUsedAlphabetSymbols(freq))); - auto histogramView = o2::rans::trim(o2::rans::makeHistogramView(freq)); - mdSave = ctf::detail::makeMetadataRansDict(newProbBits, - static_cast(histogramView.getMin()), - static_cast(histogramView.getMax()), - static_cast(histogramView.size()), - md.opt); - mFreqsAccumulation[det][ib] = std::move(freq); + if (mCreateDict) { // RSTODO + if (mFreqsAccumulation[det].empty()) { + mFreqsAccumulation[det].resize(C::getNBlocks()); + mFreqsMetaData[det].resize(C::getNBlocks()); + } + if (!mHeaders[det]) { // store 1st header + mHeaders[det] = ctfImage.cloneHeader(); + auto& hb = *static_cast(mHeaders[det].get()); + hb.det = det; + } + for (int ib = 0; ib < C::getNBlocks(); ib++) { + if (!mIsSaturatedFrequencyTable[det][ib]) { + const auto& bl = ctfImage.getBlock(ib); + if (bl.getNDict()) { + auto freq = mFreqsAccumulation[det][ib]; + auto& mdSave = mFreqsMetaData[det][ib]; + const auto& md = ctfImage.getMetadata(ib); + if ([&, this]() { + try { + freq.addFrequencies(bl.getDict(), bl.getDict() + bl.getNDict(), md.min); + } catch (const std::overflow_error& e) { + LOGP(warning, "unable to add frequency table for {}, block {} due to overflow", det.getName(), ib); + mIsSaturatedFrequencyTable[det][ib] = true; + return false; + } + return true; + }()) { + auto newProbBits = static_cast(o2::rans::compat::computeRenormingPrecision(countNUsedAlphabetSymbols(freq))); + auto histogramView = o2::rans::trim(o2::rans::makeHistogramView(freq)); + mdSave = ctf::detail::makeMetadataRansDict(newProbBits, + static_cast(histogramView.getMin()), + static_cast(histogramView.getMax()), + static_cast(histogramView.size()), + md.opt); + mFreqsAccumulation[det][ib] = std::move(freq); + } } } } } - } - } else { - if (!warnedEmpty) { - if (mNCTF) { - throw std::runtime_error(fmt::format("Empty input was seen at {}-th TF after non-empty one for {}, this will lead to misalignment of detectors in CTF", mNCTF, det.getName())); + } else { + if (!warnedEmpty) { + if (mNCTF) { + throw std::runtime_error(fmt::format("Empty input was seen at {}-th TF after non-empty one for {}, this will lead to misalignment of detectors in CTF", mNCTF, det.getName())); + } + LOGP(important, "Empty CTF provided for {}, skipping and will not report anymore", det.getName()); + warnedEmpty = true; } - LOGP(important, "Empty CTF provided for {}, skipping and will not report anymore", det.getName()); - warnedEmpty = true; } } mSizeReport += fmt::format(" {}:{}", det.getName(), fmt::group_digits(sz)); @@ -417,10 +427,19 @@ size_t CTFWriterSpec::estimateCTFSize(ProcessingContext& pc) size_t s = 0; for (auto id = DetID::First; id <= DetID::Last; id++) { DetID det(id); - if (!isPresent(det) || !pc.inputs().isValid(det.getName())) { - continue; + uint32_t nLayers = 1; + if (det == DetID::ITS) { + nLayers = mInput.doITSStaggering ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + } else if (det == DetID::MFT) { + nLayers = mInput.doMFTStaggering ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + } + for (uint32_t iLayer = 0; iLayer < nLayers; iLayer++) { + auto binding = getBinding(det.getName(), iLayer); + if (!isPresent(det) || !pc.inputs().isValid(binding)) { + continue; + } + s += pc.inputs().get>(binding).size(); } - s += pc.inputs().get>(det.getName()).size(); } return s; } @@ -496,7 +515,7 @@ void CTFWriterSpec::run(ProcessingContext& pc) szCTFperDet[DetID::FDD] = processDet(pc, DetID::FDD, header, mCTFTreeOut.get()); szCTFperDet[DetID::CTP] = processDet(pc, DetID::CTP, header, mCTFTreeOut.get()); szCTF = std::accumulate(szCTFperDet.begin(), szCTFperDet.end(), 0); - if (mReportInterval > 0 && (mTimingInfo.tfCounter % mReportInterval) == 0) { + if (mInput.reportInterval > 0 && (mTimingInfo.tfCounter % mInput.reportInterval) == 0) { LOGP(important, "CTF {} size report:{} - Total:{}", mTimingInfo.tfCounter, mSizeReport, fmt::group_digits(szCTF)); } @@ -660,7 +679,7 @@ void CTFWriterSpec::storeDictionaries() // monolitic dictionary in tree format mDictTimeStamp = uint32_t(std::time(nullptr)); auto getFileName = [this](bool curr) { - return fmt::format("{}{}Tree_{}_{}_{}.root", this->mDictDir, o2::base::NameConf::CTFDICT, DetID::getNames(this->mDets, '-'), curr ? this->mDictTimeStamp : this->mPrevDictTimeStamp, curr ? this->mNCTF : this->mNCTFPrevDict); + return fmt::format("{}{}Tree_{}_{}_{}.root", this->mDictDir, o2::base::NameConf::CTFDICT, DetID::getNames(this->mInput.detMask, '-'), curr ? this->mDictTimeStamp : this->mPrevDictTimeStamp, curr ? this->mNCTF : this->mNCTFPrevDict); }; auto dictFileName = getFileName(true); mDictFileOut.reset(TFile::Open(dictFileName.c_str(), "recreate")); @@ -788,13 +807,22 @@ size_t CTFWriterSpec::getAvailableDiskSpace(const std::string& path, int level) } //___________________________________________________________________ -DataProcessorSpec getCTFWriterSpec(DetID::mask_t dets, const std::string& outType, int verbosity, int reportInterval) +DataProcessorSpec getCTFWriterSpec(const o2::ctf::CTFWriterInp& inp) { std::vector inputs; LOG(debug) << "Detectors list:"; for (auto id = DetID::First; id <= DetID::Last; id++) { - if (dets[id]) { - inputs.emplace_back(DetID::getName(id), DetID::getDataOrigin(id), "CTFDATA", 0, Lifetime::Timeframe); + if (inp.detMask[id]) { + uint32_t nLayers = 1; + DetID det{id}; + if (det == DetID::ITS) { + nLayers = inp.doITSStaggering ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + } else if (det == DetID::MFT) { + nLayers = inp.doMFTStaggering ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + } + for (uint32_t iLayer = 0; iLayer < nLayers; iLayer++) { + inputs.emplace_back(CTFWriterSpec::getBinding(det.getName(), iLayer), det.getDataOrigin(), "CTFDATA", iLayer, Lifetime::Timeframe); + } LOG(debug) << "Det " << DetID::getName(id) << " added"; } } @@ -803,24 +831,25 @@ DataProcessorSpec getCTFWriterSpec(DetID::mask_t dets, const std::string& outTyp inputs, Outputs{{OutputLabel{"ctfdone"}, "CTF", "DONE", 0, Lifetime::Timeframe}, {"CTF", "SIZES", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(dets, outType, verbosity, reportInterval)}, // RS FIXME once global/local options clash is solved, --output-type will become device option - Options{ //{"output-type", VariantType::String, "ctf", {"output types: ctf (per TF) or dict (create dictionaries) or both or none"}}, - {"save-ctf-after", VariantType::Int64, 0ll, {"autosave CTF tree with multiple CTFs after every N CTFs if >0 or every -N MBytes if < 0"}}, - {"save-dict-after", VariantType::Int, 0, {"if > 0, in dictionary generation mode save it dictionary after certain number of TFs processed"}}, - {"ctf-dict-dir", VariantType::String, "none", {"CTF dictionary directory, must exist"}}, - {"output-dir", VariantType::String, "none", {"CTF output directory, must exist"}}, - {"output-dir-alt", VariantType::String, "/dev/null", {"Alternative CTF output directory, must exist (if not /dev/null)"}}, - {"meta-output-dir", VariantType::String, "/dev/null", {"CTF metadata output directory, must exist (if not /dev/null)"}}, - {"md5-for-meta", VariantType::Bool, false, {"fill CTF file MD5 sum in the metadata file"}}, - {"min-file-size", VariantType::Int64, 0l, {"accumulate CTFs until given file size reached"}}, - {"max-file-size", VariantType::Int64, 0l, {"if > 0, try to avoid exceeding given file size, also used for space check"}}, - {"max-ctf-per-file", VariantType::Int, 0, {"if > 0, avoid storing more than requested CTFs per file"}}, - {"ctf-rejection", VariantType::Int, 0, {">0: percentage to reject randomly, <0: reject if timeslice%|value|!=0"}}, - {"ctf-file-compression", VariantType::Int, 0, {"if >= 0: impose CTF file compression level"}}, - {"require-free-disk", VariantType::Float, 0.f, {"pause writing op. if available disk space is below this margin, in bytes if >0, as a fraction of total if <0"}}, - {"wait-for-free-disk", VariantType::Float, 10.f, {"if paused due to the low disk space, recheck after this time (in s)"}}, - {"max-wait-for-free-disk", VariantType::Float, 60.f, {"produce fatal if paused due to the low disk space for more than this amount in s."}}, - {"ignore-partition-run-dir", VariantType::Bool, false, {"Do not creare partition-run directory in output-dir"}}}}; + AlgorithmSpec{adaptFromTask(inp)}, + Options{ + //{"output-type", VariantType::String, "ctf", {"output types: ctf (per TF) or dict (create dictionaries) or both or none"}}, + {"save-ctf-after", VariantType::Int64, 0ll, {"autosave CTF tree with multiple CTFs after every N CTFs if >0 or every -N MBytes if < 0"}}, + {"save-dict-after", VariantType::Int, 0, {"if > 0, in dictionary generation mode save it dictionary after certain number of TFs processed"}}, + {"ctf-dict-dir", VariantType::String, "none", {"CTF dictionary directory, must exist"}}, + {"output-dir", VariantType::String, "none", {"CTF output directory, must exist"}}, + {"output-dir-alt", VariantType::String, "/dev/null", {"Alternative CTF output directory, must exist (if not /dev/null)"}}, + {"meta-output-dir", VariantType::String, "/dev/null", {"CTF metadata output directory, must exist (if not /dev/null)"}}, + {"md5-for-meta", VariantType::Bool, false, {"fill CTF file MD5 sum in the metadata file"}}, + {"min-file-size", VariantType::Int64, 0l, {"accumulate CTFs until given file size reached"}}, + {"max-file-size", VariantType::Int64, 0l, {"if > 0, try to avoid exceeding given file size, also used for space check"}}, + {"max-ctf-per-file", VariantType::Int, 0, {"if > 0, avoid storing more than requested CTFs per file"}}, + {"ctf-rejection", VariantType::Int, 0, {">0: percentage to reject randomly, <0: reject if timeslice%|value|!=0"}}, + {"ctf-file-compression", VariantType::Int, 0, {"if >= 0: impose CTF file compression level"}}, + {"require-free-disk", VariantType::Float, 0.f, {"pause writing op. if available disk space is below this margin, in bytes if >0, as a fraction of total if <0"}}, + {"wait-for-free-disk", VariantType::Float, 10.f, {"if paused due to the low disk space, recheck after this time (in s)"}}, + {"max-wait-for-free-disk", VariantType::Float, 60.f, {"produce fatal if paused due to the low disk space for more than this amount in s."}}, + {"ignore-partition-run-dir", VariantType::Bool, false, {"Do not creare partition-run directory in output-dir"}}}}; } } // namespace ctf diff --git a/Detectors/CTF/workflow/src/ctf-reader-workflow.cxx b/Detectors/CTF/workflow/src/ctf-reader-workflow.cxx index fc50c971c5d20..366fa76f74983 100644 --- a/Detectors/CTF/workflow/src/ctf-reader-workflow.cxx +++ b/Detectors/CTF/workflow/src/ctf-reader-workflow.cxx @@ -25,6 +25,7 @@ // Specific detectors specs #include "ITSMFTWorkflow/EntropyDecoderSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "TPCWorkflow/EntropyDecoderSpec.h" #include "TRDWorkflow/EntropyDecoderSpec.h" #include "HMPIDWorkflow/EntropyDecoderSpec.h" @@ -59,6 +60,7 @@ void customize(std::vector& workflowOptions) options.push_back(ConfigParamSpec{"delay", VariantType::Float, 0.f, {"delay in seconds between consecutive TFs sending"}}); options.push_back(ConfigParamSpec{"shuffle", VariantType::Bool, false, {"shuffle TF sending order (for debug)"}}); options.push_back(ConfigParamSpec{"copy-cmd", VariantType::String, "alien_cp ?src file://?dst", {"copy command for remote files or no-copy to avoid copying"}}); // Use "XrdSecPROTOCOL=sss,unix xrdcp -N root://eosaliceo2.cern.ch/?src ?dst" for direct EOS access + options.push_back(ConfigParamSpec{"copy-dir", VariantType::String, "/tmp/", {"copy base directory for remote files"}}); options.push_back(ConfigParamSpec{"ctf-file-regex", VariantType::String, ".*o2_ctf_run.+\\.root$", {"regex string to identify CTF files"}}); options.push_back(ConfigParamSpec{"remote-regex", VariantType::String, "^(alien://|)/alice/data/.+", {"regex string to identify remote files"}}); // Use "^/eos/aliceo2/.+" for direct EOS access options.push_back(ConfigParamSpec{"max-cached-files", VariantType::Int, 3, {"max CTF files queued (copied for remote source)"}}); @@ -80,6 +82,7 @@ void customize(std::vector& workflowOptions) options.push_back(ConfigParamSpec{"timeframes-shm-limit", VariantType::String, "0", {"Minimum amount of SHM required in order to publish data"}}); options.push_back(ConfigParamSpec{"metric-feedback-channel-format", VariantType::String, "name=metric-feedback,type=pull,method=connect,address=ipc://{}metric-feedback-{},transport=shmem,rateLogging=0", {"format for the metric-feedback channel for TF rate limiting"}}); options.push_back(ConfigParamSpec{"combine-devices", VariantType::Bool, false, {"combine multiple DPL devices (entropy decoders)"}}); + o2::itsmft::DPLAlpideParamInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -124,6 +127,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) ctfInput.shuffle = configcontext.options().get("shuffle"); ctfInput.copyCmd = configcontext.options().get("copy-cmd"); + ctfInput.copyDir = configcontext.options().get("copy-dir"); ctfInput.tffileRegex = configcontext.options().get("ctf-file-regex"); ctfInput.remoteRegex = configcontext.options().get("remote-regex"); ctfInput.allowMissingDetectors = configcontext.options().get("allow-missing-detectors"); @@ -147,6 +151,8 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (!ctfInput.fileIRFrames.empty() && !ctfInput.fileRunTimeSpans.empty()) { LOGP(fatal, "One cannot provide --ir-frames-files and --run-time-span-file options simultaneously"); } + ctfInput.doITSStaggering = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); + ctfInput.doMFTStaggering = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(configcontext); specs.push_back(o2::ctf::getCTFReaderSpec(ctfInput)); @@ -183,10 +189,12 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) // add decoders for all allowed detectors. if (ctfInput.detMask[DetID::ITS]) { - addSpecs(o2::itsmft::getEntropyDecoderSpec(DetID::getDataOrigin(DetID::ITS), verbosity, configcontext.options().get("its-digits"), ctfInput.subspec, ctfInput.dictOpt)); + bool doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); + addSpecs(o2::itsmft::getITSEntropyDecoderSpec(verbosity, doStag, configcontext.options().get("its-digits"), ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::MFT]) { - addSpecs(o2::itsmft::getEntropyDecoderSpec(DetID::getDataOrigin(DetID::MFT), verbosity, configcontext.options().get("mft-digits"), ctfInput.subspec, ctfInput.dictOpt)); + bool doStag = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(configcontext); + addSpecs(o2::itsmft::getMFTEntropyDecoderSpec(verbosity, doStag, configcontext.options().get("mft-digits"), ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::TPC]) { addSpecs(o2::tpc::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); diff --git a/Detectors/CTF/workflow/src/ctf-writer-workflow.cxx b/Detectors/CTF/workflow/src/ctf-writer-workflow.cxx index 2757192727521..77dbbd80bc1a7 100644 --- a/Detectors/CTF/workflow/src/ctf-writer-workflow.cxx +++ b/Detectors/CTF/workflow/src/ctf-writer-workflow.cxx @@ -20,6 +20,7 @@ #include "CTFWorkflow/CTFWriterSpec.h" #include "DetectorsCommonDataFormats/DetID.h" #include "CommonUtils/ConfigurableParam.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; using DetID = o2::detectors::DetID; @@ -35,6 +36,7 @@ void customize(std::vector& workflowOptions) options.push_back(ConfigParamSpec{"ctf-writer-verbosity", VariantType::Int, 0, {"verbosity level (0: summary per detector, 1: summary per block"}}); options.push_back(ConfigParamSpec{"report-data-size-interval", VariantType::Int, 200, {"report sizes per detector for every N-th timeframe"}}); options.push_back(ConfigParamSpec{"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}); + o2::itsmft::DPLAlpideParamInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -51,7 +53,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { DetID::mask_t dets = 0; o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); - std::string outType{}; // RS FIXME once global/local options clash is solved, --output-type will become device option + o2::ctf::CTFWriterInp inp; if (!configcontext.helpOnCommandLine()) { dets.set(); // by default read all auto mskOnly = DetID::getMask(configcontext.options().get("onlyDet")); @@ -64,10 +66,14 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (dets.none()) { throw std::invalid_argument("Invalid workflow: no detectors found"); } - outType = configcontext.options().get("output-type"); + inp.detMask = dets; + inp.outType = configcontext.options().get("output-type"); } - WorkflowSpec specs{o2::ctf::getCTFWriterSpec(dets, outType, - configcontext.options().get("ctf-writer-verbosity"), - configcontext.options().get("report-data-size-interval"))}; + inp.verbosity = configcontext.options().get("ctf-writer-verbosity"); + inp.reportInterval = configcontext.options().get("report-data-size-interval"); + inp.doITSStaggering = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); + inp.doMFTStaggering = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(configcontext); + + WorkflowSpec specs{o2::ctf::getCTFWriterSpec(inp)}; return std::move(specs); } diff --git a/Detectors/Calibration/include/DetectorsCalibration/TimeSlotCalibration.h b/Detectors/Calibration/include/DetectorsCalibration/TimeSlotCalibration.h index 87562afddf2ca..a8be3644619a4 100644 --- a/Detectors/Calibration/include/DetectorsCalibration/TimeSlotCalibration.h +++ b/Detectors/Calibration/include/DetectorsCalibration/TimeSlotCalibration.h @@ -281,6 +281,15 @@ template template bool TimeSlotCalibration::process(const DATA&... data) { + if (mCurrentTFInfo.discard) { + LOGP(warn, "Ignoring TF with discard flag on: Orbit {}, TFcounter {}, Run:{}, StartTime:{} CreationTime {}, ", + mCurrentTFInfo.firstTForbit, + mCurrentTFInfo.tfCounter, + mCurrentTFInfo.runNumber, + mCurrentTFInfo.startTime, + mCurrentTFInfo.creation); + return false; // ignore bad TF + } static bool firstCall = true; if (firstCall) { firstCall = false; diff --git a/Detectors/DCS/include/DetectorsDCS/DataPointCompositeObject.h b/Detectors/DCS/include/DetectorsDCS/DataPointCompositeObject.h index 9aaf6e517336d..84e15e4656cd2 100644 --- a/Detectors/DCS/include/DetectorsDCS/DataPointCompositeObject.h +++ b/Detectors/DCS/include/DetectorsDCS/DataPointCompositeObject.h @@ -28,9 +28,7 @@ #include "DetectorsDCS/DataPointValue.h" #include "DetectorsDCS/DeliveryType.h" -namespace o2 -{ -namespace dcs +namespace o2::dcs { /** * DataPointCompositeObject is a composition of a DataPointIdentifier and a @@ -47,7 +45,7 @@ struct DataPointCompositeObject final { * * @see ADAPRO::ADAPOS::DataPointIdentifier */ - const DataPointIdentifier id; + DataPointIdentifier id; /** * The DataPointValue object, which occupies the last 64 bytes of the @@ -83,15 +81,9 @@ struct DataPointCompositeObject final { /** * Copy constructor */ - DataPointCompositeObject(const DataPointCompositeObject& src) noexcept : DataPointCompositeObject(src.id, src.data) {} + DataPointCompositeObject(const DataPointCompositeObject& src) noexcept = default; - DataPointCompositeObject& operator=(const DataPointCompositeObject& src) noexcept - { - if (&src != this) { - memcpy(this, &src, sizeof(DataPointCompositeObject)); - } - return *this; - } + DataPointCompositeObject& operator=(const DataPointCompositeObject& src) noexcept = default; /** * Bit-by bit equality comparison of DataPointCompositeObjects. @@ -297,26 +289,6 @@ struct DataPointCompositeObject final { template T getValue(const DataPointCompositeObject& dpcom); -} // namespace dcs - -/// Defining DataPointCompositeObject explicitly as messageable -namespace framework -{ -template -struct is_messageable; -template <> -struct is_messageable : std::true_type { -}; -} // namespace framework - -} // namespace o2 - -/// Defining DataPointCompositeObject explicitly as copiable -namespace std -{ -template <> -struct is_trivially_copyable : std::true_type { -}; -} // namespace std +} // namespace o2::dcs #endif /* O2_DCS_DATAPOINT_COMPOSITE_OBJECT_H */ diff --git a/Detectors/DCS/include/DetectorsDCS/DataPointIdentifier.h b/Detectors/DCS/include/DetectorsDCS/DataPointIdentifier.h index 79b7d7cf886c7..faa12a3306d4f 100644 --- a/Detectors/DCS/include/DetectorsDCS/DataPointIdentifier.h +++ b/Detectors/DCS/include/DetectorsDCS/DataPointIdentifier.h @@ -31,9 +31,7 @@ #include "DetectorsDCS/GenericFunctions.h" #include "DetectorsDCS/DeliveryType.h" -namespace o2 -{ -namespace dcs +namespace o2::dcs { /** * DataPointIdentifier object is responsible for storing the alias and type @@ -41,19 +39,14 @@ namespace dcs */ class DataPointIdentifier final { - const uint64_t pt1; - const uint64_t pt2; - const uint64_t pt3; - const uint64_t pt4; - const uint64_t pt5; - const uint64_t pt6; - const uint64_t pt7; - const uint64_t pt8; // Contains the last 6 chars of alias and the type. - - DataPointIdentifier( - const uint64_t pt1, const uint64_t pt2, const uint64_t pt3, - const uint64_t pt4, const uint64_t pt5, const uint64_t pt6, - const uint64_t pt7, const uint64_t pt8) noexcept : pt1(pt1), pt2(pt2), pt3(pt3), pt4(pt4), pt5(pt5), pt6(pt6), pt7(pt7), pt8(pt8) {} + uint64_t pt1; + uint64_t pt2; + uint64_t pt3; + uint64_t pt4; + uint64_t pt5; + uint64_t pt6; + uint64_t pt7; + uint64_t pt8; // Contains the last 6 chars of alias and the type. public: /** @@ -85,15 +78,9 @@ class DataPointIdentifier final /** * A copy constructor for DataPointIdentifier. */ - DataPointIdentifier(const DataPointIdentifier& src) noexcept : DataPointIdentifier(src.pt1, src.pt2, src.pt3, src.pt4, src.pt5, src.pt6, src.pt7, src.pt8) {} + DataPointIdentifier(const DataPointIdentifier& src) noexcept = default; - DataPointIdentifier& operator=(const DataPointIdentifier& src) noexcept - { - if (&src != this) { - memcpy(this, &src, sizeof(DataPointIdentifier)); - } - return *this; - } + DataPointIdentifier& operator=(const DataPointIdentifier& src) noexcept = default; /** * This stati procedure fills the given DataPointIdentifier object with @@ -219,19 +206,7 @@ struct DPIDHash { return dpid.hash_code(); } }; -} // namespace dcs - -/// Defining DataPointIdentifier explicitly as messageable -namespace framework -{ -template -struct is_messageable; -template <> -struct is_messageable : std::true_type { -}; -} // namespace framework - -} // namespace o2 +} // namespace o2::dcs // specailized std::hash namespace std @@ -243,11 +218,6 @@ struct hash { return std::hash{}(dpid.hash_code()); } }; - -template <> -struct is_trivially_copyable : std::true_type { -}; - } // namespace std #endif /* O2_DCS_DATAPOINT_IDENTIFIER_H */ diff --git a/Detectors/DCS/test/testDataPointTypes.cxx b/Detectors/DCS/test/testDataPointTypes.cxx index 491ebae1f5d00..dd7511f63217c 100644 --- a/Detectors/DCS/test/testDataPointTypes.cxx +++ b/Detectors/DCS/test/testDataPointTypes.cxx @@ -17,8 +17,6 @@ #include #include "DetectorsDCS/DataPointCompositeObject.h" #include "Framework/TypeTraits.h" -#include -#include #include #include @@ -26,7 +24,7 @@ typedef boost::mpl::list::value, true); + BOOST_CHECK_EQUAL(std::is_trivially_copyable_v, true); BOOST_CHECK_EQUAL(std::is_polymorphic::value, false); BOOST_CHECK_EQUAL(std::is_pointer::value, false); BOOST_CHECK_EQUAL(o2::framework::is_forced_non_messageable::value, false); diff --git a/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/TRUDataHandler.h b/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/TRUDataHandler.h index 811faf13a05ff..ea9820563dee6 100644 --- a/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/TRUDataHandler.h +++ b/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/TRUDataHandler.h @@ -13,6 +13,7 @@ #include #include +#include #include #include #include diff --git a/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/CTFCoder.h b/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/CTFCoder.h index c62e013447416..24649f73a4ca3 100644 --- a/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/CTFCoder.h +++ b/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/CTFCoder.h @@ -165,7 +165,9 @@ void CTFCoder::decompress(const CompressedDigits& cd, VDIG& digitVec, VCHAN& cha uint32_t firstEntry = 0, clCount = 0, chipCount = 0; o2::InteractionRecord ir(cd.header.firstBC, cd.header.firstOrbit); - + if (mBCShift && ir.toLong() >= mBCShift) { + ir -= mBCShift; + } for (uint32_t idig = 0; idig < cd.header.nTriggers; idig++) { // restore ROFRecord if (cd.orbitInc[idig]) { // non-0 increment => new orbit diff --git a/Detectors/FIT/FDD/workflow/src/EntropyDecoderSpec.cxx b/Detectors/FIT/FDD/workflow/src/EntropyDecoderSpec.cxx index 33c140b5bc198..43615b175734d 100644 --- a/Detectors/FIT/FDD/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/FIT/FDD/workflow/src/EntropyDecoderSpec.cxx @@ -29,6 +29,7 @@ EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdict mTimer.Stop(); mTimer.Reset(); mCTFCoder.setVerbosity(verbosity); + mCTFCoder.setSupportBCShifts(true); mCTFCoder.setDictBinding("ctfdict_FDD"); } diff --git a/Detectors/FIT/FT0/base/include/FT0Base/FT0DigParam.h b/Detectors/FIT/FT0/base/include/FT0Base/FT0DigParam.h index 2bf774859aa22..074d91bb04b27 100644 --- a/Detectors/FIT/FT0/base/include/FT0Base/FT0DigParam.h +++ b/Detectors/FIT/FT0/base/include/FT0Base/FT0DigParam.h @@ -31,8 +31,8 @@ struct FT0DigParam : o2::conf::ConfigurableParamHelper { float mAmpRecordUp = 15; // to [ns] float hitTimeOffsetA = 0; ///< hit time offset on the A side [ns] float hitTimeOffsetC = 0; ///< hit time offset on the C side [ns] - int mtrg_central_trh = 600.; // channels - int mtrg_semicentral_trh = 300.; // channels + int mtrg_central_trh = 40; // Tclu units (40 for pp and 1433 for PbPb in Run3) + int mtrg_semicentral_trh = 20; // Tclu units (20 for pp and 35 for PbPb in Run3) float mMip_in_V = 7; // MIP to mV float mPe_in_mip = 0.004; // invserse Np.e. in MIP 1./250. @@ -43,11 +43,12 @@ struct FT0DigParam : o2::conf::ConfigurableParamHelper { float mNoiseVar = 0.1; // noise level float mNoisePeriod = 1 / 0.9; // GHz low frequency noise period; short mTime_trg_gate = 153; // #channels as in TCM as in Pilot beams ('OR gate' setting in TCM tab in ControlServer) + short mTime_trg_vertex_gate = 100; // #channels as in TCM as in Pilot beams ('OR gate' setting in TCM tab in ControlServer) float mAmpThresholdForReco = 5; // only channels with amplitude higher will participate in calibration and collision time: 0.3 MIP short mTimeThresholdForReco = 1000; // only channels with time below will participate in calibration and collision time - float mMV_2_Nchannels = 2.2857143; // amplitude channel 7 mV ->16channels - float mMV_2_NchannelsInverse = 0.437499997; // inverse amplitude channel 7 mV ->16channels + float mMV_2_Nchannels = 2.; // amplitude channel 7 mV ->14channels + float mMV_2_NchannelsInverse = 0.5; // inverse amplitude channel 7 mV ->14channels (nowhere used) O2ParamDef(FT0DigParam, "FT0DigParam"); }; diff --git a/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CTFCoder.h b/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CTFCoder.h index 5dc367204e1a3..41f11e303db67 100644 --- a/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CTFCoder.h +++ b/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CTFCoder.h @@ -165,7 +165,9 @@ void CTFCoder::decompress(const CompressedDigits& cd, VDIG& digitVec, VCHAN& cha uint32_t firstEntry = 0, clCount = 0, chipCount = 0; o2::InteractionRecord ir(cd.header.firstBC, cd.header.firstOrbit); - + if (mBCShift && ir.toLong() >= mBCShift) { + ir -= mBCShift; + } for (uint32_t idig = 0; idig < cd.header.nTriggers; idig++) { // restore ROFRecord if (cd.orbitInc[idig]) { // non-0 increment => new orbit diff --git a/Detectors/FIT/FT0/simulation/src/Digitizer.cxx b/Detectors/FIT/FT0/simulation/src/Digitizer.cxx index aca012f1bc5a9..de432a85765c7 100644 --- a/Detectors/FIT/FT0/simulation/src/Digitizer.cxx +++ b/Detectors/FIT/FT0/simulation/src/Digitizer.cxx @@ -16,6 +16,13 @@ #include "CommonConstants/PhysicsConstants.h" #include "CommonDataFormat/InteractionRecord.h" +#include "DataFormatsFT0/LookUpTable.h" +#include "FT0Base/Constants.h" +#include +#include +#include +#include + #include "TMath.h" #include "TRandom.h" #include @@ -35,24 +42,84 @@ namespace o2::ft0 template Float signalForm_i(Float x) { - using namespace std; - Float const a = -0.45458; - Float const b = -0.83344945; - return x > Float(0) ? -(exp(b * x) - exp(a * x)) / Float(7.8446501) : Float(0); + float p0, p1, p2, p3, p4, p5, p6, p7; + p0 = 1.30853; + p1 = 0.516807; + p2 = 3.36714; + p3 = -1.01206; + p4 = 1.42832; + p5 = 1.1589; + p6 = 1.22019; + p7 = 0.426818; + + Double_t val = 0; + + if (x > p3) { + Double_t x1 = x - p3; + Double_t arg1 = (log(x1) - p0) / p1; + val += p2 * (1.0 / (x1 * p1 * sqrt(2 * TMath::Pi()))) * exp(-0.5 * arg1 * arg1); + } + + if (x > p7) { + Double_t x2 = x - p7; + Double_t arg2 = (log(x2) - p4) / p5; + val += p6 * (1.0 / (x2 * p5 * sqrt(2 * TMath::Pi()))) * exp(-0.5 * arg2 * arg2); + } + + return val; }; // integrated signal shape function inline float signalForm_integral(float x) { - using namespace std; - double const a = -0.45458; - double const b = -0.83344945; - if (x < 0) { - x = 0; + float p0, p1, p2, p3, p4, p5, p6, p7; + p0 = 1.30853; + p1 = 0.516807; + p2 = 3.36714; + p3 = -1.01206; + p4 = 1.42832; + p5 = 1.1589; + p6 = 1.22019; + p7 = 0.426818; + Double_t val = 0; + + if (x > p3) { + Double_t x1 = x - p3; + Double_t z1 = (log(x1) - p0) / (sqrt(2) * p1); + val += p2 * 0.5 * (1 + TMath::Erf(z1)); // norm1 * CDF1 } - return -(exp(b * x) / b - exp(a * x) / a) / 7.8446501; + + if (x > p7) { + Double_t x2 = x - p7; + Double_t z2 = (log(x2) - p4) / (sqrt(2) * p5); + val += p6 * 0.5 * (1 + TMath::Erf(z2)); // norm2 * CDF2 + } + + return val; +}; +/* +// signal shape function +template +Float signalForm_i(Float x) +{ +using namespace std; +Float const a = -0.45458; +Float const b = -0.83344945; +return x > Float(0) ? -(exp(b * x) - exp(a * x)) / Float(7.8446501) : Float(0); }; +// integrated signal shape function +inline float signalForm_integral(float x) +{ +using namespace std; +double const a = -0.45458; +double const b = -0.83344945; +if (x < 0) { + x = 0; +} +return -(exp(b * x) / b - exp(a * x) / a) / 7.8446501; +}; +*/ // SIMD version of the integrated signal shape function inline Vc::float_v signalForm_integralVc(Vc::float_v x) { @@ -249,8 +316,64 @@ void Digitizer::storeBC(BCCache& bc, if (bc.hits.empty()) { return; } + // Initialize mapping channelID -> PM hash and PM side (A/C) using FT0 LUT + static bool pmLutInitialized = false; + static std::array mChID2PMhash{}; + static std::map mMapPMhash2isAside; // hashed PM -> is A side + + if (!pmLutInitialized) { + std::map mapFEE2hash; // module name -> hashed PM id + uint8_t tcmHash = 0; + + const auto& lut = o2::ft0::SingleLUT::Instance().getVecMetadataFEE(); + auto lutSorted = lut; + std::sort(lutSorted.begin(), lutSorted.end(), + [](const auto& first, const auto& second) { return first.mModuleName < second.mModuleName; }); + + uint8_t binPos = 0; + for (const auto& lutEntry : lutSorted) { + const auto& moduleName = lutEntry.mModuleName; + const auto& moduleType = lutEntry.mModuleType; + const auto& strChID = lutEntry.mChannelID; + + auto [it, inserted] = mapFEE2hash.insert({moduleName, binPos}); + if (inserted) { + if (moduleName.find("PMA") != std::string::npos) { + mMapPMhash2isAside.insert({binPos, true}); + } else if (moduleName.find("PMC") != std::string::npos) { + mMapPMhash2isAside.insert({binPos, false}); + } + ++binPos; + } + + if (std::regex_match(strChID, std::regex("^[0-9]{1,3}$"))) { + int chID = std::stoi(strChID); + if (chID < o2::ft0::Constants::sNCHANNELS_PM) { + mChID2PMhash[chID] = mapFEE2hash[moduleName]; + } else { + LOG(fatal) << "Incorrect LUT entry: chID " << strChID << " | " << moduleName; + } + } else if (moduleType != "TCM") { + LOG(fatal) << "Non-TCM module w/o numerical chID: chID " << strChID << " | " << moduleName; + } else { // TCM + tcmHash = mapFEE2hash[moduleName]; + } + } + + pmLutInitialized = true; + } + int n_hit_A = 0, n_hit_C = 0, mean_time_A = 0, mean_time_C = 0; int summ_ampl_A = 0, summ_ampl_C = 0; + int sum_A_ampl = 0, sum_C_ampl = 0; + int nPMTs = mGeometry.NCellsA * 4 + mGeometry.NCellsC * 4; + std::vector sum_ampl_ipmt(nPMTs, 0); + // Per-PM summed charge (like in digits2trgFT0) + std::map mapPMhash2sumAmpl; + for (const auto& entry : mMapPMhash2isAside) { + mapPMhash2sumAmpl.insert({entry.first, 0}); + } + int vertex_time; const auto& params = FT0DigParam::Instance(); int first = digitsCh.size(), nStored = 0; @@ -297,6 +420,10 @@ void Digitizer::storeBC(BCCache& bc, if (is_time_in_signal_gate) { chain |= (1 << o2::ft0::ChannelData::EEventDataBit::kIsCFDinADCgate); chain |= (1 << o2::ft0::ChannelData::EEventDataBit::kIsEventInTVDC); + // Sum channel charge per PM (similar logic as in digits2trgFT0) + if (ipmt < o2::ft0::Constants::sNCHANNELS_PM) { + mapPMhash2sumAmpl[mChID2PMhash[static_cast(ipmt)]] += static_cast(amp); + } } digitsCh.emplace_back(ipmt, smeared_time, int(amp), chain); nStored++; @@ -308,6 +435,8 @@ void Digitizer::storeBC(BCCache& bc, continue; } + sum_ampl_ipmt[ipmt] += amp; + if (is_A_side) { n_hit_A++; summ_ampl_A += amp; @@ -318,17 +447,47 @@ void Digitizer::storeBC(BCCache& bc, mean_time_C += smeared_time; } } + + for (size_t i = 0; i < sum_ampl_ipmt.size(); i++) { + sum_ampl_ipmt[i] = sum_ampl_ipmt[i] >> 3; + if (i < 4 * mGeometry.NCellsA) { + sum_A_ampl += sum_ampl_ipmt[i]; + } else { + sum_C_ampl += sum_ampl_ipmt[i]; + } + } + + // Sum over PMs (using per-PM map) for debug/monitoring + int sum_PM_ampl_debug = 0; + int sum_PM_ampl_A_debug = 0; + int sum_PM_ampl_C_debug = 0; + for (const auto& entry : mapPMhash2sumAmpl) { + int pmAmpl = (entry.second >> 3); + sum_PM_ampl_debug += pmAmpl; + auto itSide = mMapPMhash2isAside.find(entry.first); + if (itSide != mMapPMhash2isAside.end()) { + if (itSide->second) { + sum_PM_ampl_A_debug += pmAmpl; + } else { + sum_PM_ampl_C_debug += pmAmpl; + } + } + } + LOG(debug) << "Sum PM amplitude (LUT-based): total=" << sum_PM_ampl_debug + << " A-side=" << sum_PM_ampl_A_debug + << " C-side=" << sum_PM_ampl_C_debug; + Bool_t is_A, is_C, isVertex, is_Central, is_SemiCentral = 0; is_A = n_hit_A > 0; is_C = n_hit_C > 0; - is_Central = summ_ampl_A + summ_ampl_C >= params.mtrg_central_trh; - is_SemiCentral = summ_ampl_A + summ_ampl_C >= params.mtrg_semicentral_trh; + is_Central = sum_PM_ampl_A_debug + sum_PM_ampl_C_debug >= 2 * params.mtrg_central_trh; + is_SemiCentral = sum_PM_ampl_A_debug + sum_PM_ampl_C_debug >= 2 * params.mtrg_semicentral_trh && !is_Central; uint32_t amplA = is_A ? summ_ampl_A * 0.125 : -5000; // sum amplitude A side / 8 (hardware) uint32_t amplC = is_C ? summ_ampl_C * 0.125 : -5000; // sum amplitude C side / 8 (hardware) int timeA = is_A ? mean_time_A / n_hit_A : -5000; // average time A side int timeC = is_C ? mean_time_C / n_hit_C : -5000; // average time C side vertex_time = (timeC - timeA) * 0.5; - isVertex = is_A && is_C && (vertex_time > -params.mTime_trg_gate && vertex_time < params.mTime_trg_gate); + isVertex = is_A && is_C && (vertex_time > -params.mTime_trg_vertex_gate && vertex_time < params.mTime_trg_vertex_gate); LOG(debug) << " A " << is_A << " timeA " << timeA << " mean_time_A " << mean_time_A << " n_hit_A " << n_hit_A << " C " << is_C << " timeC " << timeC << " mean_time_C " << mean_time_C << " n_hit_C " << n_hit_C << " vertex_time " << vertex_time; Triggers triggers; bool isLaser = false; diff --git a/Detectors/FIT/FT0/workflow/src/EntropyDecoderSpec.cxx b/Detectors/FIT/FT0/workflow/src/EntropyDecoderSpec.cxx index 066c5cc547c2e..97ea337705fee 100644 --- a/Detectors/FIT/FT0/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/FIT/FT0/workflow/src/EntropyDecoderSpec.cxx @@ -29,6 +29,7 @@ EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdict mTimer.Stop(); mTimer.Reset(); mCTFCoder.setVerbosity(verbosity); + mCTFCoder.setSupportBCShifts(true); mCTFCoder.setDictBinding("ctfdict_FT0"); } diff --git a/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/CTFCoder.h b/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/CTFCoder.h index 80dcd6060455b..082fbd93a705a 100644 --- a/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/CTFCoder.h +++ b/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/CTFCoder.h @@ -168,7 +168,9 @@ void CTFCoder::decompress(const CompressedDigits& cd, VDIG& digitVec, VCHAN& cha uint32_t firstEntry = 0, clCount = 0, chipCount = 0; o2::InteractionRecord ir(cd.header.firstBC, cd.header.firstOrbit); - + if (mBCShift && ir.toLong() >= mBCShift) { + ir -= mBCShift; + } for (uint32_t idig = 0; idig < cd.header.nTriggers; idig++) { // restore ROFRecord if (cd.orbitInc[idig]) { // non-0 increment => new orbit diff --git a/Detectors/FIT/FV0/simulation/include/FV0Simulation/FV0DigParam.h b/Detectors/FIT/FV0/simulation/include/FV0Simulation/FV0DigParam.h index 383fa4cb494c1..6462323a279b7 100644 --- a/Detectors/FIT/FV0/simulation/include/FV0Simulation/FV0DigParam.h +++ b/Detectors/FIT/FV0/simulation/include/FV0Simulation/FV0DigParam.h @@ -69,6 +69,10 @@ struct FV0DigParam : o2::conf::ConfigurableParamHelper { uint8_t defaultChainQtc = 0x48; // only 2 flags are set by default in simulation: kIsCFDinADCgate and kIsEventInTVDC float mAmpThresholdForReco = 24; // only channels with amplitude higher will participate in calibration and collision time short mTimeThresholdForReco = 1000; // only channels with time below will participate in calibration and collision time + int NchannelsLevel = 2; // trigger Nchannels + float InnerChargeLevel = 4; // InnerRingsChargeLevel + float OuterChargeLevel = 4; // OuterRingsChargeLevel + float ChargeLevel = 8; // ChargeLevel O2ParamDef(FV0DigParam, "FV0DigParam"); }; diff --git a/Detectors/FIT/FV0/simulation/src/Digitizer.cxx b/Detectors/FIT/FV0/simulation/src/Digitizer.cxx index 8c1d2dc8824e2..3237f9bab7879 100644 --- a/Detectors/FIT/FV0/simulation/src/Digitizer.cxx +++ b/Detectors/FIT/FV0/simulation/src/Digitizer.cxx @@ -38,8 +38,8 @@ void Digitizer::clear() void Digitizer::init() { LOG(info) << "init"; - mNBins = FV0DigParam::Instance().waveformNbins; //Will be computed using detector set-up from CDB - mBinSize = FV0DigParam::Instance().waveformBinWidth; //Will be set-up from CDB + mNBins = FV0DigParam::Instance().waveformNbins; // Will be computed using detector set-up from CDB + mBinSize = FV0DigParam::Instance().waveformBinWidth; // Will be set-up from CDB mNTimeBinsPerBC = std::lround(o2::constants::lhc::LHCBunchSpacingNS / mBinSize); // 1920 bins/BC for (Int_t detID = 0; detID < Constants::nFv0Channels; detID++) { @@ -149,8 +149,8 @@ void Digitizer::process(const std::vector& hits, createPulse(mipFraction, hit.GetTrackID(), hitTime, hit.GetPos().R(), cachedIR, nCachedIR, detId); - } //while loop - } //hitloop + } // while loop + } // hitloop } void Digitizer::createPulse(float mipFraction, int parID, const double hitTime, const float hitR, @@ -200,7 +200,7 @@ void Digitizer::createPulse(float mipFraction, int parID, const double hitTime, } added[ir] = true; } - ///Add MC labels to BCs for those contributed to the PMT signal + /// Add MC labels to BCs for those contributed to the PMT signal for (int ir = 0; ir < nCachedIR; ir++) { if (added[ir]) { auto bcCache = getBCCache(cachedIR[ir]); @@ -238,6 +238,8 @@ void Digitizer::storeBC(const BCCache& bc, int8_t nTotFiredCells = 0; int8_t nTrgFiredCells = 0; // number of fired cells, that follow additional trigger conditions (time gate) int totalChargeAllRing = 0; + int totalChargeInnerRing = 0; + int totalChargeOuterRing = 0; int32_t avgTime = 0; double nSignalInner = 0; double nSignalOuter = 0; @@ -285,8 +287,10 @@ void Digitizer::storeBC(const BCCache& bc, avgTime += iCfdZero; if (iPmt < 24) { nSignalInner++; + totalChargeInnerRing += iTotalCharge; } else { nSignalOuter++; + totalChargeOuterRing += iTotalCharge; } } } @@ -300,13 +304,15 @@ void Digitizer::storeBC(const BCCache& bc, } else { avgTime = o2::fit::Triggers::DEFAULT_TIME; } - ///Triggers for FV0 - bool isA, isAIn, isAOut, isCen, isSCen; + /// Triggers for FV0 + bool isA, isNchannels, isAIn, isAOut, isTotalCharge; isA = nTrgFiredCells > 0; - isAIn = nSignalInner > 0; // ring 1,2 and 3 - isAOut = nSignalOuter > 0; // ring 4 and 5 - isCen = totalChargeAllRing > FV0DigParam::Instance().adcChargeCenThr; - isSCen = totalChargeAllRing > FV0DigParam::Instance().adcChargeSCenThr; + isNchannels = nTrgFiredCells > FV0DigParam::Instance().NchannelsLevel; + // isAIn = nSignalInner > FV0DigParam::Instance().NchannelsLevel; // ring 1,2 and 3 + isAIn = 0.125 * totalChargeInnerRing > 2 * FV0DigParam::Instance().InnerChargeLevel; // ring 1,2 and 3 + // isAOut = nSignalOuter > FV0DigParam::Instance().NchannelsLevel; // ring 4 and 5 + isAOut = 0.125 * totalChargeOuterRing > 2 * FV0DigParam::Instance().OuterChargeLevel; // ring 4 and 5 + isTotalCharge = 0.125 * totalChargeAllRing > 2 * FV0DigParam::Instance().ChargeLevel; Triggers triggers; const int unusedCharge = o2::fit::Triggers::DEFAULT_AMP; @@ -314,10 +320,10 @@ void Digitizer::storeBC(const BCCache& bc, const int unusedZero = o2::fit::Triggers::DEFAULT_ZERO; const bool unusedBitsInSim = false; // bits related to laser and data validity const bool bitDataIsValid = true; - triggers.setTriggers(isA, isAIn, isAOut, isCen, isSCen, nTrgFiredCells, (int8_t)unusedZero, + triggers.setTriggers(isA, isAIn, isAOut, isTotalCharge, isNchannels, nTrgFiredCells, (int8_t)unusedZero, (int32_t)(0.125 * totalChargeAllRing), (int32_t)unusedCharge, (int16_t)avgTime, (int16_t)unusedTime, unusedBitsInSim, unusedBitsInSim, bitDataIsValid); digitsBC.emplace_back(first, nTotFiredCells, bc, triggers, mEventId - 1); - digitsTrig.emplace_back(bc, isA, isAIn, isAOut, isCen, isSCen); + digitsTrig.emplace_back(bc, isA, isAIn, isAOut, isTotalCharge, isNchannels); for (auto const& lbl : bc.labels) { labels.addElement(nBC, lbl); } @@ -342,8 +348,8 @@ Int_t Digitizer::SimulateLightYield(Int_t pmt, Int_t nPhot) const //--------------------------------------------------------------------------- Float_t Digitizer::IntegrateCharge(const ChannelDigitF& pulse) const { - int const chargeIntMin = FV0DigParam::Instance().isIntegrateFull ? 0 : (FV0DigParam::Instance().avgCfdTimeForMip - 6.0) / mBinSize; //Charge integration offset (cfd mean time - 6 ns) - int const chargeIntMax = FV0DigParam::Instance().isIntegrateFull ? mNTimeBinsPerBC : (FV0DigParam::Instance().avgCfdTimeForMip + 14.0) / mBinSize; //Charge integration offset (cfd mean time + 14 ns) + int const chargeIntMin = FV0DigParam::Instance().isIntegrateFull ? 0 : (FV0DigParam::Instance().avgCfdTimeForMip - 6.0) / mBinSize; // Charge integration offset (cfd mean time - 6 ns) + int const chargeIntMax = FV0DigParam::Instance().isIntegrateFull ? mNTimeBinsPerBC : (FV0DigParam::Instance().avgCfdTimeForMip + 14.0) / mBinSize; // Charge integration offset (cfd mean time + 14 ns) if (chargeIntMin < 0 || chargeIntMin > mNTimeBinsPerBC || chargeIntMax > mNTimeBinsPerBC) { LOG(fatal) << "invalid indicess: chargeInMin=" << chargeIntMin << " chargeIntMax=" << chargeIntMax; } @@ -400,7 +406,7 @@ float Digitizer::getDistFromCellCenter(UInt_t cellId, double hitx, double hity) double a = -(y0 - pCell->y) / (x0 - pCell->x); double b = 1; double c = -(y0 - a * x0); - //Return the distance from hit to this line + // Return the distance from hit to this line return (a * hitx + b * hity + c) / TMath::Sqrt(a * a + b * b); } diff --git a/Detectors/FIT/FV0/workflow/src/EntropyDecoderSpec.cxx b/Detectors/FIT/FV0/workflow/src/EntropyDecoderSpec.cxx index 7babe9fdea6ed..6cf8043cf683f 100644 --- a/Detectors/FIT/FV0/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/FIT/FV0/workflow/src/EntropyDecoderSpec.cxx @@ -29,6 +29,7 @@ EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdict mTimer.Stop(); mTimer.Reset(); mCTFCoder.setVerbosity(verbosity); + mCTFCoder.setSupportBCShifts(true); mCTFCoder.setDictBinding("ctfdict_FV0"); } diff --git a/Detectors/Filtering/src/FilteringSpec.cxx b/Detectors/Filtering/src/FilteringSpec.cxx index bcf3c6c3539d4..ea82b1456d955 100644 --- a/Detectors/Filtering/src/FilteringSpec.cxx +++ b/Detectors/Filtering/src/FilteringSpec.cxx @@ -46,7 +46,7 @@ #include "ReconstructionDataFormats/Cascade.h" #include "MCHTracking/TrackExtrap.h" #include "MCHTracking/TrackParam.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsVertexing/PVertexerParams.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "ReconstructionDataFormats/Track.h" diff --git a/Detectors/Filtering/src/filtering-workflow.cxx b/Detectors/Filtering/src/filtering-workflow.cxx index 8e36cfc36b197..faf5463281ed8 100644 --- a/Detectors/Filtering/src/filtering-workflow.cxx +++ b/Detectors/Filtering/src/filtering-workflow.cxx @@ -17,6 +17,7 @@ #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsRaw/HBFUtilsInitializer.h" #include "Framework/CallbacksPolicy.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -36,6 +37,7 @@ void customize(std::vector& workflowOptions) {"disable-secondary-vertices", o2::framework::VariantType::Bool, false, {"disable filling secondary vertices"}}, {"data-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of sources to use"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; + o2::itsmft::DPLAlpideParamInitializer::addConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } diff --git a/Detectors/GlobalTracking/include/GlobalTracking/MatchCosmics.h b/Detectors/GlobalTracking/include/GlobalTracking/MatchCosmics.h index 7720d75590475..9aad1a820d08b 100644 --- a/Detectors/GlobalTracking/include/GlobalTracking/MatchCosmics.h +++ b/Detectors/GlobalTracking/include/GlobalTracking/MatchCosmics.h @@ -26,7 +26,6 @@ #include "SimulationDataFormat/MCCompLabel.h" #include "GlobalTracking/MatchCosmicsParams.h" #include "CommonUtils/TreeStreamRedirector.h" -#include "TPCFastTransform.h" #define _ALLOW_DEBUG_TREES_COSM // to allow debug and control tree output @@ -38,7 +37,7 @@ class VDriftCorrFact; } namespace gpu { -class CorrectionMapsHelper; +class TPCFastTransformPOD; } namespace globaltracking { @@ -85,7 +84,7 @@ class MatchCosmics GTrackID origID; ///< track origin id int matchID = MinusOne; ///< entry (none if MinusOne) of its match in the vector of matches }; - void setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph); + void setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph); void setTPCVDrift(const o2::tpc::VDriftCorrFact& v); void setITSROFrameLengthMUS(float fums) { mITSROFrameLengthMUS = fums; } void setITSDict(const o2::itsmft::TopologyDictionary* dict) { mITSDict = dict; } @@ -138,14 +137,14 @@ class MatchCosmics std::vector mRecords; std::vector mWinners; const o2::itsmft::TopologyDictionary* mITSDict = nullptr; // cluster patterns dictionary - o2::gpu::CorrectionMapsHelper* mTPCCorrMapsHelper = nullptr; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps = nullptr; int mTFCount = 0; - float mTPCVDriftRef = -1.; ///< TPC nominal drift speed in cm/microseconds - float mTPCVDriftCorrFact = 1.; ///< TPC nominal correction factort (wrt ref) - float mTPCVDrift = -1.; ///< TPC drift speed in cm/microseconds + float mTPCVDriftRef = -1.; ///< TPC nominal drift speed in cm/microseconds + float mTPCVDriftCorrFact = 1.; ///< TPC nominal correction factort (wrt ref) + float mTPCVDrift = -1.; ///< TPC drift speed in cm/microseconds float mTPCDriftTimeOffset = 0.; ///< drift time offset in mus - float mTPCTBinMUS = 0.; ///< TPC time bin duration in microseconds - float mBz = 0; ///< nominal Bz + float mTPCTBinMUS = 0.; ///< TPC time bin duration in microseconds + float mBz = 0; ///< nominal Bz bool mFieldON = true; bool mUseMC = true; float mITSROFrameLengthMUS = 0.; diff --git a/Detectors/GlobalTracking/include/GlobalTracking/MatchHMP.h b/Detectors/GlobalTracking/include/GlobalTracking/MatchHMP.h index fbbadc1820ee8..308ba9e61f794 100644 --- a/Detectors/GlobalTracking/include/GlobalTracking/MatchHMP.h +++ b/Detectors/GlobalTracking/include/GlobalTracking/MatchHMP.h @@ -41,7 +41,6 @@ #include "DataFormatsTPC/TrackTPC.h" #include "DataFormatsTRD/TrackTRD.h" #include "ReconstructionDataFormats/PID.h" -#include "TPCFastTransform.h" #include "CommonDataFormat/InteractionRecord.h" #include "ReconstructionDataFormats/MatchInfoHMP.h" #include "ReconstructionDataFormats/TrackHMP.h" diff --git a/Detectors/GlobalTracking/include/GlobalTracking/MatchTOF.h b/Detectors/GlobalTracking/include/GlobalTracking/MatchTOF.h index b66e5b143a898..8447fbc42cdce 100644 --- a/Detectors/GlobalTracking/include/GlobalTracking/MatchTOF.h +++ b/Detectors/GlobalTracking/include/GlobalTracking/MatchTOF.h @@ -37,9 +37,7 @@ #include "DataFormatsTPC/TrackTPC.h" #include "DataFormatsTRD/TrackTRD.h" #include "ReconstructionDataFormats/PID.h" -#include "TPCFastTransform.h" #include "CommonDataFormat/InteractionRecord.h" -#include "CorrectionMapsHelper.h" #include "GlobalTracking/MatchTOFParams.h" // from FIT @@ -152,7 +150,7 @@ class MatchTOF std::vector& getMatchedTOFLabelsVector(trkType index) { return mOutTOFLabels[index]; } ///< get vector of TOF labels of matched tracks void setTPCVDrift(const o2::tpc::VDriftCorrFact& v); - void setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph); + void setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph, float lumi); void setFIT(bool value = true) { mIsFIT = value; } static int findFITIndex(int bc, const gsl::span& FITRecPoints, unsigned long firstOrbit); @@ -292,7 +290,8 @@ class MatchTOF gsl::span mTPCRefitterOccMap; ///< externally set TPC clusters occupancy map const o2::tpc::ClusterNativeAccess* mTPCClusterIdxStruct = nullptr; ///< struct holding the TPC cluster indices - o2::gpu::CorrectionMapsHelper* mTPCCorrMapsHelper = nullptr; ///< TPC cluster transformation + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps = nullptr; ///< TPC cluster transformation + float mCTPLumi = {-1}; std::unique_ptr mTPCRefitter; ///< TPC refitter used for TPC tracks refit during the reconstruction const o2::dataformats::MCTruthContainer* mTOFClusLabels; ///< input TOF clusters MC labels (pointer to read from tree) diff --git a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h index 00f2fc157a5ec..e736f0c9c8a42 100644 --- a/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h +++ b/Detectors/GlobalTracking/include/GlobalTracking/MatchTPCITS.h @@ -48,14 +48,13 @@ #include "DataFormatsTPC/ClusterNativeHelper.h" #include "DataFormatsTPC/VDriftCorrFact.h" #include "ITSReconstruction/RecoGeomHelper.h" -#include "TPCFastTransform.h" #include "GPUO2InterfaceRefit.h" #include "GPUTPCGeometry.h" #include "GlobalTracking/MatchTPCITSParams.h" #include "DataFormatsITSMFT/TopologyDictionary.h" #include "DataFormatsITSMFT/TrkClusRef.h" #include "ITSMFTReconstruction/ChipMappingITS.h" -#include "CorrectionMapsHelper.h" +#include "TPCFastTransformPOD.h" #if !defined(__CINT__) && !defined(__MAKECINT__) && !defined(__ROOTCLING__) && !defined(__CLING__) #include "MemoryResources/MemoryResources.h" #endif @@ -434,7 +433,7 @@ class MatchTPCITS mVDriftCalibOn = v; } void setTPCVDrift(const o2::tpc::VDriftCorrFact& v); - void setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph); + void setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph, float lumi); ///< print settings void print() const; @@ -641,7 +640,8 @@ class MatchTPCITS float mMinITSTrackPtInv = 999.; ///< cutoff on ITS track inverse pT bool mVDriftCalibOn = false; ///< flag to produce VDrift calibration data o2::tpc::VDriftCorrFact mTPCDrift{}; - o2::gpu::CorrectionMapsHelper* mTPCCorrMapsHelper = nullptr; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps = nullptr; + float mLumiCTP{-1}; std::unique_ptr mTPCRefitter; ///< TPC refitter used for TPC tracks refit during the reconstruction diff --git a/Detectors/GlobalTracking/src/MatchCosmics.cxx b/Detectors/GlobalTracking/src/MatchCosmics.cxx index 3c20ecad2f36c..615cfcb84819b 100644 --- a/Detectors/GlobalTracking/src/MatchCosmics.cxx +++ b/Detectors/GlobalTracking/src/MatchCosmics.cxx @@ -32,7 +32,7 @@ #include "CommonConstants/GeomConstants.h" #include "DataFormatsTPC/WorkflowHelper.h" #include "DataFormatsTPC/VDriftCorrFact.h" -#include "CorrectionMapsHelper.h" +#include "TPCFastTransformPOD.h" #include #include @@ -93,7 +93,7 @@ void MatchCosmics::refitWinners(const o2::globaltracking::RecoContainer& data) std::unique_ptr tpcRefitter; if (data.inputsTPCclusters) { tpcRefitter = std::make_unique(&data.inputsTPCclusters->clusterIndex, - mTPCCorrMapsHelper, mBz, + mTPCCorrMaps, mBz, tpcClusRefs.data(), 0, tpcClusShMap.data(), tpcClusOccMap.data(), tpcClusOccMap.size(), nullptr, o2::base::Propagator::Instance()); } @@ -597,9 +597,9 @@ void MatchCosmics::setTPCVDrift(const o2::tpc::VDriftCorrFact& v) } //______________________________________________ -void MatchCosmics::setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph) +void MatchCosmics::setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph) { - mTPCCorrMapsHelper = maph; + mTPCCorrMaps = maph; } #endif diff --git a/Detectors/GlobalTracking/src/MatchTOF.cxx b/Detectors/GlobalTracking/src/MatchTOF.cxx index 6a3486dd12044..5416a612ad1f3 100644 --- a/Detectors/GlobalTracking/src/MatchTOF.cxx +++ b/Detectors/GlobalTracking/src/MatchTOF.cxx @@ -255,9 +255,10 @@ void MatchTOF::setTPCVDrift(const o2::tpc::VDriftCorrFact& v) } //______________________________________________ -void MatchTOF::setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph) +void MatchTOF::setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph, float lumi) { - mTPCCorrMapsHelper = maph; + mTPCCorrMaps = maph; + mCTPLumi = lumi; } //______________________________________________ @@ -2085,8 +2086,9 @@ void MatchTOF::updateTimeDependentParams() mMaxInvPt = std::abs(mBz) > 0.1 ? 1. / (std::abs(mBz) * 0.05) : 999.; const auto& trackTune = TrackTuneParams::Instance(); - float scale = mTPCCorrMapsHelper->getInstLumiCTP(); + float scale = mCTPLumi; if (scale < 0.f) { + LOGP(warning, "Negative scale factor for TPC covariance correction, setting it to zero"); scale = 0.f; } mCovDiagInner = trackTune.getCovInnerTotal(scale); @@ -2166,7 +2168,7 @@ bool MatchTOF::makeConstrainedTPCTrack(int matchedID, o2::dataformats::TrackTPCT void MatchTOF::checkRefitter() { if (mTPCClusterIdxStruct) { - mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, mTPCCorrMapsHelper, mBz, + mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, mTPCCorrMaps, mBz, mTPCTrackClusIdx.data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size(), nullptr, o2::base::Propagator::Instance()); } diff --git a/Detectors/GlobalTracking/src/MatchTPCITS.cxx b/Detectors/GlobalTracking/src/MatchTPCITS.cxx index 73216c8ce1eac..1457790c7c531 100644 --- a/Detectors/GlobalTracking/src/MatchTPCITS.cxx +++ b/Detectors/GlobalTracking/src/MatchTPCITS.cxx @@ -205,9 +205,10 @@ void MatchTPCITS::setTPCVDrift(const o2::tpc::VDriftCorrFact& v) } //______________________________________________ -void MatchTPCITS::setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph) +void MatchTPCITS::setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph, float lumi) { - mTPCCorrMapsHelper = maph; + mTPCCorrMaps = maph; + mLumiCTP = lumi; } //______________________________________________ @@ -286,8 +287,9 @@ void MatchTPCITS::updateTimeDependentParams() mTPCmeanX0Inv = matbd.meanX2X0 / matbd.length; const auto& trackTune = TrackTuneParams::Instance(); - float scale = mTPCCorrMapsHelper->getInstLumiCTP(); + float scale = mLumiCTP; if (scale < 0.f) { + LOGP(warning, "Negative scale factor for TPC covariance correction, setting it to zero"); scale = 0.f; } mCovDiagInner = trackTune.getCovInnerTotal(scale); @@ -504,7 +506,7 @@ bool MatchTPCITS::prepareTPCData() mTPCSectIndexCache[sec].reserve(100 + 1.2 * ntrW / o2::constants::math::NSectors); } - mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, mTPCCorrMapsHelper, mBz, mTPCTrackClusIdx.data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size(), nullptr, o2::base::Propagator::Instance()); + mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, mTPCCorrMaps, mBz, mTPCTrackClusIdx.data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size(), nullptr, o2::base::Propagator::Instance()); mNTPCOccBinLength = mTPCRefitter->getParam()->rec.tpc.occupancyMapTimeBins; mTBinClOcc.clear(); if (mNTPCOccBinLength > 1 && mTPCRefitterOccMap.size()) { @@ -707,7 +709,7 @@ bool MatchTPCITS::prepareITSData() mITSWork.reserve(mITSTracksArray.size()); // total N ITS clusters in TF - const auto& lastClROF = mITSClusterROFRec[nROFs - 1]; + const auto& lastClROF = mITSClusterROFRec.back(); int nITSClus = lastClROF.getFirstEntry() + lastClROF.getNEntries(); mABClusterLinkIndex.resize(nITSClus, MinusOne); for (int sec = o2::constants::math::NSectors; sec--;) { diff --git a/Detectors/GlobalTrackingWorkflow/helpers/CMakeLists.txt b/Detectors/GlobalTrackingWorkflow/helpers/CMakeLists.txt index 8ae5378222f5d..357f16bb0c21b 100644 --- a/Detectors/GlobalTrackingWorkflow/helpers/CMakeLists.txt +++ b/Detectors/GlobalTrackingWorkflow/helpers/CMakeLists.txt @@ -41,4 +41,4 @@ o2_add_executable(track-cluster-reader COMPONENT_NAME global TARGETVARNAME targetName SOURCES src/GlobalTrackClusterReader.cxx - PUBLIC_LINK_LIBRARIES O2::GlobalTrackingWorkflowHelpers O2::DetectorsRaw) + PUBLIC_LINK_LIBRARIES O2::GlobalTrackingWorkflowHelpers O2::DetectorsRaw O2::DataFormatsITSMFT) diff --git a/Detectors/GlobalTrackingWorkflow/helpers/src/GlobalTrackClusterReader.cxx b/Detectors/GlobalTrackingWorkflow/helpers/src/GlobalTrackClusterReader.cxx index 3fc8df70fdd84..a004432643763 100644 --- a/Detectors/GlobalTrackingWorkflow/helpers/src/GlobalTrackClusterReader.cxx +++ b/Detectors/GlobalTrackingWorkflow/helpers/src/GlobalTrackClusterReader.cxx @@ -11,6 +11,7 @@ #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "ReconstructionDataFormats/GlobalTrackID.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "CommonUtils/ConfigurableParam.h" #include "DetectorsRaw/HBFUtilsInitializer.h" #include "Framework/CallbacksPolicy.h" @@ -37,6 +38,7 @@ void customize(std::vector& workflowOptions) {"ir-frames-its", VariantType::Bool, false, {"read ITS IR frames"}}, {"disable-root-input", o2::framework::VariantType::Bool, false, {"disable reading root files, essentially making this workflow void, but needed for compatibility"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; + o2::itsmft::DPLAlpideParamInitializer::addConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } diff --git a/Detectors/GlobalTrackingWorkflow/helpers/src/InputHelper.cxx b/Detectors/GlobalTrackingWorkflow/helpers/src/InputHelper.cxx index c6c163f4b8911..e4c1e40b3a4d3 100644 --- a/Detectors/GlobalTrackingWorkflow/helpers/src/InputHelper.cxx +++ b/Detectors/GlobalTrackingWorkflow/helpers/src/InputHelper.cxx @@ -14,6 +14,7 @@ #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "Framework/ConfigParamRegistry.h" #include "ITSMFTWorkflow/ClusterReaderSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "ITSWorkflow/TrackReaderSpec.h" #include "MFTWorkflow/TrackReaderSpec.h" #include "TPCReaderWorkflow/TrackReaderSpec.h" @@ -79,13 +80,15 @@ int InputHelper::addInputSpecs(const ConfigContext& configcontext, WorkflowSpec& specs.emplace_back(o2::its::getITSTrackReaderSpec(maskTracksMC[GID::ITS])); } if (maskClusters[GID::ITS]) { - specs.emplace_back(o2::itsmft::getITSClusterReaderSpec(maskClustersMC[GID::ITS], true)); + bool doStag = itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); + specs.emplace_back(o2::itsmft::getITSClusterReaderSpec(maskClustersMC[GID::ITS], doStag, true)); } if (maskTracks[GID::MFT]) { specs.emplace_back(o2::mft::getMFTTrackReaderSpec(maskTracksMC[GID::MFT])); } if (maskClusters[GID::MFT]) { - specs.emplace_back(o2::itsmft::getMFTClusterReaderSpec(maskClustersMC[GID::MFT], true)); + bool doStag = itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(configcontext); + specs.emplace_back(o2::itsmft::getMFTClusterReaderSpec(maskClustersMC[GID::MFT], doStag, true)); } if (maskTracks[GID::MCH] || maskMatches[GID::MCHMID]) { specs.emplace_back(o2::mch::getTrackReaderSpec(maskTracksMC[GID::MCH] || maskTracksMC[GID::MCHMID])); diff --git a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/CosmicsMatchingSpec.h b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/CosmicsMatchingSpec.h index e0e74c3058086..25553c5d56d33 100644 --- a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/CosmicsMatchingSpec.h +++ b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/CosmicsMatchingSpec.h @@ -20,16 +20,11 @@ using namespace o2::framework; namespace o2 { -namespace tpc -{ -struct CorrectionMapsLoaderGloOpts; -} - namespace globaltracking { /// create a processor spec -framework::DataProcessorSpec getCosmicsMatchingSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useMC, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts); +framework::DataProcessorSpec getCosmicsMatchingSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useMC); } // namespace globaltracking } // namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/SecondaryVertexingSpec.h b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/SecondaryVertexingSpec.h index b8071ae83d347..9211a9cb0c264 100644 --- a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/SecondaryVertexingSpec.h +++ b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/SecondaryVertexingSpec.h @@ -20,16 +20,12 @@ namespace o2 { -namespace tpc -{ -struct CorrectionMapsLoaderGloOpts; -} 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, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts); +o2::framework::DataProcessorSpec getSecondaryVertexingSpec(o2::dataformats::GlobalTrackID::mask_t src, bool enableCasc, bool enable3body, bool enableStrangenesTracking, bool enableCCDBParams, bool useMC, bool useGeom); } // namespace vertexing } // namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TOFMatcherSpec.h b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TOFMatcherSpec.h index 79a4ee0ce0360..a1102af6a1f75 100644 --- a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TOFMatcherSpec.h +++ b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TOFMatcherSpec.h @@ -21,15 +21,11 @@ using namespace o2::framework; namespace o2 { -namespace tpc -{ -struct CorrectionMapsLoaderGloOpts; -} namespace globaltracking { /// create a processor spec -framework::DataProcessorSpec getTOFMatcherSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useMC, bool useFIT, bool tpcRefit, bool strict, float extratolerancetrd, bool pushMatchable, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, int nlanes = 1); +framework::DataProcessorSpec getTOFMatcherSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useMC, bool useFIT, bool tpcRefit, bool strict, float extratolerancetrd, bool pushMatchable, bool requestCTPLumi, int nlanes = 1); } // namespace globaltracking } // namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TPCITSMatchingSpec.h b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TPCITSMatchingSpec.h index 4aaed7d64eec5..56240fd2c8f98 100644 --- a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TPCITSMatchingSpec.h +++ b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/TPCITSMatchingSpec.h @@ -20,14 +20,10 @@ using namespace o2::framework; namespace o2 { -namespace tpc -{ -struct CorrectionMapsLoaderGloOpts; -} 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, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts); +framework::DataProcessorSpec getTPCITSMatchingSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useFT0, bool calib, bool skipTPCOnly, bool useGeom, bool useMC, bool requestCTPLumi); } // namespace globaltracking } // namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx index 34c41ec234dc5..095ede4f6581d 100644 --- a/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx @@ -40,14 +40,14 @@ #include "Headers/DataHeader.h" #include "CommonDataFormat/InteractionRecord.h" #include "ITSBase/GeometryTGeo.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsGlobalTracking/RecoContainer.h" #include "Framework/Task.h" #include "Framework/CCDBParamSpec.h" #include "ITSMFTReconstruction/ClustererParam.h" #include "DetectorsBase/GRPGeomHelper.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCFastTransformPOD.h" using namespace o2::framework; using MCLabelsTr = gsl::span; @@ -62,12 +62,7 @@ namespace globaltracking class CosmicsMatchingSpec : public Task { public: - CosmicsMatchingSpec(std::shared_ptr dr, std::shared_ptr gr, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, bool useMC) : mDataRequest(dr), mGGCCDBRequest(gr), mUseMC(useMC) - { - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - } + CosmicsMatchingSpec(std::shared_ptr dr, std::shared_ptr gr, bool useMC) : mDataRequest(dr), mGGCCDBRequest(gr), mUseMC(useMC) {} ~CosmicsMatchingSpec() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -79,7 +74,7 @@ class CosmicsMatchingSpec : public Task std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + const o2::gpu::TPCFastTransformPOD* mCorrMap{nullptr}; o2::globaltracking::MatchCosmics mMatching; // matching engine bool mUseMC = true; TStopwatch mTimer; @@ -92,7 +87,6 @@ void CosmicsMatchingSpec::init(InitContext& ic) o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); mMatching.setDebugFlag(ic.options().get("debug-tree-flags")); mMatching.setUseMC(mUseMC); - mTPCCorrMapsLoader.init(ic); // } @@ -115,7 +109,8 @@ void CosmicsMatchingSpec::updateTimeDependentParams(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); + auto const& raw = pc.inputs().get("corrMap"); + mCorrMap = &gpu::TPCFastTransformPOD::get(raw); static bool initOnceDone = false; if (!initOnceDone) { // this params need to be queried only once initOnceDone = true; @@ -132,12 +127,7 @@ void CosmicsMatchingSpec::updateTimeDependentParams(ProcessingContext& pc) } mMatching.init(); } - bool updateMaps = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - mMatching.setTPCCorrMaps(&mTPCCorrMapsLoader); + mMatching.setTPCCorrMaps(mCorrMap); if (mTPCVDriftHelper.isUpdated()) { LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, @@ -145,10 +135,6 @@ void CosmicsMatchingSpec::updateTimeDependentParams(ProcessingContext& pc) mTPCVDriftHelper.getSourceName()); mMatching.setTPCVDrift(mTPCVDriftHelper.getVDriftObject()); mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); } } @@ -160,9 +146,6 @@ void CosmicsMatchingSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) { LOG(info) << "cluster dictionary updated"; mMatching.setITSDict((const o2::itsmft::TopologyDictionary*)obj); @@ -177,7 +160,7 @@ void CosmicsMatchingSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getCosmicsMatchingSpec(GTrackID::mask_t src, bool useMC, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts) +DataProcessorSpec getCosmicsMatchingSpec(GTrackID::mask_t src, bool useMC) { std::vector outputs; Options opts{ @@ -203,13 +186,13 @@ DataProcessorSpec getCosmicsMatchingSpec(GTrackID::mask_t src, bool useMC, const dataRequest->inputs, true); o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); return DataProcessorSpec{ "cosmics-matcher", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, sclOpts, useMC)}, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, useMC)}, opts}; } diff --git a/Detectors/GlobalTrackingWorkflow/src/GlobalFwdMatchingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/GlobalFwdMatchingSpec.cxx index 03dc823c62c42..a43a1e8943739 100644 --- a/Detectors/GlobalTrackingWorkflow/src/GlobalFwdMatchingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/GlobalFwdMatchingSpec.cxx @@ -20,7 +20,7 @@ #include "Framework/CCDBParamSpec.h" #include "CommonUtils/StringUtils.h" #include "DetectorsCommonDataFormats/DetectorNameConf.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "SimulationDataFormat/MCCompLabel.h" #include "DataFormatsMFT/TrackMFT.h" #include "DataFormatsITSMFT/Cluster.h" diff --git a/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx index dc1107bacb18a..c1d7b62bbf731 100644 --- a/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx @@ -30,7 +30,7 @@ #include "Framework/CCDBParamSpec.h" #include "Framework/DeviceSpec.h" #include "FT0Reconstruction/InteractionTag.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsVertexing/PVertexer.h" #include "DetectorsBase/GRPGeomHelper.h" diff --git a/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx index 6dfd1cb770d7f..afce2861be2fb 100644 --- a/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx @@ -31,10 +31,9 @@ #include "DetectorsBase/GlobalParams.h" #include "TStopwatch.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCFastTransformPOD.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/DeviceSpec.h" -#include "TPCCalibration/CorrectionMapsLoader.h" using namespace o2::framework; @@ -58,14 +57,7 @@ namespace o2d = o2::dataformats; class SecondaryVertexingSpec : public Task { public: - SecondaryVertexingSpec(std::shared_ptr dr, std::shared_ptr gr, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, GTrackID::mask_t src, bool enabCasc, bool enable3body, bool enableStrangenessTracking, bool enableCCDBParams, bool useMC) : mDataRequest(dr), mGGCCDBRequest(gr), mSrc(src), mEnableCascades(enabCasc), mEnable3BodyVertices(enable3body), mEnableStrangenessTracking(enableStrangenessTracking), mEnableCCDBParams(enableCCDBParams), mUseMC(useMC) - { - if (mSrc[GTrackID::TPC]) { - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - } - } + SecondaryVertexingSpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool enabCasc, bool enable3body, bool enableStrangenessTracking, bool enableCCDBParams, bool useMC) : mDataRequest(dr), mGGCCDBRequest(gr), mSrc(src), mEnableCascades(enabCasc), mEnable3BodyVertices(enable3body), mEnableStrangenessTracking(enableStrangenessTracking), mEnableCCDBParams(enableCCDBParams), mUseMC(useMC) {} ~SecondaryVertexingSpec() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -77,7 +69,7 @@ class SecondaryVertexingSpec : public Task std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps{nullptr}; GTrackID::mask_t mSrc{}; bool mEnableCCDBParams = false; bool mEnableCascades = false; @@ -107,9 +99,6 @@ void SecondaryVertexingSpec::init(InitContext& ic) mStrTracker.setMCTruthOn(mUseMC); mVertexer.setStrangenessTracker(&mStrTracker); } - if (mSrc[GTrackID::TPC]) { - mTPCCorrMapsLoader.init(ic); - } } void SecondaryVertexingSpec::run(ProcessingContext& pc) @@ -157,9 +146,6 @@ void SecondaryVertexingSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* ob if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) { LOG(info) << "cluster dictionary updated"; mStrTracker.setClusterDictionaryITS((const o2::itsmft::TopologyDictionary*)obj); @@ -193,7 +179,8 @@ void SecondaryVertexingSpec::updateTimeDependentParams(ProcessingContext& pc) o2::base::GRPGeomHelper::instance().checkUpdates(pc); if (mSrc[GTrackID::TPC]) { mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); + auto const& raw = pc.inputs().get("corrMap"); + mTPCCorrMaps = &gpu::TPCFastTransformPOD::get(raw); } static bool initOnceDone = false; if (!initOnceDone) { // this params need to be queried only once @@ -223,12 +210,8 @@ void SecondaryVertexingSpec::updateTimeDependentParams(ProcessingContext& pc) } // we may have other params which need to be queried regularly if (mSrc[GTrackID::TPC]) { - bool updateMaps = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - mVertexer.setTPCCorrMaps(&mTPCCorrMapsLoader); + mVertexer.setTPCCorrMaps(mTPCCorrMaps); + if (mTPCVDriftHelper.isUpdated()) { LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, @@ -236,10 +219,6 @@ void SecondaryVertexingSpec::updateTimeDependentParams(ProcessingContext& pc) mTPCVDriftHelper.getSourceName()); mVertexer.setTPCVDrift(mTPCVDriftHelper.getVDriftObject()); mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); } } if (mEnableStrangenessTracking) { @@ -253,7 +232,7 @@ void SecondaryVertexingSpec::updateTimeDependentParams(ProcessingContext& pc) } DataProcessorSpec getSecondaryVertexingSpec(GTrackID::mask_t src, bool enableCasc, bool enable3body, bool enableStrangenesTracking, bool enableCCDBParams, - bool useMC, bool useGeom, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts) + bool useMC, bool useGeom) { std::vector outputs; Options opts{ @@ -297,7 +276,7 @@ DataProcessorSpec getSecondaryVertexingSpec(GTrackID::mask_t src, bool enableCas } if (src[GTrackID::TPC]) { o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); } outputs.emplace_back("GLO", "V0S_IDX", 0, Lifetime::Timeframe); // found V0s indices outputs.emplace_back("GLO", "V0S", 0, Lifetime::Timeframe); // found V0s @@ -324,7 +303,7 @@ DataProcessorSpec getSecondaryVertexingSpec(GTrackID::mask_t src, bool enableCas "secondary-vertexing", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, sclOpts, src, enableCasc, enable3body, enableStrangenesTracking, enableCCDBParams, useMC)}, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, src, enableCasc, enable3body, enableStrangenesTracking, enableCCDBParams, useMC)}, opts}; } diff --git a/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx index 8081c48e390d3..746e572c506b8 100644 --- a/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx @@ -24,7 +24,7 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/DeviceSpec.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCFastTransformPOD.h" // from Tracks #include "ReconstructionDataFormats/GlobalTrackID.h" @@ -59,12 +59,7 @@ namespace globaltracking class TOFMatcherSpec : public Task { public: - TOFMatcherSpec(std::shared_ptr dr, std::shared_ptr gr, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, bool useMC, bool useFIT, bool tpcRefit, bool strict, bool pushMatchable, int lanes = 1) : mDataRequest(dr), mGGCCDBRequest(gr), mUseMC(useMC), mUseFIT(useFIT), mDoTPCRefit(tpcRefit), mStrict(strict), mPushMatchable(pushMatchable), mNlanes(lanes) - { - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - } + TOFMatcherSpec(std::shared_ptr dr, std::shared_ptr gr, bool useMC, bool useFIT, bool tpcRefit, bool strict, bool pushMatchable, int lanes = 1, bool requestCTPLumi = false) : mDataRequest(dr), mGGCCDBRequest(gr), mUseMC(useMC), mUseFIT(useFIT), mDoTPCRefit(tpcRefit), mStrict(strict), mPushMatchable(pushMatchable), mNlanes(lanes), mRequestCTPLumi(requestCTPLumi) {} ~TOFMatcherSpec() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -76,7 +71,7 @@ class TOFMatcherSpec : public Task std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps = nullptr; bool mUseMC = true; bool mUseFIT = false; bool mDoTPCRefit = false; @@ -84,6 +79,7 @@ class TOFMatcherSpec : public Task bool mPushMatchable = false; float mExtraTolTRD = 0.; int mNlanes = 1; + bool mRequestCTPLumi = false; MatchTOF mMatcher; ///< Cluster finder TStopwatch mTimer; }; @@ -96,7 +92,6 @@ void TOFMatcherSpec::init(InitContext& ic) if (mStrict) { mMatcher.setHighPurity(); } - mTPCCorrMapsLoader.init(ic); mMatcher.storeMatchable(mPushMatchable); mMatcher.setExtraTimeToleranceTRD(mExtraTolTRD); mMatcher.setNlanes(mNlanes); @@ -106,7 +101,9 @@ void TOFMatcherSpec::updateTimeDependentParams(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); + auto const& raw = pc.inputs().get("corrMap"); + mTPCCorrMaps = &o2::gpu::TPCFastTransformPOD::get(raw); + float lumiCTP = mRequestCTPLumi ? pc.inputs().get("lumiCTP") : 0; static bool initOnceDone = false; if (!initOnceDone) { // this params need to be queried only once const auto bcs = o2::base::GRPGeomHelper::instance().getGRPLHCIF()->getBunchFilling().getFilledBCs(); @@ -116,13 +113,7 @@ void TOFMatcherSpec::updateTimeDependentParams(ProcessingContext& pc) initOnceDone = true; // put here init-once stuff } - // we may have other params which need to be queried regularly - bool updateMaps = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - mMatcher.setTPCCorrMaps(&mTPCCorrMapsLoader); + mMatcher.setTPCCorrMaps(mTPCCorrMaps, lumiCTP); if (mTPCVDriftHelper.isUpdated()) { LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, @@ -130,10 +121,6 @@ void TOFMatcherSpec::updateTimeDependentParams(ProcessingContext& pc) mTPCVDriftHelper.getSourceName()); mMatcher.setTPCVDrift(mTPCVDriftHelper.getVDriftObject()); mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); } } @@ -145,9 +132,6 @@ void TOFMatcherSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } } void TOFMatcherSpec::run(ProcessingContext& pc) @@ -247,7 +231,7 @@ void TOFMatcherSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getTOFMatcherSpec(GID::mask_t src, bool useMC, bool useFIT, bool tpcRefit, bool strict, float extratolerancetrd, bool pushMatchable, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, int nlanes) +DataProcessorSpec getTOFMatcherSpec(GID::mask_t src, bool useMC, bool useFIT, bool tpcRefit, bool strict, float extratolerancetrd, bool pushMatchable, bool requestCTPLumi, int nlanes) { uint32_t ss = o2::globaltracking::getSubSpec(strict ? o2::globaltracking::MatchingType::Strict : o2::globaltracking::MatchingType::Standard); Options opts; @@ -273,7 +257,10 @@ DataProcessorSpec getTOFMatcherSpec(GID::mask_t src, bool useMC, bool useFIT, bo dataRequest->inputs, true); o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); + if (requestCTPLumi) { + dataRequest->inputs.emplace_back("lumiCTP", o2::header::gDataOriginCTP, "LUMICTP", 0, Lifetime::Timeframe); + } std::vector outputs; if (GID::includesSource(GID::TPC, src)) { outputs.emplace_back(o2::header::gDataOriginTOF, "MTC_TPC", ss, Lifetime::Timeframe); @@ -327,7 +314,7 @@ DataProcessorSpec getTOFMatcherSpec(GID::mask_t src, bool useMC, bool useFIT, bo "tof-matcher", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, sclOpts, useMC, useFIT, tpcRefit, strict, pushMatchable, nlanes)}, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, useMC, useFIT, tpcRefit, strict, pushMatchable, nlanes, requestCTPLumi)}, opts}; } diff --git a/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx index c333c37ff245b..079fe5455fd4a 100644 --- a/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx @@ -39,7 +39,7 @@ #include "DetectorsBase/GeometryManager.h" #include "DetectorsBase/Propagator.h" #include "DetectorsBase/GlobalParams.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "ITSBase/GeometryTGeo.h" #include "GlobalTracking/MatchTPCITSParams.h" #include "DetectorsCommonDataFormats/DetectorNameConf.h" @@ -50,7 +50,7 @@ #include "ITSMFTReconstruction/ClustererParam.h" #include "DetectorsBase/GRPGeomHelper.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCFastTransformPOD.h" #ifdef ENABLE_UPGRADES #include "ITS3Reconstruction/TopologyDictionary.h" @@ -69,14 +69,9 @@ namespace globaltracking class TPCITSMatchingDPL : public Task { public: - TPCITSMatchingDPL(std::shared_ptr dr, std::shared_ptr gr, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, - bool useFT0, bool calib, bool skipTPCOnly, bool useMC) - : mDataRequest(dr), mGGCCDBRequest(gr), mUseFT0(useFT0), mCalibMode(calib), mSkipTPCOnly(skipTPCOnly), mUseMC(useMC) - { - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - } + TPCITSMatchingDPL(std::shared_ptr dr, std::shared_ptr gr, + bool useFT0, bool calib, bool skipTPCOnly, bool useMC, bool requestCTPLumi) + : mDataRequest(dr), mGGCCDBRequest(gr), mUseFT0(useFT0), mCalibMode(calib), mSkipTPCOnly(skipTPCOnly), mUseMC(useMC), mRequestCTPLumi(requestCTPLumi) {} ~TPCITSMatchingDPL() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -88,12 +83,13 @@ class TPCITSMatchingDPL : public Task std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps{}; o2::globaltracking::MatchTPCITS mMatching; // matching engine bool mUseFT0 = false; bool mCalibMode = false; bool mSkipTPCOnly = false; // to use only externally constrained tracks (for test only) bool mUseMC = true; + bool mRequestCTPLumi = false; TStopwatch mTimer; }; @@ -108,7 +104,6 @@ void TPCITSMatchingDPL::init(InitContext& ic) mMatching.setNThreads(std::max(1, ic.options().get("nthreads"))); mMatching.setUseBCFilling(!ic.options().get("ignore-bc-check")); mMatching.setDebugFlag(ic.options().get("debug-tree-flags")); - mTPCCorrMapsLoader.init(ic); } void TPCITSMatchingDPL::run(ProcessingContext& pc) @@ -157,9 +152,6 @@ void TPCITSMatchingDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } if (matcher == ConcreteDataMatcher("GLO", "ITSTPCPARAM", 0)) { LOG(info) << "ITS-TPC Matching params updated from ccdb"; return; @@ -193,7 +185,10 @@ void TPCITSMatchingDPL::updateTimeDependentParams(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); + auto const& raw = pc.inputs().get("corrMap"); + mTPCCorrMaps = &o2::gpu::TPCFastTransformPOD::get(raw); + float lumiCTP = mRequestCTPLumi ? pc.inputs().get("lumiCTP") : 0; + static bool initOnceDone = false; if (!initOnceDone) { // this params need to be queried only once initOnceDone = true; @@ -227,29 +222,20 @@ void TPCITSMatchingDPL::updateTimeDependentParams(ProcessingContext& pc) LOGP(fatal, "USEMatCorrTGeo cannot work w/o full geometry request in the GRPGeomHelper"); } } - // we may have other params which need to be queried regularly - bool updateMaps = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - mMatching.setTPCCorrMaps(&mTPCCorrMapsLoader); + + mMatching.setTPCCorrMaps(mTPCCorrMaps, lumiCTP); if (mTPCVDriftHelper.isUpdated()) { - LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", + LOGP(info, "Updating TPC VDrift: corrFact {} wrt refVDrift {} and DriftTimeOffset correction {} wrt {} from source {}", mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().timeOffsetCorr, mTPCVDriftHelper.getVDriftObject().refTimeOffset, mTPCVDriftHelper.getSourceName()); mMatching.setTPCVDrift(mTPCVDriftHelper.getVDriftObject()); mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); } } -DataProcessorSpec getTPCITSMatchingSpec(GTrackID::mask_t src, bool useFT0, bool calib, bool skipTPCOnly, bool useGeom, bool useMC, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts) +DataProcessorSpec getTPCITSMatchingSpec(GTrackID::mask_t src, bool useFT0, bool calib, bool skipTPCOnly, bool useGeom, bool useMC, bool requestCTPLumi) { std::vector outputs; auto dataRequest = std::make_shared(); @@ -305,13 +291,15 @@ DataProcessorSpec getTPCITSMatchingSpec(GTrackID::mask_t src, bool useFT0, bool {"debug-tree-flags", VariantType::Int, 0, {"DebugFlagTypes bit-pattern for debug tree"}}}; o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); - + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); + if (requestCTPLumi) { + dataRequest->inputs.emplace_back("lumiCTP", o2::header::gDataOriginCTP, "LUMICTP", 0, Lifetime::Timeframe); + } return DataProcessorSpec{ "itstpc-track-matcher", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, sclOpts, useFT0, calib, skipTPCOnly, useMC)}, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, useFT0, calib, skipTPCOnly, useMC, requestCTPLumi)}, opts}; } diff --git a/Detectors/GlobalTrackingWorkflow/src/VertexTrackMatcherSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/VertexTrackMatcherSpec.cxx index f24e7c13e336f..90e4dd4b0f001 100644 --- a/Detectors/GlobalTrackingWorkflow/src/VertexTrackMatcherSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/VertexTrackMatcherSpec.cxx @@ -23,7 +23,7 @@ #include "TPCBase/ParameterElectronics.h" #include "TPCBase/ParameterDetector.h" #include "TPCCalibration/VDriftHelper.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "TStopwatch.h" using namespace o2::framework; diff --git a/Detectors/GlobalTrackingWorkflow/src/cosmics-match-workflow.cxx b/Detectors/GlobalTrackingWorkflow/src/cosmics-match-workflow.cxx index e4082bdd14d86..14812ac25cce1 100644 --- a/Detectors/GlobalTrackingWorkflow/src/cosmics-match-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/cosmics-match-workflow.cxx @@ -31,7 +31,8 @@ #include "DetectorsRaw/HBFUtilsInitializer.h" #include "Framework/CallbacksPolicy.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" +#include "TPCCalibration/CorrectionMapsOptions.h" using namespace o2::framework; using DetID = o2::detectors::DetID; @@ -52,7 +53,8 @@ void customize(std::vector& workflowOptions) {"disable-root-output", o2::framework::VariantType::Bool, false, {"disable root-files output writer"}}, {"track-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of sources to use"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -83,7 +85,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); // write the configuration used for the workflow o2::conf::ConfigurableParam::writeINI("o2match-cosmics-workflow_configuration.ini"); - auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); auto useMC = !configcontext.options().get("disable-mc"); auto disableRootOut = configcontext.options().get("disable-root-output"); @@ -102,10 +104,10 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) } GID::mask_t srcCl = src; GID::mask_t dummy; - if (sclOpt.needTPCScalersWorkflow() && !configcontext.options().get("disable-root-input")) { - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == 2, sclOpt.enableMShapeCorrection)); + if (!configcontext.options().get("disable-root-input")) { + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpt.enableMShapeCorrection, sclOpt)); } - specs.emplace_back(o2::globaltracking::getCosmicsMatchingSpec(src, useMC, sclOpt)); + specs.emplace_back(o2::globaltracking::getCosmicsMatchingSpec(src, useMC)); o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, src, src, src, useMC, dummy); // clusters MC is not needed diff --git a/Detectors/GlobalTrackingWorkflow/src/globalfwd-matcher-workflow.cxx b/Detectors/GlobalTrackingWorkflow/src/globalfwd-matcher-workflow.cxx index 13a842130e5d1..fd90aff5f32ff 100644 --- a/Detectors/GlobalTrackingWorkflow/src/globalfwd-matcher-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/globalfwd-matcher-workflow.cxx @@ -21,6 +21,7 @@ #include "GlobalTrackingWorkflow/MatchedMFTMCHWriterSpec.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "GlobalTracking/MatchGlobalFwdParam.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -46,6 +47,7 @@ void customize(std::vector& workflowOptions) {"disable-root-output", o2::framework::VariantType::Bool, false, {"do not write output root files"}}, {"enable-match-output", o2::framework::VariantType::Bool, false, {"stores mftmch matching info on mftmchmatches.root"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; + o2::itsmft::DPLAlpideParamInitializer::addMFTConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } diff --git a/Detectors/GlobalTrackingWorkflow/src/secondary-vertexing-workflow.cxx b/Detectors/GlobalTrackingWorkflow/src/secondary-vertexing-workflow.cxx index 0ac640cbad9fd..e630a8dad72dd 100644 --- a/Detectors/GlobalTrackingWorkflow/src/secondary-vertexing-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/secondary-vertexing-workflow.cxx @@ -29,7 +29,8 @@ #include "Framework/ConfigParamSpec.h" #include "Framework/CompletionPolicyHelpers.h" #include "DetectorsBase/DPLWorkflowUtils.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" +#include "TPCCalibration/CorrectionMapsOptions.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -62,7 +63,8 @@ void customize(std::vector& workflowOptions) {"use-full-geometry", o2::framework::VariantType::Bool, false, {"use full geometry instead of the light-weight ITS part"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}, {"combine-source-devices", o2::framework::VariantType::Bool, false, {"merge DPL source devices"}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -86,7 +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 sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); + 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 if (enableStrTr) { @@ -94,15 +96,15 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) } if (src[GID::TPC]) { srcClus |= GID::getSourceMask(GID::TPC); - } - if (sclOpt.requestCTPLumi) { - src = src | GID::getSourcesMask("CTP"); + if (sclOpt.requestCTPLumi) { + src = src | GID::getSourcesMask("CTP"); + } } WorkflowSpec specs; - if (sclOpt.needTPCScalersWorkflow() && !configcontext.options().get("disable-root-input")) { - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == 2, sclOpt.enableMShapeCorrection)); + if (!configcontext.options().get("disable-root-input")) { + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpt.enableMShapeCorrection, sclOpt)); } - specs.emplace_back(o2::vertexing::getSecondaryVertexingSpec(src, enableCasc, enable3body, enableStrTr, enableCCDBParams, useMC, useGeom, sclOpt)); + specs.emplace_back(o2::vertexing::getSecondaryVertexingSpec(src, enableCasc, enable3body, enableStrTr, enableCCDBParams, useMC, useGeom)); // only TOF clusters are needed if TOF is involved, no clusters MC needed WorkflowSpec inputspecs; diff --git a/Detectors/GlobalTrackingWorkflow/src/strangeness-tracking-workflow.cxx b/Detectors/GlobalTrackingWorkflow/src/strangeness-tracking-workflow.cxx index bdc1af958886c..8c42871ac05bf 100644 --- a/Detectors/GlobalTrackingWorkflow/src/strangeness-tracking-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/strangeness-tracking-workflow.cxx @@ -21,6 +21,7 @@ #include "GlobalTrackingWorkflowReaders/TrackTPCITSReaderSpec.h" #include "GlobalTrackingWorkflowReaders/PrimaryVertexReaderSpec.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "DetectorsRaw/HBFUtilsInitializer.h" #include "Framework/CallbacksPolicy.h" @@ -46,6 +47,7 @@ void customize(std::vector& workflowOptions) {"disable-mc", o2::framework::VariantType::Bool, false, {"disable MC"}}, {"use-full-geometry", o2::framework::VariantType::Bool, false, {"use full geometry instead of the light-weight ITS part"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } diff --git a/Detectors/GlobalTrackingWorkflow/src/tof-matcher-workflow.cxx b/Detectors/GlobalTrackingWorkflow/src/tof-matcher-workflow.cxx index 9a95c83617210..96d7c783022c3 100644 --- a/Detectors/GlobalTrackingWorkflow/src/tof-matcher-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/tof-matcher-workflow.cxx @@ -32,7 +32,7 @@ #include "Steer/MCKinematicsReader.h" #include "TSystem.h" #include "DetectorsBase/DPLWorkflowUtils.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCCalibration/CorrectionMapsOptions.h" #include "TPCWorkflow/TPCScalerSpec.h" using namespace o2::framework; @@ -70,7 +70,7 @@ void customize(std::vector& workflowOptions) {"write-matchable", o2::framework::VariantType::Bool, false, {"write all matchable pairs in a file (o2matchable_tof.root)"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}, {"combine-devices", o2::framework::VariantType::Bool, false, {"merge DPL source/writer devices"}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -97,7 +97,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto diagnostic = configcontext.options().get("enable-dia"); auto extratolerancetrd = configcontext.options().get("trd-extra-tolerance"); auto writeMatchable = configcontext.options().get("write-matchable"); - auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); bool writematching = 0; bool writecalib = 0; bool refitTPCTOF = configcontext.options().get("refit-tpc-tof"); @@ -168,10 +168,10 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) specs.push_back(s); } } - if (sclOpt.needTPCScalersWorkflow() && !configcontext.options().get("disable-root-input")) { - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == 2, sclOpt.enableMShapeCorrection)); + if (!configcontext.options().get("disable-root-input")) { + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpt.enableMShapeCorrection, sclOpt)); } - specs.emplace_back(o2::globaltracking::getTOFMatcherSpec(src, useMC, useFIT, refitTPCTOF, strict, extratolerancetrd, writeMatchable, sclOpt, nLanes)); // doTPCrefit not yet supported (need to load TPC clusters?) + specs.emplace_back(o2::globaltracking::getTOFMatcherSpec(src, useMC, useFIT, refitTPCTOF, strict, extratolerancetrd, writeMatchable, sclOpt.requestCTPLumi, nLanes)); // doTPCrefit not yet supported (need to load TPC clusters?) if (!disableRootOut) { std::vector writers; diff --git a/Detectors/GlobalTrackingWorkflow/src/tpcits-match-workflow.cxx b/Detectors/GlobalTrackingWorkflow/src/tpcits-match-workflow.cxx index 810c7c564b4a8..78e5db9e4b391 100644 --- a/Detectors/GlobalTrackingWorkflow/src/tpcits-match-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/tpcits-match-workflow.cxx @@ -24,7 +24,8 @@ #include "Framework/CallbacksPolicy.h" #include "Framework/ConfigContext.h" #include "Framework/CompletionPolicyHelpers.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" +#include "TPCCalibration/CorrectionMapsOptions.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -46,7 +47,8 @@ void customize(std::vector& workflowOptions) {"produce-calibration-data", o2::framework::VariantType::Bool, false, {"produce output for TPC vdrift calibration"}}, {"use-full-geometry", o2::framework::VariantType::Bool, false, {"use full geometry instead of the light-weight ITS part"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -76,7 +78,8 @@ WorkflowSpec defineDataProcessing(o2::framework::ConfigContext const& configcont GID::mask_t alowedSources = GID::getSourcesMask("ITS,TPC,TPC-TOF"); GID::mask_t src = alowedSources & GID::getSourcesMask(configcontext.options().get("track-sources")); bool needStrictTRDTOF = (src & GID::getSourcesMask("TPC-TRD,TPC-TOF,TPC-TRD-TOF")).any(); - auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); // RS at the moment is not passed to the matching w-flow + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); auto useGeom = configcontext.options().get("use-full-geometry"); auto useFT0 = configcontext.options().get("use-ft0"); if (useFT0) { @@ -90,10 +93,10 @@ WorkflowSpec defineDataProcessing(o2::framework::ConfigContext const& configcont } o2::framework::WorkflowSpec specs; - if (sclOpt.needTPCScalersWorkflow() && !configcontext.options().get("disable-root-input")) { - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == 2, sclOpt.enableMShapeCorrection)); + if (!configcontext.options().get("disable-root-input")) { + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpt.enableMShapeCorrection, sclOpt)); } - specs.emplace_back(o2::globaltracking::getTPCITSMatchingSpec(srcL, useFT0, calib, !GID::includesSource(GID::TPC, src), useGeom, useMC, sclOpt)); + specs.emplace_back(o2::globaltracking::getTPCITSMatchingSpec(srcL, useFT0, calib, !GID::includesSource(GID::TPC, src), useGeom, useMC, sclOpt.requestCTPLumi)); if (!configcontext.options().get("disable-root-output")) { specs.emplace_back(o2::globaltracking::getTrackWriterTPCITSSpec(useMC)); diff --git a/Detectors/GlobalTrackingWorkflow/study/CMakeLists.txt b/Detectors/GlobalTrackingWorkflow/study/CMakeLists.txt index df42af503db46..4ee9f2f314a08 100644 --- a/Detectors/GlobalTrackingWorkflow/study/CMakeLists.txt +++ b/Detectors/GlobalTrackingWorkflow/study/CMakeLists.txt @@ -25,8 +25,9 @@ o2_add_library(GlobalTrackingStudy src/TrackMCStudyConfig.cxx src/TrackMCStudyTypes.cxx src/TPCClusSelector.cxx - src/CheckResid.cxx + src/CheckResidSpec.cxx src/CheckResidConfig.cxx + src/HistoManager.cxx PUBLIC_LINK_LIBRARIES O2::GlobalTracking O2::GlobalTrackingWorkflowReaders O2::GlobalTrackingWorkflowHelpers @@ -42,6 +43,7 @@ o2_target_root_dictionary(GlobalTrackingStudy include/GlobalTrackingStudy/TrackMCStudyTypes.h include/GlobalTrackingStudy/CheckResidTypes.h include/GlobalTrackingStudy/CheckResidConfig.h + include/GlobalTrackingStudy/HistoManager.h LINKDEF src/GlobalTrackingStudyLinkDef.h ) diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h index 2a07eaf87930f..09ebba2d2e3f2 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h @@ -21,7 +21,8 @@ struct CheckResidConfig : o2::conf::ConfigurableParamHelper { int minTPCCl = 60; int minITSCl = 7; float minPt = 0.4f; - float maxPt = 100.f; + float maxPt = 50.f; + float maxTgl = 2.f; float rCompIBOB = 12.f; bool pvcontribOnly = true; @@ -34,6 +35,27 @@ struct CheckResidConfig : o2::conf::ConfigurableParamHelper { float refitPVMV = false; float refitPVIniScale = 100.f; + std::string outname{"checkResid"}; + // histogram settings + int nBinsRes = 100; + int nBinsPhi = 30; + int nBinsZ = 20; + int nBinsPt = 15; + int nBinsTgl = 20; + int minHistoStat2Fit = 1000; + float maxPull = 4; + float zranges[8] = {10.f, 15.f, 15.f, 15.f, 40.f, 40.f, 74.f, 74.f}; + float maxDYZ[8] = {0.03, 0.015, 0.01, 0.01, 0.08, 0.08, 0.12, 0.1}; + float maxDPar[5] = {0.15, 0.15, 0.015, 0.015, 1.}; + // drawing settings + float resMMLrY[8] = {0.003, 0.003, 0.003, 0.003, 0.005, 0.005, 0.005, 0.005}; + float resMMLrZ[8] = {0.002, 0.0015, 0.0015, 0.0015, 0.005, 0.005, 0.005, 0.005}; + float resMMPar[5] = {0.03, 0.01, 0.005, 0.001, 0.5}; + // + // string with existing histomanagers files to draw (comma or semicolon separated) and optional legends + std::string ext_hm_list{}; + std::string ext_leg_list{}; + O2ParamDef(CheckResidConfig, "checkresid"); }; } // namespace o2::checkresid diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResid.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h similarity index 86% rename from Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResid.h rename to Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h index a78fa5e8d41da..3cae8e94b8e68 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResid.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h @@ -15,12 +15,11 @@ #include "ReconstructionDataFormats/GlobalTrackID.h" #include "Framework/Task.h" #include "Framework/DataProcessorSpec.h" -// #include "TPCCalibration/CorrectionMapsLoader.h" namespace o2::checkresid { /// create a processor spec -o2::framework::DataProcessorSpec getCheckResidSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC /*, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts*/); +o2::framework::DataProcessorSpec getCheckResidSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool drawOnly, bool postProcOnly); } // namespace o2::checkresid diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/HistoManager.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/HistoManager.h new file mode 100644 index 0000000000000..eb9cf6876333e --- /dev/null +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/HistoManager.h @@ -0,0 +1,93 @@ +// Copyright 2019-2026 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. + +#ifndef _O2_HISTOMANAGER_H_ +#define _O2_HISTOMANAGER_H_ + +#include +#include +#include "TObjArray.h" + +class TH1; +class TH2; +class TH1F; +class TH2F; +class TProfile; +class TGraph; +class TFile; + +namespace o2 +{ + +class HistoManager : public TObjArray +{ + public: + HistoManager(const std::string& dirname = "", const std::string& fname = "histoman.root", bool load = kFALSE, const std::string& prefix = ""); + ~HistoManager() override { Delete(); } + + HistoManager* createClone(const std::string& prefix) const; + void addPrefix(const std::string& pref = ""); + + int getNHistos() const { return mNHistos; } + TGraph* getGraph(int id) const; + TH1* getHisto(int id) const; + TH1* getHisto(const std::string& name) const; + TH1F* getHisto1F(int id) const; + TH2F* getHisto2F(int id) const; + TProfile* getHistoP(int id) const; + + int addHisto(TH1* histo, int at = -1); + int addGraph(TGraph* gr, int at = -1); + void delHisto(int at); + + void setFile(TFile* file); + void setFileName(const std::string& fname); + const std::string& getFileName() const { return mDefName; } + void setDirName(const std::string& name) { mDirName = name; } + const std::string& getDirName() const { return mDirName; } + + void reset(); + void write(TFile* file = nullptr); + int write(const std::string& flname) + { + setFileName(flname); + write(); + return 0; + } + + void addHistos(const HistoManager* hm, Double_t c1 = 1.); + void divideHistos(const HistoManager* hm); + void multiplyHistos(const HistoManager* hm); + void scaleHistos(Double_t c1 = 1.); + void setColor(int tcolor = 1); + void setMarkerStyle(Style_t mstyle = 1, Size_t msize = 1); + void setMarkerSize(Size_t msize = 1); + void sumw2(); + int load(const std::string& fname, const std::string& dirname = ""); + + void purify(bool emptyToo = kFALSE); + + void Print(Option_t* option = "") const override; + void Clear(Option_t* option = "") override; + void Delete(Option_t* option = "") override; + void Compress() override; + + private: + int mNHistos{0}; //! Number of histograms defined + std::string mDefName{}; //! Default file name + std::string mDirName{}; //! Directory name in the output file + + ClassDefOverride(HistoManager, 0); +}; + +} // namespace o2 + +#endif // _O2_HISTOMANAGER_H_ diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TPCTrackStudy.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TPCTrackStudy.h index 47385f400ec01..f6396f682ac3b 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TPCTrackStudy.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TPCTrackStudy.h @@ -19,15 +19,10 @@ #include "MathUtils/detail/Bracket.h" #include "DataFormatsTPC/ClusterNative.h" -namespace o2::tpc -{ -struct CorrectionMapsLoaderGloOpts; -} - namespace o2::trackstudy { /// create a processor spec -o2::framework::DataProcessorSpec getTPCTrackStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts); +o2::framework::DataProcessorSpec getTPCTrackStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC); } // namespace o2::trackstudy diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudy.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudy.h index d1326a47ac909..18ce3dbf8ab87 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudy.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudy.h @@ -15,13 +15,12 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" #include "ReconstructionDataFormats/GlobalTrackID.h" -#include "TPCCalibration/CorrectionMapsLoader.h" namespace o2::trackstudy { /// create a processor spec -o2::framework::DataProcessorSpec getTrackMCStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, bool checkSV); +o2::framework::DataProcessorSpec getTrackMCStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool checkSV); } // namespace o2::trackstudy diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackingStudy.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackingStudy.h index 7a15c191cbeed..b3a55416f4818 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackingStudy.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackingStudy.h @@ -18,12 +18,11 @@ #include "ReconstructionDataFormats/Track.h" #include "MathUtils/detail/Bracket.h" #include "DataFormatsTPC/ClusterNative.h" -#include "TPCCalibration/CorrectionMapsLoader.h" namespace o2::trackstudy { /// create a processor spec -o2::framework::DataProcessorSpec getTrackingStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts); +o2::framework::DataProcessorSpec getTrackingStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC); } // namespace o2::trackstudy diff --git a/Detectors/GlobalTrackingWorkflow/study/src/CheckResid.cxx b/Detectors/GlobalTrackingWorkflow/study/src/CheckResid.cxx deleted file mode 100644 index e6584a7055446..0000000000000 --- a/Detectors/GlobalTrackingWorkflow/study/src/CheckResid.cxx +++ /dev/null @@ -1,567 +0,0 @@ -// 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. - -#include "GlobalTrackingStudy/CheckResid.h" -#include "GlobalTrackingStudy/CheckResidTypes.h" -#include "GlobalTrackingStudy/CheckResidConfig.h" -#include -#include "ReconstructionDataFormats/Track.h" -#include -#include "DataFormatsGlobalTracking/RecoContainer.h" -#include "DataFormatsITSMFT/TrkClusRef.h" -#include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" -#include "ReconstructionDataFormats/TrackTPCITS.h" -#include "ReconstructionDataFormats/GlobalTrackID.h" -#include "DataFormatsCalibration/MeanVertexObject.h" -#include "DetectorsBase/Propagator.h" -#include "DetectorsBase/GeometryManager.h" -#include "SimulationDataFormat/MCEventLabel.h" -#include "SimulationDataFormat/MCUtils.h" -#include "CommonUtils/NameConf.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/CCDBParamSpec.h" -#include "Framework/DeviceSpec.h" -#include "ITSMFTBase/DPLAlpideParam.h" -#include "ITSBase/GeometryTGeo.h" -#include "ITStracking/IOUtils.h" -#include "DetectorsCommonDataFormats/DetID.h" -#include "DetectorsBase/GRPGeomHelper.h" -#include "ReconstructionDataFormats/PrimaryVertex.h" -#include "CommonUtils/TreeStreamRedirector.h" -#include "ReconstructionDataFormats/VtxTrackRef.h" -#include "DetectorsVertexing/PVertexer.h" -#ifdef WITH_OPENMP -#include -#endif - -// Attention: in case the residuals are checked with geometry different from the one used for initial reconstruction, -// pass a --configKeyValues option for vertex refit as: -// ;pvertexer.useMeanVertexConstraint=false;pvertexer.iniScale2=100;pvertexer.acceptableScale2=10.; -// In any case, it is better to pass ;pvertexer.useMeanVertexConstraint=false; - -namespace o2::checkresid -{ -using namespace o2::framework; -using DetID = o2::detectors::DetID; -using DataRequest = o2::globaltracking::DataRequest; - -using PVertex = o2::dataformats::PrimaryVertex; -using V2TRef = o2::dataformats::VtxTrackRef; -using VTIndex = o2::dataformats::VtxTrackIndex; -using GTrackID = o2::dataformats::GlobalTrackID; -using timeEst = o2::dataformats::TimeStampWithError; - -class CheckResidSpec : public Task -{ - public: - CheckResidSpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool useMC /*, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts*/) - : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC) - { - /* - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - */ - } - ~CheckResidSpec() final = default; - void init(InitContext& ic) final; - void run(ProcessingContext& pc) final; - void endOfStream(EndOfStreamContext& ec) final; - void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final; - void process(); - - private: - void updateTimeDependentParams(ProcessingContext& pc); - bool refitPV(o2::dataformats::PrimaryVertex& pv, int vid); - bool refitITStrack(o2::track::TrackParCov& track, GTrackID gid); - bool processITSTrack(const o2::its::TrackITS& iTrack, const o2::dataformats::PrimaryVertex& pv, o2::checkresid::Track& resTrack); - - o2::globaltracking::RecoContainer* mRecoData = nullptr; - int mNThreads = 1; - bool mMeanVertexUpdated = false; - float mITSROFrameLengthMUS = 0.f; - o2::dataformats::MeanVertexObject mMeanVtx{}; - std::vector> mITSClustersArray; ///< ITS clusters created in run() method from compact clusters - const o2::itsmft::TopologyDictionary* mITSDict = nullptr; ///< cluster patterns dictionary - o2::vertexing::PVertexer mVertexer; - std::shared_ptr mDataRequest; - std::shared_ptr mGGCCDBRequest; - bool mUseMC{false}; ///< MC flag - std::unique_ptr mDBGOut; - GTrackID::mask_t mTracksSrc{}; -}; - -void CheckResidSpec::init(InitContext& ic) -{ - o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); - int lane = ic.services().get().inputTimesliceId; - int maxLanes = ic.services().get().maxInputTimeslices; - std::string dbgnm = maxLanes == 1 ? "checkResid.root" : fmt::format("checkResid_t{}.root", lane); - mDBGOut = std::make_unique(dbgnm.c_str(), "recreate"); - mNThreads = ic.options().get("nthreads"); -#ifndef WITH_OPENMP - if (mNThreads > 1) { - LOGP(warn, "No OpenMP"); - } - mNThreads = 1; -#endif - // mTPCCorrMapsLoader.init(ic); -} - -void CheckResidSpec::run(ProcessingContext& pc) -{ - o2::globaltracking::RecoContainer recoData; - mRecoData = &recoData; - mRecoData->collectData(pc, *mDataRequest.get()); // select tracks of needed type, with minimal cuts, the real selected will be done in the vertexer - mRecoData = &recoData; - updateTimeDependentParams(pc); // Make sure this is called after recoData.collectData, which may load some conditions - process(); - mRecoData = nullptr; -} - -void CheckResidSpec::updateTimeDependentParams(ProcessingContext& pc) -{ - o2::base::GRPGeomHelper::instance().checkUpdates(pc); - pc.inputs().get("meanvtx"); - // mTPCVDriftHelper.extractCCDBInputs(pc); - // mTPCCorrMapsLoader.extractCCDBInputs(pc); - static bool initOnceDone = false; - if (!initOnceDone) { // this params need to be queried only once - const auto& params = o2::checkresid::CheckResidConfig::Instance(); - initOnceDone = true; - // Note: reading of the ITS AlpideParam needed for ITS timing is done by the RecoContainer - auto grp = o2::base::GRPGeomHelper::instance().getGRPECS(); - const auto& alpParams = o2::itsmft::DPLAlpideParam::Instance(); - if (!grp->isDetContinuousReadOut(DetID::ITS)) { - mITSROFrameLengthMUS = alpParams.roFrameLengthTrig / 1.e3; // ITS ROFrame duration in \mus - } else { - mITSROFrameLengthMUS = alpParams.roFrameLengthInBC * o2::constants::lhc::LHCBunchSpacingNS * 1e-3; // ITS ROFrame duration in \mus - } - auto geom = o2::its::GeometryTGeo::Instance(); - geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G, o2::math_utils::TransformType::T2G)); - o2::conf::ConfigurableParam::updateFromString("pvertexer.useTimeInChi2=false;"); - mVertexer.init(); - } - if (mMeanVertexUpdated) { - mMeanVertexUpdated = false; - mVertexer.initMeanVertexConstraint(); - } - bool updateMaps = false; - /* - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - if (mTPCVDriftHelper.isUpdated()) { - LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", - mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, - mTPCVDriftHelper.getVDriftObject().timeOffsetCorr, mTPCVDriftHelper.getVDriftObject().refTimeOffset, - mTPCVDriftHelper.getSourceName()); - mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); - } - */ -} - -void CheckResidSpec::process() -{ - if (!mITSDict) { - LOGP(fatal, "ITS data is not loaded"); - } - 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); - - auto pvvec = mRecoData->getPrimaryVertices(); - auto trackIndex = mRecoData->getPrimaryVertexMatchedTracks(); // Global ID's for associated tracks - auto vtxRefs = mRecoData->getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs - auto prop = o2::base::Propagator::Instance(); - static int TFCount = 0; - int nv = vtxRefs.size() - 1; - std::vector> slots; - slots.resize(mNThreads); - int nvGood = 0, nvUse = 0, nvRefFail = 0; - long pvFitDuration{}; - for (int iv = 0; iv < nv; iv++) { - const auto& vtref = vtxRefs[iv]; - auto pve = pvvec[iv]; - if (pve.getNContributors() < params.minPVContributors) { - continue; - } - nvGood++; - if (params.refitPV) { - LOGP(debug, "Refitting PV#{} of {} tracks", iv, pve.getNContributors()); - auto tStartPVF = std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count(); - bool res = refitPV(pve, iv); - pvFitDuration += std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count() - tStartPVF; - if (!res) { - nvRefFail++; - continue; - } - } - nvUse++; - for (int is = 0; is < GTrackID::NSources; is++) { - if (!mTracksSrc[is] || !mRecoData->isTrackSourceLoaded(is)) { - continue; - } - int idMin = vtref.getFirstEntryOfSource(is), idMax = idMin + vtref.getEntriesOfSource(is); - DetID::mask_t dm = GTrackID::getSourceDetectorsMask(is); - if (!dm[DetID::ITS]) { - continue; - } - if (dm[DetID::TPC] && params.minTPCCl > 0 && !mRecoData->isTrackSourceLoaded(GTrackID::TPC)) { - LOGP(fatal, "Cut on TPC tracks is requested by they are not loaded"); - } -#ifdef WITH_OPENMP -#pragma omp parallel for schedule(dynamic) num_threads(mNThreads) -#endif - for (int i = idMin; i < idMax; i++) { - auto vid = trackIndex[i]; - bool pvCont = vid.isPVContributor(); - if (!pvCont && params.pvcontribOnly) { - continue; - } - if (dm[DetID::TPC] && params.minTPCCl > 0 && mRecoData->getTPCTrack(mRecoData->getTPCContributorGID(vid)).getNClusters() < params.minTPCCl) { - continue; - } - auto gidITS = mRecoData->getITSContributorGID(vid); - if (gidITS.getSource() != GTrackID::ITS) { - continue; - } - const auto& trc = mRecoData->getTrackParam(vid); - auto pt = trc.getPt(); - if (pt < params.minPt || pt > params.maxPt) { - continue; - } - const auto& itsTrack = mRecoData->getITSTrack(gidITS); - if (itsTrack.getNClusters() < params.minITSCl) { - continue; - } -#ifdef WITH_OPENMP - auto& accum = slots[omp_get_thread_num()]; -#else - auto& accum = slots[0]; -#endif - auto& resTrack = accum.emplace_back(); - resTrack.gid = vid; - if (!processITSTrack(itsTrack, pve, resTrack)) { - accum.pop_back(); - continue; - } - } - } - } - // output - for (const auto& accum : slots) { - for (const auto& tr : accum) { - (*mDBGOut) << "res" << "tr=" << tr << "\n"; - } - } - LOGP(info, "processed {} PVs out of {} good vertices (out of {} in total), PV refits took {} mus, {} refits failed", nvUse, nvGood, nv, pvFitDuration, nvRefFail); - TFCount++; -} - -bool CheckResidSpec::processITSTrack(const o2::its::TrackITS& iTrack, const o2::dataformats::PrimaryVertex& pv, o2::checkresid::Track& resTrack) -{ - const auto itsClRefs = mRecoData->getITSTracksClusterRefs(); - auto trFitInw = iTrack.getParamOut(); // seed for inward refit - auto trFitOut = iTrack.getParamIn(); // seed for outward refit - auto prop = o2::base::Propagator::Instance(); - auto geom = o2::its::GeometryTGeo::Instance(); - float pvAlpha = 0; - float bz = prop->getNominalBz(); - std::array*, 8> clArr{}; - const auto& params = CheckResidConfig::Instance(); - std::array extrapOut, extrapInw; // 2-way Kalman extrapolations, vertex + 7 layers - - auto rotateTrack = [bz](o2::track::TrackParCov& tr, float alpha, o2::track::TrackPar* refLin) { - return refLin ? tr.rotate(alpha, *refLin, bz) : tr.rotate(alpha); - }; - - auto accountCluster = [&](int i, std::array& extrapDest, o2::track::TrackParCov& tr, o2::track::TrackPar* refLin) { - if (clArr[i]) { // update with cluster - if (!rotateTrack(tr, i == 0 ? pvAlpha : geom->getSensorRefAlpha(clArr[i]->getSensorID()), refLin) || - !prop->propagateTo(tr, refLin, clArr[i]->getX(), true)) { - return 0; - } - extrapDest[i] = tr; // before update - if (!tr.update(*clArr[i])) { - return 0; - } - } else { - extrapDest[i].invalidate(); - return -1; - } - return 1; - }; - - auto inv2d = [](float s00, float s11, float s01) -> std::array { - auto det = s00 * s11 - s01 * s01; - if (det < 1e-16) { - return {0.f, 0.f, 0.f}; - } - det = 1.f / det; - return {s11 * det, s00 * det, -s01 * det}; - }; - - resTrack.points.clear(); - if (!prop->propagateToDCA(pv, trFitOut, bz)) { - LOGP(debug, "Failed to propagateToDCA, {}", trFitOut.asString()); - return false; - } - float cosAlp, sinAlp; - pvAlpha = trFitOut.getAlpha(); - o2::math_utils::sincos(trFitOut.getAlpha(), sinAlp, cosAlp); // vertex position rotated to track frame - o2::BaseCluster bcPV; - if (params.addPVAsCluster) { - bcPV.setXYZ(pv.getX() * cosAlp + pv.getY() * sinAlp, -pv.getX() * sinAlp + pv.getY() * cosAlp, pv.getZ()); - bcPV.setSigmaY2(0.5 * (pv.getSigmaX2() + pv.getSigmaY2())); - bcPV.setSigmaZ2(pv.getSigmaZ2()); - bcPV.setSensorID(-1); - clArr[0] = &bcPV; - } - // collect all track clusters to array, placing them to layer+1 slot - int nCl = iTrack.getNClusters(); - for (int i = 0; i < nCl; i++) { // clusters are ordered from the outermost to the innermost - const auto& curClu = mITSClustersArray[itsClRefs[iTrack.getClusterEntry(i)]]; - - int llr = geom->getLayer(curClu.getSensorID()); - if (clArr[1 + llr]) { - LOGP(error, "Cluster at lr {} was already assigned, old sens {}, new sens {}", llr, clArr[1 + llr]->getSensorID(), curClu.getSensorID()); - } - clArr[1 + geom->getLayer(curClu.getSensorID())] = &curClu; - } - o2::track::TrackPar refLinInw0, refLinOut0, *refLinOut = nullptr, *refLinInw = nullptr; - o2::track::TrackPar refLinIBOut0, refLinOBInw0, *refLinOBInw = nullptr, *refLinIBOut = nullptr; - if (params.useStableRef) { - refLinOut = &(refLinOut0 = trFitOut); - refLinInw = &(refLinInw0 = trFitInw); - } - trFitOut.resetCovariance(); - trFitOut.setCov(trFitOut.getQ2Pt() * trFitOut.getQ2Pt() * trFitOut.getCov()[14], 14); - trFitInw.resetCovariance(); - trFitInw.setCov(trFitInw.getQ2Pt() * trFitInw.getQ2Pt() * trFitInw.getCov()[14], 14); - // fit in inward and outward direction - for (int i = 0; i <= 7; i++) { - int resOut, resInw; - // process resOut in ascending order (0-->7) and resInw in descending order (7-->0) - if (!(resOut = accountCluster(i, extrapOut, trFitOut, refLinOut)) || !(resInw = accountCluster(7 - i, extrapInw, trFitInw, refLinInw))) { - return false; - } - // at layer 3, find the IB track (trIBOut) and the OB track (trOBInw) - // propagate both trcaks to a common radius, RCompIBOB (12cm), and rotates - // them to the same reference frame for comparison - if (i == 3 && resOut == 1 && resInw == 1 && params.doIBOB && nCl == 7) { - resTrack.trIBOut = trFitOut; // outward track updated at outermost IB layer - resTrack.trOBInw = trFitInw; // inward track updated at innermost OB layer - o2::track::TrackPar refLinIBOut0, refLinIBIn0; - if (refLinOut) { - refLinIBOut = &(refLinIBOut0 = refLinOut0); - refLinOBInw = &(refLinOBInw0 = refLinInw0); - } - float xRref; - if (!resTrack.trOBInw.getXatLabR(params.rCompIBOB, xRref, bz) || - !prop->propagateTo(resTrack.trOBInw, refLinOBInw, xRref, true) || - !rotateTrack(resTrack.trOBInw, resTrack.trOBInw.getPhiPos(), refLinOBInw) || // propagate OB track to ref R and rotate - !rotateTrack(resTrack.trIBOut, resTrack.trOBInw.getAlpha(), refLinIBOut) || - !prop->propagateTo(resTrack.trIBOut, refLinIBOut, resTrack.trOBInw.getX(), true)) { // rotate OB track to same frame and propagate to same X - // if any propagation or rotation steps fail, invalidate both tracks - return false; - } - } - } - - bool innerDone = false; - if (params.doResid) { - for (int i = 0; i <= 7; i++) { - if (clArr[i]) { - // calculate interpolation as a weighted mean of inward/outward extrapolations to this layer - const auto &tInw = extrapInw[i], &tOut = extrapOut[i]; - auto wInw = inv2d(tInw.getSigmaY2(), tInw.getSigmaZ2(), tInw.getSigmaZY()); - auto wOut = inv2d(tOut.getSigmaY2(), tOut.getSigmaZ2(), tOut.getSigmaZY()); - if (wInw[0] == 0.f || wOut[0] == 0.f) { - return -1; - } - std::array wTot = {wInw[0] + wOut[0], wInw[1] + wOut[1], wInw[2] + wOut[2]}; - auto cTot = inv2d(wTot[0], wTot[1], wTot[2]); - auto ywi = wInw[0] * tInw.getY() + wInw[2] * tInw.getZ() + wOut[0] * tOut.getY() + wOut[2] * tOut.getZ(); - auto zwi = wInw[2] * tInw.getY() + wInw[1] * tInw.getZ() + wOut[2] * tOut.getY() + wOut[1] * tOut.getZ(); - auto yw = ywi * cTot[0] + zwi * cTot[2]; - auto zw = ywi * cTot[2] + zwi * cTot[1]; - // posCl.push_back(clArr[i]->getXYZGlo(*o2::its::GeometryTGeo::Instance())); - auto phi = i == 0 ? tInw.getPhi() : tInw.getPhiPos(); - o2::math_utils::bringTo02Pi(phi); - resTrack.points.emplace_back(clArr[i]->getY() - yw, clArr[i]->getZ() - zw, cTot[0] + clArr[i]->getSigmaY2(), cTot[1] + clArr[i]->getSigmaZ2(), phi, clArr[i]->getZ(), clArr[i]->getSensorID(), i - 1); - if (!innerDone) { - resTrack.track = tInw; - innerDone = true; - } - } else { - LOGP(debug, "No cluster on lr {}", i); - } - } - } - return true; -} - -bool CheckResidSpec::refitPV(o2::dataformats::PrimaryVertex& pv, int vid) -{ - const auto& params = o2::checkresid::CheckResidConfig::Instance(); - std::vector tracks; - std::vector useTrack; - std::vector gidsITS; - int ntr = pv.getNContributors(), ntrIni = ntr; - tracks.reserve(ntr); - useTrack.reserve(ntr); - gidsITS.reserve(ntr); - const auto& vtref = mRecoData->getPrimaryVertexMatchedTrackRefs()[vid]; - auto trackIndex = mRecoData->getPrimaryVertexMatchedTracks(); - int itr = vtref.getFirstEntry(), itLim = itr + vtref.getEntries(); - for (; itr < itLim; itr++) { - auto vid = trackIndex[itr]; - if (vid.isPVContributor()) { - tracks.emplace_back().setPID(mRecoData->getTrackParam(vid).getPID()); - gidsITS.push_back(mRecoData->getITSContributorGID(vid)); - } - } - ntr = tracks.size(); - useTrack.resize(ntr); -#ifdef WITH_OPENMP -#pragma omp parallel for schedule(dynamic) num_threads(mNThreads) -#endif - for (int itr = 0; itr < ntr; itr++) { - if (!(useTrack[itr] = refitITStrack(tracks[itr], gidsITS[itr]))) { - tracks[itr] = mRecoData->getTrackParam(gidsITS[itr]); // this track will not be used but participates in prepareVertexRefit - } - } - ntr = 0; - for (auto v : useTrack) { - ntr++; - } - if (ntr < params.minPVContributors || !mVertexer.prepareVertexRefit(tracks, pv)) { - LOGP(warn, "Abandon vertex refit: NcontribNew = {} vs NcontribOld = {}", ntr, ntrIni); - return false; - } - LOGP(debug, "Original vtx: Nc:{} {}, chi2={}", pv.getNContributors(), pv.asString(), pv.getChi2()); - auto pvSave = pv; - pv = mVertexer.refitVertexFull(useTrack, pv); - LOGP(debug, "Refitted vtx: Nc:{} {}, chi2={}", ntr, pv.asString(), pv.getChi2()); - if (pv.getChi2() < 0.f) { - LOGP(warn, "Failed to refit PV {}", pvSave.asString()); - return false; - } - return true; -} - -bool CheckResidSpec::refitITStrack(o2::track::TrackParCov& track, GTrackID gid) -{ - // destination tack might have non-default PID assigned - const auto& trkITS = mRecoData->getITSTrack(gid); - const auto itsClRefs = mRecoData->getITSTracksClusterRefs(); - const auto& params = CheckResidConfig::Instance(); - auto pid = track.getPID(); - track = trkITS.getParamOut(); - track.setPID(pid); - auto nCl = trkITS.getNumberOfClusters(); - auto geom = o2::its::GeometryTGeo::Instance(); - auto prop = o2::base::Propagator::Instance(); - float bz = prop->getNominalBz(); - o2::track::TrackPar refLin{track}; - - for (int iCl = 0; iCl < nCl; iCl++) { // clusters are stored from outer to inner layers - const auto& cls = mITSClustersArray[itsClRefs[trkITS.getClusterEntry(iCl)]]; - auto alpha = geom->getSensorRefAlpha(cls.getSensorID()); - if (!(params.useStableRef ? track.rotate(alpha, refLin, bz) : track.rotate(alpha)) || - !prop->propagateTo(track, params.useStableRef ? &refLin : nullptr, cls.getX(), true)) { - LOGP(debug, "refitITStrack failed on propagation to cl#{}, alpha={}, x={} | {}", iCl, alpha, cls.getX(), track.asString()); - return false; - } - if (!track.update(cls)) { - LOGP(debug, "refitITStrack failed on update with cl#{}, | {}", iCl, track.asString()); - return false; - } - } - return true; -} - -void CheckResidSpec::endOfStream(EndOfStreamContext& ec) -{ - mDBGOut.reset(); -} - -void CheckResidSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) -{ - if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { - return; - } - /* - if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { - return; - } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } - */ - if (matcher == ConcreteDataMatcher("GLO", "MEANVERTEX", 0)) { - LOG(info) << "Imposing new MeanVertex: " << ((const o2::dataformats::MeanVertexObject*)obj)->asString(); - mMeanVtx = *(const o2::dataformats::MeanVertexObject*)obj; - mMeanVertexUpdated = true; - return; - } - if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) { - LOG(info) << "cluster dictionary updated"; - mITSDict = (const o2::itsmft::TopologyDictionary*)obj; - return; - } -} - -DataProcessorSpec getCheckResidSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC /*, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts*/) -{ - std::vector outputs; - auto dataRequest = std::make_shared(); - dataRequest->requestTracks(srcTracks, useMC); - dataRequest->requestClusters(srcClusters, useMC); - dataRequest->requestPrimaryVertices(useMC); - auto ggRequest = std::make_shared(false, // orbitResetTime - true, // GRPECS=true - true, // GRPLHCIF - true, // GRPMagField - true, // askMatLUT - o2::base::GRPGeomRequest::Aligned, // geometry - dataRequest->inputs, - true); - dataRequest->inputs.emplace_back("meanvtx", "GLO", "MEANVERTEX", 0, Lifetime::Condition, ccdbParamSpec("GLO/Calib/MeanVertex", {}, 1)); - Options opts{ - {"nthreads", VariantType::Int, 1, {"number of threads"}}, - }; - // o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - // o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); - - return DataProcessorSpec{ - "check-resid", - dataRequest->inputs, - outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, useMC /*, sclOpts*/)}, - opts}; -} - -} // namespace o2::checkresid diff --git a/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx new file mode 100644 index 0000000000000..6a1915791a911 --- /dev/null +++ b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidSpec.cxx @@ -0,0 +1,980 @@ +// 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. + +#include "GlobalTrackingStudy/CheckResidSpec.h" +#include "GlobalTrackingStudy/CheckResidTypes.h" +#include "GlobalTrackingStudy/CheckResidConfig.h" +#include +#include "ReconstructionDataFormats/Track.h" +#include +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsITSMFT/TrkClusRef.h" +#include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" +#include "ReconstructionDataFormats/TrackTPCITS.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include "DataFormatsCalibration/MeanVertexObject.h" +#include "DetectorsBase/Propagator.h" +#include "DetectorsBase/GeometryManager.h" +#include "SimulationDataFormat/MCEventLabel.h" +#include "SimulationDataFormat/MCUtils.h" +#include "CommonUtils/NameConf.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/ControlService.h" +#include "Framework/CCDBParamSpec.h" +#include "Framework/DeviceSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" +#include "ITSBase/GeometryTGeo.h" +#include "ITStracking/IOUtils.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include "ReconstructionDataFormats/PrimaryVertex.h" +#include "CommonUtils/TreeStreamRedirector.h" +#include "ReconstructionDataFormats/VtxTrackRef.h" +#include "DetectorsVertexing/PVertexer.h" +#include "GlobalTrackingStudy/HistoManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WITH_OPENMP +#include +#endif + +// Attention: in case the residuals are checked with geometry different from the one used for initial reconstruction, +// pass a --configKeyValues option for vertex refit as: +// ;pvertexer.useMeanVertexConstraint=false;pvertexer.meanVertexExtraErrSelection=0.2;pvertexer.iniScale2=100;pvertexer.acceptableScale2=10.; +// In any case, it is better to pass ;pvertexer.useMeanVertexConstraint=false; + +namespace o2::checkresid +{ +using namespace o2::framework; +using DetID = o2::detectors::DetID; +using DataRequest = o2::globaltracking::DataRequest; + +using PVertex = o2::dataformats::PrimaryVertex; +using V2TRef = o2::dataformats::VtxTrackRef; +using VTIndex = o2::dataformats::VtxTrackIndex; +using GTrackID = o2::dataformats::GlobalTrackID; +using timeEst = o2::dataformats::TimeStampWithError; + +class CheckResidSpec : public Task +{ + public: + CheckResidSpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool drawOnly, bool postProcOnly) + : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mDrawOnly(drawOnly), mPostProcOnly(postProcOnly) + { + } + ~CheckResidSpec() final = default; + void init(InitContext& ic) final; + void run(ProcessingContext& pc) final; + void endOfStream(EndOfStreamContext& ec) final; + void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final; + void process(); + + private: + void updateTimeDependentParams(ProcessingContext& pc); + bool refitPV(o2::dataformats::PrimaryVertex& pv, int vid); + bool refitITStrack(o2::track::TrackParCov& track, GTrackID gid); + bool processITSTrack(const o2::its::TrackITS& iTrack, const o2::dataformats::PrimaryVertex& pv, o2::checkresid::Track& resTrack); + void bookHistos(); + void fillHistos(const o2::checkresid::Track& trc); + void postProcessHistos(); + void drawHistos(); + + o2::globaltracking::RecoContainer* mRecoData = nullptr; + int mNThreads = 1; + bool mMeanVertexUpdated = false; + float mITSROFrameLengthMUS = 0.f; + o2::dataformats::MeanVertexObject mMeanVtx{}; + std::vector> mITSClustersArray; ///< ITS clusters created in run() method from compact clusters + const o2::itsmft::TopologyDictionary* mITSDict = nullptr; ///< cluster patterns dictionary + o2::vertexing::PVertexer mVertexer; + std::shared_ptr mDataRequest; + std::shared_ptr mGGCCDBRequest; + std::unique_ptr mDBGOut; + GTrackID::mask_t mTracksSrc{}; + + bool mDrawOnly = false; + bool mPostProcOnly = false; + bool mDraw = false; + bool mFillHistos = true; + bool mFillTree = true; + std::vector> mHManV{}; + o2::HistoManager* mHMan = nullptr; +}; + +void CheckResidSpec::init(InitContext& ic) +{ + mDraw = true; + if (!mDrawOnly) { + mDraw = ic.options().get("draw-report"); + mFillHistos = !ic.options().get("no-hist"); + mFillTree = !ic.options().get("no-tree"); + mNThreads = ic.options().get("nthreads"); + } + const auto& params = o2::checkresid::CheckResidConfig::Instance(); + int lane = ic.services().get().inputTimesliceId; + int maxLanes = ic.services().get().maxInputTimeslices; + std::string nm = params.outname; + if (maxLanes > 1) { + o2::conf::ConfigurableParam::updateFromString(fmt::format("checkresid.outname={}_t{}", nm, lane)); + } + if (mDraw) { + mFillHistos = true; + } + if (!mDrawOnly && mFillHistos) { + bookHistos(); + } + if (!params.ext_hm_list.empty()) { + auto vecNames = o2::utils::Str::tokenize(params.ext_hm_list, ','); + auto vecLegends = o2::utils::Str::tokenize(params.ext_leg_list, ','); + bool useLeg = true; + if (vecNames.size() != vecLegends.size()) { + LOGP(warn, "{} legend names provided for {} external histomanagers, will use file names as legends", vecLegends.size(), vecNames.size()); + useLeg = false; + } + int cntH = 0; + for (const auto& vn : vecNames) { + LOGP(info, "Loading external HistoManager {}", vn); + mHManV.emplace_back() = std::make_unique("", vn, true); + auto hm = mHManV.back().get(); + if (!hm) { + LOGP(error, "Failed to load histograms from {}", vn); + mHManV.pop_back(); + } else { + hm->SetName(useLeg ? vecLegends[cntH].c_str() : vn.c_str()); + } + cntH++; + } + } + if (mDrawOnly) { + return; + } + o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); +#ifndef WITH_OPENMP + if (mNThreads > 1) { + LOGP(warn, "No OpenMP"); + } + mNThreads = 1; +#endif + if (mFillTree) { + mDBGOut = std::make_unique(fmt::format("{}.root", params.outname).c_str(), "recreate"); + } +} + +void CheckResidSpec::run(ProcessingContext& pc) +{ + bool quit = false; + if (mPostProcOnly) { + + postProcessHistos(); + quit = true; + } + if (mDrawOnly) { + drawHistos(); + quit = true; + } + if (quit) { + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } + o2::globaltracking::RecoContainer recoData; + mRecoData = &recoData; + mRecoData->collectData(pc, *mDataRequest.get()); // select tracks of needed type, with minimal cuts, the real selected will be done in the vertexer + mRecoData = &recoData; + updateTimeDependentParams(pc); // Make sure this is called after recoData.collectData, which may load some conditions + process(); + mRecoData = nullptr; +} + +void CheckResidSpec::updateTimeDependentParams(ProcessingContext& pc) +{ + o2::base::GRPGeomHelper::instance().checkUpdates(pc); + pc.inputs().get("meanvtx"); + static bool initOnceDone = false; + if (!initOnceDone) { // this params need to be queried only once + const auto& params = o2::checkresid::CheckResidConfig::Instance(); + initOnceDone = true; + // Note: reading of the ITS AlpideParam needed for ITS timing is done by the RecoContainer + auto grp = o2::base::GRPGeomHelper::instance().getGRPECS(); + const auto& alpParams = o2::itsmft::DPLAlpideParam::Instance(); + if (!grp->isDetContinuousReadOut(DetID::ITS)) { + mITSROFrameLengthMUS = alpParams.roFrameLengthTrig / 1.e3; // ITS ROFrame duration in \mus + } else { + mITSROFrameLengthMUS = alpParams.roFrameLengthInBC * o2::constants::lhc::LHCBunchSpacingNS * 1e-3; // ITS ROFrame duration in \mus + } + auto geom = o2::its::GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G, o2::math_utils::TransformType::T2G)); + o2::conf::ConfigurableParam::updateFromString("pvertexer.useTimeInChi2=false;"); + mVertexer.init(); + } + if (mMeanVertexUpdated) { + mMeanVertexUpdated = false; + mVertexer.setMeanVertex(&mMeanVtx); + mVertexer.initMeanVertexConstraint(); + } +} + +void CheckResidSpec::process() +{ + if (!mITSDict) { + LOGP(fatal, "ITS data is not loaded"); + } + 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); + + auto pvvec = mRecoData->getPrimaryVertices(); + auto trackIndex = mRecoData->getPrimaryVertexMatchedTracks(); // Global ID's for associated tracks + auto vtxRefs = mRecoData->getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs + auto prop = o2::base::Propagator::Instance(); + static int TFCount = 0; + int nv = vtxRefs.size() - 1; + std::vector> slots; + slots.resize(mNThreads); + int nvGood = 0, nvUse = 0, nvRefFail = 0; + long pvFitDuration{}; + for (int iv = 0; iv < nv; iv++) { + const auto& vtref = vtxRefs[iv]; + auto pve = pvvec[iv]; + if (pve.getNContributors() < params.minPVContributors) { + continue; + } + nvGood++; + if (params.refitPV) { + LOGP(debug, "Refitting PV#{} of {} tracks", iv, pve.getNContributors()); + auto tStartPVF = std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count(); + bool res = refitPV(pve, iv); + pvFitDuration += std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count() - tStartPVF; + if (!res) { + nvRefFail++; + continue; + } + } + nvUse++; + for (int is = 0; is < GTrackID::NSources; is++) { + if (!mTracksSrc[is] || !mRecoData->isTrackSourceLoaded(is)) { + continue; + } + int idMin = vtref.getFirstEntryOfSource(is), idMax = idMin + vtref.getEntriesOfSource(is); + DetID::mask_t dm = GTrackID::getSourceDetectorsMask(is); + if (!dm[DetID::ITS]) { + continue; + } + if (dm[DetID::TPC] && params.minTPCCl > 0 && !mRecoData->isTrackSourceLoaded(GTrackID::TPC)) { + LOGP(fatal, "Cut on TPC tracks is requested by they are not loaded"); + } +#ifdef WITH_OPENMP +#pragma omp parallel for schedule(dynamic) num_threads(mNThreads) +#endif + for (int i = idMin; i < idMax; i++) { + auto vid = trackIndex[i]; + bool pvCont = vid.isPVContributor(); + if (!pvCont && params.pvcontribOnly) { + continue; + } + if (dm[DetID::TPC] && params.minTPCCl > 0 && mRecoData->getTPCTrack(mRecoData->getTPCContributorGID(vid)).getNClusters() < params.minTPCCl) { + continue; + } + auto gidITS = mRecoData->getITSContributorGID(vid); + if (gidITS.getSource() != GTrackID::ITS) { + continue; + } + const auto& trc = mRecoData->getTrackParam(vid); + const auto& itsTrack = mRecoData->getITSTrack(gidITS); + if (itsTrack.getNClusters() < params.minITSCl) { + continue; + } + auto pt = trc.getPt(); + if (pt < params.minPt || pt > params.maxPt) { + continue; + } + if (std::abs(trc.getTgl()) > params.maxTgl) { + continue; + } + +#ifdef WITH_OPENMP + auto& accum = slots[omp_get_thread_num()]; +#else + auto& accum = slots[0]; +#endif + auto& resTrack = accum.emplace_back(); + resTrack.gid = vid; + if (!processITSTrack(itsTrack, pve, resTrack)) { + accum.pop_back(); + continue; + } + } + } + } + // output + for (const auto& accum : slots) { + for (const auto& tr : accum) { + if (mDBGOut) { + (*mDBGOut) << "res" << "tr=" << tr << "\n"; + } + if (mHMan) { + fillHistos(tr); + } + } + } + LOGP(info, "processed {} PVs out of {} good vertices (out of {} in total), PV refits took {} mus, {} refits failed", nvUse, nvGood, nv, pvFitDuration, nvRefFail); + TFCount++; +} + +bool CheckResidSpec::processITSTrack(const o2::its::TrackITS& iTrack, const o2::dataformats::PrimaryVertex& pv, o2::checkresid::Track& resTrack) +{ + const auto itsClRefs = mRecoData->getITSTracksClusterRefs(); + auto trFitInw = iTrack.getParamOut(); // seed for inward refit + auto trFitOut = iTrack.getParamIn(); // seed for outward refit + auto prop = o2::base::Propagator::Instance(); + auto geom = o2::its::GeometryTGeo::Instance(); + float pvAlpha = 0; + float bz = prop->getNominalBz(); + std::array*, 8> clArr{}; + const auto& params = CheckResidConfig::Instance(); + std::array extrapOut, extrapInw; // 2-way Kalman extrapolations, vertex + 7 layers + + auto rotateTrack = [bz](o2::track::TrackParCov& tr, float alpha, o2::track::TrackPar* refLin) { + return refLin ? tr.rotate(alpha, *refLin, bz) : tr.rotate(alpha); + }; + + auto accountCluster = [&](int i, std::array& extrapDest, o2::track::TrackParCov& tr, o2::track::TrackPar* refLin) { + if (clArr[i]) { // update with cluster + if (!rotateTrack(tr, i == 0 ? pvAlpha : geom->getSensorRefAlpha(clArr[i]->getSensorID()), refLin) || + !prop->propagateTo(tr, refLin, clArr[i]->getX(), true)) { + return 0; + } + extrapDest[i] = tr; // before update + if (!tr.update(*clArr[i])) { + return 0; + } + } else { + extrapDest[i].invalidate(); + return -1; + } + return 1; + }; + + auto inv2d = [](float s00, float s11, float s01) -> std::array { + auto det = s00 * s11 - s01 * s01; + if (det < 1e-16) { + LOGP(error, "Singular det {}, input: {} {} {}", det, s00, s11, s01); + return {0.f, 0.f, 0.f}; + } + det = 1.f / det; + return {s11 * det, s00 * det, -s01 * det}; + }; + + resTrack.points.clear(); + if (!prop->propagateToDCA(pv, trFitOut, bz)) { + LOGP(debug, "Failed to propagateToDCA, {}", trFitOut.asString()); + return false; + } + o2::BaseCluster bcPV; + if (params.addPVAsCluster) { + float cosAlp, sinAlp; + pvAlpha = trFitOut.getAlpha(); + o2::math_utils::sincos(trFitOut.getAlpha(), sinAlp, cosAlp); // vertex position rotated to track frame + bcPV.setXYZ(pv.getX() * cosAlp + pv.getY() * sinAlp, -pv.getX() * sinAlp + pv.getY() * cosAlp, pv.getZ()); + bcPV.setSigmaY2(0.5 * (pv.getSigmaX2() + pv.getSigmaY2())); + bcPV.setSigmaZ2(pv.getSigmaZ2()); + bcPV.setSensorID(-1); + clArr[0] = &bcPV; + } + // collect all track clusters to array, placing them to layer+1 slot + int nCl = iTrack.getNClusters(); + for (int i = 0; i < nCl; i++) { // clusters are ordered from the outermost to the innermost + const auto& curClu = mITSClustersArray[itsClRefs[iTrack.getClusterEntry(i)]]; + + int llr = geom->getLayer(curClu.getSensorID()); + if (clArr[1 + llr]) { + LOGP(error, "Cluster at lr {} was already assigned, old sens {}, new sens {}", llr, clArr[1 + llr]->getSensorID(), curClu.getSensorID()); + } + clArr[1 + geom->getLayer(curClu.getSensorID())] = &curClu; + } + o2::track::TrackPar refLinInw0, refLinOut0, *refLinOut = nullptr, *refLinInw = nullptr; + o2::track::TrackPar refLinIBOut0, refLinOBInw0, *refLinOBInw = nullptr, *refLinIBOut = nullptr; + if (params.useStableRef) { + refLinOut = &(refLinOut0 = trFitOut); + refLinInw = &(refLinInw0 = trFitInw); + } + trFitOut.resetCovariance(); + trFitOut.setCov(trFitOut.getQ2Pt() * trFitOut.getQ2Pt() * trFitOut.getCov()[14], 14); + trFitInw.resetCovariance(); + trFitInw.setCov(trFitInw.getQ2Pt() * trFitInw.getQ2Pt() * trFitInw.getCov()[14], 14); + // fit in inward and outward direction + for (int i = 0; i <= 7; i++) { + int resOut, resInw; + // process resOut in ascending order (0-->7) and resInw in descending order (7-->0) + if (!(resOut = accountCluster(i, extrapOut, trFitOut, refLinOut)) || !(resInw = accountCluster(7 - i, extrapInw, trFitInw, refLinInw))) { + return false; + } + // at layer 3, find the IB track (trIBOut) and the OB track (trOBInw) + // propagate both trcaks to a common radius, RCompIBOB (12cm), and rotates + // them to the same reference frame for comparison + if (i == 3 && resOut == 1 && resInw == 1 && params.doIBOB && nCl == 7) { + resTrack.trIBOut = trFitOut; // outward track updated at outermost IB layer + resTrack.trOBInw = trFitInw; // inward track updated at innermost OB layer + o2::track::TrackPar refLinIBOut0, refLinIBIn0; + if (refLinOut) { + refLinIBOut = &(refLinIBOut0 = refLinOut0); + refLinOBInw = &(refLinOBInw0 = refLinInw0); + } + float xRref; + if (!resTrack.trOBInw.getXatLabR(params.rCompIBOB, xRref, bz) || + !prop->propagateTo(resTrack.trOBInw, refLinOBInw, xRref, true) || + !rotateTrack(resTrack.trOBInw, resTrack.trOBInw.getPhiPos(), refLinOBInw) || // propagate OB track to ref R and rotate + !rotateTrack(resTrack.trIBOut, resTrack.trOBInw.getAlpha(), refLinIBOut) || + !prop->propagateTo(resTrack.trIBOut, refLinIBOut, resTrack.trOBInw.getX(), true)) { // rotate OB track to same frame and propagate to same X + // if any propagation or rotation steps fail, invalidate both tracks + return false; + } + } + } + + bool innerDone = false; + if (params.doResid) { + for (int i = 0; i <= 7; i++) { + if (clArr[i]) { + // calculate interpolation as a weighted mean of inward/outward extrapolations to this layer + const auto &tInw = extrapInw[i], &tOut = extrapOut[i]; + auto wInw = inv2d(tInw.getSigmaY2(), tInw.getSigmaZ2(), tInw.getSigmaZY()); + auto wOut = inv2d(tOut.getSigmaY2(), tOut.getSigmaZ2(), tOut.getSigmaZY()); + if (wInw[0] == 0.f || wOut[0] == 0.f) { + return false; + } + std::array wTot = {wInw[0] + wOut[0], wInw[1] + wOut[1], wInw[2] + wOut[2]}; + auto cTot = inv2d(wTot[0], wTot[1], wTot[2]); + auto ywi = wInw[0] * tInw.getY() + wInw[2] * tInw.getZ() + wOut[0] * tOut.getY() + wOut[2] * tOut.getZ(); + auto zwi = wInw[2] * tInw.getY() + wInw[1] * tInw.getZ() + wOut[2] * tOut.getY() + wOut[1] * tOut.getZ(); + auto yw = ywi * cTot[0] + zwi * cTot[2]; + auto zw = ywi * cTot[2] + zwi * cTot[1]; + // posCl.push_back(clArr[i]->getXYZGlo(*o2::its::GeometryTGeo::Instance())); + auto phi = i == 0 ? tInw.getPhi() : tInw.getPhiPos(); + o2::math_utils::bringTo02Pi(phi); + resTrack.points.emplace_back(clArr[i]->getY() - yw, clArr[i]->getZ() - zw, cTot[0] + clArr[i]->getSigmaY2(), cTot[1] + clArr[i]->getSigmaZ2(), phi, clArr[i]->getZ(), clArr[i]->getSensorID(), i - 1); + if (!innerDone) { + resTrack.track = tInw; + innerDone = true; + } + } else { + LOGP(debug, "No cluster on lr {}", i); + } + } + } + return true; +} + +bool CheckResidSpec::refitPV(o2::dataformats::PrimaryVertex& pv, int vid) +{ + const auto& params = o2::checkresid::CheckResidConfig::Instance(); + std::vector tracks; + std::vector useTrack; + std::vector gidsITS; + int ntr = pv.getNContributors(), ntrIni = ntr; + tracks.reserve(ntr); + useTrack.reserve(ntr); + gidsITS.reserve(ntr); + const auto& vtref = mRecoData->getPrimaryVertexMatchedTrackRefs()[vid]; + auto trackIndex = mRecoData->getPrimaryVertexMatchedTracks(); + int itr = vtref.getFirstEntry(), itLim = itr + vtref.getEntries(); + for (; itr < itLim; itr++) { + auto tid = trackIndex[itr]; + if (tid.isPVContributor() && mRecoData->isTrackSourceLoaded(tid.getSource())) { + tracks.emplace_back().setPID(mRecoData->getTrackParam(tid).getPID()); + gidsITS.push_back(mRecoData->getITSContributorGID(tid)); + } + } + ntr = tracks.size(); + useTrack.resize(ntr); +#ifdef WITH_OPENMP +#pragma omp parallel for schedule(dynamic) num_threads(mNThreads) +#endif + for (int itr = 0; itr < ntr; itr++) { + if (!(useTrack[itr] = refitITStrack(tracks[itr], gidsITS[itr]))) { + tracks[itr] = mRecoData->getTrackParam(gidsITS[itr]); // this track will not be used but participates in prepareVertexRefit + } + } + ntr = 0; + for (auto v : useTrack) { + ntr++; + } + if (ntr < params.minPVContributors || !mVertexer.prepareVertexRefit(tracks, pv)) { + LOGP(warn, "Abandon vertex refit: NcontribNew = {} vs NcontribOld = {}", ntr, ntrIni); + return false; + } + LOGP(debug, "Original vtx: Nc:{} {}, chi2={}", pv.getNContributors(), pv.asString(), pv.getChi2()); + auto pvSave = pv; + pv = mVertexer.refitVertexFull(useTrack, pv); + LOGP(debug, "Refitted vtx: Nc:{} {}, chi2={}", ntr, pv.asString(), pv.getChi2()); + if (pv.getChi2() < 0.f) { + LOGP(warn, "Failed to refit PV {}", pvSave.asString()); + return false; + } + return true; +} + +bool CheckResidSpec::refitITStrack(o2::track::TrackParCov& track, GTrackID gid) +{ + // destination tack might have non-default PID assigned + const auto& trkITS = mRecoData->getITSTrack(gid); + const auto itsClRefs = mRecoData->getITSTracksClusterRefs(); + const auto& params = CheckResidConfig::Instance(); + auto pid = track.getPID(); + track = trkITS.getParamOut(); + track.resetCovariance(); + track.setCov(track.getQ2Pt() * track.getQ2Pt() * track.getCov()[14], 14); + track.setPID(pid); + auto nCl = trkITS.getNumberOfClusters(); + auto geom = o2::its::GeometryTGeo::Instance(); + auto prop = o2::base::Propagator::Instance(); + float bz = prop->getNominalBz(); + o2::track::TrackPar refLin{track}; + + for (int iCl = 0; iCl < nCl; iCl++) { // clusters are stored from outer to inner layers + const auto& cls = mITSClustersArray[itsClRefs[trkITS.getClusterEntry(iCl)]]; + auto alpha = geom->getSensorRefAlpha(cls.getSensorID()); + if (!(params.useStableRef ? track.rotate(alpha, refLin, bz) : track.rotate(alpha)) || + !prop->propagateTo(track, params.useStableRef ? &refLin : nullptr, cls.getX(), true)) { + LOGP(debug, "refitITStrack failed on propagation to cl#{}, alpha={}, x={} | {}", iCl, alpha, cls.getX(), track.asString()); + return false; + } + if (!track.update(cls)) { + LOGP(debug, "refitITStrack failed on update with cl#{}, | {}", iCl, track.asString()); + return false; + } + } + return true; +} + +void CheckResidSpec::fillHistos(const o2::checkresid::Track& trc) +{ + const auto& params = CheckResidConfig::Instance(); + int np = trc.points.size(); + auto pt = trc.track.getPt(); + if (pt < params.minPt || pt > params.maxPt) { + return; + } + for (int ip = 0; ip < np; ip++) { + const auto& pnt = trc.points[ip]; + int il = pnt.lr >= 0 ? pnt.lr + 1 : 0; + mHMan->getHisto2F(il * 10 + 0 * 100)->Fill(pnt.phi, pnt.dy); + mHMan->getHisto2F(il * 10 + 0 * 100 + 1000)->Fill(pnt.z, pnt.dy); + mHMan->getHisto2F(il * 10 + 0 * 100 + 2000)->Fill(pt, pnt.dy); + mHMan->getHisto2F(il * 10 + 0 * 100 + 3000)->Fill(trc.track.getTgl(), pnt.dy); + if (pnt.sig2y > 0) { + auto pull = pnt.dy / std::sqrt(pnt.sig2y); + mHMan->getHisto2F(il * 10 + 0 * 100 + 5)->Fill(pnt.phi, pull); + mHMan->getHisto2F(il * 10 + 0 * 100 + 5 + 1000)->Fill(pnt.z, pull); + mHMan->getHisto2F(il * 10 + 0 * 100 + 5 + 2000)->Fill(pt, pull); + mHMan->getHisto2F(il * 10 + 0 * 100 + 5 + 3000)->Fill(trc.track.getTgl(), pull); + } + mHMan->getHisto2F(il * 10 + 1 * 100)->Fill(pnt.phi, pnt.dz); + mHMan->getHisto2F(il * 10 + 1 * 100 + 1000)->Fill(pnt.z, pnt.dz); + mHMan->getHisto2F(il * 10 + 1 * 100 + 2000)->Fill(pt, pnt.dz); + mHMan->getHisto2F(il * 10 + 1 * 100 + 3000)->Fill(trc.track.getTgl(), pnt.dz); + if (pnt.sig2z > 0) { + auto pull = pnt.dz / std::sqrt(pnt.sig2z); + mHMan->getHisto2F(il * 10 + 1 * 100 + 5)->Fill(pnt.phi, pull); + mHMan->getHisto2F(il * 10 + 1 * 100 + 5 + 1000)->Fill(pnt.z, pull); + mHMan->getHisto2F(il * 10 + 1 * 100 + 5 + 2000)->Fill(pt, pull); + mHMan->getHisto2F(il * 10 + 1 * 100 + 5 + 3000)->Fill(trc.track.getTgl(), pull); + } + } + //-------------- + if (trc.trIBOut.getX() > 1 && std::abs(trc.trIBOut.getX() - trc.trOBInw.getX()) < 0.1) { + for (int ip = 0; ip < 5; ip++) { + float d = trc.trIBOut.getParam(ip) - trc.trOBInw.getParam(ip); + mHMan->getHisto2F(10000 + ip * 10)->Fill(trc.trIBOut.getPhiPos(), d); + mHMan->getHisto2F(11000 + ip * 10)->Fill(trc.trIBOut.getZ(), d); + mHMan->getHisto2F(12000 + ip * 10)->Fill(pt, d); + mHMan->getHisto2F(13000 + ip * 10)->Fill(trc.track.getTgl(), d); + float sg = trc.trIBOut.getCovarElem(ip, ip) + trc.trOBInw.getCovarElem(ip, ip); + if (sg > 0) { + auto pull = d / std::sqrt(sg); + mHMan->getHisto2F(10000 + ip * 10 + 5)->Fill(trc.trIBOut.getPhiPos(), pull); + mHMan->getHisto2F(11000 + ip * 10 + 5)->Fill(trc.trIBOut.getZ(), pull); + mHMan->getHisto2F(12000 + ip * 10 + 5)->Fill(pt, pull); + mHMan->getHisto2F(13000 + ip * 10 + 5)->Fill(trc.track.getTgl(), pull); + } + } + } +} + +void CheckResidSpec::bookHistos() +{ + const auto& params = o2::checkresid::CheckResidConfig::Instance(); + mHManV.emplace_back() = std::make_unique("", fmt::format("{}_hman.root", params.outname)); + mHMan = mHManV.back().get(); + mHMan->SetName(params.outname.c_str()); + auto defLogAxis = [](float xMn, float xMx, int nbin) { // get array for log axis + if (xMn <= 0 || xMx <= xMn || nbin < 2) { + LOGP(fatal, "Wrong log axis request: xmin = {} xmax = {} nbins = {}", xMn, xMx, nbin); + } + auto dx = std::log(xMx / xMn) / nbin; + std::vector xax(nbin + 1); + for (int i = 0; i <= nbin; i++) { + xax[i] = xMn * std::exp(dx * i); + } + return xax; + }; + float minPt = std::max(0.1f, params.minPt), maxPt = std::min(50.f, params.maxPt); + auto ptax = defLogAxis(minPt, maxPt, params.nBinsPt); + + for (int il = 0; il < 8; il++) { + std::string lrName = il == 0 ? "Vtx" : fmt::format("Lr{}", il - 1); + for (int iyz = 0; iyz < 2; iyz++) { + std::string dname = iyz == 0 ? "dy" : "dz", dtit = iyz == 0 ? "#DeltaY" : "#DeltaZ"; + auto h2 = new TH2F(fmt::format("{}_{}_{}", dname, lrName, "phi").c_str(), fmt::format("{}_{{{}}} vs {};#phi;{}", dtit, lrName, "#phi", dtit).c_str(), params.nBinsPhi, 0, TMath::Pi() * 2, params.nBinsRes, -params.maxDYZ[il], params.maxDYZ[il]); + mHMan->addHisto(h2, il * 10 + iyz * 100); + auto h2p = new TH2F(fmt::format("{}_{}_{}_pull", dname, lrName, "phi").c_str(), fmt::format("pull {}_{{{}}} vs {};#phi; pull{}", dtit, lrName, "phi", dtit).c_str(), params.nBinsPhi, 0, TMath::Pi() * 2, params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(h2p, il * 10 + iyz * 100 + 5); + + auto hz2 = new TH2F(fmt::format("{}_{}_{}", dname, lrName, "Z").c_str(), fmt::format("{}_{{{}}} vs {};Z;{}", dtit, lrName, "Z", dtit).c_str(), params.nBinsZ, -params.zranges[il], params.zranges[il], params.nBinsRes, -params.maxDYZ[il], params.maxDYZ[il]); + mHMan->addHisto(hz2, il * 10 + iyz * 100 + 1000); + auto hz2p = new TH2F(fmt::format("{}_{}_{}_pull", dname, lrName, "Z").c_str(), fmt::format("pull {}_{{{}}} vs {};Z; pull{}", dtit, lrName, "Z", dtit).c_str(), params.nBinsZ, -params.zranges[il], params.zranges[il], params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(hz2p, il * 10 + iyz * 100 + 5 + 1000); + + auto hpt2 = new TH2F(fmt::format("{}_{}_{}", dname, lrName, "Pt").c_str(), fmt::format("{}_{{{}}} vs {};p_{{T}};{}", dtit, lrName, "p_{T}", dtit).c_str(), params.nBinsPt, ptax.data(), params.nBinsRes, -params.maxDYZ[il], params.maxDYZ[il]); + mHMan->addHisto(hpt2, il * 10 + iyz * 100 + 2000); + auto hpt2p = new TH2F(fmt::format("{}_{}_{}_pull", dname, lrName, "Pt").c_str(), fmt::format("pull {}_{{{}}} vs {};p_{{T}}; pull{}", dtit, lrName, "p_{T}", dtit).c_str(), params.nBinsPt, ptax.data(), params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(hpt2p, il * 10 + iyz * 100 + 5 + 2000); + + auto htgl2 = new TH2F(fmt::format("{}_{}_{}", dname, lrName, "tgl").c_str(), fmt::format("{}_{{{}}} vs {};tg#lambda;{}", dtit, lrName, "tg#lambda", dtit).c_str(), params.nBinsTgl, -params.maxTgl, params.maxTgl, params.nBinsRes, -params.maxDYZ[il], params.maxDYZ[il]); + mHMan->addHisto(htgl2, il * 10 + iyz * 100 + 3000); + auto htgl2p = new TH2F(fmt::format("{}_{}_{}_pull", dname, lrName, "tgl").c_str(), fmt::format("pull {}_{{{}}} vs {};tg#lambda; pull{}", dtit, lrName, "tg#lambda", dtit).c_str(), params.nBinsTgl, -params.maxTgl, params.maxTgl, params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(htgl2p, il * 10 + iyz * 100 + 5 + 3000); + } + } + + for (int ip = 0; ip < 5; ip++) { + auto h2 = new TH2F(fmt::format("dPar{}_IBOBphi", ip).c_str(), fmt::format("#Delta par{} IB-OB vs phi;#phi;#Delta par{}", ip, ip).c_str(), params.nBinsPhi, 0, TMath::Pi() * 2, params.nBinsRes, -params.maxDPar[ip], params.maxDPar[ip]); + mHMan->addHisto(h2, 10000 + ip * 10); + auto h2p = new TH2F(fmt::format("dPar{}_IBOBphi_pull", ip).c_str(), fmt::format("pull #Delta par{} IB-OB vs phi;#phi;pull #Delta par{}", ip, ip).c_str(), params.nBinsPhi, 0, TMath::Pi() * 2, params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(h2p, 10000 + ip * 10 + 5); + + auto hz2 = new TH2F(fmt::format("dPar{}_IBOBz", ip).c_str(), fmt::format("#Delta par{} IB-OB vs Z;Z;#Delta par{}", ip, ip).c_str(), params.nBinsZ, -20., 20., params.nBinsRes, -params.maxDPar[ip], params.maxDPar[ip]); + mHMan->addHisto(hz2, 11000 + ip * 10); + auto hz2p = new TH2F(fmt::format("dPar{}_IBOBz_pull", ip).c_str(), fmt::format("pull #Delta par{} IB-OB vs Z;Z;pull #Delta par{}", ip, ip).c_str(), params.nBinsZ, -20., 20., params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(hz2p, 11000 + ip * 10 + 5); + + auto hpt2 = new TH2F(fmt::format("dPar{}_IBOBpt", ip).c_str(), fmt::format("#Delta par{} IB-OB vs pT;p_{{T}};#Delta par{}", ip, ip).c_str(), params.nBinsPt, ptax.data(), params.nBinsRes, -params.maxDPar[ip], params.maxDPar[ip]); + mHMan->addHisto(hpt2, 12000 + ip * 10); + auto hpt2p = new TH2F(fmt::format("dPar{}_IBOBpt_pull", ip).c_str(), fmt::format("pull #Delta par{} IB-OB vs pT;p_{{T}};pull #Delta par{}", ip, ip).c_str(), params.nBinsPt, ptax.data(), params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(hpt2p, 12000 + ip * 10 + 5); + + auto htgl2 = new TH2F(fmt::format("dPar{}_IBOBtgl", ip).c_str(), fmt::format("#Delta par{} IB-OB vs tg#lambda;tg#lambda;#Delta par{}", ip, ip).c_str(), params.nBinsTgl, -params.maxTgl, params.maxTgl, params.nBinsRes, -params.maxDPar[ip], params.maxDPar[ip]); + mHMan->addHisto(htgl2, 13000 + ip * 10); + auto htgl2p = new TH2F(fmt::format("dPar{}_IBOBtgl_pull", ip).c_str(), fmt::format("pull #Delta par{} IB-OB vs tg#lambda;tg#lambda;pull #Delta par{}", ip, ip).c_str(), params.nBinsTgl, -params.maxTgl, params.maxTgl, params.nBinsRes, -params.maxPull, params.maxPull); + mHMan->addHisto(htgl2p, 13000 + ip * 10 + 5); + } +} + +void CheckResidSpec::postProcessHistos() +{ + printf("Fitting histos\n"); + if (!mHMan) { + if (mHManV.empty()) { + LOGP(warn, "nothing to process"); + return; + } + mHMan = mHManV[0].get(); + } + const auto& params = o2::checkresid::CheckResidConfig::Instance(); + auto gs = new TF1("gs", "gaus", -1, 1); + int maxH = mPostProcOnly ? mHManV.size() : 1; + TObjArray arr; + for (int ihm = 0; ihm < maxH; ihm++) { + auto* histm = mHManV[ihm].get(); + auto fitSlices = [&](int id) { + auto h2 = histm->getHisto2F(id); + if (!h2 || h2->GetEntries() < params.minHistoStat2Fit) { + return; + } + h2->FitSlicesY(gs, 0, -1, 0, "QNR", &arr); + arr.SetOwner(true); + TH1* hmean = (TH1*)arr.RemoveAt(1); + if (hmean) { + hmean->SetTitle(Form("<%s>", h2->GetTitle())); + histm->addHisto(hmean, id + 1); + } + TH1* hsig = (TH1*)arr.RemoveAt(2); + if (hsig) { + hsig->SetTitle(Form("#sigma(%s)", h2->GetTitle())); + histm->addHisto(hsig, id + 2); + } + }; + for (int ioffs = 0; ioffs <= 3; ioffs++) { // vs phi, Z, pT, tgl + int offs = ioffs * 1000; + for (int iht = 0; iht < 2; iht++) { // resid, pull + int offsV = iht == 0 ? 0 : 5; + for (int il = 0; il < 8; il++) { + for (int iyz = 0; iyz < 2; iyz++) { + fitSlices(il * 10 + iyz * 100 + offsV + offs); + } + } + for (int ip = 0; ip < 5; ip++) { + fitSlices(10000 + ip * 10 + offsV + offs); + } + } + } + histm->write(); + } + delete gs; +} + +void CheckResidSpec::drawHistos() +{ + gROOT->SetBatch(true); + gStyle->SetTitleX(0.2); + gStyle->SetTitleY(0.88); + gStyle->SetTitleW(0.25); + gStyle->SetOptStat(0); + int nhm = mHManV.size(); + std::array hcol{EColor::kRed, EColor::kBlue, EColor::kGreen + 2}; + std::unique_ptr lg; + lg = std::make_unique(0.12, 0.13, 0.9, 0.13 + std::min(0.5f, nhm * 0.2f / 3.f)); + lg->SetFillStyle(0); + lg->SetBorderSize(0); + for (int i = 0; i < nhm; i++) { + auto hman = mHManV[i].get(); + if (!hman || hman->GetLast() < 1) { + continue; + } + hman->setMarkerStyle(20 + i + (i % 2) * 4, 0.5); + hman->setColor(hcol[i % hcol.size()]); + auto le = lg->AddEntry(hman->getHisto(1), hman->GetName(), "lp"); + le->SetTextColor(hcol[i % hcol.size()]); + } + TCanvas cly("cly", "", 600, 800), clz("clz", "", 600, 800), clpar("clpar", "", 600, 800); + TCanvas czly("czly", "", 600, 800), czlz("czlz", "", 600, 800), czlpar("czlpar", "", 600, 800); + const auto& params = o2::checkresid::CheckResidConfig::Instance(); + + auto AddLabel = [](const char* txt, float x = 0.1, float y = 0.9, int color = kBlack, float size = 0.04) { + TLatex* lt = new TLatex(x, y, txt); + lt->SetNDC(); + lt->SetTextColor(color); + lt->SetTextSize(size); + lt->Draw(); + return lt; + }; + + auto drawResLr = [this](TCanvas& canv, int offs, const float resMM[8], bool logX) { + canv.Clear(); + canv.Divide(2, 4); + int nh = this->mHManV.size(); + for (int i = 0; i < 8; i++) { + canv.cd(i + 1); + bool same = false; + for (int j = 0; j < nh; j++) { + auto hman = this->mHManV[j].get(); + if (!hman || hman->GetLast() < 1) { + continue; + } + if (auto histo = hman->getHisto(10 * i + offs)) { + histo->Draw(same ? "same" : ""); + if (!same) { + histo->SetMinimum(-resMM[i]); + histo->SetMaximum(resMM[i]); + same = true; + } + } + } + gPad->SetGrid(); + gPad->SetLogx(logX); + } + }; + + auto drawResPar = [this](TCanvas& canv, int offs, const float resMM[8], bool logX) { + canv.Clear(); + canv.Divide(2, 3); + int nh = this->mHManV.size(); + for (int i = 0; i < 5; i++) { + canv.cd(i + 1); + bool same = false; + for (int j = 0; j < nh; j++) { + auto hman = this->mHManV[j].get(); + if (!hman || hman->GetLast() < 1) { + continue; + } + if (auto histo = hman->getHisto(10 * i + offs)) { + histo->Draw(same ? "same" : ""); + if (!same) { + histo->SetMinimum(-resMM[i]); + histo->SetMaximum(resMM[i]); + same = true; + } + } + } + gPad->SetGrid(); + gPad->SetLogx(logX); + } + }; + + cly.Print(Form("%s_hman.pdf[", params.outname.c_str())); + drawResLr(cly, 1, params.resMMLrY, false); + cly.cd(2); + lg->Draw(); + AddLabel("Y residuals", 0.1, 0.95); + cly.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(clz, 101, params.resMMLrZ, false); + clz.cd(2); + lg->Draw(); + AddLabel("Z residuals", 0.1, 0.95); + clz.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czly, 1001, params.resMMLrY, false); + czly.cd(2); + lg->Draw(); + AddLabel("Y residuals", 0.1, 0.95); + czly.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czlz, 1101, params.resMMLrZ, false); + czlz.cd(2); + lg->Draw(); + AddLabel("Z residuals", 0.1, 0.95); + czlz.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czly, 2001, params.resMMLrY, true); + czly.cd(2); + lg->Draw(); + AddLabel("Y residuals", 0.1, 0.95); + czly.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czlz, 2101, params.resMMLrZ, true); + czlz.cd(2); + lg->Draw(); + AddLabel("Z residuals", 0.1, 0.95); + czlz.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czly, 3001, params.resMMLrY, false); + czly.cd(2); + lg->Draw(); + AddLabel("Y residuals", 0.1, 0.95); + czly.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResLr(czlz, 3101, params.resMMLrZ, false); + czlz.cd(2); + lg->Draw(); + AddLabel("Z residuals", 0.1, 0.95); + czlz.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResPar(clpar, 10001, params.resMMPar, false); + clpar.cd(6); + lg->Draw(); + AddLabel("IB-OB tracks params differences at R = 12 cm", 0.2, 0.8); + clpar.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResPar(czlpar, 11001, params.resMMPar, false); + czlpar.cd(6); + lg->Draw(); + AddLabel("IB-OB tracks params differences at R = 12 cm", 0.2, 0.8); + czlpar.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResPar(czlpar, 12001, params.resMMPar, true); + czlpar.cd(6); + lg->Draw(); + AddLabel("IB-OB tracks params differences at R = 12 cm", 0.2, 0.8); + czlpar.Print(Form("%s_hman.pdf", params.outname.c_str())); + + drawResPar(czlpar, 13001, params.resMMPar, false); + czlpar.cd(6); + lg->Draw(); + AddLabel("IB-OB tracks params differences at R = 12 cm", 0.2, 0.8); + czlpar.Print(Form("%s_hman.pdf", params.outname.c_str())); + + cly.Print(Form("%s_hman.pdf]", params.outname.c_str())); +} + +void CheckResidSpec::endOfStream(EndOfStreamContext& ec) +{ + mDBGOut.reset(); + if (mHManV.size()) { + postProcessHistos(); + } + if (mDraw) { + drawHistos(); + } +} + +void CheckResidSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) +{ + if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { + return; + } + if (matcher == ConcreteDataMatcher("GLO", "MEANVERTEX", 0)) { + LOG(info) << "Imposing new MeanVertex: " << ((const o2::dataformats::MeanVertexObject*)obj)->asString(); + mMeanVtx = *(const o2::dataformats::MeanVertexObject*)obj; + mMeanVertexUpdated = true; + return; + } + if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) { + LOG(info) << "cluster dictionary updated"; + mITSDict = (const o2::itsmft::TopologyDictionary*)obj; + return; + } +} + +DataProcessorSpec getCheckResidSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool drawOnly, bool postProcOnly) +{ + std::vector outputs; + auto dataRequest = std::make_shared(); + if (!drawOnly && !postProcOnly) { + bool useMC = false; + dataRequest->requestTracks(srcTracks, useMC); + dataRequest->requestClusters(srcClusters, useMC); + dataRequest->requestPrimaryVertices(useMC); + dataRequest->inputs.emplace_back("meanvtx", "GLO", "MEANVERTEX", 0, Lifetime::Condition, ccdbParamSpec("GLO/Calib/MeanVertex", {}, 1)); + } + auto ggRequest = drawOnly ? std::make_shared(false, false, false, false, false, o2::base::GRPGeomRequest::None, dataRequest->inputs) : std::make_shared(false, // orbitResetTime + true, // GRPECS=true + true, // GRPLHCIF + true, // GRPMagField + true, // askMatLUT + o2::base::GRPGeomRequest::Aligned, // geometry + dataRequest->inputs, true); + Options opts; + if (!drawOnly) { + opts = Options{ + {"nthreads", VariantType::Int, 1, {"number of threads"}}, + {"no-tree", VariantType::Bool, false, {"do not fill residuals tree"}}, + {"no-hist", VariantType::Bool, false, {"do not fill residuals histograms"}}, + {"draw-report", VariantType::Bool, false, {"fill residuals report"}}, + }; + } + + return DataProcessorSpec{ + "check-resid", + dataRequest->inputs, + outputs, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, drawOnly, postProcOnly)}, + opts}; +} + +} // namespace o2::checkresid diff --git a/Detectors/GlobalTrackingWorkflow/study/src/DumpTracks.cxx b/Detectors/GlobalTrackingWorkflow/study/src/DumpTracks.cxx index d02f1df3903ec..8c7931d599e93 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/DumpTracks.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/DumpTracks.cxx @@ -21,7 +21,7 @@ #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" #include "FT0Reconstruction/InteractionTag.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsBase/GRPGeomHelper.h" #include "GlobalTrackingStudy/TrackingStudy.h" diff --git a/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h b/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h index 416820fc9aebb..b1fe732fbefc4 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h +++ b/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h @@ -49,4 +49,6 @@ #pragma link C++ class o2::checkresid::CheckResidConfig + ; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::checkresid::CheckResidConfig> + ; +#pragma link C++ class o2::HistoManager + ; + #endif diff --git a/Detectors/GlobalTrackingWorkflow/study/src/HistoManager.cxx b/Detectors/GlobalTrackingWorkflow/study/src/HistoManager.cxx new file mode 100644 index 0000000000000..e57a78e4b202d --- /dev/null +++ b/Detectors/GlobalTrackingWorkflow/study/src/HistoManager.cxx @@ -0,0 +1,505 @@ +// Copyright 2019-2026 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. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Framework/Logger.h" +#include "GlobalTrackingStudy/HistoManager.h" + +namespace o2 +{ + +HistoManager::HistoManager(const std::string& dirname, const std::string& fname, bool load, const std::string& prefix) : mDirName(dirname) +{ + setFileName(fname); + if (load && !mDefName.empty()) { + int nh = this->load(fname, dirname); + LOGP(info, "HistoManager::load was requested: got {} histos from {}/{}", nh, fname, dirname); + if (!prefix.empty()) { + addPrefix(prefix); + } + } +} + +HistoManager* HistoManager::createClone(const std::string& prefix) const +{ + auto* hm = static_cast(Clone()); + hm->addPrefix(prefix); + for (int i = 0; i < GetLast() + 1; ++i) { + TObject* obj = hm->UncheckedAt(i); + if (!obj) { + continue; + } + if (auto* histo = dynamic_cast(obj)) { + histo->SetDirectory(nullptr); + } + } + hm->mNHistos = mNHistos; + hm->setFileName(mDefName); + hm->setDirName(mDirName); + return hm; +} + +int HistoManager::addHisto(TH1* histo, int at) +{ + if (!histo) { + return mNHistos; + } + if (at < 0) { + at = mNHistos; + } + AddAtAndExpand(histo, at); + histo->SetDirectory(nullptr); + histo->SetUniqueID(at + 1); + return mNHistos++; +} + +TGraph* HistoManager::getGraph(int id) const +{ + return id <= GetLast() ? dynamic_cast(UncheckedAt(id)) : nullptr; +} + +TH1* HistoManager::getHisto(int id) const +{ + return id <= GetLast() ? dynamic_cast(UncheckedAt(id)) : nullptr; +} + +TH1* HistoManager::getHisto(const std::string& name) const +{ + return dynamic_cast(FindObject(name.c_str())); +} + +TH1F* HistoManager::getHisto1F(int id) const +{ + return dynamic_cast(UncheckedAt(id)); +} + +TH2F* HistoManager::getHisto2F(int id) const +{ + return dynamic_cast(UncheckedAt(id)); +} + +TProfile* HistoManager::getHistoP(int id) const +{ + return dynamic_cast(UncheckedAt(id)); +} + +int HistoManager::addGraph(TGraph* gr, int at) +{ + if (!gr) { + return mNHistos; + } + if (at < 0) { + at = mNHistos; + } + AddAtAndExpand(gr, at); + gr->SetUniqueID(at + 1); + return mNHistos++; +} + +void HistoManager::Compress() +{ + TObjArray::Compress(); + for (int i = 0; i < GetLast() + 1; ++i) { + if (TObject* histo = At(i)) { + histo->SetUniqueID(i + 1); + } + } +} + +void HistoManager::write(TFile* file) +{ + if (!mNHistos) { + return; + } + + bool localFile = kFALSE; + TFile* lfile = nullptr; + const char* dirName = nullptr; + if (file) { + lfile = file; + } else { + auto* tmpF = static_cast(gROOT->GetListOfFiles()->FindObject(mDefName.c_str())); + if (tmpF && tmpF->IsOpen()) { + TString opt = tmpF->GetOption(); + opt.ToLower(); + if (!opt.Contains("read")) { + lfile = tmpF; + tmpF->cd(); + } + } + } + + TString pwd = gDirectory->GetPath(); + if (!lfile) { + std::string originalName = mDefName; + if (mDefName.empty() || mDefName[0] == ' ') { + mDefName = "histoman"; + } + TString rootName = mDefName.c_str(); + if (!rootName.Contains(".root")) { + mDefName += ".root"; + } + lfile = TFile::Open(mDefName.c_str(), "UPDATE"); + mDefName = originalName; + localFile = kTRUE; + } + + lfile->cd(); + dirName = mDirName.c_str(); + if (dirName && dirName[0] && dirName[0] != ' ') { + if (!lfile->Get(dirName)) { + lfile->mkdir(dirName); + } + lfile->cd(dirName); + } + LOGP(info, "Writing histograms to: {}/{}", lfile->GetPath(), dirName); + + for (int i = 0; i < GetLast() + 1; ++i) { + TObject* obj = UncheckedAt(i); + if (!obj) { + continue; + } + auto* histo = dynamic_cast(obj); + TDirectory* dr = nullptr; + if (histo) { + dr = histo->GetDirectory(); + histo->SetDirectory(nullptr); + } + obj->Write(nullptr, TObject::kOverwrite); + if (dr && histo) { + histo->SetDirectory(dr); + } + } + + if (localFile) { + lfile->Close(); + delete lfile; + } + auto* oldDir = static_cast(gROOT->GetListOfFiles()->FindObject(pwd.Data())); + if (oldDir) { + oldDir->cd(); + } +} + +void HistoManager::Clear(Option_t*) +{ + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + TObject* hh = UncheckedAt(i); + if (!hh) { + continue; + } + RemoveAt(i); + --mNHistos; + } +} + +//_______________________________________________________________ +void HistoManager::Delete(Option_t*) +{ + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + TObject* hh = UncheckedAt(i); + if (!hh) { + continue; + } + RemoveAt(i); + delete hh; + } + mNHistos = 0; +} + +void HistoManager::Print(Option_t* option) const +{ + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + TObject* hh = UncheckedAt(i); + if (!hh) { + continue; + } + LOGP(info, "At position #{}", i); + hh->Print(option); + } + LOGP(info, "Total number of defined histograms: %d", mNHistos); + LOGP(info, "Current output path: {}/{}", mDefName, mDirName); +} + +void HistoManager::addPrefix(const std::string& pref) +{ + if (pref.empty()) { + return; + } + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + TObject* hh = UncheckedAt(i); + if (!hh) { + continue; + } + if (hh->InheritsFrom("TNamed")) { + auto name = pref + hh->GetName(); + static_cast(hh)->SetName(name.c_str()); + } + } +} + +void HistoManager::addHistos(const HistoManager* hm, Double_t c1) +{ + if (!hm) { + return; + } + int nent = GetLast() + 1; + int nent1 = hm->GetLast() + 1; + if (nent != nent1) { + Error("addHistos", "HistoManagers have different content: %d vs %d", nent, nent1); + return; + } + for (int i = 0; i < nent; ++i) { + TH1* hh1 = getHisto(i); + TH1* hh2 = hm->getHisto(i); + if (!hh1 || !hh2) { + continue; + } + hh1->Add(hh2, c1); + } +} + +void HistoManager::divideHistos(const HistoManager* hm) +{ + if (!hm) { + return; + } + int nent = GetLast() + 1; + int nent1 = hm->GetLast() + 1; + if (nent != nent1) { + Error("divideHistos", "HistoManagers have different content: %d vs %d", nent, nent1); + return; + } + for (int i = 0; i < nent; ++i) { + TH1* hh1 = getHisto(i); + TH1* hh2 = hm->getHisto(i); + if (!hh1 || !hh2) { + continue; + } + hh1->Divide(hh2); + } +} + +//_______________________________________________________________ +void HistoManager::multiplyHistos(const HistoManager* hm) +{ + if (!hm) { + return; + } + int nent = GetLast() + 1; + int nent1 = hm->GetLast() + 1; + if (nent != nent1) { + Error("multiplyHistos", "HistoManagers have different content: %d vs %d", nent, nent1); + return; + } + for (int i = 0; i < nent; ++i) { + TH1* hh1 = getHisto(i); + TH1* hh2 = hm->getHisto(i); + if (!hh1 || !hh2) { + continue; + } + hh1->Multiply(hh2); + } +} + +void HistoManager::scaleHistos(Double_t c1) +{ + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + TH1* hh1 = getHisto(i); + if (hh1) { + hh1->Scale(c1); + } + } +} + +void HistoManager::sumw2() +{ + int nent = GetLast() + 1; + for (int i = 0; i < nent; ++i) { + auto* hh1 = dynamic_cast(UncheckedAt(i)); + if (hh1) { + hh1->Sumw2(); + } + } +} + +void HistoManager::setFile(TFile* file) +{ + if (file) { + mDefName = file->GetName(); + } +} + +void HistoManager::delHisto(int at) +{ + TH1* hist = getHisto(at); + if (hist) { + RemoveAt(at); + delete hist; + --mNHistos; + } +} + +void HistoManager::purify(bool emptyToo) +{ + int last = GetLast() + 1; + if (emptyToo) { + for (int i = 0; i < last; ++i) { + TH1* hist = getHisto(i); + if (!hist) { + continue; + } + if (hist->GetEntries() < 1) { + delHisto(i); + } + } + } + Compress(); +} + +void HistoManager::setFileName(const std::string& name) +{ + mDefName = gSystem->ExpandPathName(name.c_str()); +} + +void HistoManager::reset() +{ + int last = GetLast() + 1; + for (int i = 0; i < last; ++i) { + TH1* hist = getHisto(i); + if (!hist) { + continue; + } + hist->Reset(); + } +} + +int HistoManager::load(const std::string& fname, const std::string& dirname) +{ + TFile* file = TFile::Open(gSystem->ExpandPathName(fname.c_str())); + if (!file) { + LOGP(error, "No file {}", fname); + return 0; + } + if (!dirname.empty() && dirname[0] != ' ') { + if (!file->Get(dirname.c_str())) { + LOGP(error, "No {} directory in file {}", dirname, fname); + file->Close(); + delete file; + return 0; + } + file->cd(dirname.c_str()); + } + int count = 0; + TList* lst = gDirectory->GetListOfKeys(); + TIter nextKey(lst); + TKey* key = nullptr; + while ((key = static_cast(nextKey()))) { + if (FindObject(key->GetName())) { + continue; + } + TString clName = key->GetClassName(); + if (!(clName.BeginsWith("TH") || clName.BeginsWith("TProfile") || clName.BeginsWith("TGraph"))) { + printf("Object %s of type %s is not processed\n", key->GetName(), clName.Data()); + continue; + } + TObject* hst = key->ReadObj(); + int id = hst->GetUniqueID(); + if (auto* h = dynamic_cast(hst)) { + addHisto(h, id - 1); + ++count; + continue; + } + if (auto* gr = dynamic_cast(hst)) { + addGraph(gr, id - 1); + ++count; + } + } + file->Close(); + delete file; + auto nm = fname; + if (!dirname.empty()) { + nm += fmt::format("/{}", dirname); + } + SetName(nm.c_str()); + return count; +} + +void HistoManager::setColor(int tcolor) +{ + int last = GetLast() + 1; + for (int i = 0; i < last; ++i) { + TH1* hist = getHisto(i); + if (!hist) { + continue; + } + hist->SetLineColor(tcolor); + hist->SetMarkerColor(tcolor); + TList* lst = hist->GetListOfFunctions(); + if (lst) { + int nf = lst->GetSize(); + for (int j = 0; j < nf; ++j) { + TObject* fnc = lst->At(j); + if (fnc->InheritsFrom("TF1")) { + static_cast(fnc)->SetLineColor(tcolor); + static_cast(fnc)->SetLineWidth(1); + static_cast(fnc)->ResetBit(TF1::kNotDraw); + } else if (fnc->InheritsFrom("TPaveStats")) { + static_cast(fnc)->SetTextColor(tcolor); + } + } + } + } +} + +void HistoManager::setMarkerStyle(Style_t mstyle, Size_t msize) +{ + int last = GetLast() + 1; + for (int i = 0; i < last; ++i) { + TH1* hist = getHisto(i); + if (!hist) { + continue; + } + hist->SetMarkerStyle(mstyle); + hist->SetMarkerSize(msize); + } +} + +void HistoManager::setMarkerSize(Size_t msize) +{ + int last = GetLast() + 1; + for (int i = 0; i < last; ++i) { + TH1* hist = getHisto(i); + if (!hist) { + continue; + } + hist->SetMarkerSize(msize); + } +} + +} // namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/study/src/SVStudy.cxx b/Detectors/GlobalTrackingWorkflow/study/src/SVStudy.cxx index 0129d19b02346..4d0b6bdbdb213 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/SVStudy.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/SVStudy.cxx @@ -29,7 +29,7 @@ #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" #include "FT0Reconstruction/InteractionTag.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsBase/GRPGeomHelper.h" #include "GlobalTrackingStudy/TrackingStudy.h" diff --git a/Detectors/GlobalTrackingWorkflow/study/src/TPCTrackStudy.cxx b/Detectors/GlobalTrackingWorkflow/study/src/TPCTrackStudy.cxx index ee475acbbcf70..f9ba67319e18a 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/TPCTrackStudy.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/TPCTrackStudy.cxx @@ -14,7 +14,7 @@ #include "DataFormatsGlobalTracking/RecoContainer.h" #include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCFastTransformPOD.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DetectorsBase/Propagator.h" #include "DetectorsBase/GeometryManager.h" @@ -50,13 +50,8 @@ using timeEst = o2::dataformats::TimeStampWithError; class TPCTrackStudySpec final : public Task { public: - TPCTrackStudySpec(std::shared_ptr dr, std::shared_ptr gr, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, GTrackID::mask_t src, bool useMC) - : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC) - { - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - } + TPCTrackStudySpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool useMC) + : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC) {} ~TPCTrackStudySpec() final = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -69,7 +64,7 @@ class TPCTrackStudySpec final : public Task std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps{nullptr}; bool mUseMC{false}; ///< MC flag bool mUseGPUModel{false}; float mXRef = 0.; @@ -108,7 +103,6 @@ void TPCTrackStudySpec::init(InitContext& ic) if (mXRef < 0.) { mXRef = 0.; } - mTPCCorrMapsLoader.init(ic); mDBGOut = std::make_unique("tpc-trackStudy.root", "recreate"); if (ic.options().get("dump-clusters")) { mDBGOutCl = std::make_unique("tpc-trackStudy-cl.root", "recreate"); @@ -139,29 +133,13 @@ void TPCTrackStudySpec::updateTimeDependentParams(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); + auto const& raw = pc.inputs().get("corrMap"); + mTPCCorrMaps = &o2::gpu::TPCFastTransformPOD::get(raw); static bool initOnceDone = false; if (!initOnceDone) { // this params need to be queried only once initOnceDone = true; // none at the moment } - // we may have other params which need to be queried regularly - bool updateMaps = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - if (mTPCVDriftHelper.isUpdated()) { - LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", - mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, - mTPCVDriftHelper.getVDriftObject().timeOffsetCorr, mTPCVDriftHelper.getVDriftObject().refTimeOffset, - mTPCVDriftHelper.getSourceName()); - mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); - } } void TPCTrackStudySpec::process(o2::globaltracking::RecoContainer& recoData) @@ -187,7 +165,7 @@ void TPCTrackStudySpec::process(o2::globaltracking::RecoContainer& recoData) } if (mTPCTracksArray.size()) { LOGP(info, "Found {} TPC tracks", mTPCTracksArray.size()); - mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, &mTPCCorrMapsLoader, prop->getNominalBz(), mTPCTrackClusIdx.data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size(), nullptr, o2::base::Propagator::Instance()); + mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, mTPCCorrMaps, prop->getNominalBz(), mTPCTrackClusIdx.data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size(), nullptr, o2::base::Propagator::Instance()); } float vdriftTB = mTPCVDriftHelper.getVDriftObject().getVDrift() * o2::tpc::ParameterElectronics::Instance().ZbinWidth; // VDrift expressed in cm/TimeBin float tpcTBBias = mTPCVDriftHelper.getVDriftObject().getTimeOffset() / (8 * o2::constants::lhc::LHCBunchSpacingMUS); @@ -196,7 +174,7 @@ void TPCTrackStudySpec::process(o2::globaltracking::RecoContainer& recoData) auto dumpClusters = [this] { static int tf = 0; - const auto* corrMap = this->mTPCCorrMapsLoader.getCorrMap(); + const auto* corrMap = this->mTPCCorrMaps; for (int sector = 0; sector < 36; sector++) { float alp = ((sector % 18) * 20 + 10) * TMath::DegToRad(); float sn = TMath::Sin(alp), cs = TMath::Cos(alp); @@ -273,7 +251,7 @@ void TPCTrackStudySpec::process(o2::globaltracking::RecoContainer& recoData) clSector.push_back(sector); clRow.push_back(row); float x, y, z; - mTPCCorrMapsLoader.Transform(sector, row, cl->getPad(), cl->getTime(), x, y, z, t); // nominal time of the track + mTPCCorrMaps->Transform(sector, row, cl->getPad(), cl->getTime(), x, y, z, t); // nominal time of the track clX.push_back(x); clY.push_back(y); clZ.push_back(z); @@ -415,12 +393,9 @@ void TPCTrackStudySpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } } -DataProcessorSpec getTPCTrackStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts) +DataProcessorSpec getTPCTrackStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC) { std::vector outputs; Options opts{ @@ -445,13 +420,13 @@ DataProcessorSpec getTPCTrackStudySpec(GTrackID::mask_t srcTracks, GTrackID::mas dataRequest->inputs, true); o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); return DataProcessorSpec{ "tpc-track-study", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, sclOpts, srcTracks, useMC)}, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, useMC)}, opts}; } diff --git a/Detectors/GlobalTrackingWorkflow/study/src/TrackMCStudy.cxx b/Detectors/GlobalTrackingWorkflow/study/src/TrackMCStudy.cxx index 8f6604b029605..9637c72589196 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/TrackMCStudy.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/TrackMCStudy.cxx @@ -17,7 +17,6 @@ #include "ReconstructionDataFormats/TrackTPCITS.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" #include "ITSMFTReconstruction/ChipMappingITS.h" #include "ITStracking/IOUtils.h" #include "DetectorsBase/Propagator.h" @@ -34,7 +33,7 @@ #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" #include "FT0Reconstruction/InteractionTag.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsBase/GRPGeomHelper.h" #include "GlobalTrackingStudy/TrackMCStudy.h" @@ -57,6 +56,7 @@ #include "GPUParam.h" #include "GPUParam.inc" #include "MathUtils/fit.h" +#include "TPCFastTransformPOD.h" #include #include #include @@ -86,13 +86,8 @@ using timeEst = o2::dataformats::TimeStampWithError; class TrackMCStudy final : public Task { public: - TrackMCStudy(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, bool checkSV) - : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mCheckSV(checkSV) - { - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - } + TrackMCStudy(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool checkSV) + : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mCheckSV(checkSV) {} ~TrackMCStudy() final = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -117,21 +112,21 @@ class TrackMCStudy final : public Task const std::vector* mCurrMCTracks = nullptr; TVector3 mCurrMCVertex; o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps{nullptr}; std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; std::unique_ptr mDBGOut; - std::vector mTBinClOcc; ///< TPC occupancy histo: i-th entry is the integrated occupancy for ~1 orbit starting from the TB = i*mNTPCOccBinLength - std::vector mTBinClOccHist; //< original occupancy - 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 mTBinClOcc; ///< TPC occupancy histo: i-th entry is the integrated occupancy for ~1 orbit starting from the TB = i*mNTPCOccBinLength + std::vector mTBinClOccHist; //< original occupancy + 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 const o2::itsmft::TopologyDictionary* mITSDict = nullptr; ///< cluster patterns dictionary - bool mCheckSV = false; //< check SV binding (apart from prongs availability) - bool mRecProcStage = false; //< flag that the MC particle was added only at the stage of reco tracks processing - int mNTPCOccBinLength = 0; ///< TPC occ. histo bin length in TBs + bool mCheckSV = false; //< check SV binding (apart from prongs availability) + bool mRecProcStage = false; //< flag that the MC particle was added only at the stage of reco tracks processing + int mNTPCOccBinLength = 0; ///< TPC occ. histo bin length in TBs float mNTPCOccBinLengthInv = -1.f; int mVerbose = 0; float mITSTimeBiasMUS = 0.f; @@ -179,7 +174,6 @@ void TrackMCStudy::init(InitContext& ic) mNCheckDecays++; } mDecaysMaps.resize(mNCheckDecays); - mTPCCorrMapsLoader.init(ic); } void TrackMCStudy::run(ProcessingContext& pc) @@ -202,23 +196,8 @@ void TrackMCStudy::updateTimeDependentParams(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); - bool updateMaps = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - if (mTPCVDriftHelper.isUpdated()) { - LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", - mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, - mTPCVDriftHelper.getVDriftObject().timeOffsetCorr, mTPCVDriftHelper.getVDriftObject().refTimeOffset, - mTPCVDriftHelper.getSourceName()); - mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); - } + auto const& raw = pc.inputs().get("corrMap"); + mTPCCorrMaps = &o2::gpu::TPCFastTransformPOD::get(raw); static bool initOnceDone = false; if (!initOnceDone) { // this params need to be queried only once initOnceDone = true; @@ -260,8 +239,8 @@ void TrackMCStudy::process(const o2::globaltracking::RecoContainer& recoData) auto vtxRefs = recoData.getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs auto prop = o2::base::Propagator::Instance(); int nv = vtxRefs.size(); - float vdriftTB = mTPCVDriftHelper.getVDriftObject().getVDrift() * o2::tpc::ParameterElectronics::Instance().ZbinWidth; // VDrift expressed in cm/TimeBin - float itsBias = 0.5 * mITSROFrameLengthMUS + o2::itsmft::DPLAlpideParam::Instance().roFrameBiasInBC * o2::constants::lhc::LHCBunchSpacingMUS; // ITS time is supplied in \mus as beginning of ROF + float vdriftTB = mTPCVDriftHelper.getVDriftObject().getVDrift() * o2::tpc::ParameterElectronics::Instance().ZbinWidth; // VDrift expressed in cm/TimeBin + float itsBias = 0.5 * mITSROFrameLengthMUS + o2::itsmft::DPLAlpideParam::Instance().roFrameBiasInBC * o2::constants::lhc::LHCBunchSpacingMUS; // ITS time is supplied in \mus as beginning of ROF prepareITSData(recoData); loadTPCOccMap(recoData); @@ -871,7 +850,7 @@ void TrackMCStudy::fillMCClusterInfo(const o2::globaltracking::RecoContainer& re continue; } float xc, yc, zc; - mTPCCorrMapsLoader.Transform(sector, row, clus.getPad(), clus.getTime(), xc, yc, zc, mctr.bcInTF / 8.); // nominal time of the track + mTPCCorrMaps->Transform(sector, row, clus.getPad(), clus.getTime(), xc, yc, zc, mctr.bcInTF / 8.); // nominal time of the track const auto& entTRefIDs = entTRefIDsIt->second; // find bracketing TRef params @@ -1023,9 +1002,6 @@ void TrackMCStudy::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } if (matcher == ConcreteDataMatcher("ITS", "ALPIDEPARAM", 0)) { LOG(info) << "ITS Alpide param updated"; const auto& par = o2::itsmft::DPLAlpideParam::Instance(); @@ -1086,7 +1062,7 @@ bool TrackMCStudy::processMCParticle(int src, int ev, int trid) break; } } - if (decay >= 0) { // check if decay and kinematics is acceptable + if (decay >= 0) { // check if decay and kinematics is acceptable auto& decayPool = mDecaysMaps[decay]; int idd0 = mcPart.getFirstDaughterTrackId(), idd1 = mcPart.getLastDaughterTrackId(); // we want only charged and trackable daughters int dtStart = mDecProdLblPool.size(), dtEnd = -1; @@ -1264,7 +1240,7 @@ void TrackMCStudy::loadTPCOccMap(const o2::globaltracking::RecoContainer& recoDa auto NHBPerTF = o2::base::GRPGeomHelper::instance().getGRPECS()->getNHBFPerTF(); const auto& TPCOccMap = recoData.occupancyMapTPC; auto prop = o2::base::Propagator::Instance(); - auto TPCRefitter = std::make_unique(&recoData.inputsTPCclusters->clusterIndex, &mTPCCorrMapsLoader, prop->getNominalBz(), + auto TPCRefitter = std::make_unique(&recoData.inputsTPCclusters->clusterIndex, mTPCCorrMaps, prop->getNominalBz(), recoData.getTPCTracksClusterRefs().data(), 0, recoData.clusterShMapTPC.data(), TPCOccMap.data(), TPCOccMap.size(), nullptr, prop); mNTPCOccBinLength = TPCRefitter->getParam()->rec.tpc.occupancyMapTimeBins; mTBinClOcc.clear(); @@ -1371,7 +1347,7 @@ void TrackMCStudy::processITSTracks(const o2::globaltracking::RecoContainer& rec } } -DataProcessorSpec getTrackMCStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, bool checkSV) +DataProcessorSpec getTrackMCStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool checkSV) { std::vector outputs; Options opts{ @@ -1390,7 +1366,7 @@ DataProcessorSpec getTrackMCStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask dataRequest->requestSecondaryVertices(useMC); } o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); auto ggRequest = std::make_shared(false, // orbitResetTime true, // GRPECS=true true, // GRPLHCIF @@ -1404,7 +1380,7 @@ DataProcessorSpec getTrackMCStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask "track-mc-study", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, sclOpts, checkSV)}, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, checkSV)}, opts}; } diff --git a/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx b/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx index c68e60059cd3f..881ce9041ae04 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx @@ -28,7 +28,7 @@ #include "Framework/CCDBParamSpec.h" #include "Framework/DeviceSpec.h" #include "FT0Reconstruction/InteractionTag.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsBase/GRPGeomHelper.h" #include "GlobalTrackingStudy/TrackingStudy.h" @@ -42,7 +42,6 @@ #include "ReconstructionDataFormats/VtxTrackRef.h" #include "ReconstructionDataFormats/DCA.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" #include "GPUO2InterfaceRefit.h" #include "GPUO2ExternalUser.h" // Needed for propper settings in GPUParam.h #include "GPUParam.h" @@ -50,6 +49,7 @@ #include "GPUTPCGeometry.h" #include "Steer/MCKinematicsReader.h" #include "MathUtils/fit.h" +#include "TPCFastTransformPOD.h" #include namespace o2::trackstudy @@ -70,13 +70,8 @@ using timeEst = o2::dataformats::TimeStampWithError; class TrackingStudySpec final : public Task { public: - TrackingStudySpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool useMC, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts) - : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC) - { - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - } + TrackingStudySpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool useMC) + : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC) {} ~TrackingStudySpec() final = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -91,11 +86,11 @@ class TrackingStudySpec final : public Task std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps{nullptr}; bool mUseMC{false}; ///< MC flag std::unique_ptr mDBGOut; std::unique_ptr mDBGOutVtx; - std::unique_ptr mTPCRefitter; ///< TPC refitter used for TPC tracks refit during the reconstruction + std::unique_ptr mTPCRefitter; ///< TPC refitter used for TPC tracks refit during the reconstruction std::vector mMltHistTB, mTBinClOccAft, mTBinClOccBef, mTBinClOccWgh; ///< TPC occupancy histo: i-th entry is the integrated occupancy for ~1 orbit starting/preceding from the TB = i*mNTPCOccBinLength std::unique_ptr mOccWghFun; float mITSROFrameLengthMUS = 0.f; @@ -125,7 +120,6 @@ class TrackingStudySpec final : public Task void TrackingStudySpec::init(InitContext& ic) { o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); - mTPCCorrMapsLoader.init(ic); int lane = ic.services().get().inputTimesliceId; int maxLanes = ic.services().get().maxInputTimeslices; std::string dbgnm = maxLanes == 1 ? "trackStudy.root" : fmt::format("trackStudy_{}.root", lane); @@ -160,7 +154,7 @@ void TrackingStudySpec::run(ProcessingContext& pc) recoData.collectData(pc, *mDataRequest.get()); // select tracks of needed type, with minimal cuts, the real selected will be done in the vertexer updateTimeDependentParams(pc); // Make sure this is called after recoData.collectData, which may load some conditions if (recoData.inputsTPCclusters) { - mTPCRefitter = std::make_unique(&recoData.inputsTPCclusters->clusterIndex, &mTPCCorrMapsLoader, o2::base::Propagator::Instance()->getNominalBz(), + mTPCRefitter = std::make_unique(&recoData.inputsTPCclusters->clusterIndex, mTPCCorrMaps, o2::base::Propagator::Instance()->getNominalBz(), recoData.getTPCTracksClusterRefs().data(), 0, recoData.clusterShMapTPC.data(), recoData.occupancyMapTPC.data(), recoData.occupancyMapTPC.size(), nullptr, o2::base::Propagator::Instance()); mNTPCOccBinLength = mTPCRefitter->getParam()->rec.tpc.occupancyMapTimeBins; @@ -209,7 +203,8 @@ void TrackingStudySpec::updateTimeDependentParams(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); + auto const& raw = pc.inputs().get("corrMap"); + mTPCCorrMaps = &o2::gpu::TPCFastTransformPOD::get(raw); static bool initOnceDone = false; if (!initOnceDone) { // this params need to be queried only once initOnceDone = true; @@ -227,22 +222,6 @@ void TrackingStudySpec::updateTimeDependentParams(ProcessingContext& pc) mTPCTBinMUS = elParam.ZbinWidth; // TPC bin in microseconds mTPCTBinMUSInv = 1. / mTPCTBinMUS; } - bool updateMaps = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - if (mTPCVDriftHelper.isUpdated()) { - LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", - mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, - mTPCVDriftHelper.getVDriftObject().timeOffsetCorr, mTPCVDriftHelper.getVDriftObject().refTimeOffset, - mTPCVDriftHelper.getSourceName()); - mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); - } } void TrackingStudySpec::process(o2::globaltracking::RecoContainer& recoData) @@ -296,9 +275,9 @@ void TrackingStudySpec::process(o2::globaltracking::RecoContainer& recoData) if (trExt.padFromEdge > npads / 2) { trExt.padFromEdge = npads - 1 - trExt.padFromEdge; } - this->mTPCCorrMapsLoader.Transform(clSect, clRow, clus.getPad(), clus.getTime(), trExt.innerTPCPos0[0], trExt.innerTPCPos0[1], trExt.innerTPCPos0[2], trc.getTime0()); // nominal time of the track + this->mTPCCorrMaps->Transform(clSect, clRow, clus.getPad(), clus.getTime(), trExt.innerTPCPos0[0], trExt.innerTPCPos0[1], trExt.innerTPCPos0[2], trc.getTime0()); // nominal time of the track if (timestampTB > -1e8) { - this->mTPCCorrMapsLoader.Transform(clSect, clRow, clus.getPad(), clus.getTime(), trExt.innerTPCPos[0], trExt.innerTPCPos[1], trExt.innerTPCPos[2], timestampTB); // time assigned from the global track track + this->mTPCCorrMaps->Transform(clSect, clRow, clus.getPad(), clus.getTime(), trExt.innerTPCPos[0], trExt.innerTPCPos[1], trExt.innerTPCPos[2], timestampTB); // time assigned from the global track track } else { trExt.innerTPCPos = trExt.innerTPCPos0; } @@ -724,9 +703,6 @@ void TrackingStudySpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } if (matcher == ConcreteDataMatcher("GLO", "MEANVERTEX", 0)) { LOG(info) << "Imposing new MeanVertex: " << ((const o2::dataformats::MeanVertexObject*)obj)->asString(); mMeanVtx = *(const o2::dataformats::MeanVertexObject*)obj; @@ -746,7 +722,7 @@ float TrackingStudySpec::getDCAZCut(float pt) const return fun.Eval(pt); } -DataProcessorSpec getTrackingStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts) +DataProcessorSpec getTrackingStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC) { std::vector outputs; auto dataRequest = std::make_shared(); @@ -781,13 +757,13 @@ DataProcessorSpec getTrackingStudySpec(GTrackID::mask_t srcTracks, GTrackID::mas {"min-x-prop", VariantType::Float, 100.f, {"track should be propagated to this X at least"}}, }; o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); return DataProcessorSpec{ "track-study", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, useMC, sclOpts)}, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, useMC)}, opts}; } diff --git a/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx b/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx index b8230b59405d8..0791d72474ad3 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.cxx @@ -9,7 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "GlobalTrackingStudy/CheckResid.h" +#include "GlobalTrackingStudy/CheckResidSpec.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DetectorsCommonDataFormats/DetID.h" #include "CommonUtils/ConfigurableParam.h" @@ -20,8 +20,7 @@ #include "DetectorsBase/DPLWorkflowUtils.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "DetectorsRaw/HBFUtilsInitializer.h" -#include "TPCCalibration/CorrectionMapsLoader.h" -#include "TPCWorkflow/TPCScalerSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -38,12 +37,14 @@ void customize(std::vector& workflowOptions) { // option allowing to set parameters std::vector options{ - {"enable-mc", o2::framework::VariantType::Bool, false, {"enable MC propagation"}}, + {"draw-external-only", VariantType::Bool, false, {"just draw content of comma-separated list of histomanagers from checkresid.ext_hm_list"}}, + {"postproc-external-only", VariantType::Bool, false, {"just post-process raw content of comma-separated list of histomanagers from checkresid.ext_hm_list"}}, {"track-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of track sources to use"}}, {"cluster-sources", VariantType::String, "ITS", {"comma-separated list of cluster sources to use"}}, {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; - // o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -56,23 +57,30 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { WorkflowSpec specs; + bool drawOnly = configcontext.options().get("draw-external-only"); + bool postProcOnly = configcontext.options().get("postproc-external-only"); 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"); // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); - // auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); - auto useMC = configcontext.options().get("enable-mc"); GID::mask_t srcTrc = allowedSourcesTrc & GID::getSourcesMask(configcontext.options().get("track-sources")); GID::mask_t srcCls = allowedSourcesClus & GID::getSourcesMask(configcontext.options().get("cluster-sources")); - o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC); - o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, useMC); // P-vertex is always needed - specs.emplace_back(o2::checkresid::getCheckResidSpec(srcTrc, srcCls, useMC)); + if (!drawOnly) { + o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, false); + o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, false); // P-vertex is always needed + } else { + allowedSourcesTrc = {}; + allowedSourcesClus = {}; + } + specs.emplace_back(o2::checkresid::getCheckResidSpec(srcTrc, srcCls, drawOnly, postProcOnly)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit - o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); + if (!drawOnly) { + o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); + } return std::move(specs); } diff --git a/Detectors/GlobalTrackingWorkflow/study/src/its-offset-study-workflow.cxx b/Detectors/GlobalTrackingWorkflow/study/src/its-offset-study-workflow.cxx index 9638312e13f78..5d89ba9b7eabf 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/its-offset-study-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/its-offset-study-workflow.cxx @@ -20,6 +20,7 @@ #include "DetectorsBase/DPLWorkflowUtils.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "DetectorsRaw/HBFUtilsInitializer.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -39,6 +40,7 @@ void customize(std::vector& workflowOptions) {"track-sources", VariantType::String, std::string{"ITS,ITS-TPC-TRD-TOF,ITS-TPC-TOF,ITS-TPC,ITS-TPC-TRD"}, {"comma-separated list of track sources to use"}}, {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options, "o2_tfidinfo.root"); std::swap(workflowOptions, options); } diff --git a/Detectors/GlobalTrackingWorkflow/study/src/tpc-track-study-workflow.cxx b/Detectors/GlobalTrackingWorkflow/study/src/tpc-track-study-workflow.cxx index 3e92178c81b7d..e255295d7665f 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/tpc-track-study-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/tpc-track-study-workflow.cxx @@ -20,7 +20,7 @@ #include "DetectorsBase/DPLWorkflowUtils.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "DetectorsRaw/HBFUtilsInitializer.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCCalibration/CorrectionMapsOptions.h" #include "TPCWorkflow/TPCScalerSpec.h" using namespace o2::framework; @@ -43,7 +43,7 @@ void customize(std::vector& workflowOptions) {"cluster-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of cluster sources to use"}}, {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -62,7 +62,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); auto useMC = !configcontext.options().get("disable-mc"); - auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); GID::mask_t srcTrc = allowedSourcesTrc & GID::getSourcesMask(configcontext.options().get("track-sources")); GID::mask_t srcCls = allowedSourcesClus & GID::getSourcesMask(configcontext.options().get("cluster-sources")); if (sclOpt.requestCTPLumi) { @@ -71,10 +71,10 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) } o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC); o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, useMC); // P-vertex is always needed - if (sclOpt.needTPCScalersWorkflow() && !configcontext.options().get("disable-root-input")) { - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == 2, sclOpt.enableMShapeCorrection)); + if (!configcontext.options().get("disable-root-input")) { + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpt.enableMShapeCorrection, sclOpt)); } - specs.emplace_back(o2::trackstudy::getTPCTrackStudySpec(srcTrc, srcCls, useMC, sclOpt)); + specs.emplace_back(o2::trackstudy::getTPCTrackStudySpec(srcTrc, srcCls, useMC)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); diff --git a/Detectors/GlobalTrackingWorkflow/study/src/trackMCStudy-workflow.cxx b/Detectors/GlobalTrackingWorkflow/study/src/trackMCStudy-workflow.cxx index 7aa53e2190a9e..50cc768bdc98d 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/trackMCStudy-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/trackMCStudy-workflow.cxx @@ -19,9 +19,10 @@ #include "Framework/CallbacksPolicy.h" #include "DetectorsBase/DPLWorkflowUtils.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCCalibration/CorrectionMapsOptions.h" #include "TPCWorkflow/TPCScalerSpec.h" #include "DetectorsRaw/HBFUtilsInitializer.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -44,7 +45,8 @@ void customize(std::vector& workflowOptions) {"ignore-sv-check", VariantType::Bool, false, {"disable check for SV combinatorics"}}, {"disable-mc", o2::framework::VariantType::Bool, false, {"disable MC propagation, never use it"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -61,7 +63,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (!useMC) { throw std::runtime_error("MC cannot be disabled for this workflow"); } - auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); GID::mask_t allowedSourcesTrc = GID::getSourcesMask("ITS,TPC,ITS-TPC,TPC-TOF,TPC-TRD,ITS-TPC-TRD,TPC-TRD-TOF,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); GID::mask_t allowedSourcesClus = GID::getSourcesMask("ITS,TPC"); @@ -80,11 +82,11 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (checkSV) { o2::globaltracking::InputHelper::addInputSpecsSVertex(configcontext, specs); } - if (sclOpt.needTPCScalersWorkflow() && !configcontext.options().get("disable-root-input")) { - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == 2, sclOpt.enableMShapeCorrection)); + if (!configcontext.options().get("disable-root-input")) { + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpt.enableMShapeCorrection, sclOpt)); } - specs.emplace_back(o2::trackstudy::getTrackMCStudySpec(srcTrc, srcCls, sclOpt, checkSV)); + specs.emplace_back(o2::trackstudy::getTrackMCStudySpec(srcTrc, srcCls, checkSV)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); diff --git a/Detectors/GlobalTrackingWorkflow/study/src/tracking-study-workflow.cxx b/Detectors/GlobalTrackingWorkflow/study/src/tracking-study-workflow.cxx index 413d2e63653fc..fa69d9f2808e0 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/tracking-study-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/tracking-study-workflow.cxx @@ -20,8 +20,9 @@ #include "DetectorsBase/DPLWorkflowUtils.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "DetectorsRaw/HBFUtilsInitializer.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCCalibration/CorrectionMapsOptions.h" #include "TPCWorkflow/TPCScalerSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -43,7 +44,8 @@ void customize(std::vector& workflowOptions) {"cluster-sources", VariantType::String, "TPC,TOF", {"comma-separated list of cluster sources to use"}}, {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -61,7 +63,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); - auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); auto useMC = !configcontext.options().get("disable-mc"); GID::mask_t srcTrc = allowedSourcesTrc & GID::getSourcesMask(configcontext.options().get("track-sources")); @@ -69,13 +71,13 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (sclOpt.requestCTPLumi) { srcCls = srcCls | GID::getSourcesMask("CTP"); } - if (sclOpt.needTPCScalersWorkflow() && !configcontext.options().get("disable-root-input")) { - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == 2, sclOpt.enableMShapeCorrection)); + if (!configcontext.options().get("disable-root-input")) { + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpt.enableMShapeCorrection, sclOpt)); } o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC); o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, useMC); // P-vertex is always needed - specs.emplace_back(o2::trackstudy::getTrackingStudySpec(srcTrc, srcCls, useMC, sclOpt)); + specs.emplace_back(o2::trackstudy::getTrackingStudySpec(srcTrc, srcCls, useMC)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx index 4912a1df36a33..4f17aec3d49d5 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx @@ -206,7 +206,7 @@ DataProcessorSpec getTPCInterpolationSpec(GTrackID::mask_t srcCls, GTrackID::mas AlgorithmSpec{adaptFromTask(dataRequest, srcTrk, srcTrkMap, ggRequest, useMC, processITSTPConly, sendTrackData, debugOutput, extDetResid)}, Options{ {"matCorrType", VariantType::Int, 2, {"material correction type (definition in Propagator.h)"}}, - {"sec-per-slot", VariantType::UInt32, 600u, {"number of seconds per calibration time slot (put 0 for infinite slot length)"}}, + {"sec-per-slot", VariantType::UInt32, 300u, {"number of seconds per calibration time slot (put 0 for infinite slot length)"}}, {"process-seeds", VariantType::Bool, false, {"do not remove duplicates, e.g. for ITS-TPC-TRD track also process its seeding ITS-TPC part"}}}}; } diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/tpc-interpolation-workflow.cxx b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/tpc-interpolation-workflow.cxx index 2f28fc5bb2d34..65a79a8635a49 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/tpc-interpolation-workflow.cxx +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/tpc-interpolation-workflow.cxx @@ -19,6 +19,7 @@ #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "TPCInterpolationWorkflow/TPCInterpolationSpec.h" #include "TPCInterpolationWorkflow/TPCResidualWriterSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -44,6 +45,7 @@ void customize(std::vector& workflowOptions) {"debug-output", VariantType::Bool, false, {"Dump extended tracking information for debugging"}}, {"skip-ext-det-residuals", VariantType::Bool, false, {"Do not produce residuals for external detectors"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } diff --git a/Detectors/ITSMFT/ITS/macros/test/CMakeLists.txt b/Detectors/ITSMFT/ITS/macros/test/CMakeLists.txt index dd6aacf65db99..ffdbdf1990a32 100644 --- a/Detectors/ITSMFT/ITS/macros/test/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/macros/test/CMakeLists.txt @@ -118,3 +118,19 @@ o2_add_test_root_macro(CheckDROF.C PUBLIC_LINK_LIBRARIES O2::DataFormatsITS O2::DataFormatsITSMFT LABELS its) + +o2_add_test_root_macro(CheckStaggering.C + PUBLIC_LINK_LIBRARIES O2::DataFormatsITS + O2::DataFormatsITSMFT + O2::ITSMFTReconstruction + O2::DCAFitter + O2::CCDB + O2::DetectorsVertexing + O2::ReconstructionDataFormats + LABELS its COMPILE_ONLY) + +o2_add_test_root_macro(CheckSeeding.C + PUBLIC_LINK_LIBRARIES O2::DataFormatsITS + O2::SimulationDataFormat + O2::Steer + LABELS its COMPILE_ONLY) diff --git a/Detectors/ITSMFT/ITS/macros/test/CheckDROF.C b/Detectors/ITSMFT/ITS/macros/test/CheckDROF.C index 21428ea4fcbc2..01acd2935981d 100644 --- a/Detectors/ITSMFT/ITS/macros/test/CheckDROF.C +++ b/Detectors/ITSMFT/ITS/macros/test/CheckDROF.C @@ -39,13 +39,13 @@ #include "SimulationDataFormat/MCTruthContainer.h" #include "DataFormatsITSMFT/CompCluster.h" #include "DataFormatsITS/TrackITS.h" +#include "DataFormatsITS/Vertex.h" #include "DataFormatsITSMFT/ROFRecord.h" #include "SimulationDataFormat/DigitizationContext.h" #endif using namespace std; -using Vertex = o2::dataformats::Vertex>; void plotHistos(TFile* fWO, TFile* f, const char* append = ""); @@ -93,9 +93,9 @@ struct ParticleInfo { // particle level information for tracks #pragma link C++ class ParticleInfo + ; #pragma link C++ class std::vector < ParticleInfo> + ; -struct VertexInfo { // Vertex level info - float purity; // fraction of main cont. labels to all - Vertex vertex; // reconstructed vertex +struct VertexInfo { // Vertex level info + float purity; // fraction of main cont. labels to all + o2::its::Vertex vertex; // reconstructed vertex int bcInROF{-1}; int rofId{-1}; int event{-1}; // corresponding MC event @@ -199,7 +199,7 @@ void CheckDROF(bool plot = false, bool write = false, const std::string& tracfil std::vector rofRecVec, *rofRecVecP = &rofRecVec; recTree->SetBranchAddress("ITSTracksROF", &rofRecVecP); // Vertices - std::vector* recVerArr = nullptr; + std::vector* recVerArr = nullptr; recTree->SetBranchAddress("Vertices", &recVerArr); std::vector* recVerROFArr = nullptr; recTree->SetBranchAddress("VerticesROF", &recVerROFArr); @@ -876,11 +876,12 @@ void CheckDROF(bool plot = false, bool write = false, const std::string& tracfil if (!trk.hasHitOnLayer(iL) || !trk.isFakeOnLayer(iL) || (part.clusters & (0x1 << iL)) == 0) { continue; } - if (trk.hasHitInNextROF()) { - hFakMig->Fill(trk.getPt(), trk.getNClusters(), iL); - } else { - hFakVal->Fill(trk.getPt(), trk.getNClusters(), iL); - } + // TODO: figure out how to find hit migration + // if (trk.hasHitInNextROF()) { + // hFakMig->Fill(trk.getPt(), trk.getNClusters(), iL); + // } else { + // hFakVal->Fill(trk.getPt(), trk.getNClusters(), iL); + // } } } } diff --git a/Detectors/ITSMFT/ITS/macros/test/CheckSeeding.C b/Detectors/ITSMFT/ITS/macros/test/CheckSeeding.C new file mode 100644 index 0000000000000..915f2dda75032 --- /dev/null +++ b/Detectors/ITSMFT/ITS/macros/test/CheckSeeding.C @@ -0,0 +1,706 @@ +// 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. + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DataFormatsITS/Vertex.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTrack.h" +#include "SimulationDataFormat/O2DatabasePDG.h" +#include "Steer/MCKinematicsReader.h" +#endif + +constexpr const char* tracFile = "o2trac_its.root"; +constexpr const char* collContextFile = "collisioncontext.root"; + +namespace +{ +namespace fs = std::filesystem; + +constexpr float MinPt = 0.05f; +constexpr float MaxEta = 1.1f; +constexpr int NMultiplicityBins = 11; +constexpr std::array MultiplicityLabels{{"2", "3", "4-5", "6-8", "9-13", "14-21", "22-33", "34-52", "53-83", "84-128", "128+"}}; + +struct TruthInfo { + int multiplicity = 0; + float x = 0.f; + float y = 0.f; + float z = 0.f; +}; + +struct BestRecoInfo { + o2::its::Vertex vertex; + float purity = -1.f; +}; + +struct GaussianSummary { + bool valid = false; + double mean = 0.; + double sigma = 0.; +}; + +bool isTrueVertexLabel(const o2::MCCompLabel& label) +{ + return label.isValid() && !label.isFake() && label.getSourceID() == 0; +} + +bool isChargedPrimary(const o2::MCTrack& track) +{ + if (!track.isPrimary() || track.GetPt() < MinPt || std::abs(track.GetEta()) > MaxEta) { + return false; + } + auto* pdg = o2::O2DatabasePDG::Instance()->GetParticle(track.GetPdgCode()); + return pdg != nullptr && pdg->Charge() != 0.; +} + +bool isBetterReco(const o2::its::Vertex& candidate, float candidatePurity, const o2::its::Vertex& current, float currentPurity) +{ + if (candidatePurity != currentPurity) { + return candidatePurity > currentPurity; + } + if (candidate.getNContributors() != current.getNContributors()) { + return candidate.getNContributors() > current.getNContributors(); + } + return candidate.getChi2() < current.getChi2(); +} + +int getMultiplicityCategory(int multiplicity) +{ + if (multiplicity <= 2) { + return 1; + } + if (multiplicity <= 3) { + return 2; + } + if (multiplicity <= 5) { + return 3; + } + if (multiplicity <= 8) { + return 4; + } + if (multiplicity <= 13) { + return 5; + } + if (multiplicity <= 21) { + return 6; + } + if (multiplicity <= 33) { + return 7; + } + if (multiplicity <= 52) { + return 8; + } + if (multiplicity <= 83) { + return 9; + } + if (multiplicity <= 128) { + return 10; + } + return 11; +} + +void fillMultiplicityHistogram(TH1* hist, int multiplicity) +{ + hist->Fill(getMultiplicityCategory(multiplicity)); +} + +GaussianSummary fitGaussianCore(TH1* hist, const char* funcName) +{ + if (hist == nullptr || hist->GetEntries() < 20) { + return {}; + } + const auto rms = hist->GetRMS(); + if (!(rms > 0.)) { + return {}; + } + + TF1 fit(funcName, "gaus", hist->GetMean() - 2. * rms, hist->GetMean() + 2. * rms); + fit.SetParameters(hist->GetMaximum(), hist->GetMean(), rms); + hist->Fit(&fit, "Q0R"); + + const auto mean = fit.GetParameter(1); + const auto sigma = std::abs(fit.GetParameter(2)); + if (!(sigma > 0.)) { + return {}; + } + + fit.SetRange(mean - 2. * sigma, mean + 2. * sigma); + hist->Fit(&fit, "Q0R"); + return {true, fit.GetParameter(1), std::abs(fit.GetParameter(2))}; +} + +TH1D* makeNormalizedCopy(const TH1D* source, const char* name, const char* title) +{ + auto* copy = static_cast(source->Clone(name)); + copy->SetTitle(title); + const auto integral = copy->Integral("width"); + if (integral > 0.) { + copy->Scale(1. / integral); + } + return copy; +} + +void setMultiplicityBinLabels(TH1* hist) +{ + for (int i = 0; i < NMultiplicityBins; ++i) { + hist->GetXaxis()->SetBinLabel(i + 1, MultiplicityLabels[i]); + } +} + +void setHistogramStyle(TH1* hist, int color, int marker) +{ + hist->SetLineColor(color); + hist->SetMarkerColor(color); + hist->SetMarkerStyle(marker); + hist->SetLineWidth(2); +} + +void printGaussianByMultiplicity(const std::array& summaries, const char* title) +{ + std::printf("%s:\n", title); + for (int i = 0; i < NMultiplicityBins; ++i) { + if (summaries[i].valid) { + std::printf(" %-4s : mean=%.6g sigma=%.6g\n", MultiplicityLabels[i], summaries[i].mean, summaries[i].sigma); + } else { + std::printf(" %-4s : n/a\n", MultiplicityLabels[i]); + } + } +} + +fs::path resolveContextFile(const fs::path& dir) +{ + const std::array candidates{ + dir / collContextFile, + dir.parent_path() / collContextFile, + fs::current_path() / collContextFile}; + for (const auto& candidate : candidates) { + if (!candidate.empty() && fs::exists(candidate) && fs::is_regular_file(candidate)) { + return candidate; + } + } + return {}; +} + +void printBinnedFractions(const TH1* numerator, const TH1* denominator, const char* title) +{ + if (numerator == nullptr || denominator == nullptr) { + return; + } + std::printf("%s:\n", title); + for (int iBin = 1; iBin <= denominator->GetNbinsX(); ++iBin) { + const auto den = denominator->GetBinContent(iBin); + const auto num = numerator->GetBinContent(iBin); + const auto value = den > 0. ? num / den : 0.; + std::printf(" %-4s : %.4f (%g / %g)\n", denominator->GetXaxis()->GetBinLabel(iBin), value, num, den); + } +} + +std::vector findDirs(const std::string& roots) +{ + fs::path root = roots.empty() ? fs::current_path() : fs::path{roots}; + std::vector result; + const auto hasFiles = [](const fs::path& dir) { + const auto tracPath = dir / tracFile; + return fs::exists(tracPath) && fs::is_regular_file(tracPath); + }; + + if (fs::is_directory(root) && hasFiles(root)) { + result.push_back(root); + return result; + } + + for (const auto& entry : fs::recursive_directory_iterator(root)) { + if (entry.is_directory() && hasFiles(entry.path())) { + result.push_back(entry.path()); + } + } + std::sort(result.begin(), result.end()); + return result; +} + +} // namespace + +void CheckSeeding(const std::string& dir = "") +{ + using Vertex = o2::its::Vertex; + const auto cwd = fs::current_path(); + gStyle->SetOptStat(0); + TH1::AddDirectory(kFALSE); + + auto dirs = findDirs(dir); + std::printf("Will iterate over %zu input dirs\n", dirs.size()); + if (dirs.empty()) { + std::printf("No input directories containing %s were found.\n", tracFile); + return; + } + + auto* hTruthMultiplicityFindable = new TH1D("hTruthMultiplicityFindable", + "Findable truth vertices;truth multiplicity bin;vertices", + NMultiplicityBins, 0.5, NMultiplicityBins + 0.5); + auto* hTruthMultiplicityFound = new TH1D("hTruthMultiplicityFound", + "Found truth vertices;truth multiplicity bin;vertices", + NMultiplicityBins, 0.5, NMultiplicityBins + 0.5); + auto* hRecoMultiplicityTrue = new TH1D("hRecoMultiplicityTrue", + "True reconstructed vertices;reco multiplicity bin;vertices", + NMultiplicityBins, 0.5, NMultiplicityBins + 0.5); + auto* hRecoMultiplicityFake = new TH1D("hRecoMultiplicityFake", + "Fake reconstructed vertices;reco multiplicity bin;vertices", + NMultiplicityBins, 0.5, NMultiplicityBins + 0.5); + auto* hDx = new TH1D("hDx", "Matched vertex residuals;x_{reco}-x_{MC} (cm);vertices", 400, -0.02, 0.02); + auto* hDy = new TH1D("hDy", "Matched vertex residuals;y_{reco}-y_{MC} (cm);vertices", 400, -0.02, 0.02); + auto* hDz = new TH1D("hDz", "Matched vertex residuals;z_{reco}-z_{MC} (cm);vertices", 400, -0.02, 0.02); + auto* hPullX = new TH1D("hPullX", "Matched vertex pulls;x pull;vertices", 600, -30., 30.); + auto* hPullY = new TH1D("hPullY", "Matched vertex pulls;y pull;vertices", 600, -30., 30.); + auto* hPullZ = new TH1D("hPullZ", "Matched vertex pulls;z pull;vertices", 600, -30., 30.); + std::array hPullXByMult{}; + std::array hPullYByMult{}; + std::array hPullZByMult{}; + for (int i = 0; i < NMultiplicityBins; ++i) { + const auto nameX = std::string("hPullX_") + std::to_string(i + 1); + const auto nameY = std::string("hPullY_") + std::to_string(i + 1); + const auto nameZ = std::string("hPullZ_") + std::to_string(i + 1); + const auto titleX = std::string("x pull ") + MultiplicityLabels[i] + ";x pull;vertices"; + const auto titleY = std::string("y pull ") + MultiplicityLabels[i] + ";y pull;vertices"; + const auto titleZ = std::string("z pull ") + MultiplicityLabels[i] + ";z pull;vertices"; + hPullXByMult[i] = new TH1D(nameX.c_str(), titleX.c_str(), 600, -30., 30.); + hPullYByMult[i] = new TH1D(nameY.c_str(), titleY.c_str(), 600, -30., 30.); + hPullZByMult[i] = new TH1D(nameZ.c_str(), titleZ.c_str(), 600, -30., 30.); + } + + setMultiplicityBinLabels(hTruthMultiplicityFindable); + setMultiplicityBinLabels(hTruthMultiplicityFound); + setMultiplicityBinLabels(hRecoMultiplicityTrue); + setMultiplicityBinLabels(hRecoMultiplicityFake); + setHistogramStyle(hTruthMultiplicityFindable, kGray + 2, 20); + setHistogramStyle(hTruthMultiplicityFound, kAzure + 2, 20); + setHistogramStyle(hRecoMultiplicityTrue, kAzure + 2, 20); + setHistogramStyle(hRecoMultiplicityFake, kOrange + 7, 24); + setHistogramStyle(hDx, kAzure + 2, 20); + setHistogramStyle(hDy, kGreen + 2, 21); + setHistogramStyle(hDz, kRed + 1, 24); + setHistogramStyle(hPullX, kAzure + 2, 20); + setHistogramStyle(hPullY, kGreen + 2, 21); + setHistogramStyle(hPullZ, kRed + 1, 24); + + size_t findable = 0; + size_t totalFound = 0; + size_t trueFound = 0; + size_t fakeFound = 0; + size_t uniqueTrueReco = 0; + size_t uniqueFindableFound = 0; + size_t sigmaXCount = 0; + size_t sigmaYCount = 0; + size_t sigmaZCount = 0; + double sumSigmaX = 0.; + double sumSigmaY = 0.; + double sumSigmaZ = 0.; + + for (const auto& inputDir : dirs) { + fs::current_path(inputDir); + std::printf("Working on %s\n", inputDir.c_str()); + const auto contextPath = resolveContextFile(inputDir); + if (contextPath.empty()) { + std::printf("Skipping %s: could not locate %s\n", inputDir.c_str(), collContextFile); + continue; + } + + o2::steer::MCKinematicsReader mcReader(contextPath.string()); + if (!mcReader.isInitialized()) { + std::printf("Skipping %s: failed to initialize MCKinematicsReader from %s\n", inputDir.c_str(), contextPath.c_str()); + continue; + } + + std::unordered_map findableTruths; + std::unordered_set uniqueTrueLabelsReco; + std::unordered_set uniqueFindableTruthFound; + std::unordered_map bestRecoByTruth; + + const int iSrc = 0; + const auto nEvents = static_cast(mcReader.getNEvents(iSrc)); + for (int iEve = 0; iEve < nEvents; ++iEve) { + const auto& tracks = mcReader.getTracks(iSrc, iEve); + const auto contributors = static_cast(std::count_if(tracks.begin(), tracks.end(), isChargedPrimary)); + if (contributors >= 2) { + const auto& header = mcReader.getMCEventHeader(iSrc, iEve); + findableTruths.emplace(iEve, TruthInfo{contributors, (float)header.GetX(), (float)header.GetY(), (float)header.GetZ()}); + fillMultiplicityHistogram(hTruthMultiplicityFindable, contributors); + } + mcReader.releaseTracksForSourceAndEvent(iSrc, iEve); + } + + auto* tracFileHandle = TFile::Open((inputDir / tracFile).c_str()); + if (tracFileHandle == nullptr || tracFileHandle->IsZombie()) { + std::printf("Skipping %s: failed to open %s\n", inputDir.c_str(), tracFile); + delete tracFileHandle; + continue; + } + + auto* tracTree = tracFileHandle->Get("o2sim"); + if (tracTree == nullptr) { + std::printf("Skipping %s: missing o2sim tree in %s\n", inputDir.c_str(), tracFile); + tracFileHandle->Close(); + delete tracFileHandle; + continue; + } + + if (tracTree->GetBranch("Vertices") == nullptr || tracTree->GetBranch("ITSVertexMCTruth") == nullptr) { + std::printf("Skipping %s: missing vertex branches in %s\n", inputDir.c_str(), tracFile); + tracFileHandle->Close(); + delete tracFileHandle; + continue; + } + + std::vector* vertices = nullptr; + std::vector* labels = nullptr; + std::vector* purities = nullptr; + const bool hasPurityBranch = tracTree->GetBranch("ITSVertexMCPurity") != nullptr; + + tracTree->SetBranchAddress("Vertices", &vertices); + tracTree->SetBranchAddress("ITSVertexMCTruth", &labels); + if (hasPurityBranch) { + tracTree->SetBranchAddress("ITSVertexMCPurity", &purities); + } + + const auto nEntries = tracTree->GetEntriesFast(); + for (Long64_t iEntry = 0; iEntry < nEntries; ++iEntry) { + tracTree->GetEntry(iEntry); + if (vertices == nullptr || labels == nullptr) { + continue; + } + auto nVertices = std::min(vertices->size(), labels->size()); + if (hasPurityBranch && purities != nullptr) { + nVertices = std::min(nVertices, purities->size()); + } + + for (size_t iVtx = 0; iVtx < nVertices; ++iVtx) { + const auto& vertex = (*vertices)[iVtx]; + const auto& label = (*labels)[iVtx]; + const auto multiplicity = static_cast(vertex.getNContributors()); + ++totalFound; + + if (!isTrueVertexLabel(label)) { + ++fakeFound; + fillMultiplicityHistogram(hRecoMultiplicityFake, multiplicity); + continue; + } + + ++trueFound; + const auto eventID = label.getEventID(); + uniqueTrueLabelsReco.insert(eventID); + fillMultiplicityHistogram(hRecoMultiplicityTrue, multiplicity); + + const auto truthIt = findableTruths.find(eventID); + if (truthIt == findableTruths.end()) { + continue; + } + + uniqueFindableTruthFound.insert(eventID); + const auto purity = (hasPurityBranch && purities != nullptr) ? (*purities)[iVtx] : -1.f; + const auto bestIt = bestRecoByTruth.find(eventID); + if (bestIt == bestRecoByTruth.end() || isBetterReco(vertex, purity, bestIt->second.vertex, bestIt->second.purity)) { + bestRecoByTruth[eventID] = BestRecoInfo{vertex, purity}; + } + } + } + + tracFileHandle->Close(); + delete tracFileHandle; + + findable += findableTruths.size(); + uniqueTrueReco += uniqueTrueLabelsReco.size(); + uniqueFindableFound += uniqueFindableTruthFound.size(); + + for (const auto eventID : uniqueFindableTruthFound) { + const auto truthIt = findableTruths.find(eventID); + if (truthIt != findableTruths.end()) { + fillMultiplicityHistogram(hTruthMultiplicityFound, truthIt->second.multiplicity); + } + } + + for (const auto& [eventID, reco] : bestRecoByTruth) { + const auto truthIt = findableTruths.find(eventID); + if (truthIt == findableTruths.end()) { + continue; + } + const auto dx = reco.vertex.getX() - truthIt->second.x; + const auto dy = reco.vertex.getY() - truthIt->second.y; + const auto dz = reco.vertex.getZ() - truthIt->second.z; + hDx->Fill(dx); + hDy->Fill(dy); + hDz->Fill(dz); + if (reco.vertex.getSigmaX() > 0.f) { + const auto pullX = dx / reco.vertex.getSigmaX(); + hPullX->Fill(pullX); + hPullXByMult[getMultiplicityCategory(reco.vertex.getNContributors()) - 1]->Fill(pullX); + sumSigmaX += reco.vertex.getSigmaX(); + ++sigmaXCount; + } + if (reco.vertex.getSigmaY() > 0.f) { + const auto pullY = dy / reco.vertex.getSigmaY(); + hPullY->Fill(pullY); + hPullYByMult[getMultiplicityCategory(reco.vertex.getNContributors()) - 1]->Fill(pullY); + sumSigmaY += reco.vertex.getSigmaY(); + ++sigmaYCount; + } + if (reco.vertex.getSigmaZ() > 0.f) { + const auto pullZ = dz / reco.vertex.getSigmaZ(); + hPullZ->Fill(pullZ); + hPullZByMult[getMultiplicityCategory(reco.vertex.getNContributors()) - 1]->Fill(pullZ); + sumSigmaZ += reco.vertex.getSigmaZ(); + ++sigmaZCount; + } + } + fs::current_path(cwd); + } + + auto* hTruthMultiplicityEfficiency = static_cast(hTruthMultiplicityFound->Clone("hTruthMultiplicityEfficiency")); + hTruthMultiplicityEfficiency->SetTitle("Unique efficiency vs truth multiplicity;truth multiplicity bin;efficiency"); + hTruthMultiplicityEfficiency->Divide(hTruthMultiplicityFound, hTruthMultiplicityFindable, 1., 1., "B"); + setMultiplicityBinLabels(hTruthMultiplicityEfficiency); + hTruthMultiplicityEfficiency->SetMinimum(0.); + hTruthMultiplicityEfficiency->SetMaximum(1.05); + + auto* hRecoMultiplicityTotal = static_cast(hRecoMultiplicityTrue->Clone("hRecoMultiplicityTotal")); + hRecoMultiplicityTotal->SetTitle("All reconstructed vertices;reco multiplicity bin;vertices"); + hRecoMultiplicityTotal->Add(hRecoMultiplicityFake); + setMultiplicityBinLabels(hRecoMultiplicityTotal); + + auto* hRecoMultiplicityPurity = static_cast(hRecoMultiplicityTrue->Clone("hRecoMultiplicityPurity")); + hRecoMultiplicityPurity->SetTitle("Purity vs reconstructed multiplicity;reco multiplicity bin;purity"); + hRecoMultiplicityPurity->Divide(hRecoMultiplicityTrue, hRecoMultiplicityTotal, 1., 1., "B"); + setMultiplicityBinLabels(hRecoMultiplicityPurity); + hRecoMultiplicityPurity->SetMinimum(0.); + hRecoMultiplicityPurity->SetMaximum(1.05); + + const auto duplicates = trueFound >= uniqueTrueReco ? (trueFound - uniqueTrueReco) : 0UL; + + const double uniqueEfficiency = findable > 0 ? static_cast(uniqueFindableFound) / findable : 0.; + const double purity = totalFound > 0 ? static_cast(trueFound) / totalFound : 0.; + const double fakeRate = totalFound > 0 ? static_cast(fakeFound) / totalFound : 0.; + const double duplicateRate = trueFound > 0 ? static_cast(duplicates) / trueFound : 0.; + const double f1 = (uniqueEfficiency + purity) > 0. ? 2. * uniqueEfficiency * purity / (uniqueEfficiency + purity) : 0.; + + const auto dxFit = fitGaussianCore(hDx, "fitDx"); + const auto dyFit = fitGaussianCore(hDy, "fitDy"); + const auto dzFit = fitGaussianCore(hDz, "fitDz"); + const auto pullXFit = fitGaussianCore(hPullX, "fitPullX"); + const auto pullYFit = fitGaussianCore(hPullY, "fitPullY"); + const auto pullZFit = fitGaussianCore(hPullZ, "fitPullZ"); + std::array pullXByMultFit{}; + std::array pullYByMultFit{}; + std::array pullZByMultFit{}; + for (int i = 0; i < NMultiplicityBins; ++i) { + const auto fitX = std::string("fitPullX_") + std::to_string(i + 1); + const auto fitY = std::string("fitPullY_") + std::to_string(i + 1); + const auto fitZ = std::string("fitPullZ_") + std::to_string(i + 1); + pullXByMultFit[i] = fitGaussianCore(hPullXByMult[i], fitX.c_str()); + pullYByMultFit[i] = fitGaussianCore(hPullYByMult[i], fitY.c_str()); + pullZByMultFit[i] = fitGaussianCore(hPullZByMult[i], fitZ.c_str()); + } + + std::printf("\nVertex validation summary\n"); + std::printf(" findable truth vertices : %zu\n", findable); + std::printf(" total reconstructed vertices : %zu\n", totalFound); + std::printf(" true reconstructed vertices : %zu\n", trueFound); + std::printf(" fake reconstructed vertices : %zu\n", fakeFound); + std::printf(" unique true labels (all) : %zu\n", uniqueTrueReco); + std::printf(" unique findable truth found : %zu\n", uniqueFindableFound); + std::printf(" unique efficiency : %.5f\n", uniqueEfficiency); + std::printf(" purity : %.5f\n", purity); + std::printf(" fake rate : %.5f\n", fakeRate); + std::printf(" duplicate rate : %.5f\n", duplicateRate); + std::printf(" F1(purity,efficiency) : %.5f\n", f1); + std::printf(" mean reported sigma x/y/z : %.6g / %.6g / %.6g cm\n", + sigmaXCount > 0 ? sumSigmaX / sigmaXCount : 0., + sigmaYCount > 0 ? sumSigmaY / sigmaYCount : 0., + sigmaZCount > 0 ? sumSigmaZ / sigmaZCount : 0.); + + if (dxFit.valid) { + std::printf(" x residual Gaussian: mean=%.6g cm sigma=%.6g cm\n", dxFit.mean, dxFit.sigma); + } + if (dyFit.valid) { + std::printf(" y residual Gaussian: mean=%.6g cm sigma=%.6g cm\n", dyFit.mean, dyFit.sigma); + } + if (dzFit.valid) { + std::printf(" z residual Gaussian: mean=%.6g cm sigma=%.6g cm\n", dzFit.mean, dzFit.sigma); + } + if (pullXFit.valid) { + std::printf(" x pull Gaussian : mean=%.6g sigma=%.6g\n", pullXFit.mean, pullXFit.sigma); + } + if (pullYFit.valid) { + std::printf(" y pull Gaussian : mean=%.6g sigma=%.6g\n", pullYFit.mean, pullYFit.sigma); + } + if (pullZFit.valid) { + std::printf(" z pull Gaussian : mean=%.6g sigma=%.6g\n", pullZFit.mean, pullZFit.sigma); + } + printGaussianByMultiplicity(pullXByMultFit, "x pull Gaussian by reconstructed multiplicity"); + printGaussianByMultiplicity(pullYByMultFit, "y pull Gaussian by reconstructed multiplicity"); + printGaussianByMultiplicity(pullZByMultFit, "z pull Gaussian by reconstructed multiplicity"); + + printBinnedFractions(hTruthMultiplicityFound, hTruthMultiplicityFindable, "Efficiency vs truth multiplicity"); + printBinnedFractions(hRecoMultiplicityTrue, hRecoMultiplicityTotal, "Purity vs reconstructed multiplicity"); + + auto* cValidation = new TCanvas("cVertexValidation", "Vertex validation summary", 1800, 1000); + cValidation->Divide(3, 2); + + cValidation->cd(1); + gPad->SetMargin(0.05, 0.05, 0.05, 0.05); + auto* summary = new TPaveText(0.02, 0.02, 0.98, 0.98, "NDC"); + summary->SetBorderSize(0); + summary->SetFillColor(0); + summary->SetTextAlign(12); + summary->SetTextFont(42); + char line[256]; + std::snprintf(line, sizeof(line), "Findable truth vertices : %zu", findable); + summary->AddText(line); + std::snprintf(line, sizeof(line), "Total reconstructed : %zu", totalFound); + summary->AddText(line); + std::snprintf(line, sizeof(line), "True reconstructed : %zu", trueFound); + summary->AddText(line); + std::snprintf(line, sizeof(line), "Fake reconstructed : %zu", fakeFound); + summary->AddText(line); + summary->AddText(""); + std::snprintf(line, sizeof(line), "Unique truth found : %zu", uniqueFindableFound); + summary->AddText(line); + std::snprintf(line, sizeof(line), "Unique efficiency : %.5f", uniqueEfficiency); + summary->AddText(line); + std::snprintf(line, sizeof(line), "Purity : %.5f", purity); + summary->AddText(line); + std::snprintf(line, sizeof(line), "Fake rate : %.5f", fakeRate); + summary->AddText(line); + std::snprintf(line, sizeof(line), "Duplicate rate : %.5f", duplicateRate); + summary->AddText(line); + std::snprintf(line, sizeof(line), "F1 : %.5f", f1); + summary->AddText(line); + std::snprintf(line, sizeof(line), "mean sigma x/y/z cm : %.3g / %.3g / %.3g", + sigmaXCount > 0 ? sumSigmaX / sigmaXCount : 0., + sigmaYCount > 0 ? sumSigmaY / sigmaYCount : 0., + sigmaZCount > 0 ? sumSigmaZ / sigmaZCount : 0.); + summary->AddText(line); + summary->AddText(""); + if (dxFit.valid) { + std::snprintf(line, sizeof(line), "dx fit mean/sigma cm : %.3g / %.3g", dxFit.mean, dxFit.sigma); + summary->AddText(line); + } + if (dyFit.valid) { + std::snprintf(line, sizeof(line), "dy fit mean/sigma cm : %.3g / %.3g", dyFit.mean, dyFit.sigma); + summary->AddText(line); + } + if (dzFit.valid) { + std::snprintf(line, sizeof(line), "dz fit mean/sigma cm : %.3g / %.3g", dzFit.mean, dzFit.sigma); + summary->AddText(line); + } + if (pullXFit.valid) { + std::snprintf(line, sizeof(line), "pull x sigma : %.3g", pullXFit.sigma); + summary->AddText(line); + } + if (pullYFit.valid) { + std::snprintf(line, sizeof(line), "pull y sigma : %.3g", pullYFit.sigma); + summary->AddText(line); + } + if (pullZFit.valid) { + std::snprintf(line, sizeof(line), "pull z sigma : %.3g", pullZFit.sigma); + summary->AddText(line); + } + summary->Draw(); + + cValidation->cd(2); + gPad->SetGridy(); + hTruthMultiplicityEfficiency->Draw("hist e1"); + + cValidation->cd(3); + gPad->SetGridy(); + const auto maxReco = std::max(hRecoMultiplicityTrue->GetMaximum(), hRecoMultiplicityFake->GetMaximum()); + hRecoMultiplicityTrue->SetMaximum(1.2 * std::max(1., maxReco)); + hRecoMultiplicityTrue->Draw("hist e1"); + hRecoMultiplicityFake->Draw("hist e1 same"); + auto hRecoMultiplicitySum = (TH1D*)hRecoMultiplicityTrue->Clone("hRecoMultiplicitySum"); + hRecoMultiplicitySum->Add(hRecoMultiplicityFake); + setHistogramStyle(hRecoMultiplicitySum, kBlack, 23); + hRecoMultiplicitySum->Draw("hist e1 same"); + { + auto* legend = new TLegend(0.58, 0.75, 0.88, 0.88); + legend->SetBorderSize(0); + legend->AddEntry(hRecoMultiplicityTrue, "true", "lep"); + legend->AddEntry(hRecoMultiplicityFake, "fake", "lep"); + legend->AddEntry(hRecoMultiplicitySum, "sum", "lep"); + legend->Draw(); + } + + cValidation->cd(4); + gPad->SetGridy(); + hRecoMultiplicityPurity->Draw("hist e1"); + + cValidation->cd(5); + gPad->SetGridy(); + auto* hDxNorm = makeNormalizedCopy(hDx, "hDxNorm", "Matched vertex residuals;residual (cm);normalized entries"); + auto* hDyNorm = makeNormalizedCopy(hDy, "hDyNorm", "Matched vertex residuals;residual (cm);normalized entries"); + auto* hDzNorm = makeNormalizedCopy(hDz, "hDzNorm", "Matched vertex residuals;residual (cm);normalized entries"); + const auto maxResidual = std::max({hDxNorm->GetMaximum(), hDyNorm->GetMaximum(), hDzNorm->GetMaximum()}); + hDzNorm->SetMaximum(1.2 * std::max(1., maxResidual)); + hDzNorm->Draw("hist"); + hDxNorm->Draw("hist same"); + hDyNorm->Draw("hist same"); + { + auto* legend = new TLegend(0.62, 0.72, 0.88, 0.88); + legend->SetBorderSize(0); + legend->AddEntry(hDxNorm, "dx", "l"); + legend->AddEntry(hDyNorm, "dy", "l"); + legend->AddEntry(hDzNorm, "dz", "l"); + legend->Draw(); + } + + cValidation->cd(6); + gPad->SetGridy(); + auto* hPullXNorm = makeNormalizedCopy(hPullX, "hPullXNorm", "Matched vertex pulls;pull;normalized entries"); + auto* hPullYNorm = makeNormalizedCopy(hPullY, "hPullYNorm", "Matched vertex pulls;pull;normalized entries"); + auto* hPullZNorm = makeNormalizedCopy(hPullZ, "hPullZNorm", "Matched vertex pulls;pull;normalized entries"); + const auto maxPull = std::max({hPullXNorm->GetMaximum(), hPullYNorm->GetMaximum(), hPullZNorm->GetMaximum()}); + hPullZNorm->SetMaximum(1.2 * std::max(1., maxPull)); + hPullZNorm->Draw("hist"); + hPullXNorm->Draw("hist same"); + hPullYNorm->Draw("hist same"); + { + auto* legend = new TLegend(0.62, 0.72, 0.88, 0.88); + legend->SetBorderSize(0); + legend->AddEntry(hPullXNorm, "pull x", "l"); + legend->AddEntry(hPullYNorm, "pull y", "l"); + legend->AddEntry(hPullZNorm, "pull z", "l"); + legend->Draw(); + } + + cValidation->cd(); + cValidation->Update(); + cValidation->SaveAs("checkSeeding.pdf"); +} diff --git a/Detectors/ITSMFT/ITS/macros/test/CheckStaggering.C b/Detectors/ITSMFT/ITS/macros/test/CheckStaggering.C new file mode 100644 index 0000000000000..11ebcbbb1b1f1 --- /dev/null +++ b/Detectors/ITSMFT/ITS/macros/test/CheckStaggering.C @@ -0,0 +1,527 @@ +// Copyright 2019-2026 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. + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DetectorsBase/Propagator.h" +#include "DataFormatsITSMFT/CompCluster.h" +#include "DataFormatsITS/TrackITS.h" +#include "DataFormatsITS/Vertex.h" +#include "DataFormatsITS/TimeEstBC.h" +#include "CCDB/CcdbApi.h" +#include "CCDB/BasicCCDBManager.h" +#include "CommonConstants/LHCConstants.h" +#include "DataFormatsParameters/GRPMagField.h" +#include "DataFormatsParameters/GRPLHCIFData.h" +#include "ReconstructionDataFormats/Vertex.h" +#include "DetectorsVertexing/SVertexHypothesis.h" +#include "DCAFitter/DCAFitterN.h" +#endif + +constexpr const char* tracFile = "o2trac_its.root"; +constexpr const char* clsFile = "o2clus_its.root"; + +struct PairCandidate { + int posIdx; + int negIdx; + double dca; + double mass; +}; + +std::vector findDirs(const std::string&); + +void CheckStaggering(int runNumber, int max = -1, const std::string& dir = "") +{ + gStyle->SetOptStat(0); + auto dirs = findDirs(dir); + printf("Will iterate over %zu input dirs", dirs.size()); + if (dirs.empty()) { + printf("No input found"); + return; + } + if (max > 0 && (int)dirs.size() > max) { + std::random_device rd; + std::mt19937 g(rd()); + std::shuffle(dirs.begin(), dirs.end(), g); + dirs.resize(max); + printf("restricting to %ddirs", max); + } + + auto& ccdbmgr = o2::ccdb::BasicCCDBManager::instance(); + ccdbmgr.setURL("https://alice-ccdb.cern.ch"); + auto runDuration = ccdbmgr.getRunDuration(runNumber); + auto tRun = runDuration.first + ((runDuration.second - runDuration.first) / 2); // time stamp for the middle of the run duration + ccdbmgr.setTimestamp(tRun); + printf("Run %d has TS %ld", runNumber, tRun); + auto geoAligned = ccdbmgr.get("GLO/Config/GeometryAligned"); + auto magField = ccdbmgr.get("GLO/Config/GRPMagField"); + auto grpLHC = ccdbmgr.get("GLO/Config/GRPLHCIF"); + auto bcFill = grpLHC->getBunchFilling(); + bcFill.print(-1); + const o2::base::MatLayerCylSet* matLut = o2::base::MatLayerCylSet::rectifyPtrFromFile(ccdbmgr.get("GLO/Param/MatLUT")); + o2::base::Propagator::initFieldFromGRP(magField); + auto prop = o2::base::Propagator::Instance(); + prop->setMatLUT(matLut); + const float bz = prop->getNominalBz(); + + auto hNTrkCls = new TH1D("hNTrkCls", "Number of cluster per track;nCls;entries", 4, 3.5, 7.5); + std::array hTrkTS{nullptr}; + for (int i{0}; i < 5; ++i) { + hTrkTS[i] = new TH1D(Form("hTrkTS_%d", i), Form("track time t0 (%s);t0 (BC)", i == 0 ? "all" : Form("NCls=%d", 3 + i)), o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches); + } + auto hTrkTSE = new TH1D("hTrkTSE", "assigned time errors; tE (BC)", 198, -0.5, 198 - 0.5); + + // K0 && Phi-Meson + const float mMinITSPt{0.15}; + // + const int phiMinNCls{7}; + const float phiMaxDCAR2MVTX{0.05}; // max distance to mean vtx + auto hPhiMeson = new TH1D("hPhiMeson", "#phi meson;mass (GeV/c^{2})", 200, 0.96, 1.1); + auto hPhiMesonBkg = new TH1D("hPhiMesonBkg", "#phi meson background;mass (GeV/c^{2})", 200, 0.96, 1.1); + + const int mK0MinNCls{7}; + const float mK0minCosPAXYMeanVertex = 0.98; + const float mK0MaxDCAXY2ToMeanVertex = 0.2; + const float mK0MaxTgl2V0 = 1; + const float mK0MinPt2V0 = 0.3; + const float mK0MinCosPA = 0.999; + o2::vertexing::DCAFitterN<2> k0Ft; + k0Ft.setBz(bz); + k0Ft.setPropagateToPCA(true); // After finding the vertex, propagate tracks to the DCA. This is default anyway + k0Ft.setMaxR(30); + k0Ft.setMaxDZIni(0.1); + k0Ft.setMaxDXYIni(0.1); + k0Ft.setMinParamChange(1e-3); + k0Ft.setMinRelChi2Change(0.9); + k0Ft.setMaxChi2(5); + k0Ft.setUseAbsDCA(true); + auto hK0 = new TH1D("hK0", "K0;mass (GeV/c^{2})", 100, 0.4, 0.6); + o2::vertexing::SVertexHypothesis k0Hyp; + const float k0Par[] = {0., 20, 0., 5.0, 0.0, 1.09004e-03, 2.62291e-04, 8.93179e-03, 2.83121}; + k0Hyp.set(o2::track::PID::K0, o2::track::PID::Pion, o2::track::PID::Pion, k0Par, bz); + + auto hVtxXY = new TH2F("hVtxXY", "seeding vertices XY", 200, -0.3, 0.3, 200, -0.3, 0.3); + auto hVtxZ = new TH1F("hVtxZ", "seeding vertices Z", 200, -16, 16); + auto hVtxNCont = new TH1F("hVtxNCont", "seeding vertices contributors", 100, 0, 100); + auto hVtxZNCont = new TProfile("hVtxZNCont", "seeding vertices z-contributors", 200, -16, 16); + auto hVtxCls = new TProfile("hVtxCls", ";Cls/TF;Cls/Vtx", 2000, 600000, 900000); + auto hVtxTS = new TH1D("hVtxTS", "vtx time t0;t0 (BC)", o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches); + + const float minVtxWeight{5}; + float meanVtxWeight{0}; + o2::dataformats::VertexBase meanVtx; + auto accountVtx = [&](o2::its::Vertex const& vtx) { + const float w = vtx.getNContributors(); + if (w >= minVtxWeight) { + meanVtx.setX((meanVtx.getX() * meanVtxWeight + vtx.getX() * w) / (meanVtxWeight + w)); + meanVtx.setY((meanVtx.getY() * meanVtxWeight + vtx.getY() * w) / (meanVtxWeight + w)); + meanVtxWeight += w; + } + }; + + std::vector trkArr, *trkArrPtr{&trkArr}; + std::vector vtxArr, *vtxArrPtr{&vtxArr}; + std::array*, 7> clsArr{nullptr}; + for (size_t iDir{0}; iDir < dirs.size(); ++iDir) { + int progress = static_cast((iDir + 1) * 100 / dirs.size()); + printf("\rProgress: ["); + int barWidth = 50; + int pos = barWidth * progress / 100; + for (int j = 0; j < barWidth; ++j) { + if (j < pos) { + printf("="); + } else if (j == pos) { + printf(">"); + } else { + printf(" "); + } + } + printf("] %d%%", progress); + fflush(stdout); + + const auto& d = dirs[iDir]; + auto fTrks = TFile::Open((d / tracFile).c_str()); + auto fCls = TFile::Open((d / clsFile).c_str()); + if (!fTrks || !fCls || fTrks->IsZombie() || fCls->IsZombie()) { + continue; + } + auto tTrks = fTrks->Get("o2sim"); + auto tCls = fCls->Get("o2sim"); + if (!tTrks || !tCls) { + continue; + } + + tTrks->SetBranchAddress("ITSTrack", &trkArrPtr); + tTrks->SetBranchAddress("Vertices", &vtxArrPtr); + if (tCls->GetBranchStatus("ITSClusterComp")) { + tCls->SetBranchAddress("ITSClusterComp", &clsArr[0]); + } else { + for (int i{0}; i < 7; ++i) { + tCls->SetBranchAddress(Form("ITSClusterComp_%d", i), &clsArr[i]); + } + } + + for (int iTF{0}; tTrks->LoadTree(iTF) >= 0; ++iTF) { + tTrks->GetEntry(iTF); + tCls->GetEntry(iTF); + + size_t ncls = 0; + for (int i{0}; i < 7; ++i) { + if (clsArr[i]) { + ncls += clsArr[i]->size(); + } + } + + // for each TF built pool of positive and negaitve tracks + std::vector posPool, negPool; + + for (const auto& trk : trkArr) { + hNTrkCls->Fill(trk.getNClusters()); + hTrkTS[0]->Fill(std::fmod(trk.getTimeStamp().getTimeStamp(), o2::constants::lhc::LHCMaxBunches)); + hTrkTS[trk.getNClusters() - 3]->Fill(std::fmod(trk.getTimeStamp().getTimeStamp(), o2::constants::lhc::LHCMaxBunches)); + hTrkTSE->Fill(trk.getTimeStamp().getTimeStampError()); + + if (trk.getPt() > mMinITSPt) { + if (trk.getCharge() > 0) { + posPool.push_back(&trk); + } else { + negPool.push_back(&trk); + } + } + } + + for (const auto& vtx : vtxArr) { + hVtxXY->Fill(vtx.getX(), vtx.getY()); + hVtxZ->Fill(vtx.getZ()); + hVtxNCont->Fill(vtx.getNContributors()); + hVtxZNCont->Fill(vtx.getZ(), vtx.getNContributors()); + hVtxTS->Fill(vtx.getTimeStamp().getTimeStamp()); + accountVtx(vtx); + } + hVtxCls->Fill(ncls, (float)ncls / (float)vtxArr.size()); + + std::vector k0Cands; + for (int iPos{0}; iPos < (int)posPool.size(); ++iPos) { + const auto pos = posPool[iPos]; + for (int iNeg{0}; iNeg < (int)negPool.size(); ++iNeg) { + const auto neg = negPool[iNeg]; + bool overlap = std::abs(pos->getTimeStamp().getTimeStamp() - neg->getTimeStamp().getTimeStamp()) <= (pos->getTimeStamp().getTimeStampError() + neg->getTimeStamp().getTimeStampError()); + if (!overlap) { + continue; + } + + // phi-meson + if (pos->getNClusters() >= phiMinNCls && neg->getNClusters() >= phiMinNCls) { + o2::dataformats::DCA posDCA, negDCA; + o2::track::TrackParCov posPhi = *pos; + posPhi.setPID(o2::track::PID::Kaon); + o2::track::TrackParCov negPhi = *neg; + negPhi.setPID(o2::track::PID::Kaon); + if (prop->propagateToDCA(meanVtx, posPhi, bz, 2.0, o2::base::Propagator::MatCorrType::USEMatCorrLUT, &posDCA) && prop->propagateToDCA(meanVtx, negPhi, bz, 2.0, o2::base::Propagator::MatCorrType::USEMatCorrLUT, &negDCA)) { + if (posDCA.getR2() < phiMaxDCAR2MVTX && negDCA.getR2() < phiMaxDCAR2MVTX) { + std::array pP{}, pN{}; + posPhi.getPxPyPzGlo(pP); + negPhi.getPxPyPzGlo(pN); + TLorentzVector p1, p2; + p1.SetXYZM(pP[0], pP[1], pP[2], posPhi.getPID().getMass()); + p2.SetXYZM(pN[0], pN[1], pN[2], negPhi.getPID().getMass()); + TLorentzVector mother = p1 + p2; + hPhiMeson->Fill(mother.M()); + // rotate on daughter track to estimate background + for (int i{0}; i < 10; ++i) { + double theta = gRandom->Uniform(165.f, 195.f) * TMath::DegToRad(); + double pxN = pN[0] * cos(theta) - pN[1] * sin(theta); + double pyN = pN[0] * sin(theta) + pN[1] * cos(theta); + double pxP = pP[0] * cos(-theta) - pP[1] * sin(-theta); + double pyP = pP[0] * sin(-theta) + pP[1] * cos(-theta); + p1.SetXYZM(pxP, pyP, pP[2], posPhi.getPID().getMass()); + p2.SetXYZM(pxN, pyN, pN[2], negPhi.getPID().getMass()); + mother = p1 + p2; + hPhiMesonBkg->Fill(mother.M()); + } + } + } + } + // K0 + if (pos->getNClusters() >= mK0MinNCls && neg->getNClusters() >= mK0MinNCls) { + o2::track::TrackParCov posPion = *pos; + posPion.setPID(o2::track::PID::Pion); + o2::track::TrackParCov negPion = *neg; + negPion.setPID(o2::track::PID::Pion); + int ncand = k0Ft.process(posPion, negPion); + const int cand = 0; + if (ncand) { + const auto& v0XYZ = k0Ft.getPCACandidate(); + float dxv0 = v0XYZ[0] - meanVtx.getX(), dyv0 = v0XYZ[1] - meanVtx.getY(), r2v0 = dxv0 * dxv0 + dyv0 * dyv0; + if (!k0Ft.isPropagateTracksToVertexDone(cand) && !k0Ft.propagateTracksToVertex(cand)) { + continue; + } + const auto& trPProp = k0Ft.getTrack(0, cand); + const auto& trNProp = k0Ft.getTrack(1, cand); + std::array pP{}, pN{}; + trPProp.getPxPyPzGlo(pP); + trNProp.getPxPyPzGlo(pN); + // estimate DCA of neutral V0 track to beamline: straight line with parametric equation + // x = X0 + pV0[0]*t, y = Y0 + pV0[1]*t reaches DCA to beamline (Xv, Yv) at + // t = -[ (x0-Xv)*pV0[0] + (y0-Yv)*pV0[1]) ] / ( pT(pV0)^2 ) + // Similar equation for 3D distance involving pV0[2] + std::array pV0 = {pP[0] + pN[0], pP[1] + pN[1], pP[2] + pN[2]}; + float pt2V0 = pV0[0] * pV0[0] + pV0[1] * pV0[1], prodXYv0 = dxv0 * pV0[0] + dyv0 * pV0[1], tDCAXY = prodXYv0 / pt2V0; + if (pt2V0 < mK0MinPt2V0) { // pt cut + continue; + } + if (pV0[2] * pV0[2] / pt2V0 > mK0MaxTgl2V0) { // tgLambda cut + continue; + } + float dcaX = dxv0 - pV0[0] * tDCAXY, dcaY = dyv0 - pV0[1] * tDCAXY, dca2 = dcaX * dcaX + dcaY * dcaY; + float cosPAXY = prodXYv0 / std::sqrt(r2v0 * pt2V0); + if (dca2 > mK0MaxDCAXY2ToMeanVertex || cosPAXY < mK0minCosPAXYMeanVertex) { + continue; + } + float p2V0 = pt2V0 + pV0[2] * pV0[2], ptV0 = std::sqrt(pt2V0); + float p2Pos = pP[0] * pP[0] + pP[1] * pP[1] + pP[2] * pP[2], p2Neg = pN[0] * pN[0] + pN[1] * pN[1] + pN[2] * pN[2]; + if (!k0Hyp.check(p2Pos, p2Neg, p2V0, ptV0)) { + continue; + } + + float bestCosPA = mK0MinCosPA; + bool candFound = false; + for (const auto& vtx : vtxArr) { + if (vtx.getNContributors() > minVtxWeight) { + const auto vtxT = vtx.getTimeStamp().makeSymmetrical(); + bool overlapPos = std::abs(pos->getTimeStamp().getTimeStamp() - vtxT.getTimeStamp()) <= (pos->getTimeStamp().getTimeStampError() + vtxT.getTimeStampError()); + bool overlapNeg = std::abs(neg->getTimeStamp().getTimeStamp() - vtxT.getTimeStamp()) <= (neg->getTimeStamp().getTimeStampError() + vtxT.getTimeStampError()); + if (overlapPos && overlapNeg) { + float dx = v0XYZ[0] - vtx.getX(), dy = v0XYZ[1] - vtx.getY(), dz = v0XYZ[2] - vtx.getZ(), prodXYZv0 = dx * pV0[0] + dy * pV0[1] + dz * pV0[2]; + float cosPA = prodXYZv0 / std::sqrt((dx * dx + dy * dy + dz * dz) * p2V0); + if (cosPA > bestCosPA) { + bestCosPA = cosPA; + candFound = true; + } + } + } + } + if (candFound) { + TLorentzVector p1, p2; + p1.SetXYZM(pP[0], pP[1], pP[2], posPion.getPID().getMass()); + p2.SetXYZM(pN[0], pN[1], pN[2], negPion.getPID().getMass()); + TLorentzVector mother = p1 + p2; + double mass = mother.M(); + k0Cands.emplace_back(iPos, iNeg, k0Ft.getChi2AtPCACandidate(cand), mass); + } + } + } + } + } + + // disambiguiate candidates by using the smallest DCA one + std::sort(k0Cands.begin(), k0Cands.end(), [](const auto& a, const auto& b) { return a.dca < b.dca; }); + std::vector posUsed(posPool.size(), false); + std::vector negUsed(negPool.size(), false); + for (const auto& c : k0Cands) { + if (!posUsed[c.posIdx] && !negUsed[c.negIdx]) { + posUsed[c.posIdx] = true; + negUsed[c.negIdx] = true; + hK0->Fill(c.mass); + } + } + } + + fTrks->Close(); + fCls->Close(); + } + + auto drawBCPattern = [&]() { + gPad->Update(); + // draw BC pattern + double ymin = gPad->GetUymin(); + double ymax = gPad->GetUymax(); + auto interactingBC = bcFill.getPattern(); + TLine* lastLine{nullptr}; + for (int iBC{0}; iBC < (int)interactingBC.size(); ++iBC) { + if (interactingBC.test(iBC)) { + TLine* line = new TLine(iBC, ymin, iBC, ymax); + line->SetLineColor(kRed); + line->SetLineWidth(1); + line->SetLineStyle(kDashed); + line->Draw(); + lastLine = line; + } + } + return lastLine; + }; + + { + TFile out(Form("check_%d.root", runNumber), "RECREATE"); + out.WriteTObject(hNTrkCls); + for (int i{0}; i < 5; ++i) { + out.WriteTObject(hTrkTS[i]); + } + out.WriteTObject(hTrkTSE); + out.WriteTObject(hPhiMeson); + out.WriteTObject(hPhiMesonBkg); + out.WriteTObject(hK0); + out.WriteTObject(hVtxXY); + out.WriteTObject(hVtxZ); + out.WriteTObject(hVtxNCont); + out.WriteTObject(hVtxZNCont); + out.WriteTObject(hVtxTS); + out.WriteTObject(hVtxCls); + } + + // fitK0(hK0, runNumber); + // fitPhiMeson(hPhiMeson, runNumber); + { + auto c = new TCanvas(); + hNTrkCls->Draw(); + c->Draw(); + c->SaveAs(Form("trk_%d.pdf", runNumber)); + } + { + auto c = new TCanvas(); + c->Divide(3, 2); + for (int i{0}; i < 5; ++i) { + c->cd(i + 1); + hTrkTS[i]->Draw(); + auto lastLine = drawBCPattern(); + if (i == 0) { + auto leg = new TLegend(0.6, 0.6, 0.9, 0.9); + leg->AddEntry(lastLine, "Interacting BCs", "l"); + leg->AddEntry(hTrkTS[i], "Track timestamp"); + leg->Draw(); + } + } + c->cd(6); + hTrkTSE->Draw(); + c->Draw(); + c->SaveAs(Form("time_%d.pdf", runNumber)); + } + { + auto c = new TCanvas(); + c->Divide(2, 1); + { + c->cd(1); + hPhiMeson->Draw(); + gPad->Update(); + double ymin = gPad->GetUymin(); + double ymax = gPad->GetUymax(); + const float mass = 1.019461; + TLine* line = new TLine(mass, ymin, mass, ymax); + line->SetLineColor(kRed); + line->SetLineWidth(1); + line->SetLineStyle(kDashed); + line->Draw(); + } + { + c->cd(2); + hK0->Draw(); + gPad->Update(); + double ymin = gPad->GetUymin(); + double ymax = gPad->GetUymax(); + const float mass = 0.497611; + TLine* line = new TLine(mass, ymin, mass, ymax); + line->SetLineColor(kRed); + line->SetLineWidth(1); + line->SetLineStyle(kDashed); + line->Draw(); + } + c->Draw(); + c->SaveAs(Form("mass_%d.pdf", runNumber)); + } + { + auto c = new TCanvas(); + c->Divide(3, 2); + { + c->cd(1); + hVtxXY->Draw("col"); + } + { + c->cd(2); + hVtxZ->Draw(); + } + { + c->cd(3); + hVtxNCont->Draw(); + } + { + c->cd(4); + hVtxZNCont->Draw(); + } + { + c->cd(5); + hVtxCls->Draw(); + } + { + c->cd(6); + hVtxTS->Draw(); + auto lastLine = drawBCPattern(); + auto leg = new TLegend(0.6, 0.6, 0.9, 0.9); + leg->AddEntry(lastLine, "Interacting BCs", "l"); + leg->AddEntry(hVtxTS, "Track timestamp"); + leg->Draw(); + } + c->Draw(); + c->SaveAs(Form("vertex_%d.pdf", runNumber)); + } +} + +std::vector findDirs(const std::string& roots) +{ + std::filesystem::path root; + if (roots.empty()) { + root = std::filesystem::current_path(); + } else { + root = roots; + } + namespace fs = std::filesystem; + std::vector result; + auto has_files = [](const fs::path& dir) { + auto s = dir / tracFile; + return fs::exists(dir / tracFile) && fs::exists(dir / clsFile) && + fs::is_regular_file(dir / tracFile) && fs::is_regular_file(dir / clsFile); + }; + if (fs::is_directory(root) && has_files(root)) { + result.push_back(root); + return result; + } + for (const auto& entry : fs::recursive_directory_iterator(root)) { + if (!entry.is_directory()) { + continue; + } + const fs::path dir = entry.path(); + if (has_files(dir)) { + result.push_back(dir); + } + } + return result; +} diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/CMakeLists.txt b/Detectors/ITSMFT/ITS/postprocessing/studies/CMakeLists.txt index 9794b69631d57..08ced25d0f5dc 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/CMakeLists.txt @@ -15,6 +15,7 @@ SOURCES src/ImpactParameter.cxx src/PIDStudy.cxx src/ITSStudiesConfigParam.cxx src/AnomalyStudy.cxx + src/ITSBeamBackgroundStudy.cxx src/TrackCheck.cxx src/TrackExtension.cxx src/Efficiency.cxx diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/ITSBeamBackgroundStudy.h b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/ITSBeamBackgroundStudy.h new file mode 100644 index 0000000000000..cd96a9df8dee6 --- /dev/null +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/ITSBeamBackgroundStudy.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef O2_ITS_BEAMBKG_STUDY_H +#define O2_ITS_BEAMBKG_STUDY_H + +#include "Framework/DataProcessorSpec.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" + +namespace o2::its::study +{ + +using mask_t = o2::dataformats::GlobalTrackID::mask_t; + +o2::framework::DataProcessorSpec getITSBeamBackgroundStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC); + +} // namespace o2::its::study +#endif diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/src/ITSBeamBackgroundStudy.cxx b/Detectors/ITSMFT/ITS/postprocessing/studies/src/ITSBeamBackgroundStudy.cxx new file mode 100644 index 0000000000000..fc9e139648672 --- /dev/null +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/src/ITSBeamBackgroundStudy.cxx @@ -0,0 +1,739 @@ +// 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. + +#include "ITSStudies/ITSBeamBackgroundStudy.h" +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include "DataFormatsParameters/GRPObject.h" + +#include "CCDB/BasicCCDBManager.h" +#include "CCDB/CCDBTimeStampUtils.h" +#include "DataFormatsITSMFT/CompCluster.h" +#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsITSMFT/TopologyDictionary.h" + +#include +#include +#include + +#include +#include + +#include "Framework/Task.h" +#include "Framework/Logger.h" + +// ZDC +#include "DataFormatsZDC/RecEventFlat.h" + +using namespace o2::framework; +using namespace o2::globaltracking; +using GTrackID = o2::dataformats::GlobalTrackID; + +namespace o2::its::study +{ +class ITSBeamBackgroundStudy : public Task +{ + public: + ITSBeamBackgroundStudy(std::shared_ptr dr, + std::shared_ptr gr, + bool isMC) : mDataRequest{dr}, mGGCCDBRequest(gr), mUseMC(isMC) {} + + void init(InitContext& ic) final; + void run(ProcessingContext&) final; + void endOfStream(EndOfStreamContext&) final; + void finaliseCCDB(ConcreteDataMatcher&, void*) final; + void save_and_reset(); + + // Custom + void process(o2::globaltracking::RecoContainer& recoData); + void updateTimeDependentParams(ProcessingContext& pc); + + private: + void getClusterPatterns(gsl::span&, gsl::span&, const o2::itsmft::TopologyDictionary&); + std::vector mPatterns; + + // ITS layout + int NStaves[7] = {12, 16, 20, 24, 30, 42, 48}; + int N_STAVES_IB = 48; + int N_CHIP_IB = 432; + + // Utilities + int ChipToLayer(int chip); + double ChipToPhi(int chip); + bool searchBCfromMap(std::map>& BCperorbit, long target_orbit, int target_bc); + + std::shared_ptr mGGCCDBRequest; + std::shared_ptr mDataRequest; + bool mUseMC; + const o2::itsmft::TopologyDictionary* mDict = nullptr; + + int mTFn = 0; + int mTF_first_after_dump = 1; + + int mStrobeFallBack = 594; + int mStrobe = mStrobeFallBack; + + // TODO: the following should be make configurable + + std::pair TimeWindowZDC = std::make_pair(5., 11.); + std::pair TimeWindowZNAr = std::make_pair(5.64507, 9.64507); + std::pair TimeWindowZNCr = std::make_pair(4.21299, 8.21299); + std::pair TimeWindowZNAl = std::make_pair(-11.3549, -8.35493); + std::pair TimeWindowZNCl = std::make_pair(-12.787, -9.78701); + + int targetClusterMinCol = 128; // definition of anomalous cluster + int targetClusterMaxRow = 29; // definition of anomalous cluster + + int mDumpEveryTF = 10; // use -1 to save only at the end + int mSkimmedOnlyAfterTF = 15000; + std::string mOutputChip = "chipevents"; + std::string mOutoutChipSkim = "chipeventstarget"; + + TH1F* TimeWindowCut; + TH1F* ZNACall; + TH1F* ZNCCall; + TH1F* ZDCAtagBC; + TH1F* ZDCCtagBC; + TH1I* Counters; + TTree* ITSChipEvtTree; + TTree* ITSChipEvtTargetTree; + + // Tree variables + int Tbc; + long Torbit; + int Tchip; + double Tphi; + int TZDCtag; + int Tnhit, Tnclus, Tnhit_no1pix, Tnclus_no1pix; + int Tnclus_s20, Tnclus_s100, Tnclus_s150; + int Tnclus_c20, Tnclus_c100, Tnclus_c128; + int Tnclus_target; + double Tnhit1, Tnhit10; + int Tmissingafter, Tmissingafter2; + int Tmincol, Tmaxcol; +}; + +void ITSBeamBackgroundStudy::updateTimeDependentParams(ProcessingContext& pc) +{ + // o2::base::GRPGeomHelper::instance().checkUpdates(pc); + // static bool initOnceDone = false; + // if (!initOnceDone) { // this param need to be queried only once + // initOnceDone = true; + // // mGeom = o2::its::GeometryTGeo::Instance(); + // // mGeom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::T2GRot, o2::math_utils::TransformType::T2G)); + // } +} + +void ITSBeamBackgroundStudy::init(InitContext& ic) +{ + LOGP(info, "Initializing ITSBeamBackgroundStudy"); + LOGP(info, "Fetching ClusterDictionary"); + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); + mgr.setURL("http://alice-ccdb.cern.ch"); + mgr.setTimestamp(o2::ccdb::getCurrentTimestamp()); + mDict = mgr.get("ITS/Calib/ClusterDictionary"); + + LOGP(info, "Setting up trees and histograms. They will be dumped on file every {} TFs", mDumpEveryTF); + + TimeWindowCut = new TH1F("ZDC bkg region", "ZNAr, ZNCr, -ZNAl, -ZNCl", 8, 0, 8); + TimeWindowCut->SetBinContent(1, TimeWindowZNAr.first); + TimeWindowCut->SetBinContent(2, TimeWindowZNAr.second); + TimeWindowCut->SetBinContent(3, TimeWindowZNCr.first); + TimeWindowCut->SetBinContent(4, TimeWindowZNCr.second); + TimeWindowCut->SetBinContent(5, -TimeWindowZNAl.first); + TimeWindowCut->SetBinContent(6, -TimeWindowZNAl.second); + TimeWindowCut->SetBinContent(7, -TimeWindowZNCl.first); + TimeWindowCut->SetBinContent(8, -TimeWindowZNCl.second); + ZNACall = new TH1F("ZNACall", "ZNACall", 40, -20, 20); + ZNCCall = new TH1F("ZNCCall", "ZNCCall", 40, -20, 20); + ZDCAtagBC = new TH1F("ZDCA tagged BC", "ZDCA tagged bc", 3564, 0, 3564); + ZDCCtagBC = new TH1F("ZDCC tagged BC", "ZDCC tagged bc", 3564, 0, 3564); + + Counters = new TH1I("Counters", "Counters", 20, 1, 21); + Counters->GetXaxis()->SetBinLabel(1, "TF"); + Counters->GetXaxis()->SetBinLabel(2, "ROF"); + Counters->GetXaxis()->SetBinLabel(3, "ZDCA evt"); + Counters->GetXaxis()->SetBinLabel(4, "ROF-ZDCA tagged"); + Counters->GetXaxis()->SetBinLabel(5, "ITStag any"); + Counters->GetXaxis()->SetBinLabel(6, "ITStag TO"); + Counters->GetXaxis()->SetBinLabel(7, "ITStag any + ZDC"); + Counters->GetXaxis()->SetBinLabel(8, "ITStag TO + ZDC"); + Counters->GetXaxis()->SetBinLabel(9, "ZDCC evt"); + Counters->GetXaxis()->SetBinLabel(10, "ROF-ZDCC tagged"); + + ITSChipEvtTree = new TTree("chipevt", "chipevt"); + + // Chip event branches + ITSChipEvtTree->Branch("TFprogress", &mTFn, "TFprogress/I"); + ITSChipEvtTree->Branch("orbit", &Torbit, "orbit/L"); + ITSChipEvtTree->Branch("bc", &Tbc, "bc/I"); + ITSChipEvtTree->Branch("chip", &Tchip, "chip/I"); + ITSChipEvtTree->Branch("phi", &Tphi, "phi/D"); + ITSChipEvtTree->Branch("zdctag", &TZDCtag, "zdctag/I"); + ITSChipEvtTree->Branch("nhit", &Tnhit, "nhit/I"); + ITSChipEvtTree->Branch("nhit_no1pix", &Tnhit_no1pix, "nhit_no1pix/I"); + ITSChipEvtTree->Branch("size1", &Tnhit1, "size1/D"); + ITSChipEvtTree->Branch("size10", &Tnhit10, "size10/D"); + ITSChipEvtTree->Branch("nclus", &Tnclus, "nclus/I"); + ITSChipEvtTree->Branch("nclus_no1pix", &Tnclus_no1pix, "nclus_no1pix/I"); + ITSChipEvtTree->Branch("nclus_target", &Tnclus_target, "nclus_target/I"); + ITSChipEvtTree->Branch("missingafter", &Tmissingafter, "missingafter/I"); + ITSChipEvtTree->Branch("missingafter2", &Tmissingafter2, "missingafter2/I"); + ITSChipEvtTree->Branch("mincol", &Tmincol, "mincol/I"); + ITSChipEvtTree->Branch("maxcol", &Tmaxcol, "maxcol/I"); + + ITSChipEvtTargetTree = ITSChipEvtTree->CloneTree(0); + ITSChipEvtTargetTree->SetName("chipevttarget"); + ITSChipEvtTargetTree->SetTitle("chipevttarget"); +} + +void ITSBeamBackgroundStudy::save_and_reset() +{ + + std::string outfile11 = mOutoutChipSkim + "_" + std::to_string(mTF_first_after_dump) + "_" + std::to_string(mTFn) + ".root"; + LOGP(info, "Writing ROOT file {}", outfile11); + TFile* F11 = TFile::Open(outfile11.c_str(), "recreate"); + TimeWindowCut->Write(); + ZNACall->Write(); + ZNCCall->Write(); + ZDCAtagBC->Write(); + ZDCCtagBC->Write(); + Counters->Write(); + ITSChipEvtTargetTree->Write(); + F11->Close(); + delete F11; + + if (mTFn <= mSkimmedOnlyAfterTF) { + std::string outfile1 = mOutputChip + "_" + std::to_string(mTF_first_after_dump) + "_" + std::to_string(mTFn) + ".root"; + LOGP(info, "Writing ROOT file {}", outfile1); + TFile* F1 = TFile::Open(outfile1.c_str(), "recreate"); + TimeWindowCut->Write(); + ZNACall->Write(); + ZNCCall->Write(); + ZDCAtagBC->Write(); + ZDCCtagBC->Write(); + Counters->Write(); + ITSChipEvtTree->Write(); // chip events and the skimmed one + ITSChipEvtTargetTree->Write(); + F1->Close(); + delete F1; + } + + LOGP(info, "Resetting historgrams and trees"); + // Delete clears data but keep the branch setup intact + ITSChipEvtTree->Reset(); // Delete(""); + ITSChipEvtTargetTree->Reset(); // Delete(""); + ZNACall->Reset(); + ZNCCall->Reset(); + ZDCAtagBC->Reset(); + ZDCCtagBC->Reset(); + Counters->Reset(); + + mTF_first_after_dump = mTFn + 1; +} + +void ITSBeamBackgroundStudy::endOfStream(EndOfStreamContext&) +{ + LOGP(info, "End of stream for ITSBeamBackgroundStudy"); + + save_and_reset(); + + delete TimeWindowCut; + delete ZNACall; + delete ZNCCall; + delete ZDCAtagBC; + delete ZDCCtagBC; + delete Counters; + delete ITSChipEvtTree; + delete ITSChipEvtTargetTree; +} + +void ITSBeamBackgroundStudy::run(ProcessingContext& pc) +{ + + if (mTFn == std::numeric_limits::max()) { + LOGP(error, "Max {} TFs exceeded. Skipping all next events", mTFn); + return; + } + + mTFn++; + + if (mDumpEveryTF > 0 && mTFn > 0 && (mTFn % mDumpEveryTF) == 0) { + LOGP(info, "Reached TF #{}. Exporting new root files", mTFn); + save_and_reset(); + } + + o2::globaltracking::RecoContainer recoData; + recoData.collectData(pc, *mDataRequest.get()); + // updateTimeDependentParams(pc); + LOGP(info, "Calling process() for TF: {}", mTFn); + process(recoData); +} + +void ITSBeamBackgroundStudy::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) +{ + return; +} + +// Custom area +void ITSBeamBackgroundStudy::process(o2::globaltracking::RecoContainer& recoData) +{ + + LOGP(info, "Processing RecoContainer"); + Counters->Fill(1); + + LOGP(info, "Retrieving ZDC data"); + auto RecBC = recoData.getZDCBCRecData(); + auto Energy = recoData.getZDCEnergy(); + auto TDCData = recoData.getZDCTDCData(); + auto Info2 = recoData.getZDCInfo(); + LOGP(info, "sizeof ZDC RC: {}, {}, {}, {}", RecBC.size(), Energy.size(), TDCData.size(), Info2.size()); + + LOGP(info, "Retrieving ITS clusters"); + auto rofRecVec = recoData.getITSClustersROFRecords(); + auto clusArr = recoData.getITSClusters(); + auto clusPatt = recoData.getITSClustersPatterns(); + LOGP(info, "sizeof ITS RC: {}, {}, {}", clusArr.size(), clusPatt.size(), rofRecVec.size()); + + // TODO: improve this + if (rofRecVec.size() == 576 || rofRecVec.size() == 192) { + mStrobe = 3564 / (rofRecVec.size() / 32); + LOGP(info, "Assuimg TF length = 32 orbits and setting strobe length to {} bc", mStrobe); + } else { + mStrobe = mStrobeFallBack; + LOGP(warning, "Unforeseen number of ROFs in the loop. Using the strobe length fall back value {}", mStrobe); + } + + std::map> ZNArtag{}; // ZDCAtag[orbit] = + std::map> ZNCrtag{}; + std::map> ZNAltag{}; + std::map> ZNCltag{}; + + // ________________________________________________________________ + // FILLING ZDC ARRAY + o2::zdc::RecEventFlat ev; + + ev.init(RecBC, Energy, TDCData, Info2); + + int bkgcounterAr = 0, bkgcounterCr = 0; + int bkgcounterAl = 0, bkgcounterCl = 0; + while (ev.next()) { + + int32_t itdcA = o2::zdc::TDCZNAC; // should be == 0 + int32_t itdcC = o2::zdc::TDCZNCC; + long zdcorbit = (long)ev.ir.orbit; + + // ZDC - A side + int nhitA = ev.NtdcV(itdcA); + for (int32_t ipos = 0; ipos < nhitA; ipos++) { + + double mytdc = o2::zdc::FTDCVal * ev.TDCVal[itdcA][ipos]; + + ZNACall->Fill(mytdc); + + if (mytdc >= TimeWindowZNAr.first && mytdc <= TimeWindowZNAr.second) { + + // Backgroud event found here! + bkgcounterAr++; + Counters->Fill(3); + ZDCAtagBC->Fill(ev.ir.bc); + + if (ZNArtag.find(zdcorbit) != ZNArtag.end()) { + bool double_count_bkg = ZNArtag[zdcorbit].insert((int)ev.ir.bc).second; + if (double_count_bkg) { + LOGP(warning, "Multiple ZDCAr counts in the same orbit/bc {}/{}", zdcorbit, ev.ir.bc); + } + } else { + std::set zdcbcs{(int)ev.ir.bc}; + ZNArtag[zdcorbit] = zdcbcs; + } + + } // and of ZNAr time window + + if (mytdc >= TimeWindowZNAl.first && mytdc <= TimeWindowZNAl.second) { + + // Backgroud event found here! + bkgcounterAl++; + Counters->Fill(3); + ZDCAtagBC->Fill(ev.ir.bc); + + if (ZNAltag.find(zdcorbit) != ZNAltag.end()) { + bool double_count_bkg = ZNAltag[zdcorbit].insert((int)ev.ir.bc).second; + if (double_count_bkg) { + LOGP(warning, "Multiple ZDCAl counts in the same orbit/bc {}/{}", zdcorbit, ev.ir.bc); + } + } else { + std::set zdcbcs{(int)ev.ir.bc}; + ZNAltag[zdcorbit] = zdcbcs; + } + + } // and of ZNAl time window + } + + // ZDC - C side + int nhitC = ev.NtdcV(itdcC); + for (int32_t ipos = 0; ipos < nhitC; ipos++) { + + double mytdc = o2::zdc::FTDCVal * ev.TDCVal[itdcC][ipos]; + + ZNCCall->Fill(mytdc); + + if (mytdc >= TimeWindowZNCr.first && mytdc <= TimeWindowZNCr.second) { + + // Backgroud event found here! + bkgcounterCr++; + Counters->Fill(9); + ZDCCtagBC->Fill(ev.ir.bc); + + if (ZNCrtag.find(zdcorbit) != ZNCrtag.end()) { + bool double_count_bkg = ZNCrtag[zdcorbit].insert((int)ev.ir.bc).second; + if (double_count_bkg) { + LOGP(warning, "Multiple ZNCr counts in the same orbit/bc {}/{}", zdcorbit, ev.ir.bc); + } + } else { + std::set zdcbcs{(int)ev.ir.bc}; + ZNCrtag[zdcorbit] = zdcbcs; + } + + } // end of ZNCr time window + + if (mytdc >= TimeWindowZNCl.first && mytdc <= TimeWindowZNCl.second) { + + // Backgroud event found here! + bkgcounterCl++; + Counters->Fill(9); + ZDCCtagBC->Fill(ev.ir.bc); + + if (ZNCltag.find(zdcorbit) != ZNCltag.end()) { + bool double_count_bkg = ZNCltag[zdcorbit].insert((int)ev.ir.bc).second; + if (double_count_bkg) { + LOGP(warning, "Multiple ZNCl counts in the same orbit/bc {}/{}", zdcorbit, ev.ir.bc); + } + } else { + std::set zdcbcs{(int)ev.ir.bc}; + ZNCltag[zdcorbit] = zdcbcs; + } + + } // end of ZNCl time window + } + } // end of while ev.next() + + LOGP(info, "Found background envents from ZNAright/left {}/{} -- from ZNCright/left {}/{}", bkgcounterAr, bkgcounterAl, bkgcounterCr, bkgcounterCl); + //__________________________________________________________________ + + getClusterPatterns(clusArr, clusPatt, *mDict); + + int inTFROFcounter = -1; + + std::vector ChipSeenInThisROF(N_CHIP_IB, false); // ChipSeenInThisROF[chipid] = true/false + std::vector ChipSeenInLastROF(N_CHIP_IB, false); // ChipSeenInLastROF[chipid] = true/false + std::vector ChipSeenInLast2ROF(N_CHIP_IB, false); // ChipSeenInLast2ROF[chipid] = true/false + + // Begin loop over ROFs + for (auto it = rofRecVec.rbegin(); it != rofRecVec.rend(); ++it) { + + auto& rofRec = *it; + + inTFROFcounter++; + + Counters->Fill(2); + + ChipSeenInLast2ROF = ChipSeenInLastROF; + ChipSeenInLastROF = ChipSeenInThisROF; + std::fill(ChipSeenInThisROF.begin(), ChipSeenInThisROF.end(), false); + + auto clustersInRof = rofRec.getROFData(clusArr); + auto patternsInRof = rofRec.getROFData(mPatterns); + + Tbc = (int)rofRec.getBCData().bc; + Torbit = (long)rofRec.getBCData().orbit; + + if (inTFROFcounter < 1) { + LOGP(info, "First of TF: ITS orbit/bc {}/{}", Torbit, Tbc); + } + + // shifting by 60 bc + int eff_bc = Tbc + 60; + long eff_orbit = Torbit; + if (eff_bc > 3563) { + eff_bc -= 3564; + eff_orbit += 1; + } + + // Making a bitmask with ZDC tags for this bc + bool isZNArtagged = searchBCfromMap(ZNArtag, (long)eff_orbit, eff_bc); + if (isZNArtagged) { + Counters->Fill(4); + } + + bool isZNAltagged = searchBCfromMap(ZNAltag, (long)eff_orbit, eff_bc); + if (isZNAltagged) { + Counters->Fill(4); + } + + bool isZNCrtagged = searchBCfromMap(ZNCrtag, (long)eff_orbit, eff_bc); + if (isZNCrtagged) { + Counters->Fill(10); + } + + bool isZNCltagged = searchBCfromMap(ZNCltag, (long)eff_orbit, eff_bc); + if (isZNCltagged) { + Counters->Fill(10); + } + + TZDCtag = 0; + TZDCtag |= (isZNArtagged << 0); + TZDCtag |= (isZNAltagged << 1); + TZDCtag |= (isZNCrtagged << 2); + TZDCtag |= (isZNCltagged << 3); + + if (TZDCtag > 0) { + LOGP(info, "ZDC tag with mask {}: ZNAright = {} - ZNAleft = {} - ZNCright = {} - ZNCleft = {}", + TZDCtag, (TZDCtag >> 0) & 1, (TZDCtag >> 1) & 1, (TZDCtag >> 2) & 1, (TZDCtag >> 3) & 1); + } + + // preparing arrays for clusters analysis + std::set AvailableChips{}; + std::map> MAPsize{}; // MAP[chip] = {list if sizes} + std::map> MAPcols{}; // MAP[chip] = {list of column span} + std::map MAPntarget{}; // MAP[chip] = number of bad clusters in chip + std::map MAPcoo_mincol{}; // MAP[chip] = minimum of column coordinate + std::map MAPcoo_maxcol{}; // MAP[chip] = maximum of (column coordinate + colspan) + + // Finally loop over clusters + int ntarget_in_rof = 0; + for (int iclus = 0; iclus < clustersInRof.size(); iclus++) { + + const auto& compClus = clustersInRof[iclus]; + + auto chipid = compClus.getSensorID(); + + // Analyze only IB + if (ChipToLayer(chipid) > 2) { + continue; + } + + ChipSeenInThisROF[chipid] = true; + + int coo_col = (int)compClus.getCol(); + int coo_row = (int)compClus.getRow(); + + auto patti = patternsInRof[iclus]; + int npix = patti.getNPixels(); + int colspan = patti.getColumnSpan(); + int rowspan = patti.getRowSpan(); + + bool newchip = AvailableChips.insert(chipid).second; + if (newchip) { + MAPsize[chipid] = std::vector{}; + MAPcols[chipid] = std::vector{}; + MAPntarget[chipid] = 0; + MAPcoo_mincol[chipid] = coo_col; + MAPcoo_maxcol[chipid] = coo_col + colspan; + } + + MAPsize[chipid].push_back(npix); + MAPcols[chipid].push_back(colspan); + if (colspan >= targetClusterMinCol && rowspan <= targetClusterMaxRow) { + // Anomalous cluster found + MAPntarget[chipid] += 1; + ntarget_in_rof++; + } + MAPcoo_mincol[chipid] = TMath::Min(MAPcoo_mincol[chipid], coo_col); + MAPcoo_maxcol[chipid] = TMath::Max(MAPcoo_maxcol[chipid], coo_col + colspan); + + } // end of loop over clusters in rof + + if (ntarget_in_rof == 0 && mTFn > mSkimmedOnlyAfterTF + 2) { // extra 2 to avoid edge effects? + // do not need extra computations for this rof since it will not be saved in any case + continue; + } + + for (int ic : AvailableChips) { + + Tchip = ic; + + if (inTFROFcounter < 1) { + Tmissingafter = -1; + } else if (ChipSeenInLastROF[ic]) { + Tmissingafter = 0; + } else { + Tmissingafter = 1; + } + + if (inTFROFcounter < 2) { + Tmissingafter2 = -1; + } else if (ChipSeenInLast2ROF[ic]) { + Tmissingafter2 = 0; + } else { + Tmissingafter2 = 1; + } + + Tphi = ChipToPhi(ic); + + Tnclus = MAPsize[ic].size(); + Tmincol = MAPcoo_mincol[ic]; + Tmaxcol = MAPcoo_maxcol[ic]; + + std::sort(MAPsize[ic].begin(), MAPsize[ic].end(), std::greater<>()); + + Tnhit = Tnclus_s20 = Tnclus_s100 = Tnclus_s150 = 0; + Tnhit1 = Tnhit10 = 0.; + Tnclus_c20 = Tnclus_c100 = Tnclus_c128 = 0; + Tnclus_target = MAPntarget[ic]; + Tnhit_no1pix = 0; + Tnclus_no1pix = 0; + + int nhit_no1pix = 0; + int nclus10 = 0, nclus1 = 0; + + for (int nh : MAPsize[ic]) { + + Tnhit += nh; + + if (nh > 1) { + Tnhit_no1pix += nh; + Tnclus_no1pix += 1; + } + + if (nclus10 < 10) { + nclus10++; + Tnhit10 += 1. * nh; + } + + if (nclus1 < 1) { + nclus1++; + Tnhit1 += 1. * nh; + } + + Tnclus_s20 += (nh >= 20); + Tnclus_s100 += (nh >= 100); + Tnclus_s150 += (nh >= 150); + } + + Tnhit10 = (nclus10 == 0) ? 0. : 1. * Tnhit10 / nclus10; + + for (int nc : MAPcols[ic]) { + Tnclus_c20 += (nc >= 20); + Tnclus_c100 += (nc >= 100); + Tnclus_c128 += (nc >= 128); + } + + ITSChipEvtTree->Fill(); + if (Tnclus_target > 0) { + ITSChipEvtTargetTree->Fill(); + } + + } // end of loop over available chips + } // end of loop over ROFs +} + +// TODO: To be improved using geometry tools +int ITSBeamBackgroundStudy::ChipToLayer(int chip) +{ + if (chip < 108) { + return 0; + } + if (chip < 252) { + return 1; + } + if (chip < 432) { + return 2; + } + if (chip < 3120) { + return 3; + } + if (chip < 6480) { + return 4; + } + if (chip < 14712) { + return 5; + } + return 6; +} + +// TODO: To be improved using geometry tools +double ITSBeamBackgroundStudy::ChipToPhi(int chip) +{ + int staveinlayer = (int)(chip / 9); + for (int il = 0; il < ChipToLayer(chip); il++) { + staveinlayer -= NStaves[il]; + } + return 2. * TMath::Pi() * (0.5 + staveinlayer) / NStaves[ChipToLayer(chip)]; +} + +bool ITSBeamBackgroundStudy::searchBCfromMap(std::map>& BCperorbit, long its_orbit, int its_bc) +{ + auto it = BCperorbit.find(its_orbit); + if (it == BCperorbit.end()) { + return false; + } + + for (auto bc : it->second) { + if ((bc / mStrobe) == (its_bc / mStrobe)) { + return true; + } + } + return false; +} + +void ITSBeamBackgroundStudy::getClusterPatterns(gsl::span& ITSclus, gsl::span& ITSpatt, const o2::itsmft::TopologyDictionary& mdict) +{ + mPatterns.clear(); + mPatterns.reserve(ITSclus.size()); + auto pattIt = ITSpatt.begin(); + + for (unsigned int iClus{0}; iClus < ITSclus.size(); ++iClus) { + auto& clus = ITSclus[iClus]; + + auto pattID = clus.getPatternID(); + o2::itsmft::ClusterPattern patt; + + if (pattID == o2::itsmft::CompCluster::InvalidPatternID || mdict.isGroup(pattID)) { + patt.acquirePattern(pattIt); + } else { + patt = mdict.getPattern(pattID); + } + + mPatterns.push_back(patt); + } +} + +// getter +DataProcessorSpec getITSBeamBackgroundStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC) +{ + + // std::cout<<"DEBBUG track and clus masks "< outputs; + auto dataRequest = std::make_shared(); + dataRequest->requestClusters(srcClustersMask, useMC); + // dataRequest->requestTracks(GTrackID::getSourcesMask("ZDC"), useMC); + + dataRequest->requestTracks(srcTracksMask, useMC); + + auto ggRequest = std::make_shared(false, // orbitResetTime + true, // GRPECS=true + false, // GRPLHCIF + false, // GRPMagField + false, // askMatLUT + o2::base::GRPGeomRequest::Aligned, // geometry + dataRequest->inputs, + true); + return DataProcessorSpec{ + "its-beambkg-study", + dataRequest->inputs, + outputs, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, useMC)}, + Options{}}; +} + +} // namespace o2::its::study diff --git a/Detectors/ITSMFT/ITS/postprocessing/studies/src/ImpactParameter.cxx b/Detectors/ITSMFT/ITS/postprocessing/studies/src/ImpactParameter.cxx index c0aaabddaca1b..bc8b931190ed1 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/src/ImpactParameter.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/src/ImpactParameter.cxx @@ -29,7 +29,7 @@ #include "CommonUtils/TreeStreamRedirector.h" #include "DetectorsBase/GRPGeomHelper.h" #include "DataFormatsParameters/GRPECSObject.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsCommonDataFormats/DetID.h" #include "Framework/DeviceSpec.h" #include "CommonUtils/ConfigurableParam.h" diff --git a/Detectors/ITSMFT/ITS/postprocessing/workflow/standalone-postprocessing-workflow.cxx b/Detectors/ITSMFT/ITS/postprocessing/workflow/standalone-postprocessing-workflow.cxx index 30fb39c77f235..405e80475bd25 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/workflow/standalone-postprocessing-workflow.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/workflow/standalone-postprocessing-workflow.cxx @@ -16,12 +16,14 @@ #include "Framework/CompletionPolicyHelpers.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "DetectorsRaw/HBFUtilsInitializer.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" // Include studies hereafter #include "ITSStudies/ImpactParameter.h" #include "ITSStudies/AvgClusSize.h" #include "ITSStudies/PIDStudy.h" #include "ITSStudies/AnomalyStudy.h" +#include "ITSStudies/ITSBeamBackgroundStudy.h" #include "ITSStudies/Efficiency.h" #include "ITSStudies/TrackCheck.h" #include "ITSStudies/TrackExtension.h" @@ -42,7 +44,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ {"input-from-upstream", VariantType::Bool, false, {"read clusters from the clusterer"}}, - {"track-sources", VariantType::String, std::string{"ITS,ITS-TPC-TRD-TOF,ITS-TPC-TOF,ITS-TPC,ITS-TPC-TRD"}, {"comma-separated list of track sources to use"}}, + {"track-sources", VariantType::String, std::string{"ITS,ITS-TPC-TRD-TOF,ITS-TPC-TOF,ITS-TPC,ITS-TPC-TRD,ZDC"}, {"comma-separated list of track sources to use"}}, {"cluster-sources", VariantType::String, std::string{"ITS"}, {"comma-separated list of cluster sources to use"}}, {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, {"disable-mc", VariantType::Bool, false, {"disable MC propagation even if available"}}, @@ -51,9 +53,11 @@ void customize(std::vector& workflowOptions) {"track-study", VariantType::Bool, false, {"Perform the track study"}}, {"impact-parameter-study", VariantType::Bool, false, {"Perform the impact parameter study"}}, {"anomaly-study", VariantType::Bool, false, {"Perform the anomaly study"}}, + {"its-beambkg-study", VariantType::Bool, false, {"Perform the ITS beam background study"}}, {"track-extension-study", VariantType::Bool, false, {"Perform the track extension study"}}, {"efficiency-study", VariantType::Bool, false, {"Perform the efficiency study"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); // o2::raw::HBFUtilsInitializer::addConfigOption(options, "o2_tfidinfo.root"); std::swap(workflowOptions, options); } @@ -112,6 +116,17 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) } specs.emplace_back(o2::its::study::getAnomalyStudy(srcCls, useMC)); } + if (configcontext.options().get("its-beambkg-study")) { + anyStudy = true; + + srcCls = GID::getSourcesMask(configcontext.options().get("cluster-sources")); + srcTrc = GID::getSourcesMask(configcontext.options().get("track-sources")); + + 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)); + } if (configcontext.options().get("track-extension-study")) { if (!useMC) { LOGP(fatal, "Track Extension Study needs MC!"); @@ -135,8 +150,8 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) LOGP(info, "No study selected, dryrunning"); } - o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); - // write the configuration used for the studies workflow + // o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); + // write the configuration used for the studies workflow o2::conf::ConfigurableParam::writeINI("o2_its_standalone_configuration.ini"); return std::move(specs); diff --git a/Detectors/ITSMFT/ITS/reconstruction/CMakeLists.txt b/Detectors/ITSMFT/ITS/reconstruction/CMakeLists.txt index d2126be1da2c6..be42015b95795 100644 --- a/Detectors/ITSMFT/ITS/reconstruction/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/reconstruction/CMakeLists.txt @@ -11,8 +11,6 @@ o2_add_library(ITSReconstruction SOURCES src/RecoGeomHelper.cxx - src/FastMultEstConfig.cxx - src/FastMultEst.cxx PUBLIC_LINK_LIBRARIES O2::ITSBase O2::ITSMFTReconstruction O2::DataFormatsITS @@ -20,6 +18,4 @@ o2_add_library(ITSReconstruction o2_target_root_dictionary( ITSReconstruction - HEADERS include/ITSReconstruction/RecoGeomHelper.h - include/ITSReconstruction/FastMultEst.h - include/ITSReconstruction/FastMultEstConfig.h) + HEADERS include/ITSReconstruction/RecoGeomHelper.h) diff --git a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/FastMultEst.h b/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/FastMultEst.h deleted file mode 100644 index 9e8299e89b404..0000000000000 --- a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/FastMultEst.h +++ /dev/null @@ -1,70 +0,0 @@ -// 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 FastMultEst.h -/// \brief Fast multiplicity estimator for ITS -/// \author ruben.shahoyan@cern.ch - -#ifndef ALICEO2_ITS_FASTMULTEST_ -#define ALICEO2_ITS_FASTMULTEST_ - -#include "ITSMFTReconstruction/ChipMappingITS.h" -#include "DataFormatsITSMFT/ROFRecord.h" -#include "DataFormatsITSMFT/CompCluster.h" -#include -#include "ITSReconstruction/FastMultEstConfig.h" -#include -#include - -namespace o2 -{ -namespace its -{ - -struct FastMultEst { - - static constexpr int NLayers = o2::itsmft::ChipMappingITS::NLayers; - - float mult = 0.; /// estimated signal clusters multipliciy at reference (1st?) layer - float noisePerChip = 0.; /// estimated or imposed noise per chip - float cov[3] = {0.}; /// covariance matrix of estimation - float chi2 = 0.; /// chi2 - int nLayersUsed = 0; /// number of layers actually used - uint32_t lastRandomSeed = 0; /// state of the gRandom before - - std::array nClPerLayer{0}; // measured N Cl per layer selectROFs - FastMultEst(); - - static uint32_t getCurrentRandomSeed(); - int selectROFs(const gsl::span rofs, const gsl::span clus, - const gsl::span trig, std::vector& sel); - - void fillNClPerLayer(const gsl::span& clusters); - float process(const std::array ncl) - { - return FastMultEstConfig::Instance().imposeNoisePerChip > 0 ? processNoiseImposed(ncl) : processNoiseFree(ncl); - } - float processNoiseFree(const std::array ncl); - float processNoiseImposed(const std::array ncl); - float process(const gsl::span& clusters) - { - fillNClPerLayer(clusters); - return process(nClPerLayer); - } - static bool sSeedSet; - - ClassDefNV(FastMultEst, 1); -}; - -} // namespace its -} // namespace o2 - -#endif diff --git a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/TrivialVertexer.h b/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/TrivialVertexer.h deleted file mode 100644 index 3eb218dc973f6..0000000000000 --- a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/TrivialVertexer.h +++ /dev/null @@ -1,70 +0,0 @@ -// 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 TrivialVertexer.h -/// \brief Definition of the ITS trivial vertex finder -#ifndef ALICEO2_ITS_TRIVIALVERTEXER_H -#define ALICEO2_ITS_TRIVIALVERTEXER_H - -#include - -#include "Rtypes.h" // for TrivialVertexer::Class, Double_t, ClassDef, etc - -class TFile; -class TTree; -class FairMCEventHeader; - -namespace o2 -{ -namespace itsmft -{ -class Cluster; -} -} // namespace o2 - -namespace o2 -{ -class MCCompLabel; -namespace dataformats -{ -template -class MCTruthContainer; -} -namespace its -{ -class TrivialVertexer -{ - using Cluster = o2::itsmft::Cluster; - using Label = o2::MCCompLabel; - - public: - TrivialVertexer(); - ~TrivialVertexer(); - - TrivialVertexer(const TrivialVertexer&) = delete; - TrivialVertexer& operator=(const TrivialVertexer&) = delete; - - Bool_t openInputFile(const Char_t*); - - void process(const std::vector& clusters, std::vector>& vertices); - void setMCTruthContainer(const o2::dataformats::MCTruthContainer* truth) { mClsLabels = truth; } - - private: - const o2::dataformats::MCTruthContainer* mClsLabels = nullptr; // Cluster MC labels - - TFile* mFile = nullptr; - TTree* mTree = nullptr; - FairMCEventHeader* mHeader = nullptr; -}; -} // namespace its -} // namespace o2 - -#endif /* ALICEO2_ITS_TRIVIALVERTEXER_H */ diff --git a/Detectors/ITSMFT/ITS/reconstruction/src/FastMultEst.cxx b/Detectors/ITSMFT/ITS/reconstruction/src/FastMultEst.cxx deleted file mode 100644 index c547996c6f356..0000000000000 --- a/Detectors/ITSMFT/ITS/reconstruction/src/FastMultEst.cxx +++ /dev/null @@ -1,189 +0,0 @@ -// 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 FastMultEst.h -/// \brief Fast multiplicity estimator for ITS -/// \author ruben.shahoyan@cern.ch - -#include "ITSReconstruction/FastMultEst.h" -#include "ITSMFTBase/DPLAlpideParam.h" -#include "Framework/Logger.h" -#include -#include -#include - -using namespace o2::its; - -bool FastMultEst::sSeedSet = false; - -///______________________________________________________ -FastMultEst::FastMultEst() -{ - if (!sSeedSet && FastMultEstConfig::Instance().cutRandomFraction > 0.f) { - sSeedSet = true; - if (FastMultEstConfig::Instance().randomSeed > 0) { - gRandom->SetSeed(FastMultEstConfig::Instance().randomSeed); - } else if (FastMultEstConfig::Instance().randomSeed < 0) { - gRandom->SetSeed(std::time(nullptr) % 0xffff); - } - } -} - -///______________________________________________________ -/// find multiplicity for given set of clusters -void FastMultEst::fillNClPerLayer(const gsl::span& clusters) -{ - int lr = FastMultEst::NLayers - 1, nchAcc = o2::itsmft::ChipMappingITS::getNChips() - o2::itsmft::ChipMappingITS::getNChipsPerLr(lr); - std::memset(&nClPerLayer[0], 0, sizeof(int) * FastMultEst::NLayers); - for (int i = clusters.size(); i--;) { // profit from clusters being ordered in chip increasing order - while (clusters[i].getSensorID() < nchAcc) { - assert(lr >= 0); - nchAcc -= o2::itsmft::ChipMappingITS::getNChipsPerLr(--lr); - } - nClPerLayer[lr]++; - } -} - -///______________________________________________________ -/// find multiplicity for given number of clusters per layer -float FastMultEst::processNoiseFree(const std::array ncl) -{ - // we assume that on the used layers the observed number of clusters is defined by the - // the noise ~ nu * Nchips and contribution from the signal tracks Ntr*mAccCorr - const auto& conf = FastMultEstConfig::Instance(); - - float mat[3] = {0}, b[2] = {0}; - nLayersUsed = 0; - for (int il = conf.firstLayer; il <= conf.lastLayer; il++) { - if (ncl[il] > 0) { - int nch = o2::itsmft::ChipMappingITS::getNChipsPerLr(il); - float err2i = 1. / ncl[il]; - float m2n = nch * err2i; - mat[0] += err2i * conf.accCorr[il] * conf.accCorr[il]; - mat[2] += nch * m2n; - mat[1] += conf.accCorr[il] * m2n; // non-diagonal element - b[0] += conf.accCorr[il]; - b[1] += nch; - nLayersUsed++; - } - } - mult = noisePerChip = chi2 = -1; - float det = mat[0] * mat[2] - mat[1] * mat[1]; - if (nLayersUsed < 2 || std::abs(det) < 1e-15) { - return -1; - } - float detI = 1. / det; - mult = detI * (b[0] * mat[2] - b[1] * mat[1]); - noisePerChip = detI * (b[1] * mat[0] - b[0] * mat[1]); - cov[0] = mat[2] * detI; - cov[2] = mat[0] * detI; - cov[1] = -mat[1] * detI; - chi2 = 0.; - for (int il = conf.firstLayer; il <= conf.lastLayer; il++) { - if (ncl[il] > 0) { - int nch = o2::itsmft::ChipMappingITS::getNChipsPerLr(il); - float diff = mult * conf.accCorr[il] + nch * noisePerChip - ncl[il]; - chi2 += diff * diff / ncl[il]; - } - } - chi2 = nLayersUsed > 2 ? chi2 / (nLayersUsed - 2) : 0.; - return mult > 0 ? mult : 0; -} - -///______________________________________________________ -/// find multiplicity for given number of clusters per layer with mean noise imposed -float FastMultEst::processNoiseImposed(const std::array ncl) -{ - // we assume that on the used layers the observed number of clusters is defined by the - // the noise ~ nu * Nchips and contribution from the signal tracks Ntr*conf.accCorr - // - const auto& conf = FastMultEstConfig::Instance(); - float accSum = 0., accWSum = 0., noiseSum = 0.; - nLayersUsed = 0; - for (int il = conf.firstLayer; il <= conf.lastLayer; il++) { - if (ncl[il] > 0) { - float err = 1. / ncl[il]; - accSum += conf.accCorr[il]; - accWSum += conf.accCorr[il] * conf.accCorr[il] * err; - noiseSum += o2::itsmft::ChipMappingITS::getNChipsPerLr(il) * conf.accCorr[il] * err; - nLayersUsed++; - } - } - mult = 0; - if (nLayersUsed) { - mult = (accSum - noisePerChip * noiseSum) / accWSum; - } - return mult; -} - -int FastMultEst::selectROFs(const gsl::span rofs, const gsl::span clus, - const gsl::span trig, std::vector& sel) -{ - int nrof = rofs.size(), nsel = 0; - const auto& multEstConf = FastMultEstConfig::Instance(); // parameters for mult estimation and cuts - sel.clear(); - sel.resize(nrof, true); // by default select all - lastRandomSeed = gRandom->GetSeed(); - if (multEstConf.isMultCutRequested()) { - for (uint32_t irof = 0; irof < nrof; irof++) { - nsel += sel[irof] = multEstConf.isPassingMultCut(process(rofs[irof].getROFData(clus))); - } - } else { - nsel = nrof; - } - using IdNT = std::pair; - if (multEstConf.cutRandomFraction > 0.) { - int ntrig = trig.size(), currTrig = 0; - if (multEstConf.preferTriggered) { - const auto& alpParams = o2::itsmft::DPLAlpideParam::Instance(); - std::vector nTrigROF; - nTrigROF.reserve(nrof); - for (uint32_t irof = 0; irof < nrof; irof++) { - if (sel[irof]) { - if (nsel && gRandom->Rndm() < multEstConf.cutRandomFraction) { - nsel--; - } - auto irROF = rofs[irof].getBCData(); - while (currTrig < ntrig && trig[currTrig].ir < irROF) { // triggers are sorted, jump to 1st one not less than current ROF - currTrig++; - } - auto& trof = nTrigROF.emplace_back(irof, 0); - irROF += alpParams.roFrameLengthInBC; - while (currTrig < ntrig && trig[currTrig].ir < irROF) { - trof.second++; - currTrig++; - } - } - } - if (nsel > 0) { - sort(nTrigROF.begin(), nTrigROF.end(), [](const IdNT& a, const IdNT& b) { return a.second > b.second; }); // order in number of triggers - auto last = nTrigROF.begin() + nsel; - sort(nTrigROF.begin(), last, [](const IdNT& a, const IdNT& b) { return a.first < b.first; }); // order in ROF ID first nsel ROFs - } - for (int i = nsel; i < int(nTrigROF.size()); i++) { // reject ROFs in the tail - sel[nTrigROF[i].first] = false; - } - } else { // dummy random rejection - for (int irof = 0; irof < nrof; irof++) { - if (sel[irof]) { - float sr = gRandom->Rndm(); - if (gRandom->Rndm() < multEstConf.cutRandomFraction) { - sel[irof] = false; - nsel--; - } - } - } - } - } - LOGP(debug, "NSel = {} of {} rofs Seeds: before {} after {}", nsel, nrof, lastRandomSeed, gRandom->GetSeed()); - - return nsel; -} diff --git a/Detectors/ITSMFT/ITS/reconstruction/src/ITSReconstructionLinkDef.h b/Detectors/ITSMFT/ITS/reconstruction/src/ITSReconstructionLinkDef.h index 67622303fc840..3bc8ee0f5403b 100644 --- a/Detectors/ITSMFT/ITS/reconstruction/src/ITSReconstructionLinkDef.h +++ b/Detectors/ITSMFT/ITS/reconstruction/src/ITSReconstructionLinkDef.h @@ -16,8 +16,5 @@ #pragma link off all functions; #pragma link C++ class o2::its::RecoGeomHelper + ; -#pragma link C++ class o2::its::FastMultEst + ; -#pragma link C++ class o2::its::FastMultEstConfig + ; -#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::its::FastMultEstConfig> + ; #endif diff --git a/Detectors/ITSMFT/ITS/reconstruction/src/TrivialVertexer.cxx b/Detectors/ITSMFT/ITS/reconstruction/src/TrivialVertexer.cxx deleted file mode 100644 index cb7f1eeacb02e..0000000000000 --- a/Detectors/ITSMFT/ITS/reconstruction/src/TrivialVertexer.cxx +++ /dev/null @@ -1,108 +0,0 @@ -// 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 TrivialVertexer.cxx -/// \brief Implementation of the ITS trivial vertex finder - -#include - -#include "TFile.h" -#include "TTree.h" - -#include "FairMCEventHeader.h" -#include - -#include "ITSReconstruction/TrivialVertexer.h" -#include "DataFormatsITSMFT/CompCluster.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/MCTruthContainer.h" - -using namespace o2::itsmft; -using namespace o2::its; - -using Point3Df = o2::math_utils::Point3D; - -TrivialVertexer::TrivialVertexer() = default; - -TrivialVertexer::~TrivialVertexer() -{ - if (mHeader) - delete mHeader; - if (mTree) - delete mTree; - if (mFile) - delete mFile; -} - -Bool_t TrivialVertexer::openInputFile(const Char_t* fname) -{ - mFile = TFile::Open(fname, "old"); - if (!mFile) { - LOG(error) << "TrivialVertexer::openInputFile() : " - << "Cannot open the input file !"; - return kFALSE; - } - mTree = (TTree*)mFile->Get("o2sim"); - if (!mTree) { - LOG(error) << "TrivialVertexer::openInputFile() : " - << "Cannot get the input tree !"; - return kFALSE; - } - Int_t rc = mTree->SetBranchAddress("MCEventHeader.", &mHeader); - if (rc != 0) { - LOG(error) << "TrivialVertexer::openInputFile() : " - << "Cannot get the input branch ! rc=" << rc; - return kFALSE; - } - return kTRUE; -} - -void TrivialVertexer::process(const std::vector& clusters, std::vector>& vertices) -{ - if (mClsLabels == nullptr) { - LOG(info) << "TrivialVertexer::process() : " - << "No cluster labels available ! Running with a default MC vertex..."; - vertices.emplace_back(std::array{0., 0., 0.}); - return; - } - - if (mTree == nullptr) { - LOG(info) << "TrivialVertexer::process() : " - << "No MC information available ! Running with a default MC vertex..."; - vertices.emplace_back(std::array{0., 0., 0.}); - return; - } - - Int_t lastEventID = 0; - Int_t firstEventID = std::numeric_limits::max(); - - // Find the first and last MC event within this TF - for (Int_t i = 0; i < clusters.size(); ++i) { - auto mclab = (mClsLabels->getLabels(i))[0]; - if (mclab.getTrackID() == -1) - continue; // noise - auto id = mclab.getEventID(); - if (id < firstEventID) - firstEventID = id; - if (id > lastEventID) - lastEventID = id; - } - - for (Int_t mcEv = firstEventID; mcEv <= lastEventID; ++mcEv) { - mTree->GetEvent(mcEv); - Double_t vx = mHeader->GetX(); - Double_t vy = mHeader->GetY(); - Double_t vz = mHeader->GetZ(); - vertices.emplace_back(std::array{vx, vy, vz}); - LOG(info) << "TrivialVertexer::process() : " - << "MC event #" << mcEv << " with vertex (" << vx << ',' << vy << ',' << vz << ')'; - } -} diff --git a/Detectors/ITSMFT/ITS/tracking/CMakeLists.txt b/Detectors/ITSMFT/ITS/tracking/CMakeLists.txt index 001ee537f50d2..1dd64b6f1874b 100644 --- a/Detectors/ITSMFT/ITS/tracking/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/tracking/CMakeLists.txt @@ -14,15 +14,16 @@ o2_add_library(ITStracking SOURCES src/ClusterLines.cxx src/Cluster.cxx src/Configuration.cxx + src/FastMultEstConfig.cxx + src/FastMultEst.cxx + src/LineVertexerHelpers.cxx src/TimeFrame.cxx src/IOUtils.cxx src/Tracker.cxx src/TrackerTraits.cxx src/TrackingConfigParam.cxx - src/ClusterLines.cxx src/Vertexer.cxx src/VertexerTraits.cxx - src/Smoother.cxx PUBLIC_LINK_LIBRARIES O2::GPUCommon Microsoft.GSL::GSL @@ -30,6 +31,7 @@ o2_add_library(ITStracking O2::DataFormatsITSMFT O2::SimulationDataFormat O2::ITSBase + O2::CommonUtils O2::ITSReconstruction O2::ITSMFTReconstruction O2::DataFormatsITS @@ -50,6 +52,8 @@ o2_target_root_dictionary(ITStracking HEADERS include/ITStracking/ClusterLines.h include/ITStracking/Tracklet.h include/ITStracking/Cluster.h + include/ITStracking/Definitions.h + include/ITStracking/FastMultEstConfig.h include/ITStracking/TrackingConfigParam.h LINKDEF src/TrackingLinkDef.h) diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/ClusterLinesGPU.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/ClusterLinesGPU.h deleted file mode 100644 index 75d75e0f67700..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/ClusterLinesGPU.h +++ /dev/null @@ -1,73 +0,0 @@ -// 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 ClusterLinesGPU.h -/// \brief GPU-compliant version of ClusterLines, for the moment separated, might create a common traits for ClusterLines + later specifications for each arch, later. - -#ifndef ITSTRACKINGGPU_CLUSTERLINESGPU_H_ -#define ITSTRACKINGGPU_CLUSTERLINESGPU_H_ - -#include "GPUCommonDef.h" -#include /// Required to properly compile MathUtils -#include "ITStracking/ClusterLines.h" - -namespace o2 -{ -namespace its -{ -namespace gpu -{ - -struct GPUVertex final { - GPUhd() GPUVertex() : realVertex{false} - { - } - - GPUhd() GPUVertex(float x, float y, float z, float eX, float eY, float eZ, int contrib) : xCoord{x}, - yCoord{y}, - zCoord{z}, - errorX{eZ}, - errorY{eY}, - errorZ{eZ}, - contributors{contrib}, - realVertex{true} - { - } - float xCoord; - float yCoord; - float zCoord; - float errorX; - float errorY; - float errorZ; - int contributors; - int timeStamp; - unsigned char realVertex; -}; - -class ClusterLinesGPU final -{ - public: - GPUd() ClusterLinesGPU(const Line& firstLine, const Line& secondLine); // poor man solution to calculate duplets' centroid - GPUd() void computeClusterCentroid(); - GPUdi() float* getVertex() { return mVertex; } - - private: - float mAMatrix[6]; // AX=B - float mBMatrix[3]; // AX=B - float mVertexCandidate[3]; // vertex candidate - float mWeightMatrix[9]; // weight matrix - float mVertex[3]; // cluster centroid position -}; - -} // namespace gpu -} // namespace its -} // namespace o2 -#endif \ No newline at end of file diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameChunk.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameChunk.h deleted file mode 100644 index 4a028bf12eb40..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameChunk.h +++ /dev/null @@ -1,148 +0,0 @@ -// 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. -/// - -#ifndef TRACKINGITSGPU_INCLUDE_TIMEFRAMECHUNKGPU_H -#define TRACKINGITSGPU_INCLUDE_TIMEFRAMECHUNKGPU_H - -#include "ITStracking/Configuration.h" -#include "ITStracking/TimeFrame.h" - -#include "ITStrackingGPU/ClusterLinesGPU.h" -#include "ITStrackingGPU/Stream.h" - -#include - -namespace o2::its::gpu -{ -template -struct StaticTrackingParameters { - StaticTrackingParameters& operator=(const StaticTrackingParameters& t) = default; - void set(const TrackingParameters& pars) - { - ClusterSharing = pars.ClusterSharing; - MinTrackLength = pars.MinTrackLength; - NSigmaCut = pars.NSigmaCut; - PVres = pars.PVres; - DeltaROF = pars.DeltaROF; - ZBins = pars.ZBins; - PhiBins = pars.PhiBins; - CellDeltaTanLambdaSigma = pars.CellDeltaTanLambdaSigma; - } - - /// General parameters - int ClusterSharing = 0; - int MinTrackLength = nLayers; - float NSigmaCut = 5; - float PVres = 1.e-2f; - int DeltaROF = 0; - int ZBins{256}; - int PhiBins{128}; - - /// Cell finding cuts - float CellDeltaTanLambdaSigma = 0.007f; -}; - -template -class GpuTimeFrameChunk -{ - public: - static size_t computeScalingSizeBytes(const int, const TimeFrameGPUParameters&); - static size_t computeFixedSizeBytes(const TimeFrameGPUParameters&); - static size_t computeRofPerChunk(const TimeFrameGPUParameters&, const size_t); - - GpuTimeFrameChunk() = delete; - GpuTimeFrameChunk(o2::its::TimeFrame* tf, TimeFrameGPUParameters& conf) - { - mTimeFramePtr = tf; - mTFGPUParams = &conf; - } - ~GpuTimeFrameChunk(); - - /// Most relevant operations - void allocate(const size_t, Stream&); - void reset(const Task, Stream&); - size_t loadDataOnDevice(const size_t, const size_t, const int, Stream&); - - /// Interface - Cluster* getDeviceClusters(const int); - int* getDeviceClusterExternalIndices(const int); - int* getDeviceIndexTables(const int); - Tracklet* getDeviceTracklets(const int); - int* getDeviceTrackletsLookupTables(const int); - CellSeed* getDeviceCells(const int); - int* getDeviceCellsLookupTables(const int); - int* getDeviceRoadsLookupTables(const int); - TimeFrameGPUParameters* getTimeFrameGPUParameters() const { return mTFGPUParams; } - - int* getDeviceCUBTmpBuffer() { return mCUBTmpBufferDevice; } - int* getDeviceFoundTracklets() { return mFoundTrackletsDevice; } - int* getDeviceNFoundCells() { return mNFoundCellsDevice; } - int* getDeviceCellNeigboursLookupTables(const int); - int* getDeviceCellNeighbours(const int); - CellSeed** getDeviceArrayCells() const { return mCellsDeviceArray; } - int** getDeviceArrayNeighboursCell() const { return mNeighboursCellDeviceArray; } - int** getDeviceArrayNeighboursCellLUT() const { return mNeighboursCellLookupTablesDeviceArray; } - - /// Vertexer only - int* getDeviceNTrackletCluster(const int combid) { return mNTrackletsPerClusterDevice[combid]; } - Line* getDeviceLines() { return mLinesDevice; }; - int* getDeviceNFoundLines() { return mNFoundLinesDevice; } - int* getDeviceNExclusiveFoundLines() { return mNExclusiveFoundLinesDevice; } - unsigned char* getDeviceUsedTracklets() { return mUsedTrackletsDevice; } - int* getDeviceClusteredLines() { return mClusteredLinesDevice; } - size_t getNPopulatedRof() const { return mNPopulatedRof; } - - private: - /// Host - std::array, nLayers> mHostClusters; - std::array, nLayers> mHostIndexTables; - - /// Device - std::array mClustersDevice; - std::array mClusterExternalIndicesDevice; - std::array mIndexTablesDevice; - std::array mTrackletsDevice; - std::array mTrackletsLookupTablesDevice; - std::array mCellsDevice; - // Road* mRoadsDevice; - std::array mCellsLookupTablesDevice; - std::array mNeighboursCellDevice; - std::array mNeighboursCellLookupTablesDevice; - std::array mRoadsLookupTablesDevice; - - // These are to make them accessible using layer index - CellSeed** mCellsDeviceArray; - int** mNeighboursCellDeviceArray; - int** mNeighboursCellLookupTablesDeviceArray; - - // Small accessory buffers - int* mCUBTmpBufferDevice; - int* mFoundTrackletsDevice; - int* mNFoundCellsDevice; - - /// Vertexer only - Line* mLinesDevice; - int* mNFoundLinesDevice; - int* mNExclusiveFoundLinesDevice; - unsigned char* mUsedTrackletsDevice; - std::array mNTrackletsPerClusterDevice; - int* mClusteredLinesDevice; - - /// State and configuration - bool mAllocated = false; - size_t mNRof = 0; - size_t mNPopulatedRof = 0; - o2::its::TimeFrame* mTimeFramePtr = nullptr; - TimeFrameGPUParameters* mTFGPUParams = nullptr; -}; -} // namespace o2::its::gpu -#endif \ No newline at end of file diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h index d6d87eb8c1143..5f56e3f272473 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h @@ -24,75 +24,78 @@ namespace o2::its::gpu { -template -class TimeFrameGPU final : public TimeFrame +template +class TimeFrameGPU : public TimeFrame { - using typename TimeFrame::CellSeedN; - using typename TimeFrame::IndexTableUtilsN; + using typename TimeFrame::IndexTableUtilsN; + using typename TimeFrame::ROFOverlapTableN; + using typename TimeFrame::ROFVertexLookupTableN; + using typename TimeFrame::ROFMaskTableN; + using typename TimeFrame::TrackingTopologyN; + using typename TimeFrame::TrackSeedN; + static constexpr int MaxTransitions = TrackingTopologyN::MaxTransitions; + static constexpr int MaxCells = TrackingTopologyN::MaxCells; + static constexpr int MaxStreams = MaxCells > NLayers ? MaxCells : NLayers; public: TimeFrameGPU() = default; - ~TimeFrameGPU() = default; + ~TimeFrameGPU() override = default; /// Most relevant operations void pushMemoryStack(const int); void popMemoryStack(const int); void registerHostMemory(const int); void unregisterHostMemory(const int); - void initialise(const int, const TrackingParameters&, const int, IndexTableUtilsN* utils = nullptr, const TimeFrameGPUParameters* pars = nullptr); - void initDevice(IndexTableUtilsN*, const TrackingParameters& trkParam, const TimeFrameGPUParameters&, const int, const int); - void initDeviceSAFitting(); - void loadIndexTableUtils(const int); - void loadTrackingFrameInfoDevice(const int, const int); - void createTrackingFrameInfoDeviceArray(const int); - void loadUnsortedClustersDevice(const int, const int); - void createUnsortedClustersDeviceArray(const int, const int = nLayers); - void loadClustersDevice(const int, const int); - void createClustersDeviceArray(const int, const int = nLayers); - void loadClustersIndexTables(const int, const int); - void createClustersIndexTablesArray(const int); - void createUsedClustersDevice(const int, const int); - void createUsedClustersDeviceArray(const int, const int = nLayers); + void initialise(const TrackingParameters&, int maxLayers); + void initialise(const TrackingParameters&, int maxLayers, int iteration); + void loadIndexTableUtils(); + void loadTrackingTopologies(); + void loadTrackingFrameInfoDevice(const int); + void createTrackingFrameInfoDeviceArray(); + void loadUnsortedClustersDevice(const int); + void createUnsortedClustersDeviceArray(const int = NLayers); + void loadClustersDevice(const int); + void createClustersDeviceArray(const int = NLayers); + void loadClustersIndexTables(const int); + void createClustersIndexTablesArray(); + void createUsedClustersDevice(const int); + void createUsedClustersDeviceArray(const int = NLayers); void loadUsedClustersDevice(); - void loadROFrameClustersDevice(const int, const int); - void createROFrameClustersDeviceArray(const int); - void loadMultiplicityCutMask(const int); - void loadVertices(const int); + void loadROFrameClustersDevice(const int); + void createROFrameClustersDeviceArray(); + void loadROFCutMask(const int); + void loadVertices(); + void loadROFOverlapTable(); + void loadROFVertexLookupTable(); + void updateROFVertexLookupTable(); /// - void createTrackletsLUTDevice(const int, const int); - void createTrackletsLUTDeviceArray(const int); + void createTrackletsLUTDevice(bool, const int); + void createTrackletsLUTDeviceArray(); void loadTrackletsDevice(); void loadTrackletsLUTDevice(); void loadCellsDevice(); void loadCellsLUTDevice(); void loadTrackSeedsDevice(); void loadTrackSeedsChi2Device(); - void loadRoadsDevice(); - void loadTrackSeedsDevice(bounded_vector&); + void loadTrackSeedsDevice(bounded_vector&); void createTrackletsBuffers(const int); - void createTrackletsBuffersArray(const int); + void createTrackletsBuffersArray(); void createCellsBuffers(const int); - void createCellsBuffersArray(const int); + void createCellsBuffersArray(); void createCellsDevice(); void createCellsLUTDevice(const int); - void createCellsLUTDeviceArray(const int); + void createCellsLUTDeviceArray(); void createNeighboursIndexTablesDevice(const int); void createNeighboursDevice(const unsigned int layer); void createNeighboursLUTDevice(const int, const unsigned int); void createTrackITSExtDevice(const size_t); void downloadTrackITSExtDevice(); - void downloadCellsNeighboursDevice(std::vector>>&, const int); + void downloadCellsNeighboursDevice(std::vector>&, const int); void downloadNeighboursLUTDevice(bounded_vector&, const int); void downloadCellsDevice(); void downloadCellsLUTDevice(); - /// Vertexer - void createVtxTrackletsLUTDevice(const int32_t); - void createVtxTrackletsBuffers(const int32_t); - void createVtxLinesLUTDevice(const int32_t); - void createVtxLinesBuffer(const int32_t); - /// synchronization auto& getStream(const size_t stream) { return mGpuStreams[stream]; } auto& getStreams() { return mGpuStreams; } @@ -100,16 +103,19 @@ class TimeFrameGPU final : public TimeFrame void syncStreams(const bool = true); void waitEvent(const int, const int); void recordEvent(const int); - void recordEvents(const int = 0, const int = nLayers); + void recordEvents(const int = 0, const int = NLayers); /// cleanup virtual void wipe() final; /// interface virtual bool isGPU() const noexcept final { return true; } - virtual const char* getName() const noexcept { return "GPU"; } - int getNClustersInRofSpan(const int, const int, const int) const; + virtual const char* getName() const noexcept override final { return "GPU"; } IndexTableUtilsN* getDeviceIndexTableUtils() { return mIndexTableUtilsDevice; } + const auto getDeviceROFOverlapTableView() { return mDeviceROFOverlapTableView; } + const auto getDeviceROFVertexLookupTableView() { return mDeviceROFVertexLookupTableView; } + const auto getDeviceROFMaskTableView() { return mDeviceROFMaskTableView; } + const auto getDeviceTrackingTopologyView() const { return mDeviceTrackingTopologyView; } int* getDeviceROFramesClusters(const int layer) { return mROFramesClustersDevice[layer]; } auto& getTrackITSExt() { return mTrackITSExt; } Vertex* getDeviceVertices() { return mPrimaryVerticesDevice; } @@ -118,14 +124,12 @@ class TimeFrameGPU final : public TimeFrame const o2::base::Propagator* getChainPropagator(); // Hybrid - Road* getDeviceRoads() { return mRoadsDevice; } TrackITSExt* getDeviceTrackITSExt() { return mTrackITSExtDevice; } int* getDeviceNeighboursLUT(const int layer) { return mNeighboursLUTDevice[layer]; } gsl::span getDeviceNeighboursLUTs() { return mNeighboursLUTDevice; } - gpuPair* getDeviceNeighbourPairs(const int layer) { return mNeighbourPairsDevice[layer]; } - std::array& getDeviceNeighboursAll() { return mNeighboursDevice; } - int* getDeviceNeighbours(const int layer) { return mNeighboursDevice[layer]; } - int** getDeviceNeighboursArray() { return mNeighboursDevice.data(); } + CellNeighbour** getDeviceArrayNeighbours() { return mNeighboursDeviceArray; } + std::array& getDeviceNeighboursAll() { return mNeighboursDevice; } + CellNeighbour* getDeviceNeighbours(const int layer) { return mNeighboursDevice[layer]; } TrackingFrameInfo* getDeviceTrackingFrameInfo(const int); const TrackingFrameInfo** getDeviceArrayTrackingFrameInfo() const { return mTrackingFrameInfoDeviceArray; } const Cluster** getDeviceArrayClusters() const { return mClustersDeviceArray; } @@ -138,136 +142,106 @@ class TimeFrameGPU final : public TimeFrame int** getDeviceArrayTrackletsLUT() const { return mTrackletsLUTDeviceArray; } int** getDeviceArrayCellsLUT() const { return mCellsLUTDeviceArray; } int** getDeviceArrayNeighboursCellLUT() const { return mNeighboursCellLUTDeviceArray; } - CellSeedN** getDeviceArrayCells() { return mCellsDeviceArray; } - CellSeedN* getDeviceTrackSeeds() { return mTrackSeedsDevice; } + CellSeed** getDeviceArrayCells() { return mCellsDeviceArray; } + TrackSeedN* getDeviceTrackSeeds() { return mTrackSeedsDevice; } int* getDeviceTrackSeedsLUT() { return mTrackSeedsLUTDevice; } auto getNTrackSeeds() const { return mNTracks; } o2::track::TrackParCovF** getDeviceArrayTrackSeeds() { return mCellSeedsDeviceArray; } float** getDeviceArrayTrackSeedsChi2() { return mCellSeedsChi2DeviceArray; } int* getDeviceNeighboursIndexTables(const int layer) { return mNeighboursIndexTablesDevice[layer]; } - uint8_t* getDeviceMultCutMask() { return mMultMaskDevice; } - - // Vertexer - auto& getDeviceNTrackletsPerROF() const noexcept { return mNTrackletsPerROFDevice; } - auto& getDeviceNTrackletsPerCluster() const noexcept { return mNTrackletsPerClusterDevice; } - auto& getDeviceNTrackletsPerClusterSum() const noexcept { return mNTrackletsPerClusterSumDevice; } - int32_t** getDeviceArrayNTrackletsPerROF() const noexcept { return mNTrackletsPerROFDeviceArray; } - int32_t** getDeviceArrayNTrackletsPerCluster() const noexcept { return mNTrackletsPerClusterDeviceArray; } - int32_t** getDeviceArrayNTrackletsPerClusterSum() const noexcept { return mNTrackletsPerClusterSumDeviceArray; } - uint8_t* getDeviceUsedTracklets() const noexcept { return mUsedTrackletsDevice; } - int32_t* getDeviceNLinesPerCluster() const noexcept { return mNLinesPerClusterDevice; } - int32_t* getDeviceNLinesPerClusterSum() const noexcept { return mNLinesPerClusterSumDevice; } - Line* getDeviceLines() const noexcept { return mLinesDevice; } - gsl::span getDeviceTrackletsPerROFs() { return mNTrackletsPerROFDevice; } void setDevicePropagator(const o2::base::PropagatorImpl* p) final { this->mPropagatorDevice = p; } // Host-specific getters - gsl::span getNTracklets() { return mNTracklets; } - gsl::span getNCells() { return mNCells; } + gsl::span getNTracklets() { return {mNTracklets.data(), static_cast::size_type>(this->mTrackingTopologyView.nTransitions)}; } + gsl::span getNCells() { return {mNCells.data(), static_cast::size_type>(this->mTrackingTopologyView.nCells)}; } auto& getArrayNCells() { return mNCells; } - gsl::span getNNeighbours() { return mNNeighbours; } + gsl::span getNNeighbours() { return {mNNeighbours.data(), static_cast::size_type>(this->mTrackingTopologyView.nCells)}; } auto& getArrayNNeighbours() { return mNNeighbours; } // Host-available device getters gsl::span getDeviceTrackletsLUTs() { return mTrackletsLUTDevice; } gsl::span getDeviceCellLUTs() { return mCellsLUTDevice; } gsl::span getDeviceTracklets() { return mTrackletsDevice; } - gsl::span getDeviceCells() { return mCellsDevice; } + gsl::span getDeviceCells() { return mCellsDevice; } // Overridden getters - int getNumberOfTracklets() const final; - int getNumberOfCells() const final; - int getNumberOfNeighbours() const final; + size_t getNumberOfTracklets() const final; + size_t getNumberOfCells() const final; + size_t getNumberOfNeighbours() const final; private: void allocMemAsync(void**, size_t, Stream&, bool, int32_t = o2::gpu::GPUMemoryResource::MEMORY_GPU); // Abstract owned and unowned memory allocations on specific stream void allocMem(void**, size_t, bool, int32_t = o2::gpu::GPUMemoryResource::MEMORY_GPU); // Abstract owned and unowned memory allocations on default stream - TimeFrameGPUParameters mGpuParams; // Host-available device buffer sizes - std::array mNTracklets; - std::array mNCells; - std::array mNNeighbours; + std::array mNTracklets{}; + std::array mNCells{}; + std::array mNNeighbours{}; // Device pointers IndexTableUtilsN* mIndexTableUtilsDevice; + // device navigation views + ROFOverlapTableN::View mDeviceROFOverlapTableView; + ROFVertexLookupTableN::View mDeviceROFVertexLookupTableView; + ROFMaskTableN::View mDeviceROFMaskTableView; + std::vector mDeviceTrackerTopologyViews; + typename TrackingTopologyN::View mDeviceTrackingTopologyView; // Hybrid pref - uint8_t* mMultMaskDevice; Vertex* mPrimaryVerticesDevice; int* mROFramesPVDevice; - std::array mClustersDevice; - std::array mUnsortedClustersDevice; - std::array mClustersIndexTablesDevice; - std::array mUsedClustersDevice; - std::array mROFramesClustersDevice; + std::array mClustersDevice; + std::array mUnsortedClustersDevice; + std::array mClustersIndexTablesDevice; + std::array mUsedClustersDevice; + std::array mROFramesClustersDevice; const Cluster** mClustersDeviceArray; const Cluster** mUnsortedClustersDeviceArray; const int** mClustersIndexTablesDeviceArray; uint8_t** mUsedClustersDeviceArray; const int** mROFramesClustersDeviceArray; - std::array mTrackletsDevice; - std::array mTrackletsLUTDevice; - std::array mCellsLUTDevice; - std::array mNeighboursLUTDevice; + std::array mTrackletsDevice{}; + std::array mTrackletsLUTDevice{}; + std::array mCellsLUTDevice{}; + std::array mNeighboursLUTDevice{}; Tracklet** mTrackletsDeviceArray{nullptr}; int** mCellsLUTDeviceArray{nullptr}; - int** mNeighboursCellDeviceArray{nullptr}; int** mNeighboursCellLUTDeviceArray{nullptr}; int** mTrackletsLUTDeviceArray{nullptr}; - std::array mCellsDevice; - CellSeedN** mCellsDeviceArray; - std::array mNeighboursIndexTablesDevice; - CellSeedN* mTrackSeedsDevice{nullptr}; + std::array mCellsDevice{}; + CellSeed** mCellsDeviceArray; + std::array mNeighboursIndexTablesDevice{}; + TrackSeedN* mTrackSeedsDevice{nullptr}; int* mTrackSeedsLUTDevice{nullptr}; unsigned int mNTracks{0}; - std::array mCellSeedsDevice; + std::array mCellSeedsDevice{}; o2::track::TrackParCovF** mCellSeedsDeviceArray; - std::array mCellSeedsChi2Device; + std::array mCellSeedsChi2Device{}; float** mCellSeedsChi2DeviceArray; - Road* mRoadsDevice; TrackITSExt* mTrackITSExtDevice; - std::array*, nLayers - 2> mNeighbourPairsDevice; - std::array mNeighboursDevice; - std::array mTrackingFrameInfoDevice; + std::array mNeighboursDevice{}; + CellNeighbour** mNeighboursDeviceArray{nullptr}; + std::array mTrackingFrameInfoDevice; const TrackingFrameInfo** mTrackingFrameInfoDeviceArray; - /// Vertexer - std::array mNTrackletsPerROFDevice; - std::array mNTrackletsPerClusterDevice; - std::array mNTrackletsPerClusterSumDevice; - uint8_t* mUsedTrackletsDevice; - int32_t* mNLinesPerClusterDevice; - int32_t* mNLinesPerClusterSumDevice; - int32_t** mNTrackletsPerROFDeviceArray; - int32_t** mNTrackletsPerClusterDeviceArray; - int32_t** mNTrackletsPerClusterSumDeviceArray; - Line* mLinesDevice; - // State Streams mGpuStreams; - std::bitset mPinnedUnsortedClusters{0}; - std::bitset mPinnedClusters{0}; - std::bitset mPinnedClustersIndexTables{0}; - std::bitset mPinnedUsedClusters{0}; - std::bitset mPinnedROFramesClusters{0}; - std::bitset mPinnedTrackingFrameInfo{0}; + std::bitset mPinnedUnsortedClusters{0}; + std::bitset mPinnedClusters{0}; + std::bitset mPinnedClustersIndexTables{0}; + std::bitset mPinnedUsedClusters{0}; + std::bitset mPinnedROFramesClusters{0}; + std::bitset mPinnedTrackingFrameInfo{0}; // Temporary buffer for storing output tracks from GPU tracking bounded_vector mTrackITSExt; }; -template -inline int TimeFrameGPU::getNClustersInRofSpan(const int rofIdstart, const int rofSpanSize, const int layerId) const -{ - return static_cast(this->mROFramesClusters[layerId][(rofIdstart + rofSpanSize) < this->mROFramesClusters.size() ? rofIdstart + rofSpanSize : this->mROFramesClusters.size() - 1] - this->mROFramesClusters[layerId][rofIdstart]); -} - -template -inline std::vector TimeFrameGPU::getClusterSizes() +template +inline std::vector TimeFrameGPU::getClusterSizes() { std::vector sizes(this->mUnsortedClusters.size()); std::transform(this->mUnsortedClusters.begin(), this->mUnsortedClusters.end(), sizes.begin(), @@ -275,22 +249,22 @@ inline std::vector TimeFrameGPU::getClusterSizes() return sizes; } -template -inline int TimeFrameGPU::getNumberOfTracklets() const +template +inline size_t TimeFrameGPU::getNumberOfTracklets() const { - return std::accumulate(mNTracklets.begin(), mNTracklets.end(), 0); + return std::accumulate(mNTracklets.begin(), mNTracklets.begin() + this->mTrackingTopologyView.nTransitions, 0); } -template -inline int TimeFrameGPU::getNumberOfCells() const +template +inline size_t TimeFrameGPU::getNumberOfCells() const { - return std::accumulate(mNCells.begin(), mNCells.end(), 0); + return std::accumulate(mNCells.begin(), mNCells.begin() + this->mTrackingTopologyView.nCells, 0); } -template -inline int TimeFrameGPU::getNumberOfNeighbours() const +template +inline size_t TimeFrameGPU::getNumberOfNeighbours() const { - return std::accumulate(mNNeighbours.begin(), mNNeighbours.end(), 0); + return std::accumulate(mNNeighbours.begin(), mNNeighbours.begin() + this->mTrackingTopologyView.nCells, 0); } } // namespace o2::its::gpu diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackerTraitsGPU.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackerTraitsGPU.h index 7d26e74692aa5..81d870c5b46c2 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackerTraitsGPU.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackerTraitsGPU.h @@ -19,26 +19,23 @@ namespace o2::its { -template -class TrackerTraitsGPU final : public TrackerTraits +template +class TrackerTraitsGPU final : public TrackerTraits { - using typename TrackerTraits::IndexTableUtilsN; + using typename TrackerTraits::IndexTableUtilsN; public: TrackerTraitsGPU() = default; ~TrackerTraitsGPU() final = default; - void adoptTimeFrame(TimeFrame* tf) final; + void adoptTimeFrame(TimeFrame* tf) final; void initialiseTimeFrame(const int iteration) final; - void computeLayerTracklets(const int iteration, int, int) final; + void computeLayerTracklets(const int iteration, int) final; void computeLayerCells(const int iteration) final; void findCellsNeighbours(const int iteration) final; void findRoads(const int iteration) final; - bool supportsExtendTracks() const noexcept final { return false; } - bool supportsFindShortPrimaries() const noexcept final { return false; } - void setBz(float) final; const char* getName() const noexcept final { return "GPU"; } @@ -51,7 +48,7 @@ class TrackerTraitsGPU final : public TrackerTraits private: IndexTableUtilsN* mDeviceIndexTableUtils; - gpu::TimeFrameGPU* mTimeFrameGPU; + gpu::TimeFrameGPU* mTimeFrameGPU; }; } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h index 53992ccf3eb85..161283db2a2bc 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TrackingKernels.h @@ -16,15 +16,17 @@ #include #include "ITStracking/BoundedAllocator.h" -#include "ITStracking/Definitions.h" +#include "ITStracking/ROFLookupTables.h" +#include "ITStracking/TrackingTopology.h" #include "ITStrackingGPU/Utils.h" #include "DetectorsBase/Propagator.h" -#include "GPUCommonDef.h" namespace o2::its { -template class CellSeed; +struct CellNeighbour; +template +class TrackSeed; class TrackingFrameInfo; class Tracklet; template @@ -33,18 +35,17 @@ class Cluster; class TrackITSExt; class ExternalAllocator; -template -void countTrackletsInROFsHandler(const IndexTableUtils* utils, - const uint8_t* multMask, - const int layer, - const int startROF, - const int endROF, - const int maxROF, - const int deltaROF, +template +void countTrackletsInROFsHandler(const IndexTableUtils* utils, + const typename ROFMaskTable::View& rofMask, + const int transitionId, + const int fromLayer, + const int toLayer, + const typename ROFOverlapTable::View& rofOverlaps, + const typename ROFVertexLookupTable::View& vertexLUT, const int vertexId, const Vertex* vertices, const int* rofPV, - const int nVertices, const Cluster** clusters, std::vector nClusters, const int** ROFClusters, @@ -52,32 +53,30 @@ void countTrackletsInROFsHandler(const IndexTableUtils* utils, const int** clustersIndexTables, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const typename TrackingTopology::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, - std::array& minR, - std::array& maxR, + std::array& minR, + std::array& maxR, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Streams& streams); -template -void computeTrackletsInROFsHandler(const IndexTableUtils* utils, - const uint8_t* multMask, - const int layer, - const int startROF, - const int endROF, - const int maxROF, - const int deltaROF, +template +void computeTrackletsInROFsHandler(const IndexTableUtils* utils, + const typename ROFMaskTable::View& rofMask, + const int transitionId, + const int fromLayer, + const int toLayer, + const typename ROFOverlapTable::View& rofOverlaps, + const typename ROFVertexLookupTable::View& vertexLUT, const int vertexId, const Vertex* vertices, const int* rofPV, - const int nVertices, const Cluster** clusters, std::vector nClusters, const int** ROFClusters, @@ -88,96 +87,85 @@ void computeTrackletsInROFsHandler(const IndexTableUtils* utils, gsl::span nTracklets, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const typename TrackingTopology::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, - std::array& minR, - std::array& maxR, + std::array& minR, + std::array& maxR, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Streams& streams); -template +template void countCellsHandler(const Cluster** sortedClusters, const Cluster** unsortedClusters, const TrackingFrameInfo** tfInfo, Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, - CellSeed* cells, + const int cellTopologyId, + const typename TrackingTopology::View topology, + CellSeed* cells, int** cellsLUTsDeviceArray, int* cellsLUTsHost, - const int deltaROF, const float bz, const float maxChi2ClusterAttachment, const float cellDeltaTanLambdaSigma, const float nSigmaCut, + const std::vector& layerxX0Host, o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Streams& streams); -template +template void computeCellsHandler(const Cluster** sortedClusters, const Cluster** unsortedClusters, const TrackingFrameInfo** tfInfo, Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, - CellSeed* cells, + const int cellTopologyId, + const typename TrackingTopology::View topology, + CellSeed* cells, int** cellsLUTsDeviceArray, int* cellsLUTsHost, - const int deltaROF, const float bz, const float maxChi2ClusterAttachment, const float cellDeltaTanLambdaSigma, const float nSigmaCut, - const int nBlocks, - const int nThreads, + const std::vector& layerxX0Host, gpu::Streams& streams); -template -void countCellNeighboursHandler(CellSeed** cellsLayersDevice, - int* neighboursLUTs, +template +void countCellNeighboursHandler(CellSeed** cellsLayersDevice, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, - const int deltaROF, + const int sourceCellTopologyId, + const int targetCellTopologyId, const float maxChi2ClusterAttachment, const float bz, - const int layerIndex, const unsigned int nCells, - const unsigned int nCellsNext, - const int maxCellNeighbours, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Stream& stream); -template -void computeCellNeighboursHandler(CellSeed** cellsLayersDevice, - int* neighboursLUTs, +void scanCellNeighboursHandler(int* neighboursCursor, + int* neighboursLUT, + const unsigned int nCells, + o2::its::ExternalAllocator* alloc, + gpu::Stream& stream); + +template +void computeCellNeighboursHandler(CellSeed** cellsLayersDevice, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, - const int deltaROF, + CellNeighbour* cellNeighbours, + const int sourceCellTopologyId, + const int targetCellTopologyId, const float maxChi2ClusterAttachment, const float bz, - const int layerIndex, const unsigned int nCells, - const unsigned int nCellsNext, - const int maxCellNeighbours, - const int nBlocks, - const int nThreads, gpu::Stream& stream); int filterCellNeighboursHandler(gpuPair*, @@ -186,33 +174,38 @@ int filterCellNeighboursHandler(gpuPair*, gpu::Stream&, o2::its::ExternalAllocator* = nullptr); -template -void processNeighboursHandler(const int startLayer, - const int startLevel, - CellSeed** allCellSeeds, - CellSeed* currentCellSeeds, - std::array& nCells, +template +void processNeighboursHandler(const int startLevel, + const int defaultCellTopologyId, + CellSeed** allCellSeeds, + CellSeed* currentCellSeeds, + const int* currentCellTopologyIds, + const int* currentCellIds, + const int* nCells, const unsigned char** usedClusters, - std::array& neighbours, - gsl::span neighboursDeviceLUTs, + CellNeighbour** neighbours, + int** neighboursDeviceLUTs, const TrackingFrameInfo** foundTrackingFrameInfo, - bounded_vector>& seedsHost, + bounded_vector>& seedsHost, const float bz, const float MaxChi2ClusterAttachment, const float maxChi2NDF, + const int maxHoles, + const int minTrackLength, + const LayerMask holeLayerMask, + const std::vector& layerxX0Host, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads); + o2::its::ExternalAllocator* alloc); -template -void countTrackSeedHandler(CellSeed* trackSeeds, +template +void countTrackSeedHandler(TrackSeed* trackSeeds, const TrackingFrameInfo** foundTrackingFrameInfo, const Cluster** unsortedClusters, int* seedLUT, const std::vector& layerRadiiHost, const std::vector& minPtsHost, + const std::vector& layerxX0Host, const unsigned int nSeeds, const float Bz, const int startLevel, @@ -223,18 +216,17 @@ void countTrackSeedHandler(CellSeed* trackSeeds, const bool shiftRefToCluster, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads); + o2::its::ExternalAllocator* alloc); -template -void computeTrackSeedHandler(CellSeed* trackSeeds, +template +void computeTrackSeedHandler(TrackSeed* trackSeeds, const TrackingFrameInfo** foundTrackingFrameInfo, const Cluster** unsortedClusters, o2::its::TrackITSExt* tracks, const int* seedLUT, const std::vector& layerRadiiHost, const std::vector& minPtsHost, + const std::vector& layerxX0Host, const unsigned int nSeeds, const unsigned int nTracks, const float Bz, @@ -246,9 +238,7 @@ void computeTrackSeedHandler(CellSeed* trackSeeds, const bool shiftRefToCluster, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads); + o2::its::ExternalAllocator* alloc); } // namespace o2::its #endif // ITSTRACKINGGPU_TRACKINGKERNELS_H_ diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/Utils.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/Utils.h index ee0a203f32fda..bcc20ace7bbc2 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/Utils.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/Utils.h @@ -38,7 +38,11 @@ #endif #ifdef ITS_GPU_LOG -#define GPULog(...) LOGP(info, __VA_ARGS__) +#define GPULog(...) \ + do { \ + LOGP(info, __VA_ARGS__); \ + GPUChkErrS(cudaDeviceSynchronize()); \ + } while (0) #else #define GPULog(...) #endif @@ -339,27 +343,6 @@ struct TypedAllocator { ExternalAllocator* mInternalAllocator; }; -template -GPUdii() const int4 getBinsRect(const Cluster& currentCluster, const int layerIndex, - const o2::its::IndexTableUtils* utils, - const float z1, const float z2, float maxdeltaz, float maxdeltaphi) -{ - const float zRangeMin = o2::gpu::CAMath::Min(z1, z2) - maxdeltaz; - const float phiRangeMin = (maxdeltaphi > o2::constants::math::PI) ? 0.f : currentCluster.phi - maxdeltaphi; - const float zRangeMax = o2::gpu::CAMath::Max(z1, z2) + maxdeltaz; - const float phiRangeMax = (maxdeltaphi > o2::constants::math::PI) ? o2::constants::math::TwoPI : currentCluster.phi + maxdeltaphi; - - if (zRangeMax < -utils->getLayerZ(layerIndex) || - zRangeMin > utils->getLayerZ(layerIndex) || zRangeMin > zRangeMax) { - return {}; - } - - return int4{o2::gpu::CAMath::Max(0, utils->getZBinIndex(layerIndex, zRangeMin)), - utils->getPhiBinIndex(math_utils::getNormalizedPhi(phiRangeMin)), - o2::gpu::CAMath::Min(utils->getNzBins() - 1, utils->getZBinIndex(layerIndex, zRangeMax)), - utils->getPhiBinIndex(math_utils::getNormalizedPhi(phiRangeMax))}; -} - GPUdii() gpuSpan getPrimaryVertices(const int rof, const int* roframesPV, const int nROF, diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/VertexerTraitsGPU.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/VertexerTraitsGPU.h deleted file mode 100644 index dddc247466c65..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/VertexerTraitsGPU.h +++ /dev/null @@ -1,55 +0,0 @@ -// 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 VertexerTraitsGPU.h -/// \brief -/// \author matteo.concas@cern.ch - -// #define VTX_DEBUG -#ifndef ITSTRACKINGGPU_VERTEXERTRAITSGPU_H_ -#define ITSTRACKINGGPU_VERTEXERTRAITSGPU_H_ - -#include - -#include "ITStracking/VertexerTraits.h" -#include "ITStracking/Configuration.h" -#include "ITStracking/Cluster.h" -#include "ITStracking/Constants.h" -#include "ITStracking/Definitions.h" -#include "ITStracking/Tracklet.h" - -#include "ITStrackingGPU/TimeFrameGPU.h" - -namespace o2::its -{ - -template -class VertexerTraitsGPU final : public VertexerTraits -{ - public: - void initialise(const TrackingParameters&, const int iteration = 0) final; - void adoptTimeFrame(TimeFrame* tf) noexcept final; - void computeTracklets(const int iteration = 0) final; - void computeTrackletMatching(const int iteration = 0) final; - void computeVertices(const int iteration = 0) final; - void updateVertexingParameters(const std::vector&, const TimeFrameGPUParameters&) final; - - bool isGPU() const noexcept final { return true; } - const char* getName() const noexcept final { return "GPU"; } - - protected: - gpu::TimeFrameGPU* mTimeFrameGPU; - TimeFrameGPUParameters mTfGPUParams; -}; - -} // namespace o2::its - -#endif diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/VertexingKernels.h b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/VertexingKernels.h deleted file mode 100644 index 67f12bad8486c..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/VertexingKernels.h +++ /dev/null @@ -1,115 +0,0 @@ -// 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. -/// - -#ifndef ITSTRACKINGGPU_VERTEXINGKERNELS_H_ -#define ITSTRACKINGGPU_VERTEXINGKERNELS_H_ - -#include -#include -#include -#include "ITStracking/Tracklet.h" -#include "ITStracking/Cluster.h" -#include "ITStracking/ClusterLines.h" -#include "ITStrackingGPU/Utils.h" - -namespace o2::its -{ - -/// Trackleting -template -void countTrackletsInROFsHandler(const IndexTableUtils* GPUrestrict() utils, - const uint8_t* GPUrestrict() multMask, - const int32_t nRofs, - const int32_t deltaROF, - const int32_t* GPUrestrict() rofPV, - const int32_t vertPerRofThreshold, - const Cluster** GPUrestrict() clusters, - const uint32_t nClusters, - const int32_t** GPUrestrict() ROFClusters, - const uint8_t** GPUrestrict() usedClusters, - const int32_t** GPUrestrict() clustersIndexTables, - int32_t** trackletsPerClusterLUTs, - int32_t** trackletsPerClusterSumLUTs, - int32_t** trackletsPerROF, - const std::array& trackletsPerClusterLUTsHost, - const std::array& trackletsPerClusterSumLUTsHost, - const int32_t iteration, - const float phiCut, - const int32_t maxTrackletsPerCluster, - const int32_t nBlocks, - const int32_t nThreads, - gpu::Streams& streams); - -template -void computeTrackletsInROFsHandler(const IndexTableUtils* GPUrestrict() utils, - const uint8_t* GPUrestrict() multMask, - const int32_t nRofs, - const int32_t deltaROF, - const int32_t* GPUrestrict() rofPV, - const int vertPerRofThreshold, - const Cluster** GPUrestrict() clusters, - const uint32_t nClusters, - const int32_t** GPUrestrict() ROFClusters, - const uint8_t** GPUrestrict() usedClusters, - const int32_t** GPUrestrict() clustersIndexTables, - Tracklet** GPUrestrict() foundTracklets, - const int32_t** GPUrestrict() trackletsPerClusterLUTs, - const int32_t** GPUrestrict() trackletsPerClusterSumLUTs, - const int32_t** GPUrestrict() trackletsPerROF, - const int32_t iteration, - const float phiCut, - const int32_t maxTrackletsPerCluster, - const int32_t nBlocks, - const int32_t nThreads, - gpu::Streams& streams); - -/// Selection -void countTrackletsMatchingInROFsHandler(const int32_t nRofs, - const int32_t deltaROF, - const uint32_t nClusters, - const int32_t** GPUrestrict() ROFClusters, - const Cluster** GPUrestrict() clusters, - uint8_t** GPUrestrict() usedClusters, - const Tracklet** GPUrestrict() foundTracklets, - uint8_t* GPUrestrict() usedTracklets, - const int32_t** GPUrestrict() trackletsPerClusterLUTs, - const int32_t** GPUrestrict() trackletsPerClusterSumLUTs, - int32_t* GPUrestrict() linesPerClusterLUT, - int32_t* GPUrestrict() linesPerClusterSumLUT, - const int32_t iteration, - const float phiCut, - const float tanLambdaCut, - const int32_t nBlocks, - const int32_t nThreads, - gpu::Streams& streams); - -void computeTrackletsMatchingInROFsHandler(const int32_t nRofs, - const int32_t deltaROF, - const uint32_t nClusters, - const int32_t** GPUrestrict() ROFClusters, - const Cluster** GPUrestrict() clusters, - const uint8_t** GPUrestrict() usedClusters, - const Tracklet** GPUrestrict() foundTracklets, - uint8_t* GPUrestrict() usedTracklets, - const int32_t** GPUrestrict() trackletsPerClusterLUTs, - const int32_t** GPUrestrict() trackletsPerClusterSumLUTs, - const int32_t* GPUrestrict() linesPerClusterSumLUT, - Line* GPUrestrict() lines, - const int32_t iteration, - const float phiCut, - const float tanLambdaCut, - const int32_t nBlocks, - const int32_t nThreads, - gpu::Streams& streams); - -} // namespace o2::its -#endif diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/CMakeLists.txt b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/CMakeLists.txt index e38dbb1ef20e8..38f11265682ce 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/CMakeLists.txt @@ -13,19 +13,13 @@ if(CUDA_ENABLED) find_package(CUDAToolkit) message(STATUS "Building ITS CUDA tracker") - # add_compile_options(-O0 -g -lineinfo -fPIC -DGPU_FORCE_DEVICE_ASSERTS=ON) - # add_compile_definitions(ITS_MEASURE_GPU_TIME) - # add_compile_definitions(ITS_GPU_LOG) o2_add_library(ITStrackingCUDA - SOURCES ClusterLinesGPU.cu - TrackerTraitsGPU.cxx + SOURCES TrackerTraitsGPU.cxx TimeFrameGPU.cu - TracerGPU.cu TrackingKernels.cu - VertexingKernels.cu - VertexerTraitsGPU.cxx PUBLIC_INCLUDE_DIRECTORIES ../ PUBLIC_LINK_LIBRARIES O2::ITStracking + O2::MathUtils O2::SimConfig O2::SimulationDataFormat O2::ReconstructionDataFormats @@ -33,7 +27,14 @@ if(CUDA_ENABLED) PRIVATE_LINK_LIBRARIES O2::GPUTrackingCUDAExternalProvider TARGETVARNAME targetName) + set_target_gpu_arch("CUDA" ${targetName}) + # Enable relocatable device code (needed for separable compilation + debugging) set_property(TARGET ${targetName} PROPERTY CUDA_SEPARABLE_COMPILATION ON) + target_compile_options(${targetName} PRIVATE + $<$:-diag-error=20014> + # $<$:-G;-O0;-Xptxas=-O0> + # $<$:-O0;-g> + ) + # target_compile_definitions(${targetName} PRIVATE ITS_MEASURE_GPU_TIME ITS_GPU_LOG) target_compile_definitions(${targetName} PRIVATE $) - set_target_gpu_arch("CUDA" ${targetName}) endif() diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/ClusterLinesGPU.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/ClusterLinesGPU.cu deleted file mode 100644 index 79f4e40dc5f10..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/ClusterLinesGPU.cu +++ /dev/null @@ -1,138 +0,0 @@ -// 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. -/// -/// \author matteo.concas@cern.ch - -#include -#include "ITStrackingGPU/ClusterLinesGPU.h" - -namespace o2 -{ -namespace its -{ -namespace gpu -{ - -GPUd() ClusterLinesGPU::ClusterLinesGPU(const Line& firstLine, const Line& secondLine) -{ - float covarianceFirst[3]; - float covarianceSecond[3]; - - for (int i{0}; i < 3; ++i) { - covarianceFirst[i] = 1.f; - covarianceSecond[i] = 1.f; - } - - double determinantFirst = - firstLine.cosinesDirector[2] * firstLine.cosinesDirector[2] * covarianceFirst[0] * covarianceFirst[1] + - firstLine.cosinesDirector[1] * firstLine.cosinesDirector[1] * covarianceFirst[0] * covarianceFirst[2] + - firstLine.cosinesDirector[0] * firstLine.cosinesDirector[0] * covarianceFirst[1] * covarianceFirst[2]; - double determinantSecond = - secondLine.cosinesDirector[2] * secondLine.cosinesDirector[2] * covarianceSecond[0] * covarianceSecond[1] + - secondLine.cosinesDirector[1] * secondLine.cosinesDirector[1] * covarianceSecond[0] * covarianceSecond[2] + - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[0] * covarianceSecond[1] * covarianceSecond[2]; - - mAMatrix[0] = (firstLine.cosinesDirector[2] * firstLine.cosinesDirector[2] * covarianceFirst[1] + - firstLine.cosinesDirector[1] * firstLine.cosinesDirector[1] * covarianceFirst[2]) / - determinantFirst + - (secondLine.cosinesDirector[2] * secondLine.cosinesDirector[2] * covarianceSecond[1] + - secondLine.cosinesDirector[1] * secondLine.cosinesDirector[1] * covarianceSecond[2]) / - determinantSecond; - - mAMatrix[1] = -firstLine.cosinesDirector[0] * firstLine.cosinesDirector[1] * covarianceFirst[2] / determinantFirst - - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[1] * covarianceSecond[2] / determinantSecond; - - mAMatrix[2] = -firstLine.cosinesDirector[0] * firstLine.cosinesDirector[2] * covarianceFirst[1] / determinantFirst - - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[2] * covarianceSecond[1] / determinantSecond; - - mAMatrix[3] = (firstLine.cosinesDirector[2] * firstLine.cosinesDirector[2] * covarianceFirst[0] + - firstLine.cosinesDirector[0] * firstLine.cosinesDirector[0] * covarianceFirst[2]) / - determinantFirst + - (secondLine.cosinesDirector[2] * secondLine.cosinesDirector[2] * covarianceSecond[0] + - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[0] * covarianceSecond[2]) / - determinantSecond; - - mAMatrix[4] = -firstLine.cosinesDirector[1] * firstLine.cosinesDirector[2] * covarianceFirst[0] / determinantFirst - - secondLine.cosinesDirector[1] * secondLine.cosinesDirector[2] * covarianceSecond[0] / determinantSecond; - - mAMatrix[5] = (firstLine.cosinesDirector[1] * firstLine.cosinesDirector[1] * covarianceFirst[0] + - firstLine.cosinesDirector[0] * firstLine.cosinesDirector[0] * covarianceFirst[1]) / - determinantFirst + - (secondLine.cosinesDirector[1] * secondLine.cosinesDirector[1] * covarianceSecond[0] + - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[0] * covarianceSecond[1]) / - determinantSecond; - - mBMatrix[0] = - (firstLine.cosinesDirector[1] * covarianceFirst[2] * (-firstLine.cosinesDirector[1] * firstLine.originPoint[0] + firstLine.cosinesDirector[0] * firstLine.originPoint[1]) + - firstLine.cosinesDirector[2] * covarianceFirst[1] * (-firstLine.cosinesDirector[2] * firstLine.originPoint[0] + firstLine.cosinesDirector[0] * firstLine.originPoint[2])) / - determinantFirst; - - mBMatrix[0] += - (secondLine.cosinesDirector[1] * covarianceSecond[2] * (-secondLine.cosinesDirector[1] * secondLine.originPoint[0] + secondLine.cosinesDirector[0] * secondLine.originPoint[1]) + - secondLine.cosinesDirector[2] * covarianceSecond[1] * - (-secondLine.cosinesDirector[2] * secondLine.originPoint[0] + - secondLine.cosinesDirector[0] * secondLine.originPoint[2])) / - determinantSecond; - - mBMatrix[1] = - (firstLine.cosinesDirector[0] * covarianceFirst[2] * (-firstLine.cosinesDirector[0] * firstLine.originPoint[1] + firstLine.cosinesDirector[1] * firstLine.originPoint[0]) + - firstLine.cosinesDirector[2] * covarianceFirst[0] * (-firstLine.cosinesDirector[2] * firstLine.originPoint[1] + firstLine.cosinesDirector[1] * firstLine.originPoint[2])) / - determinantFirst; - - mBMatrix[1] += - (secondLine.cosinesDirector[0] * covarianceSecond[2] * (-secondLine.cosinesDirector[0] * secondLine.originPoint[1] + secondLine.cosinesDirector[1] * secondLine.originPoint[0]) + - secondLine.cosinesDirector[2] * covarianceSecond[0] * - (-secondLine.cosinesDirector[2] * secondLine.originPoint[1] + - secondLine.cosinesDirector[1] * secondLine.originPoint[2])) / - determinantSecond; - - mBMatrix[2] = - (firstLine.cosinesDirector[0] * covarianceFirst[1] * (-firstLine.cosinesDirector[0] * firstLine.originPoint[2] + firstLine.cosinesDirector[2] * firstLine.originPoint[0]) + - firstLine.cosinesDirector[1] * covarianceFirst[0] * (-firstLine.cosinesDirector[1] * firstLine.originPoint[2] + firstLine.cosinesDirector[2] * firstLine.originPoint[1])) / - determinantFirst; - - mBMatrix[2] += - (secondLine.cosinesDirector[0] * covarianceSecond[1] * (-secondLine.cosinesDirector[0] * secondLine.originPoint[2] + secondLine.cosinesDirector[2] * secondLine.originPoint[0]) + - secondLine.cosinesDirector[1] * covarianceSecond[0] * - (-secondLine.cosinesDirector[1] * secondLine.originPoint[2] + - secondLine.cosinesDirector[2] * secondLine.originPoint[1])) / - determinantSecond; - - computeClusterCentroid(); -} - -GPUd() void ClusterLinesGPU::computeClusterCentroid() -{ - - double determinant{mAMatrix[0] * (mAMatrix[3] * mAMatrix[5] - mAMatrix[4] * mAMatrix[4]) - - mAMatrix[1] * (mAMatrix[1] * mAMatrix[5] - mAMatrix[4] * mAMatrix[2]) + - mAMatrix[2] * (mAMatrix[1] * mAMatrix[4] - mAMatrix[2] * mAMatrix[3])}; - - if (determinant == 0) { - return; - } - - mVertex[0] = -(mBMatrix[0] * (mAMatrix[3] * mAMatrix[5] - mAMatrix[4] * mAMatrix[4]) - - mAMatrix[1] * (mBMatrix[1] * mAMatrix[5] - mAMatrix[4] * mBMatrix[2]) + - mAMatrix[2] * (mBMatrix[1] * mAMatrix[4] - mBMatrix[2] * mAMatrix[3])) / - determinant; - mVertex[1] = -(mAMatrix[0] * (mBMatrix[1] * mAMatrix[5] - mBMatrix[2] * mAMatrix[4]) - - mBMatrix[0] * (mAMatrix[1] * mAMatrix[5] - mAMatrix[4] * mAMatrix[2]) + - mAMatrix[2] * (mAMatrix[1] * mBMatrix[2] - mAMatrix[2] * mBMatrix[1])) / - determinant; - mVertex[2] = -(mAMatrix[0] * (mAMatrix[3] * mBMatrix[2] - mBMatrix[1] * mAMatrix[4]) - - mAMatrix[1] * (mAMatrix[1] * mBMatrix[2] - mBMatrix[1] * mAMatrix[2]) + - mBMatrix[0] * (mAMatrix[1] * mAMatrix[4] - mAMatrix[2] * mAMatrix[3])) / - determinant; -} -} // namespace gpu -} // namespace its -} // namespace o2 diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameChunk.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameChunk.cu deleted file mode 100644 index c8512e667aea8..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameChunk.cu +++ /dev/null @@ -1,293 +0,0 @@ -// 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. - -#include -#include -#include - -#include "ITStracking/Constants.h" - -#include "ITStrackingGPU/Utils.h" -#include "ITStrackingGPU/TracerGPU.h" - -#include "ITStrackingGPU/TimeFrameChunk.h" - -#include -#include - -#include "GPUCommonDef.h" -#include "GPUCommonMath.h" -#include "GPUCommonLogger.h" -#include "GPUCommonHelpers.h" - -#ifndef __HIPCC__ -#define THRUST_NAMESPACE thrust::cuda -#else -#define THRUST_NAMESPACE thrust::hip -#endif - -namespace o2::its -{ -using constants::GB; -using constants::MB; -namespace gpu -{ - -template -GpuTimeFrameChunk::~GpuTimeFrameChunk() -{ - if (mAllocated) { - for (int i = 0; i < nLayers; ++i) { - GPUChkErrS(cudaFree(mClustersDevice[i])); - // GPUChkErrS(cudaFree(mTrackingFrameInfoDevice[i])); - GPUChkErrS(cudaFree(mClusterExternalIndicesDevice[i])); - GPUChkErrS(cudaFree(mIndexTablesDevice[i])); - if (i < nLayers - 1) { - GPUChkErrS(cudaFree(mTrackletsDevice[i])); - GPUChkErrS(cudaFree(mTrackletsLookupTablesDevice[i])); - if (i < nLayers - 2) { - GPUChkErrS(cudaFree(mCellsDevice[i])); - GPUChkErrS(cudaFree(mCellsLookupTablesDevice[i])); - GPUChkErrS(cudaFree(mRoadsLookupTablesDevice[i])); - if (i < nLayers - 3) { - GPUChkErrS(cudaFree(mNeighboursCellLookupTablesDevice[i])); - GPUChkErrS(cudaFree(mNeighboursCellDevice[i])); - } - } - } - } - // GPUChkErrS(cudaFree(mRoadsDevice)); - GPUChkErrS(cudaFree(mCUBTmpBufferDevice)); - GPUChkErrS(cudaFree(mFoundTrackletsDevice)); - GPUChkErrS(cudaFree(mNFoundCellsDevice)); - GPUChkErrS(cudaFree(mCellsDeviceArray)); - GPUChkErrS(cudaFree(mNeighboursCellDeviceArray)); - GPUChkErrS(cudaFree(mNeighboursCellLookupTablesDeviceArray)); - } -} - -template -void GpuTimeFrameChunk::allocate(const size_t nrof, Stream& stream) -{ - RANGE("device_partition_allocation", 2); - mNRof = nrof; - // for (int i = 0; i < nLayers; ++i) { - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mClustersDevice[i])), sizeof(Cluster) * mTFGPUParams->clustersPerROfCapacity * nrof, &stream, true); - // // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mTrackingFrameInfoDevice[i])), sizeof(TrackingFrameInfo) * mTFGPUParams->clustersPerROfCapacity * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mClusterExternalIndicesDevice[i])), sizeof(int) * mTFGPUParams->clustersPerROfCapacity * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mIndexTablesDevice[i])), sizeof(int) * (256 * 128 + 1) * nrof, &stream, true); - // if (i < nLayers - 1) { - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mTrackletsLookupTablesDevice[i])), sizeof(int) * mTFGPUParams->clustersPerROfCapacity * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mTrackletsDevice[i])), sizeof(Tracklet) * mTFGPUParams->maxTrackletsPerCluster * mTFGPUParams->clustersPerROfCapacity * nrof, &stream, true); - // if (i < nLayers - 2) { - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mCellsLookupTablesDevice[i])), sizeof(int) * mTFGPUParams->validatedTrackletsCapacity * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mCellsDevice[i])), sizeof(CellSeed) * mTFGPUParams->maxNeighboursSize * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mRoadsLookupTablesDevice[i]), sizeof(int) * mTFGPUParams->maxNeighboursSize * nrof, &stream, true); - // if (i < nLayers - 3) { - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mNeighboursCellLookupTablesDevice[i])), sizeof(int) * mTFGPUParams->maxNeighboursSize * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mNeighboursCellDevice[i])), sizeof(int) * mTFGPUParams->maxNeighboursSize * nrof, &stream, true); - // } - // if (i < 2) { - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&(mNTrackletsPerClusterDevice[i])), sizeof(int) * mTFGPUParams->clustersPerROfCapacity * nrof, &stream, true); - // } - // } - // } - // } - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mCUBTmpBufferDevice), mTFGPUParams->tmpCUBBufferSize * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mLinesDevice), sizeof(Line) * mTFGPUParams->maxTrackletsPerCluster * mTFGPUParams->clustersPerROfCapacity * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mNFoundLinesDevice), sizeof(int) * mTFGPUParams->clustersPerROfCapacity * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mNExclusiveFoundLinesDevice), sizeof(int) * mTFGPUParams->clustersPerROfCapacity * nrof + 1, &stream, true); // + 1 for cub::DeviceScan::ExclusiveSum, to cover cases where we have maximum number of clusters per ROF - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mUsedTrackletsDevice), sizeof(unsigned char) * mTFGPUParams->maxTrackletsPerCluster * mTFGPUParams->clustersPerROfCapacity * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mClusteredLinesDevice), sizeof(int) * mTFGPUParams->clustersPerROfCapacity * mTFGPUParams->maxTrackletsPerCluster * nrof, &stream, true); - // // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mRoadsDevice), sizeof(Road) * mTFGPUParams->maxRoadPerRofSize * nrof, &stream, true); - - // /// Invariant allocations - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mFoundTrackletsDevice), (nLayers - 1) * sizeof(int) * nrof, &stream, true); // No need to reset, we always read it after writing - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mNFoundCellsDevice), (nLayers - 2) * sizeof(int) * nrof, &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mCellsDeviceArray), (nLayers - 2) * sizeof(CellSeed*), &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mNeighboursCellDeviceArray), (nLayers - 3) * sizeof(int*), &stream, true); - // static_cast*>(mTimeFramePtr)->allocMemAsync(reinterpret_cast(&mNeighboursCellLookupTablesDeviceArray), (nLayers - 3) * sizeof(int*), &stream, true); - - // /// Copy pointers of allocated memory to regrouping arrays - // GPUChkErrS(cudaMemcpyAsync(mCellsDeviceArray, mCellsDevice.data(), (nLayers - 2) * sizeof(CellSeed*), cudaMemcpyHostToDevice, stream.get())); - // GPUChkErrS(cudaMemcpyAsync(mNeighboursCellDeviceArray, mNeighboursCellDevice.data(), (nLayers - 3) * sizeof(int*), cudaMemcpyHostToDevice, stream.get())); - // GPUChkErrS(cudaMemcpyAsync(mNeighboursCellLookupTablesDeviceArray, mNeighboursCellLookupTablesDevice.data(), (nLayers - 3) * sizeof(int*), cudaMemcpyHostToDevice, stream.get())); - - mAllocated = true; -} - -template -void GpuTimeFrameChunk::reset(const Task task, Stream& stream) -{ - RANGE("buffer_reset", 0); - // if ((bool)task) { // Vertexer-only initialisation (cannot be constexpr: due to the presence of gpu raw calls can't be put in header) - // for (int i = 0; i < 2; i++) { - // auto thrustTrackletsBegin = thrust::device_ptr(mTrackletsDevice[i]); - // auto thrustTrackletsEnd = thrustTrackletsBegin + mTFGPUParams->maxTrackletsPerCluster * mTFGPUParams->clustersPerROfCapacity * mNRof; - // thrust::fill(THRUST_NAMESPACE::par.on(stream.get()), thrustTrackletsBegin, thrustTrackletsEnd, Tracklet{}); - // GPUChkErrS(cudaMemsetAsync(mNTrackletsPerClusterDevice[i], 0, sizeof(int) * mTFGPUParams->clustersPerROfCapacity * mNRof, stream.get())); - // } - // GPUChkErrS(cudaMemsetAsync(mUsedTrackletsDevice, false, sizeof(unsigned char) * mTFGPUParams->maxTrackletsPerCluster * mTFGPUParams->clustersPerROfCapacity * mNRof, stream.get())); - // GPUChkErrS(cudaMemsetAsync(mClusteredLinesDevice, -1, sizeof(int) * mTFGPUParams->clustersPerROfCapacity * mTFGPUParams->maxTrackletsPerCluster * mNRof, stream.get())); - // } else { - // for (int i = 0; i < nLayers; ++i) { - // if (i < nLayers - 1) { - // GPUChkErrS(cudaMemsetAsync(mTrackletsLookupTablesDevice[i], 0, sizeof(int) * mTFGPUParams->clustersPerROfCapacity * mNRof, stream.get())); - // auto thrustTrackletsBegin = thrust::device_ptr(mTrackletsDevice[i]); - // auto thrustTrackletsEnd = thrustTrackletsBegin + mTFGPUParams->maxTrackletsPerCluster * mTFGPUParams->clustersPerROfCapacity * mNRof; - // thrust::fill(THRUST_NAMESPACE::par.on(stream.get()), thrustTrackletsBegin, thrustTrackletsEnd, Tracklet{}); - // if (i < nLayers - 2) { - // GPUChkErrS(cudaMemsetAsync(mCellsLookupTablesDevice[i], 0, sizeof(int) * mTFGPUParams->cellsLUTsize * mNRof, stream.get())); - // GPUChkErrS(cudaMemsetAsync(mRoadsLookupTablesDevice[i], 0, sizeof(int) * mTFGPUParams->maxNeighboursSize * mNRof, stream.get())); - // if (i < nLayers - 3) { - // GPUChkErrS(cudaMemsetAsync(mNeighboursCellLookupTablesDevice[i], 0, sizeof(int) * mTFGPUParams->maxNeighboursSize * mNRof, stream.get())); - // GPUChkErrS(cudaMemsetAsync(mNeighboursCellDevice[i], 0, sizeof(int) * mTFGPUParams->maxNeighboursSize * mNRof, stream.get())); - // } - // } - // } - // } - // GPUChkErrS(cudaMemsetAsync(mNFoundCellsDevice, 0, (nLayers - 2) * sizeof(int), stream.get())); - // } -} - -template -size_t GpuTimeFrameChunk::computeScalingSizeBytes(const int nrof, const TimeFrameGPUParameters& config) -{ - size_t rofsize = nLayers * sizeof(int); // number of clusters per ROF - // rofsize += nLayers * sizeof(Cluster) * config.clustersPerROfCapacity; // clusters - // rofsize += nLayers * sizeof(TrackingFrameInfo) * config.clustersPerROfCapacity; // tracking frame info - // rofsize += nLayers * sizeof(int) * config.clustersPerROfCapacity; // external cluster indices - // rofsize += nLayers * sizeof(int) * (256 * 128 + 1); // index tables - // rofsize += (nLayers - 1) * sizeof(int) * config.clustersPerROfCapacity; // tracklets lookup tables - // rofsize += (nLayers - 1) * sizeof(Tracklet) * config.maxTrackletsPerCluster * config.clustersPerROfCapacity; // tracklets - // rofsize += 2 * sizeof(int) * config.clustersPerROfCapacity; // tracklets found per cluster (vertexer) - // rofsize += sizeof(unsigned char) * config.maxTrackletsPerCluster * config.clustersPerROfCapacity; // used tracklets (vertexer) - // rofsize += (nLayers - 2) * sizeof(int) * config.validatedTrackletsCapacity; // cells lookup tables - // rofsize += (nLayers - 2) * sizeof(CellSeed) * config.maxNeighboursSize; // cells - // rofsize += (nLayers - 3) * sizeof(int) * config.maxNeighboursSize; // cell neighbours lookup tables - // rofsize += (nLayers - 3) * sizeof(int) * config.maxNeighboursSize; // cell neighbours - // rofsize += sizeof(Road) * config.maxRoadPerRofSize; // roads - // rofsize += (nLayers - 2) * sizeof(int) * config.maxNeighboursSize; // road LUT - // rofsize += sizeof(Line) * config.maxTrackletsPerCluster * config.clustersPerROfCapacity; // lines - // rofsize += sizeof(int) * config.clustersPerROfCapacity; // found lines - // rofsize += sizeof(int) * config.clustersPerROfCapacity; // found lines exclusive sum - // rofsize += sizeof(int) * config.clustersPerROfCapacity * config.maxTrackletsPerCluster; // lines used in clusterlines - - // rofsize += (nLayers - 1) * sizeof(int); // total found tracklets - // rofsize += (nLayers - 2) * sizeof(int); // total found cells - - return rofsize * nrof; -} - -template -size_t GpuTimeFrameChunk::computeFixedSizeBytes(const TimeFrameGPUParameters& config) -{ - size_t total = config.tmpCUBBufferSize; // CUB tmp buffers - total += sizeof(gpu::StaticTrackingParameters); // static parameters loaded once - return total; -} - -template -size_t GpuTimeFrameChunk::computeRofPerChunk(const TimeFrameGPUParameters& config, const size_t m) -{ - return (m * GB / (float)(config.nTimeFrameChunks) - GpuTimeFrameChunk::computeFixedSizeBytes(config)) / (float)GpuTimeFrameChunk::computeScalingSizeBytes(1, config); -} - -/// Interface -template -Cluster* GpuTimeFrameChunk::getDeviceClusters(const int layer) -{ - return mClustersDevice[layer]; -} - -template -int* GpuTimeFrameChunk::getDeviceClusterExternalIndices(const int layer) -{ - return mClusterExternalIndicesDevice[layer]; -} - -template -int* GpuTimeFrameChunk::getDeviceIndexTables(const int layer) -{ - return mIndexTablesDevice[layer]; -} - -template -Tracklet* GpuTimeFrameChunk::getDeviceTracklets(const int layer) -{ - return mTrackletsDevice[layer]; -} - -template -int* GpuTimeFrameChunk::getDeviceTrackletsLookupTables(const int layer) -{ - return mTrackletsLookupTablesDevice[layer]; -} - -template -CellSeed* GpuTimeFrameChunk::getDeviceCells(const int layer) -{ - return mCellsDevice[layer]; -} - -template -int* GpuTimeFrameChunk::getDeviceCellsLookupTables(const int layer) -{ - return mCellsLookupTablesDevice[layer]; -} - -template -int* GpuTimeFrameChunk::getDeviceCellNeigboursLookupTables(const int layer) -{ - return mNeighboursCellLookupTablesDevice[layer]; -} - -template -int* GpuTimeFrameChunk::getDeviceCellNeighbours(const int layer) -{ - return mNeighboursCellDevice[layer]; -} - -template -int* GpuTimeFrameChunk::getDeviceRoadsLookupTables(const int layer) -{ - return mRoadsLookupTablesDevice[layer]; -} - -// Load data -template -size_t GpuTimeFrameChunk::loadDataOnDevice(const size_t startRof, const size_t maxRof, const int maxLayers, Stream& stream) -{ - RANGE("load_clusters_data", 5); - // auto nRofs = std::min(maxRof - startRof, mNRof); - // mNPopulatedRof = mTimeFramePtr->getNClustersROFrange(startRof, nRofs, 0).size(); - // for (int i = 0; i < maxLayers; ++i) { - // mHostClusters[i] = mTimeFramePtr->getClustersPerROFrange(startRof, nRofs, i); - // mHostIndexTables[i] = mTimeFramePtr->getIndexTablePerROFrange(startRof, nRofs, i); - // if (mHostClusters[i].size() > mTFGPUParams->clustersPerROfCapacity * nRofs) { - // LOGP(warning, "Clusters on layer {} exceed the expected value, resizing to config value: {}, will lose information!", i, mTFGPUParams->clustersPerROfCapacity * nRofs); - // } - // GPUChkErrS(cudaMemcpyAsync(mClustersDevice[i], - // mHostClusters[i].data(), - // (int)std::min(mHostClusters[i].size(), mTFGPUParams->clustersPerROfCapacity * nRofs) * sizeof(Cluster), - // cudaMemcpyHostToDevice, stream.get())); - // if (mHostIndexTables[i].data()) { - // GPUChkErrS(cudaMemcpyAsync(mIndexTablesDevice[i], - // mHostIndexTables[i].data(), - // mHostIndexTables[i].size() * sizeof(int), - // cudaMemcpyHostToDevice, stream.get())); - // } - // } - return mNPopulatedRof; // return the number of ROFs we loaded the data for. -} -template class GpuTimeFrameChunk<7>; -} // namespace gpu -} // namespace o2::its \ No newline at end of file diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu index da0cd51478945..5fff30f5162b1 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu @@ -29,8 +29,8 @@ namespace o2::its::gpu { -template -void TimeFrameGPU::allocMemAsync(void** ptr, size_t size, Stream& stream, bool extAllocator, int32_t type) +template +void TimeFrameGPU::allocMemAsync(void** ptr, size_t size, Stream& stream, bool extAllocator, int32_t type) { if (extAllocator) { *ptr = (this->mExternalAllocator)->allocate(size, type); @@ -40,8 +40,8 @@ void TimeFrameGPU::allocMemAsync(void** ptr, size_t size, Stream& strea } } -template -void TimeFrameGPU::allocMem(void** ptr, size_t size, bool extAllocator, int32_t type) +template +void TimeFrameGPU::allocMem(void** ptr, size_t size, bool extAllocator, int32_t type) { if (extAllocator) { *ptr = (this->mExternalAllocator)->allocate(size, type); @@ -51,11 +51,11 @@ void TimeFrameGPU::allocMem(void** ptr, size_t size, bool extAllocator, } } -template -void TimeFrameGPU::loadIndexTableUtils(const int iteration) +template +void TimeFrameGPU::loadIndexTableUtils() { GPUTimer timer("loading indextable utils"); - if (!iteration) { + { GPULog("gpu-allocation: allocating IndexTableUtils buffer, for {:.2f} MB.", sizeof(IndexTableUtilsN) / constants::MB); allocMem(reinterpret_cast(&mIndexTableUtilsDevice), sizeof(IndexTableUtilsN), this->hasFrameworkAllocator()); } @@ -63,16 +63,16 @@ void TimeFrameGPU::loadIndexTableUtils(const int iteration) GPUChkErrS(cudaMemcpy(mIndexTableUtilsDevice, &(this->mIndexTableUtils), sizeof(IndexTableUtilsN), cudaMemcpyHostToDevice)); } -template -void TimeFrameGPU::createUnsortedClustersDeviceArray(const int iteration, const int maxLayers) +template +void TimeFrameGPU::createUnsortedClustersDeviceArray(const int maxLayers) { - if (!iteration) { + { GPUTimer timer("creating unsorted clusters array"); - allocMem(reinterpret_cast(&mUnsortedClustersDeviceArray), nLayers * sizeof(Cluster*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaHostRegister(mUnsortedClustersDevice.data(), nLayers * sizeof(Cluster*), cudaHostRegisterPortable)); - mPinnedUnsortedClusters.set(nLayers); + allocMem(reinterpret_cast(&mUnsortedClustersDeviceArray), NLayers * sizeof(Cluster*), this->hasFrameworkAllocator()); + GPUChkErrS(cudaHostRegister(mUnsortedClustersDevice.data(), NLayers * sizeof(Cluster*), cudaHostRegisterPortable)); + mPinnedUnsortedClusters.set(NLayers); if (!this->hasFrameworkAllocator()) { - for (auto iLayer{0}; iLayer < o2::gpu::CAMath::Min(maxLayers, nLayers); ++iLayer) { + for (auto iLayer{0}; iLayer < o2::gpu::CAMath::Min(maxLayers, NLayers); ++iLayer) { GPUChkErrS(cudaHostRegister(this->mUnsortedClusters[iLayer].data(), this->mUnsortedClusters[iLayer].size() * sizeof(Cluster), cudaHostRegisterPortable)); mPinnedUnsortedClusters.set(iLayer); } @@ -80,10 +80,10 @@ void TimeFrameGPU::createUnsortedClustersDeviceArray(const int iteratio } } -template -void TimeFrameGPU::loadUnsortedClustersDevice(const int iteration, const int layer) +template +void TimeFrameGPU::loadUnsortedClustersDevice(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "loading unsorted clusters", layer); GPULog("gpu-transfer: loading {} unsorted clusters on layer {}, for {:.2f} MB.", this->mUnsortedClusters[layer].size(), layer, this->mUnsortedClusters[layer].size() * sizeof(Cluster) / constants::MB); allocMemAsync(reinterpret_cast(&mUnsortedClustersDevice[layer]), this->mUnsortedClusters[layer].size() * sizeof(Cluster), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -92,16 +92,16 @@ void TimeFrameGPU::loadUnsortedClustersDevice(const int iteration, cons } } -template -void TimeFrameGPU::createClustersDeviceArray(const int iteration, const int maxLayers) +template +void TimeFrameGPU::createClustersDeviceArray(const int maxLayers) { - if (!iteration) { + { GPUTimer timer("creating sorted clusters array"); - allocMem(reinterpret_cast(&mClustersDeviceArray), nLayers * sizeof(Cluster*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaHostRegister(mClustersDevice.data(), nLayers * sizeof(Cluster*), cudaHostRegisterPortable)); - mPinnedClusters.set(nLayers); + allocMem(reinterpret_cast(&mClustersDeviceArray), NLayers * sizeof(Cluster*), this->hasFrameworkAllocator()); + GPUChkErrS(cudaHostRegister(mClustersDevice.data(), NLayers * sizeof(Cluster*), cudaHostRegisterPortable)); + mPinnedClusters.set(NLayers); if (!this->hasFrameworkAllocator()) { - for (auto iLayer{0}; iLayer < o2::gpu::CAMath::Min(maxLayers, nLayers); ++iLayer) { + for (auto iLayer{0}; iLayer < o2::gpu::CAMath::Min(maxLayers, NLayers); ++iLayer) { GPUChkErrS(cudaHostRegister(this->mClusters[iLayer].data(), this->mClusters[iLayer].size() * sizeof(Cluster), cudaHostRegisterPortable)); mPinnedClusters.set(iLayer); } @@ -109,10 +109,10 @@ void TimeFrameGPU::createClustersDeviceArray(const int iteration, const } } -template -void TimeFrameGPU::loadClustersDevice(const int iteration, const int layer) +template +void TimeFrameGPU::loadClustersDevice(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "loading sorted clusters", layer); GPULog("gpu-transfer: loading {} clusters on layer {}, for {:.2f} MB.", this->mClusters[layer].size(), layer, this->mClusters[layer].size() * sizeof(Cluster) / constants::MB); allocMemAsync(reinterpret_cast(&mClustersDevice[layer]), this->mClusters[layer].size() * sizeof(Cluster), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -121,16 +121,16 @@ void TimeFrameGPU::loadClustersDevice(const int iteration, const int la } } -template -void TimeFrameGPU::createClustersIndexTablesArray(const int iteration) +template +void TimeFrameGPU::createClustersIndexTablesArray() { - if (!iteration) { + { GPUTimer timer("creating clustersindextable array"); - allocMem(reinterpret_cast(&mClustersIndexTablesDeviceArray), nLayers * sizeof(int*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaHostRegister(mClustersIndexTablesDevice.data(), nLayers * sizeof(int*), cudaHostRegisterPortable)); - mPinnedClustersIndexTables.set(nLayers); + allocMem(reinterpret_cast(&mClustersIndexTablesDeviceArray), NLayers * sizeof(int*), this->hasFrameworkAllocator()); + GPUChkErrS(cudaHostRegister(mClustersIndexTablesDevice.data(), NLayers * sizeof(int*), cudaHostRegisterPortable)); + mPinnedClustersIndexTables.set(NLayers); if (!this->hasFrameworkAllocator()) { - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { + for (auto iLayer{0}; iLayer < NLayers; ++iLayer) { GPUChkErrS(cudaHostRegister(this->mIndexTables[iLayer].data(), this->mIndexTables[iLayer].size() * sizeof(int), cudaHostRegisterPortable)); mPinnedClustersIndexTables.set(iLayer); } @@ -138,10 +138,10 @@ void TimeFrameGPU::createClustersIndexTablesArray(const int iteration) } } -template -void TimeFrameGPU::loadClustersIndexTables(const int iteration, const int layer) +template +void TimeFrameGPU::loadClustersIndexTables(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "loading sorted clusters", layer); GPULog("gpu-transfer: loading clusters indextable for layer {} with {} elements, for {:.2f} MB.", layer, this->mIndexTables[layer].size(), this->mIndexTables[layer].size() * sizeof(int) / constants::MB); allocMemAsync(reinterpret_cast(&mClustersIndexTablesDevice[layer]), this->mIndexTables[layer].size() * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -150,16 +150,16 @@ void TimeFrameGPU::loadClustersIndexTables(const int iteration, const i } } -template -void TimeFrameGPU::createUsedClustersDeviceArray(const int iteration, const int maxLayers) +template +void TimeFrameGPU::createUsedClustersDeviceArray(const int maxLayers) { - if (!iteration) { + { GPUTimer timer("creating used clusters flags"); - allocMem(reinterpret_cast(&mUsedClustersDeviceArray), nLayers * sizeof(uint8_t*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaHostRegister(mUsedClustersDevice.data(), nLayers * sizeof(uint8_t*), cudaHostRegisterPortable)); - mPinnedUsedClusters.set(nLayers); + allocMem(reinterpret_cast(&mUsedClustersDeviceArray), NLayers * sizeof(uint8_t*), this->hasFrameworkAllocator()); + GPUChkErrS(cudaHostRegister(mUsedClustersDevice.data(), NLayers * sizeof(uint8_t*), cudaHostRegisterPortable)); + mPinnedUsedClusters.set(NLayers); if (!this->hasFrameworkAllocator()) { - for (auto iLayer{0}; iLayer < o2::gpu::CAMath::Min(maxLayers, nLayers); ++iLayer) { + for (auto iLayer{0}; iLayer < o2::gpu::CAMath::Min(maxLayers, NLayers); ++iLayer) { GPUChkErrS(cudaHostRegister(this->mUsedClusters[iLayer].data(), this->mUsedClusters[iLayer].size() * sizeof(uint8_t), cudaHostRegisterPortable)); mPinnedUsedClusters.set(iLayer); } @@ -167,10 +167,10 @@ void TimeFrameGPU::createUsedClustersDeviceArray(const int iteration, c } } -template -void TimeFrameGPU::createUsedClustersDevice(const int iteration, const int layer) +template +void TimeFrameGPU::createUsedClustersDevice(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "creating used clusters flags", layer); GPULog("gpu-transfer: creating {} used clusters flags on layer {}, for {:.2f} MB.", this->mUsedClusters[layer].size(), layer, this->mUsedClusters[layer].size() * sizeof(unsigned char) / constants::MB); allocMemAsync(reinterpret_cast(&mUsedClustersDevice[layer]), this->mUsedClusters[layer].size() * sizeof(unsigned char), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -179,26 +179,26 @@ void TimeFrameGPU::createUsedClustersDevice(const int iteration, const } } -template -void TimeFrameGPU::loadUsedClustersDevice() +template +void TimeFrameGPU::loadUsedClustersDevice() { - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { + for (auto iLayer{0}; iLayer < NLayers; ++iLayer) { GPUTimer timer(mGpuStreams[iLayer], "loading used clusters flags", iLayer); GPULog("gpu-transfer: loading {} used clusters flags on layer {}, for {:.2f} MB.", this->mUsedClusters[iLayer].size(), iLayer, this->mUsedClusters[iLayer].size() * sizeof(unsigned char) / constants::MB); GPUChkErrS(cudaMemcpyAsync(mUsedClustersDevice[iLayer], this->mUsedClusters[iLayer].data(), this->mUsedClusters[iLayer].size() * sizeof(unsigned char), cudaMemcpyHostToDevice, mGpuStreams[iLayer].get())); } } -template -void TimeFrameGPU::createROFrameClustersDeviceArray(const int iteration) +template +void TimeFrameGPU::createROFrameClustersDeviceArray() { - if (!iteration) { + { GPUTimer timer("creating ROFrame clusters array"); - allocMem(reinterpret_cast(&mROFramesClustersDeviceArray), nLayers * sizeof(int*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaHostRegister(mROFramesClustersDevice.data(), nLayers * sizeof(int*), cudaHostRegisterPortable)); - mPinnedROFramesClusters.set(nLayers); + allocMem(reinterpret_cast(&mROFramesClustersDeviceArray), NLayers * sizeof(int*), this->hasFrameworkAllocator()); + GPUChkErrS(cudaHostRegister(mROFramesClustersDevice.data(), NLayers * sizeof(int*), cudaHostRegisterPortable)); + mPinnedROFramesClusters.set(NLayers); if (!this->hasFrameworkAllocator()) { - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { + for (auto iLayer{0}; iLayer < NLayers; ++iLayer) { GPUChkErrS(cudaHostRegister(this->mROFramesClusters[iLayer].data(), this->mROFramesClusters[iLayer].size() * sizeof(int), cudaHostRegisterPortable)); mPinnedROFramesClusters.set(iLayer); } @@ -206,10 +206,10 @@ void TimeFrameGPU::createROFrameClustersDeviceArray(const int iteration } } -template -void TimeFrameGPU::loadROFrameClustersDevice(const int iteration, const int layer) +template +void TimeFrameGPU::loadROFrameClustersDevice(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "loading ROframe clusters", layer); GPULog("gpu-transfer: loading {} ROframe clusters info on layer {}, for {:.2f} MB.", this->mROFramesClusters[layer].size(), layer, this->mROFramesClusters[layer].size() * sizeof(int) / constants::MB); allocMemAsync(reinterpret_cast(&mROFramesClustersDevice[layer]), this->mROFramesClusters[layer].size() * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -218,16 +218,16 @@ void TimeFrameGPU::loadROFrameClustersDevice(const int iteration, const } } -template -void TimeFrameGPU::createTrackingFrameInfoDeviceArray(const int iteration) +template +void TimeFrameGPU::createTrackingFrameInfoDeviceArray() { - if (!iteration) { + { GPUTimer timer("creating trackingframeinfo array"); - allocMem(reinterpret_cast(&mTrackingFrameInfoDeviceArray), nLayers * sizeof(TrackingFrameInfo*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaHostRegister(mTrackingFrameInfoDevice.data(), nLayers * sizeof(TrackingFrameInfo*), cudaHostRegisterPortable)); - mPinnedTrackingFrameInfo.set(nLayers); + allocMem(reinterpret_cast(&mTrackingFrameInfoDeviceArray), NLayers * sizeof(TrackingFrameInfo*), this->hasFrameworkAllocator()); + GPUChkErrS(cudaHostRegister(mTrackingFrameInfoDevice.data(), NLayers * sizeof(TrackingFrameInfo*), cudaHostRegisterPortable)); + mPinnedTrackingFrameInfo.set(NLayers); if (!this->hasFrameworkAllocator()) { - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { + for (auto iLayer{0}; iLayer < NLayers; ++iLayer) { GPUChkErrS(cudaHostRegister(this->mTrackingFrameInfo[iLayer].data(), this->mTrackingFrameInfo[iLayer].size() * sizeof(TrackingFrameInfo), cudaHostRegisterPortable)); mPinnedTrackingFrameInfo.set(iLayer); } @@ -235,10 +235,10 @@ void TimeFrameGPU::createTrackingFrameInfoDeviceArray(const int iterati } } -template -void TimeFrameGPU::loadTrackingFrameInfoDevice(const int iteration, const int layer) +template +void TimeFrameGPU::loadTrackingFrameInfoDevice(const int layer) { - if (!iteration) { + { GPUTimer timer(mGpuStreams[layer], "loading trackingframeinfo", layer); GPULog("gpu-transfer: loading {} tfinfo on layer {}, for {:.2f} MB.", this->mTrackingFrameInfo[layer].size(), layer, this->mTrackingFrameInfo[layer].size() * sizeof(TrackingFrameInfo) / constants::MB); allocMemAsync(reinterpret_cast(&mTrackingFrameInfoDevice[layer]), this->mTrackingFrameInfo[layer].size() * sizeof(TrackingFrameInfo), mGpuStreams[layer], this->hasFrameworkAllocator()); @@ -247,47 +247,149 @@ void TimeFrameGPU::loadTrackingFrameInfoDevice(const int iteration, con } } -template -void TimeFrameGPU::loadMultiplicityCutMask(const int iteration) +template +void TimeFrameGPU::loadROFCutMask(const int iteration) { - if (!iteration || iteration == 3) { // we need to re-load the swapped mult-mask in upc iteration + { GPUTimer timer("loading multiplicity cut mask"); - GPULog("gpu-transfer: iteration {} loading multiplicity cut mask with {} elements, for {:.2f} MB.", iteration, this->mMultiplicityCutMask.size(), this->mMultiplicityCutMask.size() * sizeof(uint8_t) / constants::MB); - if (!iteration) { // only allocate on first call - allocMem(reinterpret_cast(&mMultMaskDevice), this->mMultiplicityCutMask.size() * sizeof(uint8_t), this->hasFrameworkAllocator()); - } - GPUChkErrS(cudaMemcpy(mMultMaskDevice, this->mMultiplicityCutMask.data(), this->mMultiplicityCutMask.size() * sizeof(uint8_t), cudaMemcpyHostToDevice)); - } -} - -template -void TimeFrameGPU::loadVertices(const int iteration) -{ - if (!iteration) { + const auto& hostTable = *(this->mROFMask); + const auto hostView = hostTable.getView(); + using TableEntry = ROFMaskTable::TableEntry; + using TableIndex = ROFMaskTable::TableIndex; + TableEntry* d_flatTable{nullptr}; + TableIndex* d_indices{nullptr}; + GPULog("gpu-transfer: iteration {} loading multiplicity cut mask with {} elements, for {:.2f} MB.", + iteration, hostTable.getFlatMaskSize(), hostTable.getFlatMaskSize() * sizeof(TableEntry) / constants::MB); + allocMem(reinterpret_cast(&d_flatTable), hostTable.getFlatMaskSize() * sizeof(TableEntry), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&d_indices), NLayers * sizeof(uint32_t), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemcpy(d_indices, hostView.mLayerROFOffsets, NLayers * sizeof(TableIndex), cudaMemcpyHostToDevice)); + // Re-copy the flat mask on every qualifying iteration (e.g. after swapMasks() for UPC) + GPUChkErrS(cudaMemcpy(d_flatTable, hostView.mFlatMask, hostTable.getFlatMaskSize() * sizeof(TableEntry), cudaMemcpyHostToDevice)); + mDeviceROFMaskTableView = hostTable.getDeviceView(d_flatTable, d_indices); + } +} + +template +void TimeFrameGPU::loadVertices() +{ + { GPUTimer timer("loading seeding vertices"); - GPULog("gpu-transfer: loading {} ROframes vertices, for {:.2f} MB.", this->mROFramesPV.size(), this->mROFramesPV.size() * sizeof(int) / constants::MB); - allocMem(reinterpret_cast(&mROFramesPVDevice), this->mROFramesPV.size() * sizeof(int), this->hasFrameworkAllocator()); - GPUChkErrS(cudaMemcpy(mROFramesPVDevice, this->mROFramesPV.data(), this->mROFramesPV.size() * sizeof(int), cudaMemcpyHostToDevice)); GPULog("gpu-transfer: loading {} seeding vertices, for {:.2f} MB.", this->mPrimaryVertices.size(), this->mPrimaryVertices.size() * sizeof(Vertex) / constants::MB); allocMem(reinterpret_cast(&mPrimaryVerticesDevice), this->mPrimaryVertices.size() * sizeof(Vertex), this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpy(mPrimaryVerticesDevice, this->mPrimaryVertices.data(), this->mPrimaryVertices.size() * sizeof(Vertex), cudaMemcpyHostToDevice)); } } -template -void TimeFrameGPU::createTrackletsLUTDeviceArray(const int iteration) -{ - if (!iteration) { - allocMem(reinterpret_cast(&mTrackletsLUTDeviceArray), (nLayers - 1) * sizeof(int*), this->hasFrameworkAllocator()); - } -} - -template -void TimeFrameGPU::createTrackletsLUTDevice(const int iteration, const int layer) +template +void TimeFrameGPU::loadROFOverlapTable() +{ + { + GPUTimer timer("initialising device view of ROFOverlapTable"); + const auto& hostTable = this->getROFOverlapTable(); + const auto& hostView = this->getROFOverlapTableView(); + using TableEntry = ROFOverlapTable::TableEntry; + using TableIndex = ROFOverlapTable::TableIndex; + using LayerTiming = o2::its::LayerTiming; + TableEntry* d_flatTable{nullptr}; + TableIndex* d_indices{nullptr}; + LayerTiming* d_layers{nullptr}; + size_t flatTableSize = hostTable.getFlatTableSize(); + allocMem(reinterpret_cast(&d_flatTable), flatTableSize * sizeof(TableEntry), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemcpy(d_flatTable, hostView.mFlatTable, flatTableSize * sizeof(TableEntry), cudaMemcpyHostToDevice)); + allocMem(reinterpret_cast(&d_indices), hostTable.getIndicesSize() * sizeof(TableIndex), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemcpy(d_indices, hostView.mIndices, hostTable.getIndicesSize() * sizeof(TableIndex), cudaMemcpyHostToDevice)); + allocMem(reinterpret_cast(&d_layers), NLayers * sizeof(LayerTiming), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemcpy(d_layers, hostView.mLayers, NLayers * sizeof(LayerTiming), cudaMemcpyHostToDevice)); + mDeviceROFOverlapTableView = hostTable.getDeviceView(d_flatTable, d_indices, d_layers); + } +} + +template +void TimeFrameGPU::loadROFVertexLookupTable() +{ + { + GPUTimer timer("initialising device view of ROFVertexLookupTable"); + const auto& hostTable = this->getROFVertexLookupTable(); + const auto& hostView = this->getROFVertexLookupTableView(); + using TableEntry = ROFVertexLookupTable::TableEntry; + using TableIndex = ROFVertexLookupTable::TableIndex; + using LayerTiming = o2::its::LayerTiming; + TableEntry* d_flatTable{nullptr}; + TableIndex* d_indices{nullptr}; + LayerTiming* d_layers{nullptr}; + size_t flatTableSize = hostTable.getFlatTableSize(); + allocMem(reinterpret_cast(&d_flatTable), flatTableSize * sizeof(TableEntry), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemcpy(d_flatTable, hostView.mFlatTable, flatTableSize * sizeof(TableEntry), cudaMemcpyHostToDevice)); + allocMem(reinterpret_cast(&d_indices), hostTable.getIndicesSize() * sizeof(TableIndex), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemcpy(d_indices, hostView.mIndices, hostTable.getIndicesSize() * sizeof(TableIndex), cudaMemcpyHostToDevice)); + allocMem(reinterpret_cast(&d_layers), NLayers * sizeof(LayerTiming), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemcpy(d_layers, hostView.mLayers, NLayers * sizeof(LayerTiming), cudaMemcpyHostToDevice)); + mDeviceROFVertexLookupTableView = hostTable.getDeviceView(d_flatTable, d_indices, d_layers); + } +} + +template +void TimeFrameGPU::loadTrackingTopologies() +{ + GPUTimer timer("initialising device views of TrackingTopology"); + const auto& hostTopologies = this->getTrackerTopologies(); + mDeviceTrackerTopologyViews.resize(hostTopologies.size()); + using LayerTransition = typename TrackingTopologyN::LayerTransition; + using CellTopology = typename TrackingTopologyN::CellTopology; + using Range = typename TrackingTopologyN::Range; + using Id = typename TrackingTopologyN::Id; + for (size_t iteration = 0; iteration < hostTopologies.size(); ++iteration) { + const auto& topology = hostTopologies[iteration]; + LayerTransition* dTransitions{nullptr}; + CellTopology* dCells{nullptr}; + Range* dCellsByFirstTransitionIndex{nullptr}; + Id* dCellsByFirstTransition{nullptr}; + allocMem(reinterpret_cast(&dTransitions), topology.getNTransitions() * sizeof(LayerTransition), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&dCells), topology.getNCells() * sizeof(CellTopology), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&dCellsByFirstTransitionIndex), topology.getNTransitions() * sizeof(Range), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&dCellsByFirstTransition), topology.getNCellsByFirstTransition() * sizeof(Id), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemcpy(dTransitions, topology.getTransitions().data(), topology.getNTransitions() * sizeof(LayerTransition), cudaMemcpyHostToDevice)); + GPUChkErrS(cudaMemcpy(dCells, topology.getCells().data(), topology.getNCells() * sizeof(CellTopology), cudaMemcpyHostToDevice)); + GPUChkErrS(cudaMemcpy(dCellsByFirstTransitionIndex, topology.getCellsByFirstTransitionIndex().data(), topology.getNTransitions() * sizeof(Range), cudaMemcpyHostToDevice)); + GPUChkErrS(cudaMemcpy(dCellsByFirstTransition, topology.getCellsByFirstTransition().data(), topology.getNCellsByFirstTransition() * sizeof(Id), cudaMemcpyHostToDevice)); + mDeviceTrackerTopologyViews[iteration] = topology.getDeviceView(dTransitions, dCells, dCellsByFirstTransitionIndex, dCellsByFirstTransition); + } + if (!mDeviceTrackerTopologyViews.empty()) { + mDeviceTrackingTopologyView = mDeviceTrackerTopologyViews.front(); + } +} + +template +void TimeFrameGPU::updateROFVertexLookupTable() +{ + const auto& hostTable = this->getROFVertexLookupTable(); + { + GPUTimer timer("updating device view of ROFVertexLookupTable"); + const auto& hostView = this->getROFVertexLookupTableView(); + using TableEntry = ROFVertexLookupTable::TableEntry; + TableEntry* d_flatTable{nullptr}; + size_t flatTableSize = hostTable.getFlatTableSize(); + allocMem(reinterpret_cast(&d_flatTable), flatTableSize * sizeof(TableEntry), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemcpy(d_flatTable, hostView.mFlatTable, flatTableSize * sizeof(TableEntry), cudaMemcpyHostToDevice)); + mDeviceROFVertexLookupTableView = hostTable.getDeviceView(d_flatTable, hostView.mIndices, hostView.mLayers); + } +} + +template +void TimeFrameGPU::createTrackletsLUTDeviceArray() +{ + { + allocMem(reinterpret_cast(&mTrackletsLUTDeviceArray), MaxTransitions * sizeof(int*), this->hasFrameworkAllocator()); + } +} + +template +void TimeFrameGPU::createTrackletsLUTDevice(bool allocate, const int layer) { GPUTimer timer(mGpuStreams[layer], "creating tracklets LUTs", layer); - const int ncls = this->mClusters[layer].size() + 1; - if (!iteration) { + const int fromLayer = this->mTrackingTopologyView.getTransition(layer).fromLayer; + const int ncls = this->mClusters[fromLayer].size() + 1; + if (allocate || mTrackletsLUTDevice[layer] == nullptr) { GPULog("gpu-allocation: creating tracklets LUT for {} elements on layer {}, for {:.2f} MB.", ncls, layer, ncls * sizeof(int) / constants::MB); allocMemAsync(reinterpret_cast(&mTrackletsLUTDevice[layer]), ncls * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpyAsync(&mTrackletsLUTDeviceArray[layer], &mTrackletsLUTDevice[layer], sizeof(int*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); @@ -295,52 +397,54 @@ void TimeFrameGPU::createTrackletsLUTDevice(const int iteration, const GPUChkErrS(cudaMemsetAsync(mTrackletsLUTDevice[layer], 0, ncls * sizeof(int), mGpuStreams[layer].get())); } -template -void TimeFrameGPU::createTrackletsBuffersArray(const int iteration) +template +void TimeFrameGPU::createTrackletsBuffersArray() { - if (!iteration) { + { GPUTimer timer("creating tracklet buffers array"); - allocMem(reinterpret_cast(&mTrackletsDeviceArray), (nLayers - 1) * sizeof(Tracklet*), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&mTrackletsDeviceArray), MaxTransitions * sizeof(Tracklet*), this->hasFrameworkAllocator()); } } -template -void TimeFrameGPU::createTrackletsBuffers(const int layer) +template +void TimeFrameGPU::createTrackletsBuffers(const int layer) { GPUTimer timer(mGpuStreams[layer], "creating tracklet buffers", layer); mNTracklets[layer] = 0; - GPUChkErrS(cudaMemcpyAsync(&mNTracklets[layer], mTrackletsLUTDevice[layer] + this->mClusters[layer].size(), sizeof(int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); + const int fromLayer = this->mTrackingTopologyView.getTransition(layer).fromLayer; + GPUChkErrS(cudaMemcpyAsync(&mNTracklets[layer], mTrackletsLUTDevice[layer] + this->mClusters[fromLayer].size(), sizeof(int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); mGpuStreams[layer].sync(); // ensure number of tracklets is correct GPULog("gpu-transfer: creating tracklets buffer for {} elements on layer {}, for {:.2f} MB.", mNTracklets[layer], layer, mNTracklets[layer] * sizeof(Tracklet) / constants::MB); allocMemAsync(reinterpret_cast(&mTrackletsDevice[layer]), mNTracklets[layer] * sizeof(Tracklet), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + GPUChkErrS(cudaMemsetAsync(mTrackletsDevice[layer], 0, mNTracklets[layer] * sizeof(Tracklet), mGpuStreams[layer].get())); GPUChkErrS(cudaMemcpyAsync(&mTrackletsDeviceArray[layer], &mTrackletsDevice[layer], sizeof(Tracklet*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } -template -void TimeFrameGPU::loadTrackletsDevice() +template +void TimeFrameGPU::loadTrackletsDevice() { - GPUTimer timer(mGpuStreams, "loading tracklets", nLayers - 1); - for (auto iLayer{0}; iLayer < nLayers - 1; ++iLayer) { + GPUTimer timer(mGpuStreams, "loading tracklets", NLayers - 1); + for (auto iLayer{0}; iLayer < NLayers - 1; ++iLayer) { GPULog("gpu-transfer: loading {} tracklets on layer {}, for {:.2f} MB.", this->mTracklets[iLayer].size(), iLayer, this->mTracklets[iLayer].size() * sizeof(Tracklet) / constants::MB); GPUChkErrS(cudaHostRegister(this->mTracklets[iLayer].data(), this->mTracklets[iLayer].size() * sizeof(Tracklet), cudaHostRegisterPortable)); GPUChkErrS(cudaMemcpyAsync(mTrackletsDevice[iLayer], this->mTracklets[iLayer].data(), this->mTracklets[iLayer].size() * sizeof(Tracklet), cudaMemcpyHostToDevice, mGpuStreams[iLayer].get())); } } -template -void TimeFrameGPU::loadTrackletsLUTDevice() +template +void TimeFrameGPU::loadTrackletsLUTDevice() { GPUTimer timer("loading tracklets"); - for (auto iLayer{0}; iLayer < nLayers - 2; ++iLayer) { + for (auto iLayer{0}; iLayer < NLayers - 2; ++iLayer) { GPULog("gpu-transfer: loading tracklets LUT for {} elements on layer {}, for {:.2f} MB", this->mTrackletsLookupTable[iLayer].size(), iLayer + 1, this->mTrackletsLookupTable[iLayer].size() * sizeof(int) / constants::MB); GPUChkErrS(cudaMemcpyAsync(mTrackletsLUTDevice[iLayer + 1], this->mTrackletsLookupTable[iLayer].data(), this->mTrackletsLookupTable[iLayer].size() * sizeof(int), cudaMemcpyHostToDevice, mGpuStreams[iLayer].get())); } mGpuStreams.sync(); - GPUChkErrS(cudaMemcpy(mTrackletsLUTDeviceArray, mTrackletsLUTDevice.data(), (nLayers - 1) * sizeof(int*), cudaMemcpyHostToDevice)); + GPUChkErrS(cudaMemcpy(mTrackletsLUTDeviceArray, mTrackletsLUTDevice.data(), (NLayers - 1) * sizeof(int*), cudaMemcpyHostToDevice)); } -template -void TimeFrameGPU::createNeighboursIndexTablesDevice(const int layer) +template +void TimeFrameGPU::createNeighboursIndexTablesDevice(const int layer) { GPUTimer timer(mGpuStreams[layer], "creating cells neighbours", layer); GPULog("gpu-transfer: reserving neighbours LUT for {} elements on layer {}, for {:.2f} MB.", mNCells[layer] + 1, layer, (mNCells[layer] + 1) * sizeof(int) / constants::MB); @@ -348,118 +452,125 @@ void TimeFrameGPU::createNeighboursIndexTablesDevice(const int layer) GPUChkErrS(cudaMemsetAsync(mNeighboursIndexTablesDevice[layer], 0, (mNCells[layer] + 1) * sizeof(int), mGpuStreams[layer].get())); } -template -void TimeFrameGPU::createNeighboursLUTDevice(const int layer, const unsigned int nCells) +template +void TimeFrameGPU::createNeighboursLUTDevice(const int layer, const unsigned int nCells) { GPUTimer timer(mGpuStreams[layer], "reserving neighboursLUT"); GPULog("gpu-allocation: reserving neighbours LUT for {} elements on layer {} , for {:.2f} MB.", nCells + 1, layer, (nCells + 1) * sizeof(int) / constants::MB); allocMemAsync(reinterpret_cast(&mNeighboursLUTDevice[layer]), (nCells + 1) * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); // We need one element more to move exc -> inc GPUChkErrS(cudaMemsetAsync(mNeighboursLUTDevice[layer], 0, (nCells + 1) * sizeof(int), mGpuStreams[layer].get())); + GPUChkErrS(cudaMemcpyAsync(&mNeighboursCellLUTDeviceArray[layer], &mNeighboursLUTDevice[layer], sizeof(int*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } -template -void TimeFrameGPU::loadCellsDevice() +template +void TimeFrameGPU::loadCellsDevice() { - GPUTimer timer(mGpuStreams, "loading cell seeds", nLayers - 2); - for (auto iLayer{0}; iLayer < nLayers - 2; ++iLayer) { - GPULog("gpu-transfer: loading {} cell seeds on layer {}, for {:.2f} MB.", this->mCells[iLayer].size(), iLayer, this->mCells[iLayer].size() * sizeof(CellSeedN) / constants::MB); - allocMemAsync(reinterpret_cast(&mCellsDevice[iLayer]), this->mCells[iLayer].size() * sizeof(CellSeedN), mGpuStreams[iLayer], this->hasFrameworkAllocator()); + GPUTimer timer(mGpuStreams, "loading cell seeds", NLayers - 2); + for (auto iLayer{0}; iLayer < NLayers - 2; ++iLayer) { + GPULog("gpu-transfer: loading {} cell seeds on layer {}, for {:.2f} MB.", this->mCells[iLayer].size(), iLayer, this->mCells[iLayer].size() * sizeof(CellSeed) / constants::MB); + allocMemAsync(reinterpret_cast(&mCellsDevice[iLayer]), this->mCells[iLayer].size() * sizeof(CellSeed), mGpuStreams[iLayer], this->hasFrameworkAllocator()); allocMemAsync(reinterpret_cast(&mNeighboursIndexTablesDevice[iLayer]), (this->mCells[iLayer].size() + 1) * sizeof(int), mGpuStreams[iLayer], this->hasFrameworkAllocator()); // accessory for the neigh. finding. GPUChkErrS(cudaMemsetAsync(mNeighboursIndexTablesDevice[iLayer], 0, (this->mCells[iLayer].size() + 1) * sizeof(int), mGpuStreams[iLayer].get())); - GPUChkErrS(cudaMemcpyAsync(mCellsDevice[iLayer], this->mCells[iLayer].data(), this->mCells[iLayer].size() * sizeof(CellSeedN), cudaMemcpyHostToDevice, mGpuStreams[iLayer].get())); + GPUChkErrS(cudaMemcpyAsync(mCellsDevice[iLayer], this->mCells[iLayer].data(), this->mCells[iLayer].size() * sizeof(CellSeed), cudaMemcpyHostToDevice, mGpuStreams[iLayer].get())); } } -template -void TimeFrameGPU::createCellsLUTDeviceArray(const int iteration) +template +void TimeFrameGPU::createCellsLUTDeviceArray() { - if (!iteration) { + { GPUTimer timer("creating cells LUTs array"); - allocMem(reinterpret_cast(&mCellsLUTDeviceArray), (nLayers - 2) * sizeof(int*), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&mCellsLUTDeviceArray), MaxCells * sizeof(int*), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&mNeighboursCellLUTDeviceArray), MaxCells * sizeof(int*), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemset(mNeighboursCellLUTDeviceArray, 0, MaxCells * sizeof(int*))); } } -template -void TimeFrameGPU::createCellsLUTDevice(const int layer) +template +void TimeFrameGPU::createCellsLUTDevice(const int layer) { GPUTimer timer(mGpuStreams[layer], "creating cells LUTs", layer); - GPULog("gpu-transfer: creating cell LUT for {} elements on layer {}, for {:.2f} MB.", mNTracklets[layer] + 1, layer, (mNTracklets[layer] + 1) * sizeof(int) / constants::MB); - allocMemAsync(reinterpret_cast(&mCellsLUTDevice[layer]), (mNTracklets[layer] + 1) * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); - GPUChkErrS(cudaMemsetAsync(mCellsLUTDevice[layer], 0, (mNTracklets[layer] + 1) * sizeof(int), mGpuStreams[layer].get())); + const int firstTransition = this->mTrackingTopologyView.getCell(layer).firstTransition; + GPULog("gpu-transfer: creating cell LUT for {} elements on layer {}, for {:.2f} MB.", mNTracklets[firstTransition] + 1, layer, (mNTracklets[firstTransition] + 1) * sizeof(int) / constants::MB); + allocMemAsync(reinterpret_cast(&mCellsLUTDevice[layer]), (mNTracklets[firstTransition] + 1) * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + GPUChkErrS(cudaMemsetAsync(mCellsLUTDevice[layer], 0, (mNTracklets[firstTransition] + 1) * sizeof(int), mGpuStreams[layer].get())); GPUChkErrS(cudaMemcpyAsync(&mCellsLUTDeviceArray[layer], &mCellsLUTDevice[layer], sizeof(int*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } -template -void TimeFrameGPU::createCellsBuffersArray(const int iteration) +template +void TimeFrameGPU::createCellsBuffersArray() { - if (!iteration) { + { GPUTimer timer("creating cells buffers array"); - allocMem(reinterpret_cast(&mCellsDeviceArray), (nLayers - 2) * sizeof(CellSeedN*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaMemcpy(mCellsDeviceArray, mCellsDevice.data(), mCellsDevice.size() * sizeof(CellSeedN*), cudaMemcpyHostToDevice)); + allocMem(reinterpret_cast(&mCellsDeviceArray), MaxCells * sizeof(CellSeed*), this->hasFrameworkAllocator()); + allocMem(reinterpret_cast(&mNeighboursDeviceArray), MaxCells * sizeof(CellNeighbour*), this->hasFrameworkAllocator()); + GPUChkErrS(cudaMemset(mNeighboursDeviceArray, 0, MaxCells * sizeof(CellNeighbour*))); + GPUChkErrS(cudaMemcpy(mCellsDeviceArray, mCellsDevice.data(), mCellsDevice.size() * sizeof(CellSeed*), cudaMemcpyHostToDevice)); } } -template -void TimeFrameGPU::createCellsBuffers(const int layer) +template +void TimeFrameGPU::createCellsBuffers(const int layer) { GPUTimer timer(mGpuStreams[layer], "creating cells buffers"); mNCells[layer] = 0; - GPUChkErrS(cudaMemcpyAsync(&mNCells[layer], mCellsLUTDevice[layer] + mNTracklets[layer], sizeof(int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); + const int firstTransition = this->mTrackingTopologyView.getCell(layer).firstTransition; + GPUChkErrS(cudaMemcpyAsync(&mNCells[layer], mCellsLUTDevice[layer] + mNTracklets[firstTransition], sizeof(int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); mGpuStreams[layer].sync(); // ensure number of cells is correct - GPULog("gpu-transfer: creating cell buffer for {} elements on layer {}, for {:.2f} MB.", mNCells[layer], layer, mNCells[layer] * sizeof(CellSeedN) / constants::MB); - allocMemAsync(reinterpret_cast(&mCellsDevice[layer]), mNCells[layer] * sizeof(CellSeedN), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); - GPUChkErrS(cudaMemcpyAsync(&mCellsDeviceArray[layer], &mCellsDevice[layer], sizeof(CellSeedN*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); + GPULog("gpu-transfer: creating cell buffer for {} elements on layer {}, for {:.2f} MB.", mNCells[layer], layer, mNCells[layer] * sizeof(CellSeed) / constants::MB); + allocMemAsync(reinterpret_cast(&mCellsDevice[layer]), mNCells[layer] * sizeof(CellSeed), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + GPUChkErrS(cudaMemsetAsync(mCellsDevice[layer], 0, mNCells[layer] * sizeof(CellSeed), mGpuStreams[layer].get())); + GPUChkErrS(cudaMemcpyAsync(&mCellsDeviceArray[layer], &mCellsDevice[layer], sizeof(CellSeed*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } -template -void TimeFrameGPU::loadCellsLUTDevice() +template +void TimeFrameGPU::loadCellsLUTDevice() { - GPUTimer timer(mGpuStreams, "loading cells LUTs", nLayers - 3); - for (auto iLayer{0}; iLayer < nLayers - 3; ++iLayer) { + GPUTimer timer(mGpuStreams, "loading cells LUTs", NLayers - 3); + for (auto iLayer{0}; iLayer < NLayers - 3; ++iLayer) { GPULog("gpu-transfer: loading cell LUT for {} elements on layer {}, for {:.2f} MB.", this->mCellsLookupTable[iLayer].size(), iLayer, this->mCellsLookupTable[iLayer].size() * sizeof(int) / constants::MB); GPUChkErrS(cudaHostRegister(this->mCellsLookupTable[iLayer].data(), this->mCellsLookupTable[iLayer].size() * sizeof(int), cudaHostRegisterPortable)); GPUChkErrS(cudaMemcpyAsync(mCellsLUTDevice[iLayer + 1], this->mCellsLookupTable[iLayer].data(), this->mCellsLookupTable[iLayer].size() * sizeof(int), cudaMemcpyHostToDevice, mGpuStreams[iLayer].get())); } } -template -void TimeFrameGPU::loadRoadsDevice() -{ - GPUTimer timer("loading roads device"); - GPULog("gpu-transfer: loading {} roads, for {:.2f} MB.", this->mRoads.size(), this->mRoads.size() * sizeof(Road) / constants::MB); - allocMem(reinterpret_cast(&mRoadsDevice), this->mRoads.size() * sizeof(Road), this->hasFrameworkAllocator()); - GPUChkErrS(cudaHostRegister(this->mRoads.data(), this->mRoads.size() * sizeof(Road), cudaHostRegisterPortable)); - GPUChkErrS(cudaMemcpy(mRoadsDevice, this->mRoads.data(), this->mRoads.size() * sizeof(Road), cudaMemcpyHostToDevice)); -} - -template -void TimeFrameGPU::loadTrackSeedsDevice(bounded_vector& seeds) +template +void TimeFrameGPU::loadTrackSeedsDevice(bounded_vector& seeds) { GPUTimer timer("loading track seeds"); - GPULog("gpu-transfer: loading {} track seeds, for {:.2f} MB.", seeds.size(), seeds.size() * sizeof(CellSeedN) / constants::MB); - allocMem(reinterpret_cast(&mTrackSeedsDevice), seeds.size() * sizeof(CellSeedN), this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); - GPUChkErrS(cudaMemcpy(mTrackSeedsDevice, seeds.data(), seeds.size() * sizeof(CellSeedN), cudaMemcpyHostToDevice)); + GPULog("gpu-transfer: loading {} track seeds, for {:.2f} MB.", seeds.size(), seeds.size() * sizeof(TrackSeedN) / constants::MB); + allocMem(reinterpret_cast(&mTrackSeedsDevice), seeds.size() * sizeof(TrackSeedN), this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + GPUChkErrS(cudaMemcpy(mTrackSeedsDevice, seeds.data(), seeds.size() * sizeof(TrackSeedN), cudaMemcpyHostToDevice)); GPULog("gpu-transfer: creating {} track seeds LUT, for {:.2f} MB.", seeds.size() + 1, (seeds.size() + 1) * sizeof(int) / constants::MB); allocMem(reinterpret_cast(&mTrackSeedsLUTDevice), (seeds.size() + 1) * sizeof(int), this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); GPUChkErrS(cudaMemset(mTrackSeedsLUTDevice, 0, (seeds.size() + 1) * sizeof(int))); } -template -void TimeFrameGPU::createNeighboursDevice(const unsigned int layer) +template +void TimeFrameGPU::createNeighboursDevice(const unsigned int layer) { GPUTimer timer(mGpuStreams[layer], "reserving neighbours", layer); this->mNNeighbours[layer] = 0; - GPUChkErrS(cudaMemcpyAsync(&(this->mNNeighbours[layer]), &(mNeighboursLUTDevice[layer][this->mNCells[layer + 1] - 1]), sizeof(unsigned int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); + if (this->mNCells[layer] == 0) { + mNeighboursDevice[layer] = nullptr; + GPUChkErrS(cudaMemcpyAsync(&mNeighboursDeviceArray[layer], &mNeighboursDevice[layer], sizeof(CellNeighbour*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); + return; + } + GPUChkErrS(cudaMemcpyAsync(&(this->mNNeighbours[layer]), &(mNeighboursLUTDevice[layer][this->mNCells[layer]]), sizeof(unsigned int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); mGpuStreams[layer].sync(); // ensure number of neighbours is correct - GPULog("gpu-allocation: reserving {} neighbours (pairs), for {:.2f} MB.", this->mNNeighbours[layer], (this->mNNeighbours[layer]) * sizeof(gpuPair) / constants::MB); - allocMemAsync(reinterpret_cast(&mNeighbourPairsDevice[layer]), (this->mNNeighbours[layer]) * sizeof(gpuPair), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); - GPUChkErrS(cudaMemsetAsync(mNeighbourPairsDevice[layer], -1, (this->mNNeighbours[layer]) * sizeof(gpuPair), mGpuStreams[layer].get())); - GPULog("gpu-allocation: reserving {} neighbours, for {:.2f} MB.", this->mNNeighbours[layer], (this->mNNeighbours[layer]) * sizeof(gpuPair) / constants::MB); - allocMemAsync(reinterpret_cast(&mNeighboursDevice[layer]), (this->mNNeighbours[layer]) * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + if (this->mNNeighbours[layer] == 0) { + mNeighboursDevice[layer] = nullptr; + GPUChkErrS(cudaMemcpyAsync(&mNeighboursDeviceArray[layer], &mNeighboursDevice[layer], sizeof(CellNeighbour*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); + return; + } + GPULog("gpu-allocation: reserving {} neighbours, for {:.2f} MB.", this->mNNeighbours[layer], (this->mNNeighbours[layer]) * sizeof(CellNeighbour) / constants::MB); + allocMemAsync(reinterpret_cast(&mNeighboursDevice[layer]), (this->mNNeighbours[layer]) * sizeof(CellNeighbour), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + GPUChkErrS(cudaMemsetAsync(mNeighboursDevice[layer], -1, (this->mNNeighbours[layer]) * sizeof(CellNeighbour), mGpuStreams[layer].get())); + GPUChkErrS(cudaMemcpyAsync(&mNeighboursDeviceArray[layer], &mNeighboursDevice[layer], sizeof(CellNeighbour*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } -template -void TimeFrameGPU::createTrackITSExtDevice(const size_t nSeeds) +template +void TimeFrameGPU::createTrackITSExtDevice(const size_t nSeeds) { GPUTimer timer("reserving tracks"); mNTracks = 0; @@ -470,135 +581,54 @@ void TimeFrameGPU::createTrackITSExtDevice(const size_t nSeeds) GPUChkErrS(cudaMemset(mTrackITSExtDevice, 0, mNTracks * sizeof(o2::its::TrackITSExt))); } -template -void TimeFrameGPU::createVtxTrackletsLUTDevice(const int32_t iteration) +template +void TimeFrameGPU::downloadCellsDevice() { - GPUTimer timer("creating vertexer tracklet LUTs"); - const int32_t ncls = this->mClusters[1].size(); - for (int32_t iMode{0}; iMode < 2; ++iMode) { - if (!iteration) { - GPULog("gpu-transfer: creating vertexer tracklets per cluster for {} elements for mode {}, for {:.2f} MB.", ncls, iMode, ncls * sizeof(int32_t) / constants::MB); - allocMemAsync(reinterpret_cast(&mNTrackletsPerClusterDevice[iMode]), ncls * sizeof(int32_t), mGpuStreams[iMode], this->hasFrameworkAllocator()); - - GPULog("gpu-transfer: creating vertexer tracklets per cluster sum for {} elements for mode {}, for {:.2f} MB.", ncls + 1, iMode, (ncls + 1) * sizeof(int32_t) / constants::MB); - allocMemAsync(reinterpret_cast(&mNTrackletsPerClusterSumDevice[iMode]), (ncls + 1) * sizeof(int32_t), mGpuStreams[iMode], this->hasFrameworkAllocator()); - - GPULog("gpu-transfer: creating vertexer tracklets per ROF for {} elements for mode {}, for {:.2f} MB.", this->mNrof + 1, iMode, (this->mNrof + 1) * sizeof(int32_t) / constants::MB); - allocMemAsync(reinterpret_cast(&mNTrackletsPerROFDevice[iMode]), (this->mNrof + 1) * sizeof(int32_t), mGpuStreams[iMode], this->hasFrameworkAllocator()); - } - GPUChkErrS(cudaMemsetAsync(mNTrackletsPerClusterDevice[iMode], 0, ncls * sizeof(int32_t), mGpuStreams[iMode].get())); - GPUChkErrS(cudaMemsetAsync(mNTrackletsPerClusterSumDevice[iMode], 0, (ncls + 1) * sizeof(int32_t), mGpuStreams[iMode].get())); - GPUChkErrS(cudaMemsetAsync(mNTrackletsPerROFDevice[iMode], 0, (this->mNrof + 1) * sizeof(int32_t), mGpuStreams[iMode].get())); - } - mGpuStreams[0].sync(); - mGpuStreams[1].sync(); - if (!iteration) { - allocMem(reinterpret_cast(&mNTrackletsPerClusterDeviceArray), mNTrackletsPerClusterDevice.size() * sizeof(int32_t*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaMemcpy(mNTrackletsPerClusterDeviceArray, mNTrackletsPerClusterDevice.data(), mNTrackletsPerClusterDevice.size() * sizeof(int32_t*), cudaMemcpyHostToDevice)); - - allocMem(reinterpret_cast(&mNTrackletsPerClusterSumDeviceArray), mNTrackletsPerClusterSumDevice.size() * sizeof(int32_t*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaMemcpy(mNTrackletsPerClusterSumDeviceArray, mNTrackletsPerClusterSumDevice.data(), mNTrackletsPerClusterSumDevice.size() * sizeof(int32_t*), cudaMemcpyHostToDevice)); - - allocMem(reinterpret_cast(&mNTrackletsPerROFDeviceArray), mNTrackletsPerROFDevice.size() * sizeof(int32_t*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaMemcpy(mNTrackletsPerROFDeviceArray, mNTrackletsPerROFDevice.data(), mNTrackletsPerROFDevice.size() * sizeof(int32_t*), cudaMemcpyHostToDevice)); - } -} - -template -void TimeFrameGPU::createVtxTrackletsBuffers(const int32_t iteration) -{ - GPUTimer timer("creating vertexer tracklet buffers"); - for (int32_t iMode{0}; iMode < 2; ++iMode) { - this->mTotalTracklets[iMode] = 0; - GPUChkErrS(cudaMemcpyAsync(&(this->mTotalTracklets[iMode]), mNTrackletsPerClusterSumDevice[iMode] + this->mClusters[1].size(), sizeof(int32_t), cudaMemcpyDeviceToHost, mGpuStreams[iMode].get())); - GPULog("gpu-transfer: creating vertexer tracklets buffer for {} elements on layer {}, for {:.2f} MB.", this->mTotalTracklets[iMode], iMode, this->mTotalTracklets[iMode] * sizeof(Tracklet) / constants::MB); - allocMemAsync(reinterpret_cast(&mTrackletsDevice[iMode]), this->mTotalTracklets[iMode] * sizeof(Tracklet), mGpuStreams[iMode], this->hasFrameworkAllocator()); - } - mGpuStreams[0].sync(); - mGpuStreams[1].sync(); - allocMem(reinterpret_cast(&mTrackletsDeviceArray), 2 * sizeof(Tracklet*), this->hasFrameworkAllocator()); - GPUChkErrS(cudaHostRegister(mTrackletsDevice.data(), 2 * sizeof(Tracklet*), cudaHostRegisterPortable)); - GPUChkErrS(cudaMemcpy(mTrackletsDeviceArray, mTrackletsDevice.data(), 2 * sizeof(Tracklet*), cudaMemcpyHostToDevice)); -} - -template -void TimeFrameGPU::createVtxLinesLUTDevice(const int32_t iteration) -{ - GPUTimer timer("creating vertexer lines LUT and used tracklets buffer"); - const int32_t ncls = this->mClusters[1].size(); - - GPULog("gpu-transfer: creating vertexer lines per cluster for {} elements , for {:.2f} MB.", ncls, ncls * sizeof(int32_t) / constants::MB); - allocMem(reinterpret_cast(&mNLinesPerClusterDevice), ncls * sizeof(int32_t), this->hasFrameworkAllocator()); - - GPULog("gpu-transfer: creating vertexer lines per cluster sum for {} elements , for {:.2f} MB.", ncls + 1, (ncls + 1) * sizeof(int32_t) / constants::MB); - allocMem(reinterpret_cast(&mNLinesPerClusterSumDevice), (ncls + 1) * sizeof(int32_t), this->hasFrameworkAllocator()); - - const int32_t ntrkls = this->mTotalTracklets[0]; - GPULog("gpu-transfer: creating vertexer used tracklets for {} elements , for {:.2f} MB.", ntrkls, ntrkls * sizeof(uint8_t) / constants::MB); - allocMem(reinterpret_cast(&mUsedTrackletsDevice), ntrkls * sizeof(uint8_t), this->hasFrameworkAllocator()); -} - -template -void TimeFrameGPU::createVtxLinesBuffer(const int32_t iteration) -{ - GPUTimer timer("creating vertexer lines buffer and resetting used tracklets"); - int32_t nlines = 0; - GPUChkErrS(cudaMemcpy(&nlines, mNLinesPerClusterDevice + this->mClusters[1].size(), sizeof(int32_t), cudaMemcpyDeviceToHost)); - this->mTotalLines = nlines; - GPULog("gpu-transfer: creating vertexer lines for {} elements , for {:.2f} MB.", nlines, nlines * sizeof(Line) / constants::MB); - allocMem(reinterpret_cast(&mLinesDevice), nlines * sizeof(Line), this->hasFrameworkAllocator()); - // reset used tracklets - GPUChkErrS(cudaMemset(mUsedTrackletsDevice, 0, this->mTotalTracklets[0] * sizeof(uint8_t))); -} - -template -void TimeFrameGPU::downloadCellsDevice() -{ - GPUTimer timer(mGpuStreams, "downloading cells", nLayers - 2); - for (int iLayer{0}; iLayer < nLayers - 2; ++iLayer) { - GPULog("gpu-transfer: downloading {} cells on layer: {}, for {:.2f} MB.", mNCells[iLayer], iLayer, mNCells[iLayer] * sizeof(CellSeedN) / constants::MB); + GPUTimer timer(mGpuStreams, "downloading cells", NLayers - 2); + for (int iLayer{0}; iLayer < NLayers - 2; ++iLayer) { + GPULog("gpu-transfer: downloading {} cells on layer: {}, for {:.2f} MB.", mNCells[iLayer], iLayer, mNCells[iLayer] * sizeof(CellSeed) / constants::MB); this->mCells[iLayer].resize(mNCells[iLayer]); - GPUChkErrS(cudaMemcpyAsync(this->mCells[iLayer].data(), this->mCellsDevice[iLayer], mNCells[iLayer] * sizeof(CellSeedN), cudaMemcpyDeviceToHost, mGpuStreams[iLayer].get())); + GPUChkErrS(cudaMemcpyAsync(this->mCells[iLayer].data(), this->mCellsDevice[iLayer], mNCells[iLayer] * sizeof(CellSeed), cudaMemcpyDeviceToHost, mGpuStreams[iLayer].get())); } } -template -void TimeFrameGPU::downloadCellsLUTDevice() +template +void TimeFrameGPU::downloadCellsLUTDevice() { - GPUTimer timer(mGpuStreams, "downloading cell luts", nLayers - 3); - for (auto iLayer{0}; iLayer < nLayers - 3; ++iLayer) { + GPUTimer timer(mGpuStreams, "downloading cell luts", NLayers - 3); + for (auto iLayer{0}; iLayer < NLayers - 3; ++iLayer) { GPULog("gpu-transfer: downloading cells lut on layer {} for {} elements", iLayer, (mNTracklets[iLayer + 1] + 1)); this->mCellsLookupTable[iLayer].resize(mNTracklets[iLayer + 1] + 1); GPUChkErrS(cudaMemcpyAsync(this->mCellsLookupTable[iLayer].data(), mCellsLUTDevice[iLayer + 1], (mNTracklets[iLayer + 1] + 1) * sizeof(int), cudaMemcpyDeviceToHost, mGpuStreams[iLayer].get())); } } -template -void TimeFrameGPU::downloadCellsNeighboursDevice(std::vector>>& neighbours, const int layer) +template +void TimeFrameGPU::downloadCellsNeighboursDevice(std::vector>& neighbours, const int layer) { GPUTimer timer(mGpuStreams[layer], "downloading neighbours from layer", layer); - GPULog("gpu-transfer: downloading {} neighbours, for {:.2f} MB.", neighbours[layer].size(), neighbours[layer].size() * sizeof(std::pair) / constants::MB); - GPUChkErrS(cudaMemcpyAsync(neighbours[layer].data(), mNeighbourPairsDevice[layer], neighbours[layer].size() * sizeof(gpuPair), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); + GPULog("gpu-transfer: downloading {} neighbours, for {:.2f} MB.", neighbours[layer].size(), neighbours[layer].size() * sizeof(CellNeighbour) / constants::MB); + GPUChkErrS(cudaMemcpyAsync(neighbours[layer].data(), mNeighboursDevice[layer], neighbours[layer].size() * sizeof(CellNeighbour), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); } -template -void TimeFrameGPU::downloadNeighboursLUTDevice(bounded_vector& lut, const int layer) +template +void TimeFrameGPU::downloadNeighboursLUTDevice(bounded_vector& lut, const int layer) { GPUTimer timer(mGpuStreams[layer], "downloading neighbours LUT from layer", layer); GPULog("gpu-transfer: downloading neighbours LUT for {} elements on layer {}, for {:.2f} MB.", lut.size(), layer, lut.size() * sizeof(int) / constants::MB); GPUChkErrS(cudaMemcpyAsync(lut.data(), mNeighboursLUTDevice[layer], lut.size() * sizeof(int), cudaMemcpyDeviceToHost, mGpuStreams[layer].get())); } -template -void TimeFrameGPU::downloadTrackITSExtDevice() +template +void TimeFrameGPU::downloadTrackITSExtDevice() { GPUTimer timer("downloading tracks"); GPULog("gpu-transfer: downloading {} tracks, for {:.2f} MB.", mTrackITSExt.size(), mTrackITSExt.size() * sizeof(o2::its::TrackITSExt) / constants::MB); GPUChkErrS(cudaMemcpy(mTrackITSExt.data(), mTrackITSExtDevice, mTrackITSExt.size() * sizeof(o2::its::TrackITSExt), cudaMemcpyDeviceToHost)); } -template -void TimeFrameGPU::unregisterHostMemory(const int maxLayers) +template +void TimeFrameGPU::unregisterHostMemory(const int maxLayers) { GPUTimer timer("unregistering host memory"); GPULog("unregistering host memory"); @@ -610,13 +640,13 @@ void TimeFrameGPU::unregisterHostMemory(const int maxLayers) } }; auto checkedUnregisterArray = [](auto& bits, auto& vec) { - if (bits.test(nLayers)) { + if (bits.test(NLayers)) { GPUChkErrS(cudaHostUnregister(vec.data())); - bits.reset(nLayers); + bits.reset(NLayers); } }; - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { + for (auto iLayer{0}; iLayer < NLayers; ++iLayer) { checkedUnregisterEntry(mPinnedUsedClusters, this->mUsedClusters, iLayer); checkedUnregisterEntry(mPinnedUnsortedClusters, this->mUnsortedClusters, iLayer); checkedUnregisterEntry(mPinnedClusters, this->mClusters, iLayer); @@ -646,74 +676,83 @@ constexpr auto makeIterTags(std::index_sequence) { return std::array{makeIterTag()...}; } -// FIXME: we have to be careful that the MaxIter does not diverge from the 4 here! -constexpr auto kIterTags = makeIterTags(std::make_index_sequence<4>{}); +constexpr auto kIterTags = makeIterTags(std::make_index_sequence{}); } // namespace detail -template -void TimeFrameGPU::pushMemoryStack(const int iteration) +template +void TimeFrameGPU::pushMemoryStack(const int iteration) { // mark the beginning of memory marked with MEMORY_STACK that can be discarded // after doing one iteration (this->mExternalAllocator)->pushTagOnStack(detail::kIterTags[iteration]); } -template -void TimeFrameGPU::popMemoryStack(const int iteration) +template +void TimeFrameGPU::popMemoryStack(const int iteration) { // pop all memory on the stack from this iteration (this->mExternalAllocator)->popTagOffStack(detail::kIterTags[iteration]); } -template -void TimeFrameGPU::initialise(const int iteration, - const TrackingParameters& trkParam, - const int maxLayers, - IndexTableUtilsN* utils, - const TimeFrameGPUParameters* gpuParam) +template +void TimeFrameGPU::initialise(const TrackingParameters& trkParam, int maxLayers) { - mGpuStreams.resize(nLayers); - o2::its::TimeFrame::initialise(iteration, trkParam, maxLayers); + mGpuStreams.resize(MaxStreams); + o2::its::TimeFrame::initialise(trkParam, maxLayers); +} + +template +void TimeFrameGPU::initialise(const TrackingParameters& trkParam, int maxLayers, int iteration) +{ + mGpuStreams.resize(MaxStreams); + o2::its::TimeFrame::initialise(trkParam, maxLayers, iteration); + if (iteration != constants::UnusedIndex && iteration < static_cast(mDeviceTrackerTopologyViews.size())) { + mDeviceTrackingTopologyView = mDeviceTrackerTopologyViews[iteration]; + } } -template -void TimeFrameGPU::syncStream(const size_t stream) +template +void TimeFrameGPU::syncStream(const size_t stream) { mGpuStreams[stream].sync(); } -template -void TimeFrameGPU::syncStreams(const bool device) +template +void TimeFrameGPU::syncStreams(const bool device) { mGpuStreams.sync(device); } -template -void TimeFrameGPU::waitEvent(const int stream, const int event) +template +void TimeFrameGPU::waitEvent(const int stream, const int event) { mGpuStreams.waitEvent(stream, event); } -template -void TimeFrameGPU::recordEvent(const int event) +template +void TimeFrameGPU::recordEvent(const int event) { mGpuStreams[event].record(); } -template -void TimeFrameGPU::recordEvents(const int start, const int end) +template +void TimeFrameGPU::recordEvents(const int start, const int end) { for (int i{start}; i < end; ++i) { recordEvent(i); } } -template -void TimeFrameGPU::wipe() +template +void TimeFrameGPU::wipe() { unregisterHostMemory(0); - o2::its::TimeFrame::wipe(); + o2::its::TimeFrame::wipe(); } template class TimeFrameGPU<7>; +// ALICE3 upgrade +#ifdef ENABLE_UPGRADES +template class TimeFrameGPU<11>; +#endif } // namespace o2::its::gpu diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TracerGPU.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TracerGPU.cu deleted file mode 100644 index 7c42658242231..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TracerGPU.cu +++ /dev/null @@ -1,48 +0,0 @@ -// 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. - -#include -#include "ITStrackingGPU/TracerGPU.h" - -#if !defined(__HIPCC__) && defined(__USE_GPU_TRACER__) -#include - -constexpr uint32_t colors[] = {0xff00ff00, 0xff0000ff, 0xffffff00, 0xffff00ff, 0xff00ffff, 0xffff0000, 0xffffffff}; -constexpr int num_colors = sizeof(colors) / sizeof(uint32_t); - -namespace o2 -{ -namespace its -{ -namespace gpu -{ -Tracer::Tracer(const char* name, int color_id) -{ - color_id = color_id % num_colors; - nvtxEventAttributes_t eventAttrib = {0}; - eventAttrib.version = NVTX_VERSION; - eventAttrib.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE; - eventAttrib.colorType = NVTX_COLOR_ARGB; - eventAttrib.color = colors[color_id]; - eventAttrib.messageType = NVTX_MESSAGE_TYPE_ASCII; - eventAttrib.message.ascii = name; - nvtxRangePushEx(&eventAttrib); -} - -Tracer::~Tracer() -{ - nvtxRangePop(); -} - -} // namespace gpu -} // namespace its -} // namespace o2 -#endif diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx index 42d2227de60f8..141d558712e6d 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx @@ -10,89 +10,87 @@ // or submit itself to any jurisdiction. /// -#include -#include #include -#include "DataFormatsITS/TrackITS.h" - #include "ITStrackingGPU/TrackerTraitsGPU.h" #include "ITStrackingGPU/TrackingKernels.h" -#include "ITStracking/TrackingConfigParam.h" -#include "ITStracking/Constants.h" +#include "ITStracking/Configuration.h" namespace o2::its { -template -void TrackerTraitsGPU::initialiseTimeFrame(const int iteration) +template +void TrackerTraitsGPU::initialiseTimeFrame(const int iteration) { - mTimeFrameGPU->initialise(iteration, this->mTrkParams[iteration], nLayers); - // on default stream - mTimeFrameGPU->loadVertices(iteration); - mTimeFrameGPU->loadIndexTableUtils(iteration); - mTimeFrameGPU->loadMultiplicityCutMask(iteration); - // pinned on host - mTimeFrameGPU->createUsedClustersDeviceArray(iteration); - mTimeFrameGPU->createClustersDeviceArray(iteration); - mTimeFrameGPU->createUnsortedClustersDeviceArray(iteration); - mTimeFrameGPU->createClustersIndexTablesArray(iteration); - mTimeFrameGPU->createTrackingFrameInfoDeviceArray(iteration); - mTimeFrameGPU->createROFrameClustersDeviceArray(iteration); - // device array - mTimeFrameGPU->createTrackletsLUTDeviceArray(iteration); - mTimeFrameGPU->createTrackletsBuffersArray(iteration); - mTimeFrameGPU->createCellsBuffersArray(iteration); - mTimeFrameGPU->createCellsLUTDeviceArray(iteration); + mTimeFrameGPU->initialise(this->mTrkParams[iteration], NLayers, iteration); + + if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { + // on default stream + mTimeFrameGPU->loadVertices(); + // TODO these tables can be put in persistent memory + mTimeFrameGPU->loadROFOverlapTable(); // this can be put in constant memory actually + mTimeFrameGPU->loadROFVertexLookupTable(); + mTimeFrameGPU->loadTrackingTopologies(); + // once the tables are in persistent memory just update the vertex one + // mTimeFrameGPU->updateROFVertexLookupTable(); + mTimeFrameGPU->loadIndexTableUtils(); + // pinned on host + mTimeFrameGPU->createUsedClustersDeviceArray(); + mTimeFrameGPU->createClustersDeviceArray(); + mTimeFrameGPU->createUnsortedClustersDeviceArray(); + mTimeFrameGPU->createClustersIndexTablesArray(); + mTimeFrameGPU->createTrackingFrameInfoDeviceArray(); + mTimeFrameGPU->createROFrameClustersDeviceArray(); + // device array + mTimeFrameGPU->createTrackletsLUTDeviceArray(); + mTimeFrameGPU->createTrackletsBuffersArray(); + mTimeFrameGPU->createCellsBuffersArray(); + mTimeFrameGPU->createCellsLUTDeviceArray(); + } + if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass] || this->mTrkParams[iteration].PassFlags[IterationStep::UseUPCMask]) { + mTimeFrameGPU->loadROFCutMask(iteration); + } // push every create artefact on the stack mTimeFrameGPU->pushMemoryStack(iteration); } -template -void TrackerTraitsGPU::adoptTimeFrame(TimeFrame* tf) +template +void TrackerTraitsGPU::adoptTimeFrame(TimeFrame* tf) { - mTimeFrameGPU = static_cast*>(tf); - this->mTimeFrame = static_cast*>(tf); + mTimeFrameGPU = static_cast*>(tf); + this->mTimeFrame = static_cast*>(tf); } -template -void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int iROFslice, int iVertex) +template +void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int iVertex) { - const auto& conf = o2::its::ITSGpuTrackingParamConfig::Instance(); - - int startROF{0}; - int endROF{mTimeFrameGPU->getNrof()}; - - // start by queuing loading needed of two last layers - for (int iLayer{nLayers}; iLayer-- > nLayers - 2;) { - mTimeFrameGPU->createUsedClustersDevice(iteration, iLayer); - mTimeFrameGPU->loadClustersDevice(iteration, iLayer); - mTimeFrameGPU->loadClustersIndexTables(iteration, iLayer); - mTimeFrameGPU->loadROFrameClustersDevice(iteration, iLayer); + const auto topology = mTimeFrameGPU->getDeviceTrackingTopologyView(); + const auto hostTopology = mTimeFrameGPU->getTrackingTopologyView(); + for (int iLayer{0}; iLayer < this->mTrkParams[iteration].NLayers; ++iLayer) { + if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { + mTimeFrameGPU->createUsedClustersDevice(iLayer); + mTimeFrameGPU->loadClustersDevice(iLayer); + mTimeFrameGPU->loadClustersIndexTables(iLayer); + mTimeFrameGPU->loadROFrameClustersDevice(iLayer); + } mTimeFrameGPU->recordEvent(iLayer); } - for (int iLayer{this->mTrkParams[iteration].TrackletsPerRoad()}; iLayer--;) { - if (iLayer) { // queue loading data of next layer in parallel, this the copies are overlapping with computation kernels - mTimeFrameGPU->createUsedClustersDevice(iteration, iLayer - 1); - mTimeFrameGPU->loadClustersDevice(iteration, iLayer - 1); - mTimeFrameGPU->loadClustersIndexTables(iteration, iLayer - 1); - mTimeFrameGPU->loadROFrameClustersDevice(iteration, iLayer - 1); - mTimeFrameGPU->recordEvent(iLayer - 1); - } - mTimeFrameGPU->createTrackletsLUTDevice(iteration, iLayer); - mTimeFrameGPU->waitEvent(iLayer, iLayer + 1); // wait stream until all data is available - countTrackletsInROFsHandler(mTimeFrameGPU->getDeviceIndexTableUtils(), - mTimeFrameGPU->getDeviceMultCutMask(), - iLayer, - startROF, - endROF, - mTimeFrameGPU->getNrof(), - this->mTrkParams[iteration].DeltaROF, + for (int transitionId{0}; transitionId < hostTopology.nTransitions; ++transitionId) { + const auto transition = hostTopology.getTransition(transitionId); + mTimeFrameGPU->createTrackletsLUTDevice(this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass], transitionId); + mTimeFrameGPU->waitEvent(transitionId, transition.fromLayer); + mTimeFrameGPU->waitEvent(transitionId, transition.toLayer); + countTrackletsInROFsHandler(mTimeFrameGPU->getDeviceIndexTableUtils(), + mTimeFrameGPU->getDeviceROFMaskTableView(), + transitionId, + transition.fromLayer, + transition.toLayer, + mTimeFrameGPU->getDeviceROFOverlapTableView(), + mTimeFrameGPU->getDeviceROFVertexLookupTableView(), iVertex, mTimeFrameGPU->getDeviceVertices(), mTimeFrameGPU->getDeviceROFramesPV(), - mTimeFrameGPU->getPrimaryVerticesNum(), mTimeFrameGPU->getDeviceArrayClusters(), mTimeFrameGPU->getClusterSizes(), mTimeFrameGPU->getDeviceROFrameClusters(), @@ -100,34 +98,33 @@ void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int i mTimeFrameGPU->getDeviceArrayClustersIndexTables(), mTimeFrameGPU->getDeviceArrayTrackletsLUT(), mTimeFrameGPU->getDeviceTrackletsLUTs(), - iteration, + this->mTrkParams[iteration].PassFlags[IterationStep::SelectUPCVertices], this->mTrkParams[iteration].NSigmaCut, - mTimeFrameGPU->getPhiCuts(), + topology, + mTimeFrameGPU->getTransitionPhiCuts(), this->mTrkParams[iteration].PVres, mTimeFrameGPU->getMinRs(), mTimeFrameGPU->getMaxRs(), mTimeFrameGPU->getPositionResolutions(), this->mTrkParams[iteration].LayerRadii, - mTimeFrameGPU->getMSangles(), + mTimeFrameGPU->getTransitionMSAngles(), mTimeFrameGPU->getFrameworkAllocator(), - conf.nBlocksLayerTracklets[iteration], - conf.nThreadsLayerTracklets[iteration], mTimeFrameGPU->getStreams()); - mTimeFrameGPU->createTrackletsBuffers(iLayer); - if (mTimeFrameGPU->getNTracklets()[iLayer] == 0) { + mTimeFrameGPU->createTrackletsBuffers(transitionId); + if (mTimeFrameGPU->getNTracklets()[transitionId] == 0) { + mTimeFrameGPU->recordEvent(transitionId); continue; } - computeTrackletsInROFsHandler(mTimeFrameGPU->getDeviceIndexTableUtils(), - mTimeFrameGPU->getDeviceMultCutMask(), - iLayer, - startROF, - endROF, - mTimeFrameGPU->getNrof(), - this->mTrkParams[iteration].DeltaROF, + computeTrackletsInROFsHandler(mTimeFrameGPU->getDeviceIndexTableUtils(), + mTimeFrameGPU->getDeviceROFMaskTableView(), + transitionId, + transition.fromLayer, + transition.toLayer, + mTimeFrameGPU->getDeviceROFOverlapTableView(), + mTimeFrameGPU->getDeviceROFVertexLookupTableView(), iVertex, mTimeFrameGPU->getDeviceVertices(), mTimeFrameGPU->getDeviceROFramesPV(), - mTimeFrameGPU->getPrimaryVerticesNum(), mTimeFrameGPU->getDeviceArrayClusters(), mTimeFrameGPU->getClusterSizes(), mTimeFrameGPU->getDeviceROFrameClusters(), @@ -138,184 +135,205 @@ void TrackerTraitsGPU::computeLayerTracklets(const int iteration, int i mTimeFrameGPU->getNTracklets(), mTimeFrameGPU->getDeviceArrayTrackletsLUT(), mTimeFrameGPU->getDeviceTrackletsLUTs(), - iteration, + this->mTrkParams[iteration].PassFlags[IterationStep::SelectUPCVertices], this->mTrkParams[iteration].NSigmaCut, - mTimeFrameGPU->getPhiCuts(), + topology, + mTimeFrameGPU->getTransitionPhiCuts(), this->mTrkParams[iteration].PVres, mTimeFrameGPU->getMinRs(), mTimeFrameGPU->getMaxRs(), mTimeFrameGPU->getPositionResolutions(), this->mTrkParams[iteration].LayerRadii, - mTimeFrameGPU->getMSangles(), + mTimeFrameGPU->getTransitionMSAngles(), mTimeFrameGPU->getFrameworkAllocator(), - conf.nBlocksLayerTracklets[iteration], - conf.nThreadsLayerTracklets[iteration], mTimeFrameGPU->getStreams()); + mTimeFrameGPU->recordEvent(transitionId); } } -template -void TrackerTraitsGPU::computeLayerCells(const int iteration) +template +void TrackerTraitsGPU::computeLayerCells(const int iteration) { - auto& conf = o2::its::ITSGpuTrackingParamConfig::Instance(); - - // start by queuing loading needed of three last layers - for (int iLayer{nLayers}; iLayer-- > nLayers - 3;) { - mTimeFrameGPU->loadUnsortedClustersDevice(iteration, iLayer); - mTimeFrameGPU->loadTrackingFrameInfoDevice(iteration, iLayer); + const auto topology = mTimeFrameGPU->getDeviceTrackingTopologyView(); + const auto hostTopology = mTimeFrameGPU->getTrackingTopologyView(); + for (int iLayer{0}; iLayer < this->mTrkParams[iteration].NLayers; ++iLayer) { + if (this->mTrkParams[iteration].PassFlags[IterationStep::FirstPass]) { + mTimeFrameGPU->loadUnsortedClustersDevice(iLayer); + mTimeFrameGPU->loadTrackingFrameInfoDevice(iLayer); + } mTimeFrameGPU->recordEvent(iLayer); } - for (int iLayer{this->mTrkParams[iteration].CellsPerRoad()}; iLayer--;) { - if (iLayer) { - mTimeFrameGPU->loadUnsortedClustersDevice(iteration, iLayer - 1); - mTimeFrameGPU->loadTrackingFrameInfoDevice(iteration, iLayer - 1); - mTimeFrameGPU->recordEvent(iLayer - 1); - } - - // if there are no tracklets skip entirely - const int currentLayerTrackletsNum{static_cast(mTimeFrameGPU->getNTracklets()[iLayer])}; - if (!currentLayerTrackletsNum || !mTimeFrameGPU->getNTracklets()[iLayer + 1]) { - mTimeFrameGPU->getNCells()[iLayer] = 0; + for (int cellTopologyId{hostTopology.nCells}; cellTopologyId--;) { + const auto cellTopology = hostTopology.getCell(cellTopologyId); + const auto first = hostTopology.getTransition(cellTopology.firstTransition); + const auto second = hostTopology.getTransition(cellTopology.secondTransition); + const int currentLayerTrackletsNum{static_cast(mTimeFrameGPU->getNTracklets()[cellTopology.firstTransition])}; + if (!currentLayerTrackletsNum || !mTimeFrameGPU->getNTracklets()[cellTopology.secondTransition]) { + mTimeFrameGPU->getNCells()[cellTopologyId] = 0; continue; } - mTimeFrameGPU->createCellsLUTDevice(iLayer); - mTimeFrameGPU->waitEvent(iLayer, iLayer + 1); // wait stream until all data is available - mTimeFrameGPU->waitEvent(iLayer, iLayer + 2); // wait stream until all data is available - countCellsHandler(mTimeFrameGPU->getDeviceArrayClusters(), + mTimeFrameGPU->createCellsLUTDevice(cellTopologyId); + mTimeFrameGPU->waitEvent(cellTopologyId, cellTopology.firstTransition); + mTimeFrameGPU->waitEvent(cellTopologyId, cellTopology.secondTransition); + mTimeFrameGPU->waitEvent(cellTopologyId, first.fromLayer); + mTimeFrameGPU->waitEvent(cellTopologyId, first.toLayer); + mTimeFrameGPU->waitEvent(cellTopologyId, second.toLayer); + countCellsHandler(mTimeFrameGPU->getDeviceArrayClusters(), mTimeFrameGPU->getDeviceArrayUnsortedClusters(), mTimeFrameGPU->getDeviceArrayTrackingFrameInfo(), mTimeFrameGPU->getDeviceArrayTracklets(), mTimeFrameGPU->getDeviceArrayTrackletsLUT(), currentLayerTrackletsNum, - iLayer, + cellTopologyId, + topology, nullptr, mTimeFrameGPU->getDeviceArrayCellsLUT(), - mTimeFrameGPU->getDeviceCellLUTs()[iLayer], - this->mTrkParams[iteration].DeltaROF, + mTimeFrameGPU->getDeviceCellLUTs()[cellTopologyId], this->mBz, this->mTrkParams[iteration].MaxChi2ClusterAttachment, this->mTrkParams[iteration].CellDeltaTanLambdaSigma, this->mTrkParams[iteration].NSigmaCut, + this->mTrkParams[iteration].LayerxX0, mTimeFrameGPU->getFrameworkAllocator(), - conf.nBlocksLayerCells[iteration], - conf.nThreadsLayerCells[iteration], mTimeFrameGPU->getStreams()); - mTimeFrameGPU->createCellsBuffers(iLayer); - if (mTimeFrameGPU->getNCells()[iLayer] == 0) { + mTimeFrameGPU->createCellsBuffers(cellTopologyId); + if (mTimeFrameGPU->getNCells()[cellTopologyId] == 0) { + mTimeFrameGPU->recordEvent(cellTopologyId); continue; } - computeCellsHandler(mTimeFrameGPU->getDeviceArrayClusters(), + computeCellsHandler(mTimeFrameGPU->getDeviceArrayClusters(), mTimeFrameGPU->getDeviceArrayUnsortedClusters(), mTimeFrameGPU->getDeviceArrayTrackingFrameInfo(), mTimeFrameGPU->getDeviceArrayTracklets(), mTimeFrameGPU->getDeviceArrayTrackletsLUT(), currentLayerTrackletsNum, - iLayer, - mTimeFrameGPU->getDeviceCells()[iLayer], + cellTopologyId, + topology, + mTimeFrameGPU->getDeviceCells()[cellTopologyId], mTimeFrameGPU->getDeviceArrayCellsLUT(), - mTimeFrameGPU->getDeviceCellLUTs()[iLayer], - this->mTrkParams[iteration].DeltaROF, + mTimeFrameGPU->getDeviceCellLUTs()[cellTopologyId], this->mBz, this->mTrkParams[iteration].MaxChi2ClusterAttachment, this->mTrkParams[iteration].CellDeltaTanLambdaSigma, this->mTrkParams[iteration].NSigmaCut, - conf.nBlocksLayerCells[iteration], - conf.nThreadsLayerCells[iteration], + this->mTrkParams[iteration].LayerxX0, mTimeFrameGPU->getStreams()); + mTimeFrameGPU->recordEvent(cellTopologyId); } + mTimeFrameGPU->syncStreams(false); } -template -void TrackerTraitsGPU::findCellsNeighbours(const int iteration) +template +void TrackerTraitsGPU::findCellsNeighbours(const int iteration) { - const auto& conf = o2::its::ITSGpuTrackingParamConfig::Instance(); + const auto hostTopology = mTimeFrameGPU->getTrackingTopologyView(); + for (int outerLayer{0}; outerLayer < NLayers; ++outerLayer) { + for (int targetCellTopologyId{0}; targetCellTopologyId < hostTopology.nCells; ++targetCellTopologyId) { + const auto targetCellTopology = hostTopology.getCell(targetCellTopologyId); + if (targetCellTopology.hitLayerMask.last() != outerLayer) { + continue; + } + const int targetCellsNum{static_cast(mTimeFrameGPU->getNCells()[targetCellTopologyId])}; + if (!targetCellsNum) { + mTimeFrameGPU->getNNeighbours()[targetCellTopologyId] = 0; + mTimeFrameGPU->recordEvent(targetCellTopologyId); + continue; + } + mTimeFrameGPU->createNeighboursIndexTablesDevice(targetCellTopologyId); + mTimeFrameGPU->createNeighboursLUTDevice(targetCellTopologyId, targetCellsNum); - for (int iLayer{0}; iLayer < this->mTrkParams[iteration].NeighboursPerRoad(); ++iLayer) { - const int currentLayerCellsNum{static_cast(mTimeFrameGPU->getNCells()[iLayer])}; - const int nextLayerCellsNum{static_cast(mTimeFrameGPU->getNCells()[iLayer + 1])}; - if (!nextLayerCellsNum || !currentLayerCellsNum) { - mTimeFrameGPU->getNNeighbours()[iLayer] = 0; - continue; - } - mTimeFrameGPU->createNeighboursIndexTablesDevice(iLayer); - mTimeFrameGPU->createNeighboursLUTDevice(iLayer, nextLayerCellsNum); - countCellNeighboursHandler(mTimeFrameGPU->getDeviceArrayCells(), - mTimeFrameGPU->getDeviceNeighboursLUT(iLayer), // LUT is initialised here. - mTimeFrameGPU->getDeviceArrayCellsLUT(), - mTimeFrameGPU->getDeviceNeighbourPairs(iLayer), - mTimeFrameGPU->getDeviceNeighboursIndexTables(iLayer), - (const Tracklet**)mTimeFrameGPU->getDeviceArrayTracklets(), - this->mTrkParams[0].DeltaROF, - this->mTrkParams[0].MaxChi2ClusterAttachment, - this->mBz, - iLayer, - currentLayerCellsNum, - nextLayerCellsNum, - 1e2, - mTimeFrameGPU->getFrameworkAllocator(), - conf.nBlocksFindNeighbours[iteration], - conf.nThreadsFindNeighbours[iteration], - mTimeFrameGPU->getStream(iLayer)); - mTimeFrameGPU->createNeighboursDevice(iLayer); - if (mTimeFrameGPU->getNNeighbours()[iLayer] == 0) { - continue; + for (int sourceCellTopologyId{0}; sourceCellTopologyId < hostTopology.nCells; ++sourceCellTopologyId) { + const auto sourceCellTopology = hostTopology.getCell(sourceCellTopologyId); + const int sourceCellsNum{static_cast(mTimeFrameGPU->getNCells()[sourceCellTopologyId])}; + if (!sourceCellsNum || sourceCellTopology.secondTransition != targetCellTopology.firstTransition) { + continue; + } + mTimeFrameGPU->waitEvent(targetCellTopologyId, sourceCellTopologyId); + countCellNeighboursHandler(mTimeFrameGPU->getDeviceArrayCells(), + mTimeFrameGPU->getDeviceNeighboursIndexTables(targetCellTopologyId), + mTimeFrameGPU->getDeviceArrayCellsLUT(), + sourceCellTopologyId, + targetCellTopologyId, + this->mTrkParams[iteration].MaxChi2ClusterAttachment, + this->mBz, + sourceCellsNum, + mTimeFrameGPU->getStream(targetCellTopologyId)); + } + + scanCellNeighboursHandler(mTimeFrameGPU->getDeviceNeighboursIndexTables(targetCellTopologyId), + mTimeFrameGPU->getDeviceNeighboursLUT(targetCellTopologyId), + targetCellsNum, + mTimeFrameGPU->getFrameworkAllocator(), + mTimeFrameGPU->getStream(targetCellTopologyId)); + + mTimeFrameGPU->createNeighboursDevice(targetCellTopologyId); + if (mTimeFrameGPU->getNNeighbours()[targetCellTopologyId] == 0) { + mTimeFrameGPU->recordEvent(targetCellTopologyId); + continue; + } + + for (int sourceCellTopologyId{0}; sourceCellTopologyId < hostTopology.nCells; ++sourceCellTopologyId) { + const auto sourceCellTopology = hostTopology.getCell(sourceCellTopologyId); + const int sourceCellsNum{static_cast(mTimeFrameGPU->getNCells()[sourceCellTopologyId])}; + if (!sourceCellsNum || sourceCellTopology.secondTransition != targetCellTopology.firstTransition) { + continue; + } + computeCellNeighboursHandler(mTimeFrameGPU->getDeviceArrayCells(), + mTimeFrameGPU->getDeviceNeighboursIndexTables(targetCellTopologyId), + mTimeFrameGPU->getDeviceArrayCellsLUT(), + mTimeFrameGPU->getDeviceNeighbours(targetCellTopologyId), + sourceCellTopologyId, + targetCellTopologyId, + this->mTrkParams[iteration].MaxChi2ClusterAttachment, + this->mBz, + sourceCellsNum, + mTimeFrameGPU->getStream(targetCellTopologyId)); + } + mTimeFrameGPU->recordEvent(targetCellTopologyId); } - computeCellNeighboursHandler(mTimeFrameGPU->getDeviceArrayCells(), - mTimeFrameGPU->getDeviceNeighboursLUT(iLayer), - mTimeFrameGPU->getDeviceArrayCellsLUT(), - mTimeFrameGPU->getDeviceNeighbourPairs(iLayer), - mTimeFrameGPU->getDeviceNeighboursIndexTables(iLayer), - (const Tracklet**)mTimeFrameGPU->getDeviceArrayTracklets(), - this->mTrkParams[0].DeltaROF, - this->mTrkParams[0].MaxChi2ClusterAttachment, - this->mBz, - iLayer, - currentLayerCellsNum, - nextLayerCellsNum, - 1e2, - conf.nBlocksFindNeighbours[iteration], - conf.nThreadsFindNeighbours[iteration], - mTimeFrameGPU->getStream(iLayer)); - mTimeFrameGPU->getArrayNNeighbours()[iLayer] = filterCellNeighboursHandler(mTimeFrameGPU->getDeviceNeighbourPairs(iLayer), - mTimeFrameGPU->getDeviceNeighbours(iLayer), - mTimeFrameGPU->getArrayNNeighbours()[iLayer], - mTimeFrameGPU->getStream(iLayer), - mTimeFrameGPU->getFrameworkAllocator()); } mTimeFrameGPU->syncStreams(false); } -template -void TrackerTraitsGPU::findRoads(const int iteration) +template +void TrackerTraitsGPU::findRoads(const int iteration) { - auto& conf = o2::its::ITSGpuTrackingParamConfig::Instance(); + bounded_vector> firstClusters(this->mTrkParams[iteration].NLayers, bounded_vector(this->getMemoryPool().get()), this->getMemoryPool().get()); + bounded_vector> sharedFirstClusters(this->mTrkParams[iteration].NLayers, bounded_vector(this->getMemoryPool().get()), this->getMemoryPool().get()); + firstClusters.resize(this->mTrkParams[iteration].NLayers); + sharedFirstClusters.resize(this->mTrkParams[iteration].NLayers); + const auto hostTopology = mTimeFrameGPU->getTrackingTopologyView(); for (int startLevel{this->mTrkParams[iteration].CellsPerRoad()}; startLevel >= this->mTrkParams[iteration].CellMinimumLevel(); --startLevel) { - const int minimumLayer{startLevel - 1}; - bounded_vector> trackSeeds(this->getMemoryPool().get()); - for (int startLayer{this->mTrkParams[iteration].CellsPerRoad() - 1}; startLayer >= minimumLayer; --startLayer) { - if ((this->mTrkParams[iteration].StartLayerMask & (1 << (startLayer + 2))) == 0) { + bounded_vector> trackSeeds(this->getMemoryPool().get()); + for (int startCellTopologyId{0}; startCellTopologyId < hostTopology.nCells; ++startCellTopologyId) { + const int startLayer = hostTopology.getCell(startCellTopologyId).hitLayerMask.last(); + if (!(this->mTrkParams[iteration].StartLayerMask.has(startLayer)) || mTimeFrameGPU->getNCells()[startCellTopologyId] == 0) { continue; } - processNeighboursHandler(startLayer, - startLevel, + processNeighboursHandler(startLevel, + startCellTopologyId, mTimeFrameGPU->getDeviceArrayCells(), - mTimeFrameGPU->getDeviceCells()[startLayer], - mTimeFrameGPU->getArrayNCells(), + mTimeFrameGPU->getDeviceCells()[startCellTopologyId], + nullptr, + nullptr, + mTimeFrameGPU->getArrayNCells().data(), (const uint8_t**)mTimeFrameGPU->getDeviceArrayUsedClusters(), - mTimeFrameGPU->getDeviceNeighboursAll(), - mTimeFrameGPU->getDeviceNeighboursLUTs(), + mTimeFrameGPU->getDeviceArrayNeighbours(), + mTimeFrameGPU->getDeviceArrayNeighboursCellLUT(), mTimeFrameGPU->getDeviceArrayTrackingFrameInfo(), trackSeeds, this->mBz, - this->mTrkParams[0].MaxChi2ClusterAttachment, - this->mTrkParams[0].MaxChi2NDF, + this->mTrkParams[iteration].MaxChi2ClusterAttachment, + this->mTrkParams[iteration].MaxChi2NDF, + this->mTrkParams[iteration].MaxHoles, + this->mTrkParams[iteration].MinTrackLength, + this->mTrkParams[iteration].HoleLayerMask, + this->mTrkParams[iteration].LayerxX0, mTimeFrameGPU->getDevicePropagator(), - this->mTrkParams[0].CorrType, - mTimeFrameGPU->getFrameworkAllocator(), - conf.nBlocksProcessNeighbours[iteration], - conf.nThreadsProcessNeighbours[iteration]); + this->mTrkParams[iteration].CorrType, + mTimeFrameGPU->getFrameworkAllocator()); } // fixme: I don't want to move tracks back and forth, but I need a way to use a thrust::allocator that is aware of our managed memory. if (trackSeeds.empty()) { @@ -332,19 +350,18 @@ void TrackerTraitsGPU::findRoads(const int iteration) mTimeFrameGPU->getDeviceTrackSeedsLUT(), this->mTrkParams[iteration].LayerRadii, this->mTrkParams[iteration].MinPt, + this->mTrkParams[iteration].LayerxX0, trackSeeds.size(), this->mBz, startLevel, - this->mTrkParams[0].MaxChi2ClusterAttachment, - this->mTrkParams[0].MaxChi2NDF, - this->mTrkParams[0].ReseedIfShorter, - this->mTrkParams[0].RepeatRefitOut, - this->mTrkParams[0].ShiftRefToCluster, + this->mTrkParams[iteration].MaxChi2ClusterAttachment, + this->mTrkParams[iteration].MaxChi2NDF, + this->mTrkParams[iteration].ReseedIfShorter, + this->mTrkParams[iteration].RepeatRefitOut, + this->mTrkParams[iteration].ShiftRefToCluster, mTimeFrameGPU->getDevicePropagator(), - this->mTrkParams[0].CorrType, - mTimeFrameGPU->getFrameworkAllocator(), - conf.nBlocksTracksSeeds[iteration], - conf.nThreadsTracksSeeds[iteration]); + this->mTrkParams[iteration].CorrType, + mTimeFrameGPU->getFrameworkAllocator()); mTimeFrameGPU->createTrackITSExtDevice(trackSeeds.size()); computeTrackSeedHandler(mTimeFrameGPU->getDeviceTrackSeeds(), mTimeFrameGPU->getDeviceArrayTrackingFrameInfo(), @@ -353,96 +370,57 @@ void TrackerTraitsGPU::findRoads(const int iteration) mTimeFrameGPU->getDeviceTrackSeedsLUT(), this->mTrkParams[iteration].LayerRadii, this->mTrkParams[iteration].MinPt, + this->mTrkParams[iteration].LayerxX0, trackSeeds.size(), mTimeFrameGPU->getNTrackSeeds(), this->mBz, startLevel, - this->mTrkParams[0].MaxChi2ClusterAttachment, - this->mTrkParams[0].MaxChi2NDF, - this->mTrkParams[0].ReseedIfShorter, - this->mTrkParams[0].RepeatRefitOut, - this->mTrkParams[0].ShiftRefToCluster, + this->mTrkParams[iteration].MaxChi2ClusterAttachment, + this->mTrkParams[iteration].MaxChi2NDF, + this->mTrkParams[iteration].ReseedIfShorter, + this->mTrkParams[iteration].RepeatRefitOut, + this->mTrkParams[iteration].ShiftRefToCluster, mTimeFrameGPU->getDevicePropagator(), - this->mTrkParams[0].CorrType, - mTimeFrameGPU->getFrameworkAllocator(), - conf.nBlocksTracksSeeds[iteration], - conf.nThreadsTracksSeeds[iteration]); + this->mTrkParams[iteration].CorrType, + mTimeFrameGPU->getFrameworkAllocator()); mTimeFrameGPU->downloadTrackITSExtDevice(); auto& tracks = mTimeFrameGPU->getTrackITSExt(); - - for (auto& track : tracks) { - if (!track.getChi2()) { - continue; // this is to skip the unset tracks that are put at the beginning of the vector by the sorting. To see if this can be optimised. - } - int nShared = 0; - bool isFirstShared{false}; - for (int iLayer{0}; iLayer < this->mTrkParams[0].NLayers; ++iLayer) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - nShared += int(mTimeFrameGPU->isClusterUsed(iLayer, track.getClusterIndex(iLayer))); - isFirstShared |= !iLayer && mTimeFrameGPU->isClusterUsed(iLayer, track.getClusterIndex(iLayer)); - } - - if (nShared > this->mTrkParams[0].ClusterSharing) { - continue; - } - - std::array rofs{INT_MAX, INT_MAX, INT_MAX}; - for (int iLayer{0}; iLayer < this->mTrkParams[0].NLayers; ++iLayer) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - mTimeFrameGPU->markUsedCluster(iLayer, track.getClusterIndex(iLayer)); - int currentROF = mTimeFrameGPU->getClusterROF(iLayer, track.getClusterIndex(iLayer)); - for (int iR{0}; iR < 3; ++iR) { - if (rofs[iR] == INT_MAX) { - rofs[iR] = currentROF; - } - if (rofs[iR] == currentROF) { - break; - } - } - } - if (rofs[2] != INT_MAX) { - continue; - } - if (rofs[1] != INT_MAX) { - track.setNextROFbit(); - } - mTimeFrameGPU->getTracks(std::min(rofs[0], rofs[1])).emplace_back(track); - } + this->acceptTracks(iteration, tracks, firstClusters); mTimeFrameGPU->loadUsedClustersDevice(); } + this->markTracks(iteration); // wipe the artefact memory mTimeFrameGPU->popMemoryStack(iteration); }; -template -int TrackerTraitsGPU::getTFNumberOfClusters() const +template +int TrackerTraitsGPU::getTFNumberOfClusters() const { return mTimeFrameGPU->getNumberOfClusters(); } -template -int TrackerTraitsGPU::getTFNumberOfTracklets() const +template +int TrackerTraitsGPU::getTFNumberOfTracklets() const { return std::accumulate(mTimeFrameGPU->getNTracklets().begin(), mTimeFrameGPU->getNTracklets().end(), 0); } -template -int TrackerTraitsGPU::getTFNumberOfCells() const +template +int TrackerTraitsGPU::getTFNumberOfCells() const { return mTimeFrameGPU->getNumberOfCells(); } -template -void TrackerTraitsGPU::setBz(float bz) +template +void TrackerTraitsGPU::setBz(float bz) { this->mBz = bz; mTimeFrameGPU->setBz(bz); } template class TrackerTraitsGPU<7>; +#ifdef ENABLE_UPGRADES +template class TrackerTraitsGPU<11>; +#endif } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu index 525b37eb52891..571afe08fc209 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu @@ -31,6 +31,7 @@ #include "ITStracking/Tracklet.h" #include "ITStracking/Cluster.h" #include "ITStracking/Cell.h" +#include "ITStracking/TrackHelpers.h" #include "DataFormatsITS/TrackITS.h" #include "ITStrackingGPU/TrackingKernels.h" #include "ITStrackingGPU/Utils.h" @@ -46,182 +47,6 @@ namespace o2::its namespace gpu { -GPUdii() bool fitTrack(TrackITSExt& track, - int start, - int end, - int step, - float chi2clcut, - float chi2ndfcut, - float maxQoverPt, - int nCl, - float bz, - const TrackingFrameInfo** tfInfos, - const o2::base::Propagator* prop, - o2::base::PropagatorF::MatCorrType matCorrType, - o2::track::TrackPar* linRef, - const bool shiftRefToCluster) -{ - for (int iLayer{start}; iLayer != end; iLayer += step) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - const TrackingFrameInfo& trackingHit = tfInfos[iLayer][track.getClusterIndex(iLayer)]; - if (linRef) { - if (!track.o2::track::TrackParCovF::rotate(trackingHit.alphaTrackingFrame, *linRef, bz)) { - return false; - } - if (!prop->propagateToX(track, - *linRef, - trackingHit.xTrackingFrame, - bz, - o2::base::PropagatorImpl::MAX_SIN_PHI, - o2::base::PropagatorImpl::MAX_STEP, - matCorrType)) { - - return false; - } - if (matCorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { - const float xx0 = (iLayer > 2) ? 1.e-2f : 5.e-3f; // Rough layer thickness - if (!track.correctForMaterial(*linRef, xx0, xx0 * constants::Radl * constants::Rho, true)) { - return false; - } - } - } else { - if (!track.o2::track::TrackParCovF::rotate(trackingHit.alphaTrackingFrame)) { - return false; - } - if (!prop->propagateToX(track, - trackingHit.xTrackingFrame, - bz, - o2::base::PropagatorImpl::MAX_SIN_PHI, - o2::base::PropagatorImpl::MAX_STEP, - matCorrType)) { - return false; - } - if (matCorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { - const float xx0 = (iLayer > 2) ? 1.e-2f : 5.e-3f; // Rough layer thickness - if (!track.correctForMaterial(xx0, xx0 * constants::Radl * constants::Rho, true)) { - return false; - } - } - } - - auto predChi2{track.getPredictedChi2(trackingHit.positionTrackingFrame, trackingHit.covarianceTrackingFrame)}; - if ((nCl >= 3 && predChi2 > chi2clcut) || predChi2 < 0.f) { - return false; - } - track.setChi2(track.getChi2() + predChi2); - if (!track.o2::track::TrackParCov::update(trackingHit.positionTrackingFrame, trackingHit.covarianceTrackingFrame)) { - return false; - } - if (linRef && shiftRefToCluster) { // displace the reference to the last updated cluster - linRef->setY(trackingHit.positionTrackingFrame[0]); - linRef->setZ(trackingHit.positionTrackingFrame[1]); - } - nCl++; - } - return o2::gpu::CAMath::Abs(track.getQ2Pt()) < maxQoverPt && track.getChi2() < chi2ndfcut * (nCl * 2 - 5); -} - -GPUdii() o2::track::TrackParCov buildTrackSeed(const Cluster& cluster1, - const Cluster& cluster2, - const TrackingFrameInfo& tf3, - const float bz, - const bool reverse = false) -{ - const float sign = reverse ? -1.f : 1.f; - - float ca, sa; - o2::gpu::CAMath::SinCos(tf3.alphaTrackingFrame, sa, ca); - - const float x1 = cluster1.xCoordinate * ca + cluster1.yCoordinate * sa; - const float y1 = -cluster1.xCoordinate * sa + cluster1.yCoordinate * ca; - const float x2 = cluster2.xCoordinate * ca + cluster2.yCoordinate * sa; - const float y2 = -cluster2.xCoordinate * sa + cluster2.yCoordinate * ca; - const float x3 = tf3.xTrackingFrame; - const float y3 = tf3.positionTrackingFrame[0]; - - float snp, q2pt, q2pt2; - if (o2::gpu::CAMath::Abs(bz) < 0.01f) { - const float dx = x3 - x1; - const float dy = y3 - y1; - snp = sign * dy / o2::gpu::CAMath::Hypot(dx, dy); - q2pt = 1.f / track::kMostProbablePt; - q2pt2 = 1.f; - } else { - const float crv = math_utils::computeCurvature(x3, y3, x2, y2, x1, y1); - snp = sign * crv * (x3 - math_utils::computeCurvatureCentreX(x3, y3, x2, y2, x1, y1)); - q2pt = sign * crv / (bz * o2::constants::math::B2C); - q2pt2 = crv * crv; - } - - const float tgl = 0.5f * (math_utils::computeTanDipAngle(x1, y1, x2, y2, cluster1.zCoordinate, cluster2.zCoordinate) + - math_utils::computeTanDipAngle(x2, y2, x3, y3, cluster2.zCoordinate, tf3.positionTrackingFrame[1])); - const float sg2q2pt = track::kC1Pt2max * (q2pt2 > 0.0005f ? (q2pt2 < 1.f ? q2pt2 : 1.f) : 0.0005f); - - return {x3, tf3.alphaTrackingFrame, {y3, tf3.positionTrackingFrame[1], snp, tgl, q2pt}, {tf3.covarianceTrackingFrame[0], tf3.covarianceTrackingFrame[1], tf3.covarianceTrackingFrame[2], 0.f, 0.f, track::kCSnp2max, 0.f, 0.f, 0.f, track::kCTgl2max, 0.f, 0.f, 0.f, 0.f, sg2q2pt}}; -} - -template -GPUdii() TrackITSExt seedTrackForRefit(const CellSeed& seed, - const TrackingFrameInfo** foundTrackingFrameInfo, - const Cluster** unsortedClusters, - const float* layerRadii, - const float bz, - const int reseedIfShorter) -{ - TrackITSExt temporaryTrack(seed); - int lrMin = nLayers, lrMax = 0, lrMid = 0; - for (int iL{0}; iL < nLayers; ++iL) { - const int idx = seed.getCluster(iL); - temporaryTrack.setExternalClusterIndex(iL, idx, idx != constants::UnusedIndex); - if (idx != constants::UnusedIndex) { - // TODO only works if does not have holes - lrMin = o2::gpu::CAMath::Min(lrMin, iL); - lrMax = o2::gpu::CAMath::Max(lrMax, iL); - } - } - const int ncl = temporaryTrack.getNClusters(); - if (ncl < reseedIfShorter && ncl > 0) { // need to check if there are any clusters since we keep invalidate seeeds around - if (ncl == nLayers) { - lrMin = 0; - lrMax = nLayers - 1; - lrMid = (lrMin + lrMax) / 2; - } else { - lrMid = lrMin + 1; - float midR = 0.5f * (layerRadii[lrMax] + layerRadii[lrMin]), dstMidR = o2::gpu::CAMath::Abs(midR - layerRadii[lrMid]); - for (int iL = lrMid + 1; iL < lrMax; ++iL) { // find the midpoint as closest to the midR - auto dst = o2::gpu::GPUCommonMath::Abs(midR - layerRadii[iL]); - if (dst < dstMidR) { - lrMid = iL; - dstMidR = dst; - } - } - } - const auto& cluster0_tf = foundTrackingFrameInfo[lrMin][seed.getCluster(lrMin)]; - const auto& cluster1_gl = unsortedClusters[lrMid][seed.getCluster(lrMid)]; - const auto& cluster2_gl = unsortedClusters[lrMax][seed.getCluster(lrMax)]; - temporaryTrack.getParamIn() = buildTrackSeed(cluster2_gl, cluster1_gl, cluster0_tf, bz, true); - } - temporaryTrack.resetCovariance(); - temporaryTrack.setCov(temporaryTrack.getQ2Pt() * temporaryTrack.getQ2Pt() * temporaryTrack.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2); - return temporaryTrack; -} - -struct sort_tracklets { - GPUhd() bool operator()(const Tracklet& a, const Tracklet& b) - { - if (a.firstClusterIndex != b.firstClusterIndex) { - return a.firstClusterIndex < b.firstClusterIndex; - } - return a.secondClusterIndex < b.secondClusterIndex; - } -}; - -struct equal_tracklets { - GPUhd() bool operator()(const Tracklet& a, const Tracklet& b) { return a.firstClusterIndex == b.firstClusterIndex && a.secondClusterIndex == b.secondClusterIndex; } -}; - template struct sort_by_second { GPUhd() bool operator()(const gpuPair& a, const gpuPair& b) const { return a.second < b.second; } @@ -259,34 +84,40 @@ struct is_valid_pair { } }; -template +template struct seed_selector { - float maxQ2Pt; - float maxChi2; - - GPUhd() seed_selector(float maxQ2Pt, float maxChi2) : maxQ2Pt(maxQ2Pt), maxChi2(maxChi2) {} - GPUhd() bool operator()(const CellSeed& seed) const + float mMaxQ2Pt; + float mMaxChi2; + int mMaxHoles; + int mMinTrackLength; + LayerMask mHoleLayerMask; + + GPUhd() seed_selector(float maxQ2Pt, float maxChi2, int maxHoles, int minTrackLength, LayerMask holeLayerMask) : mMaxQ2Pt(maxQ2Pt), mMaxChi2(maxChi2), mMaxHoles(maxHoles), mMinTrackLength(minTrackLength), mHoleLayerMask(holeLayerMask) {} + GPUhd() bool operator()(const TrackSeed& seed) const { - return !(seed.getQ2Pt() > maxQ2Pt || seed.getChi2() > maxChi2); + return !(seed.getQ2Pt() > mMaxQ2Pt || seed.getChi2() > mMaxChi2) && + seed.getHitLayerMask().length() >= mMinTrackLength && + seed.getHitLayerMask().isAllowed(mMaxHoles, mHoleLayerMask); } }; struct compare_track_chi2 { GPUhd() bool operator()(const TrackITSExt& a, const TrackITSExt& b) const { - return a.getChi2() < b.getChi2(); + return o2::its::track::isBetter(a, b); } }; -template +template GPUg() void __launch_bounds__(256, 1) fitTrackSeedsKernel( - CellSeed* trackSeeds, + TrackSeed* trackSeeds, const TrackingFrameInfo** foundTrackingFrameInfo, const Cluster** unsortedClusters, o2::its::TrackITSExt* tracks, maybe_const* seedLUT, const float* layerRadii, const float* minPts, + const float* layerxX0, const unsigned int nSeeds, const float bz, const int startLevel, @@ -294,7 +125,7 @@ GPUg() void __launch_bounds__(256, 1) fitTrackSeedsKernel( const float maxChi2NDF, const int reseedIfShorter, const bool repeatRefitOut, - const bool shifRefToCluster, + const bool shiftRefToCluster, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType) { @@ -305,155 +136,80 @@ GPUg() void __launch_bounds__(256, 1) fitTrackSeedsKernel( continue; } } - - TrackITSExt temporaryTrack = seedTrackForRefit(trackSeeds[iCurrentTrackSeedIndex], foundTrackingFrameInfo, unsortedClusters, layerRadii, bz, reseedIfShorter); - o2::track::TrackPar linRef{temporaryTrack}; - bool fitSuccess = fitTrack(temporaryTrack, // TrackITSExt& track, - 0, // int lastLayer, - nLayers, // int firstLayer, - 1, // int firstCluster, - maxChi2ClusterAttachment, // float maxChi2ClusterAttachment, - maxChi2NDF, // float maxChi2NDF, - o2::constants::math::VeryBig, // float maxQoverPt, - 0, // nCl, - bz, // float bz, - foundTrackingFrameInfo, // TrackingFrameInfo** trackingFrameInfo, - propagator, // const o2::base::Propagator* propagator, - matCorrType, // o2::base::PropagatorF::MatCorrType matCorrType - &linRef, - shifRefToCluster); - if (!fitSuccess) { - continue; - } - temporaryTrack.getParamOut() = temporaryTrack.getParamIn(); - linRef = temporaryTrack.getParamOut(); // use refitted track as lin.reference - temporaryTrack.resetCovariance(); - temporaryTrack.setCov(temporaryTrack.getQ2Pt() * temporaryTrack.getQ2Pt() * temporaryTrack.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2); - temporaryTrack.setChi2(0); - fitSuccess = fitTrack(temporaryTrack, // TrackITSExt& track, - nLayers - 1, // int lastLayer, - -1, // int firstLayer, - -1, // int firstCluster, - maxChi2ClusterAttachment, // float maxChi2ClusterAttachment, - maxChi2NDF, // float maxChi2NDF, - 50.f, // float maxQoverPt, - 0, // nCl, - bz, // float bz, - foundTrackingFrameInfo, // TrackingFrameInfo** trackingFrameInfo, - propagator, // const o2::base::Propagator* propagator, - matCorrType, // o2::base::PropagatorF::MatCorrType matCorrType - &linRef, - shifRefToCluster); - if (!fitSuccess || temporaryTrack.getPt() < minPts[nLayers - temporaryTrack.getNClusters()]) { - continue; - } - if (repeatRefitOut) { // repeat outward refit seeding and linearizing with the stable inward fit result - o2::track::TrackParCov saveInw{temporaryTrack}; - linRef = saveInw; // use refitted track as lin.reference - float saveChi2 = temporaryTrack.getChi2(); - temporaryTrack.resetCovariance(); - temporaryTrack.setCov(temporaryTrack.getQ2Pt() * temporaryTrack.getQ2Pt() * temporaryTrack.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2); - temporaryTrack.setChi2(0); - fitSuccess = fitTrack(temporaryTrack, // TrackITSExt& track, - 0, // int lastLayer, - nLayers, // int firstLayer, - 1, // int firstCluster, - maxChi2ClusterAttachment, // float maxChi2ClusterAttachment, - maxChi2NDF, // float maxChi2NDF, - o2::constants::math::VeryBig, // float maxQoverPt, - 0, // nCl, - bz, // float bz, - foundTrackingFrameInfo, // TrackingFrameInfo** trackingFrameInfo, - propagator, // const o2::base::Propagator* propagator, - matCorrType, // o2::base::PropagatorF::MatCorrType matCorrType - &linRef, - shifRefToCluster); - if (!fitSuccess) { - continue; + TrackITSExt temporaryTrack; + bool refitSuccess = o2::its::track::refitTrack(trackSeeds[iCurrentTrackSeedIndex], + temporaryTrack, + maxChi2ClusterAttachment, + maxChi2NDF, + bz, + foundTrackingFrameInfo, + unsortedClusters, + layerxX0, + layerRadii, + minPts, + propagator, + matCorrType, + reseedIfShorter, + shiftRefToCluster, + repeatRefitOut); + if (refitSuccess) { + if constexpr (initRun) { + seedLUT[iCurrentTrackSeedIndex] = 1; + } else { + tracks[seedLUT[iCurrentTrackSeedIndex]] = temporaryTrack; } - temporaryTrack.getParamOut() = temporaryTrack.getParamIn(); - temporaryTrack.getParamIn() = saveInw; - temporaryTrack.setChi2(saveChi2); - } - - if constexpr (initRun) { - seedLUT[iCurrentTrackSeedIndex] = 1; - } else { - tracks[seedLUT[iCurrentTrackSeedIndex]] = temporaryTrack; } } } -template +template GPUg() void __launch_bounds__(256, 1) computeLayerCellNeighboursKernel( - CellSeed** cellSeedArray, - int* neighboursLUT, - int* neighboursIndexTable, + CellSeed** cellSeedArray, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - const Tracklet** tracklets, - const int deltaROF, + CellNeighbour* cellNeighbours, + const int sourceCellTopologyId, + const int targetCellTopologyId, const float maxChi2ClusterAttachment, const float bz, - const int layerIndex, - const unsigned int nCells, - const int maxCellNeighbours = 1e2) + const unsigned int nCells) { for (int iCurrentCellIndex = blockIdx.x * blockDim.x + threadIdx.x; iCurrentCellIndex < nCells; iCurrentCellIndex += blockDim.x * gridDim.x) { - if constexpr (!initRun) { - if (neighboursIndexTable[iCurrentCellIndex] == neighboursIndexTable[iCurrentCellIndex + 1]) { - continue; - } - } - const auto& currentCellSeed{cellSeedArray[layerIndex][iCurrentCellIndex]}; + const auto& currentCellSeed{cellSeedArray[sourceCellTopologyId][iCurrentCellIndex]}; const int nextLayerTrackletIndex{currentCellSeed.getSecondTrackletIndex()}; - const int nextLayerFirstCellIndex{cellsLUTs[layerIndex + 1][nextLayerTrackletIndex]}; - const int nextLayerLastCellIndex{cellsLUTs[layerIndex + 1][nextLayerTrackletIndex + 1]}; - int foundNeighbours{0}; + const int nextLayerFirstCellIndex{cellsLUTs[targetCellTopologyId][nextLayerTrackletIndex]}; + const int nextLayerLastCellIndex{cellsLUTs[targetCellTopologyId][nextLayerTrackletIndex + 1]}; for (int iNextCell{nextLayerFirstCellIndex}; iNextCell < nextLayerLastCellIndex; ++iNextCell) { - auto nextCellSeed{cellSeedArray[layerIndex + 1][iNextCell]}; // Copy - if (nextCellSeed.getFirstTrackletIndex() != nextLayerTrackletIndex) { // Check if cells share the same tracklet + auto nextCellSeed{cellSeedArray[targetCellTopologyId][iNextCell]}; // Copy + if (nextCellSeed.getFirstTrackletIndex() != nextLayerTrackletIndex || !currentCellSeed.getTimeStamp().isCompatible(nextCellSeed.getTimeStamp())) { break; } - if (deltaROF) { - const auto& trkl00 = tracklets[layerIndex][currentCellSeed.getFirstTrackletIndex()]; - const auto& trkl01 = tracklets[layerIndex + 1][currentCellSeed.getSecondTrackletIndex()]; - const auto& trkl10 = tracklets[layerIndex + 1][nextCellSeed.getFirstTrackletIndex()]; - const auto& trkl11 = tracklets[layerIndex + 2][nextCellSeed.getSecondTrackletIndex()]; - if ((o2::gpu::CAMath::Max(trkl00.getMaxRof(), o2::gpu::CAMath::Max(trkl01.getMaxRof(), o2::gpu::CAMath::Max(trkl10.getMaxRof(), trkl11.getMaxRof()))) - - o2::gpu::CAMath::Min(trkl00.getMinRof(), o2::gpu::CAMath::Min(trkl01.getMinRof(), o2::gpu::CAMath::Min(trkl10.getMinRof(), trkl11.getMinRof())))) > deltaROF) { - continue; - } - } - if (!nextCellSeed.rotate(currentCellSeed.getAlpha()) || !nextCellSeed.propagateTo(currentCellSeed.getX(), bz)) { continue; } float chi2 = currentCellSeed.getPredictedChi2(nextCellSeed); - if (chi2 > maxChi2ClusterAttachment) /// TODO: switch to the chi2 wrt cluster to avoid correlation - { + if (chi2 > maxChi2ClusterAttachment) { continue; } if constexpr (initRun) { - atomicAdd(neighboursLUT + iNextCell, 1); - neighboursIndexTable[iCurrentCellIndex]++; + atomicAdd(neighboursCursor + iNextCell, 1); } else { - cellNeighbours[neighboursIndexTable[iCurrentCellIndex] + foundNeighbours] = {iCurrentCellIndex, iNextCell}; - foundNeighbours++; + const int offset = atomicAdd(neighboursCursor + iNextCell, 1); + cellNeighbours[offset] = {sourceCellTopologyId, iCurrentCellIndex, targetCellTopologyId, iNextCell, currentCellSeed.getLevel() + 1}; const int currentCellLevel{currentCellSeed.getLevel()}; if (currentCellLevel >= nextCellSeed.getLevel()) { - atomicMax(cellSeedArray[layerIndex + 1][iNextCell].getLevelPtr(), currentCellLevel + 1); + atomicMax(cellSeedArray[targetCellTopologyId][iNextCell].getLevelPtr(), currentCellLevel + 1); } } } } } -template +template GPUg() void __launch_bounds__(256, 1) computeLayerCellsKernel( const Cluster** sortedClusters, const Cluster** unsortedClusters, @@ -461,54 +217,58 @@ GPUg() void __launch_bounds__(256, 1) computeLayerCellsKernel( Tracklet** tracklets, int** trackletsLUT, const int nTrackletsCurrent, - const int layer, - CellSeed* cells, + const int cellTopologyId, + const typename TrackingTopology::View topology, + CellSeed* cells, int** cellsLUTs, - const int deltaROF, + const float* layerxX0, const float bz, const float maxChi2ClusterAttachment, const float cellDeltaTanLambdaSigma, const float nSigmaCut) { - constexpr float layerxX0[7] = {5.e-3f, 5.e-3f, 5.e-3f, 1.e-2f, 1.e-2f, 1.e-2f, 1.e-2f}; // FIXME: Hardcoded here for the moment. + const auto cellTopology = topology.getCell(cellTopologyId); + const auto first = topology.getTransition(cellTopology.firstTransition); + const auto second = topology.getTransition(cellTopology.secondTransition); + const int layers[3] = {first.fromLayer, first.toLayer, second.toLayer}; for (int iCurrentTrackletIndex = blockIdx.x * blockDim.x + threadIdx.x; iCurrentTrackletIndex < nTrackletsCurrent; iCurrentTrackletIndex += blockDim.x * gridDim.x) { if constexpr (!initRun) { - if (cellsLUTs[layer][iCurrentTrackletIndex] == cellsLUTs[layer][iCurrentTrackletIndex + 1]) { + if (cellsLUTs[cellTopologyId][iCurrentTrackletIndex] == cellsLUTs[cellTopologyId][iCurrentTrackletIndex + 1]) { continue; } } - const Tracklet& currentTracklet = tracklets[layer][iCurrentTrackletIndex]; + const Tracklet& currentTracklet = tracklets[cellTopology.firstTransition][iCurrentTrackletIndex]; const int nextLayerClusterIndex{currentTracklet.secondClusterIndex}; - const int nextLayerFirstTrackletIndex{trackletsLUT[layer + 1][nextLayerClusterIndex]}; - const int nextLayerLastTrackletIndex{trackletsLUT[layer + 1][nextLayerClusterIndex + 1]}; + const int nextLayerFirstTrackletIndex{trackletsLUT[cellTopology.secondTransition][nextLayerClusterIndex]}; + const int nextLayerLastTrackletIndex{trackletsLUT[cellTopology.secondTransition][nextLayerClusterIndex + 1]}; if (nextLayerFirstTrackletIndex == nextLayerLastTrackletIndex) { continue; } int foundCells{0}; for (int iNextTrackletIndex{nextLayerFirstTrackletIndex}; iNextTrackletIndex < nextLayerLastTrackletIndex; ++iNextTrackletIndex) { - if (tracklets[layer + 1][iNextTrackletIndex].firstClusterIndex != nextLayerClusterIndex) { + if (tracklets[cellTopology.secondTransition][iNextTrackletIndex].firstClusterIndex != nextLayerClusterIndex) { break; } - const Tracklet& nextTracklet = tracklets[layer + 1][iNextTrackletIndex]; - if (deltaROF && currentTracklet.getSpanRof(nextTracklet) > deltaROF) { + const Tracklet& nextTracklet = tracklets[cellTopology.secondTransition][iNextTrackletIndex]; + if (!currentTracklet.getTimeStamp().isCompatible(nextTracklet.getTimeStamp())) { continue; } const float deltaTanLambda{o2::gpu::CAMath::Abs(currentTracklet.tanLambda - nextTracklet.tanLambda)}; if (deltaTanLambda / cellDeltaTanLambdaSigma < nSigmaCut) { const int clusId[3]{ - sortedClusters[layer][currentTracklet.firstClusterIndex].clusterId, - sortedClusters[layer + 1][nextTracklet.firstClusterIndex].clusterId, - sortedClusters[layer + 2][nextTracklet.secondClusterIndex].clusterId}; - - const auto& cluster1_glo = unsortedClusters[layer][clusId[0]]; - const auto& cluster2_glo = unsortedClusters[layer + 1][clusId[1]]; - const auto& cluster3_tf = tfInfo[layer + 2][clusId[2]]; - auto track{buildTrackSeed(cluster1_glo, cluster2_glo, cluster3_tf, bz)}; + sortedClusters[layers[0]][currentTracklet.firstClusterIndex].clusterId, + sortedClusters[layers[1]][nextTracklet.firstClusterIndex].clusterId, + sortedClusters[layers[2]][nextTracklet.secondClusterIndex].clusterId}; + + const auto& cluster1_glo = unsortedClusters[layers[0]][clusId[0]]; + const auto& cluster2_glo = unsortedClusters[layers[1]][clusId[1]]; + const auto& cluster3_tf = tfInfo[layers[2]][clusId[2]]; + auto track{o2::its::track::buildTrackSeed(cluster1_glo, cluster2_glo, cluster3_tf, bz)}; float chi2{0.f}; bool good{false}; for (int iC{2}; iC--;) { - const TrackingFrameInfo& trackingHit = tfInfo[layer + iC][clusId[iC]]; + const TrackingFrameInfo& trackingHit = tfInfo[layers[iC]][clusId[iC]]; if (!track.rotate(trackingHit.alphaTrackingFrame)) { break; } @@ -516,7 +276,7 @@ GPUg() void __launch_bounds__(256, 1) computeLayerCellsKernel( break; } - if (!track.correctForMaterial(layerxX0[layer + iC], layerxX0[layer + iC] * constants::Radl * constants::Rho, true)) { + if (!track.correctForMaterial(layerxX0[layers[iC]], layerxX0[layers[iC]] * constants::Radl * constants::Rho, true)) { break; } @@ -534,64 +294,76 @@ GPUg() void __launch_bounds__(256, 1) computeLayerCellsKernel( continue; } if constexpr (!initRun) { - new (cells + cellsLUTs[layer][iCurrentTrackletIndex] + foundCells) CellSeed{layer, clusId[0], clusId[1], clusId[2], iCurrentTrackletIndex, iNextTrackletIndex, track, chi2}; + TimeEstBC ts = currentTracklet.getTimeStamp(); + ts += nextTracklet.getTimeStamp(); + new (cells + cellsLUTs[cellTopologyId][iCurrentTrackletIndex] + foundCells) CellSeed{cellTopology.hitLayerMask, clusId[0], clusId[1], clusId[2], iCurrentTrackletIndex, iNextTrackletIndex, track, chi2, ts}; } ++foundCells; } } if constexpr (initRun) { - cellsLUTs[layer][iCurrentTrackletIndex] = foundCells; + cellsLUTs[cellTopologyId][iCurrentTrackletIndex] = foundCells; } } } -template +template GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( - const IndexTableUtils* utils, - const uint8_t* multMask, - const int layerIndex, - const int startROF, - const int endROF, - const int totalROFs, - const int deltaROF, + const IndexTableUtils* utils, + const typename ROFMaskTable::View rofMask, + const int transitionId, + const typename TrackingTopology::View topology, + const typename ROFOverlapTable::View rofOverlaps, + const typename ROFVertexLookupTable::View vertexLUT, const Vertex* vertices, const int* rofPV, - const int nVertices, const int vertexId, - const Cluster** clusters, // Input data rof0 - const int** ROFClusters, // Number of clusters on layers per ROF - const unsigned char** usedClusters, // Used clusters - const int** indexTables, // Input data rof0-delta getNphiBins()}; const int zBins{utils->getNzBins()}; const int tableSize{phiBins * zBins + 1}; - for (unsigned int iROF{blockIdx.x}; iROF < endROF - startROF; iROF += gridDim.x) { - const short pivotROF = iROF + startROF; - const short minROF = o2::gpu::CAMath::Max(startROF, static_cast(pivotROF - deltaROF)); - const short maxROF = o2::gpu::CAMath::Min(endROF - 1, static_cast(pivotROF + deltaROF)); - auto primaryVertices = getPrimaryVertices(minROF, maxROF, rofPV, totalROFs, vertices); + const int totalROFs0 = rofOverlaps.getLayer(fromLayer).mNROFsTF; + const int totalROFs1 = rofOverlaps.getLayer(toLayer).mNROFsTF; + for (unsigned int pivotROF{blockIdx.x}; pivotROF < totalROFs0; pivotROF += gridDim.x) { + if (!rofMask.isROFEnabled(fromLayer, pivotROF)) { + continue; + } + + const auto& pvs = vertexLUT.getVertices(fromLayer, pivotROF); + auto primaryVertices = gpuSpan(&vertices[pvs.getFirstEntry()], pvs.getEntries()); if (primaryVertices.empty()) { continue; } const auto startVtx{vertexId >= 0 ? vertexId : 0}; const auto endVtx{vertexId >= 0 ? o2::gpu::CAMath::Min(vertexId + 1, static_cast(primaryVertices.size())) : static_cast(primaryVertices.size())}; - if ((endVtx - startVtx) <= 0) { + if (endVtx <= startVtx || (vertexId + 1) > primaryVertices.size()) { + continue; + } + + const auto& rofOverlap = rofOverlaps.getOverlap(fromLayer, toLayer, pivotROF); + if (!rofOverlap.getEntries()) { continue; } - auto clustersCurrentLayer = getClustersOnLayer(pivotROF, totalROFs, layerIndex, ROFClusters, clusters); + auto clustersCurrentLayer = getClustersOnLayer(pivotROF, totalROFs0, fromLayer, ROFClusters, clusters); if (clustersCurrentLayer.empty()) { continue; } @@ -600,12 +372,12 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( unsigned int storedTracklets{0}; const auto& currentCluster{clustersCurrentLayer[currentClusterIndex]}; - const int currentSortedIndex{ROFClusters[layerIndex][pivotROF] + currentClusterIndex}; - if (usedClusters[layerIndex][currentCluster.clusterId]) { + const int currentSortedIndex{ROFClusters[fromLayer][pivotROF] + currentClusterIndex}; + if (usedClusters[fromLayer][currentCluster.clusterId]) { continue; } if constexpr (!initRun) { - if (trackletsLUT[layerIndex][currentSortedIndex] == trackletsLUT[layerIndex][currentSortedIndex + 1]) { + if (trackletsLUT[transitionId][currentSortedIndex] == trackletsLUT[transitionId][currentSortedIndex + 1]) { continue; } } @@ -613,7 +385,10 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( const float inverseR0{1.f / currentCluster.radius}; for (int iV{startVtx}; iV < endVtx; ++iV) { auto& primaryVertex{primaryVertices[iV]}; - if ((primaryVertex.isFlagSet(Vertex::Flags::UPCMode) && iteration != 3) || (iteration == 3 && !primaryVertex.isFlagSet(Vertex::Flags::UPCMode))) { + if (!vertexLUT.isVertexCompatible(fromLayer, pivotROF, primaryVertex)) { + continue; + } + if (primaryVertex.isFlagSet(Vertex::Flags::UPCMode) != selectUPCVertices) { continue; } @@ -623,8 +398,8 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( const float zAtRmax{tanLambda * (maxR - currentCluster.radius) + currentCluster.zCoordinate}; const float sqInverseDeltaZ0{1.f / (math_utils::Sq(currentCluster.zCoordinate - primaryVertex.getZ()) + constants::Tolerance)}; /// protecting from overflows adding the detector resolution const float sigmaZ{o2::gpu::CAMath::Sqrt(math_utils::Sq(resolution) * math_utils::Sq(tanLambda) * ((math_utils::Sq(inverseR0) + sqInverseDeltaZ0) * math_utils::Sq(meanDeltaR) + 1.f) + math_utils::Sq(meanDeltaR * MSAngle))}; - const int4 selectedBinsRect{getBinsRect(currentCluster, layerIndex + 1, utils, zAtRmin, zAtRmax, sigmaZ * NSigmaCut, phiCut)}; - if (selectedBinsRect.x == 0 && selectedBinsRect.y == 0 && selectedBinsRect.z == 0 && selectedBinsRect.w == 0) { + const int4 selectedBinsRect{o2::its::getBinsRect(currentCluster, toLayer, zAtRmin, zAtRmax, sigmaZ * NSigmaCut, phiCut, *utils)}; + if (selectedBinsRect.x < 0) { continue; } int phiBinsNum{selectedBinsRect.w - selectedBinsRect.y + 1}; @@ -633,35 +408,42 @@ GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( phiBinsNum += phiBins; } - for (short targetROF{minROF}; targetROF <= maxROF; ++targetROF) { - auto clustersNextLayer = getClustersOnLayer(targetROF, totalROFs, layerIndex + 1, ROFClusters, clusters); + for (short targetROF = rofOverlap.getFirstEntry(); targetROF < rofOverlap.getEntriesBound(); ++targetROF) { + if (!rofMask.isROFEnabled(toLayer, targetROF)) { + continue; + } + auto clustersNextLayer = getClustersOnLayer(targetROF, totalROFs1, toLayer, ROFClusters, clusters); if (clustersNextLayer.empty()) { continue; } + const auto ts = rofOverlaps.getTimeStamp(fromLayer, pivotROF, toLayer, targetROF); + if (!ts.isCompatible(primaryVertex.getTimeStamp())) { + continue; + } for (int iPhiCount{0}; iPhiCount < phiBinsNum; iPhiCount++) { int iPhiBin = (selectedBinsRect.y + iPhiCount) % phiBins; const int firstBinIndex{utils->getBinIndex(selectedBinsRect.x, iPhiBin)}; const int maxBinIndex{firstBinIndex + selectedBinsRect.z - selectedBinsRect.x + 1}; - const int firstRowClusterIndex = indexTables[layerIndex + 1][(targetROF)*tableSize + firstBinIndex]; - const int maxRowClusterIndex = indexTables[layerIndex + 1][(targetROF)*tableSize + maxBinIndex]; + const int firstRowClusterIndex = indexTables[toLayer][(targetROF)*tableSize + firstBinIndex]; + const int maxRowClusterIndex = indexTables[toLayer][(targetROF)*tableSize + maxBinIndex]; for (int nextClusterIndex{firstRowClusterIndex}; nextClusterIndex < maxRowClusterIndex; ++nextClusterIndex) { if (nextClusterIndex >= clustersNextLayer.size()) { break; } const Cluster& nextCluster{clustersNextLayer[nextClusterIndex]}; - if (usedClusters[layerIndex + 1][nextCluster.clusterId]) { + if (usedClusters[toLayer][nextCluster.clusterId]) { continue; } const float deltaPhi{o2::gpu::CAMath::Abs(currentCluster.phi - nextCluster.phi)}; const float deltaZ{o2::gpu::CAMath::Abs(tanLambda * (nextCluster.radius - currentCluster.radius) + currentCluster.zCoordinate - nextCluster.zCoordinate)}; if (deltaZ / sigmaZ < NSigmaCut && (deltaPhi < phiCut || o2::gpu::CAMath::Abs(deltaPhi - o2::constants::math::TwoPI) < phiCut)) { if constexpr (initRun) { - trackletsLUT[layerIndex][currentSortedIndex]++; // we need l0 as well for usual exclusive sums. + trackletsLUT[transitionId][currentSortedIndex]++; // we need l0 as well for usual exclusive sums. } else { const float phi{o2::gpu::CAMath::ATan2(currentCluster.yCoordinate - nextCluster.yCoordinate, currentCluster.xCoordinate - nextCluster.xCoordinate)}; const float tanL{(currentCluster.zCoordinate - nextCluster.zCoordinate) / (currentCluster.radius - nextCluster.radius)}; - const int nextSortedIndex{ROFClusters[layerIndex + 1][targetROF] + nextClusterIndex}; - new (tracklets[layerIndex] + trackletsLUT[layerIndex][currentSortedIndex] + storedTracklets) Tracklet{currentSortedIndex, nextSortedIndex, tanL, phi, pivotROF, targetROF}; + const int nextSortedIndex{ROFClusters[toLayer][targetROF] + nextClusterIndex}; + new (tracklets[transitionId] + trackletsLUT[transitionId][currentSortedIndex] + storedTracklets) Tracklet{currentSortedIndex, nextSortedIndex, tanL, phi, ts}; } ++storedTracklets; } @@ -683,27 +465,29 @@ GPUg() void __launch_bounds__(256, 1) compileTrackletsLookupTableKernel( } } -template +template GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( - const int layer, + const int defaultCellTopologyId, const int level, - CellSeed** allCellSeeds, - CellSeed* currentCellSeeds, + CellSeed** allCellSeeds, + CurrentSeed* currentCellSeeds, const int* currentCellIds, + const int* currentCellTopologyIds, const unsigned int nCurrentCells, - CellSeed* updatedCellSeeds, + TrackSeed* updatedCellSeeds, int* updatedCellsIds, + int* updatedCellTopologyIds, int* foundSeedsTable, // auxiliary only in GPU code to compute the number of cells per iteration const unsigned char** usedClusters, // Used clusters - int* neighbours, - int* neighboursLUT, + CellNeighbour** neighbours, + int** neighboursLUT, const TrackingFrameInfo** foundTrackingFrameInfo, + const float* layerxX0, const float bz, const float maxChi2ClusterAttachment, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType) { - constexpr float layerxX0[7] = {5.e-3f, 5.e-3f, 5.e-3f, 1.e-2f, 1.e-2f, 1.e-2f, 1.e-2f}; // FIXME: Hardcoded here for the moment. for (unsigned int iCurrentCell = blockIdx.x * blockDim.x + threadIdx.x; iCurrentCell < nCurrentCells; iCurrentCell += blockDim.x * gridDim.x) { if constexpr (!dryRun) { if (foundSeedsTable[iCurrentCell] == foundSeedsTable[iCurrentCell + 1]) { @@ -712,34 +496,50 @@ GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( } int foundSeeds{0}; const auto& currentCell{currentCellSeeds[iCurrentCell]}; + const int cellTopologyId = currentCellTopologyIds == nullptr ? defaultCellTopologyId : currentCellTopologyIds[iCurrentCell]; if (currentCell.getLevel() != level) { continue; } - if (currentCellIds == nullptr && (usedClusters[layer][currentCell.getFirstClusterIndex()] || - usedClusters[layer + 1][currentCell.getSecondClusterIndex()] || - usedClusters[layer + 2][currentCell.getThirdClusterIndex()])) { - continue; + if (currentCellIds == nullptr) { + bool used = false; + for (int layer = 0; layer < NLayers; ++layer) { + const int clusterIndex = currentCell.getCluster(layer); + used |= clusterIndex != constants::UnusedIndex && usedClusters[layer][clusterIndex]; + } + if (used) { + continue; + } } const int cellId = currentCellIds == nullptr ? iCurrentCell : currentCellIds[iCurrentCell]; + if (cellTopologyId < 0 || neighboursLUT[cellTopologyId] == nullptr || neighbours[cellTopologyId] == nullptr) { + continue; + } - const int startNeighbourId{cellId ? neighboursLUT[cellId - 1] : 0}; - const int endNeighbourId{neighboursLUT[cellId]}; + const int startNeighbourId{neighboursLUT[cellTopologyId][cellId]}; + const int endNeighbourId{neighboursLUT[cellTopologyId][cellId + 1]}; for (int iNeighbourCell{startNeighbourId}; iNeighbourCell < endNeighbourId; ++iNeighbourCell) { - const int neighbourCellId = neighbours[iNeighbourCell]; - const auto& neighbourCell = allCellSeeds[layer - 1][neighbourCellId]; + const auto& neighbourRef = neighbours[cellTopologyId][iNeighbourCell]; + const int neighbourCellTopologyId = neighbourRef.cellTopology; + const int neighbourCellId = neighbourRef.cell; + const auto& neighbourCell = allCellSeeds[neighbourCellTopologyId][neighbourCellId]; if (neighbourCell.getSecondTrackletIndex() != currentCell.getFirstTrackletIndex()) { continue; } - if (usedClusters[layer - 1][neighbourCell.getFirstClusterIndex()]) { + if (!currentCell.getTimeStamp().isCompatible(neighbourCell.getTimeStamp())) { continue; } if (currentCell.getLevel() - 1 != neighbourCell.getLevel()) { continue; } - auto seed{currentCell}; - auto& trHit = foundTrackingFrameInfo[layer - 1][neighbourCell.getFirstClusterIndex()]; + const int neighbourLayer = neighbourCell.getInnerLayer(); + const int neighbourCluster = neighbourCell.getFirstClusterIndex(); + if (usedClusters[neighbourLayer][neighbourCluster]) { + continue; + } + TrackSeed seed{currentCell}; + auto& trHit = foundTrackingFrameInfo[neighbourLayer][neighbourCluster]; if (!seed.rotate(trHit.alphaTrackingFrame)) { continue; @@ -750,7 +550,7 @@ GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( } if (matCorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { - if (!seed.correctForMaterial(layerxX0[layer - 1], layerxX0[layer - 1] * constants::Radl * constants::Rho, true)) { + if (!seed.correctForMaterial(layerxX0[neighbourLayer], layerxX0[neighbourLayer] * constants::Radl * constants::Rho, true)) { continue; } } @@ -766,11 +566,15 @@ GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( if constexpr (dryRun) { foundSeedsTable[iCurrentCell]++; } else { - seed.getClusters()[layer - 1] = neighbourCell.getFirstClusterIndex(); + seed.getClusters()[neighbourLayer] = neighbourCluster; + auto mask = seed.getHitLayerMask(); + mask.set(neighbourLayer); + seed.setHitLayerMask(mask); seed.setLevel(neighbourCell.getLevel()); seed.setFirstTrackletIndex(neighbourCell.getFirstTrackletIndex()); seed.setSecondTrackletIndex(neighbourCell.getSecondTrackletIndex()); updatedCellsIds[foundSeedsTable[iCurrentCell] + foundSeeds] = neighbourCellId; + updatedCellTopologyIds[foundSeedsTable[iCurrentCell] + foundSeeds] = neighbourCellTopologyId; updatedCellSeeds[foundSeedsTable[iCurrentCell] + foundSeeds] = seed; } foundSeeds++; @@ -780,18 +584,17 @@ GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( } // namespace gpu -template -void countTrackletsInROFsHandler(const IndexTableUtils* utils, - const uint8_t* multMask, - const int layer, - const int startROF, - const int endROF, - const int maxROF, - const int deltaROF, +template +void countTrackletsInROFsHandler(const IndexTableUtils* utils, + const typename ROFMaskTable::View& rofMask, + const int transitionId, + const int fromLayer, + const int toLayer, + const typename ROFOverlapTable::View& rofOverlaps, + const typename ROFVertexLookupTable::View& vertexLUT, const int vertexId, const Vertex* vertices, const int* rofPV, - const int nVertices, const Cluster** clusters, std::vector nClusters, const int** ROFClusters, @@ -799,31 +602,28 @@ void countTrackletsInROFsHandler(const IndexTableUtils* utils, const int** clustersIndexTables, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const typename TrackingTopology::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, - std::array& minRs, - std::array& maxRs, + std::array& minRs, + std::array& maxRs, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Streams& streams) { - gpu::computeLayerTrackletsMultiROFKernel<<>>( + gpu::computeLayerTrackletsMultiROFKernel<<<60, 256, 0, streams[transitionId].get()>>>( utils, - multMask, - layer, - startROF, - endROF, - maxROF, - deltaROF, + rofMask, + transitionId, + topology, + rofOverlaps, + vertexLUT, vertices, rofPV, - nVertices, vertexId, clusters, ROFClusters, @@ -831,31 +631,30 @@ void countTrackletsInROFsHandler(const IndexTableUtils* utils, clustersIndexTables, nullptr, trackletsLUTs, - iteration, + selectUPCVertices, NSigmaCut, - phiCuts[layer], + transitionPhiCuts[transitionId], resolutionPV, - minRs[layer + 1], - maxRs[layer + 1], - resolutions[layer], - radii[layer + 1] - radii[layer], - mulScatAng[layer]); - auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[layer].get()); - thrust::exclusive_scan(nosync_policy, trackletsLUTsHost[layer], trackletsLUTsHost[layer] + nClusters[layer] + 1, trackletsLUTsHost[layer]); + minRs[toLayer], + maxRs[toLayer], + resolutions[fromLayer], + radii[toLayer] - radii[fromLayer], + transitionMSAngles[transitionId]); + auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[transitionId].get()); + thrust::exclusive_scan(nosync_policy, trackletsLUTsHost[transitionId], trackletsLUTsHost[transitionId] + nClusters[fromLayer] + 1, trackletsLUTsHost[transitionId]); } -template -void computeTrackletsInROFsHandler(const IndexTableUtils* utils, - const uint8_t* multMask, - const int layer, - const int startROF, - const int endROF, - const int maxROF, - const int deltaROF, +template +void computeTrackletsInROFsHandler(const IndexTableUtils* utils, + const typename ROFMaskTable::View& rofMask, + const int transitionId, + const int fromLayer, + const int toLayer, + const typename ROFOverlapTable::View& rofOverlaps, + const typename ROFVertexLookupTable::View& vertexLUT, const int vertexId, const Vertex* vertices, const int* rofPV, - const int nVertices, const Cluster** clusters, std::vector nClusters, const int** ROFClusters, @@ -866,31 +665,28 @@ void computeTrackletsInROFsHandler(const IndexTableUtils* utils, gsl::span nTracklets, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const typename TrackingTopology::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, - std::array& minRs, - std::array& maxRs, + std::array& minRs, + std::array& maxRs, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Streams& streams) { - gpu::computeLayerTrackletsMultiROFKernel<<>>( + gpu::computeLayerTrackletsMultiROFKernel<<<60, 256, 0, streams[transitionId].get()>>>( utils, - multMask, - layer, - startROF, - endROF, - maxROF, - deltaROF, + rofMask, + transitionId, + topology, + rofOverlaps, + vertexLUT, vertices, rofPV, - nVertices, vertexId, clusters, ROFClusters, @@ -898,31 +694,31 @@ void computeTrackletsInROFsHandler(const IndexTableUtils* utils, clustersIndexTables, tracklets, trackletsLUTs, - iteration, + selectUPCVertices, NSigmaCut, - phiCuts[layer], + transitionPhiCuts[transitionId], resolutionPV, - minRs[layer + 1], - maxRs[layer + 1], - resolutions[layer], - radii[layer + 1] - radii[layer], - mulScatAng[layer]); - thrust::device_ptr tracklets_ptr(spanTracklets[layer]); - auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[layer].get()); - thrust::sort(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[layer], gpu::sort_tracklets()); - auto unique_end = thrust::unique(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[layer], gpu::equal_tracklets()); - nTracklets[layer] = unique_end - tracklets_ptr; - if (layer) { - GPUChkErrS(cudaMemsetAsync(trackletsLUTsHost[layer], 0, (nClusters[layer] + 1) * sizeof(int), streams[layer].get())); - gpu::compileTrackletsLookupTableKernel<<>>( - spanTracklets[layer], - trackletsLUTsHost[layer], - nTracklets[layer]); - thrust::exclusive_scan(nosync_policy, trackletsLUTsHost[layer], trackletsLUTsHost[layer] + nClusters[layer] + 1, trackletsLUTsHost[layer]); + minRs[toLayer], + maxRs[toLayer], + resolutions[fromLayer], + radii[toLayer] - radii[fromLayer], + transitionMSAngles[transitionId]); + thrust::device_ptr tracklets_ptr(spanTracklets[transitionId]); + auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[transitionId].get()); + thrust::sort(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[transitionId]); + auto unique_end = thrust::unique(nosync_policy, tracklets_ptr, tracklets_ptr + nTracklets[transitionId]); + nTracklets[transitionId] = unique_end - tracklets_ptr; + if (fromLayer > 0) { + GPUChkErrS(cudaMemsetAsync(trackletsLUTsHost[transitionId], 0, (nClusters[fromLayer] + 1) * sizeof(int), streams[transitionId].get())); + gpu::compileTrackletsLookupTableKernel<<<60, 256, 0, streams[transitionId].get()>>>( + spanTracklets[transitionId], + trackletsLUTsHost[transitionId], + nTracklets[transitionId]); + thrust::exclusive_scan(nosync_policy, trackletsLUTsHost[transitionId], trackletsLUTsHost[transitionId] + nClusters[fromLayer] + 1, trackletsLUTsHost[transitionId]); } } -template +template void countCellsHandler( const Cluster** sortedClusters, const Cluster** unsortedClusters, @@ -930,40 +726,41 @@ void countCellsHandler( Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, - CellSeed* cells, + const int cellTopologyId, + const typename TrackingTopology::View topology, + CellSeed* cells, int** cellsLUTsArrayDevice, int* cellsLUTsHost, - const int deltaROF, const float bz, const float maxChi2ClusterAttachment, const float cellDeltaTanLambdaSigma, const float nSigmaCut, + const std::vector& layerxX0Host, o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Streams& streams) { - gpu::computeLayerCellsKernel<<>>( - sortedClusters, // const Cluster** - unsortedClusters, // const Cluster** - tfInfo, // const TrackingFrameInfo** - tracklets, // const Tracklets** - trackletsLUT, // const int** - nTracklets, // const int - layer, // const int - cells, // CellSeed* - cellsLUTsArrayDevice, // int** - deltaROF, // const int + thrust::device_vector layerxX0(layerxX0Host); + gpu::computeLayerCellsKernel<<<60, 256, 0, streams[cellTopologyId].get()>>>( + sortedClusters, // const Cluster** + unsortedClusters, // const Cluster** + tfInfo, // const TrackingFrameInfo** + tracklets, // const Tracklets** + trackletsLUT, // const int** + nTracklets, // const int + cellTopologyId, // const int + topology, + cells, // CellSeed* + cellsLUTsArrayDevice, // int** + thrust::raw_pointer_cast(&layerxX0[0]), bz, // const float maxChi2ClusterAttachment, // const float cellDeltaTanLambdaSigma, // const float nSigmaCut); // const float - auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[layer].get()); + auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(streams[cellTopologyId].get()); thrust::exclusive_scan(nosync_policy, cellsLUTsHost, cellsLUTsHost + nTracklets + 1, cellsLUTsHost); } -template +template void computeCellsHandler( const Cluster** sortedClusters, const Cluster** unsortedClusters, @@ -971,104 +768,93 @@ void computeCellsHandler( Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, - CellSeed* cells, + const int cellTopologyId, + const typename TrackingTopology::View topology, + CellSeed* cells, int** cellsLUTsArrayDevice, int* cellsLUTsHost, - const int deltaROF, const float bz, const float maxChi2ClusterAttachment, const float cellDeltaTanLambdaSigma, const float nSigmaCut, - const int nBlocks, - const int nThreads, + const std::vector& layerxX0Host, gpu::Streams& streams) { - gpu::computeLayerCellsKernel<<>>( - sortedClusters, // const Cluster** - unsortedClusters, // const Cluster** - tfInfo, // const TrackingFrameInfo** - tracklets, // const Tracklets** - trackletsLUT, // const int** - nTracklets, // const int - layer, // const int - cells, // CellSeed* - cellsLUTsArrayDevice, // int** - deltaROF, // const int + thrust::device_vector layerxX0(layerxX0Host); + gpu::computeLayerCellsKernel<<<60, 256, 0, streams[cellTopologyId].get()>>>( + sortedClusters, // const Cluster** + unsortedClusters, // const Cluster** + tfInfo, // const TrackingFrameInfo** + tracklets, // const Tracklets** + trackletsLUT, // const int** + nTracklets, // const int + cellTopologyId, // const int + topology, + cells, // CellSeed* + cellsLUTsArrayDevice, // int** + thrust::raw_pointer_cast(&layerxX0[0]), bz, // const float maxChi2ClusterAttachment, // const float cellDeltaTanLambdaSigma, // const float nSigmaCut); // const float } -template -void countCellNeighboursHandler(CellSeed** cellsLayersDevice, - int* neighboursLUT, +template +void countCellNeighboursHandler(CellSeed** cellsLayersDevice, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, - const int deltaROF, + const int sourceCellTopologyId, + const int targetCellTopologyId, const float maxChi2ClusterAttachment, const float bz, - const int layerIndex, const unsigned int nCells, - const unsigned int nCellsNext, - const int maxCellNeighbours, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Stream& stream) { - gpu::computeLayerCellNeighboursKernel<<>>( + gpu::computeLayerCellNeighboursKernel<<<60, 256, 0, stream.get()>>>( cellsLayersDevice, - neighboursLUT, - neighboursIndexTable, + neighboursCursor, cellsLUTs, - cellNeighbours, - tracklets, - deltaROF, + nullptr, + sourceCellTopologyId, + targetCellTopologyId, maxChi2ClusterAttachment, bz, - layerIndex, - nCells, - maxCellNeighbours); + nCells); +} + +void scanCellNeighboursHandler(int* neighboursCursor, + int* neighboursLUT, + const unsigned int nCells, + o2::its::ExternalAllocator* alloc, + gpu::Stream& stream) +{ auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(stream.get()); - thrust::inclusive_scan(nosync_policy, neighboursLUT, neighboursLUT + nCellsNext, neighboursLUT); - thrust::exclusive_scan(nosync_policy, neighboursIndexTable, neighboursIndexTable + nCells + 1, neighboursIndexTable); + thrust::exclusive_scan(nosync_policy, neighboursCursor, neighboursCursor + nCells + 1, neighboursCursor); + GPUChkErrS(cudaMemcpyAsync(neighboursLUT, neighboursCursor, (nCells + 1) * sizeof(int), cudaMemcpyDeviceToDevice, stream.get())); } -template -void computeCellNeighboursHandler(CellSeed** cellsLayersDevice, - int* neighboursLUT, +template +void computeCellNeighboursHandler(CellSeed** cellsLayersDevice, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, - const int deltaROF, + CellNeighbour* cellNeighbours, + const int sourceCellTopologyId, + const int targetCellTopologyId, const float maxChi2ClusterAttachment, const float bz, - const int layerIndex, const unsigned int nCells, - const unsigned int nCellsNext, - const int maxCellNeighbours, - const int nBlocks, - const int nThreads, gpu::Stream& stream) { - gpu::computeLayerCellNeighboursKernel<<>>( + gpu::computeLayerCellNeighboursKernel<<<60, 256, 0, stream.get()>>>( cellsLayersDevice, - neighboursLUT, - neighboursIndexTable, + neighboursCursor, cellsLUTs, cellNeighbours, - tracklets, - deltaROF, + sourceCellTopologyId, + targetCellTopologyId, maxChi2ClusterAttachment, bz, - layerIndex, - nCells, - maxCellNeighbours); + nCells); } int filterCellNeighboursHandler(gpuPair* cellNeighbourPairs, @@ -1087,47 +873,55 @@ int filterCellNeighboursHandler(gpuPair* cellNeighbourPairs, return newSize; } -template -void processNeighboursHandler(const int startLayer, - const int startLevel, - CellSeed** allCellSeeds, - CellSeed* currentCellSeeds, - std::array& nCells, +template +void processNeighboursHandler(const int startLevel, + const int defaultCellTopologyId, + CellSeed** allCellSeeds, + CellSeed* currentCellSeeds, + const int* currentCellTopologyIds, + const int* currentCellIds, + const int* nCells, const unsigned char** usedClusters, - std::array& neighbours, - gsl::span neighboursDeviceLUTs, + CellNeighbour** neighbours, + int** neighboursDeviceLUTs, const TrackingFrameInfo** foundTrackingFrameInfo, - bounded_vector>& seedsHost, + bounded_vector>& seedsHost, const float bz, const float maxChi2ClusterAttachment, const float maxChi2NDF, + const int maxHoles, + const int minTrackLength, + const LayerMask holeLayerMask, + const std::vector& layerxX0Host, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads) + o2::its::ExternalAllocator* alloc) { constexpr uint64_t Tag = qStr2Tag("ITS_PNH1"); alloc->pushTagOnStack(Tag); auto allocInt = gpu::TypedAllocator(alloc); - auto allocCellSeed = gpu::TypedAllocator>(alloc); - thrust::device_vector> foundSeedsTable(nCells[startLayer] + 1, 0, allocInt); + auto allocTrackSeed = gpu::TypedAllocator>(alloc); + thrust::device_vector layerxX0(layerxX0Host); + thrust::device_vector> foundSeedsTable(nCells[defaultCellTopologyId] + 1, 0, allocInt); auto nosync_policy = THRUST_NAMESPACE::par_nosync(gpu::TypedAllocator(alloc)).on(gpu::Stream::DefaultStream); - gpu::processNeighboursKernel<<>>( - startLayer, + gpu::processNeighboursKernel<<<60, 256>>>( + defaultCellTopologyId, startLevel, allCellSeeds, currentCellSeeds, nullptr, - nCells[startLayer], + nullptr, + nCells[defaultCellTopologyId], + nullptr, nullptr, nullptr, thrust::raw_pointer_cast(&foundSeedsTable[0]), usedClusters, - neighbours[startLayer - 1], - neighboursDeviceLUTs[startLayer - 1], + neighbours, + neighboursDeviceLUTs, foundTrackingFrameInfo, + thrust::raw_pointer_cast(&layerxX0[0]), bz, maxChi2ClusterAttachment, propagator, @@ -1135,21 +929,25 @@ void processNeighboursHandler(const int startLayer, thrust::exclusive_scan(nosync_policy, foundSeedsTable.begin(), foundSeedsTable.end(), foundSeedsTable.begin()); thrust::device_vector> updatedCellId(foundSeedsTable.back(), 0, allocInt); - thrust::device_vector, gpu::TypedAllocator>> updatedCellSeed(foundSeedsTable.back(), allocCellSeed); - gpu::processNeighboursKernel<<>>( - startLayer, + thrust::device_vector> updatedCellTopologyId(foundSeedsTable.back(), 0, allocInt); + thrust::device_vector, gpu::TypedAllocator>> updatedCellSeed(foundSeedsTable.back(), allocTrackSeed); + gpu::processNeighboursKernel<<<60, 256>>>( + defaultCellTopologyId, startLevel, allCellSeeds, currentCellSeeds, nullptr, - nCells[startLayer], + nullptr, + nCells[defaultCellTopologyId], thrust::raw_pointer_cast(&updatedCellSeed[0]), thrust::raw_pointer_cast(&updatedCellId[0]), + thrust::raw_pointer_cast(&updatedCellTopologyId[0]), thrust::raw_pointer_cast(&foundSeedsTable[0]), usedClusters, - neighbours[startLayer - 1], - neighboursDeviceLUTs[startLayer - 1], + neighbours, + neighboursDeviceLUTs, foundTrackingFrameInfo, + thrust::raw_pointer_cast(&layerxX0[0]), bz, maxChi2ClusterAttachment, propagator, @@ -1158,30 +956,37 @@ void processNeighboursHandler(const int startLayer, int level = startLevel; thrust::device_vector> lastCellId(allocInt); - thrust::device_vector, gpu::TypedAllocator>> lastCellSeed(allocCellSeed); - for (int iLayer{startLayer - 1}; iLayer > 0 && level > 2; --iLayer) { + thrust::device_vector> lastCellTopologyId(allocInt); + thrust::device_vector, gpu::TypedAllocator>> lastCellSeed(allocTrackSeed); + while (level > 2 && !updatedCellSeed.empty()) { lastCellSeed.swap(updatedCellSeed); lastCellId.swap(updatedCellId); - thrust::device_vector, gpu::TypedAllocator>>(allocCellSeed).swap(updatedCellSeed); + lastCellTopologyId.swap(updatedCellTopologyId); + thrust::device_vector, gpu::TypedAllocator>>(allocTrackSeed).swap(updatedCellSeed); thrust::device_vector>(allocInt).swap(updatedCellId); + thrust::device_vector>(allocInt).swap(updatedCellTopologyId); auto lastCellSeedSize{lastCellSeed.size()}; foundSeedsTable.resize(lastCellSeedSize + 1); thrust::fill(nosync_policy, foundSeedsTable.begin(), foundSeedsTable.end(), 0); - gpu::processNeighboursKernel<<>>( - iLayer, - --level, + --level; + gpu::processNeighboursKernel><<<60, 256>>>( + constants::UnusedIndex, + level, allCellSeeds, thrust::raw_pointer_cast(&lastCellSeed[0]), thrust::raw_pointer_cast(&lastCellId[0]), + thrust::raw_pointer_cast(&lastCellTopologyId[0]), lastCellSeedSize, nullptr, nullptr, + nullptr, thrust::raw_pointer_cast(&foundSeedsTable[0]), usedClusters, - neighbours[iLayer - 1], - neighboursDeviceLUTs[iLayer - 1], + neighbours, + neighboursDeviceLUTs, foundTrackingFrameInfo, + thrust::raw_pointer_cast(&layerxX0[0]), bz, maxChi2ClusterAttachment, propagator, @@ -1191,44 +996,50 @@ void processNeighboursHandler(const int startLayer, auto foundSeeds{foundSeedsTable.back()}; updatedCellId.resize(foundSeeds); thrust::fill(nosync_policy, updatedCellId.begin(), updatedCellId.end(), 0); + updatedCellTopologyId.resize(foundSeeds); + thrust::fill(nosync_policy, updatedCellTopologyId.begin(), updatedCellTopologyId.end(), 0); updatedCellSeed.resize(foundSeeds); - thrust::fill(nosync_policy, updatedCellSeed.begin(), updatedCellSeed.end(), CellSeed()); + thrust::fill(nosync_policy, updatedCellSeed.begin(), updatedCellSeed.end(), TrackSeed()); - gpu::processNeighboursKernel<<>>( - iLayer, + gpu::processNeighboursKernel><<<60, 256>>>( + constants::UnusedIndex, level, allCellSeeds, thrust::raw_pointer_cast(&lastCellSeed[0]), thrust::raw_pointer_cast(&lastCellId[0]), + thrust::raw_pointer_cast(&lastCellTopologyId[0]), lastCellSeedSize, thrust::raw_pointer_cast(&updatedCellSeed[0]), thrust::raw_pointer_cast(&updatedCellId[0]), + thrust::raw_pointer_cast(&updatedCellTopologyId[0]), thrust::raw_pointer_cast(&foundSeedsTable[0]), usedClusters, - neighbours[iLayer - 1], - neighboursDeviceLUTs[iLayer - 1], + neighbours, + neighboursDeviceLUTs, foundTrackingFrameInfo, + thrust::raw_pointer_cast(&layerxX0[0]), bz, maxChi2ClusterAttachment, propagator, matCorrType); } GPUChkErrS(cudaStreamSynchronize(gpu::Stream::DefaultStream)); - thrust::device_vector, gpu::TypedAllocator>> outSeeds(updatedCellSeed.size(), allocCellSeed); - auto end = thrust::copy_if(nosync_policy, updatedCellSeed.begin(), updatedCellSeed.end(), outSeeds.begin(), gpu::seed_selector(1.e3, maxChi2NDF * ((startLevel + 2) * 2 - 5))); + thrust::device_vector, gpu::TypedAllocator>> outSeeds(updatedCellSeed.size(), allocTrackSeed); + auto end = thrust::copy_if(nosync_policy, updatedCellSeed.begin(), updatedCellSeed.end(), outSeeds.begin(), gpu::seed_selector(1.e3, maxChi2NDF * ((startLevel + 2) * 2 - 5), maxHoles, minTrackLength, holeLayerMask)); auto s{end - outSeeds.begin()}; seedsHost.reserve(seedsHost.size() + s); thrust::copy(outSeeds.begin(), outSeeds.begin() + s, std::back_inserter(seedsHost)); alloc->popTagOffStack(Tag); } -template -void countTrackSeedHandler(CellSeed* trackSeeds, +template +void countTrackSeedHandler(TrackSeed* trackSeeds, const TrackingFrameInfo** foundTrackingFrameInfo, const Cluster** unsortedClusters, int* seedLUT, const std::vector& layerRadiiHost, const std::vector& minPtsHost, + const std::vector& layerxX0Host, const unsigned int nSeeds, const float bz, const int startLevel, @@ -1239,16 +1050,15 @@ void countTrackSeedHandler(CellSeed* trackSeeds, const bool shiftRefToCluster, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads) + o2::its::ExternalAllocator* alloc) { // TODO: the minPts&layerRadii is transfered twice // we should allocate this in constant memory and stop these // small transferes! thrust::device_vector minPts(minPtsHost); thrust::device_vector layerRadii(layerRadiiHost); - gpu::fitTrackSeedsKernel<<>>( + thrust::device_vector layerxX0(layerxX0Host); + gpu::fitTrackSeedsKernel<<<60, 256>>>( trackSeeds, // CellSeed* foundTrackingFrameInfo, // TrackingFrameInfo** unsortedClusters, // Cluster** @@ -1256,6 +1066,7 @@ void countTrackSeedHandler(CellSeed* trackSeeds, seedLUT, // int* thrust::raw_pointer_cast(&layerRadii[0]), // const float* thrust::raw_pointer_cast(&minPts[0]), // const float* + thrust::raw_pointer_cast(&layerxX0[0]), // const float* nSeeds, // const unsigned int bz, // const float startLevel, // const int @@ -1270,14 +1081,15 @@ void countTrackSeedHandler(CellSeed* trackSeeds, thrust::exclusive_scan(sync_policy, seedLUT, seedLUT + nSeeds + 1, seedLUT); } -template -void computeTrackSeedHandler(CellSeed* trackSeeds, +template +void computeTrackSeedHandler(TrackSeed* trackSeeds, const TrackingFrameInfo** foundTrackingFrameInfo, const Cluster** unsortedClusters, o2::its::TrackITSExt* tracks, const int* seedLUT, const std::vector& layerRadiiHost, const std::vector& minPtsHost, + const std::vector& layerxX0Host, const unsigned int nSeeds, const unsigned int nTracks, const float bz, @@ -1289,13 +1101,12 @@ void computeTrackSeedHandler(CellSeed* trackSeeds, const bool shiftRefToCluster, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads) + o2::its::ExternalAllocator* alloc) { thrust::device_vector minPts(minPtsHost); thrust::device_vector layerRadii(layerRadiiHost); - gpu::fitTrackSeedsKernel<<>>( + thrust::device_vector layerxX0(layerxX0Host); + gpu::fitTrackSeedsKernel<<<60, 256>>>( trackSeeds, // CellSeed* foundTrackingFrameInfo, // TrackingFrameInfo** unsortedClusters, // Cluster** @@ -1303,6 +1114,7 @@ void computeTrackSeedHandler(CellSeed* trackSeeds, seedLUT, // const int* thrust::raw_pointer_cast(&layerRadii[0]), // const float* thrust::raw_pointer_cast(&minPts[0]), // const float* + thrust::raw_pointer_cast(&layerxX0[0]), // const float* nSeeds, // const unsigned int bz, // const float startLevel, // const int @@ -1320,16 +1132,15 @@ void computeTrackSeedHandler(CellSeed* trackSeeds, /// Explicit instantiation of ITS2 handlers template void countTrackletsInROFsHandler<7>(const IndexTableUtils<7>* utils, - const uint8_t* multMask, - const int layer, - const int startROF, - const int endROF, - const int maxROF, - const int deltaROF, + const ROFMaskTable<7>::View& rofMask, + const int transitionId, + const int fromLayer, + const int toLayer, + const ROFOverlapTable<7>::View& rofOverlaps, + const ROFVertexLookupTable<7>::View& vertexLUT, const int vertexId, const Vertex* vertices, const int* rofPV, - const int nVertices, const Cluster** clusters, std::vector nClusters, const int** ROFClusters, @@ -1337,31 +1148,29 @@ template void countTrackletsInROFsHandler<7>(const IndexTableUtils<7>* utils, const int** clustersIndexTables, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const TrackingTopology<7>::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, std::array& minRs, std::array& maxRs, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Streams& streams); template void computeTrackletsInROFsHandler<7>(const IndexTableUtils<7>* utils, - const uint8_t* multMask, - const int layer, - const int startROF, - const int endROF, - const int maxROF, - const int deltaROF, + const ROFMaskTable<7>::View& rofMask, + const int transitionId, + const int fromLayer, + const int toLayer, + const ROFOverlapTable<7>::View& rofOverlaps, + const ROFVertexLookupTable<7>::View& vertexLUT, const int vertexId, const Vertex* vertices, const int* rofPV, - const int nVertices, const Cluster** clusters, std::vector nClusters, const int** ROFClusters, @@ -1372,18 +1181,17 @@ template void computeTrackletsInROFsHandler<7>(const IndexTableUtils<7>* utils, gsl::span nTracklets, int** trackletsLUTs, gsl::span trackletsLUTsHost, - const int iteration, + const bool selectUPCVertices, const float NSigmaCut, - bounded_vector& phiCuts, + const TrackingTopology<7>::View topology, + bounded_vector& transitionPhiCuts, const float resolutionPV, std::array& minRs, std::array& maxRs, bounded_vector& resolutions, std::vector& radii, - bounded_vector& mulScatAng, + bounded_vector& transitionMSAngles, o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Streams& streams); template void countCellsHandler<7>(const Cluster** sortedClusters, @@ -1392,18 +1200,17 @@ template void countCellsHandler<7>(const Cluster** sortedClusters, Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, - CellSeed<7>* cells, + const int cellTopologyId, + const TrackingTopology<7>::View topology, + CellSeed* cells, int** cellsLUTsArrayDevice, int* cellsLUTsHost, - const int deltaROF, const float bz, const float maxChi2ClusterAttachment, const float cellDeltaTanLambdaSigma, const float nSigmaCut, + const std::vector& layerxX0Host, o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Streams& streams); template void computeCellsHandler<7>(const Cluster** sortedClusters, @@ -1412,79 +1219,69 @@ template void computeCellsHandler<7>(const Cluster** sortedClusters, Tracklet** tracklets, int** trackletsLUT, const int nTracklets, - const int layer, - CellSeed<7>* cells, + const int cellTopologyId, + const TrackingTopology<7>::View topology, + CellSeed* cells, int** cellsLUTsArrayDevice, int* cellsLUTsHost, - const int deltaROF, const float bz, const float maxChi2ClusterAttachment, const float cellDeltaTanLambdaSigma, const float nSigmaCut, - const int nBlocks, - const int nThreads, + const std::vector& layerxX0Host, gpu::Streams& streams); -template void countCellNeighboursHandler<7>(CellSeed<7>** cellsLayersDevice, - int* neighboursLUT, +template void countCellNeighboursHandler<7>(CellSeed** cellsLayersDevice, + int* neighboursCursor, int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, - const int deltaROF, + const int sourceCellTopologyId, + const int targetCellTopologyId, const float maxChi2ClusterAttachment, const float bz, - const int layerIndex, const unsigned int nCells, - const unsigned int nCellsNext, - const int maxCellNeighbours, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads, gpu::Stream& stream); -template void computeCellNeighboursHandler(CellSeed<7>** cellsLayersDevice, - int* neighboursLUT, - int** cellsLUTs, - gpuPair* cellNeighbours, - int* neighboursIndexTable, - const Tracklet** tracklets, - const int deltaROF, - const float maxChi2ClusterAttachment, - const float bz, - const int layerIndex, - const unsigned int nCells, - const unsigned int nCellsNext, - const int maxCellNeighbours, - const int nBlocks, - const int nThreads, - gpu::Stream& stream); - -template void processNeighboursHandler<7>(const int startLayer, - const int startLevel, - CellSeed<7>** allCellSeeds, - CellSeed<7>* currentCellSeeds, - std::array& nCells, +template void computeCellNeighboursHandler<7>(CellSeed** cellsLayersDevice, + int* neighboursCursor, + int** cellsLUTs, + CellNeighbour* cellNeighbours, + const int sourceCellTopologyId, + const int targetCellTopologyId, + const float maxChi2ClusterAttachment, + const float bz, + const unsigned int nCells, + gpu::Stream& stream); + +template void processNeighboursHandler<7>(const int startLevel, + const int defaultCellTopologyId, + CellSeed** allCellSeeds, + CellSeed* currentCellSeeds, + const int* currentCellTopologyIds, + const int* currentCellIds, + const int* nCells, const unsigned char** usedClusters, - std::array& neighbours, - gsl::span neighboursDeviceLUTs, + CellNeighbour** neighbours, + int** neighboursDeviceLUTs, const TrackingFrameInfo** foundTrackingFrameInfo, - bounded_vector>& seedsHost, + bounded_vector>& seedsHost, const float bz, const float maxChi2ClusterAttachment, const float maxChi2NDF, + const int maxHoles, + const int minTrackLength, + const LayerMask holeLayerMask, + const std::vector& layerxX0Host, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads); + o2::its::ExternalAllocator* alloc); -template void countTrackSeedHandler(CellSeed<7>* trackSeeds, +template void countTrackSeedHandler(TrackSeed<7>* trackSeeds, const TrackingFrameInfo** foundTrackingFrameInfo, const Cluster** unsortedClusters, int* seedLUT, const std::vector& layerRadiiHost, const std::vector& minPtsHost, + const std::vector& layerxX0Host, const unsigned int nSeeds, const float bz, const int startLevel, @@ -1495,17 +1292,16 @@ template void countTrackSeedHandler(CellSeed<7>* trackSeeds, const bool shiftRefToCluster, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads); + o2::its::ExternalAllocator* alloc); -template void computeTrackSeedHandler(CellSeed<7>* trackSeeds, +template void computeTrackSeedHandler(TrackSeed<7>* trackSeeds, const TrackingFrameInfo** foundTrackingFrameInfo, const Cluster** unsortedClusters, o2::its::TrackITSExt* tracks, const int* seedLUT, const std::vector& layerRadiiHost, const std::vector& minPtsHost, + const std::vector& layerxX0Host, const unsigned int nSeeds, const unsigned int nTracks, const float bz, @@ -1517,8 +1313,192 @@ template void computeTrackSeedHandler(CellSeed<7>* trackSeeds, const bool shiftRefToCluster, const o2::base::Propagator* propagator, const o2::base::PropagatorF::MatCorrType matCorrType, - o2::its::ExternalAllocator* alloc, - const int nBlocks, - const int nThreads); + o2::its::ExternalAllocator* alloc); + +/// Explicit instantiation of ALICE3 handlers +#ifdef ENABLE_UPGRADES +template void countTrackletsInROFsHandler<11>(const IndexTableUtils<11>* utils, + const ROFMaskTable<11>::View& rofMask, + const int transitionId, + const int fromLayer, + const int toLayer, + const ROFOverlapTable<11>::View& rofOverlaps, + const ROFVertexLookupTable<11>::View& vertexLUT, + const int vertexId, + const Vertex* vertices, + const int* rofPV, + const Cluster** clusters, + std::vector nClusters, + const int** ROFClusters, + const unsigned char** usedClusters, + const int** clustersIndexTables, + int** trackletsLUTs, + gsl::span trackletsLUTsHost, + const bool selectUPCVertices, + const float NSigmaCut, + const TrackingTopology<11>::View topology, + bounded_vector& transitionPhiCuts, + const float resolutionPV, + std::array& minRs, + std::array& maxRs, + bounded_vector& resolutions, + std::vector& radii, + bounded_vector& transitionMSAngles, + o2::its::ExternalAllocator* alloc, + gpu::Streams& streams); + +template void computeTrackletsInROFsHandler<11>(const IndexTableUtils<11>* utils, + const ROFMaskTable<11>::View& rofMask, + const int transitionId, + const int fromLayer, + const int toLayer, + const ROFOverlapTable<11>::View& rofOverlaps, + const ROFVertexLookupTable<11>::View& vertexLUT, + const int vertexId, + const Vertex* vertices, + const int* rofPV, + const Cluster** clusters, + std::vector nClusters, + const int** ROFClusters, + const unsigned char** usedClusters, + const int** clustersIndexTables, + Tracklet** tracklets, + gsl::span spanTracklets, + gsl::span nTracklets, + int** trackletsLUTs, + gsl::span trackletsLUTsHost, + const bool selectUPCVertices, + const float NSigmaCut, + const TrackingTopology<11>::View topology, + bounded_vector& transitionPhiCuts, + const float resolutionPV, + std::array& minRs, + std::array& maxRs, + bounded_vector& resolutions, + std::vector& radii, + bounded_vector& transitionMSAngles, + o2::its::ExternalAllocator* alloc, + gpu::Streams& streams); + +template void countCellsHandler<11>(const Cluster** sortedClusters, + const Cluster** unsortedClusters, + const TrackingFrameInfo** tfInfo, + Tracklet** tracklets, + int** trackletsLUT, + const int nTracklets, + const int cellTopologyId, + const TrackingTopology<11>::View topology, + CellSeed* cells, + int** cellsLUTsArrayDevice, + int* cellsLUTsHost, + const float bz, + const float maxChi2ClusterAttachment, + const float cellDeltaTanLambdaSigma, + const float nSigmaCut, + const std::vector& layerxX0Host, + o2::its::ExternalAllocator* alloc, + gpu::Streams& streams); + +template void computeCellsHandler<11>(const Cluster** sortedClusters, + const Cluster** unsortedClusters, + const TrackingFrameInfo** tfInfo, + Tracklet** tracklets, + int** trackletsLUT, + const int nTracklets, + const int cellTopologyId, + const TrackingTopology<11>::View topology, + CellSeed* cells, + int** cellsLUTsArrayDevice, + int* cellsLUTsHost, + const float bz, + const float maxChi2ClusterAttachment, + const float cellDeltaTanLambdaSigma, + const float nSigmaCut, + const std::vector& layerxX0Host, + gpu::Streams& streams); + +template void countCellNeighboursHandler<11>(CellSeed** cellsLayersDevice, + int* neighboursCursor, + int** cellsLUTs, + const int sourceCellTopologyId, + const int targetCellTopologyId, + const float maxChi2ClusterAttachment, + const float bz, + const unsigned int nCells, + gpu::Stream& stream); + +template void computeCellNeighboursHandler<11>(CellSeed** cellsLayersDevice, + int* neighboursCursor, + int** cellsLUTs, + CellNeighbour* cellNeighbours, + const int sourceCellTopologyId, + const int targetCellTopologyId, + const float maxChi2ClusterAttachment, + const float bz, + const unsigned int nCells, + gpu::Stream& stream); + +template void processNeighboursHandler<11>(const int startLevel, + const int defaultCellTopologyId, + CellSeed** allCellSeeds, + CellSeed* currentCellSeeds, + const int* currentCellTopologyIds, + const int* currentCellIds, + const int* nCells, + const unsigned char** usedClusters, + CellNeighbour** neighbours, + int** neighboursDeviceLUTs, + const TrackingFrameInfo** foundTrackingFrameInfo, + bounded_vector>& seedsHost, + const float bz, + const float maxChi2ClusterAttachment, + const float maxChi2NDF, + const int maxHoles, + const int minTrackLength, + const LayerMask holeLayerMask, + const std::vector& layerxX0Host, + const o2::base::Propagator* propagator, + const o2::base::PropagatorF::MatCorrType matCorrType, + o2::its::ExternalAllocator* alloc); + +template void countTrackSeedHandler(TrackSeed<11>* trackSeeds, + const TrackingFrameInfo** foundTrackingFrameInfo, + const Cluster** unsortedClusters, + int* seedLUT, + const std::vector& layerRadiiHost, + const std::vector& minPtsHost, + const std::vector& layerxX0Host, + const unsigned int nSeeds, + const float bz, + const int startLevel, + const float maxChi2ClusterAttachment, + const float maxChi2NDF, + const int reseedIfShorter, + const bool repeatRefitOut, + const bool shiftRefToCluster, + const o2::base::Propagator* propagator, + const o2::base::PropagatorF::MatCorrType matCorrType, + o2::its::ExternalAllocator* alloc); +template void computeTrackSeedHandler(TrackSeed<11>* trackSeeds, + const TrackingFrameInfo** foundTrackingFrameInfo, + const Cluster** unsortedClusters, + o2::its::TrackITSExt* tracks, + const int* seedLUT, + const std::vector& layerRadiiHost, + const std::vector& minPtsHost, + const std::vector& layerxX0Host, + const unsigned int nSeeds, + const unsigned int nTracks, + const float bz, + const int startLevel, + const float maxChi2ClusterAttachment, + const float maxChi2NDF, + const int reseedIfShorter, + const bool repeatRefitOut, + const bool shiftRefToCluster, + const o2::base::Propagator* propagator, + const o2::base::PropagatorF::MatCorrType matCorrType, + o2::its::ExternalAllocator* alloc); +#endif } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/VertexerTraitsGPU.cxx b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/VertexerTraitsGPU.cxx deleted file mode 100644 index 658d3cf0dfb91..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/VertexerTraitsGPU.cxx +++ /dev/null @@ -1,179 +0,0 @@ -// 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. -// -/// \author matteo.concas@cern.ch - -#include - -#include "ITStracking/TrackingConfigParam.h" -#include "ITStrackingGPU/VertexingKernels.h" -#include "ITStrackingGPU/VertexerTraitsGPU.h" - -namespace o2::its -{ - -template -void VertexerTraitsGPU::initialise(const TrackingParameters& trackingParams, const int iteration) -{ - // FIXME - // Two things to fix here: - // This loads all necessary data for this step at once, can be overlayed with computation - // Also if running with the tracker some data is loaded twice! - mTimeFrameGPU->initialise(0, trackingParams, 3, &this->mIndexTableUtils, &mTfGPUParams); - - // FIXME some of these only need to be created once! - mTimeFrameGPU->loadIndexTableUtils(iteration); - mTimeFrameGPU->createUsedClustersDeviceArray(iteration, 3); - mTimeFrameGPU->createClustersDeviceArray(iteration, 3); - mTimeFrameGPU->createUnsortedClustersDeviceArray(iteration, 3); - mTimeFrameGPU->createClustersIndexTablesArray(iteration); - mTimeFrameGPU->createROFrameClustersDeviceArray(iteration); - for (int iLayer{0}; iLayer < 3; ++iLayer) { - mTimeFrameGPU->loadClustersDevice(iteration, iLayer); - mTimeFrameGPU->loadUnsortedClustersDevice(iteration, iLayer); - mTimeFrameGPU->loadClustersIndexTables(iteration, iLayer); - mTimeFrameGPU->createUsedClustersDevice(iteration, iLayer); - mTimeFrameGPU->loadROFrameClustersDevice(iteration, iLayer); - } -} - -template -void VertexerTraitsGPU::adoptTimeFrame(TimeFrame* tf) noexcept -{ - mTimeFrameGPU = static_cast*>(tf); - this->mTimeFrame = static_cast*>(tf); -} - -template -void VertexerTraitsGPU::updateVertexingParameters(const std::vector& vrtPar, const TimeFrameGPUParameters& tfPar) -{ - this->mVrtParams = vrtPar; - mTfGPUParams = tfPar; - this->mIndexTableUtils.setTrackingParameters(vrtPar[0]); - for (auto& par : this->mVrtParams) { - par.phiSpan = static_cast(std::ceil(this->mIndexTableUtils.getNphiBins() * par.phiCut / o2::constants::math::TwoPI)); - par.zSpan = static_cast(std::ceil(par.zCut * this->mIndexTableUtils.getInverseZCoordinate(0))); - } -} - -template -void VertexerTraitsGPU::computeTracklets(const int iteration) -{ - if (mTimeFrameGPU->getClusters().empty()) { - return; - } - const auto& conf = ITSGpuTrackingParamConfig::Instance(); - - mTimeFrameGPU->createVtxTrackletsLUTDevice(iteration); - countTrackletsInROFsHandler(mTimeFrameGPU->getDeviceIndexTableUtils(), - mTimeFrameGPU->getDeviceMultCutMask(), - mTimeFrameGPU->getNrof(), - this->mVrtParams[iteration].deltaRof, - mTimeFrameGPU->getDeviceROFramesPV(), - this->mVrtParams[iteration].vertPerRofThreshold, - mTimeFrameGPU->getDeviceArrayClusters(), - mTimeFrameGPU->getClusterSizes()[1], - mTimeFrameGPU->getDeviceROFrameClusters(), - (const uint8_t**)mTimeFrameGPU->getDeviceArrayUsedClusters(), - mTimeFrameGPU->getDeviceArrayClustersIndexTables(), - mTimeFrameGPU->getDeviceArrayNTrackletsPerCluster(), - mTimeFrameGPU->getDeviceArrayNTrackletsPerClusterSum(), - mTimeFrameGPU->getDeviceArrayNTrackletsPerROF(), - mTimeFrameGPU->getDeviceNTrackletsPerCluster(), - mTimeFrameGPU->getDeviceNTrackletsPerClusterSum(), - iteration, - this->mVrtParams[iteration].phiCut, - this->mVrtParams[iteration].maxTrackletsPerCluster, - conf.nBlocksVtxComputeTracklets[iteration], - conf.nThreadsVtxComputeTracklets[iteration], - mTimeFrameGPU->getStreams()); - mTimeFrameGPU->createVtxTrackletsBuffers(iteration); - computeTrackletsInROFsHandler(mTimeFrameGPU->getDeviceIndexTableUtils(), - mTimeFrameGPU->getDeviceMultCutMask(), - mTimeFrameGPU->getNrof(), - this->mVrtParams[iteration].deltaRof, - mTimeFrameGPU->getDeviceROFramesPV(), - this->mVrtParams[iteration].vertPerRofThreshold, - mTimeFrameGPU->getDeviceArrayClusters(), - mTimeFrameGPU->getClusterSizes()[1], - mTimeFrameGPU->getDeviceROFrameClusters(), - (const uint8_t**)mTimeFrameGPU->getDeviceArrayUsedClusters(), - mTimeFrameGPU->getDeviceArrayClustersIndexTables(), - mTimeFrameGPU->getDeviceArrayTracklets(), - (const int32_t**)mTimeFrameGPU->getDeviceArrayNTrackletsPerCluster(), - (const int32_t**)mTimeFrameGPU->getDeviceArrayNTrackletsPerClusterSum(), - (const int32_t**)mTimeFrameGPU->getDeviceArrayNTrackletsPerROF(), - iteration, - this->mVrtParams[iteration].phiCut, - this->mVrtParams[iteration].maxTrackletsPerCluster, - conf.nBlocksVtxComputeTracklets[iteration], - conf.nThreadsVtxComputeTracklets[iteration], - mTimeFrameGPU->getStreams()); -} - -template -void VertexerTraitsGPU::computeTrackletMatching(const int iteration) -{ - if (!mTimeFrameGPU->getTotalTrackletsTF(0) || !mTimeFrameGPU->getTotalTrackletsTF(1)) { - return; - } - - const auto& conf = ITSGpuTrackingParamConfig::Instance(); - mTimeFrameGPU->createVtxLinesLUTDevice(iteration); - countTrackletsMatchingInROFsHandler(mTimeFrameGPU->getNrof(), - this->mVrtParams[iteration].deltaRof, - mTimeFrameGPU->getClusterSizes()[1], - mTimeFrameGPU->getDeviceROFrameClusters(), - mTimeFrameGPU->getDeviceArrayClusters(), - mTimeFrameGPU->getDeviceArrayUsedClusters(), - (const Tracklet**)mTimeFrameGPU->getDeviceArrayTracklets(), - mTimeFrameGPU->getDeviceUsedTracklets(), - (const int32_t**)mTimeFrameGPU->getDeviceArrayNTrackletsPerCluster(), - (const int32_t**)mTimeFrameGPU->getDeviceArrayNTrackletsPerClusterSum(), - mTimeFrameGPU->getDeviceNLinesPerCluster(), - mTimeFrameGPU->getDeviceNLinesPerClusterSum(), - iteration, - this->mVrtParams[iteration].phiCut, - this->mVrtParams[iteration].tanLambdaCut, - conf.nBlocksVtxComputeMatching[iteration], - conf.nThreadsVtxComputeMatching[iteration], - mTimeFrameGPU->getStreams()); - mTimeFrameGPU->createVtxLinesBuffer(iteration); - computeTrackletsMatchingInROFsHandler(mTimeFrameGPU->getNrof(), - this->mVrtParams[iteration].deltaRof, - mTimeFrameGPU->getClusterSizes()[1], - mTimeFrameGPU->getDeviceROFrameClusters(), - mTimeFrameGPU->getDeviceArrayClusters(), - nullptr, - (const Tracklet**)mTimeFrameGPU->getDeviceArrayTracklets(), - mTimeFrameGPU->getDeviceUsedTracklets(), - (const int32_t**)mTimeFrameGPU->getDeviceArrayNTrackletsPerCluster(), - (const int32_t**)mTimeFrameGPU->getDeviceArrayNTrackletsPerClusterSum(), - (const int32_t*)mTimeFrameGPU->getDeviceNLinesPerClusterSum(), - mTimeFrameGPU->getDeviceLines(), - iteration, - this->mVrtParams[iteration].phiCut, - this->mVrtParams[iteration].tanLambdaCut, - conf.nBlocksVtxComputeMatching[iteration], - conf.nThreadsVtxComputeMatching[iteration], - mTimeFrameGPU->getStreams()); -} - -template -void VertexerTraitsGPU::computeVertices(const int iteration) -{ - LOGP(fatal, "This step is not implemented yet!"); - mTimeFrameGPU->loadUsedClustersDevice(); -} - -template class VertexerTraitsGPU<7>; - -} // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/VertexingKernels.cu b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/VertexingKernels.cu deleted file mode 100644 index a2787bb13598d..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/VertexingKernels.cu +++ /dev/null @@ -1,660 +0,0 @@ -// 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. -/// - -#include -#include - -#include "ITStrackingGPU/VertexingKernels.h" -#include "ITStracking/Tracklet.h" -#include "ITStracking/IndexTableUtils.h" -#include "ITStracking/ClusterLines.h" - -#include "GPUCommonMath.h" -#include "GPUCommonHelpers.h" -#include "GPUCommonDef.h" - -namespace o2::its -{ - -namespace gpu -{ - -template -GPUg() void computeLayerTrackletMutliROFKernel(const Cluster** GPUrestrict() clusters, - const int32_t** GPUrestrict() rofClusters, - const uint8_t** GPUrestrict() usedClusters, - const int32_t** GPUrestrict() clusterIndexTables, - const float phiCut, - maybe_const** GPUrestrict() tracklets, - maybe_const** GPUrestrict() trackletOffsets, - const IndexTableUtils* GPUrestrict() utils, - const int32_t nRofs, - const int32_t deltaRof, - const int32_t* GPUrestrict() rofPV, - const int32_t iteration, - const int32_t verPerRofThreshold, - const int32_t maxTrackletsPerCluster) -{ - constexpr int32_t iMode = (Mode == TrackletMode::Layer0Layer1) ? 0 : 1; - const int32_t phiBins(utils->getNphiBins()); - const int32_t zBins(utils->getNzBins()); - const int32_t tableSize{phiBins * zBins + 1}; - extern __shared__ uint16_t storedTrackletsShared[]; // each deltaROF needs its own counters - uint16_t* storedTrackletsLocal = storedTrackletsShared + threadIdx.x * (2 * deltaRof + 1); - for (uint32_t pivotRofId{blockIdx.x}; pivotRofId < (uint32_t)nRofs; pivotRofId += gridDim.x) { - if (iteration && rofPV[pivotRofId] > verPerRofThreshold) { - continue; - } - const uint16_t startROF = o2::gpu::CAMath::Max(0, (int)pivotRofId - deltaRof); - const uint16_t endROF = o2::gpu::CAMath::Min(nRofs, (int)pivotRofId + deltaRof + 1); - const auto clustersCurrentLayer = getClustersOnLayer((int32_t)pivotRofId, nRofs, 1, rofClusters, clusters); - if (clustersCurrentLayer.empty()) { - continue; - } - auto trackletsPerCluster = getNTrackletsPerCluster(pivotRofId, nRofs, iMode, rofClusters, trackletOffsets); - for (uint32_t iCurrentLayerClusterIndex{threadIdx.x}; iCurrentLayerClusterIndex < (uint32_t)clustersCurrentLayer.size(); iCurrentLayerClusterIndex += blockDim.x) { - for (int16_t i{0}; i < (int16_t)((2 * deltaRof) + 1); ++i) { - storedTrackletsLocal[i] = 0; - } - const Cluster& GPUrestrict() currentCluster { clustersCurrentLayer[iCurrentLayerClusterIndex] }; - const int4 selectedBinsRect{getBinsRect(currentCluster, (int)Mode, utils, 0.f, 0.f, 50.f, phiCut / 2)}; - if (selectedBinsRect.x != 0 || selectedBinsRect.y != 0 || selectedBinsRect.z != 0 || selectedBinsRect.w != 0) { - int phiBinsNum{selectedBinsRect.w - selectedBinsRect.y + 1}; - if (phiBinsNum < 0) { - phiBinsNum += phiBins; - } - for (int32_t iPhiBin{selectedBinsRect.y}, iPhiCount{0}; iPhiCount < phiBinsNum; iPhiBin = ++iPhiBin == phiBins ? 0 : iPhiBin, iPhiCount++) { - for (uint16_t targetRofId{startROF}; targetRofId < endROF; ++targetRofId) { - uint16_t& storedTracklets = storedTrackletsLocal[pivotRofId - targetRofId + deltaRof]; - const int32_t firstBinIndex{utils->getBinIndex(selectedBinsRect.x, iPhiBin)}; - const int32_t maxBinIndex{firstBinIndex + selectedBinsRect.z - selectedBinsRect.x + 1}; - const int32_t firstRowClusterIndex{clusterIndexTables[(int)Mode][(targetRofId)*tableSize + firstBinIndex]}; - const int32_t maxRowClusterIndex{clusterIndexTables[(int)Mode][(targetRofId)*tableSize + maxBinIndex]}; - auto clustersNextLayer = getClustersOnLayer((int32_t)targetRofId, nRofs, (int32_t)Mode, rofClusters, clusters); - if (clustersNextLayer.empty()) { - continue; - } - for (int32_t iNextLayerClusterIndex{firstRowClusterIndex}; iNextLayerClusterIndex < maxRowClusterIndex && iNextLayerClusterIndex < (int32_t)clustersNextLayer.size(); ++iNextLayerClusterIndex) { - if (iteration && usedClusters[(int32_t)Mode][iNextLayerClusterIndex]) { - continue; - } - const Cluster& GPUrestrict() nextCluster { clustersNextLayer[iNextLayerClusterIndex] }; - if (o2::gpu::GPUCommonMath::Abs(math_utils::smallestAngleDifference(currentCluster.phi, nextCluster.phi)) < phiCut) { - if (storedTracklets < maxTrackletsPerCluster) { - if constexpr (!dryRun) { - if constexpr (Mode == TrackletMode::Layer0Layer1) { - tracklets[0][trackletsPerCluster[iCurrentLayerClusterIndex] + storedTracklets] = Tracklet{iNextLayerClusterIndex, (int)iCurrentLayerClusterIndex, nextCluster, currentCluster, (short)targetRofId, (short)pivotRofId}; - } else { - tracklets[1][trackletsPerCluster[iCurrentLayerClusterIndex] + storedTracklets] = Tracklet{(int)iCurrentLayerClusterIndex, iNextLayerClusterIndex, currentCluster, nextCluster, (short)pivotRofId, (short)targetRofId}; - } - } - ++storedTracklets; - } - } - } - } - } - } - if constexpr (dryRun) { - for (int32_t i{0}; i < (int32_t)((2 * deltaRof) + 1); ++i) { - trackletsPerCluster[iCurrentLayerClusterIndex] += storedTrackletsLocal[i]; - } - } - } - } -} - -template -GPUg() void computeTrackletSelectionMutliROFKernel(const Cluster** GPUrestrict() clusters, - maybe_const** GPUrestrict() usedClusters, - const int32_t** GPUrestrict() rofClusters, - const float phiCut, - const float tanLambdaCut, - const Tracklet** GPUrestrict() tracklets, - uint8_t* GPUrestrict() usedTracklets, - const int32_t** GPUrestrict() trackletOffsets, - const int32_t** GPUrestrict() trackletLUTs, - maybe_const* lineOffsets, - maybe_const* GPUrestrict() lines, - const int32_t nRofs, - const int32_t deltaRof, - const int32_t maxTracklets) -{ - for (uint32_t pivotRofId{blockIdx.x}; pivotRofId < nRofs; pivotRofId += gridDim.x) { - const int16_t startROF = o2::gpu::CAMath::Max(0, (int32_t)pivotRofId - deltaRof); - const int16_t endROF = o2::gpu::CAMath::Min(nRofs, (int32_t)pivotRofId + deltaRof + 1); - - const uint32_t clusterOffset = rofClusters[1][pivotRofId]; - const uint32_t nClustersCurrentLayer = rofClusters[1][pivotRofId + 1] - clusterOffset; - if (nClustersCurrentLayer <= 0) { - continue; - } - - auto linesPerCluster = getNLinesPerCluster(pivotRofId, nRofs, rofClusters, lineOffsets); - auto nTrackletsPerCluster01 = getNTrackletsPerCluster(pivotRofId, nRofs, 0, rofClusters, trackletOffsets); - auto nTrackletsPerCluster12 = getNTrackletsPerCluster(pivotRofId, nRofs, 1, rofClusters, trackletOffsets); - - for (uint32_t iCurrentLayerClusterIndex{threadIdx.x}; iCurrentLayerClusterIndex < nClustersCurrentLayer; iCurrentLayerClusterIndex += blockDim.x) { - int32_t validTracklets{0}; - const int32_t nTracklets01 = nTrackletsPerCluster01[iCurrentLayerClusterIndex]; - const int32_t nTracklets12 = nTrackletsPerCluster12[iCurrentLayerClusterIndex]; - for (int32_t iTracklet12{0}; iTracklet12 < nTracklets12; ++iTracklet12) { - for (int32_t iTracklet01{0}; iTracklet01 < nTracklets01; ++iTracklet01) { - - if (usedTracklets[trackletLUTs[0][clusterOffset + iCurrentLayerClusterIndex] + iTracklet01]) { - continue; - } - - const auto& GPUrestrict() tracklet01 { tracklets[0][trackletLUTs[0][clusterOffset + iCurrentLayerClusterIndex] + iTracklet01] }; - const auto& GPUrestrict() tracklet12 { tracklets[1][trackletLUTs[1][clusterOffset + iCurrentLayerClusterIndex] + iTracklet12] }; - const int16_t rof0 = tracklet01.rof[0]; - const int16_t rof2 = tracklet12.rof[1]; - if (deltaRof > 0 && ((rof0 < startROF) || (rof0 >= endROF) || (rof2 < startROF) || (rof2 >= endROF) || (o2::gpu::CAMath::Abs(rof0 - rof2) > deltaRof))) { - continue; - } - - const float deltaTanLambda{o2::gpu::GPUCommonMath::Abs(tracklet01.tanLambda - tracklet12.tanLambda)}; - const float deltaPhi{o2::gpu::GPUCommonMath::Abs(math_utils::smallestAngleDifference(tracklet01.phi, tracklet12.phi))}; - // - if (deltaTanLambda < tanLambdaCut && deltaPhi < phiCut && validTracklets < maxTracklets) { - // TODO use atomics to avoid race conditions for torn writes but is it needed here? - usedTracklets[trackletLUTs[0][clusterOffset + iCurrentLayerClusterIndex] + iTracklet01] = 1; - if constexpr (dryRun) { - usedClusters[0][rofClusters[0][rof0] + tracklet01.firstClusterIndex] = 1; - usedClusters[2][rofClusters[2][rof2] + tracklet12.secondClusterIndex] = 1; - } else { - const Cluster* clusters0 = clusters[0] + rofClusters[0][tracklet01.rof[0]]; - const Cluster* clusters1 = clusters[1] + rofClusters[1][tracklet01.rof[1]]; - lines[lineOffsets[iCurrentLayerClusterIndex] + validTracklets] = Line(tracklet01, clusters0, clusters1); - } - ++validTracklets; - } - } - } - - if constexpr (dryRun) { - linesPerCluster[iCurrentLayerClusterIndex] = validTracklets; - } - } - } -} - -template -GPUg() void compileTrackletsPerROFKernel(const int32_t nRofs, - int** GPUrestrict() nTrackletsPerROF, - const int32_t** GPUrestrict() rofClusters, - const int32_t** GPUrestrict() nTrackletsPerCluster) -{ - // TODO is this the best reduction kernel? - constexpr int32_t iMode = (Mode == TrackletMode::Layer0Layer1) ? 0 : 1; - extern __shared__ int32_t ssum[]; - for (uint32_t rof = blockIdx.x; rof < (uint32_t)nRofs; rof += gridDim.x) { - const auto& GPUrestrict() currentNTracklets = getNTrackletsPerCluster(rof, nRofs, iMode, rofClusters, nTrackletsPerCluster); - int32_t localSum = 0; - for (uint32_t ci = threadIdx.x; ci < (uint32_t)currentNTracklets.size(); ci += blockDim.x) { - localSum += currentNTracklets[ci]; - } - ssum[threadIdx.x] = localSum; - __syncthreads(); - for (uint32_t stride = blockDim.x / 2; stride > 0; stride >>= 1) { - if (threadIdx.x < stride) { - ssum[threadIdx.x] += ssum[threadIdx.x + stride]; - } - __syncthreads(); - } - if (threadIdx.x == 0) { - nTrackletsPerROF[iMode][rof] = ssum[0]; - } - } -} - -template -GPUhi() void cubExclusiveScan(const T* GPUrestrict() in, T* GPUrestrict() out, int32_t num_items, cudaStream_t stream) -{ - void* d_temp_storage = nullptr; - size_t temp_storage_bytes = 0; - GPUChkErrS(cub::DeviceScan::InclusiveSum(d_temp_storage, temp_storage_bytes, in, out + 1, num_items, stream)); - GPUChkErrS(cudaMallocAsync(&d_temp_storage, temp_storage_bytes, stream)); - GPUChkErrS(cub::DeviceScan::InclusiveSum(d_temp_storage, temp_storage_bytes, in, out + 1, num_items, stream)); - GPUChkErrS(cudaFreeAsync(d_temp_storage, stream)); -} - -} // namespace gpu - -template -void countTrackletsInROFsHandler(const IndexTableUtils* GPUrestrict() utils, - const uint8_t* GPUrestrict() multMask, - const int32_t nRofs, - const int32_t deltaROF, - const int32_t* GPUrestrict() rofPV, - const int32_t vertPerRofThreshold, - const Cluster** GPUrestrict() clusters, - const uint32_t nClusters, - const int32_t** GPUrestrict() ROFClusters, - const uint8_t** GPUrestrict() usedClusters, - const int32_t** GPUrestrict() clustersIndexTables, - int32_t** GPUrestrict() trackletsPerClusterLUTs, - int32_t** GPUrestrict() trackletsPerClusterSumLUTs, - int32_t** GPUrestrict() trackletsPerROF, - const std::array& trackletsPerClusterLUTsHost, - const std::array& trackletsPerClusterSumLUTsHost, - const int32_t iteration, - const float phiCut, - const int32_t maxTrackletsPerCluster, - const int32_t nBlocks, - const int32_t nThreads, - gpu::Streams& streams) -{ - const uint32_t sharedBytes = nThreads * (2 * deltaROF + 1) * sizeof(uint16_t); - gpu::computeLayerTrackletMutliROFKernel<<>>(clusters, - ROFClusters, - usedClusters, - clustersIndexTables, - phiCut, - nullptr, - trackletsPerClusterLUTs, - utils, - nRofs, - deltaROF, - rofPV, - iteration, - vertPerRofThreshold, - maxTrackletsPerCluster); - gpu::compileTrackletsPerROFKernel<<>>(nRofs, trackletsPerROF, ROFClusters, (const int32_t**)trackletsPerClusterLUTs); - gpu::cubExclusiveScan(trackletsPerClusterLUTsHost[0], trackletsPerClusterSumLUTsHost[0], nClusters, streams[0].get()); - - gpu::computeLayerTrackletMutliROFKernel<<>>(clusters, - ROFClusters, - usedClusters, - clustersIndexTables, - phiCut, - nullptr, - trackletsPerClusterLUTs, - utils, - nRofs, - deltaROF, - rofPV, - iteration, - vertPerRofThreshold, - maxTrackletsPerCluster); - gpu::compileTrackletsPerROFKernel<<>>(nRofs, trackletsPerROF, ROFClusters, (const int**)trackletsPerClusterLUTs); - gpu::cubExclusiveScan(trackletsPerClusterLUTsHost[1], trackletsPerClusterSumLUTsHost[1], nClusters, streams[1].get()); -} - -template -void computeTrackletsInROFsHandler(const IndexTableUtils* GPUrestrict() utils, - const uint8_t* GPUrestrict() multMask, - const int32_t nRofs, - const int32_t deltaROF, - const int32_t* GPUrestrict() rofPV, - const int vertPerRofThreshold, - const Cluster** GPUrestrict() clusters, - const uint32_t nClusters, - const int32_t** GPUrestrict() ROFClusters, - const uint8_t** GPUrestrict() usedClusters, - const int32_t** GPUrestrict() clustersIndexTables, - Tracklet** GPUrestrict() foundTracklets, - const int32_t** GPUrestrict() trackletsPerClusterLUTs, - const int32_t** GPUrestrict() trackletsPerClusterSumLUTs, - const int32_t** GPUrestrict() trackletsPerROF, - const int32_t iteration, - const float phiCut, - const int32_t maxTrackletsPerCluster, - const int32_t nBlocks, - const int32_t nThreads, - gpu::Streams& streams) -{ - const uint32_t sharedBytes = nThreads * (2 * deltaROF + 1) * sizeof(uint16_t); - gpu::computeLayerTrackletMutliROFKernel<<>>(clusters, - ROFClusters, - usedClusters, - clustersIndexTables, - phiCut, - foundTracklets, - trackletsPerClusterSumLUTs, - utils, - nRofs, - deltaROF, - rofPV, - iteration, - vertPerRofThreshold, - maxTrackletsPerCluster); - gpu::computeLayerTrackletMutliROFKernel<<>>(clusters, - ROFClusters, - usedClusters, - clustersIndexTables, - phiCut, - foundTracklets, - trackletsPerClusterSumLUTs, - utils, - nRofs, - deltaROF, - rofPV, - iteration, - vertPerRofThreshold, - maxTrackletsPerCluster); -} - -void countTrackletsMatchingInROFsHandler(const int32_t nRofs, - const int32_t deltaROF, - const uint32_t nClusters, - const int32_t** GPUrestrict() ROFClusters, - const Cluster** GPUrestrict() clusters, - uint8_t** GPUrestrict() usedClusters, - const Tracklet** GPUrestrict() foundTracklets, - uint8_t* GPUrestrict() usedTracklets, - const int32_t** GPUrestrict() trackletsPerClusterLUTs, - const int32_t** GPUrestrict() trackletsPerClusterSumLUTs, - int32_t* GPUrestrict() linesPerClusterLUT, - int32_t* GPUrestrict() linesPerClusterSumLUT, - const int32_t iteration, - const float phiCut, - const float tanLambdaCut, - const int32_t nBlocks, - const int32_t nThreads, - gpu::Streams& streams) -{ - streams[1].sync(); // need to make sure that all tracklets are done, since this placed in 0 tracklet01 will be done but tracklet12 needs to be guaranteed - gpu::computeTrackletSelectionMutliROFKernel<<>>(nullptr, - usedClusters, - ROFClusters, - phiCut, - tanLambdaCut, - foundTracklets, - usedTracklets, - trackletsPerClusterLUTs, - trackletsPerClusterSumLUTs, - linesPerClusterLUT, - nullptr, - nRofs, - deltaROF, - 100); - gpu::cubExclusiveScan(linesPerClusterLUT, linesPerClusterSumLUT, nClusters, streams[0].get()); -} - -void computeTrackletsMatchingInROFsHandler(const int32_t nRofs, - const int32_t deltaROF, - const uint32_t nClusters, - const int32_t** GPUrestrict() ROFClusters, - const Cluster** GPUrestrict() clusters, - const uint8_t** GPUrestrict() usedClusters, - const Tracklet** GPUrestrict() foundTracklets, - uint8_t* GPUrestrict() usedTracklets, - const int32_t** GPUrestrict() trackletsPerClusterLUTs, - const int32_t** GPUrestrict() trackletsPerClusterSumLUTs, - const int32_t* GPUrestrict() linesPerClusterSumLUT, - Line* GPUrestrict() lines, - const int32_t iteration, - const float phiCut, - const float tanLambdaCut, - const int32_t nBlocks, - const int32_t nThreads, - gpu::Streams& streams) -{ - gpu::computeTrackletSelectionMutliROFKernel<<>>(clusters, - nullptr, - ROFClusters, - phiCut, - tanLambdaCut, - foundTracklets, - usedTracklets, - trackletsPerClusterLUTs, - trackletsPerClusterSumLUTs, - linesPerClusterSumLUT, - lines, - nRofs, - deltaROF, - 100); -} - -/// Explicit instantiation of ITS2 handlers -template void countTrackletsInROFsHandler<7>(const IndexTableUtils<7>* GPUrestrict() utils, - const uint8_t* GPUrestrict() multMask, - const int32_t nRofs, - const int32_t deltaROF, - const int32_t* GPUrestrict() rofPV, - const int32_t vertPerRofThreshold, - const Cluster** GPUrestrict() clusters, - const uint32_t nClusters, - const int32_t** GPUrestrict() ROFClusters, - const uint8_t** GPUrestrict() usedClusters, - const int32_t** GPUrestrict() clustersIndexTables, - int32_t** trackletsPerClusterLUTs, - int32_t** trackletsPerClusterSumLUTs, - int32_t** trackletsPerROF, - const std::array& trackletsPerClusterLUTsHost, - const std::array& trackletsPerClusterSumLUTsHost, - const int32_t iteration, - const float phiCut, - const int32_t maxTrackletsPerCluster, - const int32_t nBlocks, - const int32_t nThreads, - gpu::Streams& streams); - -template void computeTrackletsInROFsHandler<7>(const IndexTableUtils<7>* GPUrestrict() utils, - const uint8_t* GPUrestrict() multMask, - const int32_t nRofs, - const int32_t deltaROF, - const int32_t* GPUrestrict() rofPV, - const int vertPerRofThreshold, - const Cluster** GPUrestrict() clusters, - const uint32_t nClusters, - const int32_t** GPUrestrict() ROFClusters, - const uint8_t** GPUrestrict() usedClusters, - const int32_t** GPUrestrict() clustersIndexTables, - Tracklet** GPUrestrict() foundTracklets, - const int32_t** GPUrestrict() trackletsPerClusterLUTs, - const int32_t** GPUrestrict() trackletsPerClusterSumLUTs, - const int32_t** GPUrestrict() trackletsPerROF, - const int32_t iteration, - const float phiCut, - const int32_t maxTrackletsPerCluster, - const int32_t nBlocks, - const int32_t nThreads, - gpu::Streams& streams); -/* -GPUg() void lineClustererMultipleRof( - const int* sizeClustersL1, // Number of clusters on layer 1 per ROF - Line* lines, // Lines - int* nFoundLines, // Number of found lines - int* nExclusiveFoundLines, // Number of found lines exclusive scan - int* clusteredLines, // Clustered lines - const unsigned int startRofId, // Starting ROF ID - const unsigned int rofSize, // Number of ROFs to consider // Number of found lines exclusive scan - const float pairCut) // Selection on line pairs -{ - for (unsigned int iRof{threadIdx.x}; iRof < rofSize; iRof += blockDim.x) { - auto rof = iRof + startRofId; - auto clustersL1offsetRof = sizeClustersL1[rof] - sizeClustersL1[startRofId]; // starting cluster offset for this ROF - auto nClustersL1Rof = sizeClustersL1[rof + 1] - sizeClustersL1[rof]; // number of clusters for this ROF - auto linesOffsetRof = nExclusiveFoundLines[clustersL1offsetRof]; // starting line offset for this ROF - // auto* foundLinesRof = nFoundLines + clustersL1offsetRof; - auto nLinesRof = nExclusiveFoundLines[clustersL1offsetRof + nClustersL1Rof] - linesOffsetRof; - // printf("rof: %d -> %d lines.\n", rof, nLinesRof); - for (int iLine1 = 0; iLine1 < nLinesRof; ++iLine1) { - auto absLine1Index = nExclusiveFoundLines[clustersL1offsetRof] + iLine1; - if (clusteredLines[absLine1Index] > -1) { - continue; - } - for (int iLine2 = iLine1 + 1; iLine2 < nLinesRof; ++iLine2) { - auto absLine2Index = nExclusiveFoundLines[clustersL1offsetRof] + iLine2; - if (clusteredLines[absLine2Index] > -1) { - continue; - } - - if (Line::getDCA(lines[absLine1Index], lines[absLine2Index]) < pairCut) { - ClusterLinesGPU tmpClus{lines[absLine1Index], lines[absLine2Index]}; - float tmpVertex[3]; - tmpVertex[0] = tmpClus.getVertex()[0]; - tmpVertex[1] = tmpClus.getVertex()[1]; - tmpVertex[2] = tmpClus.getVertex()[2]; - if (tmpVertex[0] * tmpVertex[0] + tmpVertex[1] * tmpVertex[1] > 4.f) { // outside the beampipe, skip it - break; - } - clusteredLines[absLine1Index] = iLine1; // We set local index of first line to contribute, so we can retrieve the cluster later - clusteredLines[absLine2Index] = iLine1; - for (int iLine3 = 0; iLine3 < nLinesRof; ++iLine3) { - auto absLine3Index = nExclusiveFoundLines[clustersL1offsetRof] + iLine3; - if (clusteredLines[absLine3Index] > -1) { - continue; - } - if (Line::getDistanceFromPoint(lines[absLine3Index], tmpVertex) < pairCut) { - clusteredLines[absLine3Index] = iLine1; - } - } - break; - } - } - } - } // rof loop -} - -GPUg() void computeCentroidsKernel( - Line* lines, - int* nFoundLines, - int* nExclusiveFoundLines, - const unsigned int nClustersMiddleLayer, - float* centroids, - const float lowHistX, - const float highHistX, - const float lowHistY, - const float highHistY, - const float pairCut) -{ - const int nLines = nExclusiveFoundLines[nClustersMiddleLayer - 1] + nFoundLines[nClustersMiddleLayer - 1]; - const int maxIterations{nLines * (nLines - 1) / 2}; - for (unsigned int currentThreadIndex = blockIdx.x * blockDim.x + threadIdx.x; currentThreadIndex < maxIterations; currentThreadIndex += blockDim.x * gridDim.x) { - int iFirstLine = currentThreadIndex / nLines; - int iSecondLine = currentThreadIndex % nLines; - // All unique pairs - if (iSecondLine <= iFirstLine) { - iFirstLine = nLines - iFirstLine - 2; - iSecondLine = nLines - iSecondLine - 1; - } - if (Line::getDCA(lines[iFirstLine], lines[iSecondLine]) < pairCut) { - ClusterLinesGPU cluster{lines[iFirstLine], lines[iSecondLine]}; - if (cluster.getVertex()[0] * cluster.getVertex()[0] + cluster.getVertex()[1] * cluster.getVertex()[1] < 1.98f * 1.98f) { - // printOnThread(0, "xCentr: %f, yCentr: %f \n", cluster.getVertex()[0], cluster.getVertex()[1]); - centroids[2 * currentThreadIndex] = cluster.getVertex()[0]; - centroids[2 * currentThreadIndex + 1] = cluster.getVertex()[1]; - } else { - // write values outside the histogram boundaries, - // default behaviour is not to have them added to histogram later - // (writing zeroes would be problematic) - centroids[2 * currentThreadIndex] = 2 * lowHistX; - centroids[2 * currentThreadIndex + 1] = 2 * lowHistY; - } - } else { - // write values outside the histogram boundaries, - // default behaviour is not to have them added to histogram later - // (writing zeroes would be problematic) - centroids[2 * currentThreadIndex] = 2 * highHistX; - centroids[2 * currentThreadIndex + 1] = 2 * highHistY; - } - } -} - -GPUg() void computeZCentroidsKernel( - const int nLines, - const cub::KeyValuePair* tmpVtX, - float* beamPosition, - Line* lines, - float* centroids, - const int* histX, // X - const float lowHistX, - const float binSizeHistX, - const int nBinsHistX, - const int* histY, // Y - const float lowHistY, - const float binSizeHistY, - const int nBinsHistY, - const float lowHistZ, // Z - const float pairCut, - const int binOpeningX, - const int binOpeningY) -{ - for (unsigned int currentThreadIndex = blockIdx.x * blockDim.x + threadIdx.x; currentThreadIndex < nLines; currentThreadIndex += blockDim.x * gridDim.x) { - if (tmpVtX[0].value || tmpVtX[1].value) { - float tmpX{lowHistX + tmpVtX[0].key * binSizeHistX + binSizeHistX / 2}; - int sumWX{tmpVtX[0].value}; - float wX{tmpX * tmpVtX[0].value}; - for (int iBin{o2::gpu::GPUCommonMath::Max(0, tmpVtX[0].key - binOpeningX)}; iBin < o2::gpu::GPUCommonMath::Min(tmpVtX[0].key + binOpeningX + 1, nBinsHistX - 1); ++iBin) { - if (iBin != tmpVtX[0].key) { - wX += (lowHistX + iBin * binSizeHistX + binSizeHistX / 2) * histX[iBin]; - sumWX += histX[iBin]; - } - } - float tmpY{lowHistY + tmpVtX[1].key * binSizeHistY + binSizeHistY / 2}; - int sumWY{tmpVtX[1].value}; - float wY{tmpY * tmpVtX[1].value}; - for (int iBin{o2::gpu::GPUCommonMath::Max(0, tmpVtX[1].key - binOpeningY)}; iBin < o2::gpu::GPUCommonMath::Min(tmpVtX[1].key + binOpeningY + 1, nBinsHistY - 1); ++iBin) { - if (iBin != tmpVtX[1].key) { - wY += (lowHistY + iBin * binSizeHistY + binSizeHistY / 2) * histY[iBin]; - sumWY += histY[iBin]; - } - } - beamPosition[0] = wX / sumWX; - beamPosition[1] = wY / sumWY; - float mockBeamPoint1[3] = {beamPosition[0], beamPosition[1], -1}; // get two points laying at different z, to create line object - float mockBeamPoint2[3] = {beamPosition[0], beamPosition[1], 1}; - Line pseudoBeam = {mockBeamPoint1, mockBeamPoint2}; - if (Line::getDCA(lines[currentThreadIndex], pseudoBeam) < pairCut) { - ClusterLinesGPU cluster{lines[currentThreadIndex], pseudoBeam}; - centroids[currentThreadIndex] = cluster.getVertex()[2]; - } else { - centroids[currentThreadIndex] = 2 * lowHistZ; - } - } - } -} - -GPUg() void computeVertexKernel( - cub::KeyValuePair* tmpVertexBins, - int* histZ, // Z - const float lowHistZ, - const float binSizeHistZ, - const int nBinsHistZ, - Vertex* vertices, - float* beamPosition, - const int vertIndex, - const int minContributors, - const int binOpeningZ) -{ - for (unsigned int currentThreadIndex = blockIdx.x * blockDim.x + threadIdx.x; currentThreadIndex < binOpeningZ; currentThreadIndex += blockDim.x * gridDim.x) { - if (currentThreadIndex == 0) { - if (tmpVertexBins[2].value > 1 && (tmpVertexBins[0].value || tmpVertexBins[1].value)) { - float z{lowHistZ + tmpVertexBins[2].key * binSizeHistZ + binSizeHistZ / 2}; - float ex{0.f}; - float ey{0.f}; - float ez{0.f}; - int sumWZ{tmpVertexBins[2].value}; - float wZ{z * tmpVertexBins[2].value}; - for (int iBin{o2::gpu::GPUCommonMath::Max(0, tmpVertexBins[2].key - binOpeningZ)}; iBin < o2::gpu::GPUCommonMath::Min(tmpVertexBins[2].key + binOpeningZ + 1, nBinsHistZ - 1); ++iBin) { - if (iBin != tmpVertexBins[2].key) { - wZ += (lowHistZ + iBin * binSizeHistZ + binSizeHistZ / 2) * histZ[iBin]; - sumWZ += histZ[iBin]; - } - histZ[iBin] = 0; - } - if (sumWZ > minContributors || vertIndex == 0) { - new (vertices + vertIndex) Vertex{o2::math_utils::Point3D(beamPosition[0], beamPosition[1], wZ / sumWZ), std::array{ex, 0, ey, 0, 0, ez}, static_cast(sumWZ), 0}; - } else { - new (vertices + vertIndex) Vertex{}; - } - } else { - new (vertices + vertIndex) Vertex{}; - } - } - } -} -*/ -} // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/hip/CMakeLists.txt b/Detectors/ITSMFT/ITS/tracking/GPU/hip/CMakeLists.txt index a40aac491a386..e28fe04c06772 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/hip/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/tracking/GPU/hip/CMakeLists.txt @@ -16,13 +16,9 @@ if(HIP_ENABLED) # add_compile_definitions(ITS_MEASURE_GPU_TIME) # add_compile_definitions(ITS_GPU_LOG) o2_add_hipified_library(ITStrackingHIP - SOURCES ../cuda/ClusterLinesGPU.cu - ../cuda/TimeFrameGPU.cu + SOURCES ../cuda/TimeFrameGPU.cu ../cuda/TrackerTraitsGPU.cxx - ../cuda/TracerGPU.cu ../cuda/TrackingKernels.cu - ../cuda/VertexingKernels.cu - ../cuda/VertexerTraitsGPU.cxx PUBLIC_INCLUDE_DIRECTORIES ../ PUBLIC_LINK_LIBRARIES O2::ITStracking O2::GPUTracking diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/BoundedAllocator.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/BoundedAllocator.h index 66634c1a07eea..3a03e9d145907 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/BoundedAllocator.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/BoundedAllocator.h @@ -22,13 +22,18 @@ #include #include -#include "ITStracking/ExternalAllocator.h" - +#if !defined(__HIPCC__) && !defined(__CUDACC__) +#include +#include #include "GPUCommonLogger.h" +#endif +#include "ITStracking/ExternalAllocator.h" +#include "ITStracking/Constants.h" namespace o2::its { +// #define BOUNDED_MR_STATS class BoundedMemoryResource final : public std::pmr::memory_resource { public: @@ -36,49 +41,71 @@ class BoundedMemoryResource final : public std::pmr::memory_resource { public: MemoryLimitExceeded(size_t attempted, size_t used, size_t max) - : mAttempted(attempted), mUsed(used), mMax(max) {} - const char* what() const noexcept final { - static thread_local char msg[256]; - if (mAttempted != 0) { - snprintf(msg, sizeof(msg), - "Reached set memory limit (attempted: %zu, used: %zu, max: %zu)", - mAttempted, mUsed, mMax); + char buf[256]; + if (attempted != 0) { + (void)snprintf(buf, sizeof(buf), "Reached set memory limit (attempted: %zu, used: %zu, max: %zu)", attempted, used, max); } else { - snprintf(msg, sizeof(msg), - "New set maximum below current used (newMax: %zu, used: %zu)", - mMax, mUsed); + (void)snprintf(buf, sizeof(buf), "New set maximum below current used (newMax: %zu, used: %zu)", max, used); } - return msg; + mMsg = buf; } + const char* what() const noexcept final { return mMsg.c_str(); } private: - size_t mAttempted{0}, mUsed{0}, mMax{0}; + std::string mMsg; }; - BoundedMemoryResource(size_t maxBytes = std::numeric_limits::max(), std::pmr::memory_resource* upstream = std::pmr::get_default_resource()) + BoundedMemoryResource(size_t maxBytes = std::numeric_limits::max(), + std::pmr::memory_resource* upstream = std::pmr::get_default_resource()) : mMaxMemory(maxBytes), mUpstream(upstream) {} - BoundedMemoryResource(ExternalAllocator* alloc) : mAdaptor(std::make_unique(alloc)), mUpstream(mAdaptor.get()) {} + + BoundedMemoryResource(ExternalAllocator* alloc, + size_t maxBytes = std::numeric_limits::max()) + : mMaxMemory(maxBytes), + mAdaptor(std::make_unique(alloc)), + mUpstream(mAdaptor.get()) {} void* do_allocate(size_t bytes, size_t alignment) final { - size_t new_used{0}, current_used{mUsedMemory.load(std::memory_order_relaxed)}; + size_t new_used{0}; + size_t current_used{mUsedMemory.load(std::memory_order_relaxed)}; do { new_used = current_used + bytes; - if (new_used > mMaxMemory) { - ++mCountThrow; - throw MemoryLimitExceeded(new_used, current_used, mMaxMemory); + if (new_used > mMaxMemory.load(std::memory_order_relaxed)) { + mCountThrow.fetch_add(1, std::memory_order_relaxed); + throw MemoryLimitExceeded(new_used, current_used, + mMaxMemory.load(std::memory_order_relaxed)); } } while (!mUsedMemory.compare_exchange_weak(current_used, new_used, std::memory_order_acq_rel, std::memory_order_relaxed)); + void* p{nullptr}; try { p = mUpstream->allocate(bytes, alignment); } catch (...) { mUsedMemory.fetch_sub(bytes, std::memory_order_relaxed); +#ifdef BOUNDED_MR_STATS + mStats.upstreamFailures.fetch_add(1, std::memory_order_relaxed); +#endif throw; } + +#ifdef BOUNDED_MR_STATS + size_t peak = mStats.peak.load(std::memory_order_relaxed); + while (new_used > peak && + !mStats.peak.compare_exchange_weak(peak, new_used, + std::memory_order_relaxed)) { + } + mStats.live.fetch_add(1, std::memory_order_relaxed); + mStats.nAlloc.fetch_add(1, std::memory_order_relaxed); + mStats.totalAlloc.fetch_add(bytes, std::memory_order_relaxed); + + size_t ma = mStats.maxAlign.load(std::memory_order_relaxed); + while (alignment > ma && !mStats.maxAlign.compare_exchange_weak(ma, alignment, std::memory_order_relaxed)) { + } +#endif return p; } @@ -86,6 +113,11 @@ class BoundedMemoryResource final : public std::pmr::memory_resource { mUpstream->deallocate(p, bytes, alignment); mUsedMemory.fetch_sub(bytes, std::memory_order_relaxed); +#ifdef BOUNDED_MR_STATS + mStats.live.fetch_sub(1, std::memory_order_relaxed); + mStats.nFree.fetch_add(1, std::memory_order_relaxed); + mStats.totalFreed.fetch_add(bytes, std::memory_order_relaxed); +#endif } bool do_is_equal(const std::pmr::memory_resource& other) const noexcept final @@ -93,35 +125,94 @@ class BoundedMemoryResource final : public std::pmr::memory_resource return this == &other; } - size_t getUsedMemory() const noexcept { return mUsedMemory.load(); } - size_t getMaxMemory() const noexcept { return mMaxMemory; } + [[nodiscard]] size_t getUsedMemory() const noexcept + { + return mUsedMemory.load(std::memory_order_relaxed); + } + [[nodiscard]] size_t getMaxMemory() const noexcept + { + return mMaxMemory.load(std::memory_order_relaxed); + } + [[nodiscard]] size_t getThrowCount() const noexcept + { + return mCountThrow.load(std::memory_order_relaxed); + } + void setMaxMemory(size_t max) { - size_t used = mUsedMemory.load(std::memory_order_acquire); - if (used > max) { - ++mCountThrow; - throw MemoryLimitExceeded(0, used, max); + size_t current = mMaxMemory.load(std::memory_order_relaxed); + if (max == current) { + return; + } + for (;;) { + size_t used = mUsedMemory.load(std::memory_order_acquire); + if (used > max) { + mCountThrow.fetch_add(1, std::memory_order_relaxed); + throw MemoryLimitExceeded(0, used, max); + } + if (mMaxMemory.compare_exchange_weak(current, max, + std::memory_order_release, + std::memory_order_relaxed)) { + return; + } + if (current == max) { + return; + } } - mMaxMemory.store(max, std::memory_order_release); } - void print() const +#if !defined(__HIPCC__) && !defined(__CUDACC__) + std::string asString() const { -#if !defined(GPUCA_GPUCODE_DEVICE) - constexpr double GB{1024 * 1024 * 1024}; - auto throw_ = mCountThrow.load(std::memory_order_relaxed); - auto used = static_cast(mUsedMemory.load(std::memory_order_relaxed)); - LOGP(info, "maxthrow={} maxmem={:.2f} GB used={:.2f} ({:.2f}%)", - throw_, (double)mMaxMemory / GB, used / GB, 100. * used / (double)mMaxMemory); + const auto throw_ = mCountThrow.load(std::memory_order_relaxed); + const auto used = static_cast(mUsedMemory.load(std::memory_order_relaxed)); + const auto maxm = mMaxMemory.load(std::memory_order_relaxed); + std::string ret; + if (maxm == std::numeric_limits::max()) { + ret += std::format("maxthrow={} maxmem=unbounded used={:.2f} GB", throw_, used / constants::GB); + } else { + ret += std::format("maxthrow={} maxmem={:.2f} GB used={:.2f} GB ({:.2f}%)", throw_, (double)maxm / constants::GB, used / constants::GB, 100.0 * used / (double)maxm); + } +#ifdef BOUNDED_MR_STATS + ret += std::format(" peak={:.2f} GB live={} nAlloc={} nFree={} totalAlloc={:.2f} GB totalFreed={:.2f} GB maxAlign={} upstreamFail={}", + (float)mStats.peak.load(std::memory_order_relaxed) / constants::GB, + mStats.live.load(std::memory_order_relaxed), + mStats.nAlloc.load(std::memory_order_relaxed), + mStats.nFree.load(std::memory_order_relaxed), + (float)mStats.totalAlloc.load(std::memory_order_relaxed) / constants::GB, + (float)mStats.totalFreed.load(std::memory_order_relaxed) / constants::GB, + mStats.maxAlign.load(std::memory_order_relaxed), + mStats.upstreamFailures.load(std::memory_order_relaxed)); #endif + return ret; } + void print() const + { + LOGP(info, "{}", asString()); + } +#endif + private: std::atomic mMaxMemory{std::numeric_limits::max()}; std::atomic mCountThrow{0}; std::atomic mUsedMemory{0}; std::unique_ptr mAdaptor{nullptr}; std::pmr::memory_resource* mUpstream{nullptr}; + +#ifdef BOUNDED_MR_STATS + struct Stats { + std::atomic peak{0}; + std::atomic live{0}; + std::atomic nAlloc{0}; + std::atomic nFree{0}; + std::atomic totalAlloc{0}; + std::atomic totalFreed{0}; + std::atomic maxAlign{0}; + std::atomic upstreamFailures{0}; + }; + Stats mStats{}; +#endif }; template diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h index 902092a510eb0..4706977d08ba6 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cell.h @@ -16,85 +16,160 @@ #ifndef TRACKINGITSU_INCLUDE_CACELL_H_ #define TRACKINGITSU_INCLUDE_CACELL_H_ +#include + #include "ITStracking/Constants.h" +#include "ITStracking/LayerMask.h" +#include "DataFormatsITS/TimeEstBC.h" +#include "ReconstructionDataFormats/Track.h" #include "GPUCommonDef.h" namespace o2::its { -class Cell final +struct CellNeighbour { + int cellTopology{-1}; + int cell{-1}; + int nextCellTopology{-1}; + int nextCell{-1}; + int level{-1}; +}; + +template +class SeedBase : public o2::track::TrackParCovF { public: - GPUhd() int getFirstClusterIndex() const { return mFirstClusterIndex; }; - GPUhd() int getSecondClusterIndex() const { return mSecondClusterIndex; }; - GPUhd() int getThirdClusterIndex() const { return mThirdClusterIndex; }; - GPUhd() int getFirstTrackletIndex() const { return mFirstTrackletIndex; }; - GPUhd() int getSecondTrackletIndex() const { return mSecondTrackletIndex; }; + GPUhd() LayerMask getHitLayerMask() const { return LayerMask{static_cast(getUserField())}; } + GPUhd() void setHitLayerMask(LayerMask mask) { setUserField(mask.value()); } + GPUhd() int getInnerLayer() const { return getHitLayerMask().first(); } + GPUhd() int getFirstTrackletIndex() const { return mTracklets[0]; }; + GPUhd() void setFirstTrackletIndex(int trkl) { mTracklets[0] = trkl; }; + GPUhd() int getSecondTrackletIndex() const { return mTracklets[1]; }; + GPUhd() void setSecondTrackletIndex(int trkl) { mTracklets[1] = trkl; }; + GPUhd() float getChi2() const { return mChi2; }; + GPUhd() void setChi2(float chi2) { mChi2 = chi2; }; GPUhd() int getLevel() const { return mLevel; }; - GPUhd() void setLevel(const int level) { mLevel = level; }; + GPUhd() void setLevel(int level) { mLevel = level; }; GPUhd() int* getLevelPtr() { return &mLevel; } + GPUhd() auto& getTimeStamp() noexcept { return mTime; } + GPUhd() const auto& getTimeStamp() const noexcept { return mTime; } + + protected: + GPUhdDefault() SeedBase() = default; + GPUhdDefault() SeedBase(const SeedBase&) = default; + GPUhdDefault() ~SeedBase() = default; + GPUhdDefault() SeedBase(SeedBase&&) = default; + GPUhdDefault() SeedBase& operator=(const SeedBase&) = default; + GPUhdDefault() SeedBase& operator=(SeedBase&&) = default; + GPUhd() SeedBase(const o2::track::TrackParCovF& tpc, float chi2, int level, const TimeEstBC& time) + : o2::track::TrackParCovF(tpc), mChi2(chi2), mLevel(level), mTime(time) + { + } + GPUhd() auto& clustersRaw() { return mClusters; } + GPUhd() const auto& clustersRaw() const { return mClusters; } private: - int mFirstClusterIndex{constants::UnusedIndex}; - int mSecondClusterIndex{constants::UnusedIndex}; - int mThirdClusterIndex{constants::UnusedIndex}; - int mFirstTrackletIndex{constants::UnusedIndex}; - int mSecondTrackletIndex{constants::UnusedIndex}; + float mChi2{constants::UnsetValue}; int mLevel{constants::UnusedIndex}; + std::array mTracklets = constants::helpers::initArray(); + std::array mClusters = constants::helpers::initArray(); + TimeEstBC mTime; }; -template -class CellSeed final : public o2::track::TrackParCovF +/// CellSeed: connections of three clusters +class CellSeed final : public SeedBase { + using Base = SeedBase; + public: GPUhdDefault() CellSeed() = default; - GPUhd() CellSeed(int innerL, int cl0, int cl1, int cl2, int trkl0, int trkl1, o2::track::TrackParCovF& tpc, float chi2) : o2::track::TrackParCovF(tpc), mChi2(chi2), mLevel(1) + GPUhd() CellSeed(int innerL, int cl0, int cl1, int cl2, int trkl0, int trkl1, const o2::track::TrackParCovF& tpc, float chi2, const TimeEstBC& time) + : CellSeed(LayerMask(innerL, innerL + 1, innerL + 2), cl0, cl1, cl2, trkl0, trkl1, tpc, chi2, time) { - mClusters.fill(constants::UnusedIndex); - setUserField(innerL); - mClusters[innerL + 0] = cl0; - mClusters[innerL + 1] = cl1; - mClusters[innerL + 2] = cl2; - mTracklets[0] = trkl0; - mTracklets[1] = trkl1; + } + GPUhd() CellSeed(LayerMask hitLayerMask, int cl0, int cl1, int cl2, int trkl0, int trkl1, const o2::track::TrackParCovF& tpc, float chi2, const TimeEstBC& time) + : Base(tpc, chi2, 1, time) + { + setHitLayerMask(hitLayerMask); + auto& clusters = this->clustersRaw(); + clusters[0] = cl0; + clusters[1] = cl1; + clusters[2] = cl2; + setFirstTrackletIndex(trkl0); + setSecondTrackletIndex(trkl1); } GPUhdDefault() CellSeed(const CellSeed&) = default; GPUhdDefault() ~CellSeed() = default; - // GPUhdDefault() CellSeed(CellSeed&&) = default; TODO cannot use this yet since TrackPar only has device + GPUhdDefault() CellSeed(CellSeed&&) = default; GPUhdDefault() CellSeed& operator=(const CellSeed&) = default; GPUhdDefault() CellSeed& operator=(CellSeed&&) = default; - GPUhd() int getFirstClusterIndex() const { return mClusters[getUserField()]; }; - GPUhd() int getSecondClusterIndex() const { return mClusters[getUserField() + 1]; }; - GPUhd() int getThirdClusterIndex() const { return mClusters[getUserField() + 2]; }; - GPUhd() int getFirstTrackletIndex() const { return mTracklets[0]; }; - GPUhd() void setFirstTrackletIndex(int trkl) { mTracklets[0] = trkl; }; - GPUhd() int getSecondTrackletIndex() const { return mTracklets[1]; }; - GPUhd() void setSecondTrackletIndex(int trkl) { mTracklets[1] = trkl; }; - GPUhd() float getChi2() const { return mChi2; }; - GPUhd() void setChi2(float chi2) { mChi2 = chi2; }; - GPUhd() int getLevel() const { return mLevel; }; - GPUhd() void setLevel(int level) { mLevel = level; }; - GPUhd() int* getLevelPtr() { return &mLevel; } - GPUhd() auto& getClusters() { return mClusters; } - GPUhd() int getCluster(int i) const { return mClusters[i]; } - GPUhd() void printCell() const + GPUhd() int getFirstClusterIndex() const { return this->clustersRaw()[0]; }; + GPUhd() int getSecondClusterIndex() const { return this->clustersRaw()[1]; }; + GPUhd() int getThirdClusterIndex() const { return this->clustersRaw()[2]; }; + GPUhd() auto& getClusters() { return this->clustersRaw(); } + GPUhd() const auto& getClusters() const { return this->clustersRaw(); } + /// getCluster takes an ABSOLUTE layer index. Compact cluster slots are + /// mapped to absolute layers by set-bit order in the hit-layer mask. + GPUhd() int getCluster(int layer) const { - printf("cell: %d, %d\t lvl: %d\t chi2: %f\tcls: [", mTracklets[0], mTracklets[1], mLevel, mChi2); - for (int i = 0; i < nLayers; ++i) { - printf("%d", mClusters[i]); - if (i < nLayers - 1) { - printf(" | "); + const int slot = getHitLayerMask().slot(layer); + return (slot >= 0 && slot < constants::ClustersPerCell) ? this->clustersRaw()[slot] : constants::UnusedIndex; + } +}; + +/// TrackSeed: full-width working representation used during road finding. +/// processNeighbours extends the cluster list inward, so we need NLayers +/// absolute-indexed slots here. +template +class TrackSeed final : public SeedBase +{ + using Base = SeedBase; + + public: + GPUhdDefault() TrackSeed() = default; + GPUhd() TrackSeed(const CellSeed& cs) + : Base(static_cast(cs), cs.getChi2(), cs.getLevel(), cs.getTimeStamp()) + { + this->setHitLayerMask(cs.getHitLayerMask()); + this->setFirstTrackletIndex(cs.getFirstTrackletIndex()); + this->setSecondTrackletIndex(cs.getSecondTrackletIndex()); + auto& clusters = this->clustersRaw(); + int slot = 0; + const auto hitMask = cs.getHitLayerMask(); + for (int layer = 0; layer < NLayers; ++layer) { + if (hitMask.has(layer)) { + clusters[layer] = cs.getClusters()[slot++]; } } - printf("]\n"); } + GPUhdDefault() TrackSeed(const TrackSeed&) = default; + GPUhdDefault() ~TrackSeed() = default; + GPUhdDefault() TrackSeed(TrackSeed&&) = default; + GPUhdDefault() TrackSeed& operator=(const TrackSeed&) = default; + GPUhdDefault() TrackSeed& operator=(TrackSeed&&) = default; + + GPUhd() int getFirstClusterIndex() const { return getClusterBySlot(0); } + GPUhd() int getSecondClusterIndex() const { return getClusterBySlot(1); } + GPUhd() int getThirdClusterIndex() const { return getClusterBySlot(2); } + GPUhd() auto& getClusters() { return this->clustersRaw(); } + GPUhd() const auto& getClusters() const { return this->clustersRaw(); } + GPUhd() int getCluster(int layer) const { return this->clustersRaw()[layer]; } private: - float mChi2 = -999.f; - int mLevel = constants::UnusedIndex; - std::array mTracklets = constants::helpers::initArray(); - std::array mClusters = constants::helpers::initArray(); + GPUhd() int getClusterBySlot(int requestedSlot) const + { + int slot = 0; + const auto hitMask = this->getHitLayerMask(); + for (int layer = 0; layer < NLayers; ++layer) { + if (hitMask.has(layer)) { + if (slot++ == requestedSlot) { + return this->clustersRaw()[layer]; + } + } + } + return constants::UnusedIndex; + } }; } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cluster.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cluster.h index b96f0558943a6..34014d858648b 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cluster.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cluster.h @@ -19,6 +19,7 @@ #include #include "ITStracking/Constants.h" #include "GPUCommonRtypes.h" +#include "GPUCommonDef.h" namespace o2::its { @@ -71,8 +72,8 @@ struct TrackingFrameInfo final { float zCoordinate{-999.f}; float xTrackingFrame{-999.f}; float alphaTrackingFrame{-999.f}; - std::array positionTrackingFrame = {constants::UnusedIndex, constants::UnusedIndex}; - std::array covarianceTrackingFrame = {999., 999., 999.}; + std::array positionTrackingFrame = {-999.f, -999.f}; + std::array covarianceTrackingFrame = {-999.f, -999.f, -999.f}; ClassDefNV(TrackingFrameInfo, 1); }; diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/ClusterLines.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/ClusterLines.h index 0e7ad474ae455..bcb8a98a62cab 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/ClusterLines.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/ClusterLines.h @@ -12,202 +12,81 @@ #ifndef O2_ITS_CLUSTERLINES_H #define O2_ITS_CLUSTERLINES_H -#include +#include #include +#include +#include +#include #include "ITStracking/Cluster.h" #include "ITStracking/Constants.h" #include "ITStracking/Tracklet.h" #include "GPUCommonRtypes.h" -#include "GPUCommonMath.h" namespace o2::its { + struct Line final { - GPUhdDefault() Line() = default; - GPUhd() Line(const Line&); - Line(std::array firstPoint, std::array secondPoint); - GPUhd() Line(const Tracklet&, const Cluster*, const Cluster*); +#if !defined(__HIPCC__) && !defined(__CUDACC__) // hide the class completely for gpu-cc + using SVector3f = ROOT::Math::SVector; + using SMatrix3f = ROOT::Math::SMatrix>; + Line() = default; + Line(const Tracklet&, const Cluster*, const Cluster*); + bool operator==(const Line&) const = default; + + static float getDistance2FromPoint(const Line& line, const std::array& point); static float getDistanceFromPoint(const Line& line, const std::array& point); - GPUhd() static float getDistanceFromPoint(const Line& line, const float point[3]); - static std::array getDCAComponents(const Line& line, const std::array point); - GPUhd() static void getDCAComponents(const Line& line, const float point[3], float destArray[6]); - GPUhd() static float getDCA(const Line&, const Line&, const float precision = constants::Tolerance); - static bool areParallel(const Line&, const Line&, const float precision = constants::Tolerance); - GPUhd() unsigned char isEmpty() const { return (originPoint[0] == 0.f && originPoint[1] == 0.f && originPoint[2] == 0.f) && - (cosinesDirector[0] == 0.f && cosinesDirector[1] == 0.f && cosinesDirector[2] == 0.f); } - GPUhdi() auto getDeltaROF() const { return rof[1] - rof[0]; } - GPUhd() void print() const; - bool operator==(const Line&) const; - bool operator!=(const Line&) const; - short getMinROF() const { return rof[0] < rof[1] ? rof[0] : rof[1]; } + static SMatrix3f getDCAComponents(const Line& line, const std::array& point); + static float getDCA2(const Line&, const Line&, const float precision = constants::Tolerance); + static float getDCA(const Line&, const Line&, const float precision = constants::Tolerance); + bool isEmpty() const noexcept; + void print() const; - float originPoint[3] = {0, 0, 0}; - float cosinesDirector[3] = {0, 0, 0}; - // float weightMatrix[6] = {1., 0., 0., 1., 0., 1.}; - // weightMatrix is a symmetric matrix internally stored as - // 0 --> row = 0, col = 0 - // 1 --> 0,1 - // 2 --> 0,2 - // 3 --> 1,1 - // 4 --> 1,2 - // 5 --> 2,2 - short rof[2] = {constants::UnusedIndex, constants::UnusedIndex}; + SVector3f originPoint; + SVector3f cosinesDirector; + TimeEstBC mTime; ClassDefNV(Line, 1); +#endif }; -GPUhdi() Line::Line(const Line& other) -{ - for (int i{0}; i < 3; ++i) { - originPoint[i] = other.originPoint[i]; - cosinesDirector[i] = other.cosinesDirector[i]; - } - // for (int i{0}; i < 6; ++i) { - // weightMatrix[i] = other.weightMatrix[i]; - // } - for (int i{0}; i < 2; ++i) { - rof[i] = other.rof[i]; - } -} - -GPUhdi() Line::Line(const Tracklet& tracklet, const Cluster* innerClusters, const Cluster* outerClusters) -{ - originPoint[0] = innerClusters[tracklet.firstClusterIndex].xCoordinate; - originPoint[1] = innerClusters[tracklet.firstClusterIndex].yCoordinate; - originPoint[2] = innerClusters[tracklet.firstClusterIndex].zCoordinate; - - cosinesDirector[0] = outerClusters[tracklet.secondClusterIndex].xCoordinate - innerClusters[tracklet.firstClusterIndex].xCoordinate; - cosinesDirector[1] = outerClusters[tracklet.secondClusterIndex].yCoordinate - innerClusters[tracklet.firstClusterIndex].yCoordinate; - cosinesDirector[2] = outerClusters[tracklet.secondClusterIndex].zCoordinate - innerClusters[tracklet.firstClusterIndex].zCoordinate; - - float inverseNorm{1.f / o2::gpu::CAMath::Hypot(cosinesDirector[0], cosinesDirector[1], cosinesDirector[2])}; - cosinesDirector[0] *= inverseNorm; - cosinesDirector[1] *= inverseNorm; - cosinesDirector[2] *= inverseNorm; - - rof[0] = tracklet.rof[0]; - rof[1] = tracklet.rof[1]; -} - -// static functions: -inline float Line::getDistanceFromPoint(const Line& line, const std::array& point) -{ - float DCASquared{0}; - float cdelta{0}; - for (int i{0}; i < 3; ++i) { - cdelta -= line.cosinesDirector[i] * (line.originPoint[i] - point[i]); - } - for (int i{0}; i < 3; ++i) { - DCASquared += (line.originPoint[i] - point[i] + line.cosinesDirector[i] * cdelta) * - (line.originPoint[i] - point[i] + line.cosinesDirector[i] * cdelta); - } - return o2::gpu::CAMath::Sqrt(DCASquared); -} - -GPUhdi() float Line::getDistanceFromPoint(const Line& line, const float point[3]) -{ - const float dx = point[0] - line.originPoint[0]; - const float dy = point[1] - line.originPoint[1]; - const float dz = point[2] - line.originPoint[2]; - const float d = (dx * line.cosinesDirector[0]) + (dy * line.cosinesDirector[1]) + (dz * line.cosinesDirector[2]); - - const float vx = dx - (d * line.cosinesDirector[0]); - const float vy = dy - (d * line.cosinesDirector[1]); - const float vz = dz - (d * line.cosinesDirector[2]); - - return o2::gpu::CAMath::Hypot(vx, vy, vz); -} - -GPUhdi() float Line::getDCA(const Line& firstLine, const Line& secondLine, const float precision) -{ - const float nx = (firstLine.cosinesDirector[1] * secondLine.cosinesDirector[2]) - - (firstLine.cosinesDirector[2] * secondLine.cosinesDirector[1]); - const float ny = -(firstLine.cosinesDirector[0] * secondLine.cosinesDirector[2]) + - (firstLine.cosinesDirector[2] * secondLine.cosinesDirector[0]); - const float nz = (firstLine.cosinesDirector[0] * secondLine.cosinesDirector[1]) - - (firstLine.cosinesDirector[1] * secondLine.cosinesDirector[0]); - const float norm2 = (nx * nx) + (ny * ny) + (nz * nz); - - if (norm2 <= precision * precision) { - return getDistanceFromPoint(firstLine, secondLine.originPoint); - } - - const float dx = secondLine.originPoint[0] - firstLine.originPoint[0]; - const float dy = secondLine.originPoint[1] - firstLine.originPoint[1]; - const float dz = secondLine.originPoint[2] - firstLine.originPoint[2]; - const float triple = (dx * nx) + (dy * ny) + (dz * nz); - - return o2::gpu::CAMath::Abs(triple) / o2::gpu::CAMath::Sqrt(norm2); -} - -GPUhdi() void Line::getDCAComponents(const Line& line, const float point[3], float destArray[6]) -{ - float cdelta{0.}; - for (int i{0}; i < 3; ++i) { - cdelta -= line.cosinesDirector[i] * (line.originPoint[i] - point[i]); - } - - destArray[0] = line.originPoint[0] - point[0] + line.cosinesDirector[0] * cdelta; - destArray[3] = line.originPoint[1] - point[1] + line.cosinesDirector[1] * cdelta; - destArray[5] = line.originPoint[2] - point[2] + line.cosinesDirector[2] * cdelta; - destArray[1] = o2::gpu::CAMath::Sqrt(destArray[0] * destArray[0] + destArray[3] * destArray[3]); - destArray[2] = o2::gpu::CAMath::Sqrt(destArray[0] * destArray[0] + destArray[5] * destArray[5]); - destArray[4] = o2::gpu::CAMath::Sqrt(destArray[3] * destArray[3] + destArray[5] * destArray[5]); -} - -inline bool Line::operator==(const Line& rhs) const -{ - bool val{false}; - for (int i{0}; i < 3; ++i) { - val &= this->originPoint[i] == rhs.originPoint[i]; - } - return val; -} - -inline bool Line::operator!=(const Line& rhs) const -{ - return !(*this == rhs); -} - -GPUhdi() void Line::print() const -{ - printf("Line: originPoint = (%f, %f, %f), cosinesDirector = (%f, %f, %f), rofs = (%hd, %hd)\n", - originPoint[0], originPoint[1], originPoint[2], cosinesDirector[0], cosinesDirector[1], cosinesDirector[2], rof[0], rof[1]); -} - class ClusterLines final { +#if !defined(__HIPCC__) && !defined(__CUDACC__) // hide the class completely for gpu-cc + using SMatrix3 = ROOT::Math::SMatrix>; + using SMatrix3f = ROOT::Math::SMatrix>; + using SVector3 = ROOT::Math::SVector; + public: ClusterLines() = default; - ClusterLines(const int firstLabel, const Line& firstLine, const int secondLabel, const Line& secondLine, - const bool weight = false); - ClusterLines(const Line& firstLine, const Line& secondLine); - void add(const int& lineLabel, const Line& line, const bool& weight = false); + ClusterLines(const int firstLabel, const Line& firstLine, const int secondLabel, const Line& secondLine); + ClusterLines(gsl::span lineLabels, gsl::span lines); + void add(const int lineLabel, const Line& line); void computeClusterCentroid(); - void updateROFPoll(const Line&); - inline std::vector& getLabels() - { - return mLabels; - } - inline int getSize() const { return mLabels.size(); } - inline short getROF() const { return mROF; } - inline std::array getVertex() const { return mVertex; } - inline std::array getRMS2() const { return mRMS2; } - inline float getAvgDistance2() const { return mAvgDistance2; } - - bool operator==(const ClusterLines&) const; + void accumulate(const Line& line); + bool isValid() const noexcept { return mIsValid; } + auto const& getVertex() const { return mVertex; } + const float* getRMS2() const { return mRMS2.Array(); } + float getAvgDistance2() const { return mAvgDistance2; } + auto getSize() const noexcept { return mLabels.size(); } + auto& getLabels() const noexcept { return mLabels; } + const auto& getTimeStamp() const noexcept { return mTime; } + bool operator==(const ClusterLines& rhs) const noexcept; + float getR2() const noexcept { return (mVertex[0] * mVertex[0]) + (mVertex[1] * mVertex[1]); } + float getR() const noexcept { return std::sqrt(getR2()); } protected: - std::array mAMatrix; // AX=B - std::array mBMatrix; // AX=B - std::vector mLabels; // labels - std::array mWeightMatrix = {0.f}; // weight matrix - std::array mVertex = {0.f}; // cluster centroid position - std::array mRMS2 = {0.f}; // symmetric matrix: diagonal is RMS2 - float mAvgDistance2 = 0.f; // substitute for chi2 - int mROFWeight = 0; // rof weight for voting - short mROF = constants::UnusedIndex; // rof + SMatrix3 mAMatrix; // AX=B, symmetric normal matrix + SVector3 mBMatrix; // AX=B, right-hand side + std::array mVertex = {}; // cluster centroid position + SMatrix3f mRMS2; // symmetric matrix: diagonal is RMS2 + float mAvgDistance2 = 0.f; // substitute for chi2 + bool mIsValid = false; // true if linear system was solved successfully + TimeEstBC mTime; // time stamp + std::vector mLabels; // contributing labels + + ClassDefNV(ClusterLines, 1); +#endif }; } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h index 10e1681c73e8d..275752854665b 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Configuration.h @@ -16,28 +16,48 @@ #ifndef TRACKINGITSU_INCLUDE_CONFIGURATION_H_ #define TRACKINGITSU_INCLUDE_CONFIGURATION_H_ +#include #ifndef GPUCA_GPUCODE_DEVICE -#include #include +#include #include -#include #endif +#include "CommonUtils/EnumFlags.h" #include "DetectorsBase/Propagator.h" #include "ITStracking/Constants.h" +#include "ITStracking/LayerMask.h" namespace o2::its { +// Steering of dedicated steps in an iteration +enum class IterationStep : uint8_t { + FirstPass = 0, + RebuildClusterLUT, + UseUPCMask, + SelectUPCVertices, + ResetVertices, + SkipROFsAboveThreshold, + MarkVerticesAsUPC, +}; +using IterationSteps = o2::utils::EnumFlags; + struct TrackingParameters { - int CellMinimumLevel() const noexcept { return MinTrackLength - constants::ClustersPerCell + 1; } + int CellMinimumLevel() const noexcept + { + const int minClusters = MinTrackLength - (MaxHoles > 0 ? MaxHoles : 0); + const int effectiveMinClusters = minClusters > constants::ClustersPerCell ? minClusters : constants::ClustersPerCell; + return effectiveMinClusters - constants::ClustersPerCell + 1; + } int NeighboursPerRoad() const noexcept { return NLayers - 3; } int CellsPerRoad() const noexcept { return NLayers - 2; } int TrackletsPerRoad() const noexcept { return NLayers - 1; } std::string asString() const; + IterationSteps PassFlags{IterationStep::FirstPass, IterationStep::RebuildClusterLUT}; int NLayers = 7; - int DeltaROF = 0; + std::vector AddTimeError = {0, 0, 0, 0, 0, 0, 0}; std::vector LayerZ = {16.333f + 1, 16.333f + 1, 16.333f + 1, 42.140f + 1, 42.140f + 1, 73.745f + 1, 73.745f + 1}; std::vector LayerRadii = {2.33959f, 3.14076f, 3.91924f, 19.6213f, 24.5597f, 34.388f, 39.3329f}; std::vector LayerxX0 = {5.e-3f, 5.e-3f, 5.e-3f, 1.e-2f, 1.e-2f, 1.e-2f, 1.e-2f}; @@ -46,81 +66,78 @@ struct TrackingParameters { std::vector SystErrorZ2 = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}; int ZBins{256}; int PhiBins{128}; - int nROFsPerIterations = -1; bool UseDiamond = false; float Diamond[3] = {0.f, 0.f, 0.f}; + float DiamondCov[6] = {25.e-6f, 0.f, 0.f, 25.e-6f, 0.f, 36.f}; /// General parameters - bool AllowSharingFirstCluster = false; - int ClusterSharing = 0; int MinTrackLength = 7; + int MaxHoles = 0; + LayerMask HoleLayerMask = 0; float NSigmaCut = 5; float PVres = 1.e-2f; /// Trackleting cuts float TrackletMinPt = 0.3f; - float TrackletsPerClusterLimit = 2.f; /// Cell finding cuts float CellDeltaTanLambdaSigma = 0.007f; - float CellsPerClusterLimit = 2.f; /// Fitter parameters o2::base::PropagatorImpl::MatCorrType CorrType = o2::base::PropagatorImpl::MatCorrType::USEMatCorrNONE; float MaxChi2ClusterAttachment = 60.f; float MaxChi2NDF = 30.f; int ReseedIfShorter = 6; // reseed for the final fit track with the length shorter than this std::vector MinPt = {0.f, 0.f, 0.f, 0.f}; - uint16_t StartLayerMask = 0x7F; + LayerMask StartLayerMask = 0x7F; bool RepeatRefitOut = false; // repeat outward refit using inward refit as a seed bool ShiftRefToCluster = true; // TrackFit: after update shift the linearization reference to cluster - bool FindShortTracks = false; bool PerPrimaryVertexProcessing = false; bool SaveTimeBenchmarks = false; bool DoUPCIteration = false; bool FataliseUponFailure = true; - /// Cluster attachment - bool UseTrackFollower = false; - bool UseTrackFollowerTop = false; - bool UseTrackFollowerBot = false; - bool UseTrackFollowerMix = false; - float TrackFollowerNSigmaCutZ = 1.f; - float TrackFollowerNSigmaCutPhi = 1.f; - - bool createArtefactLabels{false}; - + bool CreateArtefactLabels{false}; bool PrintMemory = false; // print allocator usage in epilog report size_t MaxMemory = std::numeric_limits::max(); bool DropTFUponFailure = false; + + // Selections on tracks sharing clusters + bool AllowSharingFirstCluster = false; + float SharedClusterMaxDeltaPhi = 0.05f; // For tracks sharing clusters, maximum allowed delta phi at the cluster position + float SharedClusterMaxDeltaEta = 0.03f; // For tracks sharing clusters, maximum allowed delta eta at the cluster position + bool SharedClusterOppositeSign = false; // For tracks sharing clusters, require opposite sign of the tracklets + int SharedMaxClusters = 0; // Maximal allowed shared clusters (excluding first cluster) }; struct VertexingParameters { std::string asString() const; - int nIterations = 1; // Number of vertexing passes to perform - int vertPerRofThreshold = 0; // Maximum number of vertices per ROF to trigger second a round - bool allowSingleContribClusters = false; + IterationSteps PassFlags{IterationStep::FirstPass, IterationStep::ResetVertices}; std::vector LayerZ = {16.333f + 1, 16.333f + 1, 16.333f + 1, 42.140f + 1, 42.140f + 1, 73.745f + 1, 73.745f + 1}; std::vector LayerRadii = {2.33959f, 3.14076f, 3.91924f, 19.6213f, 24.5597f, 34.388f, 39.3329f}; - int ZBins{1}; - int PhiBins{128}; - int deltaRof = 0; - float zCut = 0.002f; - float phiCut = 0.005f; - float pairCut = 0.04f; - float clusterCut = 0.8f; - float histPairCut = 0.04f; - float tanLambdaCut = 0.002f; // tanLambda = deltaZ/deltaR - float lowMultBeamDistCut = 0.1f; // XY cut for low-multiplicity pile up - int vertNsigmaCut = 6; // N sigma cut for vertex XY - float vertRadiusSigma = 0.33f; // sigma of vertex XY - float trackletSigma = 0.01f; // tracklet to vertex sigma - float maxZPositionAllowed = 25.f; - int clusterContributorsCut = 16; - int maxTrackletsPerCluster = 2e3; + int vertPerRofThreshold = 0; // Maximum number of vertices per ROF to trigger second a round + int ZBins = 1; + int PhiBins = 128; + float zCut = -1.f; + float phiCut = -1.f; + float pairCut = -1.f; + float clusterCut = -1.f; + float coarseZWindow = -1.f; + float seedDedupZCut = -1.f; + float refitDedupZCut = -1.f; + float duplicateZCut = -1.f; + float finalSelectionZCut = -1.f; + float duplicateDistance2Cut = -1.f; + float tanLambdaCut = -1.f; + float NSigmaCut = -1; + float maxZPositionAllowed = -1.f; + int clusterContributorsCut = -1; + int suppressLowMultDebris = -1; + int seedMemberRadiusTime = -1; + int seedMemberRadiusZ = -1; + int maxTrackletsPerCluster = -1; int phiSpan = -1; int zSpan = -1; bool SaveTimeBenchmarks = false; bool useTruthSeeding = false; // overwrite found vertices with MC events - bool outputContLabels = false; int nThreads = 1; bool PrintMemory = false; // print allocator usage in epilog report @@ -128,26 +145,6 @@ struct VertexingParameters { bool DropTFUponFailure = false; }; -struct TimeFrameGPUParameters { - std::string asString() const; - - size_t tmpCUBBufferSize = 1e5; // In average in pp events there are required 4096 bytes - size_t maxTrackletsPerCluster = 1e2; - size_t clustersPerLayerCapacity = 2.5e5; - size_t clustersPerROfCapacity = 1.5e3; - size_t validatedTrackletsCapacity = 1e3; - size_t cellsLUTsize = validatedTrackletsCapacity; - size_t maxNeighboursSize = 1e2; - size_t neighboursLUTsize = maxNeighboursSize; - size_t maxRoadPerRofSize = 1e3; // pp! - size_t maxLinesCapacity = 1e2; - size_t maxVerticesCapacity = 5e4; - size_t nMaxROFs = 1e3; - size_t nTimeFrameChunks = 3; - size_t nROFsPerChunk = 768; // pp defaults - int maxGPUMemoryGB = -1; -}; - namespace TrackingMode { enum Type : int8_t { diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Constants.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Constants.h index 22642f2e23229..34fa819b178eb 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Constants.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Constants.h @@ -19,9 +19,6 @@ #include #include -#include "ITStracking/Definitions.h" -#include "GPUCommonDefAPI.h" - namespace o2::its::constants { @@ -30,13 +27,14 @@ constexpr float MB = KB * KB; constexpr float GB = MB * KB; constexpr bool DoTimeBenchmarks = true; constexpr bool SaveTimeBenchmarks = false; - -GPUconstexpr() float Tolerance{1e-12}; // numerical tolerance -GPUconstexpr() int ClustersPerCell{3}; -GPUconstexpr() int UnusedIndex{-1}; -GPUconstexpr() float Resolution{0.0005f}; -GPUconstexpr() float Radl = 9.36f; // Radiation length of Si [cm] -GPUconstexpr() float Rho = 2.33f; // Density of Si [g/cm^3] +constexpr float Tolerance = 1e-12; // numerical tolerance +constexpr int ClustersPerCell = 3; // number of clusters for a cell +constexpr int UnusedIndex = -1; // global unused flag +constexpr float UnsetValue = -999.f; // global unset value +constexpr float Radl = 9.36f; // Radiation length of Si [cm] +constexpr float Rho = 2.33f; // Density of Si [g/cm^3] +constexpr int MaxIter = 4; // Max. supported iterations +constexpr int MaxSelectedTrackletsPerCluster = 100; // vertexer: max lines per cluster namespace helpers { diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Definitions.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Definitions.h index c3be0de2dade7..d79ea8c8bece8 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Definitions.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Definitions.h @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -16,16 +16,7 @@ #define TRACKINGITS_DEFINITIONS_H_ #include - -#include "ReconstructionDataFormats/Vertex.h" - -#ifdef CA_DEBUG -#define CA_DEBUGGER(x) x -#else -#define CA_DEBUGGER(x) \ - do { \ - } while (0) -#endif +#include namespace o2::its { @@ -35,11 +26,43 @@ enum class TrackletMode { Layer1Layer2 = 2 }; -using Vertex = o2::dataformats::Vertex>; - template using maybe_const = typename std::conditional::type; +// simple implemnetion of logging with exp. backoff +struct LogLogThrottler { + uint64_t evCount{0}; + uint64_t nextLog{1}; + int32_t iteration{-1}; + int32_t layer{-1}; + bool needToLog(int32_t iter, int32_t lay) + { + if (iteration != iter || layer != lay) { + iteration = iter; + layer = lay; + evCount = 0; + nextLog = 1; + } + if (++evCount > nextLog) { + nextLog *= 2; + return true; + } + return false; + } +}; + +struct TimingStats { + std::uint64_t calls = 0; + double totalTimeMs = 0.; + + void add(double timeMs) + { + ++calls; + totalTimeMs += timeMs; + } + double averageTimeMs() const { return calls ? totalTimeMs / static_cast(calls) : 0.; } +}; + } // namespace o2::its #endif diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/FastMultEst.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/FastMultEst.h new file mode 100644 index 0000000000000..f94c7c2034b46 --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/FastMultEst.h @@ -0,0 +1,91 @@ +// 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 FastMultEst.h +/// \brief Fast multiplicity estimator for ITS +/// \author ruben.shahoyan@cern.ch + +#ifndef ALICEO2_ITS_FASTMULTEST_ +#define ALICEO2_ITS_FASTMULTEST_ + +#include "ITSMFTReconstruction/ChipMappingITS.h" +#include "DataFormatsITS/Vertex.h" +#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsITSMFT/CompCluster.h" +#include "DataFormatsITSMFT/PhysTrigger.h" +#include "ITStracking/FastMultEstConfig.h" +#include "ITStracking/ROFLookupTables.h" +#include +#include + +namespace o2::its +{ + +struct FastMultEst { + + static constexpr int NLayers = o2::itsmft::ChipMappingITS::NLayers; + using ROFOverlapTableN = ROFOverlapTable; + using ROFMaskTableN = ROFMaskTable; + + float mult = 0.; /// estimated signal clusters multiplicity on the selected multiplicity layer + float noisePerChip = 0.; /// imposed noise per chip (when enabled by configuration) + float cov[3] = {0.}; /// retained for compatibility; set to zero in single-layer mode + float chi2 = 0.; /// retained for compatibility; set to zero in single-layer mode + int nLayersUsed = 0; /// number of layers used by estimator (0/1 in single-layer mode) + uint32_t lastRandomSeed = 0; /// state of the gRandom before + FastMultEst(); + + static uint32_t getCurrentRandomSeed(); + int selectROFs(const std::array, NLayers>& rofs, + const std::array, NLayers>& clus, + const gsl::span trig, + uint32_t firstTForbit, + bool doStaggering, + const ROFOverlapTableN::View& overlapView, + ROFMaskTableN& sel); + void selectROFsWithVertices(const auto& vertices, const ROFOverlapTableN::View& overlapView, ROFMaskTableN& sel) const + { + const auto& multEstConf = FastMultEstConfig::Instance(); + if (!multEstConf.isVtxMultCutRequested()) { + return; + } + + for (const auto& vertex : vertices) { + if (!multEstConf.isPassingVtxMultCut(vertex.getNContributors())) { + const auto& timestamp{vertex.getTimeStamp()}; + for (int layer = 0; layer < NLayers; ++layer) { + uint32_t startROF = sel.getLayer(layer).getROF(timestamp.lower()); + uint32_t endROF = sel.getLayer(layer).getROF(timestamp.upper()); + for (uint32_t rof = startROF; rof <= endROF; ++rof) { + sel.setROFsEnabled(layer, rof, 0); + } + } + } + } + } + + int countClustersOnLayer(const gsl::span& clusters) const; + float process(int nClusters) + { + return FastMultEstConfig::Instance().imposeNoisePerChip > 0 ? processNoiseImposed(nClusters) : processNoiseFree(nClusters); + } + float processNoiseFree(int nClusters); + float processNoiseImposed(int nClusters); + float process(const gsl::span& clusters) + { + return process(countClustersOnLayer(clusters)); + } + static bool sSeedSet; +}; + +} // namespace o2::its + +#endif diff --git a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/FastMultEstConfig.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/FastMultEstConfig.h similarity index 58% rename from Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/FastMultEstConfig.h rename to Detectors/ITSMFT/ITS/tracking/include/ITStracking/FastMultEstConfig.h index c6bce50995a4b..1ab9796aa8cf6 100644 --- a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/FastMultEstConfig.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/FastMultEstConfig.h @@ -20,9 +20,7 @@ #include "CommonUtils/ConfigurableParamHelper.h" #include "ITSMFTReconstruction/ChipMappingITS.h" -namespace o2 -{ -namespace its +namespace o2::its { struct FastMultEstConfig : public o2::conf::ConfigurableParamHelper { static constexpr int NLayers = o2::itsmft::ChipMappingITS::NLayers; @@ -34,16 +32,19 @@ struct FastMultEstConfig : public o2::conf::ConfigurableParamHelper0 : set as is, <0 : use current time - bool preferTriggered = true; /// prefer ROFs with highest number of physics triggers - - bool isMultCutRequested() const { return cutMultClusLow >= 0.f && cutMultClusHigh > 0.f; }; - bool isVtxMultCutRequested() const { return cutMultVtxLow >= 0.f && cutMultVtxHigh > 0.f; }; + int cutMultClusLayer = NLayers - 1; /// layer used for cluster multiplicity selection (by default the outermost one) + float cutMultClusLow = 0; /// reject ROF with estimated cluster mult. below this value (no cut if <0) + float cutMultClusHigh = -1; /// reject ROF with estimated cluster mult. above this value (no cut if <0) + float cutMultVtxLow = -1; /// reject seed vertex if its multiplicity below this value (no cut if <0) + float cutMultVtxHigh = -1; /// reject seed vertex if its multiplicity above this value (no cut if <0) + float cutRandomFraction = -1.; /// apply random cut rejecting requested fraction + int randomSeed = 0; /// 0 - do not seet seed, >0 : set as is, <0 : use current time + bool preferTriggered = true; /// prefer ROFs with highest number of physics triggers + + bool isMultCutRequested() const noexcept { return cutMultClusLow >= 0.f && cutMultClusHigh > 0.f; }; + bool isVtxMultCutRequested() const noexcept { return cutMultVtxLow >= 0.f && cutMultVtxHigh > 0.f; }; + bool isRandCutRequested() const noexcept { return cutRandomFraction >= 0.; } + bool isRequested() const noexcept { return isMultCutRequested() || isVtxMultCutRequested() || isRandCutRequested(); } bool isPassingRandomRejection() const; bool isPassingMultCut(float mult) const { return mult >= cutMultClusLow && (mult <= cutMultClusHigh || cutMultClusHigh <= 0.f); } bool isPassingVtxMultCut(int mult) const { return mult >= cutMultVtxLow && (mult <= cutMultVtxHigh || cutMultVtxHigh <= 0.f); } @@ -51,7 +52,6 @@ struct FastMultEstConfig : public o2::conf::ConfigurableParamHelper -#include "ITStracking/Constants.h" -#include "ITStracking/Configuration.h" -#include "ITStracking/Definitions.h" +#include "ITStracking/Cluster.h" +#include "ITStracking/MathUtils.h" #include "CommonConstants/MathConstants.h" #include "GPUCommonMath.h" #include "GPUCommonDef.h" @@ -91,7 +90,7 @@ GPUhdi() int IndexTableUtils::getPhiBinIndex(const float currentPhi) co template GPUhdi() int IndexTableUtils::getBinIndex(const int zIndex, const int phiIndex) const { - return o2::gpu::GPUCommonMath::Min(phiIndex * mNzBins + zIndex, mNzBins * mNphiBins - 1); + return o2::gpu::GPUCommonMath::Min(phiIndex * mNzBins + zIndex, (mNzBins * mNphiBins) - 1); } template @@ -113,5 +112,26 @@ GPUhdi() void IndexTableUtils::print() const } } +template +GPUhdi() int4 getBinsRect(const Cluster& currentCluster, const int layerIndex, + const float z1, const float z2, const float maxdeltaz, const float maxdeltaphi, + const IndexTableUtils& utils) +{ + const float zRangeMin = o2::gpu::GPUCommonMath::Min(z1, z2) - maxdeltaz; + const float phiRangeMin = (maxdeltaphi > o2::constants::math::PI) ? 0.f : currentCluster.phi - maxdeltaphi; + const float zRangeMax = o2::gpu::GPUCommonMath::Max(z1, z2) + maxdeltaz; + const float phiRangeMax = (maxdeltaphi > o2::constants::math::PI) ? o2::constants::math::TwoPI : currentCluster.phi + maxdeltaphi; + + if (zRangeMax < -utils.getLayerZ(layerIndex) || + zRangeMin > utils.getLayerZ(layerIndex) || zRangeMin > zRangeMax) { + return int4{-1, -1, -1, -1}; + } + + return int4{o2::gpu::GPUCommonMath::Max(0, utils.getZBinIndex(layerIndex, zRangeMin)), + utils.getPhiBinIndex(math_utils::getNormalizedPhi(phiRangeMin)), + o2::gpu::GPUCommonMath::Min(utils.getNzBins() - 1, utils.getZBinIndex(layerIndex, zRangeMax)), + utils.getPhiBinIndex(math_utils::getNormalizedPhi(phiRangeMax))}; +} + } // namespace o2::its #endif /* TRACKINGITSU_INCLUDE_INDEXTABLEUTILS_H_ */ diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/LayerMask.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/LayerMask.h new file mode 100644 index 0000000000000..9fe9894b3b457 --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/LayerMask.h @@ -0,0 +1,115 @@ +// Copyright 2019-2026 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. + +#ifndef TRACKINGITSU_INCLUDE_LAYERMASK_H_ +#define TRACKINGITSU_INCLUDE_LAYERMASK_H_ + +#include +#include + +#ifndef GPUCA_GPUCODE +#include +#include +#endif + +#include "GPUCommonDef.h" +#include "GPUCommonMath.h" +#include "ITStracking/Constants.h" + +namespace o2::its +{ + +struct LayerMask { + GPUhdDefault() constexpr LayerMask() noexcept = default; + GPUhdDefault() constexpr LayerMask(uint16_t mask) noexcept : mBits{mask} {} + GPUhdDefault() constexpr LayerMask(int layer0, int layer1, int layer2) noexcept + : mBits{static_cast((uint16_t(1) << layer0) | (uint16_t(1) << layer1) | (uint16_t(1) << layer2))} + { + } + GPUhdi() constexpr operator uint16_t() const noexcept { return mBits; } + GPUhdi() constexpr uint16_t value() const noexcept { return mBits; } + GPUhdi() constexpr void set(int layer) noexcept { mBits |= (uint16_t(1) << layer); } + + GPUhdi() LayerMask operator~() const noexcept { return LayerMask{static_cast(~mBits)}; } + GPUhdi() LayerMask operator&(LayerMask other) const noexcept { return LayerMask{static_cast(mBits & other.mBits)}; } + GPUhdi() LayerMask operator|(LayerMask other) const noexcept { return LayerMask{static_cast(mBits | other.mBits)}; } + GPUhdi() LayerMask& operator&=(LayerMask other) noexcept + { + mBits &= other.mBits; + return *this; + } + GPUhdi() LayerMask& operator|=(LayerMask other) noexcept + { + mBits |= other.mBits; + return *this; + } + + GPUhdi() bool empty() const noexcept { return mBits == 0; } + GPUhdi() bool has(int layer) const noexcept { return mBits & (uint16_t(1) << layer); } + GPUhdi() bool isSubsetOf(LayerMask allowed) const noexcept { return (*this & ~allowed).empty(); } + GPUhdi() bool isAllowedHoleMask(int maxHoles, LayerMask allowedHoleMask) const noexcept + { + const int allowedHoles = maxHoles > 0 ? maxHoles : 0; + return count() <= allowedHoles && isSubsetOf(allowedHoleMask); + } + GPUhdi() bool isAllowed(int maxHoles, LayerMask allowedHoleMask) const noexcept + { + return holeMask().isAllowedHoleMask(maxHoles, allowedHoleMask); + } + GPUhdi() int length() const noexcept { return empty() ? 0 : last() - first() + 1; } + GPUhdi() int count() const noexcept { return static_cast(o2::gpu::GPUCommonMath::Popcount(mBits)); } + GPUhdi() int first() const noexcept { return mBits ? static_cast(o2::gpu::GPUCommonMath::Ctz(mBits)) : constants::UnusedIndex; } + GPUhdi() int last() const noexcept { return mBits ? 31 - static_cast(o2::gpu::GPUCommonMath::Clz(mBits)) : constants::UnusedIndex; } + GPUhdi() LayerMask holeMask() const noexcept + { + return empty() ? LayerMask{0} : (span(first(), last()) & ~(*this)); + } + + GPUhdi() int slot(int layer) const noexcept + { + if (!has(layer)) { + return constants::UnusedIndex; + } + const uint32_t lowerLayers = (uint32_t(1) << layer) - 1; + return static_cast(o2::gpu::GPUCommonMath::Popcount(static_cast(mBits) & lowerLayers)); + } + + static GPUhdi() LayerMask span(int fromLayer, int toLayer) noexcept + { + if (fromLayer > toLayer) { + return 0; + } + const uint32_t upper = (uint32_t(1) << (toLayer + 1)) - 1; + const uint32_t lower = (uint32_t(1) << fromLayer) - 1; + return static_cast(upper & ~lower); + } + + static GPUhdi() LayerMask skipped(int fromLayer, int toLayer) noexcept + { + return (toLayer - fromLayer <= 1) ? LayerMask{0} : span(fromLayer + 1, toLayer - 1); + } + +#ifndef GPUCA_GPUCODE + std::string asString() const { return fmt::format("{:016b}", mBits); } +#endif + + private: + uint16_t mBits{0}; +}; + +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +static_assert(sizeof(LayerMask) == sizeof(uint16_t)); +static_assert(alignof(LayerMask) == alignof(uint16_t)); + +} // namespace o2::its + +#endif diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/LineVertexerHelpers.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/LineVertexerHelpers.h new file mode 100644 index 0000000000000..0e3807aba8efb --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/LineVertexerHelpers.h @@ -0,0 +1,46 @@ +// Copyright 2019-2026 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. + +#ifndef O2_ITS_TRACKING_LINE_VERTEXER_HELPERS_H_ +#define O2_ITS_TRACKING_LINE_VERTEXER_HELPERS_H_ + +#include +#include + +#include "ITStracking/BoundedAllocator.h" +#include "ITStracking/ClusterLines.h" + +namespace o2::its::line_vertexer +{ + +struct Settings { + float beamX = 0.f; + float beamY = 0.f; + float pairCut = 0.f; + float pairCut2 = 0.f; + float clusterCut = 0.f; + float coarseZWindow = 0.f; + float seedDedupZCut = 0.f; + float refitDedupZCut = 0.f; + float duplicateZCut = 0.f; + float duplicateDistance2Cut = 0.f; + float finalSelectionZCut = 0.f; + float maxZ = 0.f; + int seedMemberRadiusTime = 1; + int seedMemberRadiusZ = 2; + std::shared_ptr memoryPool; +}; + +bounded_vector buildClusters(std::span lines, const Settings& settings); + +} // namespace o2::its::line_vertexer + +#endif diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h index c5c1e4a8ce220..950d8c0a9117f 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/MathUtils.h @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -16,6 +16,8 @@ #ifndef O2_ITS_TRACKING_MATHUTILS_H_ #define O2_ITS_TRACKING_MATHUTILS_H_ +#include + #include "CommonConstants/MathConstants.h" #include "ITStracking/Constants.h" #include "MathUtils/Utils.h" @@ -44,19 +46,17 @@ GPUhdi() constexpr float getNormalizedPhi(float phi) GPUhdi() float computeCurvature(float x1, float y1, float x2, float y2, float x3, float y3) { // in case the triangle is degenerate we return infinite curvature. - const float d = (x2 - x1) * (y3 - y2) - (x3 - x2) * (y2 - y1); - if (o2::gpu::CAMath::Abs(d) < o2::its::constants::Tolerance) { - return 0.f; + const float area = ((x2 - x1) * (y3 - y1)) - ((x3 - x1) * (y2 - y1)); + if (o2::gpu::CAMath::Abs(area) < constants::Tolerance) { + return o2::constants::math::Almost0; } - const float a = - 0.5f * ((y3 - y2) * (y2 * y2 - y1 * y1 + x2 * x2 - x1 * x1) - (y2 - y1) * (y3 * y3 - y2 * y2 + x3 * x3 - x2 * x2)); - const float b = - 0.5f * ((x2 - x1) * (y3 * y3 - y2 * y2 + x3 * x3 - x2 * x2) - (x3 - x2) * (y2 * y2 - y1 * y1 + x2 * x2 - x1 * x1)); - const float den = o2::gpu::CAMath::Hypot(d * x1 - a, d * y1 - b); - if (den < o2::its::constants::Tolerance) { - return 0.f; - } - return -d / den; + const float dx1 = x2 - x1, dy1 = y2 - y1; + const float dx2 = x3 - x2, dy2 = y3 - y2; + const float dx3 = x1 - x3, dy3 = y1 - y3; + const float d1 = o2::gpu::CAMath::Sqrt((dx1 * dx1) + (dy1 * dy1)); + const float d2 = o2::gpu::CAMath::Sqrt((dx2 * dx2) + (dy2 * dy2)); + const float d3 = o2::gpu::CAMath::Sqrt((dx3 * dx3) + (dy3 * dy3)); + return -2.f * area / (d1 * d2 * d3); } GPUhdi() float computeCurvatureCentreX(float x1, float y1, float x2, float y2, float x3, float y3) @@ -78,7 +78,7 @@ GPUhdi() float computeCurvatureCentreX(float x1, float y1, float x2, float y2, f GPUhdi() float computeTanDipAngle(float x1, float y1, float x2, float y2, float z1, float z2) { - // in case the points vertically align we go to pos/neg inifinity. + // in case the points vertically align we go to pos/neg infinity. const float d = o2::gpu::CAMath::Hypot(x1 - x2, y1 - y2); if (o2::gpu::CAMath::Abs(d) < o2::its::constants::Tolerance) { return ((z1 > z2) ? -1.f : 1.f) * o2::constants::math::VeryBig; @@ -91,11 +91,32 @@ GPUhdi() float smallestAngleDifference(float a, float b) return o2::gpu::CAMath::Remainderf(b - a, o2::constants::math::TwoPI); } -GPUhdi() float Sq(float v) +GPUhdi() bool isPhiDifferenceBelow(const float phiA, const float phiB, const float phiCut) +{ + const float deltaPhi = o2::gpu::CAMath::Abs(phiA - phiB); + return deltaPhi < phiCut || deltaPhi > o2::constants::math::TwoPI - phiCut; +} + +GPUhdi() constexpr float Sq(float v) { return v * v; } +GPUhdi() constexpr float SqSum(float v, float w) +{ + return Sq(v) + Sq(w); +} + +GPUhdi() constexpr float SqSum(float u, float v, float w) +{ + return Sq(u) + SqSum(v, w); +} + +GPUhdi() constexpr float SqDiff(float x, float y) +{ + return Sq(x - y); +} + GPUhdi() float MSangle(float mass, float p, float xX0) { float beta = p / o2::gpu::CAMath::Hypot(mass, p); diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/ROFLookupTables.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/ROFLookupTables.h new file mode 100644 index 0000000000000..ce20169e36c64 --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/ROFLookupTables.h @@ -0,0 +1,850 @@ +// Copyright 2019-2026 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. + +#ifndef TRACKINGITSU_INCLUDE_ROFOVERLAPTABLE_H_ +#define TRACKINGITSU_INCLUDE_ROFOVERLAPTABLE_H_ + +#include +#include +#include +#include +#include +#include + +#ifndef GPUCA_GPUCODE +#include +#include "Framework/Logger.h" +#endif + +#include "CommonConstants/LHCConstants.h" +#include "CommonDataFormat/RangeReference.h" +#include "DataFormatsITS/TimeEstBC.h" +#include "DataFormatsITS/Vertex.h" +#include "GPUCommonMath.h" +#include "GPUCommonDef.h" + +namespace o2::its +{ + +// Layer timing definition +struct LayerTiming { + using BCType = TimeStampType; + BCType mNROFsTF{0}; // number of ROFs per timeframe + BCType mROFLength{0}; // ROF length in BC + BCType mROFDelay{0}; // delay of ROFs wrt start of first orbit in TF in BC + BCType mROFBias{0}; // bias wrt to the LHC clock in BC + BCType mROFAddTimeErr{0}; // additionally imposed uncertainty on ROF time in BC + + // return start of ROF in BC + // this does not account for the opt. error! + GPUhdi() BCType getROFStartInBC(BCType rofId) const noexcept + { + assert(rofId < mNROFsTF && rofId >= 0); + return (mROFLength * rofId) + mROFDelay + mROFBias; + } + + // return end of ROF in BCs + // this does not account for the opt. error! + GPUhdi() BCType getROFEndInBC(BCType rofId) const noexcept + { + assert(rofId < mNROFsTF); + return getROFStartInBC(rofId) + mROFLength; + } + + // return (clamped) time-interval of rof + GPUhdi() TimeEstBC getROFTimeBounds(BCType rofId, bool withError = false) const noexcept + { + if (withError) { + int64_t start = getROFStartInBC(rofId); + int64_t end = getROFEndInBC(rofId); + start = o2::gpu::CAMath::Max(start - mROFAddTimeErr, int64_t(0)); + end += mROFAddTimeErr; + return {static_cast(start), static_cast(end - start)}; + } + return {getROFStartInBC(rofId), static_cast(mROFLength)}; + } + + // return which ROF this BC belongs to + GPUhi() BCType getROF(BCType bc) const noexcept + { + const BCType offset = mROFDelay + mROFBias; + if (bc <= offset) { + return 0; + } + return (bc - offset) / mROFLength; + } + + // return which ROF this timestamp belongs by its lower edge + GPUhi() BCType getROF(TimeStamp ts) const noexcept + { + const BCType offset = mROFDelay + mROFBias; + const BCType bc = (ts.getTimeStamp() < ts.getTimeStampError()) ? BCType(0) : static_cast(o2::gpu::CAMath::Floor(ts.getTimeStamp() - ts.getTimeStampError())); + if (bc <= offset) { + return 0; + } + return (bc - offset) / mROFLength; + } + +#ifndef GPUCA_GPUCODE + GPUh() std::string asString() const + { + return std::format("NROFsPerTF {:4} ROFLength {:4} ({:4} per Orbit) ROFDelay {:4} ROFBias {:4} ROFAddTimeErr {:4}", mNROFsTF, mROFLength, (o2::constants::lhc::LHCMaxBunches / mROFLength), mROFDelay, mROFBias, mROFAddTimeErr); + } + + GPUh() void print() const + { + LOG(info) << asString(); + } +#endif +}; + +// Base class for lookup to define layers +template +class LayerTimingBase +{ + protected: + LayerTiming mLayers[NLayers]; + + public: + using T = LayerTiming::BCType; + LayerTimingBase() = default; + + GPUh() void defineLayer(int32_t layer, T nROFsTF, T rofLength, T rofDelay, T rofBias, T rofTE) + { + assert(layer >= 0 && layer < NLayers); + mLayers[layer] = {nROFsTF, rofLength, rofDelay, rofBias, rofTE}; + } + + GPUh() void defineLayer(int32_t layer, const LayerTiming& timing) + { + assert(layer >= 0 && layer < NLayers); + mLayers[layer] = timing; + } + + GPUhdi() const LayerTiming& getLayer(int32_t layer) const + { + assert(layer >= 0 && layer < NLayers); + return mLayers[layer]; + } + + GPUhdi() constexpr int32_t getEntries() noexcept { return NLayers; } + +#ifndef GPUCA_GPUCODE + GPUh() void print() const + { + LOGP(info, "Imposed time structure:"); + for (int32_t iL{0}; iL < NLayers; ++iL) { + LOGP(info, "\tLayer:{} {}", iL, mLayers[iL].asString()); + } + } +#endif +}; + +// GPU friendly view of the table below +template +struct ROFOverlapTableView { + const TableEntry* mFlatTable{nullptr}; + const TableIndex* mIndices{nullptr}; + const LayerTiming* mLayers{nullptr}; + + GPUhdi() const LayerTiming& getLayer(int32_t layer) const noexcept + { + assert(layer >= 0 && layer < NLayers); + return mLayers[layer]; + } + + GPUh() int32_t getClock() const noexcept + { + // we take the fastest layer as clock + int32_t fastest = 0; + uint32_t maxNROFs{0}; + for (int32_t iL{0}; iL < NLayers; ++iL) { + const auto& layer = getLayer(iL); + // by definition the fastest layer has the most ROFs + // this also solves the problem of a delay large than ROFLength + // if mNROFsTF is correct + if (layer.mNROFsTF > maxNROFs) { + fastest = iL; + maxNROFs = layer.mNROFsTF; + } + } + return fastest; + } + + GPUh() const LayerTiming& getClockLayer() const noexcept + { + return mLayers[getClock()]; + } + + GPUhdi() const TableEntry& getOverlap(int32_t from, int32_t to, size_t rofIdx) const noexcept + { + assert(from < NLayers && to < NLayers); + const size_t linearIdx = (from * NLayers) + to; + const auto& idx = mIndices[linearIdx]; + assert(rofIdx < idx.getEntries()); + return mFlatTable[idx.getFirstEntry() + rofIdx]; + } + + GPUhdi() bool doROFsOverlap(int32_t layer0, size_t rof0, int32_t layer1, size_t rof1) const noexcept + { + if (layer0 == layer1) { // layer is compatible with itself + return rof0 == rof1; + } + + assert(layer0 < NLayers && layer1 < NLayers); + const size_t linearIdx = (layer0 * NLayers) + layer1; + const auto& idx = mIndices[linearIdx]; + + if (rof0 >= idx.getEntries()) { + return false; + } + + const auto& overlap = mFlatTable[idx.getFirstEntry() + rof0]; + + if (overlap.getEntries() == 0) { + return false; + } + + const size_t firstCompatible = overlap.getFirstEntry(); + const size_t lastCompatible = firstCompatible + overlap.getEntries() - 1; + return rof1 >= firstCompatible && rof1 <= lastCompatible; + } + + GPUhdi() TimeEstBC getTimeStamp(int32_t layer0, size_t rof0, int32_t layer1, size_t rof1) const noexcept + { + assert(layer0 < NLayers && layer1 < NLayers); + assert(doROFsOverlap(layer0, rof0, layer1, rof1)); + // retrieves the combined timestamp + // e.g., taking one cluster from rof0 and one from rof1 + // and constructing a tracklet (doublet) what is its time + // this assumes that the rofs overlap, e.g. doROFsOverlap -> true + // get timestamp including margins from rof0 and rof1 + const auto t0 = mLayers[layer0].getROFTimeBounds(rof0, true); + const auto t1 = mLayers[layer1].getROFTimeBounds(rof1, true); + return t0 + t1; + } + +#ifndef GPUCA_GPUCODE + /// Print functions + GPUh() void printAll() const + { + for (int32_t i = 0; i < NLayers; ++i) { + for (int32_t j = 0; j < NLayers; ++j) { + if (i != j) { + printMapping(i, j); + } + } + } + printSummary(); + } + + GPUh() void printMapping(int32_t from, int32_t to) const + { + if (from == to) { + LOGP(error, "No self-lookup supported"); + return; + } + + constexpr int w_index = 10; + constexpr int w_first = 12; + constexpr int w_last = 12; + constexpr int w_count = 10; + + LOGF(info, "Overlap mapping: Layer %d -> Layer %d", from, to); + LOGP(info, "From: {}", mLayers[from].asString()); + LOGP(info, "To : {}", mLayers[to].asString()); + LOGF(info, "%*s | %*s | %*s | %*s", w_index, "ROF.index", w_first, "First.ROF", w_last, "Last.ROF", w_count, "Count"); + LOGF(info, "%.*s-+-%.*s-+-%.*s-+-%.*s", w_index, "----------", w_first, "------------", w_last, "------------", w_count, "----------"); + + const size_t linearIdx = (from * NLayers) + to; + const auto& idx = mIndices[linearIdx]; + for (int32_t i = 0; i < idx.getEntries(); ++i) { + const auto& overlap = getOverlap(from, to, i); + LOGF(info, "%*d | %*d | %*d | %*d", w_index, i, w_first, overlap.getFirstEntry(), w_last, overlap.getEntriesBound() - 1, w_count, overlap.getEntries()); + } + } + + GPUh() void printSummary() const + { + uint32_t totalEntries{0}; + size_t flatTableSize{0}; + + for (int32_t i = 0; i < NLayers; ++i) { + for (int32_t j = 0; j < NLayers; ++j) { + if (i != j) { + const size_t linearIdx = (i * NLayers) + j; + const auto& idx = mIndices[linearIdx]; + totalEntries += idx.getEntries(); + flatTableSize += idx.getEntries(); + } + } + } + + for (int32_t i = 0; i < NLayers; ++i) { + mLayers[i].print(); + } + + const uint32_t totalBytes = (flatTableSize * sizeof(TableEntry)) + (static_cast(NLayers * NLayers) * sizeof(TableIndex)); + LOGF(info, "------------------------------------------------------------"); + LOGF(info, "Total overlap table size: %u entries", totalEntries); + LOGF(info, "Flat table size: %zu entries", flatTableSize); + LOGF(info, "Total view size: %u bytes", totalBytes); + LOGF(info, "------------------------------------------------------------"); + } +#endif +}; + +// Precalculated lookup table to find overlapping ROFs in another layer given a ROF index in the current layer +template +class ROFOverlapTable : public LayerTimingBase +{ + public: + using T = LayerTimingBase::T; + using TableEntry = dataformats::RangeReference; + using TableIndex = dataformats::RangeReference; + + using View = ROFOverlapTableView; + ROFOverlapTable() = default; + + GPUh() void init() + { + std::vector table[NLayers][NLayers]; + for (int32_t i{0}; i < NLayers; ++i) { + for (int32_t j{0}; j < NLayers; ++j) { + if (i != j) { // we do not need self-lookup + buildMapping(i, j, table[i][j]); + } + } + } + flatten(table); + } + + GPUh() View getView() const + { + View view; + view.mFlatTable = mFlatTable.data(); + view.mIndices = mIndices; + view.mLayers = this->mLayers; + return view; + } + + GPUh() View getDeviceView(const TableEntry* deviceFlatTablePtr, const TableIndex* deviceIndicesPtr, const LayerTiming* deviceLayerTimingPtr) const + { + View view; + view.mFlatTable = deviceFlatTablePtr; + view.mIndices = deviceIndicesPtr; + view.mLayers = deviceLayerTimingPtr; + return view; + } + + GPUh() size_t getFlatTableSize() const noexcept { return mFlatTable.size(); } + static GPUh() constexpr size_t getIndicesSize() { return static_cast(NLayers * NLayers); } + + private: + GPUh() void buildMapping(int32_t from, int32_t to, std::vector& table) + { + const auto& layerFrom = this->mLayers[from]; + const auto& layerTo = this->mLayers[to]; + table.resize(layerFrom.mNROFsTF); + + for (int32_t iROF{0}; iROF < layerFrom.mNROFsTF; ++iROF) { + int64_t fromStart = o2::gpu::CAMath::Max((int64_t)layerFrom.getROFStartInBC(iROF) - (int64_t)layerFrom.mROFAddTimeErr, int64_t(0)); + int64_t fromEnd = (int64_t)layerFrom.getROFEndInBC(iROF) + layerFrom.mROFAddTimeErr; + + int32_t firstROFTo = o2::gpu::CAMath::Max(0, (int32_t)((fromStart - (int64_t)layerTo.mROFAddTimeErr - (int64_t)layerTo.mROFDelay - (int64_t)layerTo.mROFBias) / (int64_t)layerTo.mROFLength)); + auto lastROFTo = (int32_t)((fromEnd + (int64_t)layerTo.mROFAddTimeErr - (int64_t)layerTo.mROFDelay - (int64_t)layerTo.mROFBias - 1) / (int64_t)layerTo.mROFLength); + firstROFTo = o2::gpu::CAMath::Max(0, firstROFTo); + lastROFTo = o2::gpu::CAMath::Min((int32_t)layerTo.mNROFsTF - 1, lastROFTo); + + while (firstROFTo <= lastROFTo) { + int64_t toStart = o2::gpu::CAMath::Max((int64_t)layerTo.getROFStartInBC(firstROFTo) - (int64_t)layerTo.mROFAddTimeErr, int64_t(0)); + int64_t toEnd = (int64_t)layerTo.getROFEndInBC(firstROFTo) + layerTo.mROFAddTimeErr; + if (toEnd > fromStart && toStart < fromEnd) { + break; + } + ++firstROFTo; + } + while (lastROFTo >= firstROFTo) { + int64_t toStart = o2::gpu::CAMath::Max((int64_t)layerTo.getROFStartInBC(lastROFTo) - (int64_t)layerTo.mROFAddTimeErr, int64_t(0)); + int64_t toEnd = (int64_t)layerTo.getROFEndInBC(lastROFTo) + layerTo.mROFAddTimeErr; + if (toEnd > fromStart && toStart < fromEnd) { + break; + } + --lastROFTo; + } + int32_t count = (firstROFTo <= lastROFTo) ? (lastROFTo - firstROFTo + 1) : 0; + table[iROF] = {static_cast(firstROFTo), static_cast(count)}; + } + } + + GPUh() void flatten(const std::vector table[NLayers][NLayers]) + { + size_t total{0}; + for (int32_t i{0}; i < NLayers; ++i) { + for (int32_t j{0}; j < NLayers; ++j) { + if (i != j) { // we do not need self-lookup + total += table[i][j].size(); + } + } + } + + mFlatTable.reserve(total); + + for (int32_t i{0}; i < NLayers; ++i) { + for (int32_t j{0}; j < NLayers; ++j) { + size_t idx = (i * NLayers) + j; + if (i != j) { + mIndices[idx].setFirstEntry(static_cast(mFlatTable.size())); + mIndices[idx].setEntries(static_cast(table[i][j].size())); + mFlatTable.insert(mFlatTable.end(), table[i][j].begin(), table[i][j].end()); + } else { + mIndices[idx] = {0, 0}; + } + } + } + } + + TableIndex mIndices[NLayers * NLayers]; + std::vector mFlatTable; +}; + +// GPU friendly view of the table below +template +struct ROFVertexLookupTableView { + const TableEntry* mFlatTable{nullptr}; + const TableIndex* mIndices{nullptr}; + const LayerTiming* mLayers{nullptr}; + + GPUhdi() const LayerTiming& getLayer(int32_t layer) const noexcept + { + assert(layer >= 0 && layer < NLayers); + return mLayers[layer]; + } + + GPUhdi() const TableEntry& getVertices(int32_t layer, size_t rofIdx) const noexcept + { + assert(layer < NLayers); + const auto& idx = mIndices[layer]; + assert(rofIdx < idx.getEntries()); + return mFlatTable[idx.getFirstEntry() + rofIdx]; + } + + GPUh() int32_t getMaxVerticesPerROF() const noexcept + { + int32_t maxCount = 0; + for (int32_t layer = 0; layer < NLayers; ++layer) { + const auto& idx = mIndices[layer]; + for (int32_t i = 0; i < idx.getEntries(); ++i) { + const auto& entry = mFlatTable[idx.getFirstEntry() + i]; + maxCount = o2::gpu::CAMath::Max(maxCount, static_cast(entry.getEntries())); + } + } + return maxCount; + } + + // Check if a specific vertex is compatible with a given ROF + GPUhdi() bool isVertexCompatible(int32_t layer, size_t rofIdx, const Vertex& vertex) const noexcept + { + assert(layer < NLayers); + const auto& layerDef = mLayers[layer]; + int64_t rofLower = o2::gpu::CAMath::Max((int64_t)layerDef.getROFStartInBC(rofIdx) - (int64_t)layerDef.mROFAddTimeErr, int64_t(0)); + int64_t rofUpper = (int64_t)layerDef.getROFEndInBC(rofIdx) + layerDef.mROFAddTimeErr; + auto vLower = (int64_t)vertex.getTimeStamp().lower(); + auto vUpper = (int64_t)vertex.getTimeStamp().upper(); + return vUpper >= rofLower && vLower < rofUpper; + } + +#ifndef GPUCA_GPUCODE + GPUh() void printAll() const + { + for (int32_t i = 0; i < NLayers; ++i) { + printLayer(i); + } + printSummary(); + } + + GPUh() void printLayer(int32_t layer) const + { + constexpr int w_rof = 10; + constexpr int w_first = 12; + constexpr int w_last = 12; + constexpr int w_count = 10; + + LOGF(info, "Vertex lookup: Layer %d", layer); + LOGF(info, "%*s | %*s | %*s | %*s", w_rof, "ROF.index", w_first, "First.Vtx", w_last, "Last.Vtx", w_count, "Count"); + LOGF(info, "%.*s-+-%.*s-+-%.*s-+-%.*s", w_rof, "----------", w_first, "------------", w_last, "------------", w_count, "----------"); + + const auto& idx = mIndices[layer]; + for (int32_t i = 0; i < idx.getEntries(); ++i) { + const auto& entry = mFlatTable[idx.getFirstEntry() + i]; + int first = entry.getFirstEntry(); + int count = entry.getEntries(); + int last = first + count - 1; + LOGF(info, "%*d | %*d | %*d | %*d", w_rof, i, w_first, first, w_last, last, w_count, count); + } + } + + GPUh() void printSummary() const + { + uint32_t totalROFs{0}; + uint32_t totalVertexRefs{0}; + + for (int32_t i = 0; i < NLayers; ++i) { + const auto& idx = mIndices[i]; + totalROFs += idx.getEntries(); + + for (int32_t j = 0; j < idx.getEntries(); ++j) { + const auto& entry = mFlatTable[idx.getFirstEntry() + j]; + totalVertexRefs += entry.getEntries(); + } + } + + const uint32_t totalBytes = (totalROFs * sizeof(TableEntry)) + (NLayers * sizeof(TableIndex)); + LOGF(info, "------------------------------------------------------------"); + LOGF(info, "Total ROFs in table: %u", totalROFs); + LOGF(info, "Total vertex references: %u", totalVertexRefs); + LOGF(info, "Total view size: %u bytes", totalBytes); + LOGF(info, "------------------------------------------------------------"); + } +#endif +}; + +// Precalculated lookup table to find vertices compatible with ROFs +// Given a layer and ROF index, returns the range of vertices that overlap in time. +// The vertex time is defined as symmetrical [t0-e,t0+e] +// It needs to be guaranteed that the input vertices are sorted by their lower-bound! +// additionally compatibliyty has to be queried per vertex! +template +class ROFVertexLookupTable : public LayerTimingBase +{ + public: + using T = LayerTimingBase::T; + using BCType = LayerTiming::BCType; + using TableEntry = dataformats::RangeReference; + using TableIndex = dataformats::RangeReference; + using View = ROFVertexLookupTableView; + + ROFVertexLookupTable() = default; + + GPUh() size_t getFlatTableSize() const noexcept { return mFlatTable.size(); } + static GPUh() constexpr size_t getIndicesSize() { return NLayers; } + + // Build the lookup table given a sorted array of vertices + // vertices must be sorted by timestamp, then by error (secondary) + GPUh() void init(const Vertex* vertices, size_t nVertices) + { + if (nVertices > std::numeric_limits::max()) { + LOGF(fatal, "too many vertices %zu, max supported is %u", nVertices, std::numeric_limits::max()); + } + + std::vector table[NLayers]; + for (int32_t layer{0}; layer < NLayers; ++layer) { + buildMapping(layer, vertices, nVertices, table[layer]); + } + flatten(table); + } + + // Pre-allocated needed memory, then use update(...) + GPUh() void init() + { + size_t total{0}; + for (int32_t layer{0}; layer < NLayers; ++layer) { + total += this->mLayers[layer].mNROFsTF; + } + mFlatTable.resize(total, {0, 0}); + size_t offset = 0; + for (int32_t layer{0}; layer < NLayers; ++layer) { + size_t nROFs = this->mLayers[layer].mNROFsTF; + mIndices[layer].setFirstEntry(static_cast(offset)); + mIndices[layer].setEntries(static_cast(nROFs)); + offset += nROFs; + } + } + + // Recalculate lookup table with new vertices + GPUh() void update(const Vertex* vertices, size_t nVertices) + { + size_t offset = 0; + for (int32_t layer{0}; layer < NLayers; ++layer) { + const auto& idx = mIndices[layer]; + size_t nROFs = idx.getEntries(); + for (size_t iROF = 0; iROF < nROFs; ++iROF) { + updateROFMapping(layer, iROF, vertices, nVertices, offset + iROF); + } + offset += nROFs; + } + } + + GPUh() View getView() const + { + View view; + view.mFlatTable = mFlatTable.data(); + view.mIndices = mIndices; + view.mLayers = this->mLayers; + return view; + } + + GPUh() View getDeviceView(const TableEntry* deviceFlatTablePtr, const TableIndex* deviceIndicesPtr, const LayerTiming* deviceLayerTimingPtr) const + { + View view; + view.mFlatTable = deviceFlatTablePtr; + view.mIndices = deviceIndicesPtr; + view.mLayers = deviceLayerTimingPtr; + return view; + } + + private: + // Build the mapping for one layer + GPUh() void buildMapping(int32_t layer, const Vertex* vertices, size_t nVertices, std::vector& table) + { + const auto& layerDef = this->mLayers[layer]; + table.resize(layerDef.mNROFsTF); + size_t vertexSearchStart = 0; + for (int32_t iROF{0}; iROF < layerDef.mNROFsTF; ++iROF) { + int64_t rofLower = o2::gpu::CAMath::Max((int64_t)layerDef.getROFStartInBC(iROF) - (int64_t)layerDef.mROFAddTimeErr, int64_t(0)); + int64_t rofUpper = (int64_t)layerDef.getROFEndInBC(iROF) + layerDef.mROFAddTimeErr; + size_t lastVertex = binarySearchFirst(vertices, nVertices, vertexSearchStart, rofUpper); + size_t firstVertex = vertexSearchStart; + while (firstVertex < lastVertex) { + auto vUpper = (int64_t)vertices[firstVertex].getTimeStamp().upper(); + if (vUpper > rofLower) { + break; + } + ++firstVertex; + } + size_t count = (lastVertex > firstVertex) ? (lastVertex - firstVertex) : 0; + table[iROF] = {static_cast(firstVertex), static_cast(count)}; + vertexSearchStart = firstVertex; + } + } + + // Update a single ROF's vertex mapping + GPUh() void updateROFMapping(int32_t layer, size_t iROF, const Vertex* vertices, size_t nVertices, size_t flatTableIdx) + { + const auto& layerDef = this->mLayers[layer]; + int64_t rofLower = o2::gpu::CAMath::Max((int64_t)layerDef.getROFStartInBC(iROF) - (int64_t)layerDef.mROFAddTimeErr, int64_t(0)); + int64_t rofUpper = (int64_t)layerDef.getROFEndInBC(iROF) + layerDef.mROFAddTimeErr; + size_t lastVertex = binarySearchFirst(vertices, nVertices, 0, rofUpper); + size_t firstVertex = 0; + while (firstVertex < lastVertex) { + int64_t vUpper = (int64_t)vertices[firstVertex].getTimeStamp().getTimeStamp() + + (int64_t)vertices[firstVertex].getTimeStamp().getTimeStampError(); + if (vUpper > rofLower) { + break; + } + ++firstVertex; + } + size_t count = (lastVertex > firstVertex) ? (lastVertex - firstVertex) : 0; + mFlatTable[flatTableIdx].setFirstEntry(static_cast(firstVertex)); + mFlatTable[flatTableIdx].setEntries(static_cast(count)); + } + + // Binary search for first vertex where maxBC >= targetBC + GPUh() size_t binarySearchFirst(const Vertex* vertices, size_t nVertices, size_t searchStart, BCType targetBC) const + { + size_t left = searchStart; + size_t right = nVertices; + while (left < right) { + size_t mid = left + ((right - left) / 2); + int64_t lower = (int64_t)vertices[mid].getTimeStamp().getTimeStamp() - + (int64_t)vertices[mid].getTimeStamp().getTimeStampError(); + if (lower < targetBC) { + left = mid + 1; + } else { + right = mid; + } + } + return left; + } + + // Compress the temporary table into a single flat table + GPUh() void flatten(const std::vector table[NLayers]) + { + // Count total entries + size_t total{0}; + for (int32_t i{0}; i < NLayers; ++i) { + total += table[i].size(); + } + + mFlatTable.reserve(total); + + // Build flat table and indices + for (int32_t i{0}; i < NLayers; ++i) { + mIndices[i].setFirstEntry(static_cast(mFlatTable.size())); + mIndices[i].setEntries(static_cast(table[i].size())); + mFlatTable.insert(mFlatTable.end(), table[i].begin(), table[i].end()); + } + } + + TableIndex mIndices[NLayers]; + std::vector mFlatTable; +}; + +// GPU-friendly view of the ROF mask table +template +struct ROFMaskTableView { + const TableEntry* mFlatMask{nullptr}; + const TableIndex* mLayerROFOffsets{nullptr}; // size NLayers+1 + + GPUhdi() bool isROFEnabled(int32_t layer, int32_t rofId) const noexcept + { + assert(layer >= 0 && layer < NLayers); + return mFlatMask[mLayerROFOffsets[layer] + rofId] != 0u; + } + +#ifndef GPUCA_GPUCODE + GPUh() void printAll() const + { + for (int32_t i = 0; i < NLayers; ++i) { + printLayer(i); + } + } + + GPUh() void printLayer(int32_t layer) const + { + constexpr int w_rof = 10; + constexpr int w_active = 10; + int32_t nROFs = mLayerROFOffsets[layer + 1] - mLayerROFOffsets[layer]; + LOGF(info, "Mask table: Layer %d", layer); + LOGF(info, "%*s | %*s", w_rof, "ROF", w_active, "Enabled"); + LOGF(info, "%.*s-+-%.*s", w_rof, "----------", w_active, "----------"); + for (int32_t i = 0; i < nROFs; ++i) { + LOGF(info, "%*d | %*d", w_rof, i, w_active, (int)isROFEnabled(layer, i)); + } + } + + GPUh() std::string asString(int32_t layer) const + { + int32_t nROFs = mLayerROFOffsets[layer + 1] - mLayerROFOffsets[layer]; + int32_t enabledROFs = 0; + for (int32_t j = 0; j < nROFs; ++j) { + if (isROFEnabled(layer, j)) { + ++enabledROFs; + } + } + return std::format("ROFMask on Layer {} ROFs enabled: {}/{}", layer, enabledROFs, nROFs); + } + + GPUh() void print(int32_t layer) const + { + LOG(info) << asString(layer); + } +#endif +}; + +// Per-ROF per-layer boolean mask (uint8_t for GPU compatibility). +template +class ROFMaskTable : public LayerTimingBase +{ + public: + using T = LayerTimingBase::T; + using BCRange = dataformats::RangeReference; + using TableIndex = uint32_t; + using TableEntry = uint8_t; + using View = ROFMaskTableView; + + ROFMaskTable() = default; + GPUh() explicit ROFMaskTable(const LayerTimingBase& timingBase) : LayerTimingBase(timingBase) { init(); } + + GPUh() void init() + { + int32_t totalROFs = 0; + for (int32_t layer{0}; layer < NLayers; ++layer) { + mLayerROFOffsets[layer] = totalROFs; + totalROFs += this->getLayer(layer).mNROFsTF; + } + mLayerROFOffsets[NLayers] = totalROFs; // sentinel + mFlatMask.resize(totalROFs, 0u); + } + + GPUh() size_t getFlatMaskSize() const noexcept { return mFlatMask.size(); } + + GPUh() void setROFEnabled(int32_t layer, int32_t rofId, uint8_t state = 1) noexcept + { + assert(layer >= 0 && layer < NLayers); + assert(rofId >= 0 && rofId < mLayerROFOffsets[layer + 1] - mLayerROFOffsets[layer]); + mFlatMask[mLayerROFOffsets[layer] + rofId] = state; + } + + GPUh() void setROFsEnabled(int32_t layer, int32_t firstRof, int32_t nRofs, uint8_t state = 1) noexcept + { + assert(layer >= 0 && layer < NLayers); + assert(firstRof >= 0); + assert(firstRof + nRofs <= mLayerROFOffsets[layer + 1] - mLayerROFOffsets[layer]); + std::memset(mFlatMask.data() + mLayerROFOffsets[layer] + firstRof, state, nRofs); + } + + // Enable all ROFs in all layers that are time-compatible with the given BC range + GPUh() void selectROF(const BCRange& t) + { + const int32_t bcStart = t.getFirstEntry(); + const int32_t bcEnd = t.getEntriesBound(); + for (int32_t layer{0}; layer < NLayers; ++layer) { + const auto& lay = this->getLayer(layer); + const int32_t offset = mLayerROFOffsets[layer]; + for (int32_t rofId{0}; rofId < lay.mNROFsTF; ++rofId) { + if (static_cast(lay.getROFStartInBC(rofId)) < bcEnd && + static_cast(lay.getROFEndInBC(rofId)) > bcStart) { + mFlatMask[offset + rofId] = 1u; + } + } + } + } + + // Reset mask to 0, then enable all ROFs compatible with any of the given BC ranges + GPUh() void selectROFs(const std::vector& ts) + { + resetMask(); + for (const auto& t : ts) { + selectROF(t); + } + } + + GPUh() void resetMask(uint8_t s = 0u) + { + std::memset(mFlatMask.data(), s, mFlatMask.size()); + } + + GPUh() void invertMask() + { + std::ranges::transform(mFlatMask, mFlatMask.begin(), [](uint8_t x) { return 1 - x; }); + } + + GPUh() void swap(ROFMaskTable& other) noexcept + { + std::swap(mFlatMask, other.mFlatMask); + std::swap(mLayerROFOffsets, other.mLayerROFOffsets); + } + + GPUh() View getView() const + { + View view; + view.mFlatMask = mFlatMask.data(); + view.mLayerROFOffsets = mLayerROFOffsets; + return view; + } + + GPUh() View getDeviceView(const TableEntry* deviceFlatMaskPtr, const TableIndex* deviceOffsetPtr) const + { + View view; + view.mFlatMask = deviceFlatMaskPtr; + view.mLayerROFOffsets = deviceOffsetPtr; + return view; + } + + private: + TableIndex mLayerROFOffsets[NLayers + 1] = {0}; + std::vector mFlatMask; +}; + +} // namespace o2::its + +#endif diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Road.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Road.h deleted file mode 100644 index 009f3a1b5b146..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Road.h +++ /dev/null @@ -1,72 +0,0 @@ -// 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 Road.h -/// \brief -/// - -#ifndef TRACKINGCA_INCLUDE_ROAD_H -#define TRACKINGCA_INCLUDE_ROAD_H - -#include - -#include "ITStracking/Constants.h" -#include "GPUCommonDef.h" - -namespace o2::its -{ - -template -class Road final -{ - public: - GPUhdDefault() Road() = default; - GPUhd() Road(int cellLayer, int cellId) : Road() { addCell(cellLayer, cellId); } - - GPUhdDefault() Road(const Road&) = default; - GPUhdDefault() Road(Road&&) noexcept = default; - GPUhdDefault() ~Road() = default; - - GPUhdDefault() Road& operator=(const Road&) = default; - GPUhdDefault() Road& operator=(Road&&) noexcept = default; - - GPUhdi() uint8_t getRoadSize() const { return mRoadSize; } - GPUhdi() bool isFakeRoad() const { return mIsFakeRoad; } - GPUhdi() void setFakeRoad(const bool fake) { mIsFakeRoad = fake; } - GPUhdi() int& operator[](const int& i) { return mCellIds[i]; } - GPUhdi() int operator[](const int& i) const { return mCellIds[i]; } - - GPUhd() void resetRoad() - { - for (int i = 0; i < maxRoadSize; i++) { - mCellIds[i] = constants::UnusedIndex; - } - mRoadSize = 0; - } - - GPUhd() void addCell(int cellLayer, int cellId) - { - if (mCellIds[cellLayer] == constants::UnusedIndex) { - ++mRoadSize; - } - - mCellIds[cellLayer] = cellId; - } - - private: - std::array mCellIds = constants::helpers::initArray(); - unsigned char mRoadSize{0}; - bool mIsFakeRoad{false}; -}; - -} // namespace o2::its - -#endif diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Smoother.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Smoother.h deleted file mode 100644 index 101f4b8d72601..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Smoother.h +++ /dev/null @@ -1,60 +0,0 @@ -// 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 Smoother.h -/// \brief Class to handle Kalman smoothing for ITS tracking. -/// Its instance stores the state of the track to the level we want to smooth to avoid multiple re-propagations when testing different clusters. -/// - -#include "ReconstructionDataFormats/Track.h" -#include "DataFormatsITS/TrackITS.h" -#include "DetectorsBase/Propagator.h" - -namespace o2 -{ -namespace its -{ - -template -class Smoother -{ - public: - // Smoother(TrackITSExt& track, size_t layer, const ROframe& event, float bZ, o2::base::PropagatorF::MatCorrType corr); - ~Smoother(); - - bool isValidInit() const - { - return mInitStatus; - } - // bool testCluster(const int clusterId, const ROframe& event); - bool getSmoothedTrack(); - float getChi2() const { return mBestChi2; } - float getLastChi2() const { return mLastChi2; } - - private: - float computeSmoothedPredictedChi2(const o2::track::TrackParCov& outwTrack, - const o2::track::TrackParCov& inwTrack, - const std::array& cls, - const std::array& clCov); - bool smoothTrack(); - - private: - size_t mLayerToSmooth; // Layer to compute smoothing optimization - float mBz; // Magnetic field along Z - bool mInitStatus; // State after the initialization - o2::base::PropagatorF::MatCorrType mCorr; // Type of correction to use - TrackITSExt mInwardsTrack; // outwards track: from innermost cluster to outermost - TrackITSExt mOutwardsTrack; // inwards track: from outermost cluster to innermost - float mBestChi2; // Best value of local smoothed chi2 - float mLastChi2 = 1e8; // Latest computed chi2 -}; -} // namespace its -} // namespace o2 diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h index acc884ea68b8b..3fef2dc640cbc 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h @@ -21,22 +21,21 @@ #include #include "DataFormatsITS/TrackITS.h" +#include "DataFormatsITS/Vertex.h" #include "ITStracking/Cell.h" #include "ITStracking/Cluster.h" #include "ITStracking/Configuration.h" -#include "ITStracking/Constants.h" #include "ITStracking/ClusterLines.h" -#include "ITStracking/Definitions.h" -#include "ITStracking/Road.h" #include "ITStracking/Tracklet.h" #include "ITStracking/IndexTableUtils.h" #include "ITStracking/ExternalAllocator.h" #include "ITStracking/BoundedAllocator.h" +#include "ITStracking/ROFLookupTables.h" +#include "ITStracking/TrackingTopology.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" -#include "ReconstructionDataFormats/Vertex.h" #include "DetectorsBase/Propagator.h" namespace o2 @@ -62,66 +61,63 @@ template class TimeFrameGPU; } -template +template struct TimeFrame { - using IndexTableUtilsN = IndexTableUtils; - using CellSeedN = CellSeed; - friend class gpu::TimeFrameGPU; + using IndexTableUtilsN = IndexTableUtils; + using ROFOverlapTableN = ROFOverlapTable; + using ROFVertexLookupTableN = ROFVertexLookupTable; + using ROFMaskTableN = ROFMaskTable; + using TrackingTopologyN = TrackingTopology; + using TrackSeedN = TrackSeed; + friend class gpu::TimeFrameGPU; TimeFrame() = default; virtual ~TimeFrame() = default; const Vertex& getPrimaryVertex(const int ivtx) const { return mPrimaryVertices[ivtx]; } - gsl::span getPrimaryVertices(int rofId) const; - gsl::span getPrimaryVertices(int romin, int romax) const; - gsl::span> getPrimaryVerticesMCRecInfo(const int rofId) const; - gsl::span getPrimaryVerticesContributors(const int rofId) const; - gsl::span> getPrimaryVerticesXAlpha(int rofId) const; - void fillPrimaryVerticesXandAlpha(); - int getPrimaryVerticesNum(int rofId = -1) const; - void addPrimaryVerticesLabels(bounded_vector>& labels); - void addPrimaryVerticesContributorLabels(bounded_vector& labels); - void addPrimaryVertices(const bounded_vector& vertices, const int iteration); - void addPrimaryVerticesInROF(const bounded_vector& vertices, const int rofId, const int iteration); - void addPrimaryVerticesLabelsInROF(const bounded_vector>& labels, const int rofId); - void addPrimaryVerticesContributorLabelsInROF(const bounded_vector& labels, const int rofId); - void removePrimaryVerticesInROf(const int rofId); - int loadROFrameData(const o2::itsmft::ROFRecord& rof, gsl::span clusters, - const dataformats::MCTruthContainer* mcLabels = nullptr); - - int loadROFrameData(gsl::span rofs, - gsl::span clusters, - gsl::span::iterator& pattIt, - const itsmft::TopologyDictionary* dict, - const dataformats::MCTruthContainer* mcLabels = nullptr); - void resetROFrameData(size_t nROFs); - void prepareROFrameData(gsl::span rofs, - gsl::span clusters); + auto& getPrimaryVertices() { return mPrimaryVertices; }; + auto getPrimaryVerticesNum() { return mPrimaryVertices.size(); }; + const auto& getPrimaryVertices() const { return mPrimaryVertices; }; + auto& getPrimaryVerticesLabels() { return mPrimaryVerticesLabels; }; + gsl::span getPrimaryVertices(int layer, int rofId) const; + void addPrimaryVertex(const Vertex& vertex); + void addPrimaryVertexLabel(const VertexLabel& label) { mPrimaryVerticesLabels.push_back(label); } + + // read-in data + void loadROFrameData(gsl::span rofs, + gsl::span clusters, + gsl::span::iterator& pattIt, + const itsmft::TopologyDictionary* dict, + int layer, + const dataformats::MCTruthContainer* mcLabels = nullptr); + void resetROFrameData(int iLayer); + void prepareROFrameData(gsl::span clusters, int layer); int getTotalClusters() const; - auto& getTotVertIteration() { return mTotVertPerIteration; } bool empty() const { return getTotalClusters() == 0; } int getSortedIndex(int rofId, int layer, int idx) const { return mROFramesClusters[layer][rofId] + idx; } int getSortedStartIndex(const int rofId, const int layer) const { return mROFramesClusters[layer][rofId]; } - int getNrof() const { return mNrof; } + int getNrof(int layer) const { return mROFramesClusters[layer].size() - 1; } void resetBeamXY(const float x, const float y, const float w = 0); void setBeamPosition(const float x, const float y, const float s2, const float base = 50.f, const float systematic = 0.f) { isBeamPositionOverridden = true; - resetBeamXY(x, y, s2 / o2::gpu::CAMath::Sqrt(base * base + systematic)); + resetBeamXY(x, y, s2 / o2::gpu::CAMath::Sqrt((base * base) + systematic)); } float getBeamX() const { return mBeamPos[0]; } float getBeamY() const { return mBeamPos[1]; } + std::array& getBeamXY() { return mBeamPos; } + auto& getMinRs() { return mMinR; } auto& getMaxRs() { return mMaxR; } float getMinR(int layer) const { return mMinR[layer]; } float getMaxR(int layer) const { return mMaxR[layer]; } - float getMSangle(int layer) const { return mMSangles[layer]; } - auto& getMSangles() { return mMSangles; } - float getPhiCut(int layer) const { return mPhiCuts[layer]; } - auto& getPhiCuts() { return mPhiCuts; } + float getTransitionPhiCut(int transitionId) const { return mTransitionPhiCuts[transitionId]; } + float getTransitionMSAngle(int transitionId) const { return mTransitionMSAngles[transitionId]; } + auto& getTransitionPhiCuts() { return mTransitionPhiCuts; } + auto& getTransitionMSAngles() { return mTransitionMSAngles; } float getPositionResolution(int layer) const { return mPositionResolution[layer]; } auto& getPositionResolutions() { return mPositionResolution; } @@ -134,29 +130,61 @@ struct TimeFrame { gsl::span getROFramesClustersPerROFrange(int rofMin, int range, int layerId) const; gsl::span getROFrameClusters(int layerId) const; gsl::span getNClustersROFrange(int rofMin, int range, int layerId) const; - gsl::span getIndexTablePerROFrange(int rofMin, int range, int layerId) const; gsl::span getIndexTable(int rofId, int layerId); - auto& getIndexTableWhole(int layerId) { return mIndexTables[layerId]; } const auto& getTrackingFrameInfoOnLayer(int layerId) const { return mTrackingFrameInfo[layerId]; } + // navigation tables + const auto& getIndexTableUtils() const { return mIndexTableUtils; } + const auto& getROFOverlapTable() const { return mROFOverlapTable; } + const auto& getROFOverlapTableView() const { return mROFOverlapTableView; } + const auto& getTrackerTopologies() const { return mTrackerTopologies; } + const auto& getTrackingTopologyView() const { return mTrackingTopologyView; } + void setROFOverlapTable(ROFOverlapTableN table) + { + mROFOverlapTable = std::move(table); + mROFOverlapTableView = mROFOverlapTable.getView(); + } + const auto& getROFVertexLookupTable() const { return mROFVertexLookupTable; } + const auto& getROFVertexLookupTableView() const { return mROFVertexLookupTableView; } + void setROFVertexLookupTable(ROFVertexLookupTableN table) + { + mROFVertexLookupTable = std::move(table); + mROFVertexLookupTableView = mROFVertexLookupTable.getView(); + } + void updateROFVertexLookupTable() { mROFVertexLookupTable.update(mPrimaryVertices.data(), mPrimaryVertices.size()); } + void setMultiplicityCutMask(ROFMaskTableN cutMask) + { + mMultiplicityCutMask = std::move(cutMask); + mROFMaskView = mROFMask->getView(); + } + void useMultiplictyMask() noexcept + { + mROFMask = &mMultiplicityCutMask; + mROFMaskView = mROFMask->getView(); + } + void setUPCCutMask(ROFMaskTableN cutMask) { mUPCCutMask = std::move(cutMask); } + void useUPCMask() noexcept + { + mROFMask = &mUPCCutMask; + mROFMaskView = mROFMask->getView(); + } + const auto& getROFMaskView() const { return mROFMaskView; } + const TrackingFrameInfo& getClusterTrackingFrameInfo(int layerId, const Cluster& cl) const; gsl::span getClusterLabels(int layerId, const Cluster& cl) const { return getClusterLabels(layerId, cl.clusterId); } - gsl::span getClusterLabels(int layerId, const int clId) const { return mClusterLabels->getLabels(mClusterExternalIndices[layerId][clId]); } + gsl::span getClusterLabels(int layerId, const int clId) const { return mClusterLabels[((mIsStaggered) ? layerId : 0)]->getLabels(mClusterExternalIndices[layerId][clId]); } int getClusterExternalIndex(int layerId, const int clId) const { return mClusterExternalIndices[layerId][clId]; } - int getClusterSize(int clusterId) const { return mClusterSize[clusterId]; } - void setClusterSize(bounded_vector& v) { mClusterSize = std::move(v); } + int getClusterSize(int layer, int clusterId) const { return mClusterSize[layer][clusterId]; } + void setClusterSize(int layer, bounded_vector& v) { mClusterSize[layer] = std::move(v); } auto& getTrackletsLabel(int layer) { return mTrackletLabels[layer]; } auto& getCellsLabel(int layer) { return mCellLabels[layer]; } - bool hasMCinformation() const { return mClusterLabels; } - void initialise(const int iteration, const TrackingParameters& trkParam, const int maxLayers = 7, bool resetVertices = true); - void resetRofPV() - { - deepVectorClear(mPrimaryVertices); - mROFramesPV.resize(1, 0); - mTotVertPerIteration.resize(1); - } + bool hasMCinformation() const { return mClusterLabels[0] != nullptr; } + void initVertexingTopology(const TrackingParameters& trkParam); + void initDefaultTrackingTopology(const TrackingParameters& trkParam, const int maxLayers = NLayers); + void initTrackerTopologies(gsl::span trkParams, const int maxLayers = NLayers); + void initialise(const TrackingParameters& trkParam, const int maxLayers = NLayers, const int iteration = constants::UnusedIndex); bool isClusterUsed(int layer, int clusterId) const { return mUsedClusters[layer][clusterId]; } void markUsedCluster(int layer, int clusterId) { mUsedClusters[layer][clusterId] = true; } @@ -172,21 +200,18 @@ struct TimeFrame { auto& getCellsLookupTable() { return mCellsLookupTable; } auto& getCellsNeighbours() { return mCellsNeighbours; } + auto& getCellsNeighboursTopology() { return mCellsNeighboursTopology; } auto& getCellsNeighboursLUT() { return mCellsNeighboursLUT; } - auto& getRoads() { return mRoads; } - auto& getTracks(int rofId) { return mTracks[rofId]; } - auto& getTracksLabel(const int rofId) { return mTracksLabel[rofId]; } + auto& getTracks() { return mTracks; } + auto& getTracksLabel() { return mTracksLabel; } auto& getLinesLabel(const int rofId) { return mLinesLabels[rofId]; } - auto& getVerticesMCRecInfo() { return mVerticesMCRecInfo; } - int getNumberOfClusters() const; - virtual int getNumberOfCells() const; - virtual int getNumberOfTracklets() const; - virtual int getNumberOfNeighbours() const; + size_t getNumberOfClusters() const; + virtual size_t getNumberOfCells() const; + virtual size_t getNumberOfTracklets() const; + virtual size_t getNumberOfNeighbours() const; size_t getNumberOfTracks() const; size_t getNumberOfUsedClusters() const; - auto getNumberOfExtendedTracks() const { return mNExtendedTracks; } - auto getNumberOfUsedExtendedClusters() const { return mNExtendedUsedClusters; } /// memory management void setMemoryPool(std::shared_ptr pool); @@ -195,10 +220,8 @@ struct TimeFrame { unsigned long getArtefactsMemory() const; void printArtefactsMemory() const; - /// ROF cuts - int getROFCutClusterMult() const { return mCutClusterMult; }; - int getROFCutVertexMult() const { return mCutVertexMult; }; - int getROFCutAllMult() const { return mCutClusterMult + mCutVertexMult; } + /// staggering + void setIsStaggered(bool b) noexcept { mIsStaggered = b; } // Vertexer void computeTrackletsPerROFScans(); @@ -215,20 +238,8 @@ struct TimeFrame { gsl::span getExclusiveNTrackletsCluster(int rofId, int combId); uint32_t getTotalTrackletsTF(const int iLayer) { return mTotalTracklets[iLayer]; } int getTotalClustersPerROFrange(int rofMin, int range, int layerId) const; - std::array& getBeamXY() { return mBeamPos; } - unsigned int& getNoVertexROF() { return mNoVertexROF; } - void insertPastVertex(const Vertex& vertex, const int refROFId); // \Vertexer - void initialiseRoadLabels(); - void setRoadLabel(int i, const unsigned long long& lab, bool fake); - const unsigned long long& getRoadLabel(int i) const { return mRoadLabels[i].first; } - bool isRoadFake(int i) const { return mRoadLabels[i].second; } - - void setMultiplicityCutMask(const std::vector& cutMask) { mMultiplicityCutMask = cutMask; } - void setROFMask(const std::vector& rofMask) { mROFMask = rofMask; } - void swapMasks() { mMultiplicityCutMask.swap(mROFMask); } - int hasBogusClusters() const { return std::accumulate(mBogusClusters.begin(), mBogusClusters.end(), 0); } void setBz(float bz) { mBz = bz; } @@ -244,7 +255,7 @@ struct TimeFrame { // Propagator const o2::base::PropagatorImpl* getDevicePropagator() const { return mPropagatorDevice; } - virtual void setDevicePropagator(const o2::base::PropagatorImpl*) {}; + virtual void setDevicePropagator(const o2::base::PropagatorImpl* /*unused*/) {}; template void addClusterToLayer(int layer, T&&... args); @@ -252,44 +263,26 @@ struct TimeFrame { void addTrackingFrameInfoToLayer(int layer, T&&... args); void addClusterExternalIndexToLayer(int layer, const int idx) { mClusterExternalIndices[layer].push_back(idx); } - /// Debug and printing - void checkTrackletLUTs(); - void printROFoffsets(); - void printNClsPerROF(); - void printVertices(); - void printTrackletLUTonLayer(int i); - void printCellLUTonLayer(int i); - void printTrackletLUTs(); - void printCellLUTs(); - void printSliceInfo(const int, const int); - - IndexTableUtilsN mIndexTableUtils; - - std::array, nLayers> mClusters; - std::array, nLayers> mTrackingFrameInfo; - std::array, nLayers> mClusterExternalIndices; - std::array, nLayers> mROFramesClusters; - const dataformats::MCTruthContainer* mClusterLabels = nullptr; + std::array, NLayers> mClusters; + std::array, NLayers> mTrackingFrameInfo; + std::array, NLayers> mClusterExternalIndices; + std::array, NLayers> mROFramesClusters; + std::array*, NLayers> mClusterLabels{nullptr}; std::array, 2> mNTrackletsPerCluster; std::array, 2> mNTrackletsPerClusterSum; - std::array, nLayers> mNClustersPerROF; - std::array, nLayers> mIndexTables; + std::array, NLayers> mNClustersPerROF; + std::array, NLayers> mIndexTables; std::vector> mTrackletsLookupTable; - std::array, nLayers> mUsedClusters; - int mNrof = 0; - int mNExtendedTracks{0}; - int mNExtendedUsedClusters{0}; - bounded_vector mROFramesPV; - bounded_vector mPrimaryVertices; + std::array, NLayers> mUsedClusters; - std::array, nLayers> mUnsortedClusters; + std::array, NLayers> mUnsortedClusters; std::vector> mTracklets; - std::vector> mCells; - bounded_vector> mRoads; - std::vector> mTracks; + std::vector> mCells; + bounded_vector mTracks; + bounded_vector mTracksLabel; std::vector> mCellsNeighbours; + std::vector> mCellsNeighboursTopology; std::vector> mCellsLookupTable; - std::vector mMultiplicityCutMask; const o2::base::PropagatorImpl* mPropagatorDevice = nullptr; // Needed only for GPU @@ -300,279 +293,214 @@ struct TimeFrame { virtual const char* getName() const noexcept { return "CPU"; } protected: - void prepareClusters(const TrackingParameters& trkParam, const int maxLayers = nLayers); + void prepareClusters(const TrackingParameters& trkParam, const int maxLayers = NLayers); float mBz = 5.; unsigned int mNTotalLowPtVertices = 0; int mBeamPosWeight = 0; std::array mBeamPos = {0.f, 0.f}; bool isBeamPositionOverridden = false; - std::array mMinR; - std::array mMaxR; - bounded_vector mMSangles; - bounded_vector mPhiCuts; + std::array mMinR; + std::array mMaxR; + bounded_vector mTransitionPhiCuts; + bounded_vector mTransitionMSAngles; bounded_vector mPositionResolution; - bounded_vector mClusterSize; + std::array, NLayers> mClusterSize; - std::vector mROFMask; bounded_vector> mPValphaX; /// PV x and alpha for track propagation std::vector> mTrackletLabels; std::vector> mCellLabels; std::vector> mCellsNeighboursLUT; - std::vector> mTracksLabel; bounded_vector mBogusClusters; /// keep track of clusters with wild coordinates - bounded_vector> mRoadLabels; - int mCutClusterMult{-999}; - int mCutVertexMult{-999}; - // Vertexer + bounded_vector mPrimaryVertices; + bounded_vector mPrimaryVerticesLabels; std::vector> mNTrackletsPerROF; std::vector> mLines; std::vector> mTrackletClusters; std::array, 2> mTrackletsIndexROF; std::vector> mLinesLabels; - std::vector> mVerticesMCRecInfo; - bounded_vector mVerticesContributorLabels; std::array mTotalTracklets = {0, 0}; uint32_t mTotalLines = 0; - unsigned int mNoVertexROF = 0; - bounded_vector mTotVertPerIteration; // \Vertexer + // lookup tables + IndexTableUtilsN mIndexTableUtils; + ROFOverlapTableN mROFOverlapTable; + ROFOverlapTableN::View mROFOverlapTableView; + TrackingTopologyN mVertexingTopology; + TrackingTopologyN mDefaultTrackingTopology; + std::vector mTrackerTopologies; + typename TrackingTopologyN::View mTrackingTopologyView; + ROFVertexLookupTableN mROFVertexLookupTable; + ROFVertexLookupTableN::View mROFVertexLookupTableView; + ROFMaskTableN mMultiplicityCutMask; + ROFMaskTableN mUPCCutMask; + ROFMaskTableN* mROFMask = &mMultiplicityCutMask; + ROFMaskTableN::View mROFMaskView; + + bool mIsStaggered{false}; + std::shared_ptr mMemoryPool; }; -template -inline gsl::span TimeFrame::getPrimaryVertices(int rofId) const +template +gsl::span TimeFrame::getPrimaryVertices(int layer, int rofId) const { - if (mPrimaryVertices.empty()) { + if (rofId < 0 || rofId >= getNrof(layer)) { return {}; } - const int start = mROFramesPV[rofId]; - const int stop_idx = rofId >= mNrof - 1 ? mNrof : rofId + 1; - int delta = mMultiplicityCutMask[rofId] ? mROFramesPV[stop_idx] - start : 0; // return empty span if Rof is excluded - return {&mPrimaryVertices[start], static_cast::size_type>(delta)}; + const auto& entry = mROFVertexLookupTableView.getVertices(layer, rofId); + return {&mPrimaryVertices[entry.getFirstEntry()], static_cast::size_type>(entry.getEntries())}; } -template -inline gsl::span> TimeFrame::getPrimaryVerticesMCRecInfo(const int rofId) const -{ - const int start = mROFramesPV[rofId]; - const int stop_idx = rofId >= mNrof - 1 ? mNrof : rofId + 1; - int delta = mMultiplicityCutMask[rofId] ? mROFramesPV[stop_idx] - start : 0; // return empty span if Rof is excluded - return {&(mVerticesMCRecInfo[start]), static_cast>::size_type>(delta)}; -} - -template -inline gsl::span TimeFrame::getPrimaryVerticesContributors(const int rofId) const -{ - // count the number of cont. in rofs before target rof - unsigned int start{0}, delta{0}; - const auto& pvsBefore = getPrimaryVertices(0, rofId - 1); - for (const auto& pv : pvsBefore) { - start += pv.getNContributors(); - } - const auto& pvsIn = getPrimaryVertices(rofId); - for (const auto& pv : pvsIn) { - delta += pv.getNContributors(); - } - return {&(mVerticesContributorLabels[start]), static_cast::size_type>(delta)}; -} - -template -inline gsl::span TimeFrame::getPrimaryVertices(int romin, int romax) const -{ - if (mPrimaryVertices.empty()) { - return {}; - } - const int stop_idx = romax >= mNrof - 1 ? mNrof : romax + 1; - return {&mPrimaryVertices[mROFramesPV[romin]], static_cast::size_type>(mROFramesPV[stop_idx] - mROFramesPV[romin])}; -} - -template -inline gsl::span> TimeFrame::getPrimaryVerticesXAlpha(int rofId) const -{ - const int start = mROFramesPV[rofId]; - const int stop_idx = rofId >= mNrof - 1 ? mNrof : rofId + 1; - int delta = mMultiplicityCutMask[rofId] ? mROFramesPV[stop_idx] - start : 0; // return empty span if Rof is excluded - return {&(mPValphaX[start]), static_cast>::size_type>(delta)}; -} - -template -inline int TimeFrame::getPrimaryVerticesNum(int rofId) const -{ - return rofId < 0 ? mPrimaryVertices.size() : mROFramesPV[rofId + 1] - mROFramesPV[rofId]; -} - -template -inline void TimeFrame::resetBeamXY(const float x, const float y, const float w) +template +inline void TimeFrame::resetBeamXY(const float x, const float y, const float w) { mBeamPos[0] = x; mBeamPos[1] = y; mBeamPosWeight = w; } -template -inline gsl::span TimeFrame::getROFrameClusters(int layerId) const +template +inline gsl::span TimeFrame::getROFrameClusters(int layerId) const { return {&mROFramesClusters[layerId][0], static_cast::size_type>(mROFramesClusters[layerId].size())}; } -template -inline gsl::span TimeFrame::getClustersOnLayer(int rofId, int layerId) +template +inline gsl::span TimeFrame::getClustersOnLayer(int rofId, int layerId) { - if (rofId < 0 || rofId >= mNrof) { + if (rofId < 0 || rofId >= getNrof(layerId)) { return {}; } int startIdx{mROFramesClusters[layerId][rofId]}; return {&mClusters[layerId][startIdx], static_cast::size_type>(mROFramesClusters[layerId][rofId + 1] - startIdx)}; } -template -inline gsl::span TimeFrame::getClustersOnLayer(int rofId, int layerId) const +template +inline gsl::span TimeFrame::getClustersOnLayer(int rofId, int layerId) const { - if (rofId < 0 || rofId >= mNrof) { + if (rofId < 0 || rofId >= getNrof(layerId)) { return {}; } int startIdx{mROFramesClusters[layerId][rofId]}; return {&mClusters[layerId][startIdx], static_cast::size_type>(mROFramesClusters[layerId][rofId + 1] - startIdx)}; } -template -inline gsl::span TimeFrame::getUsedClustersROF(int rofId, int layerId) +template +inline gsl::span TimeFrame::getUsedClustersROF(int rofId, int layerId) { - if (rofId < 0 || rofId >= mNrof) { + if (rofId < 0 || rofId >= getNrof(layerId)) { return {}; } int startIdx{mROFramesClusters[layerId][rofId]}; return {&mUsedClusters[layerId][startIdx], static_cast::size_type>(mROFramesClusters[layerId][rofId + 1] - startIdx)}; } -template -inline gsl::span TimeFrame::getUsedClustersROF(int rofId, int layerId) const +template +inline gsl::span TimeFrame::getUsedClustersROF(int rofId, int layerId) const { - if (rofId < 0 || rofId >= mNrof) { + if (rofId < 0 || rofId >= getNrof(layerId)) { return {}; } int startIdx{mROFramesClusters[layerId][rofId]}; return {&mUsedClusters[layerId][startIdx], static_cast::size_type>(mROFramesClusters[layerId][rofId + 1] - startIdx)}; } -template -inline gsl::span TimeFrame::getClustersPerROFrange(int rofMin, int range, int layerId) const +template +inline gsl::span TimeFrame::getClustersPerROFrange(int rofMin, int range, int layerId) const { - if (rofMin < 0 || rofMin >= mNrof) { + if (rofMin < 0 || rofMin >= getNrof(layerId)) { return {}; } int startIdx{mROFramesClusters[layerId][rofMin]}; // First cluster of rofMin - int endIdx{mROFramesClusters[layerId][o2::gpu::CAMath::Min(rofMin + range, mNrof)]}; + int endIdx{mROFramesClusters[layerId][o2::gpu::CAMath::Min(rofMin + range, getNrof(layerId))]}; return {&mClusters[layerId][startIdx], static_cast::size_type>(endIdx - startIdx)}; } -template -inline gsl::span TimeFrame::getROFramesClustersPerROFrange(int rofMin, int range, int layerId) const +template +inline gsl::span TimeFrame::getROFramesClustersPerROFrange(int rofMin, int range, int layerId) const { - int chkdRange{o2::gpu::CAMath::Min(range, mNrof - rofMin)}; + int chkdRange{o2::gpu::CAMath::Min(range, getNrof(layerId) - rofMin)}; return {&mROFramesClusters[layerId][rofMin], static_cast::size_type>(chkdRange)}; } -template -inline gsl::span TimeFrame::getNClustersROFrange(int rofMin, int range, int layerId) const +template +inline gsl::span TimeFrame::getNClustersROFrange(int rofMin, int range, int layerId) const { - int chkdRange{o2::gpu::CAMath::Min(range, mNrof - rofMin)}; + int chkdRange{o2::gpu::CAMath::Min(range, getNrof(layerId) - rofMin)}; return {&mNClustersPerROF[layerId][rofMin], static_cast::size_type>(chkdRange)}; } -template -inline int TimeFrame::getTotalClustersPerROFrange(int rofMin, int range, int layerId) const +template +inline int TimeFrame::getTotalClustersPerROFrange(int rofMin, int range, int layerId) const { int startIdx{rofMin}; // First cluster of rofMin - int endIdx{o2::gpu::CAMath::Min(rofMin + range, mNrof)}; + int endIdx{o2::gpu::CAMath::Min(rofMin + range, getNrof(layerId))}; return mROFramesClusters[layerId][endIdx] - mROFramesClusters[layerId][startIdx]; } -template -inline gsl::span TimeFrame::getIndexTablePerROFrange(int rofMin, int range, int layerId) const -{ - const int iTableSize{mIndexTableUtils.getNphiBins() * mIndexTableUtils.getNzBins() + 1}; - int chkdRange{o2::gpu::CAMath::Min(range, mNrof - rofMin)}; - return {&mIndexTables[layerId][rofMin * iTableSize], static_cast::size_type>(chkdRange * iTableSize)}; -} - -template -inline int TimeFrame::getClusterROF(int iLayer, int iCluster) +template +inline int TimeFrame::getClusterROF(int iLayer, int iCluster) { return std::lower_bound(mROFramesClusters[iLayer].begin(), mROFramesClusters[iLayer].end(), iCluster + 1) - mROFramesClusters[iLayer].begin() - 1; } -template -inline gsl::span TimeFrame::getUnsortedClustersOnLayer(int rofId, int layerId) const +template +inline gsl::span TimeFrame::getUnsortedClustersOnLayer(int rofId, int layerId) const { - if (rofId < 0 || rofId >= mNrof) { + if (rofId < 0 || rofId >= getNrof(layerId)) { return {}; } int startIdx{mROFramesClusters[layerId][rofId]}; return {&mUnsortedClusters[layerId][startIdx], static_cast::size_type>(mROFramesClusters[layerId][rofId + 1] - startIdx)}; } -template -inline gsl::span TimeFrame::getIndexTable(int rofId, int layer) +template +inline gsl::span TimeFrame::getIndexTable(int rofId, int layer) { - if (rofId < 0 || rofId >= mNrof) { + if (rofId < 0 || rofId >= getNrof(layer)) { return {}; } const int tableSize = mIndexTableUtils.getNphiBins() * mIndexTableUtils.getNzBins() + 1; return {&mIndexTables[layer][rofId * tableSize], static_cast::size_type>(tableSize)}; } -template +template template -void TimeFrame::addClusterToLayer(int layer, T&&... values) +void TimeFrame::addClusterToLayer(int layer, T&&... values) { mUnsortedClusters[layer].emplace_back(std::forward(values)...); } -template +template template -void TimeFrame::addTrackingFrameInfoToLayer(int layer, T&&... values) +void TimeFrame::addTrackingFrameInfoToLayer(int layer, T&&... values) { mTrackingFrameInfo[layer].emplace_back(std::forward(values)...); } -template -inline gsl::span TimeFrame::getUsedClusters(const int layer) +template +inline gsl::span TimeFrame::getUsedClusters(const int layer) { return {&mUsedClusters[layer][0], static_cast::size_type>(mUsedClusters[layer].size())}; } -template -inline void TimeFrame::initialiseRoadLabels() -{ - mRoadLabels.clear(); - mRoadLabels.resize(mRoads.size()); -} - -template -inline void TimeFrame::setRoadLabel(int i, const unsigned long long& lab, bool fake) -{ - mRoadLabels[i].first = lab; - mRoadLabels[i].second = fake; -} - -template -inline gsl::span TimeFrame::getNTrackletsCluster(int rofId, int combId) +template +inline gsl::span TimeFrame::getNTrackletsCluster(int rofId, int combId) { - if (rofId < 0 || rofId >= mNrof) { + if (rofId < 0 || rofId >= getNrof(1)) { return {}; } auto startIdx{mROFramesClusters[1][rofId]}; return {&mNTrackletsPerCluster[combId][startIdx], static_cast::size_type>(mROFramesClusters[1][rofId + 1] - startIdx)}; } -template -inline gsl::span TimeFrame::getExclusiveNTrackletsCluster(int rofId, int combId) +template +inline gsl::span TimeFrame::getExclusiveNTrackletsCluster(int rofId, int combId) { - if (rofId < 0 || rofId >= mNrof) { + if (rofId < 0 || rofId >= getNrof(1)) { return {}; } auto clusStartIdx{mROFramesClusters[1][rofId]}; @@ -580,38 +508,38 @@ inline gsl::span TimeFrame::getExclusiveNTrackletsCluster(int rofI return {&mNTrackletsPerClusterSum[combId][clusStartIdx], static_cast::size_type>(mROFramesClusters[1][rofId + 1] - clusStartIdx)}; } -template -inline gsl::span TimeFrame::getFoundTracklets(int rofId, int combId) +template +inline gsl::span TimeFrame::getFoundTracklets(int rofId, int combId) { - if (rofId < 0 || rofId >= mNrof || mTracklets[combId].empty()) { + if (rofId < 0 || rofId >= getNrof(1) || mTracklets[combId].empty()) { return {}; } auto startIdx{mNTrackletsPerROF[combId][rofId]}; return {&mTracklets[combId][startIdx], static_cast::size_type>(mNTrackletsPerROF[combId][rofId + 1] - startIdx)}; } -template -inline gsl::span TimeFrame::getFoundTracklets(int rofId, int combId) const +template +inline gsl::span TimeFrame::getFoundTracklets(int rofId, int combId) const { - if (rofId < 0 || rofId >= mNrof) { + if (rofId < 0 || rofId >= getNrof(1)) { return {}; } auto startIdx{mNTrackletsPerROF[combId][rofId]}; return {&mTracklets[combId][startIdx], static_cast::size_type>(mNTrackletsPerROF[combId][rofId + 1] - startIdx)}; } -template -inline gsl::span TimeFrame::getLabelsFoundTracklets(int rofId, int combId) const +template +inline gsl::span TimeFrame::getLabelsFoundTracklets(int rofId, int combId) const { - if (rofId < 0 || rofId >= mNrof || !hasMCinformation()) { + if (rofId < 0 || rofId >= getNrof(1) || !hasMCinformation()) { return {}; } auto startIdx{mNTrackletsPerROF[combId][rofId]}; return {&mTrackletLabels[combId][startIdx], static_cast::size_type>(mNTrackletsPerROF[combId][rofId + 1] - startIdx)}; } -template -inline int TimeFrame::getTotalClusters() const +template +inline int TimeFrame::getTotalClusters() const { size_t totalClusters{0}; for (const auto& clusters : mUnsortedClusters) { @@ -620,58 +548,54 @@ inline int TimeFrame::getTotalClusters() const return int(totalClusters); } -template -inline int TimeFrame::getNumberOfClusters() const +template +inline size_t TimeFrame::getNumberOfClusters() const { - int nClusters = 0; + size_t nClusters{0}; for (const auto& layer : mClusters) { nClusters += layer.size(); } return nClusters; } -template -inline int TimeFrame::getNumberOfCells() const +template +inline size_t TimeFrame::getNumberOfCells() const { - int nCells = 0; + size_t nCells{0}; for (const auto& layer : mCells) { nCells += layer.size(); } return nCells; } -template -inline int TimeFrame::getNumberOfTracklets() const +template +inline size_t TimeFrame::getNumberOfTracklets() const { - int nTracklets = 0; + size_t nTracklets{0}; for (const auto& layer : mTracklets) { nTracklets += layer.size(); } return nTracklets; } -template -inline int TimeFrame::getNumberOfNeighbours() const +template +inline size_t TimeFrame::getNumberOfNeighbours() const { - int n{0}; + size_t neigh{0}; for (const auto& l : mCellsNeighbours) { - n += l.size(); + neigh += l.size(); } - return n; + return neigh; } -template -inline size_t TimeFrame::getNumberOfTracks() const +template +inline size_t TimeFrame::getNumberOfTracks() const { - int nTracks = 0; - for (const auto& t : mTracks) { - nTracks += t.size(); - } - return nTracks; + return mTracks.size(); } -template -inline size_t TimeFrame::getNumberOfUsedClusters() const +template +inline size_t TimeFrame::getNumberOfUsedClusters() const { size_t nClusters = 0; for (const auto& layer : mUsedClusters) { @@ -680,17 +604,6 @@ inline size_t TimeFrame::getNumberOfUsedClusters() const return nClusters; } -template -inline void TimeFrame::insertPastVertex(const Vertex& vertex, const int iteration) -{ - int rofId = vertex.getTimeStamp().getTimeStamp(); - mPrimaryVertices.insert(mPrimaryVertices.begin() + mROFramesPV[rofId], vertex); - for (int i = rofId + 1; i < mROFramesPV.size(); ++i) { - mROFramesPV[i]++; - } - mTotVertPerIteration[iteration]++; -} - } // namespace its } // namespace o2 diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h new file mode 100644 index 0000000000000..d244b39ff9d11 --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackHelpers.h @@ -0,0 +1,303 @@ +// Copyright 2019-2026 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 TrackHelpers.h +/// \brief Shared host/device helpers for ITS tracker trait implementations +/// + +#ifndef O2_ITS_TRACKING_TRACKHELPERS_H_ +#define O2_ITS_TRACKING_TRACKHELPERS_H_ + +#include "DataFormatsITS/TrackITS.h" +#include "ITStracking/Cell.h" +#include "ITStracking/Cluster.h" +#include "ITStracking/Constants.h" +#include "ITStracking/MathUtils.h" +#include "DetectorsBase/Propagator.h" +#include "ReconstructionDataFormats/Track.h" + +namespace o2::its::track +{ + +// Prefer 1) longer track 2) sorted in chi2 +GPUhdi() bool isBetter(const o2::its::TrackITS& a, const o2::its::TrackITS& b) +{ + const auto ncla = a.getNumberOfClusters(); + const auto nclb = b.getNumberOfClusters(); + // is a as long as b ? then decide on chi2 + // otherwise prefer longer + return (ncla == nclb) ? (a.getChi2() < b.getChi2()) : ncla > nclb; +} + +// Find the populated interior layer closest to the radial midpoint. +// If no layer can be found, return constants::UnusedIndex. +// Should minimize the sagitta bias. +template +GPUdi() int selectReseedMidLayer(int minLayer, int maxLayer, const float* layerRadii, const TrackSeed& seed) +{ + int midLayer = constants::UnusedIndex; + float distanceToMidR = layerRadii[NLayers - 1]; // midpoint cannot be last layer + const float midR = 0.5f * (layerRadii[maxLayer] + layerRadii[minLayer]); + for (int iLayer = minLayer + 1; iLayer < maxLayer; ++iLayer) { + if (seed.getCluster(iLayer) != constants::UnusedIndex) { + const float distance = o2::gpu::CAMath::Abs(midR - layerRadii[iLayer]); + if (distance < distanceToMidR) { // keep the smaller-radius layer on ties + midLayer = iLayer; + distanceToMidR = distance; + } + } + } + return midLayer; +} + +GPUdi() void resetTrackCovariance(TrackITSExt& track) +{ + track.resetCovariance(); + track.setCov(track.getQ2Pt() * track.getQ2Pt() * track.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2); +} + +GPUdi() o2::track::TrackParCov buildTrackSeed(const Cluster& cluster1, + const Cluster& cluster2, + const TrackingFrameInfo& tf3, + const float bz, + const bool reverse = false) +{ + float ca = constants::UnsetValue, sa = constants::UnsetValue, snp = constants::UnsetValue, q2pt = constants::UnsetValue, q2pt2 = constants::UnsetValue; + o2::gpu::CAMath::SinCos(tf3.alphaTrackingFrame, sa, ca); + const float sign = reverse ? -1.f : 1.f; + const float x1 = (cluster1.xCoordinate * ca) + (cluster1.yCoordinate * sa); + const float y1 = (-cluster1.xCoordinate * sa) + (cluster1.yCoordinate * ca); + const float x2 = (cluster2.xCoordinate * ca) + (cluster2.yCoordinate * sa); + const float y2 = (-cluster2.xCoordinate * sa) + (cluster2.yCoordinate * ca); + const float x3 = tf3.xTrackingFrame; + const float y3 = tf3.positionTrackingFrame[0]; + if (o2::gpu::CAMath::Abs(bz) < 0.01f) { // zero field + const float dx = x3 - x1; + const float dy = y3 - y1; + snp = sign * dy / o2::gpu::CAMath::Hypot(dx, dy); + q2pt = 1.f / o2::track::kMostProbablePt; + q2pt2 = 1.f; + } else { + const float crv = math_utils::computeCurvature(x3, y3, x2, y2, x1, y1); + snp = sign * crv * (x3 - math_utils::computeCurvatureCentreX(x3, y3, x2, y2, x1, y1)); + q2pt = sign * crv / (bz * o2::constants::math::B2C); + q2pt2 = crv * crv; + } + const float tgl = -0.5f * sign * (math_utils::computeTanDipAngle(x1, y1, x2, y2, cluster1.zCoordinate, cluster2.zCoordinate) + math_utils::computeTanDipAngle(x2, y2, x3, y3, cluster2.zCoordinate, tf3.positionTrackingFrame[1])); + const float sg2q2pt = o2::track::kC1Pt2max * o2::gpu::CAMath::Clamp(q2pt2, 0.0005f, 1.0f); + return {x3, tf3.alphaTrackingFrame, {y3, tf3.positionTrackingFrame[1], snp, tgl, q2pt}, {tf3.covarianceTrackingFrame[0], tf3.covarianceTrackingFrame[1], tf3.covarianceTrackingFrame[2], 0.f, 0.f, o2::track::kCSnp2max, 0.f, 0.f, 0.f, o2::track::kCTgl2max, 0.f, 0.f, 0.f, 0.f, sg2q2pt}}; +} + +template +GPUdi() TrackITSExt seedTrackForRefit(const TrackSeed& seed, + const TrackingFrameInfo* const* foundTrackingFrameInfo, + const Cluster* const* unsortedClusters, + const float* layerRadii, + const float bz, + const int reseedIfShorter) +{ + TrackITSExt temporaryTrack(seed); + int lrMin = NLayers; + int lrMax = 0; + for (int iL{0}; iL < NLayers; ++iL) { + const int idx = seed.getCluster(iL); + temporaryTrack.setExternalClusterIndex(iL, idx, idx != constants::UnusedIndex); + if (idx != constants::UnusedIndex) { + lrMin = o2::gpu::CAMath::Min(lrMin, iL); + lrMax = o2::gpu::CAMath::Max(lrMax, iL); + } + } + + const int ncl = temporaryTrack.getNClusters(); + if (ncl < reseedIfShorter && ncl > 2) { + const int lrMid = selectReseedMidLayer(lrMin, lrMax, layerRadii, seed); + if (lrMid != constants::UnusedIndex) { + const auto& cluster0TF = foundTrackingFrameInfo[lrMin][seed.getCluster(lrMin)]; + const auto& cluster1GL = unsortedClusters[lrMid][seed.getCluster(lrMid)]; + const auto& cluster2GL = unsortedClusters[lrMax][seed.getCluster(lrMax)]; + temporaryTrack.getParamIn() = buildTrackSeed(cluster2GL, cluster1GL, cluster0TF, bz, true); + } + } + + resetTrackCovariance(temporaryTrack); + return temporaryTrack; +} + +GPUdi() bool fitTrack(TrackITSExt& trk, + int start, + int end, + int step, + float chi2clcut, + float chi2ndfcut, + float maxQoverPt, + int nCl, + const float bz, + const TrackingFrameInfo* const* tfInfos, + const float* layerxX0, + const o2::base::Propagator* propagator, + const o2::base::PropagatorF::MatCorrType matCorrType, + o2::track::TrackPar* linRef = nullptr, + const bool shiftRefToCluster = false) +{ + for (int iLayer{start}; iLayer != end; iLayer += step) { + if (trk.getClusterIndex(iLayer) == constants::UnusedIndex) { + continue; + } + + const TrackingFrameInfo& trackingHit = tfInfos[iLayer][trk.getClusterIndex(iLayer)]; + if (linRef) { + if (!trk.o2::track::TrackParCovF::rotate(trackingHit.alphaTrackingFrame, *linRef, bz)) { + return false; + } + if (!propagator->propagateToX(trk, *linRef, trackingHit.xTrackingFrame, bz, + o2::base::PropagatorImpl::MAX_SIN_PHI, + o2::base::PropagatorImpl::MAX_STEP, + matCorrType)) { + return false; + } + if (matCorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { + if (!trk.correctForMaterial(*linRef, layerxX0[iLayer], layerxX0[iLayer] * constants::Radl * constants::Rho, true)) { + continue; + } + } + } else { + if (!trk.o2::track::TrackParCovF::rotate(trackingHit.alphaTrackingFrame)) { + return false; + } + if (!propagator->propagateToX(trk, trackingHit.xTrackingFrame, bz, + o2::base::PropagatorImpl::MAX_SIN_PHI, + o2::base::PropagatorImpl::MAX_STEP, + matCorrType)) { + return false; + } + if (matCorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { + if (!trk.correctForMaterial(layerxX0[iLayer], layerxX0[iLayer] * constants::Radl * constants::Rho, true)) { + continue; + } + } + } + + const auto predChi2{trk.getPredictedChi2Quiet(trackingHit.positionTrackingFrame, trackingHit.covarianceTrackingFrame)}; + if ((nCl >= 3 && predChi2 > chi2clcut) || predChi2 < 0.f) { + return false; + } + trk.setChi2(trk.getChi2() + predChi2); + if (!trk.o2::track::TrackParCov::update(trackingHit.positionTrackingFrame, trackingHit.covarianceTrackingFrame)) { + return false; + } + if (linRef && shiftRefToCluster) { + linRef->setY(trackingHit.positionTrackingFrame[0]); + linRef->setZ(trackingHit.positionTrackingFrame[1]); + } + nCl++; + } + + return o2::gpu::CAMath::Abs(trk.getQ2Pt()) < maxQoverPt && trk.getChi2() < chi2ndfcut * (float)((nCl * 2) - 5); +} + +template +GPUdi() bool refitTrack(const TrackSeed& trackSeed, + TrackITSExt& temporaryTrack, + float chi2clcut, + float chi2ndfcut, + const float bz, + const TrackingFrameInfo* const* tfInfos, + const Cluster* const* clusters, + const float* layerxX0, + const float* layerRadii, + const float* minPt, + const o2::base::Propagator* propagator, + const o2::base::PropagatorF::MatCorrType matCorrType, + const int reseedIfShorter, + const bool shiftRefToCluster, + const bool repeatRefitOut) +{ + temporaryTrack = seedTrackForRefit(trackSeed, + tfInfos, + clusters, + layerRadii, + bz, + reseedIfShorter); + o2::track::TrackPar linRef{temporaryTrack}; + bool fitSuccess = fitTrack(temporaryTrack, + 0, + NLayers, + 1, + chi2clcut, + chi2ndfcut, + o2::constants::math::VeryBig, + 0, + bz, + tfInfos, + layerxX0, + propagator, + matCorrType, + &linRef, + shiftRefToCluster); + if (!fitSuccess) { + return false; + } + temporaryTrack.getParamOut() = temporaryTrack.getParamIn(); + linRef = temporaryTrack.getParamOut(); // use refitted track as lin.reference + resetTrackCovariance(temporaryTrack); + temporaryTrack.setChi2(0); + fitSuccess = fitTrack(temporaryTrack, + NLayers - 1, + -1, + -1, + chi2clcut, + chi2ndfcut, + 50.f, + 0, + bz, + tfInfos, + layerxX0, + propagator, + matCorrType, + &linRef, + shiftRefToCluster); + if (!fitSuccess || temporaryTrack.getPt() < minPt[NLayers - temporaryTrack.getNClusters()]) { + return false; + } + if (repeatRefitOut) { // repeat outward refit seeding and linearizing with the stable inward fit result + o2::track::TrackParCov saveInw{temporaryTrack}; + linRef = saveInw; // use refitted track as lin.reference + float saveChi2 = temporaryTrack.getChi2(); + track::resetTrackCovariance(temporaryTrack); + temporaryTrack.setChi2(0); + fitSuccess = o2::its::track::fitTrack(temporaryTrack, + 0, + NLayers, + 1, + chi2clcut, + chi2ndfcut, + o2::constants::math::VeryBig, + 0, + bz, + tfInfos, + layerxX0, + propagator, + matCorrType, + &linRef, + shiftRefToCluster); + if (!fitSuccess) { + return false; + } + temporaryTrack.getParamOut() = temporaryTrack.getParamIn(); + temporaryTrack.getParamIn() = saveInw; + temporaryTrack.setChi2(saveChi2); + } + return true; +} + +} // namespace o2::its::track + +#endif diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracker.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracker.h index 3ea382c626fed..240b0eb1e2f63 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracker.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracker.h @@ -18,7 +18,6 @@ #include #include -#include #include #include #include @@ -26,21 +25,16 @@ #include #include #include +#include #include #include "ITStracking/Configuration.h" -#include "CommonConstants/MathConstants.h" #include "ITStracking/Definitions.h" -#include "ITStracking/MathUtils.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/TrackerTraits.h" -#include "ITStracking/Road.h" #include "ITStracking/BoundedAllocator.h" -#include "DataFormatsITS/TrackITS.h" -#include "SimulationDataFormat/MCCompLabel.h" - namespace o2 { @@ -51,17 +45,17 @@ class GPUChainITS; namespace its { -template +template class Tracker { using LogFunc = std::function; public: - Tracker(TrackerTraits* traits); + Tracker(TrackerTraits* traits); - void adoptTimeFrame(TimeFrame& tf); + void adoptTimeFrame(TimeFrame& tf); - void clustersToTracks( + float clustersToTracks( const LogFunc& = [](const std::string& s) { std::cout << s << '\n'; }, const LogFunc& = [](const std::string& s) { std::cerr << s << '\n'; }); @@ -69,53 +63,53 @@ class Tracker void setMemoryPool(std::shared_ptr pool) { mMemoryPool = pool; } std::vector& getParameters() { return mTrkParams; } void setBz(float bz) { mTraits->setBz(bz); } - bool isMatLUT() const { return mTraits->isMatLUT(); } + void setTimeSlice(size_t slice) noexcept { mTimeSlice = slice; } void setNThreads(int n, std::shared_ptr& arena) { mTraits->setNThreads(n, arena); } void printSummary() const; void computeTracksMClabels(); private: void initialiseTimeFrame(int iteration) { mTraits->initialiseTimeFrame(iteration); } - void computeTracklets(int iteration, int iROFslice, int iVertex) { mTraits->computeLayerTracklets(iteration, iROFslice, iVertex); } + void computeTracklets(int iteration, int iVertex) { mTraits->computeLayerTracklets(iteration, iVertex); } void computeCells(int iteration) { mTraits->computeLayerCells(iteration); } void findCellsNeighbours(int iteration) { mTraits->findCellsNeighbours(iteration); } void findRoads(int iteration) { mTraits->findRoads(iteration); } - void findShortPrimaries() { mTraits->findShortPrimaries(); } - void extendTracks(int iteration) { mTraits->extendTracks(iteration); } - // MC interaction - void computeRoadsMClabels(); void rectifyClusterIndices(); + void sortTracks(); template - float evaluateTask(void (Tracker::*task)(T...), std::string_view taskName, int iteration, LogFunc logger, F&&... args); + float evaluateTask(void (Tracker::*task)(T...), std::string_view taskName, int iteration, const LogFunc& logger, F&&... args); - TrackerTraits* mTraits = nullptr; /// Observer pointer, not owned by this class - TimeFrame* mTimeFrame = nullptr; /// Observer pointer, not owned by this class + TrackerTraits* mTraits = nullptr; /// Observer pointer, not owned by this class + TimeFrame* mTimeFrame = nullptr; /// Observer pointer, not owned by this class std::vector mTrkParams; o2::gpu::GPUChainITS* mRecoChain = nullptr; + size_t mTimeSlice{0}; // current timeslice unsigned int mNumberOfDroppedTFs{0}; unsigned int mTimeFrameCounter{0}; double mTotalTime{0}; std::shared_ptr mMemoryPool; - enum State { + enum Steps { TFInit = 0, Trackleting, Celling, Neighbouring, Roading, - NStates, + NSteps, }; - State mCurState{TFInit}; - static constexpr std::array StateNames{"TimeFrame initialisation", "Tracklet finding", "Cell finding", "Neighbour finding", "Road finding"}; + Steps mCurStep{TFInit}; + static constexpr std::array StateNames{"TimeFrame initialisation", "Tracklet finding", "Cell finding", "Neighbour finding", "Road finding"}; + std::vector> mTimingStats; + void addTimingStatCurStep(int iteration, double timeMs); }; -template +template template -float Tracker::evaluateTask(void (Tracker::*task)(T...), std::string_view taskName, int iteration, LogFunc logger, F&&... args) +float Tracker::evaluateTask(void (Tracker::*task)(T...), std::string_view taskName, int iteration, const LogFunc& logger, F&&... args) { float diff{0.f}; @@ -135,7 +129,7 @@ float Tracker::evaluateTask(void (Tracker::*task)(T...), std:: } logger(sstream.str()); - if (mTrkParams[0].SaveTimeBenchmarks) { + if (mTrkParams[iteration].SaveTimeBenchmarks) { std::string taskNameStr(taskName); std::transform(taskNameStr.begin(), taskNameStr.end(), taskNameStr.begin(), [](unsigned char c) { return std::tolower(c); }); @@ -143,12 +137,17 @@ float Tracker::evaluateTask(void (Tracker::*task)(T...), std:: if (std::ofstream file{"its_time_benchmarks.txt", std::ios::app}) { file << "trk:" << iteration << '\t' << taskNameStr << '\t' << diff << '\n'; } + addTimingStatCurStep(iteration, diff); } } else { (this->*task)(std::forward(args)...); } + if (mTrkParams[iteration].PrintMemory) { + LOGP(info, "iter:{}:{}: {}", iteration, StateNames[mCurStep], mMemoryPool->asString()); + } + return diff; } diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h index ddc32ed18cbfe..f536e86fe95d5 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackerTraits.h @@ -18,9 +18,7 @@ #include -#include "DetectorsBase/Propagator.h" #include "ITStracking/Configuration.h" -#include "ITStracking/MathUtils.h" #include "ITStracking/IndexTableUtils.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/Cell.h" @@ -38,97 +36,62 @@ namespace its { class TrackITSExt; -template +template class TrackerTraits { public: - using IndexTableUtilsN = IndexTableUtils; - using CellSeedN = CellSeed; + using IndexTableUtilsN = IndexTableUtils; + using TrackSeedN = TrackSeed; virtual ~TrackerTraits() = default; - virtual void adoptTimeFrame(TimeFrame* tf) { mTimeFrame = tf; } - virtual void initialiseTimeFrame(const int iteration) { mTimeFrame->initialise(iteration, mTrkParams[iteration], mTrkParams[iteration].NLayers); } + virtual void adoptTimeFrame(TimeFrame* tf) { mTimeFrame = tf; } + virtual void initialiseTimeFrame(const int iteration) { mTimeFrame->initialise(mTrkParams[iteration], mTrkParams[iteration].NLayers, iteration); } - virtual void computeLayerTracklets(const int iteration, int iROFslice, int iVertex); + virtual void computeLayerTracklets(const int iteration, int iVertex); virtual void computeLayerCells(const int iteration); virtual void findCellsNeighbours(const int iteration); virtual void findRoads(const int iteration); - virtual bool supportsExtendTracks() const noexcept { return true; } - virtual void extendTracks(const int iteration); - virtual bool supportsFindShortPrimaries() const noexcept { return true; } - virtual void findShortPrimaries(); + template + void processNeighbours(int iteration, int defaultCellTopologyId, int iLevel, const bounded_vector& currentCellSeed, const bounded_vector& currentCellId, const bounded_vector& currentCellTopologyId, bounded_vector& updatedCellSeed, bounded_vector& updatedCellId, bounded_vector& updatedCellTopologyId); - virtual bool trackFollowing(TrackITSExt* track, int rof, bool outward, const int iteration); - virtual void processNeighbours(int iLayer, int iLevel, const bounded_vector& currentCellSeed, const bounded_vector& currentCellId, bounded_vector& updatedCellSeed, bounded_vector& updatedCellId); + void acceptTracks(int iteration, bounded_vector& tracks, bounded_vector>& firstClusters); + void markTracks(int iteration); - void updateTrackingParameters(const std::vector& trkPars) { mTrkParams = trkPars; } - TimeFrame* getTimeFrame() { return mTimeFrame; } + void updateTrackingParameters(const std::vector& trkPars) + { + mTrkParams = trkPars; + } + TimeFrame* getTimeFrame() { return mTimeFrame; } virtual void setBz(float bz); float getBz() const { return mBz; } - bool isMatLUT() const; virtual const char* getName() const noexcept { return "CPU"; } virtual bool isGPU() const noexcept { return false; } void setMemoryPool(std::shared_ptr pool) noexcept { mMemoryPool = pool; } auto getMemoryPool() const noexcept { return mMemoryPool; } // Others - GPUhd() static consteval int4 getEmptyBinsRect() { return int4{0, 0, 0, 0}; } - const int4 getBinsRect(int layer, float phi, float maxdeltaphi, float z, float maxdeltaz) const noexcept { return getBinsRect(layer, phi, maxdeltaphi, z, z, maxdeltaz); } - const int4 getBinsRect(const Cluster& cls, int layer, float z1, float z2, float maxdeltaz, float maxdeltaphi) const noexcept { return getBinsRect(layer, cls.phi, maxdeltaphi, z1, z2, maxdeltaz); } - const int4 getBinsRect(int layer, float phi, float maxdeltaphi, float z1, float z2, float maxdeltaz) const noexcept; - void SetRecoChain(o2::gpu::GPUChainITS* chain) { mChain = chain; } - void setSmoothing(bool v) { mApplySmoothing = v; } - bool getSmoothing() const { return mApplySmoothing; } void setNThreads(int n, std::shared_ptr& arena); int getNThreads() { return mTaskArena->max_concurrency(); } - o2::gpu::GPUChainITS* getChain() const { return mChain; } - // TimeFrame information forwarding virtual int getTFNumberOfClusters() const { return mTimeFrame->getNumberOfClusters(); } virtual int getTFNumberOfTracklets() const { return mTimeFrame->getNumberOfTracklets(); } virtual int getTFNumberOfCells() const { return mTimeFrame->getNumberOfCells(); } private: - track::TrackParCov buildTrackSeed(const Cluster& cluster1, const Cluster& cluster2, const TrackingFrameInfo& tf3, bool reverse = false); - TrackITSExt seedTrackForRefit(const CellSeedN& seed); - bool fitTrack(TrackITSExt& track, int start, int end, int step, float chi2clcut = o2::constants::math::VeryBig, float chi2ndfcut = o2::constants::math::VeryBig, float maxQoverPt = o2::constants::math::VeryBig, int nCl = 0, o2::track::TrackPar* refLin = nullptr); - - bool mApplySmoothing = false; std::shared_ptr mMemoryPool; std::shared_ptr mTaskArena; protected: o2::gpu::GPUChainITS* mChain = nullptr; - TimeFrame* mTimeFrame; + TimeFrame* mTimeFrame; std::vector mTrkParams; float mBz{-999.f}; - bool mIsZeroField{false}; }; -template -inline const int4 TrackerTraits::getBinsRect(const int layerIndex, float phi, float maxdeltaphi, float z1, float z2, float maxdeltaz) const noexcept -{ - const float zRangeMin = o2::gpu::GPUCommonMath::Min(z1, z2) - maxdeltaz; - const float phiRangeMin = (maxdeltaphi > o2::constants::math::PI) ? 0.f : phi - maxdeltaphi; - const float zRangeMax = o2::gpu::GPUCommonMath::Max(z1, z2) + maxdeltaz; - const float phiRangeMax = (maxdeltaphi > o2::constants::math::PI) ? o2::constants::math::TwoPI : phi + maxdeltaphi; - - if (zRangeMax < -mTrkParams[0].LayerZ[layerIndex] || - zRangeMin > mTrkParams[0].LayerZ[layerIndex] || zRangeMin > zRangeMax) { - return getEmptyBinsRect(); - } - - const IndexTableUtilsN& utils{mTimeFrame->mIndexTableUtils}; - return int4{o2::gpu::GPUCommonMath::Max(0, utils.getZBinIndex(layerIndex, zRangeMin)), - utils.getPhiBinIndex(math_utils::getNormalizedPhi(phiRangeMin)), - o2::gpu::GPUCommonMath::Min(mTrkParams[0].ZBins - 1, utils.getZBinIndex(layerIndex, zRangeMax)), // /!\ trkParams can potentially change across iterations - utils.getPhiBinIndex(math_utils::getNormalizedPhi(phiRangeMax))}; -} - } // namespace its } // namespace o2 diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h index 0529bd53f2073..69aa3c5fdaf06 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingConfigParam.h @@ -15,6 +15,7 @@ #include #include "CommonUtils/ConfigurableParam.h" #include "CommonUtils/ConfigurableParamHelper.h" +#include "ITStracking/Constants.h" namespace o2::its { @@ -22,34 +23,36 @@ namespace o2::its struct VertexerParamConfig : public o2::conf::ConfigurableParamHelper { bool saveTimeBenchmarks = false; // dump metrics on file - int nIterations = 1; // Number of vertexing passes to perform. - int vertPerRofThreshold = 0; // Maximum number of vertices per ROF to trigger second a iteration. - bool allowSingleContribClusters = false; // attempt to find vertices in case of a single tracklet found. - int deltaRof = 0; // Number of ROFs to be considered for the vertexing. + int nIterations = 1; // Number of vertexing passes to perform. + int vertPerRofThreshold = 0; // Maximum number of vertices per ROF to trigger second a iteration. - // geometrical cuts for tracklet selection + // geometrical cuts for tracklet selection for Pb-Pb float zCut = 0.002f; float phiCut = 0.005f; - float pairCut = 0.04f; - float clusterCut = 0.8f; - float histPairCut = 0.04f; - float tanLambdaCut = 0.002f; // tanLambda = deltaZ/deltaR - float lowMultBeamDistCut = 0.1f; // XY cut for low-multiplicity pile up - int vertNsigmaCut = 4; // N sigma cut for vertex XY - float vertRadiusSigma = 0.05f; // sigma of vertex XY - float trackletSigma = 0.01f; // tracklet to vertex sigma + float pairCut = 0.017321f; + float clusterCut = 0.170048f; + float coarseZWindow = 0.055458f; + float seedDedupZCut = 0.116685f; + float refitDedupZCut = 0.039855f; + float duplicateZCut = 0.200097f; + float finalSelectionZCut = 0.034535f; + float duplicateDistance2Cut = 0.005117f; + float tanLambdaCut = 0.002f; // tanLambda = deltaZ/deltaR + float nSigmaCut = 0.0164651f; float maxZPositionAllowed = 25.f; // 4x sZ of the beam // Artefacts selections - int clusterContributorsCut = 16; // minimum number of contributors for the second vertex found in the same ROF (pileup cut) - int maxTrackletsPerCluster = 1e2; + int clusterContributorsCut = 3; // minimum number of contributors for an accepted final vertex + int suppressLowMultDebris = 16; // suppress all vertices below this threshold if a vertex was already found in a rof + int seedMemberRadiusTime = 0; + int seedMemberRadiusZ = 2; + int maxTrackletsPerCluster = 100; int phiSpan = -1; int zSpan = -1; int ZBins = 1; // z-phi index table configutation: number of z bins int PhiBins = 128; // z-phi index table configutation: number of phi bins - bool useTruthSeeding{false}; // overwrite seeding vertices with MC truth - bool outputContLabels{false}; // output additioanlly for each vertex its contributing line labels + bool useTruthSeeding{false}; // overwrite seeding vertices with MC truth int nThreads = 1; bool printMemory = false; @@ -60,18 +63,19 @@ struct VertexerParamConfig : public o2::conf::ConfigurableParamHelper { - // Use TGeo for mat. budget - static const int MaxIter = 4; static const int MinTrackLength = 4; static const int MaxTrackLength = 7; - bool useMatCorrTGeo = false; // use full geometry to corect for material budget accounting in the fits. Default is to use the material budget LUT. - bool useFastMaterial = false; // use faster material approximation for material budget accounting in the fits. - int deltaRof = 0; // configure the width of the window in ROFs to be considered for the tracking. - int minTrackLgtIter[MaxIter] = {}; // minimum track length at each iteration, used only if >0, otherwise use code defaults - uint8_t startLayerMask[MaxIter] = {}; // mask of start layer for this iteration (if >0) - float minPtIterLgt[MaxIter * (MaxTrackLength - MinTrackLength + 1)] = {}; // min.pT for given track length at this iteration, used only if >0, otherwise use code defaults - float sysErrY2[7] = {0}; // systematic error^2 in Y per layer - float sysErrZ2[7] = {0}; // systematic error^2 in Z per layer + + bool useMatCorrTGeo = false; // use full geometry to corect for material budget accounting in the fits. Default is to use the material budget LUT. + bool useFastMaterial = false; // use faster material approximation for material budget accounting in the fits. + int addTimeError[7] = {0}; // configure the width of the window in BC to be considered for the tracking. + int minTrackLgtIter[constants::MaxIter] = {}; // minimum track length at each iteration, used only if >0, otherwise use code defaults + uint8_t startLayerMask[constants::MaxIter] = {}; // mask of start layer for this iteration (if >0) + int maxHolesIter[constants::MaxIter] = {}; // maximum number of missing internal layers allowed in the CA topology for each iteration + uint16_t holeLayerMaskIter[constants::MaxIter] = {}; // layers that may be skipped by the CA topology for each iteration + float minPtIterLgt[constants::MaxIter * (MaxTrackLength - MinTrackLength + 1)] = {}; // min.pT for given track length at this iteration, used only if >0, otherwise use code defaults + float sysErrY2[7] = {0}; // systematic error^2 in Y per layer + float sysErrZ2[7] = {0}; // systematic error^2 in Z per layer float maxChi2ClusterAttachment = -1.f; float maxChi2NDF = -1.f; float nSigmaCut = -1.f; @@ -80,73 +84,33 @@ struct TrackerParamConfig : public o2::conf::ConfigurableParamHelper 0 off - float trackFollowerNSigmaZ = 1.f; // sigma in z-cut for track-following search rectangle - float trackFollowerNSigmaPhi = 1.f; // sigma in phi-cut for track-following search rectangle - float cellsPerClusterLimit = -1.f; - float trackletsPerClusterLimit = -1.f; - int findShortTracks = -1; - int nROFsPerIterations = 0; // size of the slice of ROFs to be processed at a time, preferably integer divisors of nROFs per TF, to balance the iterations. - int nOrbitsPerIterations = 0; // not implemented: size of the slice of ROFs to be processed at a time, computed using the number of ROFs per orbit. + float diamondPos[3] = {0.f, 0.f, 0.f}; // override the position of the vertex + bool useDiamond = false; // enable overriding the vertex position bool perPrimaryVertexProcessing = false; // perform the full tracking considering the vertex hypotheses one at the time. bool saveTimeBenchmarks = false; // dump metrics on file bool overrideBeamEstimation = false; // use beam position from meanVertex CCDB object int trackingMode = -1; // -1: unset, 0=sync, 1=async, 2=cosmics used by gpuwf only bool doUPCIteration = false; // Perform an additional iteration for UPC events on tagged vertices. You want to combine this config with VertexerParamConfig.nIterations=2 - int nIterations = MaxIter; // overwrite the number of iterations + int nIterations = constants::MaxIter; // overwrite the number of iterations int reseedIfShorter = 6; // for the final refit reseed the track with circle if they are shorter than this value bool shiftRefToCluster{true}; // TrackFit: after update shift the linearization reference to cluster bool repeatRefitOut{false}; // repeat outward refit using inward refit as a seed - bool createArtefactLabels{false}; // create on-the-fly labels for the artefacts + bool createArtefactLabels{false}; // create on-the-fly labels for the artefacts int nThreads = 1; bool printMemory = false; size_t maxMemory = std::numeric_limits::max(); bool dropTFUponFailure = false; - bool fataliseUponFailure = true; // granular management of the fatalisation in async mode + bool fataliseUponFailure = true; // granular management of the fatalisation in async mode + + // Selections on tracks sharing clusters bool allowSharingFirstCluster = false; // allow first cluster sharing among tracks + float sharedClusterMaxDeltaPhi = 0.05f; // Maximum allowed delta phi at the cluster position + float sharedClusterMaxDeltaEta = 0.03f; // Maximum allowed delta eta at the cluster position + bool sharedClusterOppositeSign = false; // Require opposite sign of the tracklets O2ParamDef(TrackerParamConfig, "ITSCATrackerParam"); }; -struct ITSGpuTrackingParamConfig : public o2::conf::ConfigurableParamHelper { - static constexpr int MaxIter = TrackerParamConfig::MaxIter; - - /// Set nBlocks/nThreads to summarily override all kernel launch parameters in each iteration. - /// Parameters must start with nBlocks/nThreads. - static constexpr int OverrideValue{-1}; - static constexpr char const* BlocksName = "nBlocks"; - static constexpr char const* ThreadsName = "nThreads"; - int nBlocks = OverrideValue; - int nThreads = OverrideValue; - void maybeOverride() const; - - /// Individual kernel launch parameter for each iteration - int nBlocksLayerTracklets[MaxIter] = {60, 60, 60, 60}; - int nThreadsLayerTracklets[MaxIter] = {256, 256, 256, 256}; - - int nBlocksLayerCells[MaxIter] = {60, 60, 60, 60}; - int nThreadsLayerCells[MaxIter] = {256, 256, 256, 256}; - - int nBlocksFindNeighbours[MaxIter] = {60, 60, 60, 60}; - int nThreadsFindNeighbours[MaxIter] = {256, 256, 256, 256}; - - int nBlocksProcessNeighbours[MaxIter] = {60, 60, 60, 60}; - int nThreadsProcessNeighbours[MaxIter] = {256, 256, 256, 256}; - - int nBlocksTracksSeeds[MaxIter] = {60, 60, 60, 60}; - int nThreadsTracksSeeds[MaxIter] = {256, 256, 256, 256}; - - int nBlocksVtxComputeTracklets[2] = {60, 60}; - int nThreadsVtxComputeTracklets[2] = {256, 256}; - - int nBlocksVtxComputeMatching[2] = {60, 60}; - int nThreadsVtxComputeMatching[2] = {256, 256}; - - O2ParamDef(ITSGpuTrackingParamConfig, "ITSGpuTrackingParam"); -}; - } // namespace o2::its #endif diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h index a882ca9b779c4..14c5d6a62e0ad 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h @@ -34,6 +34,7 @@ namespace o2::its { class ITSTrackingInterface { + public: static constexpr int NLayers{7}; using VertexerN = Vertexer; using VertexerTraitsN = VertexerTraits; @@ -41,11 +42,12 @@ class ITSTrackingInterface using TrackerTraitsN = TrackerTraits; using TimeFrameN = TimeFrame; - public: ITSTrackingInterface(bool isMC, + bool doStag, int trgType, const bool overrBeamEst) : mIsMC{isMC}, + mDoStaggering(doStag), mUseTriggers{trgType}, mOverrideBeamEstimation{overrBeamEst} {} @@ -78,22 +80,27 @@ class ITSTrackingInterface TimeFrameN* mTimeFrame = nullptr; protected: + virtual void overrideParameters(std::vector& t, std::vector& v) {} + virtual void requestTopologyDictionary(framework::ProcessingContext& pc); virtual void loadROF(gsl::span& trackROFspan, gsl::span clusters, gsl::span::iterator& pattIt, + int layer, const dataformats::MCTruthContainer* mcLabels); private: bool mIsMC = false; + bool mDoStaggering = false; bool mRunVertexer = true; bool mCosmicsProcessing = false; int mUseTriggers = 0; + std::vector mFilter; TrackingMode::Type mMode = TrackingMode::Unset; bool mOverrideBeamEstimation = false; const o2::itsmft::TopologyDictionary* mDict = nullptr; std::unique_ptr mTracker = nullptr; std::unique_ptr mVertexer = nullptr; - const o2::dataformats::MeanVertexObject* mMeanVertex; + const o2::dataformats::MeanVertexObject* mMeanVertex{}; std::shared_ptr mMemoryPool; std::shared_ptr mTaskArena; }; diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingTopology.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingTopology.h new file mode 100644 index 0000000000000..2afb67609664f --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingTopology.h @@ -0,0 +1,219 @@ +// Copyright 2019-2026 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. + +#ifndef TRACKINGITSU_INCLUDE_TRACKINGTOPOLOGY_H_ +#define TRACKINGITSU_INCLUDE_TRACKINGTOPOLOGY_H_ + +#include +#include +#include +#include + +#ifndef GPUCA_GPUCODE +#include +#include +#include "Framework/Logger.h" +#endif + +#include "CommonDataFormat/RangeReference.h" +#include "GPUCommonDef.h" +#include "GPUCommonMath.h" +#include "ITStracking/LayerMask.h" + +namespace o2::its +{ + +template +class TrackingTopology +{ + public: + using Id = uint8_t; + using Mask = LayerMask; + using Range = o2::dataformats::RangeReference; + static constexpr int MaxTransitions = (NLayers * (NLayers - 1)) / 2; + static constexpr int MaxCells = (NLayers * (NLayers - 1) * (NLayers - 2)) / 6; + static_assert(NLayers < std::numeric_limits::max()); + static_assert(MaxTransitions <= std::numeric_limits::max()); + static_assert(MaxCells <= std::numeric_limits::max()); + + // Describes from which layer to which layer the look-up happens + struct LayerTransition { + Id fromLayer{0}; + Id toLayer{0}; + }; + static_assert(std::is_standard_layout_v); + static_assert(std::is_trivially_copyable_v); + static_assert(sizeof(LayerTransition) == (2 * sizeof(Id))); + + // Describes from which LayerTransition a tracklet is allowed to originate + // and with which LayerTransition this can be combined additionally the hitMasked is cached + struct CellTopology { + Id firstTransition{0}; + Id secondTransition{0}; + Mask hitLayerMask{0}; + }; + static_assert(std::is_standard_layout_v); + static_assert(std::is_trivially_copyable_v); + static_assert(sizeof(CellTopology) == (2 * sizeof(Id)) + sizeof(Mask)); + + // GPU ready view of the underlying LUTs + struct View { + const LayerTransition* transitions{nullptr}; + const CellTopology* cells{nullptr}; + const Range* cellsByFirstTransitionIndex{nullptr}; + const Id* cellsByFirstTransition{nullptr}; + Id nTransitions{0}; + Id nCells{0}; + Id nCellsByFirstTransition{0}; + + GPUhdi() const LayerTransition& getTransition(Id id) const { return transitions[id]; } + GPUhdi() const CellTopology& getCell(Id id) const { return cells[id]; } + GPUhdi() Range getCellsStartingWithTransition(Id transitionId) const { return cellsByFirstTransitionIndex[transitionId]; } + +#ifndef GPUCA_GPUCODE + std::string asString() const + { + std::string out = fmt::format("TrackingTopology: transitions={} cells={}", nTransitions, nCells); + out += "\n transitions:"; + for (Id transitionId = 0; transitionId < nTransitions; ++transitionId) { + const auto& t = transitions[transitionId]; + out += fmt::format("\n {}: {} -> {}", transitionId, t.fromLayer, t.toLayer); + } + out += "\n cells:"; + for (Id cellId = 0; cellId < nCells; ++cellId) { + const auto& c = cells[cellId]; + const auto& first = transitions[c.firstTransition]; + const auto& second = transitions[c.secondTransition]; + out += fmt::format("\n {}: {} -> {} -> {} hitMask={} transitions=({}, {})", cellId, first.fromLayer, first.toLayer, second.toLayer, c.hitLayerMask.asString(), c.firstTransition, c.secondTransition); + } + return out; + } + + void print() const + { + LOGP(info, "{}", asString()); + } +#endif + }; + + void init(int maxLayers, int maxHoles, Mask holeLayerMask) + { + clear(); + mMaxLayers = o2::gpu::CAMath::Max(0, o2::gpu::CAMath::Min(maxLayers, NLayers)); + mMaxHoles = o2::gpu::CAMath::Max(maxHoles, 0); + mHoleLayerMask = holeLayerMask; + for (int fromLayer = 0; fromLayer < mMaxLayers; ++fromLayer) { + for (int toLayer = fromLayer + 1; toLayer < mMaxLayers; ++toLayer) { + if (Mask::skipped(fromLayer, toLayer).isAllowedHoleMask(mMaxHoles, mHoleLayerMask)) { + mTransitions[mNTransitions++] = LayerTransition{static_cast(fromLayer), static_cast(toLayer)}; + } + } + } + + for (Id firstId = 0; firstId < mNTransitions; ++firstId) { + const auto& first = mTransitions[firstId]; + for (Id secondId = 0; secondId < mNTransitions; ++secondId) { + const auto& second = mTransitions[secondId]; + if (first.toLayer != second.fromLayer) { + continue; + } + const Mask hitMask{first.fromLayer, first.toLayer, second.toLayer}; + if (hitMask.isAllowed(mMaxHoles, mHoleLayerMask)) { + mCells[mNCells++] = CellTopology{firstId, secondId, hitMask}; + } + } + } + + fillCellsByTransition(); + } + + View getView() const + { + return View{mTransitions.data(), + mCells.data(), + mCellsByFirstTransitionIndex.data(), + mCellsByFirstTransition.data(), + mNTransitions, + mNCells, + mNCellsByFirstTransition}; + } + + View getDeviceView(const LayerTransition* deviceTransitions, + const CellTopology* deviceCells, + const Range* deviceCellsByFirstTransitionIndex, + const Id* deviceCellsByFirstTransition) const + { + return View{deviceTransitions, + deviceCells, + deviceCellsByFirstTransitionIndex, + deviceCellsByFirstTransition, + mNTransitions, + mNCells, + mNCellsByFirstTransition}; + } + + const auto& getTransitions() const noexcept { return mTransitions; } + const auto& getCells() const noexcept { return mCells; } + const auto& getCellsByFirstTransitionIndex() const noexcept { return mCellsByFirstTransitionIndex; } + const auto& getCellsByFirstTransition() const noexcept { return mCellsByFirstTransition; } + Id getNTransitions() const noexcept { return mNTransitions; } + Id getNCells() const noexcept { return mNCells; } + Id getNCellsByFirstTransition() const noexcept { return mNCellsByFirstTransition; } + + private: + void clear() + { + mNTransitions = 0; + mNCells = 0; + mNCellsByFirstTransition = 0; + mTransitions.fill({}); + mCells.fill({}); + mCellsByFirstTransitionIndex.fill(Range{0, 0}); + mCellsByFirstTransition.fill(0); + } + + void fillCellsByTransition() + { + std::array counts{}; + for (Id cellId = 0; cellId < mNCells; ++cellId) { + ++counts[mCells[cellId].firstTransition]; + } + + Id offset = 0; + for (Id transitionId = 0; transitionId < mNTransitions; ++transitionId) { + mCellsByFirstTransitionIndex[transitionId].setFirstEntry(offset); + mCellsByFirstTransitionIndex[transitionId].setEntries(counts[transitionId]); + offset += counts[transitionId]; + } + + std::array cursor{}; + for (Id cellId = 0; cellId < mNCells; ++cellId) { + const Id transitionId = mCells[cellId].firstTransition; + mCellsByFirstTransition[mCellsByFirstTransitionIndex[transitionId].getFirstEntry() + cursor[transitionId]++] = cellId; + } + mNCellsByFirstTransition = offset; + } + + int mMaxLayers{0}; + int mMaxHoles{0}; + Mask mHoleLayerMask{0}; + Id mNTransitions{0}; + Id mNCells{0}; + Id mNCellsByFirstTransition{0}; + std::array mTransitions{}; + std::array mCells{}; + std::array mCellsByFirstTransitionIndex{}; + std::array mCellsByFirstTransition{}; +}; + +} // namespace o2::its + +#endif diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracklet.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracklet.h index e6c9db55198a3..829fe9fa984e4 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracklet.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Tracklet.h @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -17,81 +17,58 @@ #define TRACKINGITS_INCLUDE_TRACKLET_H_ #include "ITStracking/Constants.h" +#include "DataFormatsITS/TimeEstBC.h" #include "ITStracking/Cluster.h" #include "GPUCommonRtypes.h" #include "GPUCommonMath.h" #include "GPUCommonDef.h" #include "GPUCommonLogger.h" -#ifndef GPUCA_GPUCODE_DEVICE -#ifndef GPU_NO_FMT -#include -#include -#endif -#endif - namespace o2::its { +// tracklets are entirely determined by their two cluster idx struct Tracklet final { GPUhdDefault() Tracklet() = default; - GPUhdi() Tracklet(const int, const int, const Cluster&, const Cluster&, short rof0, short rof1); - GPUhdi() Tracklet(const int, const int, float tanL, float phi, short rof0, short rof1); - GPUhdDefault() bool operator==(const Tracklet&) const = default; - GPUhdi() unsigned char isEmpty() const + GPUhdi() Tracklet(const int firstClusterOrderingIndex, const int secondClusterOrderingIndex, + const Cluster& firstCluster, const Cluster& secondCluster, const TimeEstBC& t) + : firstClusterIndex(firstClusterOrderingIndex), + secondClusterIndex(secondClusterOrderingIndex), + tanLambda((firstCluster.zCoordinate - secondCluster.zCoordinate) / (firstCluster.radius - secondCluster.radius)), + phi(o2::gpu::GPUCommonMath::ATan2(firstCluster.yCoordinate - secondCluster.yCoordinate, firstCluster.xCoordinate - secondCluster.xCoordinate)), + mTime(t) {} + + GPUhdi() Tracklet(const int idx0, const int idx1, float tanL, float phi, const TimeEstBC& t) + : firstClusterIndex(idx0), + secondClusterIndex(idx1), + tanLambda(tanL), + phi(phi), + mTime(t) {} + GPUhdi() bool operator<(const Tracklet& o) const noexcept + { + return (firstClusterIndex != o.firstClusterIndex) ? firstClusterIndex < o.firstClusterIndex : secondClusterIndex < o.secondClusterIndex; + } + GPUhdi() bool operator==(const Tracklet& o) const noexcept { - return firstClusterIndex < 0 || secondClusterIndex < 0; + return firstClusterIndex == o.firstClusterIndex && secondClusterIndex == o.secondClusterIndex; } - GPUhdi() auto getMinRof() const noexcept { return o2::gpu::CAMath::Min(rof[0], rof[1]); } - GPUhdi() auto getMaxRof() const noexcept { return o2::gpu::CAMath::Max(rof[0], rof[1]); } - GPUhdi() auto getDeltaRof() const { return rof[1] - rof[0]; } - GPUhdi() auto getSpanRof(const Tracklet& o) const noexcept { return o2::gpu::CAMath::Max(getMaxRof(), o.getMaxRof()) - o2::gpu::CAMath::Min(getMinRof(), o.getMinRof()); } - GPUhdi() unsigned char operator<(const Tracklet&) const; + GPUhdi() bool isCompatible(const Tracklet& o) const { return mTime.isCompatible(o.mTime); } GPUhd() void print() const { - printf("TRKLT: fClIdx:%d fROF:%d sClIdx:%d sROF:%d (DROF:%d) tgl=%f phi=%f\n", firstClusterIndex, rof[0], secondClusterIndex, rof[1], getDeltaRof(), tanLambda, phi); + LOGP(info, "TRKLT: fClIdx:{} sClIdx:{} ts:{}+/-{} TgL={} Phi={}", firstClusterIndex, secondClusterIndex, mTime.getTimeStamp(), mTime.getTimeStampError(), tanLambda, phi); } + GPUhd() auto& getTimeStamp() noexcept { return mTime; } + GPUhd() const auto& getTimeStamp() const noexcept { return mTime; } int firstClusterIndex{constants::UnusedIndex}; int secondClusterIndex{constants::UnusedIndex}; - float tanLambda{-999}; - float phi{-999}; - short rof[2] = {constants::UnusedIndex, constants::UnusedIndex}; + float tanLambda{constants::UnsetValue}; + float phi{constants::UnsetValue}; + TimeEstBC mTime; ClassDefNV(Tracklet, 1); }; -GPUhdi() Tracklet::Tracklet(const int firstClusterOrderingIndex, const int secondClusterOrderingIndex, - const Cluster& firstCluster, const Cluster& secondCluster, short rof0 = -1, short rof1 = -1) - : firstClusterIndex{firstClusterOrderingIndex}, - secondClusterIndex{secondClusterOrderingIndex}, - tanLambda{(firstCluster.zCoordinate - secondCluster.zCoordinate) / - (firstCluster.radius - secondCluster.radius)}, - phi{o2::gpu::GPUCommonMath::ATan2(firstCluster.yCoordinate - secondCluster.yCoordinate, - firstCluster.xCoordinate - secondCluster.xCoordinate)}, - rof{static_cast(rof0), static_cast(rof1)} -{ - // Nothing to do -} - -GPUhdi() Tracklet::Tracklet(const int idx0, const int idx1, float tanL, float phi, short rof0, short rof1) - : firstClusterIndex{idx0}, - secondClusterIndex{idx1}, - tanLambda{tanL}, - phi{phi}, - rof{static_cast(rof0), static_cast(rof1)} -{ - // Nothing to do -} - -GPUhdi() unsigned char Tracklet::operator<(const Tracklet& t) const -{ - if (isEmpty()) { - return false; - } - return true; -} - } // namespace o2::its #endif /* TRACKINGITS_INCLUDE_TRACKLET_H_ */ diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Vertexer.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Vertexer.h index d66bcd6ee2358..eff91e820c56d 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Vertexer.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Vertexer.h @@ -22,11 +22,11 @@ #include #include #include +#include #include #include "ITStracking/Constants.h" -#include "ITStracking/Definitions.h" #include "ITStracking/Configuration.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/VertexerTraits.h" @@ -35,11 +35,11 @@ namespace o2::its { -template +template class Vertexer { - using TimeFrameN = TimeFrame; - using VertexerTraitsN = VertexerTraits; + using TimeFrameN = TimeFrame; + using VertexerTraitsN = VertexerTraits; using LogFunc = std::function; public: @@ -54,11 +54,9 @@ class Vertexer const auto& getParameters() const noexcept { return mVertParams; } void setMemoryPool(std::shared_ptr pool) { mMemoryPool = pool; } - std::vector exportVertices(); - VertexerTraitsN* getTraits() const { return mTraits; }; - float clustersToVertices(LogFunc = [](const std::string& s) { std::cout << s << '\n'; }); void filterMCTracklets(); + void printSummary() const; template void findTracklets(T&&... args) @@ -86,14 +84,16 @@ class Vertexer template void initialiseTimeFrame(T&&... args); + void sortVertices(); + // Utils template float evaluateTask(void (Vertexer::*task)(T...), std::string_view taskName, int iteration, LogFunc& logger, T&&... args); void printEpilog(LogFunc& logger, - const unsigned int trackletN01, const unsigned int trackletN12, - const unsigned selectedN, const unsigned int vertexN, const float initT, - const float trackletT, const float selecT, const float vertexT); + unsigned int trackletN01, unsigned int trackletN12, + unsigned selectedN, unsigned int vertexN, unsigned int totalVertexN, + float initT, float trackletT, float selecT, float vertexT); void setNThreads(int n, std::shared_ptr& arena) { mTraits->setNThreads(n, arena); } @@ -106,21 +106,23 @@ class Vertexer std::vector mVertParams; std::shared_ptr mMemoryPool; - enum State { + enum Steps { Init = 0, Trackleting, - Validating, + Selection, Finding, TruthSeeding, - NStates, + NSteps, }; - State mCurState{Init}; - static constexpr std::array StateNames{"Initialisation", "Tracklet finding", "Tracklet validation", "Vertex finding", "Truth seeding"}; + Steps mCurStep{Init}; + static constexpr std::array StateNames{"Initialisation", "Tracklet finding", "Tracklet selection", "Vertex finding", "Truth seeding"}; + std::vector> mTimingStats; + void addTimingStatCurStep(int iteration, double timeMs); }; -template +template template -float Vertexer::evaluateTask(void (Vertexer::*task)(T...), std::string_view taskName, int iteration, LogFunc& logger, T&&... args) +float Vertexer::evaluateTask(void (Vertexer::*task)(T...), std::string_view taskName, int iteration, LogFunc& logger, T&&... args) { float diff{0.f}; @@ -140,7 +142,7 @@ float Vertexer::evaluateTask(void (Vertexer::*task)(T...), std } logger(sstream.str()); - if (mVertParams[0].SaveTimeBenchmarks) { + if (mVertParams[iteration].SaveTimeBenchmarks) { std::string taskNameStr(taskName); std::transform(taskNameStr.begin(), taskNameStr.end(), taskNameStr.begin(), [](unsigned char c) { return std::tolower(c); }); @@ -148,11 +150,16 @@ float Vertexer::evaluateTask(void (Vertexer::*task)(T...), std if (std::ofstream file{"its_time_benchmarks.txt", std::ios::app}) { file << "vtx:" << iteration << '\t' << taskNameStr << '\t' << diff << '\n'; } + addTimingStatCurStep(iteration, diff); } } else { (this->*task)(std::forward(args)...); } + if (mVertParams[iteration].PrintMemory) { + LOGP(info, "iter:{}:{}: {}", iteration, StateNames[mCurStep], mMemoryPool->asString()); + } + return diff; } diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h index b1422d66e12df..daf8d708e1e23 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h @@ -43,32 +43,23 @@ class MCCompLabel; namespace its { -template +template class VertexerTraits { - using IndexTableUtilsN = IndexTableUtils; - using TimeFrameN = TimeFrame; + using IndexTableUtilsN = IndexTableUtils; + using TimeFrameN = TimeFrame; public: VertexerTraits() = default; virtual ~VertexerTraits() = default; - GPUhdi() static consteval int4 getEmptyBinsRect() - { - return int4{0, 0, 0, 0}; - } - GPUhd() const int4 getBinsRect(const Cluster&, const int, const float, float maxdeltaz, float maxdeltaphi); - GPUhd() static const int4 getBinsRect(const Cluster&, const int, const float, float maxdeltaz, float maxdeltaphi, const IndexTableUtilsN&); - GPUhd() static const int2 getPhiBins(float phi, float deltaPhi, const IndexTableUtilsN&); - GPUhd() const int2 getPhiBins(float phi, float deltaPhi) { return getPhiBins(phi, deltaPhi, mIndexTableUtils); } - // virtual vertexer interface - virtual void initialise(const TrackingParameters& trackingParams, const int iteration = 0); - virtual void computeTracklets(const int iteration = 0); - virtual void computeTrackletMatching(const int iteration = 0); - virtual void computeVertices(const int iteration = 0); + virtual void initialise(const TrackingParameters& trackingParams); + virtual void computeTracklets(const int iteration); + virtual void computeTrackletMatching(const int iteration); + virtual void computeVertices(const int iteration); virtual void adoptTimeFrame(TimeFrameN* tf) noexcept { mTimeFrame = tf; } - virtual void updateVertexingParameters(const std::vector& vrtPar, const TimeFrameGPUParameters& gpuTfPar); + virtual void updateVertexingParameters(const std::vector& vrtPar); // truth tracking void addTruthSeedingVertices(); @@ -84,7 +75,7 @@ class VertexerTraits virtual bool usesMemoryPool() const noexcept { return true; } void setMemoryPool(std::shared_ptr pool) { mMemoryPool = pool; } - static std::pair computeMain(const bounded_vector& elements) + static VertexLabel computeMain(const bounded_vector& elements) { // we only care about the source&event of the tracks, not the trackId auto composeVtxLabel = [](const o2::MCCompLabel& lbl) -> o2::MCCompLabel { @@ -102,6 +93,9 @@ class VertexerTraits elem = key; } } + if (maxCount <= 1) { // need >50% + elem.setFakeFlag(); + } return std::make_pair(elem, static_cast(maxCount) / static_cast(elements.size())); } @@ -112,56 +106,12 @@ class VertexerTraits // Frame related quantities TimeFrameN* mTimeFrame = nullptr; // observer ptr private: + bool skipROF(int iteration, int rof) const; + std::shared_ptr mMemoryPool; std::shared_ptr mTaskArena; - - // debug output - void debugComputeTracklets(int iteration); - void debugComputeTrackletMatching(int iteration); - void debugComputeVertices(int iteration); }; -template -inline void VertexerTraits::initialise(const TrackingParameters& trackingParams, const int iteration) -{ - mTimeFrame->initialise(0, trackingParams, 3, (bool)(!iteration)); // iteration for initialisation must be 0 for correctly resetting the frame, we need to pass the non-reset flag for vertices as well, tho. -} - -template -GPUhdi() const int2 VertexerTraits::getPhiBins(float phi, float dPhi, const IndexTableUtilsN& utils) -{ - return int2{utils.getPhiBinIndex(math_utils::getNormalizedPhi(phi - dPhi)), - utils.getPhiBinIndex(math_utils::getNormalizedPhi(phi + dPhi))}; -} - -template -GPUhdi() const int4 VertexerTraits::getBinsRect(const Cluster& currentCluster, const int layerIndex, - const float directionZIntersection, float maxdeltaz, float maxdeltaphi, - const IndexTableUtilsN& utils) -{ - const float zRangeMin = directionZIntersection - 2 * maxdeltaz; - const float phiRangeMin = currentCluster.phi - maxdeltaphi; - const float zRangeMax = directionZIntersection + 2 * maxdeltaz; - const float phiRangeMax = currentCluster.phi + maxdeltaphi; - - if (zRangeMax < -utils.getLayerZ(layerIndex + 1) || - zRangeMin > utils.getLayerZ(layerIndex + 1) || zRangeMin > zRangeMax) { - return getEmptyBinsRect(); - } - - return int4{o2::gpu::GPUCommonMath::Max(0, utils.getZBinIndex(layerIndex + 1, zRangeMin)), - utils.getPhiBinIndex(math_utils::getNormalizedPhi(phiRangeMin)), - o2::gpu::GPUCommonMath::Min(utils.getNzBins() - 1, utils.getZBinIndex(layerIndex + 1, zRangeMax)), - utils.getPhiBinIndex(math_utils::getNormalizedPhi(phiRangeMax))}; -} - -template -GPUhdi() const int4 VertexerTraits::getBinsRect(const Cluster& currentCluster, const int layerIndex, - const float directionZIntersection, float maxdeltaz, float maxdeltaphi) -{ - return VertexerTraits::getBinsRect(currentCluster, layerIndex, directionZIntersection, maxdeltaz, maxdeltaphi, mIndexTableUtils); -} - } // namespace its } // namespace o2 #endif diff --git a/Detectors/ITSMFT/ITS/tracking/src/Cluster.cxx b/Detectors/ITSMFT/ITS/tracking/src/Cluster.cxx index c4d288bd61777..8c01fb25c545e 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Cluster.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Cluster.cxx @@ -69,9 +69,7 @@ Cluster::Cluster(const int layerIndex, const float3& primaryVertex, const IndexT GPUhd() void Cluster::print() const { -#if !defined(GPUCA_GPUCODE_DEVICE) || (!defined(__OPENCL__) && defined(GPUCA_GPU_DEBUG_PRINT)) printf("Cluster: %f %f %f %f %f %d %d\n", xCoordinate, yCoordinate, zCoordinate, phi, radius, clusterId, indexTableBinIndex); -#endif } TrackingFrameInfo::TrackingFrameInfo(float x, float y, float z, float xTF, float alpha, std::array&& posTF, @@ -83,10 +81,8 @@ TrackingFrameInfo::TrackingFrameInfo(float x, float y, float z, float xTF, float GPUhd() void TrackingFrameInfo::print() const { -#if !defined(GPUCA_GPUCODE_DEVICE) || (!defined(__OPENCL__) && defined(GPUCA_GPU_DEBUG_PRINT)) printf("x: %f y: %f z: %f xTF: %f alphaTF: %f posTF: %f %f covTF: %f %f %f\n", xCoordinate, yCoordinate, zCoordinate, xTrackingFrame, alphaTrackingFrame, positionTrackingFrame[0], positionTrackingFrame[1], covarianceTrackingFrame[0], covarianceTrackingFrame[1], covarianceTrackingFrame[2]); -#endif } diff --git a/Detectors/ITSMFT/ITS/tracking/src/ClusterLines.cxx b/Detectors/ITSMFT/ITS/tracking/src/ClusterLines.cxx index 1a0fa1d3908a4..3e3e1b8b46338 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/ClusterLines.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/ClusterLines.cxx @@ -10,385 +10,208 @@ // or submit itself to any jurisdiction. #include -#include +#include "Framework/Logger.h" #include "ITStracking/ClusterLines.h" -namespace o2 -{ -namespace its +namespace o2::its { -Line::Line(std::array firstPoint, std::array secondPoint) +Line::Line(const Tracklet& tracklet, const Cluster* innerClusters, const Cluster* outerClusters) : mTime(tracklet.mTime) { - for (int index{0}; index < 3; ++index) { - originPoint[index] = firstPoint.data()[index]; - cosinesDirector[index] = secondPoint[index] - firstPoint[index]; - } + const auto& inner = innerClusters[tracklet.firstClusterIndex]; + const auto& outer = outerClusters[tracklet.secondClusterIndex]; + + originPoint = SVector3f(inner.xCoordinate, inner.yCoordinate, inner.zCoordinate); + cosinesDirector = SVector3f(outer.xCoordinate - inner.xCoordinate, + outer.yCoordinate - inner.yCoordinate, + outer.zCoordinate - inner.zCoordinate); + cosinesDirector /= std::sqrt(ROOT::Math::Dot(cosinesDirector, cosinesDirector)); +} - float inverseNorm{1.f / o2::gpu::CAMath::Sqrt(cosinesDirector[0] * cosinesDirector[0] + cosinesDirector[1] * cosinesDirector[1] + - cosinesDirector[2] * cosinesDirector[2])}; - for (int index{0}; index < 3; ++index) { - cosinesDirector[index] *= inverseNorm; - } +float Line::getDistance2FromPoint(const Line& line, const std::array& point) +{ + const SVector3f p(point.data(), 3); + const SVector3f delta = p - line.originPoint; + const float proj = ROOT::Math::Dot(delta, line.cosinesDirector); + const SVector3f residual = delta - proj * line.cosinesDirector; + return ROOT::Math::Dot(residual, residual); } -bool Line::areParallel(const Line& firstLine, const Line& secondLine, const float precision) +float Line::getDistanceFromPoint(const Line& line, const std::array& point) { - float crossProdX{firstLine.cosinesDirector[1] * secondLine.cosinesDirector[2] - - firstLine.cosinesDirector[2] * secondLine.cosinesDirector[1]}; - float module{std::abs(firstLine.cosinesDirector[1] * secondLine.cosinesDirector[2]) + - std::abs(firstLine.cosinesDirector[2] * secondLine.cosinesDirector[1])}; - if (std::abs(crossProdX) > precision * module) { - return false; - } + return std::sqrt(getDistance2FromPoint(line, point)); +} - float crossProdY{-firstLine.cosinesDirector[0] * secondLine.cosinesDirector[2] + - firstLine.cosinesDirector[2] * secondLine.cosinesDirector[0]}; - module = std::abs(firstLine.cosinesDirector[0] * secondLine.cosinesDirector[2]) + - std::abs(firstLine.cosinesDirector[2] * secondLine.cosinesDirector[0]); - if (std::abs(crossProdY) > precision * module) { - return false; +float Line::getDCA2(const Line& firstLine, const Line& secondLine, const float precision) +{ + const SVector3f n = ROOT::Math::Cross(firstLine.cosinesDirector, secondLine.cosinesDirector); + const float norm2 = ROOT::Math::Dot(n, n); + + if (norm2 <= precision * precision) { + // lines are parallel, fall back to point-to-line distance + const SVector3f d = secondLine.originPoint - firstLine.originPoint; + const float proj = ROOT::Math::Dot(d, firstLine.cosinesDirector); + const SVector3f residual = d - proj * firstLine.cosinesDirector; + return ROOT::Math::Dot(residual, residual); } - float crossProdZ = firstLine.cosinesDirector[0] * secondLine.cosinesDirector[1] - - firstLine.cosinesDirector[1] * secondLine.cosinesDirector[0]; - module = std::abs(firstLine.cosinesDirector[0] * secondLine.cosinesDirector[1]) + - std::abs(firstLine.cosinesDirector[1] * secondLine.cosinesDirector[0]); - if (std::abs(crossProdZ) > precision * module) { - return false; - } + const SVector3f delta = secondLine.originPoint - firstLine.originPoint; + const float numerator = ROOT::Math::Dot(delta, n); + return (numerator * numerator) / norm2; +} - return true; +float Line::getDCA(const Line& firstLine, const Line& secondLine, const float precision) +{ + return std::sqrt(getDCA2(firstLine, secondLine, precision)); } -std::array Line::getDCAComponents(const Line& line, const std::array point) +Line::SMatrix3f Line::getDCAComponents(const Line& line, const std::array& point) { - std::array components{0., 0., 0., 0., 0., 0.}; - float cdelta{0.}; - for (int i{0}; i < 3; ++i) { - cdelta -= line.cosinesDirector[i] * (line.originPoint[i] - point[i]); - } + const SVector3f p(point.data(), 3); + const SVector3f delta = line.originPoint - p; + const float proj = ROOT::Math::Dot(line.cosinesDirector, delta); + const SVector3f residual = delta - proj * line.cosinesDirector; + + // symmetric 3x3: diagonal = residual components, off-diagonal = 2D projected distances + SMatrix3f m; + m(0, 0) = residual(0); + m(1, 1) = residual(1); + m(2, 2) = residual(2); + m(0, 1) = std::hypot(m(0, 0), m(1, 1)); + m(0, 2) = std::hypot(m(0, 0), m(2, 2)); + m(1, 2) = std::hypot(m(1, 1), m(2, 2)); + return m; +} - components[0] = line.originPoint[0] - point[0] + line.cosinesDirector[0] * cdelta; - components[3] = line.originPoint[1] - point[1] + line.cosinesDirector[1] * cdelta; - components[5] = line.originPoint[2] - point[2] + line.cosinesDirector[2] * cdelta; - components[1] = o2::gpu::CAMath::Sqrt(components[0] * components[0] + components[3] * components[3]); - components[2] = o2::gpu::CAMath::Sqrt(components[0] * components[0] + components[5] * components[5]); - components[4] = o2::gpu::CAMath::Sqrt(components[3] * components[3] + components[5] * components[5]); +bool Line::isEmpty() const noexcept +{ + return ROOT::Math::Dot(originPoint, originPoint) == 0.f && + ROOT::Math::Dot(cosinesDirector, cosinesDirector) == 0.f; +} - return components; +void Line::print() const +{ + LOGP(info, "\tLine: originPoint = ({}, {}, {}), cosinesDirector = ({}, {}, {}) ts={}+-{}", + originPoint(0), originPoint(1), originPoint(2), + cosinesDirector(0), cosinesDirector(1), cosinesDirector(2), + mTime.getTimeStamp(), mTime.getTimeStampError()); } -ClusterLines::ClusterLines(const int firstLabel, const Line& firstLine, const int secondLabel, const Line& secondLine, - const bool weight) +// Accumulate the weighted normal equation contributions (A matrix and B vector) +// from a single line into the running sums. The covariance is assumed to be +// diagonal and uniform ({1,1,1}) so the weights simplify accordingly. +// The A matrix entry (i,j) = (delta_ij - d_i*d_j) / det, and the B vector +// entry b_i = sum_j d_j*(d_j*o_i - d_i*o_j) / det, where d = cosinesDirector +// and o = originPoint. +void ClusterLines::accumulate(const Line& line) +{ + const ROOT::Math::SVector d(line.cosinesDirector(0), line.cosinesDirector(1), line.cosinesDirector(2)); + const ROOT::Math::SVector o(line.originPoint(0), line.originPoint(1), line.originPoint(2)); + + // == 1 for normalised directors, kept for generality + const double det = ROOT::Math::Dot(d, d); + // A matrix (symmetric): A_ij = (delta_ij * |d|^2 - d_i * d_j) / det + for (int i = 0; i < 3; ++i) { + for (int j = i; j < 3; ++j) { + mAMatrix(i, j) += ((i == j ? det : 0.) - d(i) * d(j)) / det; + } + } + + // B vector: b_i = (d_i * dot(d,o) - |d|^2 * o_i) / det + const double dDotO = ROOT::Math::Dot(d, o); + for (int i = 0; i < 3; ++i) { + mBMatrix(i) += (d(i) * dDotO - det * o(i)) / det; + } +} + +ClusterLines::ClusterLines(const int firstLabel, const Line& firstLine, const int secondLabel, const Line& secondLine) : mTime(firstLine.mTime) { - updateROFPoll(firstLine); - updateROFPoll(secondLine); + mTime += secondLine.mTime; mLabels.push_back(firstLabel); if (secondLabel > 0) { mLabels.push_back(secondLabel); // don't add info in case of beamline used } - std::array covarianceFirst{1., 1., 1.}; - std::array covarianceSecond{1., 1., 1.}; - - // for (int i{0}; i < 6; ++i) { - // mWeightMatrix[i] = firstLine.weightMatrix[i] + secondLine.weightMatrix[i]; - // } - - float determinantFirst = - firstLine.cosinesDirector[2] * firstLine.cosinesDirector[2] * covarianceFirst[0] * covarianceFirst[1] + - firstLine.cosinesDirector[1] * firstLine.cosinesDirector[1] * covarianceFirst[0] * covarianceFirst[2] + - firstLine.cosinesDirector[0] * firstLine.cosinesDirector[0] * covarianceFirst[1] * covarianceFirst[2]; - float determinantSecond = - secondLine.cosinesDirector[2] * secondLine.cosinesDirector[2] * covarianceSecond[0] * covarianceSecond[1] + - secondLine.cosinesDirector[1] * secondLine.cosinesDirector[1] * covarianceSecond[0] * covarianceSecond[2] + - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[0] * covarianceSecond[1] * covarianceSecond[2]; - - mAMatrix[0] = (firstLine.cosinesDirector[2] * firstLine.cosinesDirector[2] * covarianceFirst[1] + - firstLine.cosinesDirector[1] * firstLine.cosinesDirector[1] * covarianceFirst[2]) / - determinantFirst + - (secondLine.cosinesDirector[2] * secondLine.cosinesDirector[2] * covarianceSecond[1] + - secondLine.cosinesDirector[1] * secondLine.cosinesDirector[1] * covarianceSecond[2]) / - determinantSecond; - - mAMatrix[1] = -firstLine.cosinesDirector[0] * firstLine.cosinesDirector[1] * covarianceFirst[2] / determinantFirst - - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[1] * covarianceSecond[2] / determinantSecond; - - mAMatrix[2] = -firstLine.cosinesDirector[0] * firstLine.cosinesDirector[2] * covarianceFirst[1] / determinantFirst - - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[2] * covarianceSecond[1] / determinantSecond; - - mAMatrix[3] = (firstLine.cosinesDirector[2] * firstLine.cosinesDirector[2] * covarianceFirst[0] + - firstLine.cosinesDirector[0] * firstLine.cosinesDirector[0] * covarianceFirst[2]) / - determinantFirst + - (secondLine.cosinesDirector[2] * secondLine.cosinesDirector[2] * covarianceSecond[0] + - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[0] * covarianceSecond[2]) / - determinantSecond; - - mAMatrix[4] = -firstLine.cosinesDirector[1] * firstLine.cosinesDirector[2] * covarianceFirst[0] / determinantFirst - - secondLine.cosinesDirector[1] * secondLine.cosinesDirector[2] * covarianceSecond[0] / determinantSecond; - - mAMatrix[5] = (firstLine.cosinesDirector[1] * firstLine.cosinesDirector[1] * covarianceFirst[0] + - firstLine.cosinesDirector[0] * firstLine.cosinesDirector[0] * covarianceFirst[1]) / - determinantFirst + - (secondLine.cosinesDirector[1] * secondLine.cosinesDirector[1] * covarianceSecond[0] + - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[0] * covarianceSecond[1]) / - determinantSecond; - - mBMatrix[0] = - (firstLine.cosinesDirector[1] * covarianceFirst[2] * (-firstLine.cosinesDirector[1] * firstLine.originPoint[0] + firstLine.cosinesDirector[0] * firstLine.originPoint[1]) + - firstLine.cosinesDirector[2] * covarianceFirst[1] * (-firstLine.cosinesDirector[2] * firstLine.originPoint[0] + firstLine.cosinesDirector[0] * firstLine.originPoint[2])) / - determinantFirst; - - mBMatrix[0] += - (secondLine.cosinesDirector[1] * covarianceSecond[2] * (-secondLine.cosinesDirector[1] * secondLine.originPoint[0] + secondLine.cosinesDirector[0] * secondLine.originPoint[1]) + - secondLine.cosinesDirector[2] * covarianceSecond[1] * - (-secondLine.cosinesDirector[2] * secondLine.originPoint[0] + - secondLine.cosinesDirector[0] * secondLine.originPoint[2])) / - determinantSecond; - - mBMatrix[1] = - (firstLine.cosinesDirector[0] * covarianceFirst[2] * (-firstLine.cosinesDirector[0] * firstLine.originPoint[1] + firstLine.cosinesDirector[1] * firstLine.originPoint[0]) + - firstLine.cosinesDirector[2] * covarianceFirst[0] * (-firstLine.cosinesDirector[2] * firstLine.originPoint[1] + firstLine.cosinesDirector[1] * firstLine.originPoint[2])) / - determinantFirst; - - mBMatrix[1] += - (secondLine.cosinesDirector[0] * covarianceSecond[2] * (-secondLine.cosinesDirector[0] * secondLine.originPoint[1] + secondLine.cosinesDirector[1] * secondLine.originPoint[0]) + - secondLine.cosinesDirector[2] * covarianceSecond[0] * - (-secondLine.cosinesDirector[2] * secondLine.originPoint[1] + - secondLine.cosinesDirector[1] * secondLine.originPoint[2])) / - determinantSecond; - - mBMatrix[2] = - (firstLine.cosinesDirector[0] * covarianceFirst[1] * (-firstLine.cosinesDirector[0] * firstLine.originPoint[2] + firstLine.cosinesDirector[2] * firstLine.originPoint[0]) + - firstLine.cosinesDirector[1] * covarianceFirst[0] * (-firstLine.cosinesDirector[1] * firstLine.originPoint[2] + firstLine.cosinesDirector[2] * firstLine.originPoint[1])) / - determinantFirst; - - mBMatrix[2] += - (secondLine.cosinesDirector[0] * covarianceSecond[1] * (-secondLine.cosinesDirector[0] * secondLine.originPoint[2] + secondLine.cosinesDirector[2] * secondLine.originPoint[0]) + - secondLine.cosinesDirector[1] * covarianceSecond[0] * - (-secondLine.cosinesDirector[1] * secondLine.originPoint[2] + - secondLine.cosinesDirector[2] * secondLine.originPoint[1])) / - determinantSecond; - + accumulate(firstLine); + accumulate(secondLine); computeClusterCentroid(); - // RMS2 + // RMS2: running mean update mRMS2 = Line::getDCAComponents(firstLine, mVertex); - const std::array tmpRMS2Line2 = Line::getDCAComponents(secondLine, mVertex); - std::transform(mRMS2.begin(), mRMS2.end(), tmpRMS2Line2.begin(), mRMS2.begin(), [&](const float a, const float b) { return a + (b - a) / mLabels.size(); }); + const auto tmpRMS2 = Line::getDCAComponents(secondLine, mVertex); + mRMS2 += (tmpRMS2 - mRMS2) * (1.f / static_cast(getSize())); // AvgDistance2 - mAvgDistance2 = std::move(Line::getDistanceFromPoint(firstLine, mVertex) * Line::getDistanceFromPoint(firstLine, mVertex)); - mAvgDistance2 += (Line::getDistanceFromPoint(secondLine, mVertex) * Line::getDistanceFromPoint(secondLine, mVertex) - mAvgDistance2) / mLabels.size(); + mAvgDistance2 = Line::getDistance2FromPoint(firstLine, mVertex); + mAvgDistance2 += (Line::getDistance2FromPoint(secondLine, mVertex) - mAvgDistance2) / (float)getSize(); } -ClusterLines::ClusterLines(const Line& firstLine, const Line& secondLine) +ClusterLines::ClusterLines(gsl::span lineLabels, gsl::span lines) { + if (lineLabels.size() < 2) { + return; + } - std::array covarianceFirst{1., 1., 1.}; - std::array covarianceSecond{1., 1., 1.}; - updateROFPoll(firstLine); - updateROFPoll(secondLine); - // for (int i{0}; i < 6; ++i) { - // mWeightMatrix[i] = firstLine.weightMatrix[i] + secondLine.weightMatrix[i]; - // } - - float determinantFirst = - firstLine.cosinesDirector[2] * firstLine.cosinesDirector[2] * covarianceFirst[0] * covarianceFirst[1] + - firstLine.cosinesDirector[1] * firstLine.cosinesDirector[1] * covarianceFirst[0] * covarianceFirst[2] + - firstLine.cosinesDirector[0] * firstLine.cosinesDirector[0] * covarianceFirst[1] * covarianceFirst[2]; - float determinantSecond = - secondLine.cosinesDirector[2] * secondLine.cosinesDirector[2] * covarianceSecond[0] * covarianceSecond[1] + - secondLine.cosinesDirector[1] * secondLine.cosinesDirector[1] * covarianceSecond[0] * covarianceSecond[2] + - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[0] * covarianceSecond[1] * covarianceSecond[2]; - - mAMatrix[0] = (firstLine.cosinesDirector[2] * firstLine.cosinesDirector[2] * covarianceFirst[1] + - firstLine.cosinesDirector[1] * firstLine.cosinesDirector[1] * covarianceFirst[2]) / - determinantFirst + - (secondLine.cosinesDirector[2] * secondLine.cosinesDirector[2] * covarianceSecond[1] + - secondLine.cosinesDirector[1] * secondLine.cosinesDirector[1] * covarianceSecond[2]) / - determinantSecond; - - mAMatrix[1] = -firstLine.cosinesDirector[0] * firstLine.cosinesDirector[1] * covarianceFirst[2] / determinantFirst - - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[1] * covarianceSecond[2] / determinantSecond; - - mAMatrix[2] = -firstLine.cosinesDirector[0] * firstLine.cosinesDirector[2] * covarianceFirst[1] / determinantFirst - - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[2] * covarianceSecond[1] / determinantSecond; - - mAMatrix[3] = (firstLine.cosinesDirector[2] * firstLine.cosinesDirector[2] * covarianceFirst[0] + - firstLine.cosinesDirector[0] * firstLine.cosinesDirector[0] * covarianceFirst[2]) / - determinantFirst + - (secondLine.cosinesDirector[2] * secondLine.cosinesDirector[2] * covarianceSecond[0] + - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[0] * covarianceSecond[2]) / - determinantSecond; - - mAMatrix[4] = -firstLine.cosinesDirector[1] * firstLine.cosinesDirector[2] * covarianceFirst[0] / determinantFirst - - secondLine.cosinesDirector[1] * secondLine.cosinesDirector[2] * covarianceSecond[0] / determinantSecond; - - mAMatrix[5] = (firstLine.cosinesDirector[1] * firstLine.cosinesDirector[1] * covarianceFirst[0] + - firstLine.cosinesDirector[0] * firstLine.cosinesDirector[0] * covarianceFirst[1]) / - determinantFirst + - (secondLine.cosinesDirector[1] * secondLine.cosinesDirector[1] * covarianceSecond[0] + - secondLine.cosinesDirector[0] * secondLine.cosinesDirector[0] * covarianceSecond[1]) / - determinantSecond; - - mBMatrix[0] = - (firstLine.cosinesDirector[1] * covarianceFirst[2] * (-firstLine.cosinesDirector[1] * firstLine.originPoint[0] + firstLine.cosinesDirector[0] * firstLine.originPoint[1]) + - firstLine.cosinesDirector[2] * covarianceFirst[1] * (-firstLine.cosinesDirector[2] * firstLine.originPoint[0] + firstLine.cosinesDirector[0] * firstLine.originPoint[2])) / - determinantFirst; - - mBMatrix[0] += - (secondLine.cosinesDirector[1] * covarianceSecond[2] * (-secondLine.cosinesDirector[1] * secondLine.originPoint[0] + secondLine.cosinesDirector[0] * secondLine.originPoint[1]) + - secondLine.cosinesDirector[2] * covarianceSecond[1] * - (-secondLine.cosinesDirector[2] * secondLine.originPoint[0] + - secondLine.cosinesDirector[0] * secondLine.originPoint[2])) / - determinantSecond; - - mBMatrix[1] = - (firstLine.cosinesDirector[0] * covarianceFirst[2] * (-firstLine.cosinesDirector[0] * firstLine.originPoint[1] + firstLine.cosinesDirector[1] * firstLine.originPoint[0]) + - firstLine.cosinesDirector[2] * covarianceFirst[0] * (-firstLine.cosinesDirector[2] * firstLine.originPoint[1] + firstLine.cosinesDirector[1] * firstLine.originPoint[2])) / - determinantFirst; - - mBMatrix[1] += - (secondLine.cosinesDirector[0] * covarianceSecond[2] * (-secondLine.cosinesDirector[0] * secondLine.originPoint[1] + secondLine.cosinesDirector[1] * secondLine.originPoint[0]) + - secondLine.cosinesDirector[2] * covarianceSecond[0] * - (-secondLine.cosinesDirector[2] * secondLine.originPoint[1] + - secondLine.cosinesDirector[1] * secondLine.originPoint[2])) / - determinantSecond; - - mBMatrix[2] = - (firstLine.cosinesDirector[0] * covarianceFirst[1] * (-firstLine.cosinesDirector[0] * firstLine.originPoint[2] + firstLine.cosinesDirector[2] * firstLine.originPoint[0]) + - firstLine.cosinesDirector[1] * covarianceFirst[0] * (-firstLine.cosinesDirector[1] * firstLine.originPoint[2] + firstLine.cosinesDirector[2] * firstLine.originPoint[1])) / - determinantFirst; - - mBMatrix[2] += - (secondLine.cosinesDirector[0] * covarianceSecond[1] * (-secondLine.cosinesDirector[0] * secondLine.originPoint[2] + secondLine.cosinesDirector[2] * secondLine.originPoint[0]) + - secondLine.cosinesDirector[1] * covarianceSecond[0] * - (-secondLine.cosinesDirector[1] * secondLine.originPoint[2] + - secondLine.cosinesDirector[2] * secondLine.originPoint[1])) / - determinantSecond; + mLabels.reserve(lineLabels.size()); + mTime = lines[lineLabels[0]].mTime; + for (size_t index = 0; index < lineLabels.size(); ++index) { + const auto lineLabel = lineLabels[index]; + if (index > 0) { + mTime += lines[lineLabel].mTime; + } + mLabels.push_back(lineLabel); + accumulate(lines[lineLabel]); + } computeClusterCentroid(); + if (!mIsValid) { + return; + } + + mRMS2 = Line::getDCAComponents(lines[lineLabels[0]], mVertex); + mAvgDistance2 = Line::getDistance2FromPoint(lines[lineLabels[0]], mVertex); + for (size_t index = 1; index < lineLabels.size(); ++index) { + const auto lineLabel = lineLabels[index]; + const auto tmpRMS2 = Line::getDCAComponents(lines[lineLabel], mVertex); + mRMS2 += (tmpRMS2 - mRMS2) * (1.f / static_cast(index + 1)); + mAvgDistance2 += (Line::getDistance2FromPoint(lines[lineLabel], mVertex) - mAvgDistance2) / static_cast(index + 1); + } } -void ClusterLines::add(const int& lineLabel, const Line& line, const bool& weight) +void ClusterLines::add(const int lineLabel, const Line& line) { + mTime += line.mTime; mLabels.push_back(lineLabel); - updateROFPoll(line); - std::array covariance{1., 1., 1.}; - - // for (int i{0}; i < 6; ++i) { - // mWeightMatrix[i] += line.weightMatrix[i]; - // } - // if(weight) line->GetSigma2P0(covariance); - - double determinant{line.cosinesDirector[2] * line.cosinesDirector[2] * covariance[0] * covariance[1] + - line.cosinesDirector[1] * line.cosinesDirector[1] * covariance[0] * covariance[2] + - line.cosinesDirector[0] * line.cosinesDirector[0] * covariance[1] * covariance[2]}; - - mAMatrix[0] += (line.cosinesDirector[2] * line.cosinesDirector[2] * covariance[1] + - line.cosinesDirector[1] * line.cosinesDirector[1] * covariance[2]) / - determinant; - mAMatrix[1] += -line.cosinesDirector[0] * line.cosinesDirector[1] * covariance[2] / determinant; - mAMatrix[2] += -line.cosinesDirector[0] * line.cosinesDirector[2] * covariance[1] / determinant; - mAMatrix[3] += (line.cosinesDirector[2] * line.cosinesDirector[2] * covariance[0] + - line.cosinesDirector[0] * line.cosinesDirector[0] * covariance[2]) / - determinant; - mAMatrix[4] += -line.cosinesDirector[1] * line.cosinesDirector[2] * covariance[0] / determinant; - mAMatrix[5] += (line.cosinesDirector[1] * line.cosinesDirector[1] * covariance[0] + - line.cosinesDirector[0] * line.cosinesDirector[0] * covariance[1]) / - determinant; - - mBMatrix[0] += (line.cosinesDirector[1] * covariance[2] * - (-line.cosinesDirector[1] * line.originPoint[0] + line.cosinesDirector[0] * line.originPoint[1]) + - line.cosinesDirector[2] * covariance[1] * - (-line.cosinesDirector[2] * line.originPoint[0] + line.cosinesDirector[0] * line.originPoint[2])) / - determinant; - mBMatrix[1] += (line.cosinesDirector[0] * covariance[2] * - (-line.cosinesDirector[0] * line.originPoint[1] + line.cosinesDirector[1] * line.originPoint[0]) + - line.cosinesDirector[2] * covariance[0] * - (-line.cosinesDirector[2] * line.originPoint[1] + line.cosinesDirector[1] * line.originPoint[2])) / - determinant; - mBMatrix[2] += (line.cosinesDirector[0] * covariance[1] * - (-line.cosinesDirector[0] * line.originPoint[2] + line.cosinesDirector[2] * line.originPoint[0]) + - line.cosinesDirector[1] * covariance[0] * - (-line.cosinesDirector[1] * line.originPoint[2] + line.cosinesDirector[2] * line.originPoint[1])) / - determinant; + accumulate(line); computeClusterCentroid(); - mAvgDistance2 += (Line::getDistanceFromPoint(line, mVertex) * Line::getDistanceFromPoint(line, mVertex) - mAvgDistance2) / mLabels.size(); + mAvgDistance2 += (Line::getDistance2FromPoint(line, mVertex) - mAvgDistance2) / (float)getSize(); } void ClusterLines::computeClusterCentroid() { - - double determinant{mAMatrix[0] * (mAMatrix[3] * mAMatrix[5] - mAMatrix[4] * mAMatrix[4]) - - mAMatrix[1] * (mAMatrix[1] * mAMatrix[5] - mAMatrix[4] * mAMatrix[2]) + - mAMatrix[2] * (mAMatrix[1] * mAMatrix[4] - mAMatrix[2] * mAMatrix[3])}; - - if (determinant == 0) { + // Solve the 3x3 symmetric linear system AX = -B using SMatrix inversion. + // Invert() returns false if the matrix is singular or ill-conditioned. + SMatrix3 invA{mAMatrix}; + mIsValid = invA.Invert(); + if (!mIsValid) { return; } - mVertex[0] = -(mBMatrix[0] * (mAMatrix[3] * mAMatrix[5] - mAMatrix[4] * mAMatrix[4]) - - mAMatrix[1] * (mBMatrix[1] * mAMatrix[5] - mAMatrix[4] * mBMatrix[2]) + - mAMatrix[2] * (mBMatrix[1] * mAMatrix[4] - mBMatrix[2] * mAMatrix[3])) / - determinant; - mVertex[1] = -(mAMatrix[0] * (mBMatrix[1] * mAMatrix[5] - mBMatrix[2] * mAMatrix[4]) - - mBMatrix[0] * (mAMatrix[1] * mAMatrix[5] - mAMatrix[4] * mAMatrix[2]) + - mAMatrix[2] * (mAMatrix[1] * mBMatrix[2] - mAMatrix[2] * mBMatrix[1])) / - determinant; - mVertex[2] = -(mAMatrix[0] * (mAMatrix[3] * mBMatrix[2] - mBMatrix[1] * mAMatrix[4]) - - mAMatrix[1] * (mAMatrix[1] * mBMatrix[2] - mBMatrix[1] * mAMatrix[2]) + - mBMatrix[0] * (mAMatrix[1] * mAMatrix[4] - mAMatrix[2] * mAMatrix[3])) / - determinant; + SVector3 result = invA * mBMatrix; + mVertex[0] = static_cast(-result(0)); + mVertex[1] = static_cast(-result(1)); + mVertex[2] = static_cast(-result(2)); } -bool ClusterLines::operator==(const ClusterLines& rhs) const +bool ClusterLines::operator==(const ClusterLines& rhs) const noexcept { - bool retval{true}; - for (auto i{0}; i < 6; ++i) { - retval &= this->mRMS2[i] == rhs.mRMS2[i]; - } - for (auto i{0}; i < 3; ++i) { - retval &= this->mVertex[i] == rhs.mVertex[i]; - } - if (this->mLabels.size() != rhs.mLabels.size()) { - retval = false; - } else { - for (size_t i{0}; i < this->mLabels.size(); ++i) { - retval &= this->mLabels[i] == rhs.mLabels[i]; - } - } - return retval && this->mAvgDistance2 == rhs.mAvgDistance2; -} - -GPUhdi() void ClusterLines::updateROFPoll(const Line& line) -{ - // option 1: Boyer-Moore voting for rof label - if (mROFWeight == 0) { - mROF = line.getMinROF(); - mROFWeight = 1; - } else { - if (mROF == line.getMinROF()) { - mROFWeight++; - } else { - mROFWeight--; - } - } - - // option 2 - // if (mROF == -1) { - // mROF = line.getMinROF(); - // } else { - // if (line.getMinROF() < mROF) { - // mROF = line.getMinROF(); - // } - // } + return mRMS2 == rhs.mRMS2 && + mVertex == rhs.mVertex && + mLabels == rhs.mLabels && + mAvgDistance2 == rhs.mAvgDistance2; } -} // namespace its -} // namespace o2 +} // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx b/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx index 202dc87f04237..f07a8f3394c05 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Configuration.cxx @@ -24,8 +24,9 @@ using namespace o2::its; std::string TrackingParameters::asString() const { - std::string str = std::format("NZb:{} NPhB:{} NROFIt:{} DRof:{} PerVtx:{} DropFail:{} ClSh:{} TtklMinPt:{:.2f} MinCl:{}", - ZBins, PhiBins, nROFsPerIterations, DeltaROF, PerPrimaryVertexProcessing, DropTFUponFailure, ClusterSharing, TrackletMinPt, MinTrackLength); + std::string str = std::format("NZb:{} NPhB:{} PerVtx:{} DropFail:{} TtklMinPt:{:.2f} MinCl:{}", ZBins, PhiBins, PerPrimaryVertexProcessing, DropTFUponFailure, TrackletMinPt, MinTrackLength); + auto isSet = [](auto e) { return e >= 0; }; + auto isAnySet = [&isSet](auto v) { return !v.empty() && std::any_of(v.begin(), v.end(), isSet); }; bool first = true; for (int il = NLayers; il >= MinTrackLength; il--) { int slot = NLayers - il; @@ -37,9 +38,26 @@ std::string TrackingParameters::asString() const str += std::format("L{}:{:.2f} ", il, MinPt[slot]); } } - str += " SystErrY/Z:"; - for (size_t i = 0; i < SystErrorY2.size(); i++) { - str += std::format("{:.2e}/{:.2e} ", SystErrorY2[i], SystErrorZ2[i]); + if (isAnySet(SystErrorY2) || isAnySet(SystErrorZ2)) { + str += " SystErrY/Z:"; + for (size_t i = 0; i < SystErrorY2.size(); i++) { + str += std::format("{:.2e}/{:.2e} ", SystErrorY2[i], SystErrorZ2[i]); + } + } + if (isAnySet(AddTimeError)) { + str += " AddTimeError:"; + for (unsigned int i : AddTimeError) { + str += std::format("{} ", i); + } + } + if (SharedMaxClusters) { + str += std::format(" ShaMaxCls:{} ", SharedMaxClusters); + } + if (AllowSharingFirstCluster) { + str += std::format(" ShaClsDPhi:{} ShaClsDEta:{} ShaClsSign:{}", SharedClusterMaxDeltaPhi, SharedClusterMaxDeltaEta, SharedClusterOppositeSign); + } + if (MaxHoles) { + str += std::format(" MaxHoles:{} HoleMask:{}", MaxHoles, HoleLayerMask.asString()); } if (std::numeric_limits::max() != MaxMemory) { str += std::format(" MemLimit {:.2f} GB", double(MaxMemory) / constants::GB); @@ -49,7 +67,8 @@ std::string TrackingParameters::asString() const std::string VertexingParameters::asString() const { - std::string str = std::format("NZb:{} NPhB:{} DRof:{} ClsCont:{} MaxTrkltCls:{} ZCut:{} PhCut:{}", ZBins, PhiBins, deltaRof, clusterContributorsCut, maxTrackletsPerCluster, zCut, phiCut); + std::string str = std::format("NZb:{} NPhB:{} MinVtxCont:{} SupLowMultDebris:{} MaxTrkltCls:{} ZCut:{} PhCut:{} PairCut:{} ClCut:{} SeedRad:{}x{}", + ZBins, PhiBins, clusterContributorsCut, suppressLowMultDebris, maxTrackletsPerCluster, zCut, phiCut, pairCut, clusterCut, seedMemberRadiusTime, seedMemberRadiusZ); if (std::numeric_limits::max() != MaxMemory) { str += std::format(" MemLimit {:.2f} GB", double(MaxMemory) / constants::GB); } @@ -126,24 +145,21 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode trackParams[3].MinTrackLength = 4; trackParams[3].TrackletMinPt = 0.1f; trackParams[3].CellDeltaTanLambdaSigma *= 4.; - trackParams[3].DeltaROF = 0; // UPC specific setting } - for (size_t ip = 0; ip < trackParams.size(); ip++) { + for (int ip = 0; ip < (int)trackParams.size(); ip++) { auto& param = trackParams[ip]; param.ZBins = 64; param.PhiBins = 32; - param.CellsPerClusterLimit = 1.e3f; - param.TrackletsPerClusterLimit = 1.e3f; // check if something was overridden via configurable params - if (ip < tc.MaxIter) { + if (ip < constants::MaxIter) { if (tc.startLayerMask[ip] > 0) { - trackParams[2].StartLayerMask = tc.startLayerMask[ip]; + param.StartLayerMask = tc.startLayerMask[ip]; } if (tc.minTrackLgtIter[ip] > 0) { param.MinTrackLength = tc.minTrackLgtIter[ip]; } for (int ilg = tc.MaxTrackLength; ilg >= tc.MinTrackLength; ilg--) { - int lslot0 = (tc.MaxTrackLength - ilg), lslot = lslot0 + ip * (tc.MaxTrackLength - tc.MinTrackLength + 1); + int lslot0 = (tc.MaxTrackLength - ilg), lslot = lslot0 + (ip * (tc.MaxTrackLength - tc.MinTrackLength + 1)); if (tc.minPtIterLgt[lslot] > 0.) { param.MinPt[lslot0] = tc.minPtIterLgt[lslot]; } @@ -164,19 +180,20 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode trackParams[0].PVres = 1.e5f; trackParams[0].MaxChi2ClusterAttachment = 60.; trackParams[0].MaxChi2NDF = 40.; - trackParams[0].TrackletsPerClusterLimit = 100.; - trackParams[0].CellsPerClusterLimit = 100.; } else { LOGP(fatal, "Unsupported ITS tracking mode {} ", toString(mode)); } - float bFactor = std::abs(o2::base::Propagator::Instance()->getNominalBz()) / 5.0066791; - float bFactorTracklets = bFactor < 0.01 ? 1. : bFactor; // for tracklets only - int nROFsPerIterations = tc.nROFsPerIterations > 0 ? tc.nROFsPerIterations : -1; - - if (tc.nOrbitsPerIterations > 0) { - /// code to be used when the number of ROFs per orbit is known, this gets priority over the number of ROFs per iteration + for (auto& param : trackParams) { + param.PassFlags.reset(); } + trackParams[0].PassFlags.set(IterationStep::FirstPass, IterationStep::RebuildClusterLUT); + if (trackParams.size() > 3 && tc.doUPCIteration) { + trackParams[3].PassFlags.set(IterationStep::UseUPCMask, IterationStep::RebuildClusterLUT, IterationStep::SelectUPCVertices); + } + + float bFactor = std::abs(o2::base::Propagator::Instance()->getNominalBz()) / 5.0066791f; + float bFactorTracklets = bFactor < 0.01f ? 1.f : bFactor; // for tracklets only // global parameters set for every iteration for (auto& p : trackParams) { @@ -189,7 +206,7 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode p.ReseedIfShorter = tc.reseedIfShorter; p.RepeatRefitOut = tc.repeatRefitOut; p.ShiftRefToCluster = tc.shiftRefToCluster; - p.createArtefactLabels = tc.createArtefactLabels; + p.CreateArtefactLabels = tc.createArtefactLabels; p.PrintMemory = tc.printMemory; p.MaxMemory = tc.maxMemory; @@ -197,6 +214,14 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode p.SaveTimeBenchmarks = tc.saveTimeBenchmarks; p.FataliseUponFailure = tc.fataliseUponFailure; p.AllowSharingFirstCluster = tc.allowSharingFirstCluster; + p.SharedClusterMaxDeltaPhi = tc.sharedClusterMaxDeltaPhi; + p.SharedClusterMaxDeltaEta = tc.sharedClusterMaxDeltaEta; + p.SharedClusterOppositeSign = tc.sharedClusterOppositeSign; + const auto iter = &p - trackParams.data(); + if (iter < constants::MaxIter) { + p.MaxHoles = tc.maxHolesIter[iter]; + p.HoleLayerMask = tc.holeLayerMaskIter[iter]; + } if (tc.useMatCorrTGeo) { p.CorrType = o2::base::PropagatorImpl::MatCorrType::USEMatCorrTGeo; @@ -212,7 +237,9 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode p.SystErrorZ2[i] = tc.sysErrZ2[i] > 0 ? tc.sysErrZ2[i] : p.SystErrorZ2[i]; } } - p.DeltaROF = tc.deltaRof; + for (int i{0}; i < 7; ++i) { + p.AddTimeError[i] = tc.addTimeError[i]; + } p.DoUPCIteration = tc.doUPCIteration; p.MaxChi2ClusterAttachment = tc.maxChi2ClusterAttachment > 0 ? tc.maxChi2ClusterAttachment : p.MaxChi2ClusterAttachment; p.MaxChi2NDF = tc.maxChi2NDF > 0 ? tc.maxChi2NDF : p.MaxChi2NDF; @@ -222,32 +249,11 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode p.NSigmaCut *= tc.nSigmaCut > 0 ? tc.nSigmaCut : 1.f; p.CellDeltaTanLambdaSigma *= tc.deltaTanLres > 0 ? tc.deltaTanLres : 1.f; p.TrackletMinPt *= tc.minPt > 0 ? tc.minPt : 1.f; - p.nROFsPerIterations = nROFsPerIterations; p.PerPrimaryVertexProcessing = tc.perPrimaryVertexProcessing; for (int iD{0}; iD < 3; ++iD) { p.Diamond[iD] = tc.diamondPos[iD]; } p.UseDiamond = tc.useDiamond; - if (tc.useTrackFollower > 0) { - p.UseTrackFollower = true; - // Bit 0: Allow for mixing of top&bot extension --> implies Bits 1&2 set - // Bit 1: Allow for top extension - // Bit 2: Allow for bot extension - p.UseTrackFollowerMix = ((tc.useTrackFollower & (1 << 0)) != 0); - p.UseTrackFollowerTop = ((tc.useTrackFollower & (1 << 1)) != 0); - p.UseTrackFollowerBot = ((tc.useTrackFollower & (1 << 2)) != 0); - p.TrackFollowerNSigmaCutZ = tc.trackFollowerNSigmaZ; - p.TrackFollowerNSigmaCutPhi = tc.trackFollowerNSigmaPhi; - } - if (tc.cellsPerClusterLimit >= 0) { - p.CellsPerClusterLimit = tc.cellsPerClusterLimit; - } - if (tc.trackletsPerClusterLimit >= 0) { - p.TrackletsPerClusterLimit = tc.trackletsPerClusterLimit; - } - if (tc.findShortTracks >= 0) { - p.FindShortTracks = tc.findShortTracks; - } } if (trackParams.size() > tc.nIterations) { @@ -260,52 +266,59 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode std::vector TrackingMode::getVertexingParameters(TrackingMode::Type mode) { const auto& vc = o2::its::VertexerParamConfig::Instance(); - std::vector vertParams; - if (mode == TrackingMode::Async) { - vertParams.resize(2); // The number of actual iterations will be set as a configKeyVal to allow for pp/PbPb choice - vertParams[1].phiCut = 0.015f; - vertParams[1].tanLambdaCut = 0.015f; - vertParams[1].vertPerRofThreshold = 0; - vertParams[1].deltaRof = 0; - } else if (mode == TrackingMode::Sync) { - vertParams.resize(1); - } else if (mode == TrackingMode::Cosmics) { - vertParams.resize(1); - } else { - LOGP(fatal, "Unsupported ITS vertexing mode {} ", toString(mode)); + std::vector vertParams(2); // The number of actual iterations will be set as a configKeyVal to allow for pp/PbPb choice + for (auto& param : vertParams) { + param.PassFlags.reset(); } + vertParams[0].PassFlags.set(IterationStep::FirstPass, IterationStep::ResetVertices); + vertParams[1].PassFlags.set(IterationStep::SkipROFsAboveThreshold, IterationStep::MarkVerticesAsUPC); // global parameters set for every iteration for (auto& p : vertParams) { + p.vertPerRofThreshold = vc.vertPerRofThreshold; p.SaveTimeBenchmarks = vc.saveTimeBenchmarks; p.PrintMemory = vc.printMemory; p.MaxMemory = vc.maxMemory; p.DropTFUponFailure = vc.dropTFUponFailure; - p.nIterations = vc.nIterations; - p.deltaRof = vc.deltaRof; - p.allowSingleContribClusters = vc.allowSingleContribClusters; - p.trackletSigma = vc.trackletSigma; + p.NSigmaCut = vc.nSigmaCut; p.maxZPositionAllowed = vc.maxZPositionAllowed; p.clusterContributorsCut = vc.clusterContributorsCut; + p.suppressLowMultDebris = vc.suppressLowMultDebris; + p.seedMemberRadiusTime = vc.seedMemberRadiusTime; + p.seedMemberRadiusZ = vc.seedMemberRadiusZ; p.phiSpan = vc.phiSpan; p.nThreads = vc.nThreads; p.ZBins = vc.ZBins; p.PhiBins = vc.PhiBins; - p.useTruthSeeding = vc.useTruthSeeding; - p.outputContLabels = vc.outputContLabels; + p.maxTrackletsPerCluster = vc.maxTrackletsPerCluster; + p.zCut = vc.zCut; + p.phiCut = vc.phiCut; + p.pairCut = vc.pairCut; + p.clusterCut = vc.clusterCut; + p.coarseZWindow = vc.coarseZWindow; + p.seedDedupZCut = vc.seedDedupZCut; + p.refitDedupZCut = vc.refitDedupZCut; + p.duplicateZCut = vc.duplicateZCut; + p.finalSelectionZCut = vc.finalSelectionZCut; + p.duplicateDistance2Cut = vc.duplicateDistance2Cut; + p.tanLambdaCut = vc.tanLambdaCut; + } + + if (mode == TrackingMode::Async) { + // relax for UPC iteration + vertParams[1].phiCut = 0.015f; + vertParams[1].tanLambdaCut = 0.015f; + vertParams[1].maxTrackletsPerCluster = 2000; + } else if (mode == TrackingMode::Sync || TrackingMode::Cosmics) { + vertParams.resize(1); + } else { + LOGP(fatal, "Unsupported ITS vertexing mode {} ", toString(mode)); + } + + if (vertParams.size() > vc.nIterations) { + vertParams.resize(vc.nIterations); } - // set for now outside to not disturb status quo - vertParams[0].vertNsigmaCut = vc.vertNsigmaCut; - vertParams[0].vertRadiusSigma = vc.vertRadiusSigma; - vertParams[0].maxTrackletsPerCluster = vc.maxTrackletsPerCluster; - vertParams[0].lowMultBeamDistCut = vc.lowMultBeamDistCut; - vertParams[0].zCut = vc.zCut; - vertParams[0].phiCut = vc.phiCut; - vertParams[0].pairCut = vc.pairCut; - vertParams[0].clusterCut = vc.clusterCut; - vertParams[0].histPairCut = vc.histPairCut; - vertParams[0].tanLambdaCut = vc.tanLambdaCut; return vertParams; } diff --git a/Detectors/ITSMFT/ITS/tracking/src/FastMultEst.cxx b/Detectors/ITSMFT/ITS/tracking/src/FastMultEst.cxx new file mode 100644 index 0000000000000..cfbfdd8a9150e --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/src/FastMultEst.cxx @@ -0,0 +1,208 @@ +// 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 FastMultEst.h +/// \brief Fast multiplicity estimator for ITS +/// \author ruben.shahoyan@cern.ch + +#include "ITStracking/FastMultEst.h" +#include "Framework/Logger.h" +#include +#include +#include +#include + +using namespace o2::its; + +namespace +{ + +// Convert trigger IR to ROF index on a given layer using LayerTiming +int findROFForIR(const o2::InteractionRecord& ir, + const o2::InteractionRecord& tfStartIR, + const LayerTiming& layerTiming) +{ + // Convert IR to BC-from-TF-start, which is the time base expected by LayerTiming. + const int64_t bcFromTFStart = ir.differenceInBC(tfStartIR); + if (bcFromTFStart < 0) { + return -1; + } + return layerTiming.getROF(static_cast(bcFromTFStart)); +} + +template +void enableCompatibleROFs(int baseLayer, + int baseRof, + const typename o2::its::ROFOverlapTable::View& overlapView, + o2::its::ROFMaskTable& sel) +{ + sel.setROFEnabled(baseLayer, baseRof); + for (int layer = 0; layer < NLayers; ++layer) { + if (layer == baseLayer) { + continue; + } + const auto& overlap = overlapView.getOverlap(baseLayer, layer, baseRof); + if (overlap.getEntries() > 0) { + sel.setROFsEnabled(layer, overlap.getFirstEntry(), overlap.getEntries()); + } + } +} + +template +std::vector buildMultiplicityCounts(const std::array, NLayers>& rofs, + const std::array, NLayers>& clus, + bool doStaggering, + int multLayer) +{ + std::vector multCounts; + if (doStaggering) { + multCounts.resize(rofs[multLayer].size()); + for (size_t iRof = 0; iRof < rofs[multLayer].size(); ++iRof) { + multCounts[iRof] = rofs[multLayer][iRof].getNEntries(); + } + return multCounts; + } + + static const o2::itsmft::ChipMappingITS chipMapping; + multCounts.resize(rofs[0].size(), 0); + for (size_t iRof = 0; iRof < rofs[0].size(); ++iRof) { + for (const auto& cluster : rofs[0][iRof].getROFData(clus[0])) { + if (chipMapping.getLayer(cluster.getSensorID()) == multLayer) { + ++multCounts[iRof]; + } + } + } + return multCounts; +} +} // namespace + +bool FastMultEst::sSeedSet = false; + +///______________________________________________________ +FastMultEst::FastMultEst() +{ + if (!sSeedSet && FastMultEstConfig::Instance().cutRandomFraction > 0.f) { + sSeedSet = true; + if (FastMultEstConfig::Instance().randomSeed > 0) { + gRandom->SetSeed(FastMultEstConfig::Instance().randomSeed); + } else if (FastMultEstConfig::Instance().randomSeed < 0) { + gRandom->SetSeed(std::time(nullptr) % 0xffff); + } + } +} + +///______________________________________________________ +/// count clusters on the configured multiplicity layer +int FastMultEst::countClustersOnLayer(const gsl::span& clusters) const +{ + const int targetLayer = std::clamp(FastMultEstConfig::Instance().cutMultClusLayer, 0, NLayers - 1); + int count = 0; + int lr = FastMultEst::NLayers - 1; + int nchAcc = o2::itsmft::ChipMappingITS::getNChips() - o2::itsmft::ChipMappingITS::getNChipsPerLr(lr); + for (int i = clusters.size(); i--;) { // profit from clusters being ordered in chip increasing order + while (clusters[i].getSensorID() < nchAcc) { + assert(lr >= 0); + nchAcc -= o2::itsmft::ChipMappingITS::getNChipsPerLr(--lr); + } + if (lr == targetLayer) { + ++count; + } + } + return count; +} + +///______________________________________________________ +/// find multiplicity for given number of clusters per layer +float FastMultEst::processNoiseFree(int nClusters) +{ + // Single-layer regime: estimate multiplicity from one configured layer only. + const auto& conf = FastMultEstConfig::Instance(); + const int layer = std::clamp(conf.cutMultClusLayer, 0, NLayers - 1); + const float acc = conf.accCorr[layer]; + nLayersUsed = nClusters > 0 ? 1 : 0; + noisePerChip = 0.f; + chi2 = 0.f; + cov[0] = cov[1] = cov[2] = 0.f; + if (nLayersUsed == 0 || acc <= 0.f) { + mult = -1.f; + return -1.f; + } + mult = nClusters / acc; + return mult > 0 ? mult : 0; +} + +///______________________________________________________ +/// find multiplicity for given number of clusters per layer with mean noise imposed +float FastMultEst::processNoiseImposed(int nClusters) +{ + // Single-layer regime with imposed noise subtraction. + const auto& conf = FastMultEstConfig::Instance(); + const int layer = std::clamp(conf.cutMultClusLayer, 0, NLayers - 1); + const float acc = conf.accCorr[layer]; + const float nch = static_cast(o2::itsmft::ChipMappingITS::getNChipsPerLr(layer)); + nLayersUsed = nClusters > 0 ? 1 : 0; + chi2 = 0.f; + cov[0] = cov[1] = cov[2] = 0.f; + if (nLayersUsed == 0 || acc <= 0.f) { + mult = -1.f; + return -1.f; + } + mult = (nClusters - noisePerChip * nch) / acc; + return mult; +} + +int FastMultEst::selectROFs(const std::array, NLayers>& rofs, + const std::array, NLayers>& clus, + const gsl::span trig, + uint32_t firstTForbit, + bool doStaggering, + const ROFOverlapTableN::View& overlapView, + ROFMaskTableN& sel) +{ + const auto& multEstConf = FastMultEstConfig::Instance(); // parameters for mult estimation and cuts + const int selectionLayer = multEstConf.isMultCutRequested() ? std::clamp(multEstConf.cutMultClusLayer, 0, NLayers - 1) : overlapView.getClock(); + const auto multCounts = buildMultiplicityCounts(rofs, clus, doStaggering, selectionLayer); + const int selectionRofCount = doStaggering ? static_cast(rofs[selectionLayer].size()) : static_cast(rofs[0].size()); + + sel.resetMask(); + lastRandomSeed = gRandom->GetSeed(); + const o2::InteractionRecord tfStartIR{0, firstTForbit}; + // mask ROFs which are not good from the multiplicity selection (if any) POV + struct ROFStatus { + int entry = 0, priority = 0; + }; + std::vector selROFs; + selROFs.reserve(selectionRofCount); + bool selmult = multEstConf.isMultCutRequested(); + for (int selectionRof = 0; selectionRof < selectionRofCount; ++selectionRof) { + selROFs.emplace_back(selectionRof, (selmult && !multEstConf.isPassingMultCut(process(multCounts[selectionRof]))) ? -1 : 0); + } + if (!trig.empty() && multEstConf.preferTriggered) { + const auto& selectionLayerTiming = overlapView.getLayer(selectionLayer); + for (const auto& trigger : trig) { + const int selectionRof = findROFForIR(trigger.ir, tfStartIR, selectionLayerTiming); + if (selectionRof < 0 || selROFs[selectionRof].priority < 0) { + continue; + } + selROFs[selectionRof].priority++; // increment trigger counter + } + sort(selROFs.begin(), selROFs.end(), [](const ROFStatus& a, const ROFStatus& b) { return a.priority > b.priority; }); // order in number of triggers, masked will go to the end + } + int nsel = 0; + for (auto& rof : selROFs) { + if (rof.priority >= 0 && (multEstConf.cutRandomFraction <= 0.f || (gRandom->Rndm() > multEstConf.cutRandomFraction))) { + enableCompatibleROFs(selectionLayer, rof.entry, overlapView, sel); + nsel++; + } + } + LOGP(debug, "NSel = {} of {} rofs on layer {} Seeds: before {} after {}", nsel, selectionRofCount, selectionLayer, lastRandomSeed, gRandom->GetSeed()); + return nsel; +} diff --git a/Detectors/ITSMFT/ITS/reconstruction/src/FastMultEstConfig.cxx b/Detectors/ITSMFT/ITS/tracking/src/FastMultEstConfig.cxx similarity index 94% rename from Detectors/ITSMFT/ITS/reconstruction/src/FastMultEstConfig.cxx rename to Detectors/ITSMFT/ITS/tracking/src/FastMultEstConfig.cxx index 63c43cf26ba15..1568d8ed9f9fb 100644 --- a/Detectors/ITSMFT/ITS/reconstruction/src/FastMultEstConfig.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/FastMultEstConfig.cxx @@ -9,7 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "ITSReconstruction/FastMultEstConfig.h" +#include "ITStracking/FastMultEstConfig.h" #include "TRandom.h" O2ParamImpl(o2::its::FastMultEstConfig); diff --git a/Detectors/ITSMFT/ITS/tracking/src/IndexTableUtils.cxx b/Detectors/ITSMFT/ITS/tracking/src/IndexTableUtils.cxx deleted file mode 100644 index 7152640e9a70f..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/src/IndexTableUtils.cxx +++ /dev/null @@ -1,49 +0,0 @@ -// 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 IndexTableUtils.cxx -/// \brief -/// - -#include "ITStracking/IndexTableUtils.h" - -namespace o2 -{ -namespace its -{ - -const std::vector> index_table_utils::selectClusters( - const std::array& indexTable, - const std::array& selectedBinsRect) -{ - std::vector> filteredBins{}; - - int phiBinsNum{selectedBinsRect[3] - selectedBinsRect[1] + 1}; - - if (phiBinsNum < 0) { - phiBinsNum += constants::index_table::PhiBins; - } - - filteredBins.reserve(phiBinsNum); - - for (int iPhiBin{selectedBinsRect[1]}, iPhiCount{0}; iPhiCount < phiBinsNum; - iPhiBin = ++iPhiBin == constants::index_table::PhiBins ? 0 : iPhiBin, iPhiCount++) { - - const int firstBinIndex{index_table_utils::getBinIndex(selectedBinsRect[0], iPhiBin)}; - - filteredBins.emplace_back(indexTable[firstBinIndex], - countRowSelectedBins(indexTable, iPhiBin, selectedBinsRect[0], selectedBinsRect[2])); - } - - return filteredBins; -} -} // namespace its -} // namespace o2 diff --git a/Detectors/ITSMFT/ITS/tracking/src/LineVertexerHelpers.cxx b/Detectors/ITSMFT/ITS/tracking/src/LineVertexerHelpers.cxx new file mode 100644 index 0000000000000..cbb8d52571ec9 --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/src/LineVertexerHelpers.cxx @@ -0,0 +1,1036 @@ +// Copyright 2019-2026 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. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ITStracking/Constants.h" +#include "ITStracking/MathUtils.h" +#include "ITStracking/LineVertexerHelpers.h" + +namespace o2::its::line_vertexer +{ +namespace +{ +using SymMatrix3 = ROOT::Math::SMatrix>; +using SVector3 = ROOT::Math::SVector; + +constexpr float TukeyC = 4.685f; +constexpr float TukeyC2 = TukeyC * TukeyC; +constexpr float InitialScale2 = 5.f; +constexpr float MinScale2 = 1.f; +constexpr float MedianToSigma = 1.4826f; +constexpr float VertexShiftZTol = 0.01f; +constexpr float VertexShiftR2Tol = 1.e-4f; +constexpr int MaxFitIterations = 10; +constexpr int MaxSeedsPerCluster = 32; +constexpr float MinRelativePeakSupport = 0.1f; +constexpr int MaxHistogramBins = 0x7fff; +constexpr float TieTolerance = 1e-5f; + +struct LineRef { + LineRef(const Line& line, const int index, const float beamX, const float beamY, const float maxZ) : lineIndex(index) + { + const auto symTime = line.mTime.makeSymmetrical(); + tCenter = symTime.getTimeStamp(); + tHalfWidth = symTime.getTimeStampError(); + const auto dx = line.originPoint(0) - beamX; + const auto dy = line.originPoint(1) - beamY; + const auto ux = line.cosinesDirector(0); + const auto uy = line.cosinesDirector(1); + const auto uz = line.cosinesDirector(2); + const auto den = math_utils::SqSum(ux, uy); + if (den <= constants::Tolerance) { + lineIndex = constants::UnusedIndex; + return; + } + const auto s0 = -((dx * ux) + (dy * uy)) / den; + const auto xb = dx + (s0 * ux); + const auto yb = dy + (s0 * uy); + zBeam = line.originPoint(2) + s0 * uz; + if (!std::isfinite(zBeam) || o2::gpu::CAMath::Abs(zBeam) > maxZ) { + lineIndex = constants::UnusedIndex; + } + } + bool isDead() const noexcept { return lineIndex == constants::UnusedIndex; } + + int lineIndex = constants::UnusedIndex; + float zBeam = 0.f; + float tCenter = 0.f; + float tHalfWidth = 0.f; +}; + +struct VertexSeed { + explicit VertexSeed(const std::shared_ptr& mr) : contributors(mr.get()), assigned(mr.get()) {} + + std::array vertex = {}; + TimeEstBC time; + float scale2 = InitialScale2; + bounded_vector contributors; + bounded_vector assigned; + bool valid = false; + bool isUsableSeed() const noexcept + { + return valid && contributors.size() >= 2; + } +}; + +void compactSeeds(bounded_vector& seeds) +{ + seeds.erase(std::remove_if(seeds.begin(), seeds.end(), [](const VertexSeed& seed) { + return !seed.isUsableSeed(); + }), + seeds.end()); +} + +struct Histogram2D { + explicit Histogram2D(const std::shared_ptr& mr) : bins(mr.get()) {} + + int nTimeBins = 0; + int nZBins = 0; + float timeMin = 0.f; + float zMin = 0.f; + float timeBinSize = 1.f; + float zBinSize = 1.f; + bounded_vector bins; + + int getIndex(const int tBin, const int zBin) const noexcept + { + return (tBin * nZBins) + zBin; + } + + std::pair decodeIndex(const int index) const noexcept + { + return {index / nZBins, index % nZBins}; + } + + int getTimeBin(const float time) const noexcept + { + if (time < timeMin) { + return -1; + } + const auto bin = static_cast((time - timeMin) / timeBinSize); + return (bin >= 0 && bin < nTimeBins) ? bin : -1; + } + + int getZBin(const float z) const noexcept + { + if (z < zMin) { + return -1; + } + const auto bin = static_cast((z - zMin) / zBinSize); + return (bin >= 0 && bin < nZBins) ? bin : -1; + } + + void fill(const float time, const float z, const float weight) noexcept + { + const auto tBin = getTimeBin(time); + const auto zBin = getZBin(z); + if (tBin < 0 || zBin < 0) { + return; + } + bins[getIndex(tBin, zBin)] += weight; + } + + int findPeakBin() const noexcept + { + float bestWeight = 0.f; + int bestIndex = -1; + for (int index = 0; index < static_cast(bins.size()); ++index) { + if (bins[index] > bestWeight) { + bestWeight = bins[index]; + bestIndex = index; + } + } + return bestIndex; + } + + void suppressBin(const int index) noexcept + { + if (index >= 0 && index < static_cast(bins.size())) { + bins[index] = -1.f; + } + } + + void suppressNeighborhood(const int index, const int radiusTime, const int radiusZ) noexcept + { + if (index < 0) { + return; + } + const auto [tBin, zBin] = decodeIndex(index); + for (int dt = -radiusTime; dt <= radiusTime; ++dt) { + const auto tt = tBin + dt; + if (tt < 0 || tt >= nTimeBins) { + continue; + } + for (int dz = -radiusZ; dz <= radiusZ; ++dz) { + const auto zz = zBin + dz; + if (zz < 0 || zz >= nZBins) { + continue; + } + bins[getIndex(tt, zz)] = -1.f; + } + } + } + + float getNeighborhoodSum(const int index, const int radiusTime, const int radiusZ) const noexcept + { + if (index < 0) { + return 0.f; + } + const auto [tBin, zBin] = decodeIndex(index); + float sum = 0.f; + for (int dt = -radiusTime; dt <= radiusTime; ++dt) { + const auto tt = tBin + dt; + if (tt < 0 || tt >= nTimeBins) { + continue; + } + for (int dz = -radiusZ; dz <= radiusZ; ++dz) { + const auto zz = zBin + dz; + if (zz < 0 || zz >= nZBins) { + continue; + } + const auto value = bins[getIndex(tt, zz)]; + if (value > 0.f) { + sum += value; + } + } + } + return sum; + } + + float getTimeBinCenter(const int tBin) const noexcept + { + return timeMin + ((static_cast(tBin) + 0.5f) * timeBinSize); + } + + float getZBinCenter(const int zBin) const noexcept + { + return zMin + ((static_cast(zBin) + 0.5f) * zBinSize); + } + + TimeEstBC getTimeInterval(const int tBin) const noexcept + { + const auto lowFloat = timeMin + (static_cast(tBin) * timeBinSize); + const auto highFloat = lowFloat + timeBinSize; + const auto low = std::max(0., std::floor(lowFloat)); + const auto high = std::max(low + 1., (double)std::ceil(highFloat)); + constexpr auto maxTS = std::numeric_limits::max(); + const auto clampedLow = std::min(low, maxTS - 1.); + const auto width = std::min(high - clampedLow, std::numeric_limits::max()); + return {static_cast(clampedLow), static_cast(std::max(1., width))}; + } + + TimeEstBC getTimeNeighborhoodInterval(const int tBin, const int radius) const noexcept + { + const auto lowBin = std::max(0, tBin - radius); + const auto highBin = std::min(nTimeBins - 1, tBin + radius); + const auto lowFloat = timeMin + (static_cast(lowBin) * timeBinSize); + const auto highFloat = timeMin + (static_cast(highBin + 1) * timeBinSize); + const auto low = std::max(0., std::floor(lowFloat)); + const auto high = std::max(low + 1., (double)std::ceil(highFloat)); + constexpr auto maxTS = std::numeric_limits::max(); + const auto clampedLow = std::min(low, maxTS - 1.); + const auto width = std::min(high - clampedLow, std::numeric_limits::max()); + return {static_cast(clampedLow), static_cast(std::max(1., width))}; + } +}; + +class SeedHistogram +{ + public: + SeedHistogram(std::span members, + std::span lineRefs, + std::span lines, + const Settings& settings) + : mMembers(members), mLineRefs(lineRefs), mSeedMemberRadiusTime(settings.seedMemberRadiusTime), mSeedMemberRadiusZ(settings.seedMemberRadiusZ), mMemoryPool(settings.memoryPool), mHistogram(mMemoryPool) + { + const auto zBinSize = 0.25f * settings.clusterCut; + const auto timeBinSize = medianTimeError(lines); + + float minZ = std::numeric_limits::max(); + float maxZ = std::numeric_limits::lowest(); + float minTime = std::numeric_limits::max(); + float maxTime = std::numeric_limits::lowest(); + for (const auto lineRefIdx : mMembers) { + minZ = std::min(minZ, mLineRefs[lineRefIdx].zBeam); + maxZ = std::max(maxZ, mLineRefs[lineRefIdx].zBeam); + minTime = std::min(minTime, mLineRefs[lineRefIdx].tCenter); + maxTime = std::max(maxTime, mLineRefs[lineRefIdx].tCenter); + } + + const auto dz = std::max(0.f, maxZ - minZ); + const auto dt = std::max(0.f, maxTime - minTime); + mHistogram.nZBins = 1 + static_cast(dz / zBinSize); + mHistogram.nTimeBins = 1 + static_cast(dt / timeBinSize); + if (mHistogram.nTimeBins * mHistogram.nZBins > MaxHistogramBins) { + if (mHistogram.nTimeBins > mHistogram.nZBins) { + mHistogram.nTimeBins = std::max(1, (MaxHistogramBins - 1) / std::max(1, mHistogram.nZBins)); + } else { + mHistogram.nZBins = std::max(1, (MaxHistogramBins - 1) / std::max(1, mHistogram.nTimeBins)); + } + } + + mHistogram.timeBinSize = std::max(timeBinSize, dt / (float)std::max(1, mHistogram.nTimeBins)); + mHistogram.zBinSize = std::max(zBinSize, dz / (float)std::max(1, mHistogram.nZBins)); + const auto paddedTime = 0.5f * ((float)mHistogram.nTimeBins * mHistogram.timeBinSize - dt); + const auto paddedZ = 0.5f * ((float)mHistogram.nZBins * mHistogram.zBinSize - dz); + mHistogram.timeMin = minTime - paddedTime; + mHistogram.zMin = minZ - paddedZ; + mHistogram.bins.assign((size_t)mHistogram.nTimeBins * (size_t)mHistogram.nZBins, 0.f); + + for (const auto lineRefIdx : mMembers) { + mHistogram.fill(mLineRefs[lineRefIdx].tCenter, mLineRefs[lineRefIdx].zBeam, 1.f); + } + } + + int findPeakBin() const noexcept + { + return mHistogram.findPeakBin(); + } + + float getPeakSupport(const int peakIndex) const noexcept + { + return mHistogram.getNeighborhoodSum(peakIndex, mSeedMemberRadiusTime, mSeedMemberRadiusZ); + } + + bounded_vector collectLocalMembers(const int peakIndex, const int radiusTime, const int radiusZ) const + { + bounded_vector localMembers(mMemoryPool.get()); + localMembers.reserve(mMembers.size()); + const auto [timeBin, zBin] = mHistogram.decodeIndex(peakIndex); + for (const auto lineRefIdx : mMembers) { + const auto memberTimeBin = mHistogram.getTimeBin(mLineRefs[lineRefIdx].tCenter); + const auto memberZBin = mHistogram.getZBin(mLineRefs[lineRefIdx].zBeam); + if (memberTimeBin < 0 || memberZBin < 0) { + continue; + } + if (o2::gpu::GPUCommonMath::Abs(memberTimeBin - timeBin) > radiusTime) { + continue; + } + if (o2::gpu::GPUCommonMath::Abs(memberZBin - zBin) > radiusZ) { + continue; + } + localMembers.push_back(lineRefIdx); + } + return localMembers; + } + + TimeEstBC getPeakTimeInterval(const int peakIndex, const int radius = 0) const noexcept + { + return mHistogram.getTimeNeighborhoodInterval(mHistogram.decodeIndex(peakIndex).first, radius); + } + + float getPeakZCenter(const int peakIndex) const noexcept + { + return mHistogram.getZBinCenter(mHistogram.decodeIndex(peakIndex).second); + } + + void suppressPeak(const int peakIndex) noexcept + { + mHistogram.suppressBin(peakIndex); + } + + void suppressPeakNeighborhood(const int peakIndex) noexcept + { + mHistogram.suppressNeighborhood(peakIndex, mSeedMemberRadiusTime, mSeedMemberRadiusZ); + } + + private: + float medianTimeError(std::span lines) const + { + bounded_vector errors(mMemoryPool.get()); + errors.reserve(mMembers.size()); + for (const auto lineRefIdx : mMembers) { + errors.push_back(static_cast(lines[mLineRefs[lineRefIdx].lineIndex].mTime.getTimeStampError())); + } + std::sort(errors.begin(), errors.end()); + return errors.empty() ? 1.f : std::max(1.f, errors[errors.size() / 2]); + } + + std::span mMembers; + std::span mLineRefs; + int mSeedMemberRadiusTime = 1; + int mSeedMemberRadiusZ = 2; + std::shared_ptr mMemoryPool; + Histogram2D mHistogram; +}; + +float updateScale2(const std::span chi2s, const std::shared_ptr& mr) noexcept +{ + if (chi2s.empty()) { + return MinScale2; + } + + bounded_vector sorted(chi2s.begin(), chi2s.end(), mr.get()); + std::sort(sorted.begin(), sorted.end()); + const auto median = sorted[sorted.size() / 2]; + + for (auto& value : sorted) { + value = o2::gpu::GPUCommonMath::Abs(value - median); + } + std::sort(sorted.begin(), sorted.end()); + const auto mad = sorted[sorted.size() / 2]; + if (!std::isfinite(mad) || mad <= constants::Tolerance) { + return MinScale2; + } + return std::max(MinScale2, MedianToSigma * mad); +} + +class VertexFit +{ + public: + void add(const Line& line, const float weight) noexcept + { + const auto& direction = line.cosinesDirector; + const auto& origin = line.originPoint; + const auto det = ROOT::Math::Dot(direction, direction); + if (det <= constants::Tolerance) { + return; + } + + for (int i = 0; i < 3; ++i) { + for (int j = i; j < 3; ++j) { + mMatrix(i, j) += weight * (((i == j ? det : 0.f) - direction(i) * direction(j)) / det); + } + } + + const auto dDotO = ROOT::Math::Dot(direction, origin); + for (int i = 0; i < 3; ++i) { + mRhs(i) += weight * ((direction(i) * dDotO - det * origin(i)) / det); + } + } + + bool solve(std::array& vertexOut) const noexcept + { + SymMatrix3 inv{mMatrix}; + if (!inv.InvertFast()) { + return false; + } + const auto solution = inv * mRhs; + vertexOut[0] = static_cast(-solution(0)); + vertexOut[1] = static_cast(-solution(1)); + vertexOut[2] = static_cast(-solution(2)); + return std::isfinite(vertexOut[0]) && std::isfinite(vertexOut[1]) && std::isfinite(vertexOut[2]); + } + + private: + SymMatrix3 mMatrix; + SVector3 mRhs; +}; + +VertexSeed fitSeed(const VertexSeed& initialSeed, + std::span members, + std::span lineRefs, + std::span lines, + const std::shared_ptr& mr, + const float pairCut2) +{ + VertexSeed seed{mr}; + seed.vertex = initialSeed.vertex; + seed.time = initialSeed.time; + seed.scale2 = initialSeed.scale2; + seed.valid = false; + seed.contributors.clear(); + seed.assigned.clear(); + if (members.size() < 2) { + return seed; + } + + for (int iteration = 0; iteration < MaxFitIterations; ++iteration) { + VertexFit vertexFit; + TimeEstBC commonTime{}; + bool hasCommonTime = false; + bounded_vector contributors{mr.get()}; + const auto scale2 = std::max(seed.scale2, MinScale2); + const auto tukeyFactor = 1.f / (scale2 * TukeyC2); + + for (const auto lineRefIdx : members) { + const auto lineIdx = lineRefs[lineRefIdx].lineIndex; + const auto& line = lines[lineIdx]; + if (!line.mTime.isCompatible(seed.time)) { + continue; + } + if (hasCommonTime && !line.mTime.isCompatible(commonTime)) { + continue; + } + + const auto chi2 = Line::getDistance2FromPoint(line, seed.vertex) / pairCut2; + auto weight = 1.f - (chi2 * tukeyFactor); + if (weight <= 0.f) { + continue; + } + weight *= weight; + + if (!hasCommonTime) { + commonTime = line.mTime; + hasCommonTime = true; + } else { + commonTime += line.mTime; + } + + contributors.push_back(lineRefIdx); + vertexFit.add(line, weight); + } + + if (!hasCommonTime || contributors.size() < 2) { + return seed; + } + + std::sort(contributors.begin(), contributors.end()); + + std::array updatedVertex{}; + if (!vertexFit.solve(updatedVertex)) { + return seed; + } + + const auto sameContributors = contributors == seed.contributors; + const auto dz = o2::gpu::GPUCommonMath::Abs(updatedVertex[2] - seed.vertex[2]); + const auto oldR2 = (seed.vertex[0] * seed.vertex[0]) + (seed.vertex[1] * seed.vertex[1]); + const auto newR2 = (updatedVertex[0] * updatedVertex[0]) + (updatedVertex[1] * updatedVertex[1]); + const auto dr2 = o2::gpu::GPUCommonMath::Abs(newR2 - oldR2); + + seed.vertex = updatedVertex; + seed.time = commonTime; + bounded_vector updatedChi2s{mr.get()}; + updatedChi2s.reserve(contributors.size()); + for (const auto lineRefIx : contributors) { + updatedChi2s.push_back(Line::getDistance2FromPoint(lines[lineRefs[lineRefIx].lineIndex], seed.vertex) / pairCut2); + } + seed.scale2 = updateScale2(updatedChi2s, mr); + seed.contributors = std::move(contributors); + seed.valid = true; + + if (sameContributors && dz < VertexShiftZTol && dr2 < VertexShiftR2Tol) { + break; + } + } + + return seed; +} + +size_t countSharedContributors(std::span lhs, std::span rhs) noexcept +{ + size_t shared = 0; + auto lhsIt = lhs.begin(); + auto rhsIt = rhs.begin(); + while (lhsIt != lhs.end() && rhsIt != rhs.end()) { + if (*lhsIt == *rhsIt) { + ++shared; + ++lhsIt; + ++rhsIt; + } else if (*lhsIt < *rhsIt) { + ++lhsIt; + } else { + ++rhsIt; + } + } + return shared; +} + +bounded_vector collectCompatibleContributors(const VertexSeed& seed, + std::span members, + std::span lineRefs, + std::span lines, + const std::shared_ptr& mr, + const float pairCut2) +{ + bounded_vector contributors{mr.get()}; + contributors.reserve(members.size()); + for (const auto lineRefIdx : members) { + const auto lineIdx = lineRefs[lineRefIdx].lineIndex; + const auto& line = lines[lineIdx]; + if (!line.mTime.isCompatible(seed.time)) { + continue; + } + if (Line::getDistance2FromPoint(line, seed.vertex) >= pairCut2) { + continue; + } + contributors.push_back(lineRefIdx); + } + std::sort(contributors.begin(), contributors.end()); + return contributors; +} + +void deduplicateSeeds(bounded_vector& seeds, const Settings& settings) +{ + if (seeds.size() < 2) { + return; + } + + std::sort(seeds.begin(), seeds.end(), [](const VertexSeed& lhs, const VertexSeed& rhs) { + if (lhs.contributors.size() != rhs.contributors.size()) { + return lhs.contributors.size() > rhs.contributors.size(); + } + if (o2::gpu::GPUCommonMath::Abs(lhs.scale2 - rhs.scale2) > constants::Tolerance) { + return lhs.scale2 < rhs.scale2; + } + return lhs.vertex[2] < rhs.vertex[2]; + }); + + const auto dedupZCut = settings.seedDedupZCut > 0.f ? settings.seedDedupZCut : 0.25f * settings.clusterCut; + for (size_t i = 0; i < seeds.size(); ++i) { + auto& candidate = seeds[i]; + if (!candidate.isUsableSeed()) { + candidate.valid = false; + continue; + } + bool duplicate = false; + for (size_t j = 0; j < i; ++j) { + const auto& kept = seeds[j]; + if (!kept.isUsableSeed()) { + continue; + } + if (!candidate.time.isCompatible(kept.time)) { + continue; + } + const auto shared = countSharedContributors(candidate.contributors, kept.contributors); + const auto minSize = std::min(candidate.contributors.size(), kept.contributors.size()); + const auto zDelta = o2::gpu::GPUCommonMath::Abs(candidate.vertex[2] - kept.vertex[2]); + const bool clearlyWorse = kept.contributors.size() > candidate.contributors.size() || + kept.scale2 + constants::Tolerance < 0.9f * candidate.scale2; + const bool overlapDuplicate = shared > 0 && shared * 2 >= minSize; + const bool nearbyDuplicate = zDelta < dedupZCut && (shared > 0 || clearlyWorse); + if (overlapDuplicate || nearbyDuplicate) { + duplicate = true; + break; + } + } + if (duplicate) { + candidate.valid = false; + } + } + compactSeeds(seeds); +} + +void deduplicateRefittedSeeds(bounded_vector& seeds, const Settings& settings) +{ + if (seeds.size() < 2) { + return; + } + + std::sort(seeds.begin(), seeds.end(), [](const VertexSeed& lhs, const VertexSeed& rhs) { + if (lhs.contributors.size() != rhs.contributors.size()) { + return lhs.contributors.size() > rhs.contributors.size(); + } + if (o2::gpu::GPUCommonMath::Abs(lhs.scale2 - rhs.scale2) > constants::Tolerance) { + return lhs.scale2 < rhs.scale2; + } + return lhs.vertex[2] < rhs.vertex[2]; + }); + + const auto zCut = settings.refitDedupZCut > 0.f ? settings.refitDedupZCut : 0.25f * settings.clusterCut; + for (size_t i = 0; i < seeds.size(); ++i) { + auto& candidate = seeds[i]; + if (!candidate.isUsableSeed()) { + candidate.valid = false; + continue; + } + bool duplicate = false; + for (size_t j = 0; j < i; ++j) { + const auto& kept = seeds[j]; + if (!kept.isUsableSeed()) { + continue; + } + if (!candidate.time.isCompatible(kept.time)) { + continue; + } + const auto shared = countSharedContributors(candidate.contributors, kept.contributors); + const auto minSize = std::min(candidate.contributors.size(), kept.contributors.size()); + const auto zDelta = o2::gpu::GPUCommonMath::Abs(candidate.vertex[2] - kept.vertex[2]); + const bool overlapDuplicate = shared > 0 && shared * 2 >= minSize; + const bool lowSupportPair = std::min(candidate.contributors.size(), kept.contributors.size()) < 4; + const bool clearlyWorse = kept.contributors.size() > candidate.contributors.size() || + kept.scale2 + constants::Tolerance < 0.9f * candidate.scale2; + const bool geometricDuplicate = zDelta < zCut && (lowSupportPair || clearlyWorse); + if (overlapDuplicate || geometricDuplicate) { + duplicate = true; + break; + } + } + if (duplicate) { + candidate.valid = false; + } + } + compactSeeds(seeds); +} + +struct OrderedComponent { + explicit OrderedComponent(const std::shared_ptr& mr) : members(mr.get()) {} + float center = 0.f; + bounded_vector members; +}; + +bounded_vector> buildCoarseClusters(std::span lineRefs, + std::span lines, + const Settings& settings) +{ + bounded_vector> clusters(settings.memoryPool.get()); + if (lineRefs.size() < 2) { + return clusters; + } + + bounded_vector sortedByLower(lineRefs.size(), settings.memoryPool.get()); + std::iota(sortedByLower.begin(), sortedByLower.end(), 0); + std::sort(sortedByLower.begin(), sortedByLower.end(), [&](const int lhs, const int rhs) { + const auto lhsLower = lines[lineRefs[lhs].lineIndex].mTime.lower(); + const auto rhsLower = lines[lineRefs[rhs].lineIndex].mTime.lower(); + if (lhsLower != rhsLower) { + return lhsLower < rhsLower; + } + return lineRefs[lhs].lineIndex < lineRefs[rhs].lineIndex; + }); + + const auto coarseZWindow = settings.coarseZWindow > 0.f ? settings.coarseZWindow : settings.clusterCut; + bounded_vector parent(lineRefs.size(), settings.memoryPool.get()); + bounded_vector componentSize(lineRefs.size(), 1, settings.memoryPool.get()); + std::iota(parent.begin(), parent.end(), 0); + float minZ = std::numeric_limits::max(); + float maxZ = std::numeric_limits::lowest(); + for (const auto& lineRef : lineRefs) { + minZ = std::min(minZ, lineRef.zBeam); + maxZ = std::max(maxZ, lineRef.zBeam); + } + const auto nZBins = std::max(1, 1 + static_cast((maxZ - minZ) / coarseZWindow)); + auto getZBin = [&](const float z) { + return std::clamp(static_cast((z - minZ) / coarseZWindow), 0, nZBins - 1); + }; + + auto findRoot = [&](int idx) { + int root = idx; + while (parent[root] != root) { + root = parent[root]; + } + while (parent[idx] != idx) { + const auto next = parent[idx]; + parent[idx] = root; + idx = next; + } + return root; + }; + + auto unite = [&](const int lhs, const int rhs) { + auto lhsRoot = findRoot(lhs); + auto rhsRoot = findRoot(rhs); + if (lhsRoot == rhsRoot) { + return; + } + if (componentSize[lhsRoot] < componentSize[rhsRoot]) { + std::swap(lhsRoot, rhsRoot); + } + parent[rhsRoot] = lhsRoot; + componentSize[lhsRoot] += componentSize[rhsRoot]; + }; + + using ActiveEntry = std::pair; + bounded_vector activeEntries(settings.memoryPool.get()); + std::priority_queue, std::greater<>> activeByUpper(std::greater<>{}, std::move(activeEntries)); + bounded_vector activeMask(lineRefs.size(), 0, settings.memoryPool.get()); + bounded_vector> activeByZBin(settings.memoryPool.get()); + activeByZBin.reserve(nZBins); + for (int iBin = 0; iBin < nZBins; ++iBin) { + activeByZBin.emplace_back(); + } + for (const auto lineRefIdx : sortedByLower) { + const auto& lineRef = lineRefs[lineRefIdx]; + const auto& line = lines[lineRef.lineIndex]; + const auto currentLower = line.mTime.lower(); + + while (!activeByUpper.empty() && activeByUpper.top().first < currentLower) { + activeMask[activeByUpper.top().second] = 0; + activeByUpper.pop(); + } + + const auto zBin = getZBin(lineRef.zBeam); + for (int neighborBin = std::max(0, zBin - 1); neighborBin <= std::min(nZBins - 1, zBin + 1); ++neighborBin) { + auto& bucket = activeByZBin[neighborBin]; + size_t writePos = 0; + for (size_t readPos = 0; readPos < bucket.size(); ++readPos) { + const auto oLineRefIdx = bucket[readPos]; + if (!activeMask[oLineRefIdx]) { + continue; + } + bucket[writePos++] = oLineRefIdx; + const auto& oLineRef = lineRefs[oLineRefIdx]; + if (o2::gpu::GPUCommonMath::Abs(lineRef.zBeam - oLineRef.zBeam) >= coarseZWindow) { + continue; + } + const auto& otherLine = lines[oLineRef.lineIndex]; + if (line.mTime.isCompatible(otherLine.mTime)) { + unite(lineRefIdx, oLineRefIdx); + } + } + bucket.resize(writePos); + } + + activeMask[lineRefIdx] = 1; + activeByUpper.emplace(line.mTime.upper(), lineRefIdx); + activeByZBin[zBin].push_back(lineRefIdx); + } + + std::unordered_map> components; + components.reserve(lineRefs.size()); + for (int lineRefIdx = 0; lineRefIdx < static_cast(lineRefs.size()); ++lineRefIdx) { + const auto root = findRoot(lineRefIdx); + auto [it, inserted] = components.try_emplace(root, std::pmr::polymorphic_allocator{settings.memoryPool.get()}); + (void)inserted; + it->second.push_back(lineRefIdx); + } + + bounded_vector orderedComponents(settings.memoryPool.get()); + orderedComponents.reserve(components.size()); + for (auto& [root, members] : components) { + (void)root; + if (members.size() < 2) { + continue; + } + std::sort(members.begin(), members.end(), [&](const int lhs, const int rhs) { + const auto lhsLower = lines[lineRefs[lhs].lineIndex].mTime.lower(); + const auto rhsLower = lines[lineRefs[rhs].lineIndex].mTime.lower(); + if (lhsLower != rhsLower) { + return lhsLower < rhsLower; + } + return lineRefs[lhs].lineIndex < lineRefs[rhs].lineIndex; + }); + orderedComponents.emplace_back(settings.memoryPool); + orderedComponents.back().center = lineRefs[members.front()].tCenter; + orderedComponents.back().members = std::move(members); + } + + std::sort(orderedComponents.begin(), orderedComponents.end(), [](const auto& lhs, const auto& rhs) { + if (o2::gpu::GPUCommonMath::Abs(lhs.center - rhs.center) > TieTolerance) { + return lhs.center < rhs.center; + } + return lhs.members.front() < rhs.members.front(); + }); + clusters.reserve(orderedComponents.size()); + for (auto& component : orderedComponents) { + clusters.push_back(std::move(component.members)); + } + return clusters; +} + +bounded_vector buildSeeds(std::span members, + std::span lineRefs, + std::span lines, + const Settings& settings) +{ + SeedHistogram histogram(members, lineRefs, lines, settings); + bounded_vector seeds(settings.memoryPool.get()); + seeds.reserve(MaxSeedsPerCluster); + float leadingPeakSupport = 0.f; + + while (static_cast(seeds.size()) < MaxSeedsPerCluster) { + const auto peak = histogram.findPeakBin(); + if (peak < 0) { + break; + } + const auto peakSupport = histogram.getPeakSupport(peak); + if (peakSupport < 2.f) { + break; + } + if (leadingPeakSupport <= 0.f) { + leadingPeakSupport = peakSupport; + } else if (peakSupport < std::max(2.f, MinRelativePeakSupport * leadingPeakSupport)) { + break; + } + auto localMembers = histogram.collectLocalMembers(peak, 0, 0); + if (localMembers.size() < 2) { + localMembers = histogram.collectLocalMembers(peak, settings.seedMemberRadiusTime, settings.seedMemberRadiusZ); + } + if (localMembers.size() < 2) { + histogram.suppressPeak(peak); + continue; + } + + VertexSeed seed(settings.memoryPool); + seed.vertex = {settings.beamX, settings.beamY, histogram.getPeakZCenter(peak)}; + seed.time = histogram.getPeakTimeInterval(peak); + seed.scale2 = InitialScale2; + + auto fitted = fitSeed(seed, localMembers, lineRefs, lines, settings.memoryPool, settings.pairCut2); + if (fitted.valid && fitted.contributors.size() >= 2) { + seeds.push_back(std::move(fitted)); + histogram.suppressPeakNeighborhood(peak); + } else { + histogram.suppressPeak(peak); + } + } + + return seeds; +} + +void assignLinesToSeeds(bounded_vector& seeds, + std::span members, + std::span lineRefs, + std::span lines, + const float pairCut2) +{ + for (auto& seed : seeds) { + seed.assigned.clear(); + } + + for (const auto lineRefIdx : members) { + const auto lineIdx = lineRefs[lineRefIdx].lineIndex; + const auto& line = lines[lineIdx]; + + int bestSeed = -1; + float bestScore = std::numeric_limits::max(); + size_t bestMult = 0; + float bestZResidual = std::numeric_limits::max(); + + for (int seedIdx = 0; seedIdx < static_cast(seeds.size()); ++seedIdx) { + const auto& seed = seeds[seedIdx]; + if (!seed.valid || seed.contributors.size() < 2) { + continue; + } + if (!line.mTime.isCompatible(seed.time)) { + continue; + } + + const auto distance2 = Line::getDistance2FromPoint(line, seed.vertex); + if (distance2 >= pairCut2) { + continue; + } + + const auto score = distance2 / std::max(seed.scale2, MinScale2); + const auto zResidual = o2::gpu::GPUCommonMath::Abs(lineRefs[lineRefIdx].zBeam - seed.vertex[2]); + const auto multiplicity = seed.contributors.size(); + + const auto betterScore = score + TieTolerance < bestScore; + const auto betterMultiplicity = o2::gpu::GPUCommonMath::Abs(score - bestScore) <= TieTolerance && multiplicity > bestMult; + const auto betterZ = o2::gpu::GPUCommonMath::Abs(score - bestScore) <= TieTolerance && + multiplicity == bestMult && zResidual + constants::Tolerance < bestZResidual; + if (betterScore || betterMultiplicity || betterZ) { + bestSeed = seedIdx; + bestScore = score; + bestMult = multiplicity; + bestZResidual = zResidual; + } + } + + if (bestSeed >= 0) { + seeds[bestSeed].assigned.push_back(lineRefIdx); + } + } +} + +ClusterLines materializeCluster(const VertexSeed& seed, + std::span lineRefs, + std::span lines, + const std::shared_ptr& mr) +{ + bounded_vector lineIndices{mr.get()}; + lineIndices.reserve(seed.contributors.size()); + for (const auto lineRefIdx : seed.contributors) { + lineIndices.push_back(lineRefs[lineRefIdx].lineIndex); + } + std::sort(lineIndices.begin(), lineIndices.end()); + lineIndices.erase(std::unique(lineIndices.begin(), lineIndices.end()), lineIndices.end()); + + if (lineIndices.size() < 2) { + return {}; + } + + return {std::span{lineIndices.data(), lineIndices.size()}, lines}; +} + +} // namespace + +bounded_vector buildClusters(std::span lines, const Settings& settings) +{ + bounded_vector clusters(settings.memoryPool.get()); + if (lines.size() < 2) { + return clusters; + } + + bounded_vector refs(settings.memoryPool.get()); + refs.reserve(lines.size()); + for (int lineIdx = 0; lineIdx < static_cast(lines.size()); ++lineIdx) { + LineRef ref(lines[lineIdx], lineIdx, settings.beamX, settings.beamY, settings.maxZ); + if (!ref.isDead()) { + refs.push_back(ref); + } + } + + if (refs.size() < 2) { + return clusters; + } + + const auto coarseClusters = buildCoarseClusters(refs, lines, settings); + + for (const auto& members : coarseClusters) { + auto seeds = buildSeeds(members, refs, lines, settings); + if (seeds.empty()) { + continue; + } + + for (auto& seed : seeds) { + if (!seed.isUsableSeed()) { + seed.valid = false; + continue; + } + auto contributors = collectCompatibleContributors(seed, members, refs, lines, settings.memoryPool, settings.pairCut2); + if (contributors.size() < 2) { + seed.valid = false; + continue; + } + seed.contributors = std::move(contributors); + } + compactSeeds(seeds); + if (seeds.empty()) { + continue; + } + deduplicateSeeds(seeds, settings); + if (seeds.empty()) { + continue; + } + assignLinesToSeeds(seeds, members, refs, lines, settings.pairCut2); + for (auto& seed : seeds) { + if (seed.assigned.size() < 2) { + seed.valid = false; + continue; + } + seed = fitSeed(seed, seed.assigned, refs, lines, settings.memoryPool, settings.pairCut2); + if (!seed.isUsableSeed()) { + seed.valid = false; + continue; + } + } + compactSeeds(seeds); + deduplicateRefittedSeeds(seeds, settings); + for (auto& refit : seeds) { + auto cluster = materializeCluster(refit, refs, lines, settings.memoryPool); + if (cluster.getSize() < 2) { + continue; + } + if (!cluster.isValid()) { + continue; + } + clusters.push_back(std::move(cluster)); + } + } + + return clusters; +} + +} // namespace o2::its::line_vertexer diff --git a/Detectors/ITSMFT/ITS/tracking/src/Smoother.cxx b/Detectors/ITSMFT/ITS/tracking/src/Smoother.cxx deleted file mode 100644 index f2f7dbc81398f..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/src/Smoother.cxx +++ /dev/null @@ -1,222 +0,0 @@ -// 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. -// -// \author matteo.concas@cern.ch - -#include "ITStracking/Smoother.h" - -namespace o2 -{ -namespace its -{ - -constexpr std::array getInverseSymm2D(const std::array& mat) -{ - const double det = mat[0] * mat[2] - mat[1] * mat[1]; - return std::array{mat[2] / det, -mat[1] / det, mat[0] / det}; -} - -// Smoother -// template -// Smoother::Smoother(TrackITSExt& track, size_t smoothingLayer, const ROframe& event, float bZ, o2::base::PropagatorF::MatCorrType corr) : mLayerToSmooth{smoothingLayer}, -// mBz(bZ), -// mCorr(corr) -// { -// -// auto propInstance = o2::base::Propagator::Instance(); -// const TrackingFrameInfo& originalTf = event.getTrackingFrameInfoOnLayer(mLayerToSmooth).at(track.getClusterIndex(mLayerToSmooth)); -// -// mOutwardsTrack = track; // This track will be propagated outwards inside the smoother! (as last step of fitting did inward propagation) -// mInwardsTrack = {track.getParamOut(), // This track will be propagated inwards inside the smoother! -// static_cast(mOutwardsTrack.getNumberOfClusters()), -999, static_cast(event.getROFrameId()), -// mOutwardsTrack.getParamOut(), mOutwardsTrack.getClusterIndexes()}; -// -// mOutwardsTrack.resetCovariance(); -// mOutwardsTrack.setChi2(0); -// mInwardsTrack.resetCovariance(); -// mInwardsTrack.setChi2(0); -// -// bool statusOutw{false}; -// bool statusInw{false}; -// -// ////////////////////// -// // Outward propagation -// for (size_t iLayer{0}; iLayer < mLayerToSmooth; ++iLayer) { -// if (mOutwardsTrack.getClusterIndex(iLayer) == constants::UnusedIndex) { // Shorter tracks -// continue; -// } -// const TrackingFrameInfo& tF = event.getTrackingFrameInfoOnLayer(iLayer).at(mOutwardsTrack.getClusterIndex(iLayer)); -// statusOutw = mOutwardsTrack.rotate(tF.alphaTrackingFrame); -// statusOutw &= propInstance->propagateToX(mOutwardsTrack, -// tF.xTrackingFrame, -// mBz, -// o2::base::PropagatorImpl::MAX_SIN_PHI, -// o2::base::PropagatorImpl::MAX_STEP, -// mCorr); -// mOutwardsTrack.setChi2(mOutwardsTrack.getChi2() + mOutwardsTrack.getPredictedChi2(tF.positionTrackingFrame, tF.covarianceTrackingFrame)); -// statusOutw &= mOutwardsTrack.o2::track::TrackParCov::update(tF.positionTrackingFrame, tF.covarianceTrackingFrame); -// // LOG(info) << "Outwards loop on inwards track, layer: " << iLayer << " x: " << mOutwardsTrack.getX(); -// } -// -// // Prediction on the previously outwards-propagated track is done on a copy, as the process seems to be not reversible -// auto outwardsClone = mOutwardsTrack; -// statusOutw = outwardsClone.rotate(originalTf.alphaTrackingFrame); -// statusOutw &= propInstance->propagateToX(outwardsClone, -// originalTf.xTrackingFrame, -// mBz, -// o2::base::PropagatorImpl::MAX_SIN_PHI, -// o2::base::PropagatorImpl::MAX_STEP, -// mCorr); -// ///////////////////// -// // Inward propagation -// for (size_t iLayer{D - 1}; iLayer > mLayerToSmooth; --iLayer) { -// if (mInwardsTrack.getClusterIndex(iLayer) == constants::UnusedIndex) { // Shorter tracks -// continue; -// } -// const TrackingFrameInfo& tF = event.getTrackingFrameInfoOnLayer(iLayer).at(mInwardsTrack.getClusterIndex(iLayer)); -// statusInw = mInwardsTrack.rotate(tF.alphaTrackingFrame); -// statusInw &= propInstance->propagateToX(mInwardsTrack, -// tF.xTrackingFrame, -// mBz, -// o2::base::PropagatorImpl::MAX_SIN_PHI, -// o2::base::PropagatorImpl::MAX_STEP, -// mCorr); -// mInwardsTrack.setChi2(mInwardsTrack.getChi2() + mInwardsTrack.getPredictedChi2(tF.positionTrackingFrame, tF.covarianceTrackingFrame)); -// statusInw &= mInwardsTrack.o2::track::TrackParCov::update(tF.positionTrackingFrame, tF.covarianceTrackingFrame); -// // LOG(info) << "Inwards loop on outwards track, layer: " << iLayer << " x: " << mInwardsTrack.getX(); -// } -// -// // Prediction on the previously inwards-propagated track is done on a copy, as the process seems to be not revesible -// auto inwardsClone = mInwardsTrack; -// statusInw = inwardsClone.rotate(originalTf.alphaTrackingFrame); -// statusInw &= propInstance->propagateToX(inwardsClone, -// originalTf.xTrackingFrame, -// mBz, -// o2::base::PropagatorImpl::MAX_SIN_PHI, -// o2::base::PropagatorImpl::MAX_STEP, -// mCorr); -// // Compute weighted local chi2 -// mInitStatus = statusInw && statusOutw; -// if (mInitStatus) { -// mBestChi2 = computeSmoothedPredictedChi2(inwardsClone, outwardsClone, originalTf.positionTrackingFrame, originalTf.covarianceTrackingFrame); -// mLastChi2 = mBestChi2; -// LOG(info) << "Smoothed chi2 on original cluster: " << mBestChi2; -// } -// } - -template -Smoother::~Smoother() = default; - -template -float Smoother::computeSmoothedPredictedChi2(const o2::track::TrackParCov& firstTrack, // outwards track: from innermost cluster to outermost - const o2::track::TrackParCov& secondTrack, // inwards track: from outermost cluster to innermost - const std::array& cls, - const std::array& clCov) -{ - // Tracks need to be already propagated, compute only chi2 - // Symmetric covariances assumed - - if (firstTrack.getX() != secondTrack.getX()) { - LOG(fatal) << "Tracks need to be propagated to the same point! secondTrack.X=" << secondTrack.getX() << " firstTrack.X=" << firstTrack.getX(); - } - - std::array pp1 = {static_cast(firstTrack.getY()), static_cast(firstTrack.getZ())}; // P1: predicted Y,Z points - std::array pp2 = {static_cast(secondTrack.getY()), static_cast(secondTrack.getZ())}; // P2: predicted Y,Z points - - std::array c1 = {static_cast(firstTrack.getSigmaY2()), - static_cast(firstTrack.getSigmaZY()), - static_cast(firstTrack.getSigmaZ2())}; // Cov. track 1 - - std::array c2 = {static_cast(secondTrack.getSigmaY2()), - static_cast(secondTrack.getSigmaZY()), - static_cast(secondTrack.getSigmaZ2())}; // Cov. track 2 - - std::array w1 = getInverseSymm2D(c1); // weight matrices - std::array w2 = getInverseSymm2D(c2); - - std::array w1w2 = {w1[0] + w2[0], w1[1] + w2[1], w1[2] + w2[2]}; // (W1 + W2) - std::array C = getInverseSymm2D(w1w2); // C = (W1+W2)^-1 - - std::array w1pp1 = {w1[0] * pp1[0] + w1[1] * pp1[1], w1[1] * pp1[0] + w1[2] * pp1[1]}; // W1 * P1 - std::array w2pp2 = {w2[0] * pp2[0] + w2[1] * pp2[1], w2[1] * pp2[0] + w2[2] * pp2[1]}; // W2 * P2 - - double Y = C[0] * (w1pp1[0] + w2pp2[0]) + C[1] * (w1pp1[1] + w2pp2[1]); // Pp: weighted normalized combination of the predictions: - double Z = C[1] * (w1pp1[0] + w2pp2[0]) + C[2] * (w1pp1[1] + w2pp2[1]); // Pp = [(W1 * P1) + (W2 * P2)] / (W1 + W2) - - std::array delta = {Y - cls[0], Z - cls[1]}; // Δ = Pp - X, X: space point of cluster (Y,Z) - std::array CCp = {C[0] + static_cast(clCov[0]), C[1] + static_cast(clCov[1]), C[2] + static_cast(clCov[2])}; // Transformation of cluster covmat: CCp = C + Cov - std::array Wp = getInverseSymm2D(CCp); // Get weight matrix: Wp = CCp^-1 - - float chi2 = static_cast(delta[0] * (Wp[0] * delta[0] + Wp[1] * delta[1]) + delta[1] * (Wp[1] * delta[0] + Wp[2] * delta[1])); // chi2 = tΔ * (Wp * Δ) - - // #ifdef CA_DEBUG - LOG(info) << "Cluster_y: " << cls[0] << " Cluster_z: " << cls[1]; - LOG(info) << "\t\t- Covariance cluster: Y2: " << clCov[0] << " YZ: " << clCov[1] << " Z2: " << clCov[2]; - LOG(info) << "\t\t- Propagated t1_y: " << pp1[0] << " t1_z: " << pp1[1]; - LOG(info) << "\t\t- Propagated t2_y: " << pp2[0] << " t2_z: " << pp2[1]; - LOG(info) << "\t\t- Covariance t1: sY2: " << c1[0] << " sYZ: " << c1[1] << " sZ2: " << c1[2]; - LOG(info) << "\t\t- Covariance t2: sY2: " << c2[0] << " sYZ: " << c2[1] << " sZ2: " << c2[2]; - LOG(info) << "Smoother prediction Y: " << Y << " Z: " << Z; - LOG(info) << "\t\t- Delta_y: " << delta[0] << " Delta_z: " << delta[1]; - LOG(info) << "\t\t- Covariance Pr: Y2: " << C[0] << " YZ: " << C[1] << " Z2: " << C[2]; - LOG(info) << "\t\t- predicted chi2 t1: " << firstTrack.getPredictedChi2(cls, clCov); - LOG(info) << "\t\t- predicted chi2 t2: " << secondTrack.getPredictedChi2(cls, clCov); - // #endif - return chi2; -} - -// template -// bool Smoother::testCluster(const int clusterId, const ROframe& event) -// { -// if (!mInitStatus) { -// return false; -// } -// auto propInstance = o2::base::Propagator::Instance(); -// const TrackingFrameInfo& testTf = event.getTrackingFrameInfoOnLayer(mLayerToSmooth).at(clusterId); -// -// bool statusOutw{false}; -// bool statusInw{false}; -// -// // Prediction on the previously outwards-propagated track is done on a copy, as the process seems to be not reversible -// auto outwardsClone = mOutwardsTrack; -// statusOutw = outwardsClone.rotate(testTf.alphaTrackingFrame); -// statusOutw &= propInstance->propagateToX(outwardsClone, -// testTf.xTrackingFrame, -// mBz, -// o2::base::PropagatorImpl::MAX_SIN_PHI, -// o2::base::PropagatorImpl::MAX_STEP, -// mCorr); -// -// // Prediction on the previously inwards-propagated track is done on a copy, as the process seems to be not reversible -// auto inwardsClone = mInwardsTrack; -// statusInw = inwardsClone.rotate(testTf.alphaTrackingFrame); -// statusInw &= propInstance->propagateToX(inwardsClone, -// testTf.xTrackingFrame, -// mBz, -// o2::base::PropagatorImpl::MAX_SIN_PHI, -// o2::base::PropagatorImpl::MAX_STEP, -// mCorr); -// if (!(statusOutw && statusInw)) { -// LOG(warning) << "Failed propagation in smoother!"; -// return false; -// } -// -// // Compute weighted local chi2 -// mLastChi2 = computeSmoothedPredictedChi2(inwardsClone, outwardsClone, testTf.positionTrackingFrame, testTf.covarianceTrackingFrame); -// LOG(info) << "Smoothed chi2 on tested cluster: " << mLastChi2; -// -// return true; -// } - -template class Smoother<7>; - -} // namespace its -} // namespace o2 diff --git a/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx b/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx index 29fb4ac4c69b5..8375004cbfbad 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TimeFrame.cxx @@ -14,19 +14,16 @@ /// #include -#include #include "Framework/Logger.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/MathUtils.h" -#include "DataFormatsITSMFT/Cluster.h" #include "DataFormatsITSMFT/CompCluster.h" #include "DataFormatsITSMFT/ROFRecord.h" #include "DataFormatsITSMFT/TopologyDictionary.h" #include "ITSBase/GeometryTGeo.h" #include "ITSMFTBase/SegmentationAlpide.h" #include "ITStracking/BoundedAllocator.h" -#include "ITStracking/TrackingConfigParam.h" namespace { @@ -46,82 +43,43 @@ constexpr float DefClusErrorCol = o2::itsmft::SegmentationAlpide::PitchCol * 0.5 constexpr float DefClusError2Row = DefClusErrorRow * DefClusErrorRow; constexpr float DefClusError2Col = DefClusErrorCol * DefClusErrorCol; -template -void TimeFrame::addPrimaryVertices(const bounded_vector& vertices, const int iteration) +template +void TimeFrame::addPrimaryVertex(const Vertex& vert) { - for (const auto& vertex : vertices) { - mPrimaryVertices.emplace_back(vertex); // put a copy in the present - mTotVertPerIteration[iteration]++; - if (!isBeamPositionOverridden) { // beam position is updated only at first occurrence of the vertex. A bit sketchy if we have past/future vertices, it should not impact too much. - const float w = vertex.getNContributors(); - mBeamPos[0] = (mBeamPos[0] * mBeamPosWeight + vertex.getX() * w) / (mBeamPosWeight + w); - mBeamPos[1] = (mBeamPos[1] * mBeamPosWeight + vertex.getY() * w) / (mBeamPosWeight + w); - mBeamPosWeight += w; - } - } - mROFramesPV.push_back(mPrimaryVertices.size()); // current rof must have number of vertices up to present -} - -template -void TimeFrame::addPrimaryVerticesLabels(bounded_vector>& labels) -{ - mVerticesMCRecInfo.insert(mVerticesMCRecInfo.end(), labels.begin(), labels.end()); -} - -template -void TimeFrame::addPrimaryVerticesContributorLabels(bounded_vector& labels) -{ - mVerticesContributorLabels.insert(mVerticesContributorLabels.end(), labels.begin(), labels.end()); -} - -template -void TimeFrame::addPrimaryVerticesInROF(const bounded_vector& vertices, const int rofId, const int iteration) -{ - mPrimaryVertices.insert(mPrimaryVertices.begin() + mROFramesPV[rofId], vertices.begin(), vertices.end()); - for (int i = rofId + 1; i < mROFramesPV.size(); ++i) { - mROFramesPV[i] += vertices.size(); + mPrimaryVertices.emplace_back(vert); + if (!isBeamPositionOverridden) { + const float w = vert.getNContributors(); + mBeamPos[0] = (mBeamPos[0] * mBeamPosWeight + vert.getX() * w) / (mBeamPosWeight + w); + mBeamPos[1] = (mBeamPos[1] * mBeamPosWeight + vert.getY() * w) / (mBeamPosWeight + w); + mBeamPosWeight += w; } - mTotVertPerIteration[iteration] += vertices.size(); -} - -template -void TimeFrame::addPrimaryVerticesLabelsInROF(const bounded_vector>& labels, const int rofId) -{ - mVerticesMCRecInfo.insert(mVerticesMCRecInfo.begin() + mROFramesPV[rofId], labels.begin(), labels.end()); } -template -void TimeFrame::addPrimaryVerticesContributorLabelsInROF(const bounded_vector& labels, const int rofId) -{ - // count the number of cont. in rofs before and including the target rof - unsigned int n{0}; - const auto& pvs = getPrimaryVertices(0, rofId); - for (const auto& pv : pvs) { - n += pv.getNContributors(); - } - mVerticesContributorLabels.insert(mVerticesContributorLabels.begin() + n, labels.begin(), labels.end()); -} - -template -int TimeFrame::loadROFrameData(gsl::span rofs, - gsl::span clusters, - gsl::span::iterator& pattIt, - const itsmft::TopologyDictionary* dict, - const dataformats::MCTruthContainer* mcLabels) +template +void TimeFrame::loadROFrameData(gsl::span rofs, + gsl::span clusters, + gsl::span::iterator& pattIt, + const itsmft::TopologyDictionary* dict, + int layer, + const dataformats::MCTruthContainer* mcLabels) { GeometryTGeo* geom = GeometryTGeo::Instance(); geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G)); + resetROFrameData(layer); + prepareROFrameData(clusters, layer); - resetROFrameData(rofs.size()); - prepareROFrameData(rofs, clusters); + // check for missing/empty/unset rofs + // the code requires consistent monotonically increasing input without gaps + const auto& timing = mROFOverlapTableView.getLayer(layer >= 0 ? layer : 0); + if (timing.mNROFsTF != rofs.size()) { + LOGP(fatal, "Received inconsistent number of rofs on layer:{} expected:{} received:{}", layer, timing.mNROFsTF, rofs.size()); + } - for (size_t iRof{0}; iRof < rofs.size(); ++iRof) { + for (int32_t iRof{0}; iRof < rofs.size(); ++iRof) { const auto& rof = rofs[iRof]; for (int clusterId{rof.getFirstEntry()}; clusterId < rof.getFirstEntry() + rof.getNEntries(); ++clusterId) { const auto& c = clusters[clusterId]; - - int layer = geom->getLayer(c.getSensorID()); - + int lay = geom->getLayer(c.getSensorID()); auto pattID = c.getPatternID(); o2::math_utils::Point3D locXYZ; float sigmaY2 = DefClusError2Row, sigmaZ2 = DefClusError2Col, sigmaYZ = 0; // Dummy COG errors (about half pixel size) @@ -142,85 +100,97 @@ int TimeFrame::loadROFrameData(gsl::span r locXYZ = dict->getClusterCoordinates(c, patt, false); clusterSize = patt.getNPixels(); } - mClusterSize[clusterId] = std::clamp(clusterSize, 0u, 255u); + mClusterSize[layer >= 0 ? layer : 0][clusterId] = std::clamp(clusterSize, 0u, 255u); auto sensorID = c.getSensorID(); // Inverse transformation to the local --> tracking auto trkXYZ = geom->getMatrixT2L(sensorID) ^ locXYZ; // Transformation to the local --> global auto gloXYZ = geom->getMatrixL2G(sensorID) * locXYZ; - - addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), geom->getSensorRefAlpha(sensorID), + addTrackingFrameInfoToLayer(lay, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), geom->getSensorRefAlpha(sensorID), std::array{trkXYZ.y(), trkXYZ.z()}, std::array{sigmaY2, sigmaYZ, sigmaZ2}); /// Rotate to the global frame - addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), mUnsortedClusters[layer].size()); - addClusterExternalIndexToLayer(layer, clusterId); + addClusterToLayer(lay, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), mUnsortedClusters[lay].size()); + addClusterExternalIndexToLayer(lay, clusterId); } - for (unsigned int iL{0}; iL < mUnsortedClusters.size(); ++iL) { - mROFramesClusters[iL][iRof + 1] = mUnsortedClusters[iL].size(); // effectively calculating and exclusive sum + // effectively calculating an exclusive sum + if (layer >= 0) { + mROFramesClusters[layer][iRof + 1] = mUnsortedClusters[layer].size(); + } else { + for (unsigned int iL{0}; iL < mUnsortedClusters.size(); ++iL) { + mROFramesClusters[iL][iRof + 1] = mUnsortedClusters[iL].size(); + } } } - for (auto i = 0; i < mNTrackletsPerCluster.size(); ++i) { - mNTrackletsPerCluster[i].resize(mUnsortedClusters[1].size()); - mNTrackletsPerClusterSum[i].resize(mUnsortedClusters[1].size() + 1); // Exc sum "prepends" a 0 + if (layer == 1 || layer == -1) { + for (auto i = 0; i < mNTrackletsPerCluster.size(); ++i) { + mNTrackletsPerCluster[i].resize(mUnsortedClusters[1].size()); + mNTrackletsPerClusterSum[i].resize(mUnsortedClusters[1].size() + 1); + } } if (mcLabels != nullptr) { - mClusterLabels = mcLabels; + mClusterLabels[layer >= 0 ? layer : 0] = mcLabels; + } else { + mClusterLabels[layer >= 0 ? layer : 0] = nullptr; } - - return mNrof; } -template -void TimeFrame::resetROFrameData(size_t nRofs) +template +void TimeFrame::resetROFrameData(int layer) { - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - deepVectorClear(mUnsortedClusters[iLayer], getMaybeFrameworkHostResource()); - deepVectorClear(mTrackingFrameInfo[iLayer], getMaybeFrameworkHostResource()); - clearResizeBoundedVector(mROFramesClusters[iLayer], nRofs + 1, getMaybeFrameworkHostResource()); - deepVectorClear(mClusterExternalIndices[iLayer], mMemoryPool.get()); - - if (iLayer < 2) { - deepVectorClear(mTrackletsIndexROF[iLayer], mMemoryPool.get()); - deepVectorClear(mNTrackletsPerCluster[iLayer], mMemoryPool.get()); - deepVectorClear(mNTrackletsPerClusterSum[iLayer], mMemoryPool.get()); + if (layer >= 0) { + deepVectorClear(mUnsortedClusters[layer], getMaybeFrameworkHostResource()); + deepVectorClear(mTrackingFrameInfo[layer], getMaybeFrameworkHostResource()); + deepVectorClear(mClusterExternalIndices[layer], mMemoryPool.get()); + clearResizeBoundedVector(mROFramesClusters[layer], mROFOverlapTableView.getLayer(layer).mNROFsTF + 1, getMaybeFrameworkHostResource()); + } else { + for (int iLayer{0}; iLayer < NLayers; ++iLayer) { + deepVectorClear(mUnsortedClusters[iLayer], getMaybeFrameworkHostResource()); + deepVectorClear(mTrackingFrameInfo[iLayer], getMaybeFrameworkHostResource()); + deepVectorClear(mClusterExternalIndices[iLayer], mMemoryPool.get()); + clearResizeBoundedVector(mROFramesClusters[iLayer], mROFOverlapTableView.getLayer(iLayer).mNROFsTF + 1, getMaybeFrameworkHostResource()); } } } -template -void TimeFrame::prepareROFrameData(gsl::span rofs, - gsl::span clusters) +template +void TimeFrame::prepareROFrameData(gsl::span clusters, int layer) { - GeometryTGeo* geom = GeometryTGeo::Instance(); - mNrof = rofs.size(); - clearResizeBoundedVector(mClusterSize, clusters.size(), mMemoryPool.get()); - std::array clusterCountPerLayer{}; - for (const auto& clus : clusters) { - ++clusterCountPerLayer[geom->getLayer(clus.getSensorID())]; - } - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); - mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); - mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); + if (layer >= 0) { + mUnsortedClusters[layer].reserve(clusters.size()); + mTrackingFrameInfo[layer].reserve(clusters.size()); + mClusterExternalIndices[layer].reserve(clusters.size()); + clearResizeBoundedVector(mClusterSize[layer], clusters.size(), mMemoryPool.get()); + } else { + auto* geom = GeometryTGeo::Instance(); + clearResizeBoundedVector(mClusterSize[0], clusters.size(), mMemoryPool.get()); + std::array clusterCountPerLayer{0}; + for (const auto& cls : clusters) { + ++clusterCountPerLayer[geom->getLayer(cls.getChipID())]; + } + for (int iLayer{0}; iLayer < NLayers; ++iLayer) { + mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); + mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); + mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); + } } } -template -void TimeFrame::prepareClusters(const TrackingParameters& trkParam, const int maxLayers) +template +void TimeFrame::prepareClusters(const TrackingParameters& trkParam, const int maxLayers) { const int numBins{trkParam.PhiBins * trkParam.ZBins}; const int stride{numBins + 1}; bounded_vector cHelper(mMemoryPool.get()); bounded_vector clsPerBin(numBins, 0, mMemoryPool.get()); bounded_vector lutPerBin(numBins, 0, mMemoryPool.get()); - for (int rof{0}; rof < mNrof; ++rof) { - if ((int)mMultiplicityCutMask.size() == mNrof && !mMultiplicityCutMask[rof]) { - continue; - } - for (int iLayer{0}, stopLayer = std::min(trkParam.NLayers, maxLayers); iLayer < stopLayer; ++iLayer) { + for (int iLayer{0}, stopLayer = std::min(trkParam.NLayers, maxLayers); iLayer < stopLayer; ++iLayer) { + for (int rof{0}; rof < getNrof(iLayer); ++rof) { + if (!mROFMaskView.isROFEnabled(iLayer, rof)) { + continue; + } const auto& unsortedClusters{getUnsortedClustersOnLayer(rof, iLayer)}; const int clustersNum{static_cast(unsortedClusters.size())}; auto* tableBase = mIndexTables[iLayer].data() + rof * stride; @@ -270,46 +240,58 @@ void TimeFrame::prepareClusters(const TrackingParameters& trkParam, con } } -template -void TimeFrame::initialise(const int iteration, const TrackingParameters& trkParam, const int maxLayers, bool resetVertices) +template +void TimeFrame::initVertexingTopology(const TrackingParameters& trkParam) { - if (iteration == 0) { - if (maxLayers < trkParam.NLayers && resetVertices) { - resetRofPV(); - deepVectorClear(mTotVertPerIteration); - } + mVertexingTopology.init(3, trkParam.MaxHoles, trkParam.HoleLayerMask); +} + +template +void TimeFrame::initDefaultTrackingTopology(const TrackingParameters& trkParam, const int maxLayers) +{ + mDefaultTrackingTopology.init(maxLayers, trkParam.MaxHoles, trkParam.HoleLayerMask); +} + +template +void TimeFrame::initTrackerTopologies(gsl::span trkParams, const int maxLayers) +{ + mTrackerTopologies.resize(trkParams.size()); + for (size_t iteration = 0; iteration < trkParams.size(); ++iteration) { + const int iterationMaxLayers = std::min(maxLayers, trkParams[iteration].NLayers); + mTrackerTopologies[iteration].init(iterationMaxLayers, trkParams[iteration].MaxHoles, trkParams[iteration].HoleLayerMask); + } +} + +template +void TimeFrame::initialise(const TrackingParameters& trkParam, const int maxLayers, const int iteration) +{ + mTrackingTopologyView = iteration != constants::UnusedIndex ? mTrackerTopologies[iteration].getView() : (maxLayers == 3 ? mVertexingTopology.getView() : mDefaultTrackingTopology.getView()); + + if (trkParam.PassFlags[IterationStep::FirstPass]) { deepVectorClear(mTracks); deepVectorClear(mTracksLabel); deepVectorClear(mLines); deepVectorClear(mLinesLabels); - if (resetVertices) { - deepVectorClear(mVerticesMCRecInfo); - deepVectorClear(mVerticesContributorLabels); + if (trkParam.PassFlags[IterationStep::ResetVertices]) { + deepVectorClear(mPrimaryVertices); + deepVectorClear(mPrimaryVerticesLabels); } - clearResizeBoundedVector(mTracks, mNrof, mMemoryPool.get()); - clearResizeBoundedVector(mTracksLabel, mNrof, mMemoryPool.get()); - clearResizeBoundedVector(mLinesLabels, mNrof, mMemoryPool.get()); - clearResizeBoundedVector(mCells, trkParam.CellsPerRoad(), mMemoryPool.get()); - clearResizeBoundedVector(mCellsLookupTable, trkParam.CellsPerRoad() - 1, mMemoryPool.get()); - clearResizeBoundedVector(mCellsNeighbours, trkParam.CellsPerRoad() - 1, mMemoryPool.get()); - clearResizeBoundedVector(mCellsNeighboursLUT, trkParam.CellsPerRoad() - 1, mMemoryPool.get()); - clearResizeBoundedVector(mCellLabels, trkParam.CellsPerRoad(), mMemoryPool.get()); - clearResizeBoundedVector(mTracklets, std::min(trkParam.TrackletsPerRoad(), maxLayers - 1), mMemoryPool.get()); - clearResizeBoundedVector(mTrackletLabels, trkParam.TrackletsPerRoad(), mMemoryPool.get()); - clearResizeBoundedVector(mTrackletsLookupTable, trkParam.TrackletsPerRoad(), mMemoryPool.get()); + clearResizeBoundedVector(mLinesLabels, getNrof(1), mMemoryPool.get()); mIndexTableUtils.setTrackingParameters(trkParam); clearResizeBoundedVector(mPositionResolution, trkParam.NLayers, mMemoryPool.get()); clearResizeBoundedVector(mBogusClusters, trkParam.NLayers, mMemoryPool.get()); deepVectorClear(mTrackletClusters); for (unsigned int iLayer{0}; iLayer < std::min((int)mClusters.size(), maxLayers); ++iLayer) { - clearResizeBoundedVector(mClusters[iLayer], mUnsortedClusters[iLayer].size(), getMaybeFrameworkHostResource(maxLayers != nLayers)); - clearResizeBoundedVector(mUsedClusters[iLayer], mUnsortedClusters[iLayer].size(), getMaybeFrameworkHostResource(maxLayers != nLayers)); - mPositionResolution[iLayer] = o2::gpu::CAMath::Sqrt(0.5f * (trkParam.SystErrorZ2[iLayer] + trkParam.SystErrorY2[iLayer]) + trkParam.LayerResolution[iLayer] * trkParam.LayerResolution[iLayer]); + clearResizeBoundedVector(mClusters[iLayer], mUnsortedClusters[iLayer].size(), getMaybeFrameworkHostResource(maxLayers != NLayers)); + clearResizeBoundedVector(mUsedClusters[iLayer], mUnsortedClusters[iLayer].size(), getMaybeFrameworkHostResource(maxLayers != NLayers)); + mPositionResolution[iLayer] = o2::gpu::CAMath::Sqrt((0.5f * (trkParam.SystErrorZ2[iLayer] + trkParam.SystErrorY2[iLayer])) + (trkParam.LayerResolution[iLayer] * trkParam.LayerResolution[iLayer])); } - clearResizeBoundedArray(mIndexTables, mNrof * (trkParam.ZBins * trkParam.PhiBins + 1), getMaybeFrameworkHostResource(maxLayers != nLayers)); - clearResizeBoundedVector(mLines, mNrof, mMemoryPool.get()); - clearResizeBoundedVector(mTrackletClusters, mNrof, mMemoryPool.get()); + clearResizeBoundedVector(mLines, getNrof(1), mMemoryPool.get()); + clearResizeBoundedVector(mTrackletClusters, getNrof(1), mMemoryPool.get()); + for (int iLayer{0}; iLayer < NLayers; ++iLayer) { + clearResizeBoundedVector(mIndexTables[iLayer], getNrof(iLayer) * ((trkParam.ZBins * trkParam.PhiBins) + 1), getMaybeFrameworkHostResource()); + } for (int iLayer{0}; iLayer < trkParam.NLayers; ++iLayer) { if (trkParam.SystErrorY2[iLayer] > 0.f || trkParam.SystErrorZ2[iLayer] > 0.f) { for (auto& tfInfo : mTrackingFrameInfo[iLayer]) { @@ -319,14 +301,26 @@ void TimeFrame::initialise(const int iteration, const TrackingParameter } } } - mMinR.fill(10000.); - mMaxR.fill(-1.); - } + + mMinR.fill(std::numeric_limits::max()); + mMaxR.fill(std::numeric_limits::min()); + } + clearResizeBoundedVector(mCells, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mCellsLookupTable, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mCellsNeighbours, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mCellsNeighboursTopology, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mCellsNeighboursLUT, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mCellLabels, mTrackingTopologyView.nCells, mMemoryPool.get()); + clearResizeBoundedVector(mTracklets, mTrackingTopologyView.nTransitions, mMemoryPool.get()); + clearResizeBoundedVector(mTrackletLabels, mTrackingTopologyView.nTransitions, mMemoryPool.get()); + clearResizeBoundedVector(mTrackletsLookupTable, mTrackingTopologyView.nTransitions, mMemoryPool.get()); + clearResizeBoundedVector(mTransitionPhiCuts, mTrackingTopologyView.nTransitions, mMemoryPool.get()); + clearResizeBoundedVector(mTransitionMSAngles, mTrackingTopologyView.nTransitions, mMemoryPool.get()); mNTrackletsPerROF.resize(2); for (auto& v : mNTrackletsPerROF) { - v = bounded_vector(mNrof + 1, 0, mMemoryPool.get()); + v = bounded_vector(getNrof(1) + 1, 0, mMemoryPool.get()); } - if (iteration == 0 || iteration == 3) { + if (trkParam.PassFlags[IterationStep::RebuildClusterLUT]) { prepareClusters(trkParam, maxLayers); } mTotalTracklets = {0, 0}; @@ -337,88 +331,82 @@ void TimeFrame::initialise(const int iteration, const TrackingParameter } } - mTotVertPerIteration.resize(1 + iteration); - mNoVertexROF = 0; - deepVectorClear(mRoads); - deepVectorClear(mRoadLabels); - - mMSangles.resize(trkParam.NLayers); - mPhiCuts.resize(mClusters.size() - 1, 0.f); - float oneOverR{0.001f * 0.3f * std::abs(mBz) / trkParam.TrackletMinPt}; - for (unsigned int iLayer{0}; iLayer < nLayers; ++iLayer) { - mMSangles[iLayer] = math_utils::MSangle(0.14f, trkParam.TrackletMinPt, trkParam.LayerxX0[iLayer]); - mPositionResolution[iLayer] = o2::gpu::CAMath::Sqrt(0.5f * (trkParam.SystErrorZ2[iLayer] + trkParam.SystErrorY2[iLayer]) + trkParam.LayerResolution[iLayer] * trkParam.LayerResolution[iLayer]); - if (iLayer < mClusters.size() - 1) { - const float& r1 = trkParam.LayerRadii[iLayer]; - const float& r2 = trkParam.LayerRadii[iLayer + 1]; - oneOverR = (0.5 * oneOverR >= 1.f / r2) ? 2.f / r2 - o2::constants::math::Almost0 : oneOverR; - const float res1 = o2::gpu::CAMath::Hypot(trkParam.PVres, mPositionResolution[iLayer]); - const float res2 = o2::gpu::CAMath::Hypot(trkParam.PVres, mPositionResolution[iLayer + 1]); - const float cosTheta1half = o2::gpu::CAMath::Sqrt(1.f - math_utils::Sq(0.5f * r1 * oneOverR)); - const float cosTheta2half = o2::gpu::CAMath::Sqrt(1.f - math_utils::Sq(0.5f * r2 * oneOverR)); - float x = r2 * cosTheta1half - r1 * cosTheta2half; - float delta = o2::gpu::CAMath::Sqrt(1.f / (1.f - 0.25f * math_utils::Sq(x * oneOverR)) * (math_utils::Sq(0.25f * r1 * r2 * math_utils::Sq(oneOverR) / cosTheta2half + cosTheta1half) * math_utils::Sq(res1) + math_utils::Sq(0.25f * r1 * r2 * math_utils::Sq(oneOverR) / cosTheta1half + cosTheta2half) * math_utils::Sq(res2))); - /// the expression std::asin(0.5f * x * oneOverR) is equivalent to std::aCos(0.5f * r1 * oneOverR) - std::acos(0.5 * r2 * oneOverR) - mPhiCuts[iLayer] = std::min(o2::gpu::CAMath::ASin(0.5f * x * oneOverR) + 2.f * mMSangles[iLayer] + delta, o2::constants::math::PI * 0.5f); - } + // estimate MS per layer + std::array msAngles{}; + for (unsigned int iLayer{0}; iLayer < NLayers; ++iLayer) { + msAngles[iLayer] = math_utils::MSangle(0.14f, trkParam.TrackletMinPt, trkParam.LayerxX0[iLayer]); + mPositionResolution[iLayer] = o2::gpu::CAMath::Sqrt((0.5f * (trkParam.SystErrorZ2[iLayer] + trkParam.SystErrorY2[iLayer])) + (trkParam.LayerResolution[iLayer] * trkParam.LayerResolution[iLayer])); } - for (int iLayer{0}; iLayer < std::min((int)mTracklets.size(), maxLayers); ++iLayer) { - deepVectorClear(mTracklets[iLayer]); - deepVectorClear(mTrackletLabels[iLayer]); - if (iLayer < (int)mCells.size()) { - deepVectorClear(mCells[iLayer]); - deepVectorClear(mTrackletsLookupTable[iLayer]); - mTrackletsLookupTable[iLayer].resize(mClusters[iLayer + 1].size() + 1, 0); - deepVectorClear(mCellLabels[iLayer]); - } - - if (iLayer < (int)mCells.size() - 1) { - deepVectorClear(mCellsLookupTable[iLayer]); - deepVectorClear(mCellsNeighbours[iLayer]); - deepVectorClear(mCellsNeighboursLUT[iLayer]); + // for each transition calculate the phi-cuts + integrated MS + float oneOverR{0.001f * 0.3f * std::abs(mBz) / trkParam.TrackletMinPt}; + for (int transitionId{0}; transitionId < (int)mTracklets.size(); ++transitionId) { + const auto& transition = mTrackingTopologyView.getTransition(transitionId); + float ms2 = 0.; + for (int layer = transition.fromLayer; layer < transition.toLayer; ++layer) { + ms2 += math_utils::Sq(msAngles[layer]); } + mTransitionMSAngles[transitionId] = o2::gpu::CAMath::Sqrt(ms2); + const float& r1 = trkParam.LayerRadii[transition.fromLayer]; + const float& r2 = trkParam.LayerRadii[transition.toLayer]; + oneOverR = (0.5 * oneOverR >= 1.f / r2) ? (2.f / r2) - o2::constants::math::Almost0 : oneOverR; + const float res1 = o2::gpu::CAMath::Hypot(trkParam.PVres, mPositionResolution[transition.fromLayer]); + const float res2 = o2::gpu::CAMath::Hypot(trkParam.PVres, mPositionResolution[transition.toLayer]); + const float cosTheta1half = o2::gpu::CAMath::Sqrt(1.f - math_utils::Sq(0.5f * r1 * oneOverR)); + const float cosTheta2half = o2::gpu::CAMath::Sqrt(1.f - math_utils::Sq(0.5f * r2 * oneOverR)); + float x = (r2 * cosTheta1half) - (r1 * cosTheta2half); + float delta = o2::gpu::CAMath::Sqrt(1.f / (1.f - 0.25f * math_utils::Sq(x * oneOverR)) * (math_utils::Sq((0.25f * r1 * r2 * math_utils::Sq(oneOverR) / cosTheta2half) + cosTheta1half) * math_utils::Sq(res1) + math_utils::Sq((0.25f * r1 * r2 * math_utils::Sq(oneOverR) / cosTheta1half) + cosTheta2half) * math_utils::Sq(res2))); + /// the expression std::asin(0.5f * x * oneOverR) is equivalent to std::aCos(0.5f * r1 * oneOverR) - std::acos(0.5 * r2 * oneOverR) + mTransitionPhiCuts[transitionId] = o2::gpu::CAMath::Min(o2::gpu::CAMath::ASin(0.5f * x * oneOverR) + 2.f * mTransitionMSAngles[transitionId] + delta, o2::constants::math::PI * 0.5f); + + // some cleanup + deepVectorClear(mTracklets[transitionId]); + deepVectorClear(mTrackletLabels[transitionId]); + deepVectorClear(mTrackletsLookupTable[transitionId]); + mTrackletsLookupTable[transitionId].resize(mClusters[transition.fromLayer].size() + 1, 0); + } + + for (int cellId{0}; cellId < (int)mCells.size(); ++cellId) { + deepVectorClear(mCells[cellId]); + deepVectorClear(mCellsLookupTable[cellId]); + deepVectorClear(mCellsNeighbours[cellId]); + deepVectorClear(mCellsNeighboursTopology[cellId]); + deepVectorClear(mCellsNeighboursLUT[cellId]); + deepVectorClear(mCellLabels[cellId]); } } -template -unsigned long TimeFrame::getArtefactsMemory() const +template +unsigned long TimeFrame::getArtefactsMemory() const { unsigned long size{0}; for (const auto& trkl : mTracklets) { size += sizeof(Tracklet) * trkl.size(); } for (const auto& cells : mCells) { - size += sizeof(CellSeedN) * cells.size(); + size += sizeof(CellSeed) * cells.size(); } for (const auto& cellsN : mCellsNeighbours) { size += sizeof(int) * cellsN.size(); } - return size + sizeof(Road) * mRoads.size(); + for (const auto& cellsN : mCellsNeighboursTopology) { + size += sizeof(int) * cellsN.size(); + } + return size; } -template -void TimeFrame::printArtefactsMemory() const +template +void TimeFrame::printArtefactsMemory() const { LOGP(info, "TimeFrame: Artefacts occupy {:.2f} MB", getArtefactsMemory() / constants::MB); } -template -void TimeFrame::fillPrimaryVerticesXandAlpha() -{ - deepVectorClear(mPValphaX); - mPValphaX.reserve(mPrimaryVertices.size()); - for (auto& pv : mPrimaryVertices) { - mPValphaX.emplace_back(std::array{o2::gpu::CAMath::Hypot(pv.getX(), pv.getY()), math_utils::computePhi(pv.getX(), pv.getY())}); - } -} - -template -void TimeFrame::computeTrackletsPerROFScans() +template +void TimeFrame::computeTrackletsPerROFScans() { for (ushort iLayer = 0; iLayer < 2; ++iLayer) { - for (unsigned int iRof{0}; iRof < mNrof; ++iRof) { - if (mMultiplicityCutMask[iRof]) { + for (unsigned int iRof{0}; iRof < getNrof(1); ++iRof) { + if (mROFMaskView.isROFEnabled(1, iRof)) { mTotalTracklets[iLayer] += mNTrackletsPerROF[iLayer][iRof]; } } @@ -427,141 +415,8 @@ void TimeFrame::computeTrackletsPerROFScans() } } -template -void TimeFrame::checkTrackletLUTs() -{ - for (uint32_t iLayer{0}; iLayer < getTracklets().size(); ++iLayer) { - int prev{-1}; - int count{0}; - for (uint32_t iTracklet{0}; iTracklet < getTracklets()[iLayer].size(); ++iTracklet) { - auto& trk = getTracklets()[iLayer][iTracklet]; - int currentId{trk.firstClusterIndex}; - if (currentId < prev) { - LOG(info) << "First Cluster Index not increasing monotonically on L:T:ID:Prev " << iLayer << "\t" << iTracklet << "\t" << currentId << "\t" << prev; - } else if (currentId == prev) { - count++; - } else { - if (iLayer > 0) { - auto& lut{getTrackletsLookupTable()[iLayer - 1]}; - if (count != lut[prev + 1] - lut[prev]) { - LOG(info) << "LUT count broken " << iLayer - 1 << "\t" << prev << "\t" << count << "\t" << lut[prev + 1] << "\t" << lut[prev]; - } - } - count = 1; - } - prev = currentId; - if (iLayer > 0) { - auto& lut{getTrackletsLookupTable()[iLayer - 1]}; - if (iTracklet >= (uint32_t)(lut[currentId + 1]) || iTracklet < (uint32_t)(lut[currentId])) { - LOG(info) << "LUT broken: " << iLayer - 1 << "\t" << currentId << "\t" << iTracklet; - } - } - } - } -} - -template -void TimeFrame::printTrackletLUTonLayer(int i) -{ - LOG(info) << "-------- Tracklet LUT " << i; - std::stringstream s; - for (int j : mTrackletsLookupTable[i]) { - s << j << "\t"; - } - LOG(info) << s.str(); - LOG(info) << "--------"; -} - -template -void TimeFrame::printCellLUTonLayer(int i) -{ - LOG(info) << "-------- Cell LUT " << i; - std::stringstream s; - for (int j : mCellsLookupTable[i]) { - s << j << "\t"; - } - LOG(info) << s.str(); - LOG(info) << "--------"; -} - -template -void TimeFrame::printTrackletLUTs() -{ - for (unsigned int i{0}; i < mTrackletsLookupTable.size(); ++i) { - printTrackletLUTonLayer(i); - } -} - -template -void TimeFrame::printCellLUTs() -{ - for (unsigned int i{0}; i < mCellsLookupTable.size(); ++i) { - printCellLUTonLayer(i); - } -} - -template -void TimeFrame::printVertices() -{ - LOG(info) << "Vertices in ROF (nROF = " << mNrof << ", lut size = " << mROFramesPV.size() << ")"; - for (unsigned int iR{0}; iR < mROFramesPV.size(); ++iR) { - LOG(info) << mROFramesPV[iR] << "\t"; - } - LOG(info) << "\n\n Vertices:"; - for (unsigned int iV{0}; iV < mPrimaryVertices.size(); ++iV) { - LOG(info) << mPrimaryVertices[iV].getX() << "\t" << mPrimaryVertices[iV].getY() << "\t" << mPrimaryVertices[iV].getZ(); - } - LOG(info) << "--------"; -} - -template -void TimeFrame::printROFoffsets() -{ - LOG(info) << "--------"; - for (unsigned int iLayer{0}; iLayer < mROFramesClusters.size(); ++iLayer) { - LOG(info) << "Layer " << iLayer; - std::stringstream s; - for (auto value : mROFramesClusters[iLayer]) { - s << value << "\t"; - } - LOG(info) << s.str(); - } -} - -template -void TimeFrame::printNClsPerROF() -{ - LOG(info) << "--------"; - for (unsigned int iLayer{0}; iLayer < mNClustersPerROF.size(); ++iLayer) { - LOG(info) << "Layer " << iLayer; - std::stringstream s; - for (auto& value : mNClustersPerROF[iLayer]) { - s << value << "\t"; - } - LOG(info) << s.str(); - } -} - -template -void TimeFrame::printSliceInfo(const int startROF, const int sliceSize) -{ - LOG(info) << "Dumping slice of " << sliceSize << " rofs:"; - for (int iROF{startROF}; iROF < startROF + sliceSize; ++iROF) { - LOG(info) << "ROF " << iROF << " dump:"; - for (unsigned int iLayer{0}; iLayer < mClusters.size(); ++iLayer) { - LOG(info) << "Layer " << iLayer << " has: " << getClustersOnLayer(iROF, iLayer).size() << " clusters."; - } - LOG(info) << "Number of seeding vertices: " << getPrimaryVertices(iROF).size(); - int iVertex{0}; - for (auto& v : getPrimaryVertices(iROF)) { - LOG(info) << "\t vertex " << iVertex++ << ": x=" << v.getX() << " " - << " y=" << v.getY() << " z=" << v.getZ() << " has " << v.getNContributors() << " contributors."; - } - } -} - -template -void TimeFrame::setMemoryPool(std::shared_ptr pool) +template +void TimeFrame::setMemoryPool(std::shared_ptr pool) { mMemoryPool = pool; @@ -577,33 +432,29 @@ void TimeFrame::setMemoryPool(std::shared_ptr po }; // these will only reside on the host for the cpu part - initVector(mTotVertPerIteration); initContainers(mClusterExternalIndices); initContainers(mNTrackletsPerCluster); initContainers(mNTrackletsPerClusterSum); initContainers(mNClustersPerROF); - initVector(mROFramesPV); initVector(mPrimaryVertices); - initVector(mRoads); - initVector(mMSangles); - initVector(mPhiCuts); + initVector(mTransitionPhiCuts); + initVector(mTransitionMSAngles); initVector(mPositionResolution); - initVector(mClusterSize); + initContainers(mClusterSize); initVector(mPValphaX); initVector(mBogusClusters); initContainers(mTrackletsIndexROF); - initContainers(mTracks); + initVector(mTracks); initContainers(mTracklets); initContainers(mCells); initContainers(mCellsNeighbours); initContainers(mCellsLookupTable); // MC info (we don't know if we have MC) - initVector(mVerticesContributorLabels); + initVector(mPrimaryVerticesLabels); initContainers(mLinesLabels); initContainers(mTrackletLabels); initContainers(mCellLabels); - initVector(mRoadLabels); - initContainers(mTracksLabel); + initVector(mTracksLabel); // these will use possibly an externally provided allocator initContainers(mClusters, hasFrameworkAllocator()); initContainers(mUsedClusters, hasFrameworkAllocator()); @@ -613,32 +464,30 @@ void TimeFrame::setMemoryPool(std::shared_ptr po initContainers(mROFramesClusters, hasFrameworkAllocator()); } -template -void TimeFrame::setFrameworkAllocator(ExternalAllocator* ext) +template +void TimeFrame::setFrameworkAllocator(ExternalAllocator* ext) { mExternalAllocator = ext; mExtMemoryPool = std::make_shared(mExternalAllocator); } -template -void TimeFrame::wipe() +template +void TimeFrame::wipe() { deepVectorClear(mTracks); deepVectorClear(mTracklets); deepVectorClear(mCells); - deepVectorClear(mRoads); deepVectorClear(mCellsNeighbours); + deepVectorClear(mCellsNeighboursTopology); deepVectorClear(mCellsLookupTable); - deepVectorClear(mTotVertPerIteration); deepVectorClear(mPrimaryVertices); deepVectorClear(mTrackletsLookupTable); deepVectorClear(mClusterExternalIndices); deepVectorClear(mNTrackletsPerCluster); deepVectorClear(mNTrackletsPerClusterSum); deepVectorClear(mNClustersPerROF); - deepVectorClear(mROFramesPV); - deepVectorClear(mMSangles); - deepVectorClear(mPhiCuts); + deepVectorClear(mTransitionPhiCuts); + deepVectorClear(mTransitionMSAngles); deepVectorClear(mPositionResolution); deepVectorClear(mClusterSize); deepVectorClear(mPValphaX); @@ -659,10 +508,9 @@ void TimeFrame::wipe() // only needed to clear if we have MC info if (hasMCinformation()) { deepVectorClear(mLinesLabels); - deepVectorClear(mVerticesContributorLabels); + deepVectorClear(mPrimaryVerticesLabels); deepVectorClear(mTrackletLabels); deepVectorClear(mCellLabels); - deepVectorClear(mRoadLabels); deepVectorClear(mTracksLabel); } } diff --git a/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx b/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx index 658a90b37613f..f17d961fc7bb7 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx @@ -14,58 +14,44 @@ /// #include "ITStracking/Tracker.h" - #include "ITStracking/BoundedAllocator.h" -#include "ITStracking/Cell.h" #include "ITStracking/Constants.h" -#include "ITStracking/IndexTableUtils.h" -#include "ITStracking/Tracklet.h" #include "ITStracking/TrackerTraits.h" #include "ITStracking/TrackingConfigParam.h" -#include "ReconstructionDataFormats/Track.h" #include #include #include #include -#include namespace o2::its { using o2::its::constants::GB; -template -Tracker::Tracker(TrackerTraits* traits) : mTraits(traits) +template +Tracker::Tracker(TrackerTraits* traits) : mTraits(traits) { - /// Initialise standard configuration with 1 iteration - mTrkParams.resize(1); - if (traits->isGPU()) { - ITSGpuTrackingParamConfig::Instance().maybeOverride(); - ITSGpuTrackingParamConfig::Instance().printKeyValues(true, true); - } } -template -void Tracker::clustersToTracks(const LogFunc& logger, const LogFunc& error) +template +float Tracker::clustersToTracks(const LogFunc& logger, const LogFunc& error) { LogFunc evalLog = [](const std::string&) {}; - double total{0}; + float total{0}; mTraits->updateTrackingParameters(mTrkParams); + int maxNvertices{-1}; if (mTrkParams[0].PerPrimaryVertexProcessing) { - for (int iROF{0}; iROF < mTimeFrame->getNrof(); ++iROF) { - int minRof = o2::gpu::CAMath::Max(0, iROF - mTrkParams[0].DeltaROF); - int maxRof = o2::gpu::CAMath::Min(mTimeFrame->getNrof(), iROF + mTrkParams[0].DeltaROF); - maxNvertices = std::max(maxNvertices, (int)mTimeFrame->getPrimaryVertices(minRof, maxRof).size()); - } + maxNvertices = mTimeFrame->getROFVertexLookupTableView().getMaxVerticesPerROF(); } - int iteration{0}, iROFs{0}, iVertex{0}; + int iteration{0}, iVertex{0}; auto handleException = [&](const auto& err) { - LOGP(error, "Too much memory used during {} in iteration {} in ROF span {}-{} iVtx={}: {:.2f} GB. Current limit is {:.2f} GB, check the detector status and/or the selections.", - StateNames[mCurState], iteration, iROFs, iROFs + mTrkParams[iteration].nROFsPerIterations, iVertex, - (double)mTimeFrame->getArtefactsMemory() / GB, (double)mTrkParams[iteration].MaxMemory / GB); + LOGP(error, "Too much memory in {} in iteration {} iVtx={}: {:.2f} GB. Current limit is {:.2f} GB, check the detector status and/or the selections.", + StateNames[mCurStep], iteration, iVertex, + (double)mTimeFrame->getArtefactsMemory() / GB, + (double)mTrkParams[iteration].MaxMemory / GB); if (typeid(err) != typeid(std::bad_alloc)) { // only print if the exceptions is different from what is expected LOGP(error, "Exception: {}", err.what()); } @@ -73,7 +59,7 @@ void Tracker::clustersToTracks(const LogFunc& logger, const LogFunc& er mMemoryPool->print(); mTimeFrame->wipe(); ++mNumberOfDroppedTFs; - error("...Dropping Timeframe..."); + error(std::format("...Dropping TimeSlice {} (out of {} dropped {})...", mTimeSlice, mTimeFrameCounter, mNumberOfDroppedTFs)); } else { throw err; } @@ -82,173 +68,73 @@ void Tracker::clustersToTracks(const LogFunc& logger, const LogFunc& er try { for (iteration = 0; iteration < (int)mTrkParams.size(); ++iteration) { mMemoryPool->setMaxMemory(mTrkParams[iteration].MaxMemory); - if (iteration == 3 && mTrkParams[0].DoUPCIteration) { - mTimeFrame->swapMasks(); + if (mTrkParams[iteration].PassFlags[IterationStep::UseUPCMask]) { + mTimeFrame->useUPCMask(); } - double timeTracklets{0.}, timeCells{0.}, timeNeighbours{0.}, timeRoads{0.}; - int nTracklets{0}, nCells{0}, nNeighbours{0}, nTracks{-static_cast(mTimeFrame->getNumberOfTracks())}; - int nROFsIterations = (mTrkParams[iteration].nROFsPerIterations > 0 && !mTimeFrame->isGPU()) ? mTimeFrame->getNrof() / mTrkParams[iteration].nROFsPerIterations + bool(mTimeFrame->getNrof() % mTrkParams[iteration].nROFsPerIterations) : 1; + float timeFrame{0.}, timeTracklets{0.}, timeCells{0.}, timeNeighbours{0.}, timeRoads{0.}; + size_t nTracklets{0}, nCells{0}, nNeighbours{0}; + int nTracks{-static_cast(mTimeFrame->getNumberOfTracks())}; iVertex = std::min(maxNvertices, 0); logger(std::format("==== ITS {} Tracking iteration {} summary ====", mTraits->getName(), iteration)); - - total += evaluateTask(&Tracker::initialiseTimeFrame, StateNames[mCurState = TFInit], iteration, logger, iteration); + total += timeFrame = evaluateTask(&Tracker::initialiseTimeFrame, StateNames[mCurStep = TFInit], iteration, evalLog, iteration); + logger(std::format(" - TimeFrame initialisation completed in {:.2f} ms", timeFrame)); do { - for (iROFs = 0; iROFs < nROFsIterations; ++iROFs) { - timeTracklets += evaluateTask(&Tracker::computeTracklets, StateNames[mCurState = Trackleting], iteration, evalLog, iteration, iROFs, iVertex); - nTracklets += mTraits->getTFNumberOfTracklets(); - float trackletsPerCluster = mTraits->getTFNumberOfClusters() > 0 ? float(mTraits->getTFNumberOfTracklets()) / float(mTraits->getTFNumberOfClusters()) : 0.f; - if (trackletsPerCluster > mTrkParams[iteration].TrackletsPerClusterLimit) { - error(std::format("Too many tracklets per cluster ({}) in iteration {} in ROF span {}-{}:, check the detector status and/or the selections. Current limit is {}", - trackletsPerCluster, iteration, iROFs, iROFs + mTrkParams[iteration].nROFsPerIterations, mTrkParams[iteration].TrackletsPerClusterLimit)); - break; - } - timeCells += evaluateTask(&Tracker::computeCells, StateNames[mCurState = Celling], iteration, evalLog, iteration); - nCells += mTraits->getTFNumberOfCells(); - float cellsPerCluster = mTraits->getTFNumberOfClusters() > 0 ? float(mTraits->getTFNumberOfCells()) / float(mTraits->getTFNumberOfClusters()) : 0.f; - if (cellsPerCluster > mTrkParams[iteration].CellsPerClusterLimit) { - error(std::format("Too many cells per cluster ({}) in iteration {} in ROF span {}-{}, check the detector status and/or the selections. Current limit is {}", - cellsPerCluster, iteration, iROFs, iROFs + mTrkParams[iteration].nROFsPerIterations, mTrkParams[iteration].CellsPerClusterLimit)); - break; - } - timeNeighbours += evaluateTask(&Tracker::findCellsNeighbours, StateNames[mCurState = Neighbouring], iteration, evalLog, iteration); - nNeighbours += mTimeFrame->getNumberOfNeighbours(); - timeRoads += evaluateTask(&Tracker::findRoads, StateNames[mCurState = Roading], iteration, evalLog, iteration); - } + timeTracklets += evaluateTask(&Tracker::computeTracklets, StateNames[mCurStep = Trackleting], iteration, evalLog, iteration, iVertex); + nTracklets += mTraits->getTFNumberOfTracklets(); + timeCells += evaluateTask(&Tracker::computeCells, StateNames[mCurStep = Celling], iteration, evalLog, iteration); + nCells += mTraits->getTFNumberOfCells(); + timeNeighbours += evaluateTask(&Tracker::findCellsNeighbours, StateNames[mCurStep = Neighbouring], iteration, evalLog, iteration); + nNeighbours += mTimeFrame->getNumberOfNeighbours(); + timeRoads += evaluateTask(&Tracker::findRoads, StateNames[mCurStep = Roading], iteration, evalLog, iteration); } while (++iVertex < maxNvertices); logger(std::format(" - Tracklet finding: {} tracklets found in {:.2f} ms", nTracklets, timeTracklets)); logger(std::format(" - Cell finding: {} cells found in {:.2f} ms", nCells, timeCells)); logger(std::format(" - Neighbours finding: {} neighbours found in {:.2f} ms", nNeighbours, timeNeighbours)); logger(std::format(" - Track finding: {} tracks found in {:.2f} ms", nTracks + mTimeFrame->getNumberOfTracks(), timeRoads)); total += timeTracklets + timeCells + timeNeighbours + timeRoads; - if (mTraits->supportsExtendTracks() && mTrkParams[iteration].UseTrackFollower) { - int nExtendedTracks{-mTimeFrame->mNExtendedTracks}, nExtendedClusters{-mTimeFrame->mNExtendedUsedClusters}; - auto timeExtending = evaluateTask(&Tracker::extendTracks, "Extending tracks", iteration, evalLog, iteration); - total += timeExtending; - logger(std::format(" - Extending Tracks: {} extended tracks using {} clusters found in {:.2f} ms", nExtendedTracks + mTimeFrame->mNExtendedTracks, nExtendedClusters + mTimeFrame->mNExtendedUsedClusters, timeExtending)); - } - if (mTrkParams[iteration].PrintMemory) { - mMemoryPool->print(); - } - } - if (mTraits->supportsFindShortPrimaries() && mTrkParams[0].FindShortTracks) { - auto nTracksB = mTimeFrame->getNumberOfTracks(); - total += evaluateTask(&Tracker::findShortPrimaries, "Short primaries finding", 0, logger); - auto nTracksA = mTimeFrame->getNumberOfTracks(); - logger(std::format(" `-> found {} additional tracks", nTracksA - nTracksB)); - } - if constexpr (constants::DoTimeBenchmarks) { - logger(std::format("=== TimeFrame {} processing completed in: {:.2f} ms using {} thread(s) ===", mTimeFrameCounter, total, mTraits->getNThreads())); } } catch (const BoundedMemoryResource::MemoryLimitExceeded& err) { handleException(err); - return; + return -1.f; } catch (const std::bad_alloc& err) { handleException(err); - return; + return -1.f; } catch (const std::exception& err) { error(std::format("Uncaught exception, all bets are off... {}", err.what())); // clear tracks explicitly since if not fatalising on exception this may contain partial output - for (int iROF{0}; iROF < mTimeFrame->getNrof(); ++iROF) { - mTimeFrame->getTracks(iROF).clear(); - } - return; + mTimeFrame->getTracks().clear(); + return -1.f; } if (mTimeFrame->hasMCinformation()) { computeTracksMClabels(); } rectifyClusterIndices(); + sortTracks(); + ++mTimeFrameCounter; mTotalTime += total; - if (mTrkParams[0].PrintMemory) { - mTimeFrame->printArtefactsMemory(); - mMemoryPool->print(); - } + return total; } -template -void Tracker::computeRoadsMClabels() +template +void Tracker::computeTracksMClabels() { - /// Moore's Voting Algorithm - if (!mTimeFrame->hasMCinformation()) { - return; - } - - mTimeFrame->initialiseRoadLabels(); - - int roadsNum{static_cast(mTimeFrame->getRoads().size())}; - - for (int iRoad{0}; iRoad < roadsNum; ++iRoad) { - - auto& currentRoad{mTimeFrame->getRoads()[iRoad]}; + for (auto& track : mTimeFrame->getTracks()) { std::vector> occurrences; - bool isFakeRoad{false}; - bool isFirstRoadCell{true}; - - for (int iCell{0}; iCell < mTrkParams[0].CellsPerRoad(); ++iCell) { - const int currentCellIndex{currentRoad[iCell]}; - - if (currentCellIndex == constants::UnusedIndex) { - if (isFirstRoadCell) { - continue; - } else { - break; - } - } - - const auto& currentCell{mTimeFrame->getCells()[iCell][currentCellIndex]}; - - if (isFirstRoadCell) { - - const int cl0index{mTimeFrame->getClusters()[iCell][currentCell.getFirstClusterIndex()].clusterId}; - auto cl0labs{mTimeFrame->getClusterLabels(iCell, cl0index)}; - bool found{false}; - for (size_t iOcc{0}; iOcc < occurrences.size(); ++iOcc) { - std::pair& occurrence = occurrences[iOcc]; - for (const auto& label : cl0labs) { - if (label == occurrence.first) { - ++occurrence.second; - found = true; - // break; // uncomment to stop to the first hit - } - } - } - if (!found) { - for (const auto& label : cl0labs) { - occurrences.emplace_back(label, 1); - } - } - - const int cl1index{mTimeFrame->getClusters()[iCell + 1][currentCell.getSecondClusterIndex()].clusterId}; + occurrences.clear(); - const auto& cl1labs{mTimeFrame->getClusterLabels(iCell + 1, cl1index)}; - found = false; - for (size_t iOcc{0}; iOcc < occurrences.size(); ++iOcc) { - std::pair& occurrence = occurrences[iOcc]; - for (auto& label : cl1labs) { - if (label == occurrence.first) { - ++occurrence.second; - found = true; - // break; // uncomment to stop to the first hit - } - } - } - if (!found) { - for (auto& label : cl1labs) { - occurrences.emplace_back(label, 1); - } - } - - isFirstRoadCell = false; + for (int iCluster = 0; iCluster < TrackITSExt::MaxClusters; ++iCluster) { + const int index = track.getClusterIndex(iCluster); + if (index == constants::UnusedIndex) { + continue; } - - const int cl2index{mTimeFrame->getClusters()[iCell + 2][currentCell.getThirdClusterIndex()].clusterId}; - const auto& cl2labs{mTimeFrame->getClusterLabels(iCell + 2, cl2index)}; + auto labels = mTimeFrame->getClusterLabels(iCluster, index); bool found{false}; for (size_t iOcc{0}; iOcc < occurrences.size(); ++iOcc) { std::pair& occurrence = occurrences[iOcc]; - for (auto& label : cl2labs) { + for (const auto& label : labels) { if (label == occurrence.first) { ++occurrence.second; found = true; @@ -257,108 +143,119 @@ void Tracker::computeRoadsMClabels() } } if (!found) { - for (auto& label : cl2labs) { + for (const auto& label : labels) { occurrences.emplace_back(label, 1); } } } - - std::sort(occurrences.begin(), occurrences.end(), [](auto e1, auto e2) { + std::sort(std::begin(occurrences), std::end(occurrences), [](auto e1, auto e2) { return e1.second > e2.second; }); auto maxOccurrencesValue = occurrences[0].first; - mTimeFrame->setRoadLabel(iRoad, maxOccurrencesValue.getRawValue(), isFakeRoad); - } -} - -template -void Tracker::computeTracksMClabels() -{ - for (int iROF{0}; iROF < mTimeFrame->getNrof(); ++iROF) { - for (auto& track : mTimeFrame->getTracks(iROF)) { - std::vector> occurrences; - occurrences.clear(); - - for (int iCluster = 0; iCluster < TrackITSExt::MaxClusters; ++iCluster) { - const int index = track.getClusterIndex(iCluster); - if (index == constants::UnusedIndex) { - continue; - } - auto labels = mTimeFrame->getClusterLabels(iCluster, index); - bool found{false}; - for (size_t iOcc{0}; iOcc < occurrences.size(); ++iOcc) { - std::pair& occurrence = occurrences[iOcc]; - for (const auto& label : labels) { - if (label == occurrence.first) { - ++occurrence.second; - found = true; - // break; // uncomment to stop to the first hit - } - } - } - if (!found) { - for (const auto& label : labels) { - occurrences.emplace_back(label, 1); + uint32_t pattern = track.getPattern(); + // set fake clusters pattern + for (int ic{TrackITSExt::MaxClusters}; ic--;) { + auto clid = track.getClusterIndex(ic); + if (clid != constants::UnusedIndex) { + auto labelsSpan = mTimeFrame->getClusterLabels(ic, clid); + for (const auto& currentLabel : labelsSpan) { + if (currentLabel == maxOccurrencesValue) { + pattern |= 0x1 << (16 + ic); // set bit if correct + break; } } } - std::sort(std::begin(occurrences), std::end(occurrences), [](auto e1, auto e2) { - return e1.second > e2.second; - }); + } + track.setPattern(pattern); + if (occurrences[0].second < track.getNumberOfClusters()) { + maxOccurrencesValue.setFakeFlag(); + } + mTimeFrame->getTracksLabel().emplace_back(maxOccurrencesValue); + } +} - auto maxOccurrencesValue = occurrences[0].first; - uint32_t pattern = track.getPattern(); - // set fake clusters pattern - for (int ic{TrackITSExt::MaxClusters}; ic--;) { - auto clid = track.getClusterIndex(ic); - if (clid != constants::UnusedIndex) { - auto labelsSpan = mTimeFrame->getClusterLabels(ic, clid); - for (const auto& currentLabel : labelsSpan) { - if (currentLabel == maxOccurrencesValue) { - pattern |= 0x1 << (16 + ic); // set bit if correct - break; - } - } - } - } - track.setPattern(pattern); - if (occurrences[0].second < track.getNumberOfClusters()) { - maxOccurrencesValue.setFakeFlag(); +template +void Tracker::rectifyClusterIndices() +{ + for (auto& track : mTimeFrame->getTracks()) { + for (int iCluster = 0; iCluster < TrackITSExt::MaxClusters; ++iCluster) { + const int index = track.getClusterIndex(iCluster); + if (index != constants::UnusedIndex) { + track.setExternalClusterIndex(iCluster, mTimeFrame->getClusterExternalIndex(iCluster, index)); } - mTimeFrame->getTracksLabel(iROF).emplace_back(maxOccurrencesValue); } } } -template -void Tracker::rectifyClusterIndices() +template +void Tracker::sortTracks() { - for (int iROF{0}; iROF < mTimeFrame->getNrof(); ++iROF) { - for (auto& track : mTimeFrame->getTracks(iROF)) { - for (int iCluster = 0; iCluster < TrackITSExt::MaxClusters; ++iCluster) { - const int index = track.getClusterIndex(iCluster); - if (index != constants::UnusedIndex) { - track.setExternalClusterIndex(iCluster, mTimeFrame->getClusterExternalIndex(iCluster, index)); - } - } + auto& trks = mTimeFrame->getTracks(); + bounded_vector indices(trks.size(), mMemoryPool.get()); + std::iota(indices.begin(), indices.end(), 0); + std::sort(indices.begin(), indices.end(), [&trks](size_t i, size_t j) { + // provide tracks sorted by lower-bound + const auto& a = trks[i]; + const auto& b = trks[j]; + const auto aLower = a.getTimeStamp().getTimeStamp() - a.getTimeStamp().getTimeStampError(); + const auto bLower = b.getTimeStamp().getTimeStamp() - b.getTimeStamp().getTimeStampError(); + if (aLower != bLower) { + return aLower < bLower; + } + return a.isBetter(b, 1e9); // then sort tracks in quality + }); + bounded_vector sortedTrks(mMemoryPool.get()); + sortedTrks.reserve(trks.size()); + for (size_t idx : indices) { + sortedTrks.push_back(trks[idx]); + } + trks.swap(sortedTrks); + if (mTimeFrame->hasMCinformation()) { + auto& trksLabels = mTimeFrame->getTracksLabel(); + bounded_vector sortedLabels(mMemoryPool.get()); + sortedLabels.reserve(trksLabels.size()); + for (size_t idx : indices) { + sortedLabels.push_back(trksLabels[idx]); } + trksLabels.swap(sortedLabels); } } -template -void Tracker::adoptTimeFrame(TimeFrame& tf) +template +void Tracker::adoptTimeFrame(TimeFrame& tf) { mTimeFrame = &tf; mTraits->adoptTimeFrame(&tf); } -template -void Tracker::printSummary() const +template +void Tracker::addTimingStatCurStep(int iteration, double timeMs) +{ + if (iteration < 0) { + return; + } + if (mTimingStats.size() < (iteration + 1)) { + mTimingStats.resize(iteration + 1); + } + mTimingStats[iteration][mCurStep].add(timeMs); +} + +template +void Tracker::printSummary() const { auto avgTF = mTotalTime * 1.e-3 / ((mTimeFrameCounter > 0) ? (double)mTimeFrameCounter : -1.0); auto avgTFwithDropped = mTotalTime * 1.e-3 / (((mTimeFrameCounter + mNumberOfDroppedTFs) > 0) ? (double)(mTimeFrameCounter + mNumberOfDroppedTFs) : -1.0); LOGP(info, "Tracker summary: Processed {} TFs (dropped {}) in TOT={:.2f} s, AVG/TF={:.2f} ({:.2f}) s", mTimeFrameCounter, mNumberOfDroppedTFs, mTotalTime * 1.e-3, avgTF, avgTFwithDropped); + for (size_t iteration = 0; iteration < mTimingStats.size(); ++iteration) { + for (size_t state = 0; state < NSteps; ++state) { + const auto& stats = mTimingStats[iteration][state]; + if (!stats.calls) { + continue; + } + LOGP(info, " - iter {} {}: calls={} total={:.2f} ms avg={:.2f} ms", iteration, StateNames[state], stats.calls, stats.totalTimeMs, stats.averageTimeMs()); + } + } } template class Tracker<7>; diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx index da7c9afdd3ed6..c4439dc74d29e 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx @@ -14,31 +14,24 @@ /// #include -#include #include -#include +#include #include -#ifdef OPTIMISATION_OUTPUT -#include -#include -#endif - #include -#include +#include -#include "CommonConstants/MathConstants.h" #include "DetectorsBase/Propagator.h" #include "GPUCommonMath.h" +#include "ITStracking/BoundedAllocator.h" #include "ITStracking/Cell.h" #include "ITStracking/Constants.h" -#include "ITStracking/TrackerTraits.h" -#include "ITStracking/BoundedAllocator.h" #include "ITStracking/IndexTableUtils.h" +#include "ITStracking/LayerMask.h" +#include "ITStracking/ROFLookupTables.h" +#include "ITStracking/TrackerTraits.h" +#include "ITStracking/TrackHelpers.h" #include "ITStracking/Tracklet.h" -#include "ReconstructionDataFormats/Track.h" - -using o2::base::PropagatorF; namespace o2::its { @@ -49,78 +42,77 @@ struct PassMode { using TwoPassInsert = std::integral_constant; }; -template -void TrackerTraits::computeLayerTracklets(const int iteration, int iROFslice, int iVertex) +template +void TrackerTraits::computeLayerTracklets(const int iteration, int iVertex) { -#ifdef OPTIMISATION_OUTPUT - static int iter{0}; - std::ofstream off(std::format("tracklets{}.txt", iter++)); -#endif - - for (int iLayer = 0; iLayer < mTrkParams[iteration].TrackletsPerRoad(); ++iLayer) { - mTimeFrame->getTracklets()[iLayer].clear(); - mTimeFrame->getTrackletsLabel(iLayer).clear(); - if (iLayer > 0) { - std::fill(mTimeFrame->getTrackletsLookupTable()[iLayer - 1].begin(), - mTimeFrame->getTrackletsLookupTable()[iLayer - 1].end(), 0); - } + const auto topology = mTimeFrame->getTrackingTopologyView(); + for (int transitionId = 0; transitionId < topology.nTransitions; ++transitionId) { + mTimeFrame->getTracklets()[transitionId].clear(); + mTimeFrame->getTrackletsLabel(transitionId).clear(); + std::fill(mTimeFrame->getTrackletsLookupTable()[transitionId].begin(), mTimeFrame->getTrackletsLookupTable()[transitionId].end(), 0); } - const Vertex diamondVert({mTrkParams[iteration].Diamond[0], mTrkParams[iteration].Diamond[1], mTrkParams[iteration].Diamond[2]}, {25.e-6f, 0.f, 0.f, 25.e-6f, 0.f, 36.f}, 1, 1.f); + const Vertex diamondVert(mTrkParams[iteration].Diamond, mTrkParams[iteration].DiamondCov, 1, 1.f); gsl::span diamondSpan(&diamondVert, 1); - int startROF{mTrkParams[iteration].nROFsPerIterations > 0 ? iROFslice * mTrkParams[iteration].nROFsPerIterations : 0}; - int endROF{o2::gpu::GPUCommonMath::Min(mTrkParams[iteration].nROFsPerIterations > 0 ? (iROFslice + 1) * mTrkParams[iteration].nROFsPerIterations + mTrkParams[iteration].DeltaROF : mTimeFrame->getNrof(), mTimeFrame->getNrof())}; mTaskArena->execute([&] { - auto forTracklets = [&](auto Tag, int iLayer, int pivotROF, int base, int& offset) -> int { - if (!mTimeFrame->mMultiplicityCutMask[pivotROF]) { + auto forTracklets = [&](auto Tag, int transitionId, int pivotROF, int base, int& offset) -> int { + const auto& transition = topology.getTransition(transitionId); + if (!mTimeFrame->getROFMaskView().isROFEnabled(transition.fromLayer, pivotROF)) { return 0; } - int minROF = o2::gpu::CAMath::Max(startROF, pivotROF - mTrkParams[iteration].DeltaROF); - int maxROF = o2::gpu::CAMath::Min(endROF - 1, pivotROF + mTrkParams[iteration].DeltaROF); - gsl::span primaryVertices = mTrkParams[iteration].UseDiamond ? diamondSpan : mTimeFrame->getPrimaryVertices(minROF, maxROF); + gsl::span primaryVertices = mTrkParams[iteration].UseDiamond ? diamondSpan : mTimeFrame->getPrimaryVertices(transition.fromLayer, pivotROF); if (primaryVertices.empty()) { return 0; } const int startVtx = iVertex >= 0 ? iVertex : 0; const int endVtx = iVertex >= 0 ? o2::gpu::CAMath::Min(iVertex + 1, int(primaryVertices.size())) : int(primaryVertices.size()); - if (endVtx <= startVtx) { + if (endVtx <= startVtx || (iVertex + 1) > primaryVertices.size()) { + return 0; + } + + const auto& rofOverlap = mTimeFrame->getROFOverlapTableView().getOverlap(transition.fromLayer, transition.toLayer, pivotROF); + if (!rofOverlap.getEntries()) { return 0; } int localCount = 0; - auto& tracklets = mTimeFrame->getTracklets()[iLayer]; - auto layer0 = mTimeFrame->getClustersOnLayer(pivotROF, iLayer); + auto& tracklets = mTimeFrame->getTracklets()[transitionId]; + auto layer0 = mTimeFrame->getClustersOnLayer(pivotROF, transition.fromLayer); if (layer0.empty()) { return 0; } - const float meanDeltaR = mTrkParams[iteration].LayerRadii[iLayer + 1] - mTrkParams[iteration].LayerRadii[iLayer]; + const float meanDeltaR = mTrkParams[iteration].LayerRadii[transition.toLayer] - mTrkParams[iteration].LayerRadii[transition.fromLayer]; + const float phiCut = mTimeFrame->getTransitionPhiCut(transitionId); + const float msAngle = mTimeFrame->getTransitionMSAngle(transitionId); for (int iCluster = 0; iCluster < int(layer0.size()); ++iCluster) { const Cluster& currentCluster = layer0[iCluster]; - const int currentSortedIndex = mTimeFrame->getSortedIndex(pivotROF, iLayer, iCluster); - if (mTimeFrame->isClusterUsed(iLayer, currentCluster.clusterId)) { + const int currentSortedIndex = mTimeFrame->getSortedIndex(pivotROF, transition.fromLayer, iCluster); + if (mTimeFrame->isClusterUsed(transition.fromLayer, currentCluster.clusterId)) { continue; } const float inverseR0 = 1.f / currentCluster.radius; for (int iV = startVtx; iV < endVtx; ++iV) { const auto& pv = primaryVertices[iV]; - if ((pv.isFlagSet(Vertex::Flags::UPCMode) && iteration != 3) || (iteration == 3 && !pv.isFlagSet(Vertex::Flags::UPCMode))) { + if (!mTimeFrame->getROFVertexLookupTableView().isVertexCompatible(transition.fromLayer, pivotROF, pv)) { continue; } - - const float resolution = o2::gpu::CAMath::Sqrt(math_utils::Sq(mTimeFrame->getPositionResolution(iLayer)) + math_utils::Sq(mTrkParams[iteration].PVres) / float(pv.getNContributors())); + if (pv.isFlagSet(Vertex::Flags::UPCMode) != mTrkParams[iteration].PassFlags[IterationStep::SelectUPCVertices]) { + continue; + } + const float resolution = o2::gpu::CAMath::Sqrt(math_utils::Sq(mTimeFrame->getPositionResolution(transition.fromLayer)) + math_utils::Sq(mTrkParams[iteration].PVres) / float(pv.getNContributors())); const float tanLambda = (currentCluster.zCoordinate - pv.getZ()) * inverseR0; - const float zAtRmin = tanLambda * (mTimeFrame->getMinR(iLayer + 1) - currentCluster.radius) + currentCluster.zCoordinate; - const float zAtRmax = tanLambda * (mTimeFrame->getMaxR(iLayer + 1) - currentCluster.radius) + currentCluster.zCoordinate; + const float zAtRmin = tanLambda * (mTimeFrame->getMinR(transition.toLayer) - currentCluster.radius) + currentCluster.zCoordinate; + const float zAtRmax = tanLambda * (mTimeFrame->getMaxR(transition.toLayer) - currentCluster.radius) + currentCluster.zCoordinate; const float sqInvDeltaZ0 = 1.f / (math_utils::Sq(currentCluster.zCoordinate - pv.getZ()) + constants::Tolerance); - const float sigmaZ = o2::gpu::CAMath::Sqrt( - math_utils::Sq(resolution) * math_utils::Sq(tanLambda) * ((math_utils::Sq(inverseR0) + sqInvDeltaZ0) * math_utils::Sq(meanDeltaR) + 1.f) + math_utils::Sq(meanDeltaR * mTimeFrame->getMSangle(iLayer))); - - auto bins = getBinsRect(currentCluster, iLayer + 1, zAtRmin, zAtRmax, sigmaZ * mTrkParams[iteration].NSigmaCut, mTimeFrame->getPhiCut(iLayer)); - if (bins.x == 0 && bins.y == 0 && bins.z == 0 && bins.w == 0) { + const float sigmaZ = o2::gpu::CAMath::Sqrt((math_utils::Sq(resolution) * math_utils::Sq(tanLambda) * ((math_utils::Sq(inverseR0) + sqInvDeltaZ0) * math_utils::Sq(meanDeltaR) + 1.f)) + math_utils::Sq(meanDeltaR * msAngle)); + const auto bins = o2::its::getBinsRect(currentCluster, transition.toLayer, zAtRmin, zAtRmax, + sigmaZ * mTrkParams[iteration].NSigmaCut, phiCut, + mTimeFrame->getIndexTableUtils()); + if (bins.x < 0) { continue; } int phiBinsNum = bins.w - bins.y + 1; @@ -128,60 +120,47 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iROF phiBinsNum += mTrkParams[iteration].PhiBins; } - for (int targetROF{minROF}; targetROF <= maxROF; ++targetROF) { - if (!mTimeFrame->mMultiplicityCutMask[targetROF]) { + for (int targetROF = rofOverlap.getFirstEntry(); targetROF < rofOverlap.getEntriesBound(); ++targetROF) { + if (!mTimeFrame->getROFMaskView().isROFEnabled(transition.toLayer, targetROF)) { continue; } - auto layer1 = mTimeFrame->getClustersOnLayer(targetROF, iLayer + 1); + auto layer1 = mTimeFrame->getClustersOnLayer(targetROF, transition.toLayer); if (layer1.empty()) { continue; } + const auto ts = mTimeFrame->getROFOverlapTableView().getTimeStamp(transition.fromLayer, pivotROF, transition.toLayer, targetROF); + if (!ts.isCompatible(pv.getTimeStamp())) { + continue; + } + const auto& targetIndexTable = mTimeFrame->getIndexTable(targetROF, transition.toLayer); + const int zBinRange = (bins.z - bins.x) + 1; for (int iPhi = 0; iPhi < phiBinsNum; ++iPhi) { const int iPhiBin = (bins.y + iPhi) % mTrkParams[iteration].PhiBins; - const int firstBinIdx = mTimeFrame->mIndexTableUtils.getBinIndex(bins.x, iPhiBin); - const int maxBinIdx = firstBinIdx + (bins.z - bins.x) + 1; - const int firstRow = mTimeFrame->getIndexTable(targetROF, iLayer + 1)[firstBinIdx]; - const int lastRow = mTimeFrame->getIndexTable(targetROF, iLayer + 1)[maxBinIdx]; + const int firstBinIdx = mTimeFrame->getIndexTableUtils().getBinIndex(bins.x, iPhiBin); + const int maxBinIdx = firstBinIdx + zBinRange; + const int firstRow = targetIndexTable[firstBinIdx]; + const int lastRow = targetIndexTable[maxBinIdx]; for (int iNext = firstRow; iNext < lastRow; ++iNext) { if (iNext >= int(layer1.size())) { break; } const Cluster& nextCluster = layer1[iNext]; - if (mTimeFrame->isClusterUsed(iLayer + 1, nextCluster.clusterId)) { + if (mTimeFrame->isClusterUsed(transition.toLayer, nextCluster.clusterId)) { continue; } - float deltaPhi = o2::gpu::GPUCommonMath::Abs(currentCluster.phi - nextCluster.phi); - float deltaZ = o2::gpu::GPUCommonMath::Abs((tanLambda * (nextCluster.radius - currentCluster.radius)) + currentCluster.zCoordinate - nextCluster.zCoordinate); - -#ifdef OPTIMISATION_OUTPUT - MCCompLabel label; - int currentId{currentCluster.clusterId}; - int nextId{nextCluster.clusterId}; - for (auto& lab1 : mTimeFrame->getClusterLabels(iLayer, currentId)) { - for (auto& lab2 : mTimeFrame->getClusterLabels(iLayer + 1, nextId)) { - if (lab1 == lab2 && lab1.isValid()) { - label = lab1; - break; - } - } - if (label.isValid()) { - break; - } - } - off << std::format("{}\t{:d}\t{}\t{}\t{}\t{}", iLayer, label.isValid(), (tanLambda * (nextCluster.radius - currentCluster.radius) + currentCluster.zCoordinate - nextCluster.zCoordinate) / sigmaZ, tanLambda, resolution, sigmaZ) << std::endl; -#endif + const float deltaZ = o2::gpu::CAMath::Abs((tanLambda * (nextCluster.radius - currentCluster.radius)) + currentCluster.zCoordinate - nextCluster.zCoordinate); if (deltaZ / sigmaZ < mTrkParams[iteration].NSigmaCut && - ((deltaPhi < mTimeFrame->getPhiCut(iLayer) || o2::gpu::GPUCommonMath::Abs(deltaPhi - o2::constants::math::TwoPI) < mTimeFrame->getPhiCut(iLayer)))) { + math_utils::isPhiDifferenceBelow(currentCluster.phi, nextCluster.phi, phiCut)) { const float phi{o2::gpu::CAMath::ATan2(currentCluster.yCoordinate - nextCluster.yCoordinate, currentCluster.xCoordinate - nextCluster.xCoordinate)}; const float tanL = (currentCluster.zCoordinate - nextCluster.zCoordinate) / (currentCluster.radius - nextCluster.radius); if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { - tracklets.emplace_back(currentSortedIndex, mTimeFrame->getSortedIndex(targetROF, iLayer + 1, iNext), tanL, phi, pivotROF, targetROF); + tracklets.emplace_back(currentSortedIndex, mTimeFrame->getSortedIndex(targetROF, transition.toLayer, iNext), tanL, phi, ts); } else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) { ++localCount; } else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) { const int idx = base + offset++; - tracklets[idx] = Tracklet(currentSortedIndex, mTimeFrame->getSortedIndex(targetROF, iLayer + 1, iNext), tanL, phi, pivotROF, targetROF); + tracklets[idx] = Tracklet(currentSortedIndex, mTimeFrame->getSortedIndex(targetROF, transition.toLayer, iNext), tanL, phi, ts); } } } @@ -194,84 +173,64 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iROF int dummy{0}; if (mTaskArena->max_concurrency() <= 1) { - for (int pivotROF{startROF}; pivotROF < endROF; ++pivotROF) { - for (int iLayer{0}; iLayer < mTrkParams[iteration].TrackletsPerRoad(); ++iLayer) { - forTracklets(PassMode::OnePass{}, iLayer, pivotROF, 0, dummy); + for (int transitionId{0}; transitionId < topology.nTransitions; ++transitionId) { + const int fromLayer = topology.getTransition(transitionId).fromLayer; + const int startROF = 0, endROF = mTimeFrame->getROFOverlapTableView().getLayer(fromLayer).mNROFsTF; + for (int pivotROF{startROF}; pivotROF < endROF; ++pivotROF) { + forTracklets(PassMode::OnePass{}, transitionId, pivotROF, 0, dummy); } } } else { - bounded_vector> perROFCount(mTrkParams[iteration].TrackletsPerRoad(), bounded_vector(endROF - startROF + 1, 0, mMemoryPool.get()), mMemoryPool.get()); - tbb::parallel_for( - tbb::blocked_range2d(0, mTrkParams[iteration].TrackletsPerRoad(), 1, - startROF, endROF, 1), - [&](auto const& Range) { - for (int iLayer{Range.rows().begin()}; iLayer < Range.rows().end(); ++iLayer) { - for (int pivotROF = Range.cols().begin(); pivotROF < Range.cols().end(); ++pivotROF) { - perROFCount[iLayer][pivotROF - startROF] = forTracklets(PassMode::TwoPassCount{}, iLayer, pivotROF, 0, dummy); - } - } + tbb::parallel_for(0, static_cast(topology.nTransitions), [&](const int transitionId) { + const int fromLayer = topology.getTransition(transitionId).fromLayer; + const int startROF = 0, endROF = mTimeFrame->getROFOverlapTableView().getLayer(fromLayer).mNROFsTF; + bounded_vector perROFCount((endROF - startROF) + 1, mMemoryPool.get()); + tbb::parallel_for(startROF, endROF, [&](const int pivotROF) { + perROFCount[pivotROF - startROF] = forTracklets(PassMode::TwoPassCount{}, transitionId, pivotROF, 0, dummy); }); - - tbb::parallel_for(0, mTrkParams[iteration].TrackletsPerRoad(), [&](const int iLayer) { - std::exclusive_scan(perROFCount[iLayer].begin(), perROFCount[iLayer].end(), perROFCount[iLayer].begin(), 0); - mTimeFrame->getTracklets()[iLayer].resize(perROFCount[iLayer].back()); - }); - - tbb::parallel_for( - tbb::blocked_range2d(0, mTrkParams[iteration].TrackletsPerRoad(), 1, - startROF, endROF, 1), - [&](auto const& Range) { - for (int iLayer{Range.rows().begin()}; iLayer < Range.rows().end(); ++iLayer) { - if (perROFCount[iLayer].back() == 0) { - continue; - } - for (int pivotROF = Range.cols().begin(); pivotROF < Range.cols().end(); ++pivotROF) { - int baseIdx = perROFCount[iLayer][pivotROF - startROF]; - if (baseIdx == perROFCount[iLayer][pivotROF - startROF + 1]) { - continue; - } - int localIdx = 0; - forTracklets(PassMode::TwoPassInsert{}, iLayer, pivotROF, baseIdx, localIdx); - } + std::exclusive_scan(perROFCount.begin(), perROFCount.end(), perROFCount.begin(), 0); + const int nTracklets = perROFCount.back(); + mTimeFrame->getTracklets()[transitionId].resize(nTracklets); + if (nTracklets == 0) { + return; + } + tbb::parallel_for(startROF, endROF, [&](const int pivotROF) { + int baseIdx = perROFCount[pivotROF - startROF]; + if (baseIdx == perROFCount[pivotROF + 1 - startROF]) { + return; } + int localIdx = 0; + forTracklets(PassMode::TwoPassInsert{}, transitionId, pivotROF, baseIdx, localIdx); }); + }); } - tbb::parallel_for(0, mTrkParams[iteration].TrackletsPerRoad(), [&](const int iLayer) { - /// Sort tracklets - auto& trkl{mTimeFrame->getTracklets()[iLayer]}; - tbb::parallel_sort(trkl.begin(), trkl.end(), [](const Tracklet& a, const Tracklet& b) -> bool { - if (a.firstClusterIndex != b.firstClusterIndex) { - return a.firstClusterIndex < b.firstClusterIndex; - } - return a.secondClusterIndex < b.secondClusterIndex; - }); - /// Remove duplicates - trkl.erase(std::unique(trkl.begin(), trkl.end(), [](const Tracklet& a, const Tracklet& b) -> bool { - return a.firstClusterIndex == b.firstClusterIndex && a.secondClusterIndex == b.secondClusterIndex; - }), - trkl.end()); + tbb::parallel_for(0, static_cast(topology.nTransitions), [&](const int transitionId) { + /// Sort tracklets & remove duplicates + // duplicates can exist simply since we evaluate per vertex + auto& trkl{mTimeFrame->getTracklets()[transitionId]}; + std::sort(trkl.begin(), trkl.end()); + trkl.erase(std::unique(trkl.begin(), trkl.end()), trkl.end()); trkl.shrink_to_fit(); - if (iLayer > 0) { /// recalculate lut - auto& lut{mTimeFrame->getTrackletsLookupTable()[iLayer - 1]}; - if (!trkl.empty()) { - for (const auto& tkl : trkl) { - lut[tkl.firstClusterIndex + 1]++; - } - std::inclusive_scan(lut.begin(), lut.end(), lut.begin()); + auto& lut{mTimeFrame->getTrackletsLookupTable()[transitionId]}; + if (!trkl.empty()) { + for (const auto& tkl : trkl) { + lut[tkl.firstClusterIndex + 1]++; } + std::inclusive_scan(lut.begin(), lut.end(), lut.begin()); } }); /// Create tracklets labels - if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].createArtefactLabels) { - tbb::parallel_for(0, mTrkParams[iteration].TrackletsPerRoad(), [&](const int iLayer) { - for (auto& trk : mTimeFrame->getTracklets()[iLayer]) { + if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].CreateArtefactLabels) { + tbb::parallel_for(0, static_cast(topology.nTransitions), [&](const int transitionId) { + const auto& transition = topology.getTransition(transitionId); + for (auto& trk : mTimeFrame->getTracklets()[transitionId]) { MCCompLabel label; - int currentId{mTimeFrame->getClusters()[iLayer][trk.firstClusterIndex].clusterId}; - int nextId{mTimeFrame->getClusters()[iLayer + 1][trk.secondClusterIndex].clusterId}; - for (const auto& lab1 : mTimeFrame->getClusterLabels(iLayer, currentId)) { - for (const auto& lab2 : mTimeFrame->getClusterLabels(iLayer + 1, nextId)) { + int currentId{mTimeFrame->getClusters()[transition.fromLayer][trk.firstClusterIndex].clusterId}; + int nextId{mTimeFrame->getClusters()[transition.toLayer][trk.secondClusterIndex].clusterId}; + for (const auto& lab1 : mTimeFrame->getClusterLabels(transition.fromLayer, currentId)) { + for (const auto& lab2 : mTimeFrame->getClusterLabels(transition.toLayer, nextId)) { if (lab1 == lab2 && lab1.isValid()) { label = lab1; break; @@ -281,73 +240,63 @@ void TrackerTraits::computeLayerTracklets(const int iteration, int iROF break; } } - mTimeFrame->getTrackletsLabel(iLayer).emplace_back(label); + mTimeFrame->getTrackletsLabel(transitionId).emplace_back(label); } }); } }); -} // namespace o2::its +} -template -void TrackerTraits::computeLayerCells(const int iteration) +template +void TrackerTraits::computeLayerCells(const int iteration) { -#ifdef OPTIMISATION_OUTPUT - static int iter{0}; - std::ofstream off(std::format("cells{}.txt", iter++)); -#endif - - for (int iLayer = 0; iLayer < mTrkParams[iteration].CellsPerRoad(); ++iLayer) { - deepVectorClear(mTimeFrame->getCells()[iLayer]); - if (iLayer > 0) { - deepVectorClear(mTimeFrame->getCellsLookupTable()[iLayer - 1]); - } - if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].createArtefactLabels) { - deepVectorClear(mTimeFrame->getCellsLabel(iLayer)); + const auto topology = mTimeFrame->getTrackingTopologyView(); + for (int cellTopologyId = 0; cellTopologyId < topology.nCells; ++cellTopologyId) { + deepVectorClear(mTimeFrame->getCells()[cellTopologyId]); + deepVectorClear(mTimeFrame->getCellsLookupTable()[cellTopologyId]); + if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].CreateArtefactLabels) { + deepVectorClear(mTimeFrame->getCellsLabel(cellTopologyId)); } } mTaskArena->execute([&] { - auto forTrackletCells = [&](auto Tag, int iLayer, bounded_vector& layerCells, int iTracklet, int offset = 0) -> int { - const Tracklet& currentTracklet{mTimeFrame->getTracklets()[iLayer][iTracklet]}; + auto forTrackletCells = [&](auto Tag, int cellTopologyId, bounded_vector& layerCells, int iTracklet, int offset = 0) -> int { + const auto& cellTopology = topology.getCell(cellTopologyId); + const auto& firstTransition = topology.getTransition(cellTopology.firstTransition); + const auto& secondTransition = topology.getTransition(cellTopology.secondTransition); + const Tracklet& currentTracklet{mTimeFrame->getTracklets()[cellTopology.firstTransition][iTracklet]}; const int nextLayerClusterIndex{currentTracklet.secondClusterIndex}; - const int nextLayerFirstTrackletIndex{mTimeFrame->getTrackletsLookupTable()[iLayer][nextLayerClusterIndex]}; - const int nextLayerLastTrackletIndex{mTimeFrame->getTrackletsLookupTable()[iLayer][nextLayerClusterIndex + 1]}; + const int nextLayerFirstTrackletIndex{mTimeFrame->getTrackletsLookupTable()[cellTopology.secondTransition][nextLayerClusterIndex]}; + const int nextLayerLastTrackletIndex{mTimeFrame->getTrackletsLookupTable()[cellTopology.secondTransition][nextLayerClusterIndex + 1]}; int foundCells{0}; for (int iNextTracklet{nextLayerFirstTrackletIndex}; iNextTracklet < nextLayerLastTrackletIndex; ++iNextTracklet) { - const Tracklet& nextTracklet{mTimeFrame->getTracklets()[iLayer + 1][iNextTracklet]}; - const auto& nextLbl = mTimeFrame->getTrackletsLabel(iLayer + 1)[iNextTracklet]; - if (mTimeFrame->getTracklets()[iLayer + 1][iNextTracklet].firstClusterIndex != nextLayerClusterIndex) { + const Tracklet& nextTracklet{mTimeFrame->getTracklets()[cellTopology.secondTransition][iNextTracklet]}; + if (nextTracklet.firstClusterIndex != nextLayerClusterIndex) { break; } - if (mTrkParams[iteration].DeltaROF && currentTracklet.getSpanRof(nextTracklet) > mTrkParams[iteration].DeltaROF) { // TODO this has to be improved for the staggering + if (!currentTracklet.getTimeStamp().isCompatible(nextTracklet.getTimeStamp())) { continue; } - const float deltaTanLambda{std::abs(currentTracklet.tanLambda - nextTracklet.tanLambda)}; - -#ifdef OPTIMISATION_OUTPUT - float resolution{o2::gpu::CAMath::Sqrt(0.5f * (mTrkParams[iteration].SystErrorZ2[iLayer] + mTrkParams[iteration].SystErrorZ2[iLayer + 1] + mTrkParams[iteration].SystErrorZ2[iLayer + 2] + mTrkParams[iteration].SystErrorY2[iLayer] + mTrkParams[iteration].SystErrorY2[iLayer + 1] + mTrkParams[iteration].SystErrorY2[iLayer + 2])) / mTrkParams[iteration].LayerResolution[iLayer]}; - resolution = resolution > 1.e-12 ? resolution : 1.f; - bool good{mTimeFrame->getTrackletsLabel(iLayer)[iTracklet] == mTimeFrame->getTrackletsLabel(iLayer + 1)[iNextTracklet]}; - float signedDelta{currentTracklet.tanLambda - nextTracklet.tanLambda}; - off << std::format("{}\t{:d}\t{}\t{}\t{}\t{}", iLayer, good, signedDelta, signedDelta / (mTrkParams[iteration].CellDeltaTanLambdaSigma), tanLambda, resolution) << std::endl; -#endif - if (deltaTanLambda / mTrkParams[iteration].CellDeltaTanLambdaSigma < mTrkParams[iteration].NSigmaCut) { + const float deltaTanLambdaSigma = std::abs(currentTracklet.tanLambda - nextTracklet.tanLambda) / mTrkParams[iteration].CellDeltaTanLambdaSigma; + if (deltaTanLambdaSigma < mTrkParams[iteration].NSigmaCut) { /// Track seed preparation. Clusters are numbered progressively from the innermost going outward. const int clusId[3]{ - mTimeFrame->getClusters()[iLayer][currentTracklet.firstClusterIndex].clusterId, - mTimeFrame->getClusters()[iLayer + 1][nextTracklet.firstClusterIndex].clusterId, - mTimeFrame->getClusters()[iLayer + 2][nextTracklet.secondClusterIndex].clusterId}; - const auto& cluster1_glo = mTimeFrame->getUnsortedClusters()[iLayer][clusId[0]]; - const auto& cluster2_glo = mTimeFrame->getUnsortedClusters()[iLayer + 1][clusId[1]]; - const auto& cluster3_tf = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer + 2)[clusId[2]]; - auto track{buildTrackSeed(cluster1_glo, cluster2_glo, cluster3_tf)}; + mTimeFrame->getClusters()[firstTransition.fromLayer][currentTracklet.firstClusterIndex].clusterId, + mTimeFrame->getClusters()[firstTransition.toLayer][nextTracklet.firstClusterIndex].clusterId, + mTimeFrame->getClusters()[secondTransition.toLayer][nextTracklet.secondClusterIndex].clusterId}; + const int hitLayers[3]{firstTransition.fromLayer, firstTransition.toLayer, secondTransition.toLayer}; + const auto& cluster1_glo = mTimeFrame->getUnsortedClusters()[firstTransition.fromLayer][clusId[0]]; + const auto& cluster2_glo = mTimeFrame->getUnsortedClusters()[firstTransition.toLayer][clusId[1]]; + const auto& cluster3_tf = mTimeFrame->getTrackingFrameInfoOnLayer(secondTransition.toLayer)[clusId[2]]; + auto track{o2::its::track::buildTrackSeed(cluster1_glo, cluster2_glo, cluster3_tf, mBz)}; float chi2{0.f}; bool good{false}; for (int iC{2}; iC--;) { - const TrackingFrameInfo& trackingHit = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer + iC)[clusId[iC]]; + const int hitLayer = hitLayers[iC]; + const TrackingFrameInfo& trackingHit = mTimeFrame->getTrackingFrameInfoOnLayer(hitLayer)[clusId[iC]]; if (!track.rotate(trackingHit.alphaTrackingFrame)) { break; @@ -357,7 +306,7 @@ void TrackerTraits::computeLayerCells(const int iteration) break; } - if (!track.correctForMaterial(mTrkParams[0].LayerxX0[iLayer + iC], mTrkParams[0].LayerxX0[iLayer + iC] * constants::Radl * constants::Rho, true)) { + if (!track.correctForMaterial(mTrkParams[iteration].LayerxX0[hitLayer], mTrkParams[iteration].LayerxX0[hitLayer] * constants::Radl * constants::Rho, true)) { break; } @@ -374,13 +323,16 @@ void TrackerTraits::computeLayerCells(const int iteration) chi2 += predChi2; } if (good) { + TimeEstBC ts = currentTracklet.getTimeStamp(); + ts += nextTracklet.getTimeStamp(); if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { - layerCells.emplace_back(iLayer, clusId[0], clusId[1], clusId[2], iTracklet, iNextTracklet, track, chi2); + layerCells.emplace_back(cellTopology.hitLayerMask, clusId[0], clusId[1], clusId[2], iTracklet, iNextTracklet, track, chi2, ts); ++foundCells; } else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) { ++foundCells; } else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) { - layerCells[offset++] = CellSeedN(iLayer, clusId[0], clusId[1], clusId[2], iTracklet, iNextTracklet, track, chi2); + layerCells[offset++] = CellSeed(cellTopology.hitLayerMask, clusId[0], clusId[1], clusId[2], iTracklet, iNextTracklet, track, chi2, ts); + ++foundCells; } else { static_assert(false, "Unknown mode!"); } @@ -390,29 +342,33 @@ void TrackerTraits::computeLayerCells(const int iteration) return foundCells; }; - tbb::parallel_for(0, mTrkParams[iteration].CellsPerRoad(), [&](const int iLayer) { - if (mTimeFrame->getTracklets()[iLayer + 1].empty() || - mTimeFrame->getTracklets()[iLayer].empty()) { - return; + for (int cellTopologyId = 0; cellTopologyId < topology.nCells; ++cellTopologyId) { + const auto& cellTopology = topology.getCell(cellTopologyId); + if (mTimeFrame->getTracklets()[cellTopology.firstTransition].empty() || + mTimeFrame->getTracklets()[cellTopology.secondTransition].empty()) { + continue; } - auto& layerCells = mTimeFrame->getCells()[iLayer]; - const int currentLayerTrackletsNum{static_cast(mTimeFrame->getTracklets()[iLayer].size())}; + auto& layerCells = mTimeFrame->getCells()[cellTopologyId]; + const int currentLayerTrackletsNum{static_cast(mTimeFrame->getTracklets()[cellTopology.firstTransition].size())}; bounded_vector perTrackletCount(currentLayerTrackletsNum + 1, 0, mMemoryPool.get()); if (mTaskArena->max_concurrency() <= 1) { for (int iTracklet{0}; iTracklet < currentLayerTrackletsNum; ++iTracklet) { - perTrackletCount[iTracklet] = forTrackletCells(PassMode::OnePass{}, iLayer, layerCells, iTracklet); + perTrackletCount[iTracklet] = forTrackletCells(PassMode::OnePass{}, cellTopologyId, layerCells, iTracklet); } std::exclusive_scan(perTrackletCount.begin(), perTrackletCount.end(), perTrackletCount.begin(), 0); } else { tbb::parallel_for(0, currentLayerTrackletsNum, [&](const int iTracklet) { - perTrackletCount[iTracklet] = forTrackletCells(PassMode::TwoPassCount{}, iLayer, layerCells, iTracklet); + perTrackletCount[iTracklet] = forTrackletCells(PassMode::TwoPassCount{}, cellTopologyId, layerCells, iTracklet); }); std::exclusive_scan(perTrackletCount.begin(), perTrackletCount.end(), perTrackletCount.begin(), 0); auto totalCells{perTrackletCount.back()}; if (totalCells == 0) { - return; + auto& lut = mTimeFrame->getCellsLookupTable()[cellTopologyId]; + lut.resize(currentLayerTrackletsNum + 1); + std::fill(lut.begin(), lut.end(), 0); + continue; } layerCells.resize(totalCells); @@ -421,240 +377,233 @@ void TrackerTraits::computeLayerCells(const int iteration) if (offset == perTrackletCount[iTracklet + 1]) { return; } - forTrackletCells(PassMode::TwoPassInsert{}, iLayer, layerCells, iTracklet, offset); + forTrackletCells(PassMode::TwoPassInsert{}, cellTopologyId, layerCells, iTracklet, offset); }); } - if (iLayer > 0) { - auto& lut = mTimeFrame->getCellsLookupTable()[iLayer - 1]; - lut.resize(currentLayerTrackletsNum + 1); - std::copy_n(perTrackletCount.begin(), currentLayerTrackletsNum + 1, lut.begin()); - } - }); + auto& lut = mTimeFrame->getCellsLookupTable()[cellTopologyId]; + lut.resize(currentLayerTrackletsNum + 1); + std::copy_n(perTrackletCount.begin(), currentLayerTrackletsNum + 1, lut.begin()); - /// Create cells labels - if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].createArtefactLabels) { - tbb::parallel_for(0, mTrkParams[iteration].CellsPerRoad(), [&](const int iLayer) { - mTimeFrame->getCellsLabel(iLayer).reserve(mTimeFrame->getCells()[iLayer].size()); - for (const auto& cell : mTimeFrame->getCells()[iLayer]) { - MCCompLabel currentLab{mTimeFrame->getTrackletsLabel(iLayer)[cell.getFirstTrackletIndex()]}; - MCCompLabel nextLab{mTimeFrame->getTrackletsLabel(iLayer + 1)[cell.getSecondTrackletIndex()]}; - mTimeFrame->getCellsLabel(iLayer).emplace_back(currentLab == nextLab ? currentLab : MCCompLabel()); + if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].CreateArtefactLabels) { + auto& labels = mTimeFrame->getCellsLabel(cellTopologyId); + labels.reserve(layerCells.size()); + for (const auto& cell : layerCells) { + MCCompLabel currentLab{mTimeFrame->getTrackletsLabel(cellTopology.firstTransition)[cell.getFirstTrackletIndex()]}; + MCCompLabel nextLab{mTimeFrame->getTrackletsLabel(cellTopology.secondTransition)[cell.getSecondTrackletIndex()]}; + labels.emplace_back(currentLab == nextLab ? currentLab : MCCompLabel()); } - }); + } } }); + + for (int transitionId = 0; transitionId < topology.nTransitions; ++transitionId) { + deepVectorClear(mTimeFrame->getTracklets()[transitionId]); + deepVectorClear(mTimeFrame->getTrackletsLabel(transitionId)); + } } -template -void TrackerTraits::findCellsNeighbours(const int iteration) +template +void TrackerTraits::findCellsNeighbours(const int iteration) { -#ifdef OPTIMISATION_OUTPUT - std::ofstream off(std::format("cellneighs{}.txt", iteration)); -#endif - - struct Neighbor { - int cell{-1}, nextCell{-1}, level{-1}; - }; - + const auto topology = mTimeFrame->getTrackingTopologyView(); mTaskArena->execute([&] { - for (int iLayer{0}; iLayer < mTrkParams[iteration].NeighboursPerRoad(); ++iLayer) { - deepVectorClear(mTimeFrame->getCellsNeighbours()[iLayer]); - deepVectorClear(mTimeFrame->getCellsNeighboursLUT()[iLayer]); - if (mTimeFrame->getCells()[iLayer + 1].empty() || - mTimeFrame->getCellsLookupTable()[iLayer].empty()) { - continue; - } + std::vector> cellsNeighboursByTarget; + cellsNeighboursByTarget.reserve(topology.nCells); + for (int cellTopologyId{0}; cellTopologyId < topology.nCells; ++cellTopologyId) { + deepVectorClear(mTimeFrame->getCellsNeighbours()[cellTopologyId]); + deepVectorClear(mTimeFrame->getCellsNeighboursTopology()[cellTopologyId]); + deepVectorClear(mTimeFrame->getCellsNeighboursLUT()[cellTopologyId]); + cellsNeighboursByTarget.emplace_back(mMemoryPool.get()); + } - int nCells{static_cast(mTimeFrame->getCells()[iLayer].size())}; - bounded_vector cellsNeighbours(mMemoryPool.get()); - - auto forCellNeighbour = [&](auto Tag, int iCell, int offset = 0) -> int { - const auto& currentCellSeed{mTimeFrame->getCells()[iLayer][iCell]}; - const int nextLayerTrackletIndex{currentCellSeed.getSecondTrackletIndex()}; - const int nextLayerFirstCellIndex{mTimeFrame->getCellsLookupTable()[iLayer][nextLayerTrackletIndex]}; - const int nextLayerLastCellIndex{mTimeFrame->getCellsLookupTable()[iLayer][nextLayerTrackletIndex + 1]}; - int foundNextCells{0}; - for (int iNextCell{nextLayerFirstCellIndex}; iNextCell < nextLayerLastCellIndex; ++iNextCell) { - auto nextCellSeed{mTimeFrame->getCells()[iLayer + 1][iNextCell]}; /// copy - if (nextCellSeed.getFirstTrackletIndex() != nextLayerTrackletIndex) { - break; - } + for (int outerLayer{0}; outerLayer < NLayers; ++outerLayer) { + for (int cellTopologyId{0}; cellTopologyId < topology.nCells; ++cellTopologyId) { + const auto& cellTopology = topology.getCell(cellTopologyId); + if (cellTopology.hitLayerMask.last() != outerLayer || + mTimeFrame->getCells()[cellTopologyId].empty()) { + continue; + } + const auto successors = topology.getCellsStartingWithTransition(cellTopology.secondTransition); + if (!successors.getEntries()) { + continue; + } - if (mTrkParams[iteration].DeltaROF) { // TODO this has to be improved for the staggering - const auto& trkl00 = mTimeFrame->getTracklets()[iLayer][currentCellSeed.getFirstTrackletIndex()]; - const auto& trkl01 = mTimeFrame->getTracklets()[iLayer + 1][currentCellSeed.getSecondTrackletIndex()]; - const auto& trkl10 = mTimeFrame->getTracklets()[iLayer + 1][nextCellSeed.getFirstTrackletIndex()]; - const auto& trkl11 = mTimeFrame->getTracklets()[iLayer + 2][nextCellSeed.getSecondTrackletIndex()]; - if ((std::max({trkl00.getMaxRof(), trkl01.getMaxRof(), trkl10.getMaxRof(), trkl11.getMaxRof()}) - - std::min({trkl00.getMinRof(), trkl01.getMinRof(), trkl10.getMinRof(), trkl11.getMinRof()})) > mTrkParams[0].DeltaROF) { + tbb::enumerable_thread_specific> sourceNeighbours([&]() { return bounded_vector{mMemoryPool.get()}; }); + tbb::parallel_for(0, static_cast(mTimeFrame->getCells()[cellTopologyId].size()), [&](const int iCell) { + auto& localNeighbours = sourceNeighbours.local(); + const auto& currentCellSeed{mTimeFrame->getCells()[cellTopologyId][iCell]}; + const int nextLayerTrackletIndex{currentCellSeed.getSecondTrackletIndex()}; + for (int iSuccessor{0}; iSuccessor < successors.getEntries(); ++iSuccessor) { + const int nextCellTopologyId = topology.cellsByFirstTransition[successors.getFirstEntry() + iSuccessor]; + if (mTimeFrame->getCells()[nextCellTopologyId].empty() || + mTimeFrame->getCellsLookupTable()[nextCellTopologyId].empty()) { continue; } - } + const auto& nextCellLUT = mTimeFrame->getCellsLookupTable()[nextCellTopologyId]; + if (nextLayerTrackletIndex + 1 >= static_cast(nextCellLUT.size())) { + continue; + } + const int nextLayerFirstCellIndex{nextCellLUT[nextLayerTrackletIndex]}; + const int nextLayerLastCellIndex{nextCellLUT[nextLayerTrackletIndex + 1]}; + for (int iNextCell{nextLayerFirstCellIndex}; iNextCell < nextLayerLastCellIndex; ++iNextCell) { + const auto& nextCellSeedRef{mTimeFrame->getCells()[nextCellTopologyId][iNextCell]}; + if (nextCellSeedRef.getFirstTrackletIndex() != nextLayerTrackletIndex || !currentCellSeed.getTimeStamp().isCompatible(nextCellSeedRef.getTimeStamp())) { + break; + } - if (!nextCellSeed.rotate(currentCellSeed.getAlpha()) || - !nextCellSeed.propagateTo(currentCellSeed.getX(), getBz())) { - continue; - } - float chi2 = currentCellSeed.getPredictedChi2(nextCellSeed); /// TODO: switch to the chi2 wrt cluster to avoid correlation + auto nextCellSeed{mTimeFrame->getCells()[nextCellTopologyId][iNextCell]}; /// copy + if (!nextCellSeed.rotate(currentCellSeed.getAlpha()) || + !nextCellSeed.propagateTo(currentCellSeed.getX(), getBz())) { + continue; + } -#ifdef OPTIMISATION_OUTPUT - bool good{mTimeFrame->getCellsLabel(iLayer)[iCell] == mTimeFrame->getCellsLabel(iLayer + 1)[iNextCell]}; - off << std::format("{}\t{:d}\t{}", iLayer, good, chi2) << std::endl; -#endif + float chi2 = currentCellSeed.getPredictedChi2(nextCellSeed); + if (chi2 > mTrkParams[iteration].MaxChi2ClusterAttachment) { + continue; + } - if (chi2 > mTrkParams[0].MaxChi2ClusterAttachment) { - continue; + const int nextLevel = currentCellSeed.getLevel() + 1; + localNeighbours.emplace_back(cellTopologyId, iCell, nextCellTopologyId, iNextCell, nextLevel); + } } + }); - if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { - cellsNeighbours.emplace_back(iCell, iNextCell, currentCellSeed.getLevel() + 1); - } else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) { - ++foundNextCells; - } else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) { - cellsNeighbours[offset++] = {iCell, iNextCell, currentCellSeed.getLevel() + 1}; - } else { - static_assert(false, "Unknown mode!"); + bounded_vector count(topology.nCells, 0, mMemoryPool.get()); + for (const auto& localNeighbours : sourceNeighbours) { + for (const auto& neigh : localNeighbours) { + ++count[neigh.nextCellTopology]; } } - return foundNextCells; - }; - - if (mTaskArena->max_concurrency() <= 1) { - for (int iCell{0}; iCell < nCells; ++iCell) { - forCellNeighbour(PassMode::OnePass{}, iCell); + for (size_t i{0}; i < topology.nCells; ++i) { + cellsNeighboursByTarget[i].reserve(count[i]); } - } else { - bounded_vector perCellCount(nCells + 1, 0, mMemoryPool.get()); - tbb::parallel_for(0, nCells, [&](const int iCell) { - perCellCount[iCell] = forCellNeighbour(PassMode::TwoPassCount{}, iCell); - }); - - std::exclusive_scan(perCellCount.begin(), perCellCount.end(), perCellCount.begin(), 0); - int totalCellNeighbours = perCellCount.back(); - if (totalCellNeighbours == 0) { - deepVectorClear(mTimeFrame->getCellsNeighbours()[iLayer]); - continue; - } - cellsNeighbours.resize(totalCellNeighbours); - - tbb::parallel_for(0, nCells, [&](const int iCell) { - int offset = perCellCount[iCell]; - if (offset == perCellCount[iCell + 1]) { - return; + for (const auto& localNeighbours : sourceNeighbours) { + for (const auto& neigh : localNeighbours) { + cellsNeighboursByTarget[neigh.nextCellTopology].emplace_back(neigh); + if (neigh.level > mTimeFrame->getCells()[neigh.nextCellTopology][neigh.nextCell].getLevel()) { + mTimeFrame->getCells()[neigh.nextCellTopology][neigh.nextCell].setLevel(neigh.level); + } } - forCellNeighbour(PassMode::TwoPassInsert{}, iCell, offset); - }); + } } + } + for (int cellTopologyId{0}; cellTopologyId < topology.nCells; ++cellTopologyId) { + auto& cellsNeighbours = cellsNeighboursByTarget[cellTopologyId]; if (cellsNeighbours.empty()) { continue; } - tbb::parallel_sort(cellsNeighbours.begin(), cellsNeighbours.end(), [](const auto& a, const auto& b) { + std::sort(cellsNeighbours.begin(), cellsNeighbours.end(), [](const auto& a, const auto& b) { return a.nextCell < b.nextCell; }); - auto& cellsNeighbourLUT = mTimeFrame->getCellsNeighboursLUT()[iLayer]; - cellsNeighbourLUT.assign(mTimeFrame->getCells()[iLayer + 1].size(), 0); + auto& cellsNeighbourLUT = mTimeFrame->getCellsNeighboursLUT()[cellTopologyId]; + cellsNeighbourLUT.assign(mTimeFrame->getCells()[cellTopologyId].size(), 0); for (const auto& neigh : cellsNeighbours) { ++cellsNeighbourLUT[neigh.nextCell]; } std::inclusive_scan(cellsNeighbourLUT.begin(), cellsNeighbourLUT.end(), cellsNeighbourLUT.begin()); - mTimeFrame->getCellsNeighbours()[iLayer].reserve(cellsNeighbours.size()); - std::ranges::transform(cellsNeighbours, std::back_inserter(mTimeFrame->getCellsNeighbours()[iLayer]), [](const auto& neigh) { return neigh.cell; }); + mTimeFrame->getCellsNeighbours()[cellTopologyId].reserve(cellsNeighbours.size()); + mTimeFrame->getCellsNeighboursTopology()[cellTopologyId].reserve(cellsNeighbours.size()); + std::ranges::transform(cellsNeighbours, std::back_inserter(mTimeFrame->getCellsNeighbours()[cellTopologyId]), [](const auto& neigh) { return neigh.cell; }); + std::ranges::transform(cellsNeighbours, std::back_inserter(mTimeFrame->getCellsNeighboursTopology()[cellTopologyId]), [](const auto& neigh) { return neigh.cellTopology; }); + } - for (auto it = cellsNeighbours.begin(); it != cellsNeighbours.end();) { - int cellIdx = it->nextCell; - int maxLvl = it->level; - while (++it != cellsNeighbours.end() && it->nextCell == cellIdx) { - maxLvl = std::max(maxLvl, it->level); - } - mTimeFrame->getCells()[iLayer + 1][cellIdx].setLevel(maxLvl); - } + // clean up LUTs + for (auto& cellLUT : mTimeFrame->getCellsLookupTable()) { + deepVectorClear(cellLUT); } }); } -template -void TrackerTraits::processNeighbours(int iLayer, int iLevel, const bounded_vector& currentCellSeed, const bounded_vector& currentCellId, bounded_vector& updatedCellSeeds, bounded_vector& updatedCellsIds) +template +template +void TrackerTraits::processNeighbours(int iteration, int defaultCellTopologyId, int iLevel, const bounded_vector& currentCellSeed, const bounded_vector& currentCellId, const bounded_vector& currentCellTopologyId, bounded_vector& updatedCellSeeds, bounded_vector& updatedCellsIds, bounded_vector& updatedCellsTopologyIds) { - CA_DEBUGGER(std::cout << "Processing neighbours layer " << iLayer << " level " << iLevel << ", size of the cell seeds: " << currentCellSeed.size() << std::endl); auto propagator = o2::base::Propagator::Instance(); -#ifdef CA_DEBUG - int failed[5]{0, 0, 0, 0, 0}, attempts{0}, failedByMismatch{0}; -#endif - mTaskArena->execute([&] { auto forCellNeighbours = [&](auto Tag, int iCell, int offset = 0) -> int { const auto& currentCell{currentCellSeed[iCell]}; + const int cellTopologyId = currentCellTopologyId.empty() ? defaultCellTopologyId : currentCellTopologyId[iCell]; if constexpr (decltype(Tag)::value != PassMode::TwoPassInsert::value) { if (currentCell.getLevel() != iLevel) { return 0; } - if (currentCellId.empty() && (mTimeFrame->isClusterUsed(iLayer, currentCell.getFirstClusterIndex()) || - mTimeFrame->isClusterUsed(iLayer + 1, currentCell.getSecondClusterIndex()) || - mTimeFrame->isClusterUsed(iLayer + 2, currentCell.getThirdClusterIndex()))) { - return 0; /// this we do only on the first iteration, hence the check on currentCellId + if (currentCellId.empty()) { + for (int layer = 0; layer < NLayers; ++layer) { + const int clusterIndex = currentCell.getCluster(layer); + if (clusterIndex != constants::UnusedIndex && mTimeFrame->isClusterUsed(layer, clusterIndex)) { + return 0; /// this we do only on the first iteration, hence the check on currentCellId + } + } } } const int cellId = currentCellId.empty() ? iCell : currentCellId[iCell]; - const int startNeighbourId{cellId ? mTimeFrame->getCellsNeighboursLUT()[iLayer - 1][cellId - 1] : 0}; - const int endNeighbourId{mTimeFrame->getCellsNeighboursLUT()[iLayer - 1][cellId]}; + if (cellTopologyId < 0 || mTimeFrame->getCellsNeighboursLUT()[cellTopologyId].empty()) { + return 0; + } + const int startNeighbourId{cellId ? mTimeFrame->getCellsNeighboursLUT()[cellTopologyId][cellId - 1] : 0}; + const int endNeighbourId{mTimeFrame->getCellsNeighboursLUT()[cellTopologyId][cellId]}; int foundSeeds{0}; for (int iNeighbourCell{startNeighbourId}; iNeighbourCell < endNeighbourId; ++iNeighbourCell) { - CA_DEBUGGER(attempts++); - const int neighbourCellId = mTimeFrame->getCellsNeighbours()[iLayer - 1][iNeighbourCell]; - const auto& neighbourCell = mTimeFrame->getCells()[iLayer - 1][neighbourCellId]; + const int neighbourCellTopologyId = mTimeFrame->getCellsNeighboursTopology()[cellTopologyId][iNeighbourCell]; + const int neighbourCellId = mTimeFrame->getCellsNeighbours()[cellTopologyId][iNeighbourCell]; + const auto& neighbourCell = mTimeFrame->getCells()[neighbourCellTopologyId][neighbourCellId]; if (neighbourCell.getSecondTrackletIndex() != currentCell.getFirstTrackletIndex()) { - CA_DEBUGGER(failedByMismatch++); continue; } - if (mTimeFrame->isClusterUsed(iLayer - 1, neighbourCell.getFirstClusterIndex())) { + if (!currentCell.getTimeStamp().isCompatible(neighbourCell.getTimeStamp())) { continue; } if (currentCell.getLevel() - 1 != neighbourCell.getLevel()) { - CA_DEBUGGER(failed[0]++); + continue; + } + const int neighbourLayer = neighbourCell.getInnerLayer(); + const int neighbourCluster = neighbourCell.getFirstClusterIndex(); + if (mTimeFrame->isClusterUsed(neighbourLayer, neighbourCluster)) { continue; } /// Let's start the fitting procedure - CellSeedN seed{currentCell}; - const auto& trHit = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer - 1)[neighbourCell.getFirstClusterIndex()]; + TrackSeedN seed{currentCell}; + seed.getTimeStamp() = currentCell.getTimeStamp(); + seed.getTimeStamp() += neighbourCell.getTimeStamp(); + const auto& trHit = mTimeFrame->getTrackingFrameInfoOnLayer(neighbourLayer)[neighbourCluster]; if (!seed.rotate(trHit.alphaTrackingFrame)) { - CA_DEBUGGER(failed[1]++); continue; } - if (!propagator->propagateToX(seed, trHit.xTrackingFrame, getBz(), o2::base::PropagatorImpl::MAX_SIN_PHI, o2::base::PropagatorImpl::MAX_STEP, mTrkParams[0].CorrType)) { - CA_DEBUGGER(failed[2]++); + if (!propagator->propagateToX(seed, trHit.xTrackingFrame, getBz(), o2::base::PropagatorImpl::MAX_SIN_PHI, o2::base::PropagatorImpl::MAX_STEP, mTrkParams[iteration].CorrType)) { continue; } - if (mTrkParams[0].CorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { - if (!seed.correctForMaterial(mTrkParams[0].LayerxX0[iLayer - 1], mTrkParams[0].LayerxX0[iLayer - 1] * constants::Radl * constants::Rho, true)) { + if (mTrkParams[iteration].CorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { + if (!seed.correctForMaterial(mTrkParams[iteration].LayerxX0[neighbourLayer], mTrkParams[iteration].LayerxX0[neighbourLayer] * constants::Radl * constants::Rho, true)) { continue; } } auto predChi2{seed.getPredictedChi2Quiet(trHit.positionTrackingFrame, trHit.covarianceTrackingFrame)}; - if ((predChi2 > mTrkParams[0].MaxChi2ClusterAttachment) || predChi2 < 0.f) { - CA_DEBUGGER(failed[3]++); + if ((predChi2 > mTrkParams[iteration].MaxChi2ClusterAttachment) || predChi2 < 0.f) { continue; } seed.setChi2(seed.getChi2() + predChi2); if (!seed.o2::track::TrackParCov::update(trHit.positionTrackingFrame, trHit.covarianceTrackingFrame)) { - CA_DEBUGGER(failed[4]++); continue; } if constexpr (decltype(Tag)::value != PassMode::TwoPassCount::value) { - seed.getClusters()[iLayer - 1] = neighbourCell.getFirstClusterIndex(); + seed.getClusters()[neighbourLayer] = neighbourCluster; + auto mask = seed.getHitLayerMask(); + mask.set(neighbourLayer); + seed.setHitLayerMask(mask); seed.setLevel(neighbourCell.getLevel()); seed.setFirstTrackletIndex(neighbourCell.getFirstTrackletIndex()); seed.setSecondTrackletIndex(neighbourCell.getSecondTrackletIndex()); @@ -663,11 +612,13 @@ void TrackerTraits::processNeighbours(int iLayer, int iLevel, const bou if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { updatedCellSeeds.push_back(seed); updatedCellsIds.push_back(neighbourCellId); + updatedCellsTopologyIds.push_back(neighbourCellTopologyId); } else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) { ++foundSeeds; } else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) { updatedCellSeeds[offset] = seed; - updatedCellsIds[offset++] = neighbourCellId; + updatedCellsIds[offset] = neighbourCellId; + updatedCellsTopologyIds[offset++] = neighbourCellTopologyId; } else { static_assert(false, "Unknown mode!"); } @@ -693,6 +644,7 @@ void TrackerTraits::processNeighbours(int iLayer, int iLevel, const bou } updatedCellSeeds.resize(totalNeighbours); updatedCellsIds.resize(totalNeighbours); + updatedCellsTopologyIds.resize(totalNeighbours); tbb::parallel_for(0, nCells, [&](const int iCell) { int offset = perCellCount[iCell]; @@ -703,52 +655,55 @@ void TrackerTraits::processNeighbours(int iLayer, int iLevel, const bou }); } }); - -#ifdef CA_DEBUG - std::cout << "\t\t- Found " << updatedCellSeeds.size() << " cell seeds out of " << attempts << " attempts" << std::endl; - std::cout << "\t\t\t> " << failed[0] << " failed because of level" << std::endl; - std::cout << "\t\t\t> " << failed[1] << " failed because of rotation" << std::endl; - std::cout << "\t\t\t> " << failed[2] << " failed because of propagation" << std::endl; - std::cout << "\t\t\t> " << failed[3] << " failed because of chi2 cut" << std::endl; - std::cout << "\t\t\t> " << failed[4] << " failed because of update" << std::endl; - std::cout << "\t\t\t> " << failedByMismatch << " failed because of mismatch" << std::endl; -#endif } -template -void TrackerTraits::findRoads(const int iteration) +template +void TrackerTraits::findRoads(const int iteration) { bounded_vector> firstClusters(mTrkParams[iteration].NLayers, bounded_vector(mMemoryPool.get()), mMemoryPool.get()); - bounded_vector> sharedFirstClusters(mTrkParams[iteration].NLayers, bounded_vector(mMemoryPool.get()), mMemoryPool.get()); firstClusters.resize(mTrkParams[iteration].NLayers); - sharedFirstClusters.resize(mTrkParams[iteration].NLayers); + const auto propagator = o2::base::Propagator::Instance(); + const TrackingFrameInfo* tfInfos[NLayers]{}; + const Cluster* unsortedClusters[NLayers]{}; + for (int iLayer = 0; iLayer < NLayers; ++iLayer) { + tfInfos[iLayer] = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer).data(); + unsortedClusters[iLayer] = mTimeFrame->getUnsortedClusters()[iLayer].data(); + } + const auto topology = mTimeFrame->getTrackingTopologyView(); for (int startLevel{mTrkParams[iteration].CellsPerRoad()}; startLevel >= mTrkParams[iteration].CellMinimumLevel(); --startLevel) { auto seedFilter = [&](const auto& seed) { - return seed.getQ2Pt() <= 1.e3 && seed.getChi2() <= mTrkParams[0].MaxChi2NDF * ((startLevel + 2) * 2 - 5); + return seed.getHitLayerMask().isAllowed(mTrkParams[iteration].MaxHoles, mTrkParams[iteration].HoleLayerMask) && + seed.getHitLayerMask().length() >= mTrkParams[iteration].MinTrackLength && + seed.getQ2Pt() <= 1.e3 && seed.getChi2() <= mTrkParams[iteration].MaxChi2NDF * ((startLevel + 2) * 2 - 5); }; - bounded_vector trackSeeds(mMemoryPool.get()); - for (int startLayer{mTrkParams[iteration].NeighboursPerRoad()}; startLayer >= startLevel - 1; --startLayer) { - if ((mTrkParams[iteration].StartLayerMask & (1 << (startLayer + 2))) == 0) { + bounded_vector trackSeeds(mMemoryPool.get()); + for (int startCellTopologyId{0}; startCellTopologyId < topology.nCells; ++startCellTopologyId) { + const int startLayer = topology.getCell(startCellTopologyId).hitLayerMask.last(); + if (!(mTrkParams[iteration].StartLayerMask.has(startLayer)) || mTimeFrame->getCells()[startCellTopologyId].empty()) { continue; } bounded_vector lastCellId(mMemoryPool.get()), updatedCellId(mMemoryPool.get()); - bounded_vector lastCellSeed(mMemoryPool.get()), updatedCellSeed(mMemoryPool.get()); + bounded_vector lastCellTopologyId(mMemoryPool.get()), updatedCellTopologyId(mMemoryPool.get()); + bounded_vector lastCellSeed(mMemoryPool.get()), updatedCellSeed(mMemoryPool.get()); - processNeighbours(startLayer, startLevel, mTimeFrame->getCells()[startLayer], lastCellId, updatedCellSeed, updatedCellId); + processNeighbours(iteration, startCellTopologyId, startLevel, mTimeFrame->getCells()[startCellTopologyId], lastCellId, lastCellTopologyId, updatedCellSeed, updatedCellId, updatedCellTopologyId); int level = startLevel; - for (int iLayer{startLayer - 1}; iLayer > 0 && level > 2; --iLayer) { + while (level > 2 && !updatedCellSeed.empty()) { lastCellSeed.swap(updatedCellSeed); lastCellId.swap(updatedCellId); + lastCellTopologyId.swap(updatedCellTopologyId); deepVectorClear(updatedCellSeed); /// tame the memory peaks deepVectorClear(updatedCellId); /// tame the memory peaks - processNeighbours(iLayer, --level, lastCellSeed, lastCellId, updatedCellSeed, updatedCellId); + deepVectorClear(updatedCellTopologyId); + processNeighbours(iteration, constants::UnusedIndex, --level, lastCellSeed, lastCellId, lastCellTopologyId, updatedCellSeed, updatedCellId, updatedCellTopologyId); } - deepVectorClear(lastCellId); /// tame the memory peaks - deepVectorClear(lastCellSeed); /// tame the memory peaks + deepVectorClear(lastCellId); /// tame the memory peaks + deepVectorClear(lastCellTopologyId); /// tame the memory peaks + deepVectorClear(lastCellSeed); /// tame the memory peaks if (!updatedCellSeed.empty()) { trackSeeds.reserve(trackSeeds.size() + std::count_if(updatedCellSeed.begin(), updatedCellSeed.end(), seedFilter)); @@ -763,47 +718,36 @@ void TrackerTraits::findRoads(const int iteration) bounded_vector tracks(mMemoryPool.get()); mTaskArena->execute([&] { auto forSeed = [&](auto Tag, int iSeed, int offset = 0) { - TrackITSExt temporaryTrack = seedTrackForRefit(trackSeeds[iSeed]); - o2::track::TrackPar linRef{temporaryTrack}; - bool fitSuccess = fitTrack(temporaryTrack, 0, mTrkParams[0].NLayers, 1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, o2::constants::math::VeryBig, 0, &linRef); - if (!fitSuccess) { - return 0; - } - temporaryTrack.getParamOut() = temporaryTrack.getParamIn(); - linRef = temporaryTrack.getParamOut(); // use refitted track as lin.reference - temporaryTrack.resetCovariance(); - temporaryTrack.setCov(temporaryTrack.getQ2Pt() * temporaryTrack.getQ2Pt() * temporaryTrack.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2); - temporaryTrack.setChi2(0); - fitSuccess = fitTrack(temporaryTrack, mTrkParams[0].NLayers - 1, -1, -1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, 50.f, 0, &linRef); - if (!fitSuccess || temporaryTrack.getPt() < mTrkParams[iteration].MinPt[mTrkParams[iteration].NLayers - temporaryTrack.getNClusters()]) { - return 0; - } - if (mTrkParams[0].RepeatRefitOut) { // repeat outward refit seeding and linearizing with the stable inward fit result - o2::track::TrackParCov saveInw{temporaryTrack}; - linRef = saveInw; // use refitted track as lin.reference - float saveChi2 = temporaryTrack.getChi2(); - temporaryTrack.resetCovariance(); - temporaryTrack.setCov(temporaryTrack.getQ2Pt() * temporaryTrack.getQ2Pt() * temporaryTrack.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2); - temporaryTrack.setChi2(0); - fitSuccess = fitTrack(temporaryTrack, 0, mTrkParams[0].NLayers, 1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, o2::constants::math::VeryBig, 0, &linRef); - if (!fitSuccess) { - return 0; + TrackITSExt temporaryTrack; + bool refitSuccess = track::refitTrack(trackSeeds[iSeed], + temporaryTrack, + mTrkParams[iteration].MaxChi2ClusterAttachment, + mTrkParams[iteration].MaxChi2NDF, + mBz, + tfInfos, + unsortedClusters, + mTrkParams[iteration].LayerxX0.data(), + mTrkParams[iteration].LayerRadii.data(), + mTrkParams[iteration].MinPt.data(), + propagator, + mTrkParams[iteration].CorrType, + mTrkParams[iteration].ReseedIfShorter, + mTrkParams[iteration].ShiftRefToCluster, + mTrkParams[iteration].RepeatRefitOut); + + if (refitSuccess) { + if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { + tracks.push_back(temporaryTrack); + } else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) { + // nothing to do + } else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) { + tracks[offset] = temporaryTrack; + } else { + static_assert(false, "Unknown mode!"); } - temporaryTrack.getParamOut() = temporaryTrack.getParamIn(); - temporaryTrack.getParamIn() = saveInw; - temporaryTrack.setChi2(saveChi2); - } - - if constexpr (decltype(Tag)::value == PassMode::OnePass::value) { - tracks.push_back(temporaryTrack); - } else if constexpr (decltype(Tag)::value == PassMode::TwoPassCount::value) { - // nothing to do - } else if constexpr (decltype(Tag)::value == PassMode::TwoPassInsert::value) { - tracks[offset] = temporaryTrack; - } else { - static_assert(false, "Unknown mode!"); + return 1; } - return 1; + return 0; }; const int nSeeds = static_cast(trackSeeds.size()); @@ -812,6 +756,7 @@ void TrackerTraits::findRoads(const int iteration) forSeed(PassMode::OnePass{}, iSeed); } } else { + // The double-pass allows us to avoid sizeable memory spikes bounded_vector perSeedCount(nSeeds + 1, 0, mMemoryPool.get()); tbb::parallel_for(0, nSeeds, [&](const int iSeed) { perSeedCount[iSeed] = forSeed(PassMode::TwoPassCount{}, iSeed); @@ -833,488 +778,146 @@ void TrackerTraits::findRoads(const int iteration) } deepVectorClear(trackSeeds); - tbb::parallel_sort(tracks.begin(), tracks.end(), [](const auto& a, const auto& b) { - return a.getChi2() < b.getChi2(); - }); }); - for (auto& track : tracks) { - int nShared = 0; - bool isFirstShared{false}; - int firstLayer{-1}, firstCluster{-1}; - for (int iLayer{0}; iLayer < mTrkParams[0].NLayers; ++iLayer) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - bool isShared = mTimeFrame->isClusterUsed(iLayer, track.getClusterIndex(iLayer)); - nShared += int(isShared); - if (firstLayer < 0) { - firstCluster = track.getClusterIndex(iLayer); - isFirstShared = isShared && mTrkParams[0].AllowSharingFirstCluster && std::find(firstClusters[iLayer].begin(), firstClusters[iLayer].end(), firstCluster) != firstClusters[iLayer].end(); - firstLayer = iLayer; - } - } - - /// do not account for the first cluster in the shared clusters number if it is allowed - if (nShared - int(isFirstShared && mTrkParams[0].AllowSharingFirstCluster) > mTrkParams[0].ClusterSharing) { - continue; - } - - std::array rofs{INT_MAX, INT_MAX, INT_MAX}; - for (int iLayer{0}; iLayer < mTrkParams[0].NLayers; ++iLayer) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - mTimeFrame->markUsedCluster(iLayer, track.getClusterIndex(iLayer)); - int currentROF = mTimeFrame->getClusterROF(iLayer, track.getClusterIndex(iLayer)); - for (int iR{0}; iR < 3; ++iR) { - if (rofs[iR] == INT_MAX) { - rofs[iR] = currentROF; - } - if (rofs[iR] == currentROF) { - break; - } - } - } - if (rofs[2] != INT_MAX) { - continue; - } - track.setUserField(0); - track.getParamOut().setUserField(0); - if (rofs[1] != INT_MAX) { - track.setNextROFbit(); - } - mTimeFrame->getTracks(o2::gpu::CAMath::Min(rofs[0], rofs[1])).emplace_back(track); - - firstClusters[firstLayer].push_back(firstCluster); - if (isFirstShared) { - sharedFirstClusters[firstLayer].push_back(firstCluster); - } - } - } - - /// Now we have to set the shared cluster flag - for (int iLayer{0}; iLayer < mTrkParams[0].NLayers; ++iLayer) { - std::sort(sharedFirstClusters[iLayer].begin(), sharedFirstClusters[iLayer].end()); - } + std::sort(tracks.begin(), tracks.end(), [](const auto& a, const auto& b) { + return track::isBetter(a, b); + }); - for (int iROF{0}; iROF < mTimeFrame->getNrof(); ++iROF) { - for (auto& track : mTimeFrame->getTracks(iROF)) { - int firstLayer{mTrkParams[0].NLayers}, firstCluster{constants::UnusedIndex}; - for (int iLayer{0}; iLayer < mTrkParams[0].NLayers; ++iLayer) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - firstLayer = iLayer; - firstCluster = track.getClusterIndex(iLayer); - break; - } - if (std::binary_search(sharedFirstClusters[firstLayer].begin(), sharedFirstClusters[firstLayer].end(), firstCluster)) { - track.setSharedClusters(); - } - } + acceptTracks(iteration, tracks, firstClusters); } + markTracks(iteration); } -template -void TrackerTraits::extendTracks(const int iteration) +template +void TrackerTraits::acceptTracks(int iteration, bounded_vector& tracks, bounded_vector>& firstClusters) { - for (int rof{0}; rof < mTimeFrame->getNrof(); ++rof) { - for (auto& track : mTimeFrame->getTracks(rof)) { - auto backup{track}; - bool success{false}; - // the order here biases towards top extension, tracks should probably be fitted separately in the directions and then compared. - if ((mTrkParams[iteration].UseTrackFollowerMix || mTrkParams[iteration].UseTrackFollowerTop) && track.getLastClusterLayer() != mTrkParams[iteration].NLayers - 1) { - success = success || trackFollowing(&track, rof, true, iteration); - } - if ((mTrkParams[iteration].UseTrackFollowerMix || (mTrkParams[iteration].UseTrackFollowerBot && !success)) && track.getFirstClusterLayer() != 0) { - success = success || trackFollowing(&track, rof, false, iteration); + auto& trks = mTimeFrame->getTracks(); + trks.reserve(trks.size() + tracks.size()); + const float smallestROFHalf = mTimeFrame->getROFOverlapTableView().getClockLayer().mROFLength * 0.5f; + for (auto& track : tracks) { + int nShared = 0; + bool isFirstShared{false}; + int firstLayer{-1}, firstCluster{-1}; + for (int iLayer{0}; iLayer < mTrkParams[iteration].NLayers; ++iLayer) { + if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { + continue; } - if (success) { - /// We have to refit the track - track.resetCovariance(); - track.setChi2(0); - bool fitSuccess = fitTrack(track, 0, mTrkParams[iteration].NLayers, 1, mTrkParams[iteration].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF); - if (!fitSuccess) { - track = backup; - continue; - } - track.getParamOut() = track; - track.resetCovariance(); - track.setChi2(0); - fitSuccess = fitTrack(track, mTrkParams[iteration].NLayers - 1, -1, -1, mTrkParams[iteration].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, 50.); - if (!fitSuccess) { - track = backup; - continue; - } - mTimeFrame->mNExtendedTracks++; - mTimeFrame->mNExtendedUsedClusters += track.getNClusters() - backup.getNClusters(); - auto pattern = track.getPattern(); - auto diff = (pattern & ~backup.getPattern()) & 0xff; - pattern |= (diff << 24); - track.setPattern(pattern); - /// Make sure that the newly attached clusters get marked as used - for (int iLayer{0}; iLayer < mTrkParams[iteration].NLayers; ++iLayer) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - mTimeFrame->markUsedCluster(iLayer, track.getClusterIndex(iLayer)); - } + bool isShared = mTimeFrame->isClusterUsed(iLayer, track.getClusterIndex(iLayer)); + nShared += int(isShared); + if (firstLayer < 0) { + firstCluster = track.getClusterIndex(iLayer); + isFirstShared = isShared && mTrkParams[iteration].AllowSharingFirstCluster && std::find(firstClusters[iLayer].begin(), firstClusters[iLayer].end(), firstCluster) != firstClusters[iLayer].end(); + firstLayer = iLayer; } } - } -} - -template -void TrackerTraits::findShortPrimaries() -{ - const auto propagator = o2::base::Propagator::Instance(); - mTimeFrame->fillPrimaryVerticesXandAlpha(); - - for (auto& cell : mTimeFrame->getCells()[0]) { - auto& cluster3_glo = mTimeFrame->getClusters()[2][cell.getThirdClusterIndex()]; - auto& cluster2_glo = mTimeFrame->getClusters()[1][cell.getSecondClusterIndex()]; - auto& cluster1_glo = mTimeFrame->getClusters()[0][cell.getFirstClusterIndex()]; - if (mTimeFrame->isClusterUsed(2, cluster1_glo.clusterId) || - mTimeFrame->isClusterUsed(1, cluster2_glo.clusterId) || - mTimeFrame->isClusterUsed(0, cluster3_glo.clusterId)) { - continue; - } - std::array rofs{ - mTimeFrame->getClusterROF(2, cluster3_glo.clusterId), - mTimeFrame->getClusterROF(1, cluster2_glo.clusterId), - mTimeFrame->getClusterROF(0, cluster1_glo.clusterId)}; - if (rofs[0] != rofs[1] && rofs[1] != rofs[2] && rofs[0] != rofs[2]) { + /// do not account for the first cluster in the shared clusters number if it is allowed + if (nShared - int(isFirstShared && mTrkParams[iteration].AllowSharingFirstCluster) > mTrkParams[iteration].SharedMaxClusters) { continue; } - int rof{rofs[0]}; - if (rofs[1] == rofs[2]) { - rof = rofs[2]; - } - - auto pvs{mTimeFrame->getPrimaryVertices(rof)}; - auto pvsXAlpha{mTimeFrame->getPrimaryVerticesXAlpha(rof)}; - - const auto& cluster3_tf = mTimeFrame->getTrackingFrameInfoOnLayer(2)[cluster3_glo.clusterId]; - TrackITSExt temporaryTrack{buildTrackSeed(cluster1_glo, cluster2_glo, cluster3_tf)}; - temporaryTrack.setExternalClusterIndex(0, cluster1_glo.clusterId, true); - temporaryTrack.setExternalClusterIndex(1, cluster2_glo.clusterId, true); - temporaryTrack.setExternalClusterIndex(2, cluster3_glo.clusterId, true); - - /// add propagation to the primary vertices compatible with the ROF(s) of the cell - bool fitSuccess = fitTrack(temporaryTrack, 1, -1, -1); - if (!fitSuccess) { - continue; - } - fitSuccess = false; - - TrackITSExt bestTrack{temporaryTrack}, backup{temporaryTrack}; - float bestChi2{std::numeric_limits::max()}; - for (int iV{0}; iV < (int)pvs.size(); ++iV) { - temporaryTrack = backup; - if (!temporaryTrack.rotate(pvsXAlpha[iV][1])) { - continue; - } - if (!propagator->propagateTo(temporaryTrack, pvsXAlpha[iV][0], true)) { + bool firstCls{true}, nominalCompatible{true}; + TimeEstBC nominalTS, expandedTS; + for (int iLayer{0}; iLayer < mTrkParams[iteration].NLayers; ++iLayer) { + if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { continue; } - - float pvRes{mTrkParams[0].PVres / o2::gpu::CAMath::Sqrt(float(pvs[iV].getNContributors()))}; - const float posVtx[2]{0.f, pvs[iV].getZ()}; - const float covVtx[3]{pvRes, 0.f, pvRes}; - float chi2 = temporaryTrack.getPredictedChi2Quiet(posVtx, covVtx); - if (chi2 < bestChi2) { - if (!temporaryTrack.track::TrackParCov::update(posVtx, covVtx)) { - continue; + mTimeFrame->markUsedCluster(iLayer, track.getClusterIndex(iLayer)); + int currentROF = mTimeFrame->getClusterROF(iLayer, track.getClusterIndex(iLayer)); + const auto nominalROFTS = mTimeFrame->getROFOverlapTableView().getLayer(iLayer).getROFTimeBounds(currentROF); + const auto expandedROFTS = mTimeFrame->getROFOverlapTableView().getLayer(iLayer).getROFTimeBounds(currentROF, true); + if (firstCls) { + firstCls = false; + nominalTS = nominalROFTS; + expandedTS = expandedROFTS; + } else { + if (nominalCompatible) { + if (nominalTS.isCompatible(nominalROFTS)) { + nominalTS += nominalROFTS; + } else { + nominalCompatible = false; + } } - bestTrack = temporaryTrack; - bestChi2 = chi2; + if (!expandedTS.isCompatible(expandedROFTS)) { + LOGP(fatal, "TS {}+/-{} are incompatible with {}+/-{}, this should not happen!", expandedROFTS.getTimeStamp(), expandedROFTS.getTimeStampError(), expandedTS.getTimeStamp(), expandedTS.getTimeStampError()); + } + expandedTS += expandedROFTS; } } - - bestTrack.resetCovariance(); - bestTrack.setChi2(0.f); - fitSuccess = fitTrack(bestTrack, 0, mTrkParams[0].NLayers, 1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF); - if (!fitSuccess) { - continue; + track.getTimeStamp() = (nominalCompatible ? nominalTS : expandedTS).makeSymmetrical(); + // this is a sanity clamp + // we cannot be worse than the clock so we clamp to this + if (track.getTimeStamp().getTimeStampError() > smallestROFHalf) { + track.getTimeStamp().setTimeStampError(smallestROFHalf); } - bestTrack.getParamOut() = bestTrack; - bestTrack.resetCovariance(); - bestTrack.setChi2(0.f); - fitSuccess = fitTrack(bestTrack, mTrkParams[0].NLayers - 1, -1, -1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, 50.); - if (!fitSuccess) { - continue; + track.setUserField(0); + track.getParamOut().setUserField(0); + trks.emplace_back(track); + + if (mTrkParams[iteration].AllowSharingFirstCluster) { + firstClusters[firstLayer].push_back(firstCluster); } - mTimeFrame->markUsedCluster(0, bestTrack.getClusterIndex(0)); - mTimeFrame->markUsedCluster(1, bestTrack.getClusterIndex(1)); - mTimeFrame->markUsedCluster(2, bestTrack.getClusterIndex(2)); - mTimeFrame->getTracks(rof).emplace_back(bestTrack); } } -template -bool TrackerTraits::fitTrack(TrackITSExt& track, int start, int end, int step, float chi2clcut, float chi2ndfcut, float maxQoverPt, int nCl, o2::track::TrackPar* linRef) +template +void TrackerTraits::markTracks(int iteration) { - auto propInstance = o2::base::Propagator::Instance(); + if (mTrkParams[iteration].AllowSharingFirstCluster) { + /// Now we have to set the shared cluster flag + auto& tracks = mTimeFrame->getTracks(); + + bounded_vector fclusSort(tracks.size(), mMemoryPool.get()); + std::iota(fclusSort.begin(), fclusSort.end(), 0); + std::sort(fclusSort.begin(), fclusSort.end(), [&tracks](int a, int b) { + return tracks[a].getFirstLayerClusterIndex() < tracks[b].getFirstLayerClusterIndex(); + }); - for (int iLayer{start}; iLayer != end; iLayer += step) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - const TrackingFrameInfo& trackingHit = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer)[track.getClusterIndex(iLayer)]; - if (linRef) { - if (!track.rotate(trackingHit.alphaTrackingFrame, *linRef, getBz())) { + auto areTracksSelected = [this, iteration](const TrackITSExt& t1, const TrackITSExt& t2) { + const auto t1FirstLayer{t1.getFirstClusterLayer()}, t2FirstLayer{t2.getFirstClusterLayer()}; + if (t1FirstLayer != t2FirstLayer) { return false; } - if (!propInstance->propagateToX(track, *linRef, trackingHit.xTrackingFrame, getBz(), o2::base::PropagatorImpl::MAX_SIN_PHI, o2::base::PropagatorImpl::MAX_STEP, mTrkParams[0].CorrType)) { + if (mTimeFrame->getClusterROF(t1FirstLayer, t1.getClusterIndex(t1FirstLayer)) != mTimeFrame->getClusterROF(t2FirstLayer, t2.getClusterIndex(t2FirstLayer))) { return false; } - if (mTrkParams[0].CorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { - if (!track.correctForMaterial(*linRef, mTrkParams[0].LayerxX0[iLayer], mTrkParams[0].LayerxX0[iLayer] * constants::Radl * constants::Rho, true)) { - continue; - } - } - } else { - if (!track.rotate(trackingHit.alphaTrackingFrame)) { + if (!math_utils::isPhiDifferenceBelow(t1.getPhi(), t2.getPhi(), mTrkParams[iteration].SharedClusterMaxDeltaPhi)) { return false; } - if (!propInstance->propagateToX(track, trackingHit.xTrackingFrame, getBz(), o2::base::PropagatorImpl::MAX_SIN_PHI, o2::base::PropagatorImpl::MAX_STEP, mTrkParams[0].CorrType)) { + if (std::abs(t1.getEta() - t2.getEta()) > mTrkParams[iteration].SharedClusterMaxDeltaEta) { return false; } - if (mTrkParams[0].CorrType == o2::base::PropagatorF::MatCorrType::USEMatCorrNONE) { - if (!track.correctForMaterial(mTrkParams[0].LayerxX0[iLayer], mTrkParams[0].LayerxX0[iLayer] * constants::Radl * constants::Rho, true)) { - continue; - } - } - } - auto predChi2{track.getPredictedChi2Quiet(trackingHit.positionTrackingFrame, trackingHit.covarianceTrackingFrame)}; - if ((nCl >= 3 && predChi2 > chi2clcut) || predChi2 < 0.f) { - return false; - } - track.setChi2(track.getChi2() + predChi2); - if (!track.o2::track::TrackParCov::update(trackingHit.positionTrackingFrame, trackingHit.covarianceTrackingFrame)) { - return false; - } - if (linRef && mTrkParams[0].ShiftRefToCluster) { // displace the reference to the last updated cluster - linRef->setY(trackingHit.positionTrackingFrame[0]); - linRef->setZ(trackingHit.positionTrackingFrame[1]); - } - nCl++; - } - return std::abs(track.getQ2Pt()) < maxQoverPt && track.getChi2() < chi2ndfcut * (nCl * 2 - 5); -} - -template -bool TrackerTraits::trackFollowing(TrackITSExt* track, int rof, bool outward, const int iteration) -{ - auto propInstance = o2::base::Propagator::Instance(); - const int step = -1 + outward * 2; - const int end = outward ? mTrkParams[iteration].NLayers - 1 : 0; - bounded_vector hypotheses(1, *track, mMemoryPool.get()); // possibly avoid reallocation - for (size_t iHypo{0}; iHypo < hypotheses.size(); ++iHypo) { - auto hypo{hypotheses[iHypo]}; - int iLayer = static_cast(outward ? hypo.getLastClusterLayer() : hypo.getFirstClusterLayer()); - // per layer we add new hypotheses - while (iLayer != end) { - iLayer += step; // step through all layers until we reach the end, this allows for skipping on empty layers - const float r = mTrkParams[iteration].LayerRadii[iLayer]; - // get an estimate of the trackinf-frame x for the next step - float x{-999}; - if (!hypo.getXatLabR(r, x, mTimeFrame->getBz(), o2::track::DirAuto) || x <= 0.f) { - continue; - } - // estimate hypo's trk parameters at that x - auto& hypoParam{outward ? hypo.getParamOut() : hypo.getParamIn()}; - if (!propInstance->propagateToX(hypoParam, x, mTimeFrame->getBz(), PropagatorF::MAX_SIN_PHI, - PropagatorF::MAX_STEP, mTrkParams[iteration].CorrType)) { - continue; - } - - if (mTrkParams[iteration].CorrType == PropagatorF::MatCorrType::USEMatCorrNONE) { // account for material affects if propagator does not - if (!hypoParam.correctForMaterial(mTrkParams[iteration].LayerxX0[iLayer], mTrkParams[iteration].LayerxX0[iLayer] * constants::Radl * constants::Rho, true)) { - continue; - } - } - - // calculate the search window on this layer - const float phi{hypoParam.getPhi()}; - const float ePhi{o2::gpu::CAMath::Sqrt(hypoParam.getSigmaSnp2() / hypoParam.getCsp2())}; - const float z{hypoParam.getZ()}; - const float eZ{o2::gpu::CAMath::Sqrt(hypoParam.getSigmaZ2())}; - const int4 selectedBinsRect{getBinsRect(iLayer, phi, mTrkParams[iteration].TrackFollowerNSigmaCutPhi * ePhi, z, mTrkParams[iteration].TrackFollowerNSigmaCutZ * eZ)}; - if (selectedBinsRect.x == 0 && selectedBinsRect.y == 0 && selectedBinsRect.z == 0 && selectedBinsRect.w == 0) { - continue; - } - - int phiBinsNum{selectedBinsRect.w - selectedBinsRect.y + 1}; - - if (phiBinsNum < 0) { - phiBinsNum += mTrkParams[iteration].PhiBins; - } - - gsl::span layer1 = mTimeFrame->getClustersOnLayer(rof, iLayer); - if (layer1.empty()) { - continue; - } - - // check all clusters in search windows for possible new hypotheses - for (int iPhiCount = 0; iPhiCount < phiBinsNum; iPhiCount++) { - int iPhiBin = (selectedBinsRect.y + iPhiCount) % mTrkParams[iteration].PhiBins; - const int firstBinIndex{mTimeFrame->mIndexTableUtils.getBinIndex(selectedBinsRect.x, iPhiBin)}; - const int maxBinIndex{firstBinIndex + selectedBinsRect.z - selectedBinsRect.x + 1}; - const int firstRowClusterIndex = mTimeFrame->getIndexTable(rof, iLayer)[firstBinIndex]; - const int maxRowClusterIndex = mTimeFrame->getIndexTable(rof, iLayer)[maxBinIndex]; - - for (int iNextCluster{firstRowClusterIndex}; iNextCluster < maxRowClusterIndex; ++iNextCluster) { - if (iNextCluster >= (int)layer1.size()) { - break; - } - const Cluster& nextCluster{layer1[iNextCluster]}; - - if (mTimeFrame->isClusterUsed(iLayer, nextCluster.clusterId)) { - continue; - } - - const TrackingFrameInfo& trackingHit = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer)[nextCluster.clusterId]; - - auto tbupdated{hypo}; - auto& tbuParams = outward ? tbupdated.getParamOut() : tbupdated.getParamIn(); - if (!tbuParams.rotate(trackingHit.alphaTrackingFrame)) { - continue; - } - - if (!propInstance->propagateToX(tbuParams, trackingHit.xTrackingFrame, mTimeFrame->getBz(), - PropagatorF::MAX_SIN_PHI, PropagatorF::MAX_STEP, PropagatorF::MatCorrType::USEMatCorrNONE)) { - continue; - } - - auto predChi2{tbuParams.getPredictedChi2Quiet(trackingHit.positionTrackingFrame, trackingHit.covarianceTrackingFrame)}; - if (predChi2 >= track->getChi2() * mTrkParams[iteration].NSigmaCut) { - continue; - } - - if (!tbuParams.o2::track::TrackParCov::update(trackingHit.positionTrackingFrame, trackingHit.covarianceTrackingFrame)) { - continue; - } - tbupdated.setChi2(tbupdated.getChi2() + predChi2); /// This is wrong for outward propagation as the chi2 refers to inward parameters - tbupdated.setExternalClusterIndex(iLayer, nextCluster.clusterId, true); - hypotheses.emplace_back(tbupdated); - } + if (mTrkParams[iteration].SharedClusterOppositeSign && t1.getSign() == t2.getSign()) { + return false; } - } - } - - TrackITSExt* bestHypo{track}; - bool swapped{false}; - for (auto& hypo : hypotheses) { - if (hypo.isBetter(*bestHypo, track->getChi2() * mTrkParams[iteration].NSigmaCut)) { - bestHypo = &hypo; - swapped = true; - } - } - *track = *bestHypo; - return swapped; -} + return true; + }; -// create a new seed either from the existing track inner param or reseed from the edgepointd and cluster in the middle -template -TrackITSExt TrackerTraits::seedTrackForRefit(const CellSeedN& seed) -{ - TrackITSExt temporaryTrack(seed); - int lrMin = nLayers, lrMax = 0, lrMid = 0; - for (int iL = 0; iL < nLayers; ++iL) { - const int idx = seed.getCluster(iL); - temporaryTrack.setExternalClusterIndex(iL, idx, idx != constants::UnusedIndex); - if (idx != constants::UnusedIndex) { - lrMin = o2::gpu::CAMath::Min(lrMin, iL); - lrMax = o2::gpu::CAMath::Max(lrMax, iL); - } - } - int ncl = temporaryTrack.getNClusters(); - if (ncl < mTrkParams[0].ReseedIfShorter) { // reseed with circle passing via edges and the midpoint - if (ncl == mTrkParams[0].NLayers) { - lrMin = 0; - lrMax = mTrkParams[0].NLayers - 1; - lrMid = (lrMin + lrMax) / 2; - } else { - lrMid = lrMin + 1; - float midR = 0.5 * (mTrkParams[0].LayerRadii[lrMax] + mTrkParams[0].LayerRadii[lrMin]), dstMidR = o2::gpu::GPUCommonMath::Abs(midR - mTrkParams[0].LayerRadii[lrMid]); - for (int iL = lrMid + 1; iL < lrMax; ++iL) { // find the midpoint as closest to the midR - auto dst = o2::gpu::GPUCommonMath::Abs(midR - mTrkParams[0].LayerRadii[iL]); - if (dst < dstMidR) { - lrMid = iL; - dstMidR = dst; + for (int i{0}; i < static_cast(fclusSort.size()); ++i) { + auto& track = tracks[fclusSort[i]]; + for (int j{i + 1}; j < static_cast(fclusSort.size()) && tracks[fclusSort[j]].getFirstLayerClusterIndex() == track.getFirstLayerClusterIndex(); ++j) { + auto& track2 = tracks[fclusSort[j]]; + if (areTracksSelected(track, track2)) { + track.setSharedClusters(); + track2.setSharedClusters(); } } } - const auto& cluster0_tf = mTimeFrame->getTrackingFrameInfoOnLayer(lrMin)[seed.getCluster(lrMin)]; // if the sensor frame! - const auto& cluster1_gl = mTimeFrame->getUnsortedClusters()[lrMid][seed.getCluster(lrMid)]; // global frame - const auto& cluster2_gl = mTimeFrame->getUnsortedClusters()[lrMax][seed.getCluster(lrMax)]; // global frame - temporaryTrack.getParamIn() = buildTrackSeed(cluster2_gl, cluster1_gl, cluster0_tf, true); } - temporaryTrack.resetCovariance(); - temporaryTrack.setCov(temporaryTrack.getQ2Pt() * temporaryTrack.getQ2Pt() * temporaryTrack.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2); - return temporaryTrack; } -/// Clusters are given from inside outward (cluster3 is the outermost). The outermost cluster is given in the tracking -/// frame coordinates whereas the others are referred to the global frame. -template -track::TrackParCov TrackerTraits::buildTrackSeed(const Cluster& cluster1, const Cluster& cluster2, const TrackingFrameInfo& tf3, bool reverse) -{ - const float sign = reverse ? -1.f : 1.f; - - float ca, sa; - o2::gpu::CAMath::SinCos(tf3.alphaTrackingFrame, sa, ca); - - const float x1 = cluster1.xCoordinate * ca + cluster1.yCoordinate * sa; - const float y1 = -cluster1.xCoordinate * sa + cluster1.yCoordinate * ca; - const float x2 = cluster2.xCoordinate * ca + cluster2.yCoordinate * sa; - const float y2 = -cluster2.xCoordinate * sa + cluster2.yCoordinate * ca; - const float x3 = tf3.xTrackingFrame; - const float y3 = tf3.positionTrackingFrame[0]; - - float snp, q2pt, q2pt2; - if (mIsZeroField) { - const float dx = x3 - x1; - const float dy = y3 - y1; - snp = sign * dy / o2::gpu::CAMath::Hypot(dx, dy); - q2pt = 1.f / track::kMostProbablePt; - q2pt2 = 1.f; - } else { - const float crv = math_utils::computeCurvature(x3, y3, x2, y2, x1, y1); - snp = sign * crv * (x3 - math_utils::computeCurvatureCentreX(x3, y3, x2, y2, x1, y1)); - q2pt = sign * crv / (mBz * o2::constants::math::B2C); - q2pt2 = crv * crv; - } - - const float tgl = 0.5f * (math_utils::computeTanDipAngle(x1, y1, x2, y2, cluster1.zCoordinate, cluster2.zCoordinate) + - math_utils::computeTanDipAngle(x2, y2, x3, y3, cluster2.zCoordinate, tf3.positionTrackingFrame[1])); - const float sg2q2pt = track::kC1Pt2max * (q2pt2 > 0.0005f ? (q2pt2 < 1.f ? q2pt2 : 1.f) : 0.0005f); - - return {x3, tf3.alphaTrackingFrame, {y3, tf3.positionTrackingFrame[1], snp, tgl, q2pt}, {tf3.covarianceTrackingFrame[0], tf3.covarianceTrackingFrame[1], tf3.covarianceTrackingFrame[2], 0.f, 0.f, track::kCSnp2max, 0.f, 0.f, 0.f, track::kCTgl2max, 0.f, 0.f, 0.f, 0.f, sg2q2pt}}; -} - -template -void TrackerTraits::setBz(float bz) +template +void TrackerTraits::setBz(float bz) { mBz = bz; - mIsZeroField = std::abs(mBz) < 0.01; mTimeFrame->setBz(bz); } -template -bool TrackerTraits::isMatLUT() const -{ - return o2::base::Propagator::Instance()->getMatLUT() && (mTrkParams[0].CorrType == o2::base::PropagatorImpl::MatCorrType::USEMatCorrLUT); -} - -template -void TrackerTraits::setNThreads(int n, std::shared_ptr& arena) +template +void TrackerTraits::setNThreads(int n, std::shared_ptr& arena) { -#if defined(OPTIMISATION_OUTPUT) || defined(CA_DEBUG) +#if defined(OPTIMISATION_OUTPUT) mTaskArena = std::make_shared(1); #else if (arena == nullptr) { @@ -1322,15 +925,18 @@ void TrackerTraits::setNThreads(int n, std::shared_ptr LOGP(info, "Setting tracker with {} threads.", n); } else { mTaskArena = arena; - LOGP(info, "Attaching tracker to calling thread's arena"); } #endif } template class TrackerTraits<7>; +template void TrackerTraits<7>::processNeighbours(int, int, int, const bounded_vector&, const bounded_vector&, const bounded_vector&, bounded_vector>&, bounded_vector&, bounded_vector&); +template void TrackerTraits<7>::processNeighbours>(int, int, int, const bounded_vector>&, const bounded_vector&, const bounded_vector&, bounded_vector>&, bounded_vector&, bounded_vector&); // ALICE3 upgrade #ifdef ENABLE_UPGRADES template class TrackerTraits<11>; +template void TrackerTraits<11>::processNeighbours(int, int, int, const bounded_vector&, const bounded_vector&, const bounded_vector&, bounded_vector>&, bounded_vector&, bounded_vector&); +template void TrackerTraits<11>::processNeighbours>(int, int, int, const bounded_vector>&, const bounded_vector&, const bounded_vector&, bounded_vector>&, bounded_vector&, bounded_vector&); #endif } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackingConfigParam.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackingConfigParam.cxx index 3101c34d4ab8f..47b5f8ffffdb1 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackingConfigParam.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackingConfigParam.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -9,36 +9,6 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include - -#include "Framework/Logger.h" #include "ITStracking/TrackingConfigParam.h" - O2ParamImpl(o2::its::VertexerParamConfig); O2ParamImpl(o2::its::TrackerParamConfig); -O2ParamImpl(o2::its::ITSGpuTrackingParamConfig); - -namespace o2::its -{ - -void ITSGpuTrackingParamConfig::maybeOverride() const -{ - if (nBlocks == OverrideValue && nThreads == OverrideValue) { - return; - } - const auto name = getName(); - auto members = getDataMembers(); - for (auto member : *members) { - if (!member.name.ends_with(BlocksName) && !member.name.ends_with(ThreadsName)) { - if (nBlocks != OverrideValue && member.name.starts_with(BlocksName) && (member.value != nBlocks)) { - o2::conf::ConfigurableParam::setValue(name, member.name, nBlocks); - } - if (nThreads != OverrideValue && member.name.starts_with(ThreadsName) && (member.value != nThreads)) { - o2::conf::ConfigurableParam::setValue(name, member.name, nThreads); - } - } - } - LOGP(info, "Overwriting gpu threading parameters"); -} // namespace o2::its - -} // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx index d5f13cd9d25ea..7f10419d63fea 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackingInterface.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -9,16 +9,20 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +#include +#include +#include #include #include -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "ITSBase/GeometryTGeo.h" -#include "ITSReconstruction/FastMultEstConfig.h" -#include "ITSReconstruction/FastMultEst.h" +#include "ITStracking/FastMultEstConfig.h" +#include "ITStracking/FastMultEst.h" +#include "ITStracking/ROFLookupTables.h" #include "ITStracking/TrackingConfigParam.h" #include "ITStracking/TrackingInterface.h" @@ -28,6 +32,8 @@ #include "CommonDataFormat/IRFrame.h" #include "DetectorsBase/GRPGeomHelper.h" #include "ITStracking/BoundedAllocator.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/DataRefUtils.h" #include "Framework/DeviceSpec.h" using namespace o2::framework; @@ -44,9 +50,16 @@ void ITSTrackingInterface::initialise() } auto trackParams = TrackingMode::getTrackingParameters(mMode); auto vertParams = TrackingMode::getVertexingParameters(mMode); + overrideParameters(trackParams, vertParams); LOGP(info, "Initializing tracker in {} phase reconstruction with {} passes for tracking and {}/{} for vertexing", TrackingMode::toString(mMode), trackParams.size(), o2::its::VertexerParamConfig::Instance().nIterations, vertParams.size()); mTracker->setParameters(trackParams); mVertexer->setParameters(vertParams); + TrackingParameters vertexTrackingParams; + mTimeFrame->initVertexingTopology(vertexTrackingParams); + if (!trackParams.empty()) { + mTimeFrame->initDefaultTrackingTopology(trackParams[0], NLayers); + mTimeFrame->initTrackerTopologies(gsl::span(trackParams.data(), trackParams.size())); + } if (mMode == TrackingMode::Cosmics) { mRunVertexer = false; @@ -69,21 +82,67 @@ void ITSTrackingInterface::initialise() } mVertexer->setNThreads(vertConf.nThreads, mTaskArena); mTracker->setNThreads(trackConf.nThreads, mTaskArena); + mTimeFrame->setIsStaggered(mDoStaggering); + + // prepare data filter + for (int iLayer = 0; iLayer < ((mDoStaggering) ? NLayers : 1); ++iLayer) { + mFilter.emplace_back("compClusters", "ITS", "COMPCLUSTERS", iLayer, Lifetime::Timeframe); + mFilter.emplace_back("patterns", "ITS", "PATTERNS", iLayer, Lifetime::Timeframe); + mFilter.emplace_back("ROframe", "ITS", "CLUSTERSROF", iLayer, Lifetime::Timeframe); + if (mIsMC) { + mFilter.emplace_back("itsmclabels", "ITS", "CLUSTERSMCTR", iLayer, Lifetime::Timeframe); + } + } } void ITSTrackingInterface::run(framework::ProcessingContext& pc) { - auto compClusters = pc.inputs().get>("compClusters"); - gsl::span patterns = pc.inputs().get>("patterns"); + const auto& par = o2::itsmft::DPLAlpideParam::Instance(); + + // filter input and compose + std::array, NLayers> compClusters; + std::array, NLayers> patterns; + std::array, NLayers> rofsinput; + std::array*, NLayers> labels{}; + for (const DataRef& ref : framework::InputRecordWalker{pc.inputs(), mFilter}) { + auto const* dh = DataRefUtils::getHeader(ref); + if (framework::DataRefUtils::match(ref, {"compClusters", framework::ConcreteDataTypeMatcher{"ITS", "COMPCLUSTERS"}})) { + compClusters[dh->subSpecification] = pc.inputs().get>(ref); + } + if (framework::DataRefUtils::match(ref, {"patterns", framework::ConcreteDataTypeMatcher{"ITS", "PATTERNS"}})) { + patterns[dh->subSpecification] = pc.inputs().get>(ref); + } + if (framework::DataRefUtils::match(ref, {"ROframes", framework::ConcreteDataTypeMatcher{"ITS", "CLUSTERSROF"}})) { + rofsinput[dh->subSpecification] = pc.inputs().get>(ref); + } + if (framework::DataRefUtils::match(ref, {"itsmclabels", framework::ConcreteDataTypeMatcher{"ITS", "CLUSTERSMCTR"}})) { + labels[dh->subSpecification] = pc.inputs().get*>(ref).release(); + } + } + + bool hasClusters = false; + for (int iLayer = 0; iLayer < ((mDoStaggering) ? NLayers : 1); ++iLayer) { + LOGP(info, "ITSTracker{} pulled {} clusters, {} RO frames", ((mDoStaggering) ? std::format(" on layer {}", iLayer) : ""), compClusters[iLayer].size(), rofsinput[iLayer].size()); + if (compClusters[iLayer].empty()) { + LOGP(warn, " -> received no processable data{}", (mDoStaggering) ? std::format(" on layer {}", iLayer) : ""); + } else { + hasClusters = true; + } + if (mIsMC) { + LOG(info) << " -> " << labels[iLayer]->getIndexedSize() << " MC label objects"; + } + } + + const auto& tfInfo = pc.services().get(); gsl::span physTriggers; std::vector fromTRD; if (mUseTriggers == 2) { // use TRD triggers - o2::InteractionRecord ir{0, pc.services().get().firstTForbit}; + o2::InteractionRecord irFirstTF{0, tfInfo.firstTForbit}; auto trdTriggers = pc.inputs().get>("phystrig"); for (const auto& trig : trdTriggers) { - if (trig.getBCData() >= ir && trig.getNumberOfTracklets()) { - ir = trig.getBCData(); - fromTRD.emplace_back(o2::itsmft::PhysTrigger{ir, 0}); + if (trig.getBCData() >= irFirstTF && trig.getNumberOfTracklets()) { + irFirstTF = trig.getBCData(); + fromTRD.emplace_back(o2::itsmft::PhysTrigger{.ir = irFirstTF, .data = 0}); } } physTriggers = gsl::span(fromTRD.data(), fromTRD.size()); @@ -91,42 +150,46 @@ void ITSTrackingInterface::run(framework::ProcessingContext& pc) physTriggers = pc.inputs().get>("phystrig"); } - auto rofsinput = pc.inputs().get>("ROframes"); - auto& trackROFvec = pc.outputs().make>(Output{"ITS", "ITSTrackROF", 0}, rofsinput.begin(), rofsinput.end()); + const int clockLayerId{mDoStaggering ? mTimeFrame->getROFOverlapTableView().getClock() : 0}; auto& irFrames = pc.outputs().make>(Output{"ITS", "IRFRAMES", 0}); - const auto& alpParams = o2::itsmft::DPLAlpideParam::Instance(); // RS: this should come from CCDB - - irFrames.reserve(trackROFvec.size()); - int nBCPerTF = alpParams.roFrameLengthInBC; - - LOGP(info, "ITSTracker pulled {} clusters, {} RO frames {}", compClusters.size(), trackROFvec.size(), compClusters.empty() ? " -> received no processable data will skip" : ""); - const dataformats::MCTruthContainer* labels = nullptr; - gsl::span mc2rofs; - if (mIsMC) { - labels = pc.inputs().get*>("itsmclabels").release(); - // get the array as read-only span, a snapshot is sent forward - pc.outputs().snapshot(Output{"ITS", "ITSTrackMC2ROF", 0}, pc.inputs().get>("ITSMC2ROframes")); - LOG(info) << labels->getIndexedSize() << " MC label objects , in " << mc2rofs.size() << " MC events"; - } + irFrames.reserve(rofsinput[clockLayerId].size()); auto& allClusIdx = pc.outputs().make>(Output{"ITS", "TRACKCLSID", 0}); auto& allTracks = pc.outputs().make>(Output{"ITS", "TRACKS", 0}); - auto& vertROFvec = pc.outputs().make>(Output{"ITS", "VERTICESROF", 0}); + auto& allTrackROFs = pc.outputs().make>(Output{"ITS", "ITSTrackROF", 0}); auto& vertices = pc.outputs().make>(Output{"ITS", "VERTICES", 0}); + auto& vertROFvec = pc.outputs().make>(Output{"ITS", "VERTICESROF", 0}); // TODO fill this! // MC static pmr::vector dummyMCLabTracks, dummyMCLabVerts; static pmr::vector dummyMCPurVerts; auto& allTrackLabels = mIsMC ? pc.outputs().make>(Output{"ITS", "TRACKSMCTR", 0}) : dummyMCLabTracks; auto& allVerticesLabels = mIsMC ? pc.outputs().make>(Output{"ITS", "VERTICESMCTR", 0}) : dummyMCLabVerts; - bool writeContLabels = mIsMC && o2::its::VertexerParamConfig::Instance().outputContLabels; - auto& allVerticesContLabels = writeContLabels ? pc.outputs().make>(Output{"ITS", "VERTICESMCTRCONT", 0}) : dummyMCLabVerts; auto& allVerticesPurities = mIsMC ? pc.outputs().make>(Output{"ITS", "VERTICESMCPUR", 0}) : dummyMCPurVerts; - std::uint32_t roFrame = 0; - - bool continuous = o2::base::GRPGeomHelper::instance().getGRPECS()->isDetContinuousReadOut(o2::detectors::DetID::ITS); - LOG(info) << "ITSTracker RO: continuous=" << continuous; + const auto clock = mTimeFrame->getROFOverlapTableView().getClock(); + const auto& clockLayer = mTimeFrame->getROFOverlapTableView().getClockLayer(); + auto setBCData = [&](auto& rofs) { + for (size_t iROF{0}; iROF < rofs.size(); ++iROF) { // set BC data + auto& rof = rofs[iROF]; + int orb = (iROF * par.getROFLengthInBC(clock) / o2::constants::lhc::LHCMaxBunches) + tfInfo.firstTForbit; + int bc = (iROF * par.getROFLengthInBC(clock) % o2::constants::lhc::LHCMaxBunches) + par.getROFDelayInBC(clock); + o2::InteractionRecord ir(bc, orb); + rof.setBCData(ir); + rof.setROFrame(iROF); + rof.setNEntries(0); + rof.setFirstEntry(-1); + } + }; + + if (!hasClusters) { + // skip processing if no data is received entirely but still create empty output so consumers do not wait + allTrackROFs.resize(clockLayer.mNROFsTF); + vertROFvec.resize(clockLayer.mNROFsTF); + setBCData(allTrackROFs); + setBCData(vertROFvec); + return; + } if (mOverrideBeamEstimation) { mTimeFrame->setBeamPosition(mMeanVertex->getX(), @@ -137,52 +200,54 @@ void ITSTrackingInterface::run(framework::ProcessingContext& pc) } mTracker->setBz(o2::base::Propagator::Instance()->getNominalBz()); + mTracker->setTimeSlice(tfInfo.timeslice); - gsl::span::iterator pattIt = patterns.begin(); - gsl::span trackROFspan(trackROFvec); - loadROF(trackROFspan, compClusters, pattIt, labels); - pattIt = patterns.begin(); + for (int iLayer = 0; iLayer < ((mDoStaggering) ? NLayers : 1); ++iLayer) { + gsl::span::iterator pattIt = patterns[iLayer].begin(); + loadROF(rofsinput[iLayer], compClusters[iLayer], pattIt, ((mDoStaggering) ? iLayer : -1), labels[iLayer]); + } auto logger = [&](const std::string& s) { LOG(info) << s; }; auto fatalLogger = [&](const std::string& s) { LOG(fatal) << s; }; auto errorLogger = [&](const std::string& s) { LOG(error) << s; }; FastMultEst multEst; // mult estimator - std::vector processingMask, processUPCMask; - int cutVertexMult{0}, cutUPCVertex{0}, cutRandomMult = int(trackROFvec.size()) - multEst.selectROFs(trackROFvec, compClusters, physTriggers, processingMask); - processUPCMask.resize(processingMask.size(), false); - mTimeFrame->setMultiplicityCutMask(processingMask); - float vertexerElapsedTime{0.f}; + o2::its::ROFMaskTable processMultiplictyMask{mTimeFrame->getROFOverlapTable()}, processUPCMask{mTimeFrame->getROFOverlapTable()}; + multEst.selectROFs(rofsinput, compClusters, physTriggers, tfInfo.firstTForbit, mDoStaggering, mTimeFrame->getROFOverlapTableView(), processMultiplictyMask); + mTimeFrame->setMultiplicityCutMask(processMultiplictyMask); + for (int iLayer = 0; iLayer < ((mDoStaggering) ? NLayers : 1); ++iLayer) { + mTimeFrame->getROFMaskView().print(iLayer); + } + + float vertexerElapsedTime{0.f}, trackerElapsedTime{0.f}; if (mRunVertexer) { - vertROFvec.reserve(trackROFvec.size()); // Run seeding vertexer - if (!compClusters.empty()) { - vertexerElapsedTime = mVertexer->clustersToVertices(logger); + vertexerElapsedTime = mVertexer->clustersToVertices(logger); + const auto& vtx = mTimeFrame->getPrimaryVertices(); + vertices.insert(vertices.begin(), vtx.begin(), vtx.end()); + if (mIsMC) { + allVerticesLabels.reserve(vertices.size()); + allVerticesPurities.reserve(vertices.size()); + for (const auto& lbl : mTimeFrame->getPrimaryVerticesLabels()) { + allVerticesLabels.push_back(lbl.first); + allVerticesPurities.push_back(lbl.second); + } } - } else { // cosmics - mTimeFrame->resetRofPV(); - } - const auto& multEstConf = FastMultEstConfig::Instance(); // parameters for mult estimation and cuts - gsl::span> vMCRecInfo; - gsl::span vMCContLabels; - for (auto iRof{0}; iRof < trackROFspan.size(); ++iRof) { - bounded_vector vtxVecLoc; - auto& vtxROF = vertROFvec.emplace_back(trackROFspan[iRof]); - vtxROF.setFirstEntry(vertices.size()); + } + multEst.selectROFsWithVertices(vertices, mTimeFrame->getROFOverlapTableView(), processMultiplictyMask); + + auto clockROFspan = rofsinput[clockLayerId]; + auto clockTiming = mTimeFrame->getROFOverlapTableView().getClockLayer(); + for (auto iRof{0}; iRof < clockROFspan.size(); ++iRof) { + auto& vtxROF = vertROFvec.emplace_back(clockROFspan[iRof]); if (mRunVertexer) { - auto vtxSpan = mTimeFrame->getPrimaryVertices(iRof); - if (mIsMC) { - vMCRecInfo = mTimeFrame->getPrimaryVerticesMCRecInfo(iRof); - if (o2::its::VertexerParamConfig::Instance().outputContLabels) { - vMCContLabels = mTimeFrame->getPrimaryVerticesContributors(iRof); - } - } + auto vtxSpan = mTimeFrame->getPrimaryVertices(clockLayerId, iRof); if (o2::its::TrackerParamConfig::Instance().doUPCIteration) { if (!vtxSpan.empty()) { - if (vtxSpan[0].isFlagSet(Vertex::UPCMode) == 1) { // at least one vertex in this ROF and it is from second vertex iteration + bool hasUPC = std::any_of(vtxSpan.begin(), vtxSpan.end(), [](const auto& v) { return v.isFlagSet(Vertex::UPCMode); }); + if (hasUPC) { // at least one vertex in this ROF and it is from second vertex iteration LOGP(debug, "ROF {} rejected as vertices are from the UPC iteration", iRof); - processUPCMask[iRof] = true; - cutUPCVertex++; + processUPCMask.selectROF({clockTiming.getROFStartInBC(iRof), clockTiming.getROFEndInBC(iRof)}); vtxROF.setFlag(o2::itsmft::ROFRecord::VtxUPCMode); } else { // in all cases except if as standard mode vertex was found, the ROF was processed with UPC settings vtxROF.setFlag(o2::itsmft::ROFRecord::VtxStdMode); @@ -193,124 +258,114 @@ void ITSTrackingInterface::run(framework::ProcessingContext& pc) } else { vtxROF.setFlag(o2::itsmft::ROFRecord::VtxStdMode); } - vtxROF.setNEntries(vtxSpan.size()); - bool selROF = vtxSpan.empty(); - for (int iV{0}, iVC{0}; iV < vtxSpan.size(); ++iV) { - const auto& v = vtxSpan[iV]; - if (multEstConf.isVtxMultCutRequested() && !multEstConf.isPassingVtxMultCut(v.getNContributors())) { - iVC += v.getNContributors(); - continue; // skip vertex of unwanted multiplicity - } - selROF = true; - vertices.push_back(v); - if (mIsMC && !VertexerParamConfig::Instance().useTruthSeeding) { - allVerticesLabels.push_back(vMCRecInfo[iV].first); - allVerticesPurities.push_back(vMCRecInfo[iV].second); - if (o2::its::VertexerParamConfig::Instance().outputContLabels) { - allVerticesContLabels.insert(allVerticesContLabels.end(), vMCContLabels.begin() + iVC, vMCContLabels.begin() + iVC + v.getNContributors()); - } - } - iVC += v.getNContributors(); - } - if (processingMask[iRof] && !selROF) { // passed selection in clusters and not in vertex multiplicity - LOGP(info, "ROF {} rejected by the vertex multiplicity selection [{},{}]", iRof, multEstConf.cutMultVtxLow, multEstConf.cutMultVtxHigh); - processingMask[iRof] = selROF; - cutVertexMult++; - } - } else { // cosmics - vtxVecLoc.emplace_back(); - vtxVecLoc.back().setNContributors(1); - vtxROF.setNEntries(vtxVecLoc.size()); - for (auto& v : vtxVecLoc) { - vertices.push_back(v); - } - mTimeFrame->addPrimaryVertices(vtxVecLoc, 0); } } - if (mRunVertexer && !compClusters.empty()) { - LOG(info) << fmt::format(" - Vertex seeding total elapsed time: {} ms for {} ({} + {}) vertices found in {}/{} ROFs", - vertexerElapsedTime, - mTimeFrame->getPrimaryVerticesNum(), - mTimeFrame->getTotVertIteration()[0], - o2::its::VertexerParamConfig::Instance().nIterations > 1 ? mTimeFrame->getTotVertIteration()[1] : 0, - trackROFspan.size() - mTimeFrame->getNoVertexROF(), - trackROFspan.size()); - LOG(info) << fmt::format(" - FastMultEst: rejected {}/{} ROFs: random/mult.sel:{} (seed {}), vtx.sel:{}", cutRandomMult + cutVertexMult, trackROFspan.size(), cutRandomMult, multEst.lastRandomSeed, cutVertexMult); + + if (mRunVertexer && hasClusters) { + LOGP(info, " + Vertex seeding total elapsed time: {} ms for {} vertices found", vertexerElapsedTime, mTimeFrame->getPrimaryVerticesNum()); } + if (mOverrideBeamEstimation) { - LOG(info) << fmt::format(" - Beam position set to: {}, {} from meanvertex object", mTimeFrame->getBeamX(), mTimeFrame->getBeamY()); + LOG(info) << fmt::format(" + Beam position set to: {}, {} from meanvertex object", mTimeFrame->getBeamX(), mTimeFrame->getBeamY()); } else { - LOG(info) << fmt::format(" - Beam position computed for the TF: {}, {}", mTimeFrame->getBeamX(), mTimeFrame->getBeamY()); + LOG(info) << fmt::format(" + Beam position computed for the TF: {}, {}", mTimeFrame->getBeamX(), mTimeFrame->getBeamY()); } - if (mCosmicsProcessing && compClusters.size() > 1500 * trackROFspan.size()) { - LOG(error) << "Cosmics processing was requested with an average detector occupancy exceeding 1.e-7, skipping TF processing."; - } else { - if (!compClusters.empty()) { - mTimeFrame->setMultiplicityCutMask(processingMask); - mTimeFrame->setROFMask(processUPCMask); - // Run CA tracker - if (mMode == o2::its::TrackingMode::Async && o2::its::TrackerParamConfig::Instance().fataliseUponFailure) { - mTracker->clustersToTracks(logger, fatalLogger); - } else { - mTracker->clustersToTracks(logger, errorLogger); - } + + if (hasClusters) { + mTimeFrame->setMultiplicityCutMask(processMultiplictyMask); + mTimeFrame->setUPCCutMask(processUPCMask); + if (mMode == o2::its::TrackingMode::Async && o2::its::TrackerParamConfig::Instance().fataliseUponFailure) { + trackerElapsedTime = mTracker->clustersToTracks(logger, fatalLogger); + } else { + trackerElapsedTime = mTracker->clustersToTracks(logger, errorLogger); } - size_t totTracks{mTimeFrame->getNumberOfTracks()}, totClusIDs{mTimeFrame->getNumberOfUsedClusters()}; - if (totTracks) { - allTracks.reserve(totTracks); - allClusIdx.reserve(totClusIDs); + LOGP(info, " + Tracking total elapse time: {} ms for {} tracks found", trackerElapsedTime, mTimeFrame->getNumberOfTracks()); + } + if constexpr (constants::DoTimeBenchmarks) { + const auto& trackConf = o2::its::TrackerParamConfig::Instance(); + const auto& vertConf = o2::its::VertexerParamConfig::Instance(); + logger(std::format("=== TimeSlice {} processing completed in: {:.2f} ms using {}/{} thread(s) ===", tfInfo.timeslice, trackerElapsedTime + vertexerElapsedTime, vertConf.nThreads, trackConf.nThreads)); + } - if (mTimeFrame->hasBogusClusters()) { - LOG(warning) << fmt::format(" - The processed timeframe had {} clusters with wild z coordinates, check the dictionaries", mTimeFrame->hasBogusClusters()); - } + size_t totTracks{mTimeFrame->getNumberOfTracks()}, totClusIDs{mTimeFrame->getNumberOfUsedClusters()}; + if (totTracks) { + allTracks.reserve(totTracks); + allClusIdx.reserve(totClusIDs); - for (unsigned int iROF{0}; iROF < trackROFvec.size(); ++iROF) { - auto& tracksROF{trackROFvec[iROF]}; - auto& vtxROF = vertROFvec[iROF]; - auto& tracks = mTimeFrame->getTracks(iROF); - auto number{tracks.size()}; - auto first{allTracks.size()}; - int offset = -tracksROF.getFirstEntry(); // cluster entry!!! - tracksROF.setFirstEntry(first); - tracksROF.setNEntries(number); - tracksROF.setFlags(vtxROF.getFlags()); // copies 0xffffffff if cosmics - if (processingMask[iROF]) { - irFrames.emplace_back(tracksROF.getBCData(), tracksROF.getBCData() + nBCPerTF - 1).info = tracks.size(); - } - allTrackLabels.reserve(mTimeFrame->getTracksLabel(iROF).size()); // should be 0 if not MC - std::copy(mTimeFrame->getTracksLabel(iROF).begin(), mTimeFrame->getTracksLabel(iROF).end(), std::back_inserter(allTrackLabels)); - // Some conversions that needs to be moved in the tracker internals - for (unsigned int iTrk{0}; iTrk < tracks.size(); ++iTrk) { - auto& trc{tracks[iTrk]}; - trc.setFirstClusterEntry(allClusIdx.size()); // before adding tracks, create final cluster indices - int ncl = trc.getNumberOfClusters(), nclf = 0; - for (int ic = TrackITSExt::MaxClusters; ic--;) { // track internally keeps in->out cluster indices, but we want to store the references as out->in!!! - auto clid = trc.getClusterIndex(ic); - if (clid >= 0) { - trc.setClusterSize(ic, mTimeFrame->getClusterSize(clid)); - allClusIdx.push_back(clid); - nclf++; - } - } - assert(ncl == nclf); - allTracks.emplace_back(trc); - } - } - } else { - for (auto& r : trackROFvec) { // reset data copied from the clusters - r.setFirstEntry(0); - r.setNEntries(0); - } + if (mTimeFrame->hasBogusClusters()) { + LOG(warning) << fmt::format(" + The processed timeframe had {} clusters with wild z coordinates, check the dictionaries", mTimeFrame->hasBogusClusters()); } - LOGP(info, "ITSTracker pushed {} tracks and {} vertices", allTracks.size(), vertices.size()); - if (mIsMC) { - LOGP(info, "ITSTracker pushed {} track labels", allTrackLabels.size()); - LOGP(info, "ITSTracker pushed {} vertex labels", allVerticesLabels.size()); - if (!allVerticesContLabels.empty()) { - LOGP(info, "ITSTracker pushed {} vertex contributor labels", allVerticesContLabels.size()); + } + + auto& tracks = mTimeFrame->getTracks(); + allTrackLabels.reserve(mTimeFrame->getTracksLabel().size()); // should be 0 if not MC + std::copy(mTimeFrame->getTracksLabel().begin(), mTimeFrame->getTracksLabel().end(), std::back_inserter(allTrackLabels)); + // create the track to clock ROF association here + // the clock ROF is just the fastest ROF + // the number of ROFs does not necessarily reflect the actual ROFs + // due to possible delay of other layers, however it is guaranteed to be >=0 + // tracks are guaranteed to be sorted here by their lower edge + // we pick whatever is the largest possible number of rofs since there might be tracks/vertices which are beyond + // the clock layer + int highestROF{0}; + for (const auto& trc : tracks) { + highestROF = std::max(highestROF, (int)clockLayer.getROF(trc.getTimeStamp())); + } + for (const auto& vtx : vertices) { + highestROF = std::max(highestROF, (int)clockLayer.getROF(vtx.getTimeStamp().lower())); + } + highestROF = std::max(highestROF, (int)clockLayer.mNROFsTF); + allTrackROFs.resize(highestROF); + vertROFvec.resize(highestROF); + setBCData(allTrackROFs); + setBCData(vertROFvec); + + mTimeFrame->useMultiplictyMask(); // use multiplicty selection for IR frames + + std::vector rofEntries(highestROF + 1, 0); + for (unsigned int iTrk{0}; iTrk < tracks.size(); ++iTrk) { + auto& trc{tracks[iTrk]}; + trc.setFirstClusterEntry((int)allClusIdx.size()); // before adding tracks, create final cluster indices + int ncl = trc.getNumberOfClusters(), nclf = 0; + for (int ic = TrackITSExt::MaxClusters; ic--;) { // track internally keeps in->out cluster indices, but we want to store the references as out->in!!! + auto clid = trc.getClusterIndex(ic); + if (clid >= 0) { + trc.setClusterSize(ic, mTimeFrame->getClusterSize((mDoStaggering) ? ic : 0, clid)); + allClusIdx.push_back(clid); + nclf++; } - LOGP(info, "ITSTracker pushed {} vertex purities", allVerticesPurities.size()); } + assert(ncl == nclf); + allTracks.emplace_back(trc); + auto rof = clockLayer.getROF(trc.getTimeStamp()); + ++rofEntries[rof]; + } + std::exclusive_scan(rofEntries.begin(), rofEntries.end(), rofEntries.begin(), 0); + for (size_t iROF{0}; iROF < allTrackROFs.size(); ++iROF) { + allTrackROFs[iROF].setFirstEntry(rofEntries[iROF]); + allTrackROFs[iROF].setNEntries(rofEntries[iROF + 1] - rofEntries[iROF]); + if (mTimeFrame->getROFMaskView().isROFEnabled(clockLayerId, (int)iROF)) { + auto& irFrame = irFrames.emplace_back(allTrackROFs[iROF].getBCData(), allTrackROFs[iROF].getBCData() + clockLayer.mROFLength - 1); + irFrame.info = allTrackROFs[iROF].getNEntries(); + } + } + // same thing for vertices rofs + std::fill(rofEntries.begin(), rofEntries.end(), 0); + for (const auto& vtx : vertices) { + auto rof = clockLayer.getROF(vtx.getTimeStamp().lower()); + ++rofEntries[rof]; + } + std::exclusive_scan(rofEntries.begin(), rofEntries.end(), rofEntries.begin(), 0); + for (size_t iROF{0}; iROF < vertROFvec.size(); ++iROF) { + vertROFvec[iROF].setFirstEntry(rofEntries[iROF]); + vertROFvec[iROF].setNEntries(rofEntries[iROF + 1] - rofEntries[iROF]); + } + + LOGP(info, "ITSTracker pushed {} tracks in {} rofs and {} vertices {}", allTracks.size(), allTrackROFs.size(), vertices.size(), ((mDoStaggering) ? "in staggered-readout mode" : "")); + if (mIsMC) { + LOGP(info, "ITSTracker pushed {} track labels", allTrackLabels.size()); + LOGP(info, "ITSTracker pushed {} vertex labels", allVerticesLabels.size()); + LOGP(info, "ITSTracker pushed {} vertex purities", allVerticesPurities.size()); } mTimeFrame->wipe(); } @@ -324,7 +379,7 @@ void ITSTrackingInterface::updateTimeDependentParams(framework::ProcessingContex } if (!initOnceDone) { // this params need to be queried only once initOnceDone = true; - pc.inputs().get("itscldict"); // just to trigger the finaliseCCDB + requestTopologyDictionary(pc); pc.inputs().get*>("itsalppar"); if (pc.inputs().getPos("itsTGeo") >= 0) { pc.inputs().get("itsTGeo"); @@ -334,19 +389,50 @@ void ITSTrackingInterface::updateTimeDependentParams(framework::ProcessingContex initialise(); if (pc.services().get().inputTimesliceId == 0) { // print settings only for the 1st pipeling - o2::its::VertexerParamConfig::Instance().printKeyValues(); - o2::its::TrackerParamConfig::Instance().printKeyValues(); + // print all used settings + if (o2::its::FastMultEstConfig::Instance().isRequested()) { + o2::its::FastMultEstConfig::Instance().printKeyValues(true, true); + } const auto& vtxParams = mVertexer->getParameters(); + if (!vtxParams.empty()) { + o2::its::VertexerParamConfig::Instance().printKeyValues(true, true); + } + const auto& trParams = mTracker->getParameters(); + if (!trParams.empty()) { + o2::its::TrackerParamConfig::Instance().printKeyValues(true, true); + } + // quick summary for (size_t it = 0; it < vtxParams.size(); it++) { const auto& par = vtxParams[it]; LOGP(info, "vtxIter#{} : {}", it, par.asString()); } - const auto& trParams = mTracker->getParameters(); for (size_t it = 0; it < trParams.size(); it++) { const auto& par = trParams[it]; LOGP(info, "recoIter#{} : {}", it, par.asString()); } } + + // prepare rof lookup table(s) + const auto& par = o2::itsmft::DPLAlpideParam::Instance(); + const int nOrbitsPerTF = o2::base::GRPGeomHelper::getNHBFPerTF(); + TimeFrameN::ROFOverlapTableN rofTable; + TimeFrameN::ROFVertexLookupTableN vtxTable; + const auto& trackParams = mTracker->getParameters(); + for (int iLayer = 0; iLayer < NLayers; ++iLayer) { + const unsigned int nROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / par.getROFLengthInBC(iLayer); + const LayerTiming timing{ + .mNROFsTF = (nROFsPerOrbit * nOrbitsPerTF), + .mROFLength = (uint32_t)par.getROFLengthInBC(iLayer), + .mROFDelay = (uint32_t)par.getROFDelayInBC(iLayer), + .mROFBias = (uint32_t)par.getROFBiasInBC(iLayer), + .mROFAddTimeErr = (trackParams.empty() ? o2::its::TrackerParamConfig::Instance().addTimeError[iLayer] : trackParams[0].AddTimeError[iLayer])}; + rofTable.defineLayer(iLayer, timing); + vtxTable.defineLayer(iLayer, timing); + } + rofTable.init(); + mTimeFrame->setROFOverlapTable(rofTable); + vtxTable.init(); + mTimeFrame->setROFVertexLookupTable(vtxTable); } } @@ -381,6 +467,7 @@ void ITSTrackingInterface::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) void ITSTrackingInterface::printSummary() const { + mVertexer->printSummary(); mTracker->printSummary(); } @@ -405,10 +492,16 @@ void ITSTrackingInterface::setTraitsFromProvider(VertexerTraitsN* vertexerTraits mVertexer->setMemoryPool(mMemoryPool); } +void ITSTrackingInterface::requestTopologyDictionary(framework::ProcessingContext& pc) +{ + pc.inputs().get("itscldict"); // just to trigger the finaliseCCDB +} + void ITSTrackingInterface::loadROF(gsl::span& trackROFspan, gsl::span clusters, gsl::span::iterator& pattIt, + int layer, const dataformats::MCTruthContainer* mcLabels) { - mTimeFrame->loadROFrameData(trackROFspan, clusters, pattIt, mDict, mcLabels); + mTimeFrame->loadROFrameData(trackROFspan, clusters, pattIt, mDict, layer, mcLabels); } diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackingLinkDef.h b/Detectors/ITSMFT/ITS/tracking/src/TrackingLinkDef.h index 2fe70e96248f9..46af692fe0c15 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/TrackingLinkDef.h +++ b/Detectors/ITSMFT/ITS/tracking/src/TrackingLinkDef.h @@ -24,6 +24,9 @@ #pragma link C++ class o2::its::TrackingFrameInfo + ; #pragma link C++ class std::vector < o2::its::TrackingFrameInfo> + ; +#pragma link C++ class o2::its::TrackingFrameInfo + ; +#pragma link C++ class std::vector < o2::its::TrackingFrameInfo> + ; + #pragma link C++ class o2::its::Line + ; #pragma link C++ class std::vector < o2::its::Line> + ; @@ -36,7 +39,7 @@ #pragma link C++ class o2::its::TrackerParamConfig + ; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::its::TrackerParamConfig> + ; -#pragma link C++ class o2::its::ITSGpuTrackingParamConfig + ; -#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::its::ITSGpuTrackingParamConfig> + ; +#pragma link C++ class o2::its::FastMultEstConfig + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::its::FastMultEstConfig> + ; #endif diff --git a/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx b/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx index c4b1fb427513f..ba37275f87688 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Vertexer.cxx @@ -15,19 +15,14 @@ #include "ITStracking/Vertexer.h" #include "ITStracking/BoundedAllocator.h" -#include "ITStracking/Cluster.h" - -#include "ITStracking/ClusterLines.h" -#include "ITStracking/Tracklet.h" -#include "ITStracking/IndexTableUtils.h" #include "ITStracking/VertexerTraits.h" #include "ITStracking/TrackingConfigParam.h" namespace o2::its { -template -Vertexer::Vertexer(VertexerTraitsN* traits) : mTraits(traits) +template +Vertexer::Vertexer(VertexerTraitsN* traits) : mTraits(traits) { if (!mTraits) { LOG(fatal) << "nullptr passed to ITS vertexer construction."; @@ -35,21 +30,23 @@ Vertexer::Vertexer(VertexerTraitsN* traits) : mTraits(traits) mVertParams.resize(1); } -template -float Vertexer::clustersToVertices(LogFunc logger) +template +float Vertexer::clustersToVertices(LogFunc logger) { LogFunc evalLog = [](const std::string&) {}; if (mTimeFrame->hasMCinformation() && mVertParams[0].useTruthSeeding) { - return evaluateTask(&Vertexer::addTruthSeeds, StateNames[mCurState = TruthSeeding], 0, evalLog); + float t = evaluateTask(&Vertexer::addTruthSeeds, StateNames[mCurStep = TruthSeeding], 0, evalLog); + sortVertices(); + ++mTimeFrameCounter; + return t; } TrackingParameters trkPars; - TimeFrameGPUParameters tfGPUpar; - mTraits->updateVertexingParameters(mVertParams, tfGPUpar); + mTraits->updateVertexingParameters(mVertParams); auto handleException = [&](const auto& err) { - LOGP(error, "Encountered critical error in step {}, stopping further processing of this TF: {}", StateNames[mCurState], err.what()); + LOGP(error, "Encountered critical error in step {}, stopping further processing of this TF: {}", StateNames[mCurStep], err.what()); if (!mVertParams[0].DropTFUponFailure) { throw err; } else { @@ -58,25 +55,37 @@ float Vertexer::clustersToVertices(LogFunc logger) }; float timeTracklet{0.f}, timeSelection{0.f}, timeVertexing{0.f}, timeInit{0.f}; + bool completed = false; try { - for (int iteration = 0; iteration < std::min(mVertParams[0].nIterations, (int)mVertParams.size()); ++iteration) { + for (int iteration = 0; iteration < (int)mVertParams.size(); ++iteration) { mMemoryPool->setMaxMemory(mVertParams[iteration].MaxMemory); unsigned int nTracklets01{0}, nTracklets12{0}; logger(fmt::format("=== ITS {} Seeding vertexer iteration {} summary:", mTraits->getName(), iteration)); - trkPars.PhiBins = mTraits->getVertexingParameters()[0].PhiBins; - trkPars.ZBins = mTraits->getVertexingParameters()[0].ZBins; - auto timeInitIteration = evaluateTask(&Vertexer::initialiseVertexer, StateNames[mCurState = Init], iteration, evalLog, trkPars, iteration); - auto timeTrackletIteration = evaluateTask(&Vertexer::findTracklets, StateNames[mCurState = Trackleting], iteration, evalLog, iteration); + const auto& currentVtxPars = mTraits->getVertexingParameters()[iteration]; + trkPars.PhiBins = currentVtxPars.PhiBins; + trkPars.ZBins = currentVtxPars.ZBins; + trkPars.LayerZ = currentVtxPars.LayerZ; + trkPars.LayerRadii = currentVtxPars.LayerRadii; + trkPars.PassFlags = mVertParams[iteration].PassFlags; + trkPars.PassFlags.set(IterationStep::FirstPass, IterationStep::RebuildClusterLUT); + auto timeInitIteration = evaluateTask(&Vertexer::initialiseVertexer, StateNames[mCurStep = Init], iteration, evalLog, trkPars); + auto timeTrackletIteration = evaluateTask(&Vertexer::findTracklets, StateNames[mCurStep = Trackleting], iteration, evalLog, iteration); nTracklets01 = mTimeFrame->getTotalTrackletsTF(0); nTracklets12 = mTimeFrame->getTotalTrackletsTF(1); - auto timeSelectionIteration = evaluateTask(&Vertexer::validateTracklets, StateNames[mCurState = Validating], iteration, evalLog, iteration); - auto timeVertexingIteration = evaluateTask(&Vertexer::findVertices, StateNames[mCurState = Finding], iteration, evalLog, iteration); - printEpilog(logger, nTracklets01, nTracklets12, mTimeFrame->getNLinesTotal(), mTimeFrame->getTotVertIteration()[iteration], timeInitIteration, timeTrackletIteration, timeSelectionIteration, timeVertexingIteration); + auto timeSelectionIteration = evaluateTask(&Vertexer::validateTracklets, StateNames[mCurStep = Selection], iteration, evalLog, iteration); + const auto nVerticesBefore = mTimeFrame->getPrimaryVertices().size(); + auto timeVertexingIteration = evaluateTask(&Vertexer::findVertices, StateNames[mCurStep = Finding], iteration, evalLog, iteration); + const auto nVerticesAfter = mTimeFrame->getPrimaryVertices().size(); + printEpilog(logger, nTracklets01, nTracklets12, mTimeFrame->getNLinesTotal(), nVerticesAfter - nVerticesBefore, nVerticesAfter, timeInitIteration, timeTrackletIteration, timeSelectionIteration, timeVertexingIteration); timeInit += timeInitIteration; timeTracklet += timeTrackletIteration; timeSelection += timeSelectionIteration; timeVertexing += timeVertexingIteration; + + // update LUT with all currently found vertices so in second iteration we can check vertPerROFThreshold + sortVertices(); } + completed = true; } catch (const BoundedMemoryResource::MemoryLimitExceeded& err) { handleException(err); } catch (const std::bad_alloc& err) { @@ -85,31 +94,94 @@ float Vertexer::clustersToVertices(LogFunc logger) LOGP(fatal, "Uncaught exception!"); } + if (completed) { + ++mTimeFrameCounter; + } + return timeInit + timeTracklet + timeSelection + timeVertexing; } -template -void Vertexer::adoptTimeFrame(TimeFrameN& tf) +template +void Vertexer::sortVertices() +{ + auto& pvs = mTimeFrame->getPrimaryVertices(); + bounded_vector indices(pvs.size(), mMemoryPool.get()); + std::iota(indices.begin(), indices.end(), 0); + // provide vertices sorted by lower-bound + std::sort(indices.begin(), indices.end(), [&pvs](size_t i, size_t j) { + const auto& a = pvs[i].getTimeStamp(); + const auto& b = pvs[j].getTimeStamp(); + const auto aLower = a.lower(); + const auto bLower = b.lower(); + if (aLower != bLower) { + return aLower < bLower; + } + return pvs[i].getNContributors() > pvs[j].getNContributors(); + }); + bounded_vector sortedVtx(mMemoryPool.get()); + sortedVtx.reserve(pvs.size()); + for (const size_t idx : indices) { + sortedVtx.push_back(pvs[idx]); + } + pvs.swap(sortedVtx); + if (mTimeFrame->hasMCinformation()) { + auto& mc = mTimeFrame->getPrimaryVerticesLabels(); + bounded_vector sortedMC(mMemoryPool.get()); + for (const size_t idx : indices) { + sortedMC.push_back(mc[idx]); + } + mc.swap(sortedMC); + } + // update LUT after sorting + mTimeFrame->updateROFVertexLookupTable(); +} + +template +void Vertexer::adoptTimeFrame(TimeFrameN& tf) { mTimeFrame = &tf; mTraits->adoptTimeFrame(&tf); } -template -void Vertexer::printEpilog(LogFunc& logger, - const unsigned int trackletN01, const unsigned int trackletN12, - const unsigned selectedN, const unsigned int vertexN, const float initT, - const float trackletT, const float selecT, const float vertexT) +template +void Vertexer::addTimingStatCurStep(int iteration, double timeMs) +{ + if (iteration < 0) { + return; + } + if (mTimingStats.size() < (iteration + 1)) { + mTimingStats.resize(iteration + 1); + } + mTimingStats[iteration][mCurStep].add(timeMs); +} + +template +void Vertexer::printSummary() const { - logger(fmt::format(" - {} Vertexer: found {} | {} tracklets in: {} ms", mTraits->getName(), trackletN01, trackletN12, trackletT)); - logger(fmt::format(" - {} Vertexer: selected {} tracklets in: {} ms", mTraits->getName(), selectedN, selecT)); - logger(fmt::format(" - {} Vertexer: found {} vertices in: {} ms", mTraits->getName(), vertexN, vertexT)); - if (mVertParams[0].PrintMemory) { - mTimeFrame->printArtefactsMemory(); - mMemoryPool->print(); + LOGP(info, "Vertexer summary: Processed {} TFs", mTimeFrameCounter); + for (size_t iteration = 0; iteration < mTimingStats.size(); ++iteration) { + for (size_t state = 0; state < NSteps; ++state) { + const auto& stats = mTimingStats[iteration][state]; + if (!stats.calls) { + continue; + } + LOGP(info, " - iter {} {}: calls={} total={:.2f} ms avg={:.2f} ms", iteration, StateNames[state], stats.calls, stats.totalTimeMs, stats.averageTimeMs()); + } } } +template +void Vertexer::printEpilog(LogFunc& logger, + unsigned int trackletN01, unsigned int trackletN12, + unsigned selectedN, unsigned int vertexN, unsigned int totalVertexN, + float initT, float trackletT, float selecT, float vertexT) +{ + logger(fmt::format(" - {}: completed in {:.2f} ms", StateNames[Init], initT)); + logger(fmt::format(" - {}: found {} | {} tracklets in {:.2f} ms", StateNames[Trackleting], trackletN01, trackletN12, trackletT)); + logger(fmt::format(" - {}: selected {} tracklets in {:.2f} ms", StateNames[Selection], selectedN, selecT)); + logger(fmt::format(" - {}: found {} vertices (total {}) in {:.2f} ms", StateNames[Finding], vertexN, totalVertexN, vertexT)); +} + template class Vertexer<7>; } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx b/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx index 0c4ecb0b12df1..237e99e57e0da 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/VertexerTraits.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -10,10 +10,11 @@ // or submit itself to any jurisdiction. /// +#include #include #include -#include -#include +#include +#include #include #include @@ -22,30 +23,35 @@ #include "ITStracking/VertexerTraits.h" #include "ITStracking/BoundedAllocator.h" #include "ITStracking/ClusterLines.h" +#include "ITStracking/Definitions.h" +#include "ITStracking/LineVertexerHelpers.h" #include "ITStracking/Tracklet.h" #include "SimulationDataFormat/DigitizationContext.h" +#include "SimulationDataFormat/O2DatabasePDG.h" #include "Steer/MCKinematicsReader.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsRaw/HBFUtils.h" -#include "CommonUtils/TreeStreamRedirector.h" namespace o2::its { +namespace +{ -template -static void trackleterKernelHost( +template +void trackleterKernelHost( const gsl::span& clustersNextLayer, // 0 2 const gsl::span& clustersCurrentLayer, // 1 1 const gsl::span& usedClustersNextLayer, // 0 2 - int* indexTableNext, + const int* indexTableNext, const float phiCut, bounded_vector& tracklets, gsl::span foundTracklets, - const IndexTableUtils& utils, - const short pivotRof, - const short targetRof, - gsl::span rofFoundTrackletsOffsets, // we want to change those, to keep track of the offset in deltaRof>0 - const int maxTrackletsPerCluster = static_cast(2e3)) + const IndexTableUtils& utils, + const TimeEstBC& timErr, + gsl::span rofFoundTrackletsOffsets, + const int globalOffsetNextLayer, + const int globalOffsetCurrentLayer, + const int maxTrackletsPerCluster) { const int PhiBins{utils.getNphiBins()}; const int ZBins{utils.getNzBins()}; @@ -53,30 +59,30 @@ static void trackleterKernelHost( for (int iCurrentLayerClusterIndex = 0; iCurrentLayerClusterIndex < clustersCurrentLayer.size(); ++iCurrentLayerClusterIndex) { int storedTracklets{0}; const Cluster& currentCluster{clustersCurrentLayer[iCurrentLayerClusterIndex]}; - const int4 selectedBinsRect{VertexerTraits::getBinsRect(currentCluster, (int)Mode, 0.f, 50.f, phiCut / 2, utils)}; - if (selectedBinsRect.x != 0 || selectedBinsRect.y != 0 || selectedBinsRect.z != 0 || selectedBinsRect.w != 0) { + const int4 selectedBinsRect{o2::its::getBinsRect(currentCluster, (int)Mode + 1, 0.f, 0.f, 100.f, phiCut / 2, utils)}; + if (selectedBinsRect.x >= 0) { int phiBinsNum{selectedBinsRect.w - selectedBinsRect.y + 1}; if (phiBinsNum < 0) { phiBinsNum += PhiBins; } // loop on phi bins next layer - for (int iPhiBin{selectedBinsRect.y}, iPhiCount{0}; iPhiCount < phiBinsNum; iPhiBin = ++iPhiBin == PhiBins ? 0 : iPhiBin, iPhiCount++) { + for (int iPhiBin{selectedBinsRect.y}, iPhiCount{0}; iPhiCount < phiBinsNum && storedTracklets < maxTrackletsPerCluster; iPhiBin = ++iPhiBin == PhiBins ? 0 : iPhiBin, iPhiCount++) { const int firstBinIndex{utils.getBinIndex(selectedBinsRect.x, iPhiBin)}; const int firstRowClusterIndex{indexTableNext[firstBinIndex]}; const int maxRowClusterIndex{indexTableNext[firstBinIndex + ZBins]}; // loop on clusters next layer - for (int iNextLayerClusterIndex{firstRowClusterIndex}; iNextLayerClusterIndex < maxRowClusterIndex && iNextLayerClusterIndex < static_cast(clustersNextLayer.size()); ++iNextLayerClusterIndex) { + for (int iNextLayerClusterIndex{firstRowClusterIndex}; iNextLayerClusterIndex < maxRowClusterIndex && iNextLayerClusterIndex < static_cast(clustersNextLayer.size()) && storedTracklets < maxTrackletsPerCluster; ++iNextLayerClusterIndex) { if (usedClustersNextLayer[iNextLayerClusterIndex]) { continue; } const Cluster& nextCluster{clustersNextLayer[iNextLayerClusterIndex]}; - if (o2::gpu::GPUCommonMath::Abs(math_utils::smallestAngleDifference(currentCluster.phi, nextCluster.phi)) < phiCut) { + if (math_utils::isPhiDifferenceBelow(currentCluster.phi, nextCluster.phi, phiCut)) { if (storedTracklets < maxTrackletsPerCluster) { if constexpr (!EvalRun) { if constexpr (Mode == TrackletMode::Layer0Layer1) { - tracklets[rofFoundTrackletsOffsets[iCurrentLayerClusterIndex] + storedTracklets] = Tracklet{iNextLayerClusterIndex, iCurrentLayerClusterIndex, nextCluster, currentCluster, targetRof, pivotRof}; + tracklets[rofFoundTrackletsOffsets[iCurrentLayerClusterIndex] + storedTracklets] = Tracklet{globalOffsetNextLayer + iNextLayerClusterIndex, globalOffsetCurrentLayer + iCurrentLayerClusterIndex, nextCluster, currentCluster, timErr}; } else { - tracklets[rofFoundTrackletsOffsets[iCurrentLayerClusterIndex] + storedTracklets] = Tracklet{iCurrentLayerClusterIndex, iNextLayerClusterIndex, currentCluster, nextCluster, pivotRof, targetRof}; + tracklets[rofFoundTrackletsOffsets[iCurrentLayerClusterIndex] + storedTracklets] = Tracklet{globalOffsetCurrentLayer + iCurrentLayerClusterIndex, globalOffsetNextLayer + iNextLayerClusterIndex, currentCluster, nextCluster, timErr}; } } ++storedTracklets; @@ -93,54 +99,50 @@ static void trackleterKernelHost( } } -static void trackletSelectionKernelHost( - const gsl::span clusters0, // 0 - const gsl::span clusters1, // 1 - gsl::span usedClusters0, // Layer 0 - gsl::span usedClusters2, // Layer 2 +void trackletSelectionKernelHost( + const Cluster* clusters0, // global layer 0 clusters + const Cluster* clusters1, // global layer 1 clusters + gsl::span usedClusters0, // global layer 0 used clusters + gsl::span usedClusters2, // global layer 2 used clusters const gsl::span& tracklets01, const gsl::span& tracklets12, - bounded_vector& usedTracklets, + bounded_vector& usedTracklets, const gsl::span foundTracklets01, const gsl::span foundTracklets12, bounded_vector& lines, const gsl::span& trackletLabels, bounded_vector& linesLabels, - const short targetRofId0, - const short targetRofId2, - bool safeWrites = false, - const float tanLambdaCut = 0.025f, - const float phiCut = 0.005f, - const int maxTracklets = static_cast(1e2)) + const int nLayer1Clusters, + const float tanLambdaCut, + const float phiCut, + const int maxTracklets) { int offset01{0}, offset12{0}; - for (unsigned int iCurrentLayerClusterIndex{0}; iCurrentLayerClusterIndex < clusters1.size(); ++iCurrentLayerClusterIndex) { + for (int iCurrentLayerClusterIndex{0}; iCurrentLayerClusterIndex < nLayer1Clusters; ++iCurrentLayerClusterIndex) { int validTracklets{0}; - for (int iTracklet12{offset12}; iTracklet12 < offset12 + foundTracklets12[iCurrentLayerClusterIndex]; ++iTracklet12) { - for (int iTracklet01{offset01}; iTracklet01 < offset01 + foundTracklets01[iCurrentLayerClusterIndex]; ++iTracklet01) { + const int endTracklet01 = offset01 + foundTracklets01[iCurrentLayerClusterIndex]; + const int endTracklet12 = offset12 + foundTracklets12[iCurrentLayerClusterIndex]; + for (int iTracklet12{offset12}; iTracklet12 < endTracklet12 && validTracklets != maxTracklets; ++iTracklet12) { + const auto& tracklet12{tracklets12[iTracklet12]}; + for (int iTracklet01{offset01}; iTracklet01 < endTracklet01 && validTracklets != maxTracklets; ++iTracklet01) { if (usedTracklets[iTracklet01]) { continue; } const auto& tracklet01{tracklets01[iTracklet01]}; - const auto& tracklet12{tracklets12[iTracklet12]}; - - if (tracklet01.rof[0] != targetRofId0 || tracklet12.rof[1] != targetRofId2) { + if (!tracklet01.getTimeStamp().isCompatible(tracklet12.getTimeStamp())) { continue; } const float deltaTanLambda{o2::gpu::GPUCommonMath::Abs(tracklet01.tanLambda - tracklet12.tanLambda)}; - const float deltaPhi{o2::gpu::GPUCommonMath::Abs(math_utils::smallestAngleDifference(tracklet01.phi, tracklet12.phi))}; - if (deltaTanLambda < tanLambdaCut && deltaPhi < phiCut && validTracklets != maxTracklets) { - if (safeWrites) { - __atomic_store_n(&usedClusters0[tracklet01.firstClusterIndex], 1, __ATOMIC_RELAXED); - __atomic_store_n(&usedClusters2[tracklet12.secondClusterIndex], 1, __ATOMIC_RELAXED); - } else { - usedClusters0[tracklet01.firstClusterIndex] = 1; - usedClusters2[tracklet12.secondClusterIndex] = 1; - } + if (deltaTanLambda >= tanLambdaCut) { + continue; + } + if (math_utils::isPhiDifferenceBelow(tracklet01.phi, tracklet12.phi, phiCut) && validTracklets != maxTracklets) { + usedClusters0[tracklet01.firstClusterIndex] = 1; + usedClusters2[tracklet12.secondClusterIndex] = 1; usedTracklets[iTracklet01] = true; - lines.emplace_back(tracklet01, clusters0.data(), clusters1.data()); + lines.emplace_back(tracklet01, clusters0, clusters1); if (!trackletLabels.empty()) { linesLabels.emplace_back(trackletLabels[iTracklet01]); } @@ -152,9 +154,16 @@ static void trackletSelectionKernelHost( offset12 += foundTracklets12[iCurrentLayerClusterIndex]; } } +} // namespace -template -void VertexerTraits::updateVertexingParameters(const std::vector& vrtPar, const TimeFrameGPUParameters& tfPar) +template +void VertexerTraits::initialise(const TrackingParameters& trackingParams) +{ + mTimeFrame->initialise(trackingParams, 3); +} + +template +void VertexerTraits::updateVertexingParameters(const std::vector& vrtPar) { mVrtParams = vrtPar; mIndexTableUtils.setTrackingParameters(vrtPar[0]); @@ -165,40 +174,46 @@ void VertexerTraits::updateVertexingParameters(const std::vector -void VertexerTraits::computeTracklets(const int iteration) +template +void VertexerTraits::computeTracklets(const int iteration) { mTaskArena->execute([&] { - tbb::parallel_for(0, mTimeFrame->getNrof(), [&](const short pivotRofId) { - bool skipROF = iteration && (int)mTimeFrame->getPrimaryVertices(pivotRofId).size() > mVrtParams[iteration].vertPerRofThreshold; - short startROF{std::max((short)0, static_cast(pivotRofId - mVrtParams[iteration].deltaRof))}; - short endROF{std::min(static_cast(mTimeFrame->getNrof()), static_cast(pivotRofId + mVrtParams[iteration].deltaRof + 1))}; - for (auto targetRofId = startROF; targetRofId < endROF; ++targetRofId) { + tbb::parallel_for(0, mTimeFrame->getNrof(1), [&](const short pivotRofId) { + bool skip = skipROF(iteration, pivotRofId); + const auto& rofRange01 = mTimeFrame->getROFOverlapTableView().getOverlap(1, 0, pivotRofId); + for (auto targetRofId = rofRange01.getFirstEntry(); targetRofId < rofRange01.getEntriesBound(); ++targetRofId) { + const auto timeErr = mTimeFrame->getROFOverlapTableView().getTimeStamp(0, targetRofId, 1, pivotRofId); trackleterKernelHost( - !skipROF ? mTimeFrame->getClustersOnLayer(targetRofId, 0) : gsl::span(), // Clusters to be matched with the next layer in target rof - !skipROF ? mTimeFrame->getClustersOnLayer(pivotRofId, 1) : gsl::span(), // Clusters to be matched with the current layer in pivot rof - mTimeFrame->getUsedClustersROF(targetRofId, 0), // Span of the used clusters in the target rof - mTimeFrame->getIndexTable(targetRofId, 0).data(), // Index table to access the data on the next layer in target rof + !skip ? mTimeFrame->getClustersOnLayer(targetRofId, 0) : gsl::span(), // Clusters to be matched with the next layer in target rof + !skip ? mTimeFrame->getClustersOnLayer(pivotRofId, 1) : gsl::span(), // Clusters to be matched with the current layer in pivot rof + mTimeFrame->getUsedClustersROF(targetRofId, 0), // Span of the used clusters in the target rof + mTimeFrame->getIndexTable(targetRofId, 0).data(), // Index table to access the data on the next layer in target rof mVrtParams[iteration].phiCut, mTimeFrame->getTracklets()[0], // Flat tracklet buffer mTimeFrame->getNTrackletsCluster(pivotRofId, 0), // Span of the number of tracklets per each cluster in pivot rof mIndexTableUtils, - pivotRofId, - targetRofId, + timeErr, gsl::span(), // Offset in the tracklet buffer + 0, + 0, mVrtParams[iteration].maxTrackletsPerCluster); + } + const auto& rofRange12 = mTimeFrame->getROFOverlapTableView().getOverlap(1, 2, pivotRofId); + for (auto targetRofId = rofRange12.getFirstEntry(); targetRofId < rofRange12.getEntriesBound(); ++targetRofId) { + const auto timeErr = mTimeFrame->getROFOverlapTableView().getTimeStamp(2, targetRofId, 1, pivotRofId); trackleterKernelHost( - !skipROF ? mTimeFrame->getClustersOnLayer(targetRofId, 2) : gsl::span(), - !skipROF ? mTimeFrame->getClustersOnLayer(pivotRofId, 1) : gsl::span(), + !skip ? mTimeFrame->getClustersOnLayer(targetRofId, 2) : gsl::span(), + !skip ? mTimeFrame->getClustersOnLayer(pivotRofId, 1) : gsl::span(), mTimeFrame->getUsedClustersROF(targetRofId, 2), mTimeFrame->getIndexTable(targetRofId, 2).data(), mVrtParams[iteration].phiCut, mTimeFrame->getTracklets()[1], mTimeFrame->getNTrackletsCluster(pivotRofId, 1), // Span of the number of tracklets per each cluster in pivot rof mIndexTableUtils, - pivotRofId, - targetRofId, + timeErr, gsl::span(), // Offset in the tracklet buffer + 0, + 0, mVrtParams[iteration].maxTrackletsPerCluster); } mTimeFrame->getNTrackletsROF(pivotRofId, 0) = std::accumulate(mTimeFrame->getNTrackletsCluster(pivotRofId, 0).begin(), mTimeFrame->getNTrackletsCluster(pivotRofId, 0).end(), 0); @@ -214,629 +229,414 @@ void VertexerTraits::computeTracklets(const int iteration) mTimeFrame->getTracklets()[1].resize(tot1); } - tbb::parallel_for(0, mTimeFrame->getNrof(), [&](const short pivotRofId) { - bool skipROF = iteration && (int)mTimeFrame->getPrimaryVertices(pivotRofId).size() > mVrtParams[iteration].vertPerRofThreshold; - short startROF{std::max((short)0, static_cast(pivotRofId - mVrtParams[iteration].deltaRof))}; - short endROF{std::min(static_cast(mTimeFrame->getNrof()), static_cast(pivotRofId + mVrtParams[iteration].deltaRof + 1))}; - auto mobileOffset0 = mTimeFrame->getNTrackletsROF(pivotRofId, 0); - auto mobileOffset1 = mTimeFrame->getNTrackletsROF(pivotRofId, 1); - for (auto targetRofId = startROF; targetRofId < endROF; ++targetRofId) { + tbb::parallel_for(0, mTimeFrame->getNrof(1), [&](const short pivotRofId) { + bool skip = skipROF(iteration, pivotRofId); + const int globalOffsetPivot = mTimeFrame->getSortedStartIndex(pivotRofId, 1); + const auto& rofRange01 = mTimeFrame->getROFOverlapTableView().getOverlap(1, 0, pivotRofId); + for (auto targetRofId = rofRange01.getFirstEntry(); targetRofId < rofRange01.getEntriesBound(); ++targetRofId) { + const auto timeErr = mTimeFrame->getROFOverlapTableView().getTimeStamp(0, targetRofId, 1, pivotRofId); trackleterKernelHost( - !skipROF ? mTimeFrame->getClustersOnLayer(targetRofId, 0) : gsl::span(), - !skipROF ? mTimeFrame->getClustersOnLayer(pivotRofId, 1) : gsl::span(), + !skip ? mTimeFrame->getClustersOnLayer(targetRofId, 0) : gsl::span(), + !skip ? mTimeFrame->getClustersOnLayer(pivotRofId, 1) : gsl::span(), mTimeFrame->getUsedClustersROF(targetRofId, 0), mTimeFrame->getIndexTable(targetRofId, 0).data(), mVrtParams[iteration].phiCut, mTimeFrame->getTracklets()[0], mTimeFrame->getNTrackletsCluster(pivotRofId, 0), mIndexTableUtils, - pivotRofId, - targetRofId, + timeErr, mTimeFrame->getExclusiveNTrackletsCluster(pivotRofId, 0), + mTimeFrame->getSortedStartIndex(targetRofId, 0), + globalOffsetPivot, mVrtParams[iteration].maxTrackletsPerCluster); + } + const auto& rofRange12 = mTimeFrame->getROFOverlapTableView().getOverlap(1, 2, pivotRofId); + for (auto targetRofId = rofRange12.getFirstEntry(); targetRofId < rofRange12.getEntriesBound(); ++targetRofId) { + const auto timeErr = mTimeFrame->getROFOverlapTableView().getTimeStamp(2, targetRofId, 1, pivotRofId); trackleterKernelHost( - !skipROF ? mTimeFrame->getClustersOnLayer(targetRofId, 2) : gsl::span(), - !skipROF ? mTimeFrame->getClustersOnLayer(pivotRofId, 1) : gsl::span(), + !skip ? mTimeFrame->getClustersOnLayer(targetRofId, 2) : gsl::span(), + !skip ? mTimeFrame->getClustersOnLayer(pivotRofId, 1) : gsl::span(), mTimeFrame->getUsedClustersROF(targetRofId, 2), mTimeFrame->getIndexTable(targetRofId, 2).data(), mVrtParams[iteration].phiCut, mTimeFrame->getTracklets()[1], mTimeFrame->getNTrackletsCluster(pivotRofId, 1), mIndexTableUtils, - pivotRofId, - targetRofId, + timeErr, mTimeFrame->getExclusiveNTrackletsCluster(pivotRofId, 1), + mTimeFrame->getSortedStartIndex(targetRofId, 2), + globalOffsetPivot, mVrtParams[iteration].maxTrackletsPerCluster); } }); }); - /// Create tracklets labels for L0-L1, information is as flat as in tracklets vector (no rofId) + /// Create flat L0-L1 tracklet labels (no rofId) if (mTimeFrame->hasMCinformation()) { for (const auto& trk : mTimeFrame->getTracklets()[0]) { o2::MCCompLabel label; - if (!trk.isEmpty()) { - int sortedId0{mTimeFrame->getSortedIndex(trk.rof[0], 0, trk.firstClusterIndex)}; - int sortedId1{mTimeFrame->getSortedIndex(trk.rof[1], 1, trk.secondClusterIndex)}; - for (const auto& lab0 : mTimeFrame->getClusterLabels(0, mTimeFrame->getClusters()[0][sortedId0].clusterId)) { - for (const auto& lab1 : mTimeFrame->getClusterLabels(1, mTimeFrame->getClusters()[1][sortedId1].clusterId)) { - if (lab0 == lab1 && lab0.isValid()) { - label = lab0; - break; - } - } - if (label.isValid()) { + int sortedId0{trk.firstClusterIndex}; + int sortedId1{trk.secondClusterIndex}; + for (const auto& lab0 : mTimeFrame->getClusterLabels(0, mTimeFrame->getClusters()[0][sortedId0].clusterId)) { + for (const auto& lab1 : mTimeFrame->getClusterLabels(1, mTimeFrame->getClusters()[1][sortedId1].clusterId)) { + if (lab0 == lab1 && lab0.isValid()) { + label = lab0; break; } } + if (label.isValid()) { + break; + } } mTimeFrame->getTrackletsLabel(0).emplace_back(label); } } - -#ifdef VTX_DEBUG - debugComputeTracklets(iteration); -#endif } -template -void VertexerTraits::computeTrackletMatching(const int iteration) +template +void VertexerTraits::computeTrackletMatching(const int iteration) { mTaskArena->execute([&] { tbb::combinable totalLines{0}; tbb::parallel_for( - tbb::blocked_range(0, (short)mTimeFrame->getNrof()), + tbb::blocked_range(0, (short)mTimeFrame->getNrof(1)), [&](const tbb::blocked_range& Rofs) { for (short pivotRofId = Rofs.begin(); pivotRofId < Rofs.end(); ++pivotRofId) { - if (iteration && (int)mTimeFrame->getPrimaryVertices(pivotRofId).size() > mVrtParams[iteration].vertPerRofThreshold) { + if (mTimeFrame->getFoundTracklets(pivotRofId, 0).empty() || skipROF(iteration, pivotRofId)) { continue; } - if (mTimeFrame->getFoundTracklets(pivotRofId, 0).empty()) { - continue; - } - mTimeFrame->getLines(pivotRofId).reserve(mTimeFrame->getNTrackletsCluster(pivotRofId, 0).size()); - bounded_vector usedTracklets(mTimeFrame->getFoundTracklets(pivotRofId, 0).size(), false, mMemoryPool.get()); - short startROF{std::max((short)0, static_cast(pivotRofId - mVrtParams[iteration].deltaRof))}; - short endROF{std::min(static_cast(mTimeFrame->getNrof()), static_cast(pivotRofId + mVrtParams[iteration].deltaRof + 1))}; - - // needed only if multi-threaded using deltaRof and only at the overlap edges of the ranges - bool safeWrite = mTaskArena->max_concurrency() > 1 && mVrtParams[iteration].deltaRof != 0 && ((Rofs.begin() - startROF < 0) || (endROF - Rofs.end() > 0)); - - for (short targetRofId0 = startROF; targetRofId0 < endROF; ++targetRofId0) { - for (short targetRofId2 = startROF; targetRofId2 < endROF; ++targetRofId2) { - if (std::abs(targetRofId0 - targetRofId2) > mVrtParams[iteration].deltaRof) { // do not allow over 3 ROFs - continue; - } - trackletSelectionKernelHost( - mTimeFrame->getClustersOnLayer(targetRofId0, 0), - mTimeFrame->getClustersOnLayer(pivotRofId, 1), - mTimeFrame->getUsedClustersROF(targetRofId0, 0), - mTimeFrame->getUsedClustersROF(targetRofId2, 2), - mTimeFrame->getFoundTracklets(pivotRofId, 0), - mTimeFrame->getFoundTracklets(pivotRofId, 1), - usedTracklets, - mTimeFrame->getNTrackletsCluster(pivotRofId, 0), - mTimeFrame->getNTrackletsCluster(pivotRofId, 1), - mTimeFrame->getLines(pivotRofId), - mTimeFrame->getLabelsFoundTracklets(pivotRofId, 0), - mTimeFrame->getLinesLabel(pivotRofId), - targetRofId0, - targetRofId2, - safeWrite, - mVrtParams[iteration].tanLambdaCut, - mVrtParams[iteration].phiCut); - } - } + mTimeFrame->getLines(pivotRofId).reserve(std::min(mTimeFrame->getFoundTracklets(pivotRofId, 0).size(), mTimeFrame->getNTrackletsCluster(pivotRofId, 0).size() * constants::MaxSelectedTrackletsPerCluster)); + bounded_vector usedTracklets(mTimeFrame->getFoundTracklets(pivotRofId, 0).size(), 0, mMemoryPool.get()); + trackletSelectionKernelHost( + mTimeFrame->getClusters()[0].data(), + mTimeFrame->getClusters()[1].data(), + mTimeFrame->getUsedClusters(0), + mTimeFrame->getUsedClusters(2), + mTimeFrame->getFoundTracklets(pivotRofId, 0), + mTimeFrame->getFoundTracklets(pivotRofId, 1), + usedTracklets, + mTimeFrame->getNTrackletsCluster(pivotRofId, 0), + mTimeFrame->getNTrackletsCluster(pivotRofId, 1), + mTimeFrame->getLines(pivotRofId), + mTimeFrame->getLabelsFoundTracklets(pivotRofId, 0), + mTimeFrame->getLinesLabel(pivotRofId), + static_cast(mTimeFrame->getClustersOnLayer(pivotRofId, 1).size()), + mVrtParams[iteration].tanLambdaCut, + mVrtParams[iteration].phiCut, + constants::MaxSelectedTrackletsPerCluster); totalLines.local() += mTimeFrame->getLines(pivotRofId).size(); } }); mTimeFrame->setNLinesTotal(totalLines.combine(std::plus())); }); -#ifdef VTX_DEBUG - debugComputeTrackletMatching(iteration); -#endif - - // from here on we do not use tracklets from L1-2 anymore, so let's free them - deepVectorClear(mTimeFrame->getTracklets()[1]); + // from here on we do not use tracklets anymore, so let's free them + deepVectorClear(mTimeFrame->getTracklets()); } -template -void VertexerTraits::computeVertices(const int iteration) +template +void VertexerTraits::computeVertices(const int iteration) { - auto nsigmaCut{std::min(mVrtParams[iteration].vertNsigmaCut * mVrtParams[iteration].vertNsigmaCut * (mVrtParams[iteration].vertRadiusSigma * mVrtParams[iteration].vertRadiusSigma + mVrtParams[iteration].trackletSigma * mVrtParams[iteration].trackletSigma), 1.98f)}; - bounded_vector vertices(mMemoryPool.get()); - bounded_vector> polls(mMemoryPool.get()); - bounded_vector contLabels(mMemoryPool.get()); - bounded_vector noClustersVec(mTimeFrame->getNrof(), 0, mMemoryPool.get()); - for (int rofId{0}; rofId < mTimeFrame->getNrof(); ++rofId) { - if (iteration && (int)mTimeFrame->getPrimaryVertices(rofId).size() > mVrtParams[iteration].vertPerRofThreshold) { - continue; + const int nRofs = mTimeFrame->getNrof(1); + std::vector> rofVertices(nRofs); + std::vector> rofLabels(nRofs); + const float pairCut2 = mVrtParams[iteration].pairCut * mVrtParams[iteration].pairCut; + const float duplicateZCut = mVrtParams[iteration].duplicateZCut > 0.f ? mVrtParams[iteration].duplicateZCut : std::max(4.f * mVrtParams[iteration].pairCut, 0.5f * mVrtParams[iteration].clusterCut); + const float duplicateDistance2Cut = mVrtParams[iteration].duplicateDistance2Cut > 0.f ? mVrtParams[iteration].duplicateDistance2Cut : std::max(16.f * pairCut2, 0.0625f * mVrtParams[iteration].clusterCut * mVrtParams[iteration].clusterCut); + line_vertexer::Settings settings; + settings.beamX = mTimeFrame->getBeamX(); + settings.beamY = mTimeFrame->getBeamY(); + settings.pairCut = mVrtParams[iteration].pairCut; + settings.pairCut2 = pairCut2; + settings.clusterCut = mVrtParams[iteration].clusterCut; + settings.coarseZWindow = mVrtParams[iteration].coarseZWindow; + settings.seedDedupZCut = mVrtParams[iteration].seedDedupZCut; + settings.refitDedupZCut = mVrtParams[iteration].refitDedupZCut; + settings.duplicateZCut = duplicateZCut; + settings.duplicateDistance2Cut = duplicateDistance2Cut; + settings.finalSelectionZCut = mVrtParams[iteration].finalSelectionZCut; + settings.maxZ = mVrtParams[iteration].maxZPositionAllowed; + settings.seedMemberRadiusTime = mVrtParams[iteration].seedMemberRadiusTime; + settings.seedMemberRadiusZ = mVrtParams[iteration].seedMemberRadiusZ; + settings.memoryPool = mMemoryPool; + + const auto processROF = [&](const int rofId) { + if (skipROF(iteration, rofId)) { + return; } - const int numTracklets{static_cast(mTimeFrame->getLines(rofId).size())}; - - bounded_vector usedTracklets(numTracklets, false, mMemoryPool.get()); - for (int line1{0}; line1 < numTracklets; ++line1) { - if (usedTracklets[line1]) { - continue; + auto& lines = mTimeFrame->getLines(rofId); + auto clusters = line_vertexer::buildClusters(std::span{lines.data(), lines.size()}, settings); + deepVectorClear(lines); // not needed after + auto clusterBeamDistance2 = [&](const ClusterLines& cluster) { + return (mTimeFrame->getBeamX() - cluster.getVertex()[0]) * (mTimeFrame->getBeamX() - cluster.getVertex()[0]) + + (mTimeFrame->getBeamY() - cluster.getVertex()[1]) * (mTimeFrame->getBeamY() - cluster.getVertex()[1]); + }; + auto clusterBetter = [&](const ClusterLines& lhs, const ClusterLines& rhs) { + if (lhs.getSize() != rhs.getSize()) { + return lhs.getSize() > rhs.getSize(); + } + if (o2::gpu::GPUCommonMath::Abs(lhs.getAvgDistance2() - rhs.getAvgDistance2()) > constants::Tolerance) { + return lhs.getAvgDistance2() < rhs.getAvgDistance2(); } - for (int line2{line1 + 1}; line2 < numTracklets; ++line2) { - if (usedTracklets[line2]) { + const auto lhsBeam = clusterBeamDistance2(lhs); + const auto rhsBeam = clusterBeamDistance2(rhs); + if (o2::gpu::GPUCommonMath::Abs(lhsBeam - rhsBeam) > constants::Tolerance) { + return lhsBeam < rhsBeam; + } + return lhs.getVertex()[2] < rhs.getVertex()[2]; + }; + + // Cluster deduplication by local non-maximum suppression in time/space + std::sort(clusters.begin(), clusters.end(), clusterBetter); + float minClusterZ = std::numeric_limits::max(); + for (const auto& cluster : clusters) { + minClusterZ = std::min(minClusterZ, cluster.getVertex()[2]); + } + bounded_vector deduplicated(mMemoryPool.get()); + deduplicated.reserve(clusters.size()); + std::unordered_map> keptByZBin; + for (auto& candidate : clusters) { + bool duplicate = false; + const auto candidateZ = candidate.getVertex()[2]; + const auto zBin = static_cast(std::floor((candidateZ - minClusterZ) / settings.duplicateZCut)); + for (int neighborBin = zBin - 1; neighborBin <= zBin + 1 && !duplicate; ++neighborBin) { + const auto found = keptByZBin.find(neighborBin); + if (found == keptByZBin.end()) { continue; } - auto dca{Line::getDCA(mTimeFrame->getLines(rofId)[line1], mTimeFrame->getLines(rofId)[line2])}; - if (dca < mVrtParams[iteration].pairCut) { - mTimeFrame->getTrackletClusters(rofId).emplace_back(line1, mTimeFrame->getLines(rofId)[line1], line2, mTimeFrame->getLines(rofId)[line2]); - std::array tmpVertex{mTimeFrame->getTrackletClusters(rofId).back().getVertex()}; - if (tmpVertex[0] * tmpVertex[0] + tmpVertex[1] * tmpVertex[1] > 4.f) { - mTimeFrame->getTrackletClusters(rofId).pop_back(); - break; + for (const auto ownerId : found->second) { + const auto& owner = deduplicated[ownerId]; + if (!candidate.getTimeStamp().isCompatible(owner.getTimeStamp())) { + continue; } - usedTracklets[line1] = true; - usedTracklets[line2] = true; - for (int tracklet3{0}; tracklet3 < numTracklets; ++tracklet3) { - if (usedTracklets[tracklet3]) { - continue; - } - if (Line::getDistanceFromPoint(mTimeFrame->getLines(rofId)[tracklet3], tmpVertex) < mVrtParams[iteration].pairCut) { - mTimeFrame->getTrackletClusters(rofId).back().add(tracklet3, mTimeFrame->getLines(rofId)[tracklet3]); - usedTracklets[tracklet3] = true; - tmpVertex = mTimeFrame->getTrackletClusters(rofId).back().getVertex(); - } + if (o2::gpu::GPUCommonMath::Abs(candidate.getVertex()[2] - owner.getVertex()[2]) >= settings.duplicateZCut) { + continue; + } + const auto dx = candidate.getVertex()[0] - owner.getVertex()[0]; + const auto dy = candidate.getVertex()[1] - owner.getVertex()[1]; + const auto dz = candidate.getVertex()[2] - owner.getVertex()[2]; + const auto distance2 = math_utils::SqSum(dx, dy, dz); + if (distance2 < settings.duplicateDistance2Cut) { + duplicate = true; + break; } - break; } } + if (duplicate) { + continue; + } + + const auto ownerId = static_cast(deduplicated.size()); + keptByZBin[zBin].push_back(ownerId); + deduplicated.push_back(std::move(candidate)); } - if (mVrtParams[iteration].allowSingleContribClusters) { - auto beamLine = Line{{mTimeFrame->getBeamX(), mTimeFrame->getBeamY(), -50.f}, {mTimeFrame->getBeamX(), mTimeFrame->getBeamY(), 50.f}}; // use beam position as contributor - for (size_t iLine{0}; iLine < numTracklets; ++iLine) { - if (!usedTracklets[iLine]) { - auto dca = Line::getDCA(mTimeFrame->getLines(rofId)[iLine], beamLine); - if (dca < mVrtParams[iteration].pairCut) { - mTimeFrame->getTrackletClusters(rofId).emplace_back(iLine, mTimeFrame->getLines(rofId)[iLine], -1, beamLine); // beamline must be passed as second line argument - } - } + clusters = std::move(deduplicated); + int nClusters = static_cast(clusters.size()); + + // Vertex filtering with score-based local NMS + std::sort(clusters.begin(), clusters.end(), clusterBetter); + std::vector candidateIndices; + candidateIndices.reserve(nClusters); + for (int iCluster{0}; iCluster < nClusters; ++iCluster) { + const bool zCompatible = o2::gpu::GPUCommonMath::Abs(clusters[iCluster].getVertex()[2]) < mVrtParams[iteration].maxZPositionAllowed; + + if (zCompatible) { + candidateIndices.push_back(iCluster); } } - // Cluster merging - std::sort(mTimeFrame->getTrackletClusters(rofId).begin(), mTimeFrame->getTrackletClusters(rofId).end(), - [](ClusterLines& cluster1, ClusterLines& cluster2) { return cluster1.getSize() > cluster2.getSize(); }); - noClustersVec[rofId] = static_cast(mTimeFrame->getTrackletClusters(rofId).size()); - for (int iCluster1{0}; iCluster1 < noClustersVec[rofId]; ++iCluster1) { - std::array vertex1{mTimeFrame->getTrackletClusters(rofId)[iCluster1].getVertex()}; - std::array vertex2{}; - for (int iCluster2{iCluster1 + 1}; iCluster2 < noClustersVec[rofId]; ++iCluster2) { - vertex2 = mTimeFrame->getTrackletClusters(rofId)[iCluster2].getVertex(); - if (o2::gpu::GPUCommonMath::Abs(vertex1[2] - vertex2[2]) < mVrtParams[iteration].clusterCut) { - float distance{(vertex1[0] - vertex2[0]) * (vertex1[0] - vertex2[0]) + - (vertex1[1] - vertex2[1]) * (vertex1[1] - vertex2[1]) + - (vertex1[2] - vertex2[2]) * (vertex1[2] - vertex2[2])}; - if (distance < mVrtParams[iteration].pairCut * mVrtParams[iteration].pairCut) { - for (auto label : mTimeFrame->getTrackletClusters(rofId)[iCluster2].getLabels()) { - mTimeFrame->getTrackletClusters(rofId)[iCluster1].add(label, mTimeFrame->getLines(rofId)[label]); - vertex1 = mTimeFrame->getTrackletClusters(rofId)[iCluster1].getVertex(); - } - mTimeFrame->getTrackletClusters(rofId).erase(mTimeFrame->getTrackletClusters(rofId).begin() + iCluster2); - --iCluster2; - --noClustersVec[rofId]; - } - } - } + if (candidateIndices.empty()) { + return; } - } - for (int rofId{0}; rofId < mTimeFrame->getNrof(); ++rofId) { - std::sort(mTimeFrame->getTrackletClusters(rofId).begin(), mTimeFrame->getTrackletClusters(rofId).end(), - [](const ClusterLines& cluster1, const ClusterLines& cluster2) { return cluster1.getSize() > cluster2.getSize(); }); // ensure clusters are ordered by contributors, so that we can cat after the first. - bool atLeastOneFound{false}; - for (int iCluster{0}; iCluster < noClustersVec[rofId]; ++iCluster) { - bool lowMultCandidate{false}; - double beamDistance2{(mTimeFrame->getBeamX() - mTimeFrame->getTrackletClusters(rofId)[iCluster].getVertex()[0]) * (mTimeFrame->getBeamX() - mTimeFrame->getTrackletClusters(rofId)[iCluster].getVertex()[0]) + - (mTimeFrame->getBeamY() - mTimeFrame->getTrackletClusters(rofId)[iCluster].getVertex()[1]) * (mTimeFrame->getBeamY() - mTimeFrame->getTrackletClusters(rofId)[iCluster].getVertex()[1])}; - if (atLeastOneFound && (lowMultCandidate = mTimeFrame->getTrackletClusters(rofId)[iCluster].getSize() < mVrtParams[iteration].clusterContributorsCut)) { // We might have pile up with nContr > cut. - lowMultCandidate &= (beamDistance2 < mVrtParams[iteration].lowMultBeamDistCut * mVrtParams[iteration].lowMultBeamDistCut); - if (!lowMultCandidate) { // Not the first cluster and not a low multiplicity candidate, we can remove it - mTimeFrame->getTrackletClusters(rofId).erase(mTimeFrame->getTrackletClusters(rofId).begin() + iCluster); - noClustersVec[rofId]--; - continue; + + auto countSharedLabels = [](const ClusterLines& lhs, const ClusterLines& rhs) { + size_t shared = 0; + auto lhsIt = lhs.getLabels().begin(); + auto rhsIt = rhs.getLabels().begin(); + while (lhsIt != lhs.getLabels().end() && rhsIt != rhs.getLabels().end()) { + if (*lhsIt == *rhsIt) { + ++shared; + ++lhsIt; + ++rhsIt; + } else if (*lhsIt < *rhsIt) { + ++lhsIt; + } else { + ++rhsIt; } } + return shared; + }; - if (beamDistance2 < nsigmaCut && o2::gpu::GPUCommonMath::Abs(mTimeFrame->getTrackletClusters(rofId)[iCluster].getVertex()[2]) < mVrtParams[iteration].maxZPositionAllowed) { - atLeastOneFound = true; - auto& vertex = vertices.emplace_back(o2::math_utils::Point3D(mTimeFrame->getTrackletClusters(rofId)[iCluster].getVertex()[0], - mTimeFrame->getTrackletClusters(rofId)[iCluster].getVertex()[1], - mTimeFrame->getTrackletClusters(rofId)[iCluster].getVertex()[2]), - mTimeFrame->getTrackletClusters(rofId)[iCluster].getRMS2(), // Symm matrix. Diagonal: RMS2 components, - // off-diagonal: square mean of projections on planes. - mTimeFrame->getTrackletClusters(rofId)[iCluster].getSize(), // Contributors - mTimeFrame->getTrackletClusters(rofId)[iCluster].getAvgDistance2()); // In place of chi2 - - if (iteration) { - vertex.setFlags(Vertex::UPCMode); + float minCandidateZ = std::numeric_limits::max(); + for (const auto clusterId : candidateIndices) { + minCandidateZ = std::min(minCandidateZ, clusters[clusterId].getVertex()[2]); + } + std::unordered_map> selectedByZBin; + std::vector selectedIndices; + selectedIndices.reserve(candidateIndices.size()); + for (const auto clusterId : candidateIndices) { + const auto& candidate = clusters[clusterId]; + const auto candidateZ = candidate.getVertex()[2]; + const auto zBin = static_cast((candidateZ - minCandidateZ) / settings.finalSelectionZCut); + bool suppressed = false; + for (int neighborBin = zBin - 1; neighborBin <= zBin + 1 && !suppressed; ++neighborBin) { + const auto found = selectedByZBin.find(neighborBin); + if (found == selectedByZBin.end()) { + continue; } - vertex.setTimeStamp(mTimeFrame->getTrackletClusters(rofId)[iCluster].getROF()); - if (mTimeFrame->hasMCinformation()) { - bounded_vector labels(mMemoryPool.get()); - for (auto& index : mTimeFrame->getTrackletClusters(rofId)[iCluster].getLabels()) { - labels.push_back(mTimeFrame->getLinesLabel(rofId)[index]); // then we can use nContributors from vertices to get the labels + for (const auto selectedId : found->second) { + const auto& selected = clusters[selectedId]; + if (!candidate.getTimeStamp().isCompatible(selected.getTimeStamp())) { + continue; } - polls.push_back(computeMain(labels)); - if (mVrtParams[iteration].outputContLabels) { - contLabels.insert(contLabels.end(), labels.begin(), labels.end()); + const auto zDelta = o2::gpu::GPUCommonMath::Abs(candidateZ - selected.getVertex()[2]); + const auto sharedLabels = countSharedLabels(candidate, selected); + const auto minSize = std::min(candidate.getSize(), selected.getSize()); + const bool overlapDuplicate = sharedLabels > 0 && sharedLabels * 4 >= minSize; + const bool strongZDuplicate = zDelta < settings.finalSelectionZCut; + const bool clearlyBetterMultiplicity = selected.getSize() >= candidate.getSize() + 3; + const bool clearlyBetterQuality = selected.getSize() > candidate.getSize() && + selected.getAvgDistance2() + constants::Tolerance < 0.8f * candidate.getAvgDistance2(); + const bool weakCandidate = clearlyBetterMultiplicity || clearlyBetterQuality; + if (overlapDuplicate || (strongZDuplicate && weakCandidate)) { + suppressed = true; + break; } } } + if (suppressed) { + continue; + } + selectedByZBin[zBin].push_back(clusterId); + selectedIndices.push_back(clusterId); } - if (!iteration) { - mTimeFrame->addPrimaryVertices(vertices, iteration); - if (mTimeFrame->hasMCinformation()) { - mTimeFrame->addPrimaryVerticesLabels(polls); - if (mVrtParams[iteration].outputContLabels) { - mTimeFrame->addPrimaryVerticesContributorLabels(contLabels); - } + + // sort vertices by their multiplicity to opt. suppress lower mult. debris + std::vector sortedIndices(selectedIndices.size()); + std::iota(sortedIndices.begin(), sortedIndices.end(), 0); + std::sort(sortedIndices.begin(), sortedIndices.end(), [&selectedIndices, &clusters](int i, int j) { + return clusters[selectedIndices[i]].getSize() > clusters[selectedIndices[j]].getSize(); + }); + for (const auto sortedId : sortedIndices) { + const auto& cluster = clusters[selectedIndices[sortedId]]; + const auto beamDistance2 = clusterBeamDistance2(cluster); + if (!(beamDistance2 < mVrtParams[iteration].NSigmaCut)) { + continue; } - } else { - mTimeFrame->addPrimaryVerticesInROF(vertices, rofId, iteration); + if (cluster.getSize() < mVrtParams[iteration].clusterContributorsCut) { + continue; + } + if (!rofVertices[rofId].empty() && cluster.getSize() < mVrtParams[iteration].suppressLowMultDebris) { + continue; + } + + Vertex vertex{cluster.getVertex().data(), + cluster.getRMS2(), + (ushort)cluster.getSize(), + cluster.getAvgDistance2()}; + if (mVrtParams[iteration].PassFlags[IterationStep::MarkVerticesAsUPC]) { + vertex.setFlags(Vertex::UPCMode); + } + vertex.setTimeStamp(cluster.getTimeStamp()); + rofVertices[rofId].push_back(vertex); if (mTimeFrame->hasMCinformation()) { - mTimeFrame->addPrimaryVerticesLabelsInROF(polls, rofId); - if (mVrtParams[iteration].outputContLabels) { - mTimeFrame->addPrimaryVerticesContributorLabelsInROF(contLabels, rofId); + auto& lineLabels = mTimeFrame->getLinesLabel(rofId); + bounded_vector labels(mMemoryPool.get()); + for (auto& index : cluster.getLabels()) { + labels.push_back(lineLabels[index]); } + const auto mainLabel = computeMain(labels); + rofLabels[rofId].push_back(mainLabel); } } - if (vertices.empty() && !(iteration && (int)mTimeFrame->getPrimaryVertices(rofId).size() > mVrtParams[iteration].vertPerRofThreshold)) { - mTimeFrame->getNoVertexROF()++; + }; + + if (mTaskArena->max_concurrency() <= 1) { + for (int rofId{0}; rofId < nRofs; ++rofId) { + processROF(rofId); + } + } else { + mTaskArena->execute([&] { + tbb::parallel_for(0, nRofs, [&](const int rofId) { + processROF(rofId); + }); + }); + } + // add vertices, these anyways get sorted afterward + for (int rofId{0}; rofId < nRofs; ++rofId) { + for (auto& vertex : rofVertices[rofId]) { + mTimeFrame->addPrimaryVertex(vertex); + } + if (mTimeFrame->hasMCinformation()) { + for (auto& label : rofLabels[rofId]) { + mTimeFrame->addPrimaryVertexLabel(label); + } } - vertices.clear(); - polls.clear(); } - -#ifdef VTX_DEBUG - debugComputeVertices(iteration); -#endif } -template -void VertexerTraits::addTruthSeedingVertices() +template +void VertexerTraits::addTruthSeedingVertices() { LOGP(info, "Using truth seeds as vertices; will skip computations"); - mTimeFrame->resetRofPV(); const auto dc = o2::steer::DigitizationContext::loadFromFile("collisioncontext.root"); const auto irs = dc->getEventRecords(); - int64_t roFrameBiasInBC = o2::itsmft::DPLAlpideParam::Instance().roFrameBiasInBC; - int64_t roFrameLengthInBC = o2::itsmft::DPLAlpideParam::Instance().roFrameLengthInBC; + int64_t roFrameBiasInBC = o2::itsmft::DPLAlpideParam::Instance().getROFBiasInBC(1); + int64_t roFrameLengthInBC = o2::itsmft::DPLAlpideParam::Instance().getROFLengthInBC(1); o2::steer::MCKinematicsReader mcReader(dc); - struct VertInfo { - bounded_vector vertices; - bounded_vector srcs; - bounded_vector events; - }; - std::map vertices; const int iSrc = 0; // take only events from collision generator auto eveId2colId = dc->getCollisionIndicesForSource(iSrc); for (int iEve{0}; iEve < mcReader.getNEvents(iSrc); ++iEve) { const auto& ir = irs[eveId2colId[iEve]]; if (!ir.isDummy()) { // do we need this, is this for diffractive events? const auto& eve = mcReader.getMCEventHeader(iSrc, iEve); - int rofId = ((ir - raw::HBFUtils::Instance().getFirstSampledTFIR()).toLong() - roFrameBiasInBC) / roFrameLengthInBC; - if (!vertices.contains(rofId)) { - vertices[rofId] = { - .vertices = bounded_vector(mMemoryPool.get()), - .srcs = bounded_vector(mMemoryPool.get()), - .events = bounded_vector(mMemoryPool.get()), - }; + auto bc = (ir - raw::HBFUtils::Instance().getFirstSampledTFIR()).toLong() - roFrameBiasInBC; + if (bc < 0) { // event happened before TF + continue; } Vertex vert; - vert.setTimeStamp(rofId); + vert.getTimeStamp().setTimeStamp(bc); + vert.getTimeStamp().setTimeStampError(roFrameLengthInBC / 2); // set minimum to 1 sometimes for diffractive events there is nothing acceptance vert.setNContributors(std::max(1L, std::ranges::count_if(mcReader.getTracks(iSrc, iEve), [](const auto& trk) { - return trk.isPrimary() && trk.GetPt() > 0.05 && std::abs(trk.GetEta()) < 1.1; + if (!trk.isPrimary() || trk.GetPt() < 0.05 || std::abs(trk.GetEta()) > 1.1) { + return false; + } + const auto* p = o2::O2DatabasePDG::Instance()->GetParticle(trk.GetPdgCode()); + return (!p) ? false : p->Charge() != 0; }))); vert.setXYZ((float)eve.GetX(), (float)eve.GetY(), (float)eve.GetZ()); vert.setChi2(1); // not used as constraint - constexpr float cov = 50e-9; - vert.setCov(cov, cov, cov, cov, cov, cov); - vertices[rofId].vertices.push_back(vert); - vertices[rofId].srcs.push_back(iSrc); - vertices[rofId].events.push_back(iEve); + constexpr float cov = 25e-4; + vert.setSigmaX(cov); + vert.setSigmaY(cov); + vert.setSigmaZ(cov); + mTimeFrame->addPrimaryVertex(vert); + o2::MCCompLabel mcLbl(o2::MCCompLabel::maxTrackID(), iEve, iSrc, false); + VertexLabel lbl(mcLbl, 1.0); + mTimeFrame->addPrimaryVertexLabel(lbl); } mcReader.releaseTracksForSourceAndEvent(iSrc, iEve); } - size_t nVerts{0}; - for (int iROF{0}; iROF < mTimeFrame->getNrof(); ++iROF) { - bounded_vector verts(mMemoryPool.get()); - bounded_vector> polls(mMemoryPool.get()); - if (vertices.contains(iROF)) { - const auto& vertInfo = vertices[iROF]; - verts = vertInfo.vertices; - nVerts += verts.size(); - for (size_t i{0}; i < verts.size(); ++i) { - o2::MCCompLabel lbl(o2::MCCompLabel::maxTrackID(), vertInfo.events[i], vertInfo.srcs[i], false); - polls.emplace_back(lbl, 1.f); - } - } else { - mTimeFrame->getNoVertexROF()++; - } - mTimeFrame->addPrimaryVertices(verts, 0); - mTimeFrame->addPrimaryVerticesLabels(polls); - } - LOGP(info, "Found {}/{} ROFs with {} vertices -> ={:.2f}", vertices.size(), mTimeFrame->getNrof(), nVerts, (float)nVerts / (float)vertices.size()); + LOGP(info, "Imposed {} pv collisions from mc-truth", mTimeFrame->getPrimaryVertices().size()); } -template -void VertexerTraits::setNThreads(int n, std::shared_ptr& arena) +template +void VertexerTraits::setNThreads(int n, std::shared_ptr& arena) { -#if defined(VTX_DEBUG) - LOGP(info, "Vertexer with debug output forcing single thread"); - mTaskArena = std::make_shared(1); -#else if (arena == nullptr) { mTaskArena = std::make_shared(std::abs(n)); LOGP(info, "Setting seeding vertexer with {} threads.", n); } else { mTaskArena = arena; - LOGP(info, "Attaching vertexer to calling thread's arena"); - } -#endif -} - -template -void VertexerTraits::debugComputeTracklets(int iteration) -{ - auto stream = new utils::TreeStreamRedirector("artefacts_tf.root", "recreate"); - LOGP(info, "writing debug output for computeTracklets"); - for (int rofId{0}; rofId < mTimeFrame->getNrof(); ++rofId) { - const auto& strk0 = mTimeFrame->getFoundTracklets(rofId, 0); - std::vector trk0(strk0.begin(), strk0.end()); - const auto& strk1 = mTimeFrame->getFoundTracklets(rofId, 1); - std::vector trk1(strk1.begin(), strk1.end()); - (*stream) << "tracklets" - << "Tracklets0=" << trk0 - << "Tracklets1=" << trk1 - << "iteration=" << iteration - << "\n"; - } - stream->Close(); - delete stream; -} - -template -void VertexerTraits::debugComputeTrackletMatching(int iteration) -{ - auto stream = new utils::TreeStreamRedirector("artefacts_tf.root", "update"); - LOGP(info, "writing debug output for computeTrackletMatching"); - for (int rofId{0}; rofId < mTimeFrame->getNrof(); ++rofId) { - (*stream) << "lines" - << "Lines=" << toSTDVector(mTimeFrame->getLines(rofId)) - << "NTrackletCluster01=" << mTimeFrame->getNTrackletsCluster(rofId, 0) - << "NTrackletCluster12=" << mTimeFrame->getNTrackletsCluster(rofId, 1) - << "iteration=" << iteration - << "\n"; - } - - if (mTimeFrame->hasMCinformation()) { - LOGP(info, "\tdumping also MC information"); - const auto dc = o2::steer::DigitizationContext::loadFromFile("collisioncontext.root"); - const auto irs = dc->getEventRecords(); - int64_t roFrameBiasInBC = o2::itsmft::DPLAlpideParam::Instance().roFrameBiasInBC; - int64_t roFrameLengthInBC = o2::itsmft::DPLAlpideParam::Instance().roFrameLengthInBC; - o2::steer::MCKinematicsReader mcReader(dc); - - std::map eve2BcInROF, bcInRofNEve; - for (int iSrc{0}; iSrc < mcReader.getNSources(); ++iSrc) { - auto eveId2colId = dc->getCollisionIndicesForSource(iSrc); - for (int iEve{0}; iEve < mcReader.getNEvents(iSrc); ++iEve) { - const auto& ir = irs[eveId2colId[iEve]]; - if (!ir.isDummy()) { // do we need this, is this for diffractive events? - const auto& eve = mcReader.getMCEventHeader(iSrc, iEve); - const int bcInROF = ((ir - raw::HBFUtils::Instance().getFirstSampledTFIR()).toLong() - roFrameBiasInBC) % roFrameLengthInBC; - eve2BcInROF[iEve] = bcInROF; - ++bcInRofNEve[bcInROF]; - } - } - } - - std::unordered_map bcROFNTracklets01, bcROFNTracklets12; - std::vector> tracklet01BC, tracklet12BC; - for (int rofId{0}; rofId < mTimeFrame->getNrof(); ++rofId) { - { // 0-1 - const auto& tracklet01 = mTimeFrame->getFoundTracklets(rofId, 0); - const auto& lbls01 = mTimeFrame->getLabelsFoundTracklets(rofId, 0); - auto& trkls01 = tracklet01BC.emplace_back(); - for (int iTrklt{0}; iTrklt < (int)tracklet01.size(); ++iTrklt) { - const auto& tracklet = tracklet01[iTrklt]; - const auto& lbl = lbls01[iTrklt]; - if (lbl.isCorrect()) { - ++bcROFNTracklets01[eve2BcInROF[lbl.getEventID()]]; - trkls01.push_back(eve2BcInROF[lbl.getEventID()]); - } else { - trkls01.push_back(-1); - } - } - } - { // 1-2 computed on the fly! - const auto& tracklet12 = mTimeFrame->getFoundTracklets(rofId, 1); - auto& trkls12 = tracklet12BC.emplace_back(); - for (int iTrklt{0}; iTrklt < (int)tracklet12.size(); ++iTrklt) { - const auto& tracklet = tracklet12[iTrklt]; - o2::MCCompLabel label; - - int sortedId1{mTimeFrame->getSortedIndex(tracklet.rof[0], 1, tracklet.firstClusterIndex)}; - int sortedId2{mTimeFrame->getSortedIndex(tracklet.rof[1], 2, tracklet.secondClusterIndex)}; - for (const auto& lab1 : mTimeFrame->getClusterLabels(1, mTimeFrame->getClusters()[1][sortedId1].clusterId)) { - for (const auto& lab2 : mTimeFrame->getClusterLabels(2, mTimeFrame->getClusters()[2][sortedId2].clusterId)) { - if (lab1 == lab2 && lab1.isValid()) { - label = lab1; - break; - } - } - if (label.isValid()) { - break; - } - } - - if (label.isCorrect()) { - ++bcROFNTracklets12[eve2BcInROF[label.getEventID()]]; - trkls12.push_back(eve2BcInROF[label.getEventID()]); - } else { - trkls12.push_back(-1); - } - } - } - } - LOGP(info, "\tdumping ntracklets/RofBC ({})", bcInRofNEve.size()); - for (const auto& [bcInRof, neve] : bcInRofNEve) { - (*stream) << "ntracklets" - << "bcInROF=" << bcInRof - << "ntrkl01=" << bcROFNTracklets01[bcInRof] - << "ntrkl12=" << bcROFNTracklets12[bcInRof] - << "neve=" << neve - << "iteration=" << iteration - << "\n"; - } - - std::unordered_map bcROFNLines; - for (int rofId{0}; rofId < mTimeFrame->getNrof(); ++rofId) { - const auto& lines = mTimeFrame->getLines(rofId); - const auto& lbls = mTimeFrame->getLinesLabel(rofId); - for (int iLine{0}; iLine < (int)lines.size(); ++iLine) { - const auto& line = lines[iLine]; - const auto& lbl = lbls[iLine]; - if (lbl.isCorrect()) { - ++bcROFNLines[eve2BcInROF[lbl.getEventID()]]; - } - } - } - - LOGP(info, "\tdumping nlines/RofBC"); - for (const auto& [bcInRof, neve] : bcInRofNEve) { - (*stream) << "nlines" - << "bcInROF=" << bcInRof - << "nline=" << bcROFNLines[bcInRof] - << "neve=" << neve - << "iteration=" << iteration - << "\n"; - } } - stream->Close(); - delete stream; } -template -void VertexerTraits::debugComputeVertices(int iteration) +template +bool VertexerTraits::skipROF(int iteration, int rof) const { - auto stream = new utils::TreeStreamRedirector("artefacts_tf.root", "update"); - LOGP(info, "writing debug output for computeVertices"); - for (auto rofId{0}; rofId < mTimeFrame->getNrof(); ++rofId) { - (*stream) << "clusterlines" - << "clines_post=" << toSTDVector(mTimeFrame->getTrackletClusters(rofId)) - << "iteration=" << iteration - << "\n"; - } - - if (mTimeFrame->hasMCinformation()) { - LOGP(info, "\tdumping also MC information"); - const auto dc = o2::steer::DigitizationContext::loadFromFile("collisioncontext.root"); - const auto irs = dc->getEventRecords(); - int64_t roFrameBiasInBC = o2::itsmft::DPLAlpideParam::Instance().roFrameBiasInBC; - int64_t roFrameLengthInBC = o2::itsmft::DPLAlpideParam::Instance().roFrameLengthInBC; - o2::steer::MCKinematicsReader mcReader(dc); - - std::map eve2BcInROF, bcInRofNEve; - for (int iSrc{0}; iSrc < mcReader.getNSources(); ++iSrc) { - auto eveId2colId = dc->getCollisionIndicesForSource(iSrc); - for (int iEve{0}; iEve < mcReader.getNEvents(iSrc); ++iEve) { - const auto& ir = irs[eveId2colId[iEve]]; - if (!ir.isDummy()) { // do we need this, is this for diffractive events? - const auto& eve = mcReader.getMCEventHeader(iSrc, iEve); - const int bcInROF = ((ir - raw::HBFUtils::Instance().getFirstSampledTFIR()).toLong() - roFrameBiasInBC) % roFrameLengthInBC; - eve2BcInROF[iEve] = bcInROF; - ++bcInRofNEve[bcInROF]; - } - } - } - - std::unordered_map bcROFNVtx; - std::unordered_map bcROFNPur; - std::unordered_map uniqueVertices; - for (int rofId{0}; rofId < mTimeFrame->getNrof(); ++rofId) { - const auto& pvs = mTimeFrame->getPrimaryVertices(rofId); - const auto& lblspv = mTimeFrame->getPrimaryVerticesMCRecInfo(rofId); - for (int i{0}; i < (int)pvs.size(); ++i) { - const auto& pv = pvs[i]; - const auto& [lbl, pur] = lblspv[i]; - if (lbl.isCorrect()) { - ++uniqueVertices[lbl]; - ++bcROFNVtx[eve2BcInROF[lbl.getEventID()]]; - bcROFNPur[eve2BcInROF[lbl.getEventID()]] += pur; - } - } - } - - std::unordered_map bcROFNUVtx, bcROFNCVtx; - for (const auto& [k, _] : eve2BcInROF) { - bcROFNUVtx[k] = bcROFNCVtx[k] = 0; - } - - for (const auto& [lbl, c] : uniqueVertices) { - if (c <= 1) { - ++bcROFNUVtx[eve2BcInROF[lbl.getEventID()]]; - } else { - ++bcROFNCVtx[eve2BcInROF[lbl.getEventID()]]; - } - } - - LOGP(info, "\tdumping nvtx/RofBC"); - for (const auto& [bcInRof, neve] : bcInRofNEve) { - (*stream) << "nvtx" - << "bcInROF=" << bcInRof - << "nvtx=" << bcROFNVtx[bcInRof] // all vertices - << "nuvtx=" << bcROFNUVtx[bcInRof] // unique vertices - << "ncvtx=" << bcROFNCVtx[bcInRof] // cloned vertices - << "npur=" << bcROFNPur[bcInRof] - << "neve=" << neve - << "iteration=" << iteration - << "\n"; - } - - // check dist of clones - std::unordered_map> cVtx; - for (int rofId{0}; rofId < mTimeFrame->getNrof(); ++rofId) { - const auto& pvs = mTimeFrame->getPrimaryVertices(rofId); - const auto& lblspv = mTimeFrame->getPrimaryVerticesMCRecInfo(rofId); - for (int i{0}; i < (int)pvs.size(); ++i) { - const auto& pv = pvs[i]; - const auto& [lbl, pur] = lblspv[i]; - if (lbl.isCorrect() && uniqueVertices.contains(lbl) && uniqueVertices[lbl] > 1) { - if (!cVtx.contains(lbl)) { - cVtx[lbl] = std::vector(); - } - cVtx[lbl].push_back(pv); - } - } - } - - for (auto& [_, vertices] : cVtx) { - std::sort(vertices.begin(), vertices.end(), [](const Vertex& a, const Vertex& b) { return a.getNContributors() > b.getNContributors(); }); - for (int i{0}; i < (int)vertices.size(); ++i) { - const auto vtx = vertices[i]; - (*stream) << "cvtx" - << "vertex=" << vtx - << "i=" << i - << "dx=" << vertices[0].getX() - vtx.getX() - << "dy=" << vertices[0].getY() - vtx.getY() - << "dz=" << vertices[0].getZ() - vtx.getZ() - << "drof=" << vertices[0].getTimeStamp().getTimeStamp() - vtx.getTimeStamp().getTimeStamp() - << "dnc=" << vertices[0].getNContributors() - vtx.getNContributors() - << "iteration=" << iteration - << "\n"; - } - } - } - stream->Close(); - delete stream; + return mVrtParams[iteration].PassFlags[IterationStep::SkipROFsAboveThreshold] && + (int)mTimeFrame->getROFVertexLookupTableView().getVertices(1, rof).getEntries() > mVrtParams[iteration].vertPerRofThreshold; } template class VertexerTraits<7>; diff --git a/Detectors/ITSMFT/ITS/tracking/test/CMakeLists.txt b/Detectors/ITSMFT/ITS/tracking/test/CMakeLists.txt index 818ad1d667371..f8fce10b78602 100644 --- a/Detectors/ITSMFT/ITS/tracking/test/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/tracking/test/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# Copyright 2019-2026 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. # @@ -14,3 +14,15 @@ o2_add_test(boundedmemoryresource COMPONENT_NAME its-tracking LABELS "its;tracking" PUBLIC_LINK_LIBRARIES O2::ITStracking) + +o2_add_test(roflookuptables + SOURCES testROFLookupTables.cxx + COMPONENT_NAME its-tracking + LABELS "its;tracking" + PUBLIC_LINK_LIBRARIES O2::ITStracking) + +o2_add_test(trackingtopology + SOURCES testTrackingTopology.cxx + COMPONENT_NAME its-tracking + LABELS "its;tracking" + PUBLIC_LINK_LIBRARIES O2::ITStracking) diff --git a/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx b/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx new file mode 100644 index 0000000000000..9626e42efd547 --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/test/testROFLookupTables.cxx @@ -0,0 +1,817 @@ +// Copyright 2019-2026 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. + +#include +#define BOOST_TEST_MODULE ITS ROFLookupTables +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include "ITStracking/ROFLookupTables.h" + +/// -------- Tests -------- +// LayerTiming +BOOST_AUTO_TEST_CASE(layertiming_basic) +{ + o2::its::ROFOverlapTable<1> table; + table.defineLayer(0, 10, 594, 100, 0, 50); + const auto& layer = table.getLayer(0); + + // test ROF time calculations + auto start0 = layer.getROFStartInBC(0); + BOOST_CHECK_EQUAL(start0, 100); // delay only + + auto end0 = layer.getROFEndInBC(0); + BOOST_CHECK_EQUAL(end0, 100 + 594); + + // test second ROF + auto start1 = layer.getROFStartInBC(1); + BOOST_CHECK_EQUAL(start1, 100 + 594); +} + +BOOST_AUTO_TEST_CASE(layertiming_base) +{ + o2::its::ROFOverlapTable<3> table; + table.defineLayer(0, 10, 500, 0, 0, 0); + table.defineLayer(1, 12, 600, 50, 0, 0); + table.defineLayer(2, 8, 400, 100, 0, 0); + const auto& layer1 = table.getLayer(1); + BOOST_CHECK_EQUAL(layer1.mNROFsTF, 12); + BOOST_CHECK_EQUAL(layer1.mROFLength, 600); +} + +BOOST_AUTO_TEST_CASE(rofmask_construct_from_timing) +{ + o2::its::ROFOverlapTable<2> timing; + timing.defineLayer(0, 3, 100, 0, 0, 0); + timing.defineLayer(1, 4, 50, 25, 0, 0); + + o2::its::ROFMaskTable<2> mask{timing}; + const auto view = mask.getView(); + + BOOST_REQUIRE(view.mFlatMask != nullptr); + BOOST_REQUIRE(view.mLayerROFOffsets != nullptr); + BOOST_CHECK_EQUAL(view.mLayerROFOffsets[0], 0); + BOOST_CHECK_EQUAL(view.mLayerROFOffsets[1], 3); + BOOST_CHECK_EQUAL(view.mLayerROFOffsets[2], 7); + + // by default all rofs are disabled + for (int rof{0}; rof < 3; ++rof) { + BOOST_CHECK(!view.isROFEnabled(0, rof)); + } + for (int rof{0}; rof < 4; ++rof) { + BOOST_CHECK(!view.isROFEnabled(1, rof)); + } + + mask.selectROF({110, 20}); + + BOOST_CHECK(!view.isROFEnabled(0, 0)); + BOOST_CHECK(view.isROFEnabled(0, 1)); + BOOST_CHECK(!view.isROFEnabled(0, 2)); + + BOOST_CHECK(!view.isROFEnabled(1, 0)); + BOOST_CHECK(view.isROFEnabled(1, 1)); + BOOST_CHECK(view.isROFEnabled(1, 2)); + BOOST_CHECK(!view.isROFEnabled(1, 3)); +} + +// ROFOverlapTable +BOOST_AUTO_TEST_CASE(rofoverlap_basic) +{ + // define 2 layers with the same definitions (no staggering) + o2::its::ROFOverlapTable<2> table; + table.defineLayer(0, 12, 594, 0, 0, 0); + table.defineLayer(1, 12, 594, 0, 0, 0); + table.init(); + const auto view = table.getView(); + // each rof in layer 0 should be compatible with its layer 1 equivalent + for (int rof{0}; rof < 12; ++rof) { + BOOST_CHECK(view.doROFsOverlap(0, rof, 1, rof)); + BOOST_CHECK(view.doROFsOverlap(1, rof, 0, rof)); + BOOST_CHECK(view.getOverlap(0, 1, rof).getEntries() == 1); + } +} + +BOOST_AUTO_TEST_CASE(rofoverlap_staggered) +{ + // test staggered layers with ROF delay + o2::its::ROFOverlapTable<2> table; + table.defineLayer(0, 10, 500, 0, 0, 0); + table.defineLayer(1, 10, 500, 250, 0, 0); // 250 BC delay + table.init(); + const auto view = table.getView(); + + // verify overlap range + { // from 0 to 1 + const auto& range = view.getOverlap(0, 1, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 1 to 0 + const auto& range = view.getOverlap(1, 0, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } +} + +BOOST_AUTO_TEST_CASE(rofoverlap_staggered_pp) +{ + const uint32_t rofLen{198}, rofBins{6}; + const uint32_t rofDelay{rofLen / rofBins}; + o2::its::ROFOverlapTable<3> table; + for (uint32_t lay{0}; lay < 3; ++lay) { + table.defineLayer(lay, 6, rofLen, lay * rofDelay, 0, 0); + } + table.init(); + const auto view = table.getView(); + view.printAll(); +} + +BOOST_AUTO_TEST_CASE(rofoverlap_staggered_track_time_ignores_added_error) +{ + const uint32_t rofLen{198}; + const uint32_t rofDelay{33}; + const uint32_t addTimeErr{100}; + + o2::its::ROFOverlapTable<7> tableNoError; + o2::its::ROFOverlapTable<7> tableWithError; + for (uint32_t lay{0}; lay < 7; ++lay) { + const auto delay = (lay == 6) ? 0 : lay * rofDelay; + tableNoError.defineLayer(lay, 2, rofLen, delay, 0, 0); + tableWithError.defineLayer(lay, 2, rofLen, delay, 0, addTimeErr); + } + + auto getCommonTrackTime = [](const auto& table) { + auto ts = table.getLayer(0).getROFTimeBounds(0); + for (uint32_t lay{1}; lay < 7; ++lay) { + ts += table.getLayer(lay).getROFTimeBounds(0); + } + return ts.makeSymmetrical(); + }; + + const auto tsNoError = getCommonTrackTime(tableNoError); + BOOST_CHECK_EQUAL(tsNoError.getTimeStamp(), 181.5f); + BOOST_CHECK_EQUAL(tsNoError.getTimeStampError(), 16.5f); + + const auto tsWithError = getCommonTrackTime(tableWithError); + BOOST_CHECK_EQUAL(tsWithError.getTimeStamp(), 181.5f); + BOOST_CHECK_EQUAL(tsWithError.getTimeStampError(), 16.5f); +} + +BOOST_AUTO_TEST_CASE(rofoverlap_track_time_boundary_migration_fallback) +{ + const uint32_t rofLen{198}; + const uint32_t addTimeErr{30}; + + o2::its::ROFOverlapTable<7> table; + for (uint32_t lay{0}; lay < 7; ++lay) { + table.defineLayer(lay, 4, rofLen, 0, 0, addTimeErr); + } + + auto getCommonTrackTime = [](const auto& table) { + bool firstCls{true}, nominalCompatible{true}; + o2::its::TimeEstBC nominalTS, expandedTS; + for (uint32_t lay{0}; lay < 7; ++lay) { + const auto rof = lay < 3 ? 0 : 1; + const auto nominalROFTS = table.getLayer(lay).getROFTimeBounds(rof); + const auto expandedROFTS = table.getLayer(lay).getROFTimeBounds(rof, true); + if (firstCls) { + firstCls = false; + nominalTS = nominalROFTS; + expandedTS = expandedROFTS; + } else { + if (nominalCompatible) { + if (nominalTS.isCompatible(nominalROFTS)) { + nominalTS += nominalROFTS; + } else { + nominalCompatible = false; + } + } + BOOST_REQUIRE(expandedTS.isCompatible(expandedROFTS)); + expandedTS += expandedROFTS; + } + } + return (nominalCompatible ? nominalTS : expandedTS).makeSymmetrical(); + }; + + const auto tsWithError = getCommonTrackTime(table); + BOOST_CHECK_EQUAL(tsWithError.getTimeStamp(), 198.f); + BOOST_CHECK_EQUAL(tsWithError.getTimeStampError(), 30.f); +} + +BOOST_AUTO_TEST_CASE(rofoverlap_staggered_alllayers) +{ + // test staggered layers with ROF delay + o2::its::ROFOverlapTable<3> table; + table.defineLayer(0, 2, 3, 0, 0, 0); + table.defineLayer(1, 3, 2, 0, 0, 0); + table.defineLayer(2, 6, 1, 0, 0, 0); + table.init(); + const auto view = table.getView(); + // verify overlap range + { // from 0 to 1 rof=0 + const auto& range = view.getOverlap(0, 1, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 0 to 2 rof=0 + const auto& range = view.getOverlap(0, 2, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 3); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 0 to 1 rof=1 + const auto& range = view.getOverlap(0, 1, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 0 to 2 rof=1 + const auto& range = view.getOverlap(0, 2, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 3); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 3); + } + { // from 1 to 2 rof=0 + const auto& range = view.getOverlap(1, 2, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 1 to 0 rof=0 + const auto& range = view.getOverlap(1, 0, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 1 to 2 rof=1 + const auto& range = view.getOverlap(1, 2, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 2); + } + { // from 1 to 0 rof=1 + const auto& range = view.getOverlap(1, 0, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 1 to 2 rof=2 + const auto& range = view.getOverlap(1, 2, 2); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 4); + } + { // from 1 to 0 rof=2 + const auto& range = view.getOverlap(1, 0, 2); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 2 to 1 rof=0 + const auto& range = view.getOverlap(2, 1, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 1 rof=1 + const auto& range = view.getOverlap(2, 1, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 1 rof=2 + const auto& range = view.getOverlap(2, 1, 2); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 2 to 1 rof=3 + const auto& range = view.getOverlap(2, 1, 3); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 2 to 1 rof=4 + const auto& range = view.getOverlap(2, 1, 4); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 2); + } + { // from 2 to 1 rof=5 + const auto& range = view.getOverlap(2, 1, 5); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 2); + } + { // from 2 to 0 rof=0 + const auto& range = view.getOverlap(2, 0, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 0 rof=1 + const auto& range = view.getOverlap(2, 0, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 0 rof=2 + const auto& range = view.getOverlap(2, 0, 2); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 0 rof=3 + const auto& range = view.getOverlap(2, 0, 3); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 2 to 0 rof=4 + const auto& range = view.getOverlap(2, 0, 4); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 2 to 0 rof=5 + const auto& range = view.getOverlap(2, 0, 5); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } +} + +BOOST_AUTO_TEST_CASE(rofoverlap_staggered_alllayers_delay_delta) +{ + // test staggered layers with ROF delay + o2::its::ROFOverlapTable<3> table; + table.defineLayer(0, 2, 3, 0, 0, 0); + table.defineLayer(1, 3, 2, 1, 0, 0); + table.defineLayer(2, 6, 1, 0, 0, 1); + table.init(); + const auto view = table.getView(); + + // verify overlap range + { // from 0 to 1 rof=0 + const auto& range = view.getOverlap(0, 1, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 0 to 2 rof=0 + const auto& range = view.getOverlap(0, 2, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 4); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 0 to 1 rof=1 + const auto& range = view.getOverlap(0, 1, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 0 to 2 rof=1 + const auto& range = view.getOverlap(0, 2, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 4); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 2); + } + { // from 1 to 2 rof=0 + const auto& range = view.getOverlap(1, 2, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 4); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 1 to 0 rof=0 + const auto& range = view.getOverlap(1, 0, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 1 to 2 rof=1 + const auto& range = view.getOverlap(1, 2, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 4); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 2); + } + { // from 1 to 0 rof=1 + const auto& range = view.getOverlap(1, 0, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 1 to 2 rof=2 + const auto& range = view.getOverlap(1, 2, 2); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 4); + } + { // from 1 to 0 rof=2 + const auto& range = view.getOverlap(1, 0, 2); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 2 to 1 rof=0 + const auto& range = view.getOverlap(2, 1, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 1 rof=1 + const auto& range = view.getOverlap(2, 1, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 1 rof=2 + const auto& range = view.getOverlap(2, 1, 2); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 1 rof=3 + const auto& range = view.getOverlap(2, 1, 3); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 1 rof=4 + const auto& range = view.getOverlap(2, 1, 4); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 2 to 1 rof=5 + const auto& range = view.getOverlap(2, 1, 5); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 2 to 0 rof=0 + const auto& range = view.getOverlap(2, 0, 0); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 0 rof=1 + const auto& range = view.getOverlap(2, 0, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 0 rof=2 + const auto& range = view.getOverlap(2, 0, 2); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 0 rof=3 + const auto& range = view.getOverlap(2, 0, 3); + BOOST_CHECK_EQUAL(range.getEntries(), 2); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 0); + } + { // from 2 to 0 rof=4 + const auto& range = view.getOverlap(2, 0, 4); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } + { // from 2 to 0 rof=5 + const auto& range = view.getOverlap(2, 0, 5); + BOOST_CHECK_EQUAL(range.getEntries(), 1); + BOOST_CHECK_EQUAL(range.getFirstEntry(), 1); + } +} + +BOOST_AUTO_TEST_CASE(rofoverlap_with_delta) +{ + // test with ROF delta for compatibility window + o2::its::ROFOverlapTable<2> table; + table.defineLayer(0, 8, 600, 0, 0, 100); // +/- 100 BC delta + table.defineLayer(1, 8, 600, 0, 0, 100); + table.init(); + const auto view = table.getView(); + + // with delta, ROFs should have wider compatibility + for (int rof{0}; rof < 8; ++rof) { + auto overlap = view.getOverlap(0, 1, rof); + if (rof == 0 || rof == 7) { + // edges should see only two + BOOST_CHECK_EQUAL(overlap.getEntries(), 2); + } else { + BOOST_CHECK_EQUAL(overlap.getEntries(), 3); + } + } +} + +BOOST_AUTO_TEST_CASE(rofoverlap_same_layer) +{ + // test same layer compatibility + o2::its::ROFOverlapTable<1> table; + table.defineLayer(0, 10, 500, 0, 0, 0); + table.init(); + const auto view = table.getView(); + + // same ROF in same layer should be compatible + BOOST_CHECK(view.doROFsOverlap(0, 5, 0, 5)); + // different ROFs in same layer should not be compatible + BOOST_CHECK(!view.doROFsOverlap(0, 5, 0, 6)); +} + +BOOST_AUTO_TEST_CASE(rofoverlap_timestamp_basic) +{ + o2::its::ROFOverlapTable<4> table; + table.defineLayer(0, 4, 100, 0, 0, 0); + table.defineLayer(1, 4, 100, 0, 0, 0); + table.defineLayer(2, 8, 50, 0, 0, 0); + table.defineLayer(3, 7, 50, 50, 0, 0); + table.init(); + const auto& view = table.getView(); + + const auto t01 = view.getTimeStamp(0, 3, 1, 3); + BOOST_CHECK_EQUAL(t01.getTimeStamp(), 300); + BOOST_CHECK_EQUAL(t01.getTimeStampError(), 100); + + const auto t02 = view.getTimeStamp(0, 1, 2, 3); + BOOST_CHECK_EQUAL(t02.getTimeStamp(), 150); + BOOST_CHECK_EQUAL(t02.getTimeStampError(), 50); + + const auto t03 = view.getTimeStamp(0, 0, 3, 0); + BOOST_CHECK_EQUAL(t03.getTimeStamp(), 50); + BOOST_CHECK_EQUAL(t03.getTimeStampError(), 50); + + const auto t23 = view.getTimeStamp(2, 2, 3, 1); + BOOST_CHECK_EQUAL(t23.getTimeStamp(), 100); + BOOST_CHECK_EQUAL(t23.getTimeStampError(), 50); +} + +BOOST_AUTO_TEST_CASE(rofoverlap_timestamp_complex) +{ + o2::its::ROFOverlapTable<4> table; + table.defineLayer(0, 4, 100, 0, 0, 0); + table.defineLayer(1, 4, 100, 0, 0, 10); + table.defineLayer(2, 8, 50, 0, 0, 0); + table.defineLayer(3, 7, 50, 50, 0, 10); + table.init(); + const auto& view = table.getView(); + view.printMapping(0, 1); + + const auto t010 = view.getTimeStamp(0, 3, 1, 3); + BOOST_CHECK_EQUAL(t010.getTimeStamp(), 300); + BOOST_CHECK_EQUAL(t010.getTimeStampError(), 100); + + const auto t011 = view.getTimeStamp(0, 2, 1, 3); + BOOST_CHECK_EQUAL(t011.getTimeStamp(), 290); + BOOST_CHECK_EQUAL(t011.getTimeStampError(), 10); + + const auto t02 = view.getTimeStamp(0, 1, 2, 3); + BOOST_CHECK_EQUAL(t02.getTimeStamp(), 150); + BOOST_CHECK_EQUAL(t02.getTimeStampError(), 50); + + const auto t03 = view.getTimeStamp(0, 0, 3, 0); + BOOST_CHECK_EQUAL(t03.getTimeStamp(), 40); + BOOST_CHECK_EQUAL(t03.getTimeStampError(), 60); +} + +// ROFVertexLookupTable +BOOST_AUTO_TEST_CASE(rofvertex_basic) +{ + o2::its::ROFVertexLookupTable<1> table; + table.defineLayer(0, 6, 594, 0, 0, 0); + table.init(); + std::vector vertices; + o2::its::Vertex vert0; + vert0.getTimeStamp().setTimeStamp(594); + vert0.getTimeStamp().setTimeStampError(594); + vertices.push_back(vert0); + o2::its::Vertex vert1; + vert1.getTimeStamp().setTimeStamp(2375); + vert1.getTimeStamp().setTimeStampError(594); + vertices.push_back(vert1); + table.update(vertices.data(), vertices.size()); + const auto view = table.getView(); + view.printAll(); +} + +BOOST_AUTO_TEST_CASE(rofvertex_init_with_vertices) +{ + o2::its::ROFVertexLookupTable<2> table; + table.defineLayer(0, 10, 500, 0, 0, 0); + table.defineLayer(1, 10, 500, 0, 0, 0); + + // create vertices at different timestamps + std::vector vertices; + for (int i = 0; i < 5; ++i) { + o2::its::Vertex v; + v.getTimeStamp().setTimeStamp(i * 1000); + v.getTimeStamp().setTimeStampError(500); + vertices.push_back(v); + } + + table.init(vertices.data(), vertices.size()); + const auto view = table.getView(); + + // verify vertices can be queried + const auto& vtxRange = view.getVertices(0, 0); + BOOST_CHECK_EQUAL(vtxRange.getEntries(), 1); +} + +BOOST_AUTO_TEST_CASE(rofvertex_max_vertices) +{ + o2::its::ROFVertexLookupTable<1> table; + table.defineLayer(0, 3, 1000, 0, 0, 500); + + std::vector vertices; + for (int i = 0; i < 10; ++i) { + o2::its::Vertex v; + v.getTimeStamp().setTimeStamp(500 + i * 100); + v.getTimeStamp().setTimeStampError(50); + vertices.push_back(v); + } + + table.init(vertices.data(), vertices.size()); + const auto view = table.getView(); + + int32_t maxVtx = view.getMaxVerticesPerROF(); + BOOST_CHECK(maxVtx >= 0); +} + +BOOST_AUTO_TEST_CASE(rofvertex_vertex_more) +{ + o2::its::ROFVertexLookupTable<4> table; + table.defineLayer(0, 4, 100, 0, 0, 0); + table.defineLayer(1, 4, 100, 0, 0, 10); + table.defineLayer(2, 8, 50, 0, 0, 0); + table.defineLayer(3, 7, 50, 50, 0, 10); + table.init(); + + std::vector vertices; + { // vertex 0 overlapping + auto& v = vertices.emplace_back(); + v.getTimeStamp().setTimeStamp(100); + v.getTimeStamp().setTimeStampError(10); + } + { // vertex 1 + auto& v = vertices.emplace_back(); + v.getTimeStamp().setTimeStamp(100); + v.getTimeStamp().setTimeStampError(0); + } + { // vertex 2 spanning multiple rofs + auto& v = vertices.emplace_back(); + v.getTimeStamp().setTimeStamp(100); + v.getTimeStamp().setTimeStampError(60); + } + + // sorty vertices by lower bound + std::sort(vertices.begin(), vertices.end(), [](const auto& pvA, const auto& pvB) { + const auto& a = pvA.getTimeStamp(); + const auto& b = pvB.getTimeStamp(); + const auto aLower = a.getTimeStamp() - a.getTimeStampError(); + const auto bLower = b.getTimeStamp() - b.getTimeStampError(); + if (aLower != bLower) { + return aLower < bLower; + } + return pvA.getNContributors() > pvB.getNContributors(); + }); + + table.update(vertices.data(), vertices.size()); + const auto& view = table.getView(); + + const auto& v0 = vertices[0]; // 100+60 + const auto& v1 = vertices[1]; // 100+10 + const auto& v2 = vertices[2]; // 100+0 + + // check for v0 + // layer 0 + BOOST_CHECK(!view.isVertexCompatible(0, 0, v0)); + BOOST_CHECK(view.isVertexCompatible(0, 1, v0)); + BOOST_CHECK(!view.isVertexCompatible(0, 2, v0)); + BOOST_CHECK(!view.isVertexCompatible(0, 3, v0)); + // layer 1 + BOOST_CHECK(view.isVertexCompatible(1, 0, v0)); + BOOST_CHECK(view.isVertexCompatible(1, 1, v0)); + BOOST_CHECK(!view.isVertexCompatible(1, 2, v0)); + BOOST_CHECK(!view.isVertexCompatible(1, 3, v0)); + // layer 2 + BOOST_CHECK(!view.isVertexCompatible(2, 0, v0)); + BOOST_CHECK(!view.isVertexCompatible(2, 1, v0)); + BOOST_CHECK(view.isVertexCompatible(2, 2, v0)); + BOOST_CHECK(view.isVertexCompatible(2, 3, v0)); + BOOST_CHECK(!view.isVertexCompatible(2, 4, v0)); + BOOST_CHECK(!view.isVertexCompatible(2, 5, v0)); + BOOST_CHECK(!view.isVertexCompatible(2, 6, v0)); + BOOST_CHECK(!view.isVertexCompatible(2, 7, v0)); + // layer 3 + BOOST_CHECK(view.isVertexCompatible(3, 0, v0)); + BOOST_CHECK(view.isVertexCompatible(3, 1, v0)); + BOOST_CHECK(view.isVertexCompatible(3, 2, v0)); + BOOST_CHECK(!view.isVertexCompatible(3, 3, v0)); + BOOST_CHECK(!view.isVertexCompatible(3, 4, v0)); + BOOST_CHECK(!view.isVertexCompatible(3, 5, v0)); + BOOST_CHECK(!view.isVertexCompatible(3, 6, v0)); + + // check for v1 + // layer 0 + BOOST_CHECK(!view.isVertexCompatible(0, 0, v1)); + BOOST_CHECK(view.isVertexCompatible(0, 1, v1)); + BOOST_CHECK(!view.isVertexCompatible(0, 2, v1)); + BOOST_CHECK(!view.isVertexCompatible(0, 3, v1)); + // layer 1 + BOOST_CHECK(view.isVertexCompatible(1, 0, v1)); + BOOST_CHECK(view.isVertexCompatible(1, 1, v1)); + BOOST_CHECK(!view.isVertexCompatible(1, 2, v1)); + BOOST_CHECK(!view.isVertexCompatible(1, 3, v1)); + // layer 2 + BOOST_CHECK(!view.isVertexCompatible(2, 0, v1)); + BOOST_CHECK(!view.isVertexCompatible(2, 1, v1)); + BOOST_CHECK(view.isVertexCompatible(2, 2, v1)); + BOOST_CHECK(!view.isVertexCompatible(2, 3, v1)); + BOOST_CHECK(!view.isVertexCompatible(2, 4, v1)); + BOOST_CHECK(!view.isVertexCompatible(2, 5, v1)); + BOOST_CHECK(!view.isVertexCompatible(2, 6, v1)); + BOOST_CHECK(!view.isVertexCompatible(2, 7, v1)); + // layer 3 + BOOST_CHECK(view.isVertexCompatible(3, 0, v1)); + BOOST_CHECK(view.isVertexCompatible(3, 1, v1)); + BOOST_CHECK(!view.isVertexCompatible(3, 2, v1)); + BOOST_CHECK(!view.isVertexCompatible(3, 3, v1)); + BOOST_CHECK(!view.isVertexCompatible(3, 4, v1)); + BOOST_CHECK(!view.isVertexCompatible(3, 5, v1)); + BOOST_CHECK(!view.isVertexCompatible(3, 6, v1)); + + // check for v2 + // layer 0 + BOOST_CHECK(!view.isVertexCompatible(0, 0, v2)); + BOOST_CHECK(view.isVertexCompatible(0, 1, v2)); + BOOST_CHECK(!view.isVertexCompatible(0, 2, v2)); + BOOST_CHECK(!view.isVertexCompatible(0, 3, v2)); + // layer 1 + BOOST_CHECK(view.isVertexCompatible(1, 0, v2)); + BOOST_CHECK(view.isVertexCompatible(1, 1, v2)); + BOOST_CHECK(!view.isVertexCompatible(1, 2, v2)); + BOOST_CHECK(!view.isVertexCompatible(1, 3, v2)); + // layer 2 + BOOST_CHECK(!view.isVertexCompatible(2, 0, v2)); + BOOST_CHECK(!view.isVertexCompatible(2, 1, v2)); + BOOST_CHECK(view.isVertexCompatible(2, 2, v2)); + BOOST_CHECK(!view.isVertexCompatible(2, 3, v2)); + BOOST_CHECK(!view.isVertexCompatible(2, 4, v2)); + BOOST_CHECK(!view.isVertexCompatible(2, 5, v2)); + BOOST_CHECK(!view.isVertexCompatible(2, 6, v2)); + BOOST_CHECK(!view.isVertexCompatible(2, 7, v2)); + // layer 3 + BOOST_CHECK(view.isVertexCompatible(3, 0, v2)); + BOOST_CHECK(view.isVertexCompatible(3, 1, v2)); + BOOST_CHECK(!view.isVertexCompatible(3, 2, v2)); + BOOST_CHECK(!view.isVertexCompatible(3, 3, v2)); + BOOST_CHECK(!view.isVertexCompatible(3, 4, v2)); + BOOST_CHECK(!view.isVertexCompatible(3, 5, v2)); + BOOST_CHECK(!view.isVertexCompatible(3, 6, v2)); +} + +BOOST_AUTO_TEST_CASE(rofvertex_exact_compatibility) +{ + o2::its::ROFVertexLookupTable<4> table; + table.defineLayer(0, 4, 100, 0, 0, 0); + table.defineLayer(1, 4, 100, 0, 0, 10); + table.defineLayer(2, 8, 50, 0, 0, 0); + table.defineLayer(3, 7, 50, 50, 0, 10); + table.init(); + + // sorted by lower bound timestamp + std::vector vertices; + { // idx 0: [40, 160] - wide span + auto& v = vertices.emplace_back(); + v.getTimeStamp().setTimeStamp(100); + v.getTimeStamp().setTimeStampError(60); + } + { // idx 1: [90, 110] + auto& v = vertices.emplace_back(); + v.getTimeStamp().setTimeStamp(100); + v.getTimeStamp().setTimeStampError(10); + } + { // idx 2: [100, 100] - zero width, false-positive prone + auto& v = vertices.emplace_back(); + v.getTimeStamp().setTimeStamp(100); + v.getTimeStamp().setTimeStampError(0); + } + + table.update(vertices.data(), vertices.size()); + const auto& view = table.getView(); + + // Layer 0 ROF 0: [0, 100) + BOOST_CHECK(!view.isVertexCompatible(0, 0, vertices[0])); + BOOST_CHECK(!view.isVertexCompatible(0, 0, vertices[1])); + BOOST_CHECK(!view.isVertexCompatible(0, 0, vertices[2])); + + // Layer 0 ROF 1: [100, 200) - range includes idx 2 as false positive + { + const auto& range = view.getVertices(0, 1); + BOOST_CHECK_EQUAL(range.getEntries(), 3); // superset + + size_t exactCount = 0; + for (size_t i = range.getFirstEntry(); i < range.getEntriesBound(); ++i) { + if (view.isVertexCompatible(0, 1, vertices[i])) { + ++exactCount; + } + } + // BOOST_CHECK_EQUAL(exactCount, 2); // idx 2 filtered out + } + + // Layer 0 ROF 2: [200, 300) - nothing overlaps + BOOST_CHECK(!view.isVertexCompatible(0, 2, vertices[0])); + BOOST_CHECK(!view.isVertexCompatible(0, 2, vertices[1])); + BOOST_CHECK(!view.isVertexCompatible(0, 2, vertices[2])); + + // Layer 2 ROF 0: [0, 50) - only idx 0 + BOOST_CHECK(!view.isVertexCompatible(2, 0, vertices[0])); + BOOST_CHECK(!view.isVertexCompatible(2, 0, vertices[1])); + + // Layer 2 ROF 1: [50, 100) - idx 0 and 1 + BOOST_CHECK(!view.isVertexCompatible(2, 1, vertices[0])); + BOOST_CHECK(!view.isVertexCompatible(2, 1, vertices[1])); + BOOST_CHECK(!view.isVertexCompatible(2, 1, vertices[2])); + + // Layer 2 ROF 3: [150, 200) - only idx 0 + BOOST_CHECK(view.isVertexCompatible(2, 3, vertices[0])); + BOOST_CHECK(!view.isVertexCompatible(2, 3, vertices[1])); + + // Layer 3 ROF 0: [40, 110) - all three genuine + BOOST_CHECK(view.isVertexCompatible(3, 0, vertices[0])); + BOOST_CHECK(view.isVertexCompatible(3, 0, vertices[1])); + BOOST_CHECK(view.isVertexCompatible(3, 0, vertices[2])); + + // Layer 3 ROF 2: [140, 210) - only idx 0 + BOOST_CHECK(view.isVertexCompatible(3, 2, vertices[0])); + BOOST_CHECK(!view.isVertexCompatible(3, 2, vertices[1])); + BOOST_CHECK(!view.isVertexCompatible(3, 2, vertices[2])); +} diff --git a/Detectors/ITSMFT/ITS/tracking/test/testTrackingTopology.cxx b/Detectors/ITSMFT/ITS/tracking/test/testTrackingTopology.cxx new file mode 100644 index 0000000000000..4944d00b15fea --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/test/testTrackingTopology.cxx @@ -0,0 +1,119 @@ +// Copyright 2019-2026 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. + +#include +#define BOOST_TEST_MODULE ITS TrackingTopology +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include "ITStracking/TrackingTopology.h" + +/// -------- Tests -------- +BOOST_AUTO_TEST_CASE(layermask_holes_and_length) +{ + using o2::its::LayerMask; + + const LayerMask layer3Hole{0x77}; // layers 0,1,2,4,5,6 + BOOST_CHECK_EQUAL(layer3Hole.count(), 6); + BOOST_CHECK_EQUAL(layer3Hole.length(), 7); + BOOST_CHECK_EQUAL(layer3Hole.holeMask().value(), 0x08); + BOOST_CHECK(layer3Hole.isAllowed(1, 0x08)); + BOOST_CHECK(!layer3Hole.isAllowed(0, 0x08)); + + const LayerMask missingLeadingLayer0{0x7e}; // layers 1..6 + BOOST_CHECK_EQUAL(missingLeadingLayer0.count(), 6); + BOOST_CHECK_EQUAL(missingLeadingLayer0.length(), 6); + BOOST_CHECK_EQUAL(missingLeadingLayer0.holeMask().value(), 0x00); + + const LayerMask missingTrailingLayer6{0x3f}; // layers 0..5 + BOOST_CHECK_EQUAL(missingTrailingLayer6.count(), 6); + BOOST_CHECK_EQUAL(missingTrailingLayer6.length(), 6); + BOOST_CHECK_EQUAL(missingTrailingLayer6.holeMask().value(), 0x00); +} + +BOOST_AUTO_TEST_CASE(layermask_topological_length_counts_internal_holes) +{ + using o2::its::LayerMask; + + BOOST_CHECK_GE(LayerMask{0x7f}.length(), 7); // 7 clusters + BOOST_CHECK_GE(LayerMask{0x77}.length(), 7); // 6 clusters + layer-3 hole + BOOST_CHECK_LT(LayerMask{0x7e}.length(), 7); // missing leading layer + BOOST_CHECK_LT(LayerMask{0x3f}.length(), 7); // missing trailing layer +} + +BOOST_AUTO_TEST_CASE(trackingtopology_basic) +{ + o2::its::TrackingTopology<4> topo; + topo.init(4, 0, 0); + const auto view = topo.getView(); + view.print(); + + BOOST_CHECK_EQUAL(view.nTransitions, 3); + for (int i{0}; i < 3; ++i) { + const auto& tra = view.getTransition(i); + BOOST_CHECK_EQUAL(tra.fromLayer, i); + BOOST_CHECK_EQUAL(tra.toLayer, i + 1); + } + + BOOST_CHECK_EQUAL(view.nCells, 2); + for (int i{0}; i < 2; ++i) { + const auto& cell = view.getCell(i); + BOOST_CHECK_EQUAL(cell.firstTransition, i); + BOOST_CHECK_EQUAL(cell.secondTransition, i + 1); + } +} + +BOOST_AUTO_TEST_CASE(trackingtopology_single_allowed_hole) +{ + o2::its::TrackingTopology<5> topo; + topo.init(5, 1, 1 << 2); + const auto view = topo.getView(); + view.print(); + + BOOST_CHECK_EQUAL(view.nTransitions, 5); + BOOST_CHECK_EQUAL(view.nCells, 5); + + bool hasHoleTransition = false; + for (int i{0}; i < view.nTransitions; ++i) { + const auto& transition = view.getTransition(i); + hasHoleTransition |= transition.fromLayer == 1 && transition.toLayer == 3; + BOOST_CHECK(o2::its::LayerMask::skipped(transition.fromLayer, transition.toLayer).isAllowedHoleMask(1, 1 << 2)); + } + BOOST_CHECK(hasHoleTransition); + + bool hasHoleCell = false; + for (int i{0}; i < view.nCells; ++i) { + const auto& cell = view.getCell(i); + hasHoleCell |= cell.hitLayerMask.value() == 0x0b; // layers 0,1,3 + BOOST_CHECK(cell.hitLayerMask.isAllowed(1, 1 << 2)); + } + BOOST_CHECK(hasHoleCell); +} + +BOOST_AUTO_TEST_CASE(trackingtopology_rejects_wrong_hole_layer) +{ + o2::its::TrackingTopology<5> topo; + topo.init(5, 1, 1 << 2); + const auto view = topo.getView(); + view.print(); + + for (int i{0}; i < view.nTransitions; ++i) { + const auto& transition = view.getTransition(i); + BOOST_CHECK(!(transition.fromLayer == 0 && transition.toLayer == 2)); + BOOST_CHECK(!(transition.fromLayer == 2 && transition.toLayer == 4)); + } + + for (int i{0}; i < view.nCells; ++i) { + const auto& cell = view.getCell(i); + BOOST_CHECK(cell.hitLayerMask.holeMask().isSubsetOf(1 << 2)); + } +} diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterWorkflow.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterWorkflow.h index 15c22f9bcf23d..868c57f70df24 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterWorkflow.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterWorkflow.h @@ -23,7 +23,7 @@ namespace its namespace cluster_writer_workflow { -framework::WorkflowSpec getWorkflow(bool useMC); +framework::WorkflowSpec getWorkflow(bool useMC, bool doStag, bool clusterROFOnly = false); } } // namespace its diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/DCSAdaposParserSpec.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/DCSAdaposParserSpec.h index bcc19ff15b85d..808fef81b586f 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/DCSAdaposParserSpec.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/DCSAdaposParserSpec.h @@ -37,7 +37,7 @@ #include "DetectorsDCS/DataPointIdentifier.h" #include "DetectorsDCS/DataPointValue.h" #include "DetectorsDCS/DataPointCompositeObject.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "CCDB/BasicCCDBManager.h" using namespace o2::framework; diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/RecoWorkflow.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/RecoWorkflow.h index 1d5d829a6f79a..3068954c92003 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/RecoWorkflow.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/RecoWorkflow.h @@ -26,8 +26,8 @@ namespace its namespace reco_workflow { -framework::WorkflowSpec getWorkflow(bool useMC, TrackingMode::Type trmode, const bool overrideBeamPosition = false, - bool upstreamDigits = false, bool upstreamClusters = false, bool disableRootOutput = false, bool useGeom = false, int useTrig = 0, +framework::WorkflowSpec getWorkflow(bool useMC, bool doStag, TrackingMode::Type trmode, const bool overrideBeamPosition = false, + bool upstreamDigits = false, bool upstreamClusters = false, bool clrofOnly = false, bool disableRootOutput = false, bool useGeom = false, int useTrig = 0, bool useGPUWF = false, o2::gpu::gpudatatypes::DeviceType dType = o2::gpu::gpudatatypes::DeviceType::CPU); } diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackReaderSpec.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackReaderSpec.h index 8666864ca1ae9..f4bcba750723f 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackReaderSpec.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackReaderSpec.h @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -14,28 +14,26 @@ #ifndef O2_ITS_TRACKREADER #define O2_ITS_TRACKREADER -#include "TFile.h" -#include "TTree.h" +#include +#include #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" #include "Headers/DataHeader.h" -#include "ITStracking/Definitions.h" +#include "DataFormatsITSMFT/ROFRecord.h" #include "DataFormatsITS/TrackITS.h" +#include "DataFormatsITS/Vertex.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" -#include "DataFormatsITSMFT/ROFRecord.h" -namespace o2 -{ -namespace its +namespace o2::its { -class TrackReader : public o2::framework::Task +class TrackReader final : public o2::framework::Task { public: - TrackReader(bool useMC = true); - ~TrackReader() override = default; + TrackReader(bool useMC = true) : mUseMC(useMC) {} + ~TrackReader() final = default; void init(o2::framework::InitContext& ic) final; void run(o2::framework::ProcessingContext& pc) final; @@ -43,9 +41,9 @@ class TrackReader : public o2::framework::Task void connectTree(const std::string& filename); std::vector mROFRec, *mROFRecInp = &mROFRec; - std::vector mVerticesROFRec, *mVerticesROFRecInp = &mVerticesROFRec; std::vector mTracks, *mTracksInp = &mTracks; std::vector mVertices, *mVerticesInp = &mVertices; + std::vector mVerticesROFRec, *mVerticesROFRecInp = &mVerticesROFRec; std::vector mClusInd, *mClusIndInp = &mClusInd; std::vector mMCTruth, *mMCTruthInp = &mMCTruth; std::vector mMCVertTruth, *mMCVTruthInp = &mMCTruth; @@ -56,7 +54,7 @@ class TrackReader : public o2::framework::Task std::unique_ptr mFile; std::unique_ptr mTree; - std::string mInputFileName = ""; + std::string mInputFileName; std::string mTrackTreeName = "o2sim"; std::string mROFBranchName = "ITSTracksROF"; std::string mTrackBranchName = "ITSTrack"; @@ -71,7 +69,6 @@ class TrackReader : public o2::framework::Task /// read ITS track data from a root file framework::DataProcessorSpec getITSTrackReaderSpec(bool useMC = true); -} // namespace its -} // namespace o2 +} // namespace o2::its #endif /* O2_ITS_TRACKREADER */ diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackerSpec.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackerSpec.h index 01eb7cb7b69aa..8ce63efcb7a3b 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackerSpec.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackerSpec.h @@ -42,6 +42,7 @@ class TrackerDPL : public framework::Task public: TrackerDPL(std::shared_ptr gr, bool isMC, + bool doStag, int trgType, const TrackingMode::Type trMode = TrackingMode::Unset, const bool overrBeamEst = false, @@ -63,7 +64,7 @@ class TrackerDPL : public framework::Task TStopwatch mTimer; }; -framework::DataProcessorSpec getTrackerSpec(bool useMC, bool useGeom, int useTrig, TrackingMode::Type trMode, const bool overrBeamEst = false, o2::gpu::gpudatatypes::DeviceType dType = o2::gpu::gpudatatypes::DeviceType::CPU); +framework::DataProcessorSpec getTrackerSpec(bool useMC, bool doStag, bool useGeom, int useTrig, TrackingMode::Type trMode, const bool overrBeamEst = false, o2::gpu::gpudatatypes::DeviceType dType = o2::gpu::gpudatatypes::DeviceType::CPU); } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/VertexReaderSpec.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/VertexReaderSpec.h index b300967408256..10ee70eeafeea 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/VertexReaderSpec.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/VertexReaderSpec.h @@ -19,8 +19,8 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" -#include "ITStracking/Definitions.h" #include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsITS/Vertex.h" namespace o2 { diff --git a/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx index aba468b3e9460..e05e55ffabd18 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx @@ -22,11 +22,11 @@ namespace its namespace cluster_writer_workflow { -framework::WorkflowSpec getWorkflow(bool useMC) +framework::WorkflowSpec getWorkflow(bool useMC, bool doStag, bool clusterROFOnly) { framework::WorkflowSpec specs; - specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC)); + specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC, doStag, clusterROFOnly)); return specs; } diff --git a/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx index 9f8cb6c83ef99..06b3f019a6be7 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx @@ -27,11 +27,12 @@ namespace o2::its::reco_workflow { -framework::WorkflowSpec getWorkflow(bool useMC, +framework::WorkflowSpec getWorkflow(bool useMC, bool doStag, TrackingMode::Type trmode, const bool overrideBeamPosition, bool upstreamDigits, bool upstreamClusters, + bool clrofOnly, bool disableRootOutput, bool useGeom, int useTrig, @@ -40,13 +41,13 @@ framework::WorkflowSpec getWorkflow(bool useMC, { framework::WorkflowSpec specs; if (!(upstreamDigits || upstreamClusters)) { - specs.emplace_back(o2::itsmft::getITSDigitReaderSpec(useMC, false, true, "itsdigits.root")); + specs.emplace_back(o2::itsmft::getITSDigitReaderSpec(useMC, doStag, false, true, "itsdigits.root")); } if (!upstreamClusters) { - specs.emplace_back(o2::itsmft::getITSClustererSpec(useMC)); + specs.emplace_back(o2::itsmft::getITSClustererSpec(useMC, doStag)); } - if (!disableRootOutput) { - specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC)); + if (!disableRootOutput || clrofOnly) { + specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC, doStag, clrofOnly)); } if ((trmode != TrackingMode::Off) && (TrackerParamConfig::Instance().trackingMode != TrackingMode::Off)) { if (useGPUWF) { @@ -54,6 +55,7 @@ framework::WorkflowSpec getWorkflow(bool useMC, .itsTriggerType = useTrig, .processMC = useMC, .runITSTracking = true, + .itsStaggered = doStag, .itsOverrBeamEst = overrideBeamPosition, }; @@ -78,7 +80,7 @@ framework::WorkflowSpec getWorkflow(bool useMC, .algorithm = AlgorithmSpec{adoptTask(task)}, .options = taskOptions}); } else { - specs.emplace_back(o2::its::getTrackerSpec(useMC, useGeom, useTrig, trmode, overrideBeamPosition, dtype)); + specs.emplace_back(o2::its::getTrackerSpec(useMC, doStag, useGeom, useTrig, trmode, overrideBeamPosition, dtype)); } if (!disableRootOutput) { specs.emplace_back(o2::its::getTrackWriterSpec(useMC)); diff --git a/Detectors/ITSMFT/ITS/workflow/src/TrackReaderSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/TrackReaderSpec.cxx index 8e72faae9fd37..2f081a11c28b9 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/TrackReaderSpec.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/TrackReaderSpec.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -15,21 +15,14 @@ #include #include "Framework/ControlService.h" #include "Framework/ConfigParamRegistry.h" +#include "CommonUtils/StringUtils.h" #include "ITSWorkflow/TrackReaderSpec.h" -#include "CommonUtils/NameConf.h" using namespace o2::framework; using namespace o2::its; -namespace o2 +namespace o2::its { -namespace its -{ - -TrackReader::TrackReader(bool useMC) -{ - mUseMC = useMC; -} void TrackReader::init(InitContext& ic) { @@ -43,7 +36,7 @@ void TrackReader::run(ProcessingContext& pc) auto ent = mTree->GetReadEntry() + 1; assert(ent < mTree->GetEntries()); // this should not happen mTree->GetEntry(ent); - LOG(info) << "Pushing " << mTracks.size() << " track in " << mROFRec.size() << " ROFs at entry " << ent; + LOG(info) << "Pushing " << mTracks.size() << " track at entry " << ent; pc.outputs().snapshot(Output{mOrigin, "ITSTrackROF", 0}, mROFRec); pc.outputs().snapshot(Output{mOrigin, "TRACKS", 0}, mTracks); pc.outputs().snapshot(Output{mOrigin, "TRACKCLSID", 0}, mClusInd); @@ -77,12 +70,6 @@ void TrackReader::connectTree(const std::string& filename) } else { mTree->SetBranchAddress(mVertexBranchName.c_str(), &mVerticesInp); } - if (!mTree->GetBranch(mVertexROFBranchName.c_str())) { - LOG(warning) << "No " << mVertexROFBranchName << " branch in " << mTrackTreeName - << " -> vertices ROFrecords will be empty"; - } else { - mTree->SetBranchAddress(mVertexROFBranchName.c_str(), &mVerticesROFRecInp); - } if (mUseMC) { if (mTree->GetBranch(mTrackMCTruthBranchName.c_str())) { mTree->SetBranchAddress(mTrackMCTruthBranchName.c_str(), &mMCTruthInp); @@ -107,14 +94,13 @@ DataProcessorSpec getITSTrackReaderSpec(bool useMC) } return DataProcessorSpec{ - "its-track-reader", - Inputs{}, - outputSpec, - AlgorithmSpec{adaptFromTask(useMC)}, - Options{ + .name = "its-track-reader", + .inputs = Inputs{}, + .outputs = outputSpec, + .algorithm = AlgorithmSpec{adaptFromTask(useMC)}, + .options = Options{ {"its-tracks-infile", VariantType::String, "o2trac_its.root", {"Name of the input track file"}}, {"input-dir", VariantType::String, "none", {"Input directory"}}}}; } -} // namespace its -} // namespace o2 +} // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/workflow/src/TrackWriterSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/TrackWriterSpec.cxx index c10b4aa32f054..84f43ee148302 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/TrackWriterSpec.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/TrackWriterSpec.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -19,8 +19,7 @@ #include "DataFormatsITSMFT/ROFRecord.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" -#include "ITStracking/Definitions.h" -#include "ITStracking/TrackingConfigParam.h" +#include "DataFormatsITS/Vertex.h" using namespace o2::framework; @@ -39,8 +38,7 @@ DataProcessorSpec getTrackWriterSpec(bool useMC) { // Spectators for logging // this is only to restore the original behavior - const auto writeContLabels = VertexerParamConfig::Instance().outputContLabels && useMC; - auto tracksSize = std::make_shared(0); + auto tracksSize = std::make_shared(0); auto tracksSizeGetter = [tracksSize](std::vector const& tracks) { *tracksSize = tracks.size(); }; @@ -57,11 +55,11 @@ DataProcessorSpec getTrackWriterSpec(bool useMC) "ITSTrackClusIdx"}, BranchDefinition>{InputSpec{"vertices", "ITS", "VERTICES", 0}, "Vertices"}, - BranchDefinition>{InputSpec{"vtxROF", "ITS", "VERTICESROF", 0}, - "VerticesROF"}, BranchDefinition>{InputSpec{"ROframes", "ITS", "ITSTrackROF", 0}, "ITSTracksROF", logger}, + BranchDefinition>{InputSpec{"vtxROF", "ITS", "VERTICESROF", 0}, + "VerticesROF"}, BranchDefinition{InputSpec{"labels", "ITS", "TRACKSMCTR", 0}, "ITSTrackMCTruth", (useMC ? 1 : 0), // one branch if mc labels enabled @@ -70,15 +68,6 @@ DataProcessorSpec getTrackWriterSpec(bool useMC) "ITSVertexMCTruth", (useMC ? 1 : 0), // one branch if mc labels enabled ""}, - BranchDefinition{InputSpec{"labelsVerticesContributors", "ITS", "VERTICESMCTRCONT", 0}, - "ITSVertexMCTruthCont", - (writeContLabels ? 1 : 0), // one branch if - // requested - ""}, - BranchDefinition{InputSpec{"MC2ROframes", "ITS", "ITSTrackMC2ROF", 0}, - "ITSTracksMC2ROF", - (useMC ? 1 : 0), // one branch if mc labels enabled - ""}, BranchDefinition>{InputSpec{"purityVertices", "ITS", "VERTICESMCPUR", 0}, "ITSVertexMCPurity", (useMC ? 1 : 0), // one branch if mc labels enabled ""})(); diff --git a/Detectors/ITSMFT/ITS/workflow/src/TrackWriterWorkflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/TrackWriterWorkflow.cxx index ae2cb3648ec86..ce1d238188ec5 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/TrackWriterWorkflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/TrackWriterWorkflow.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -31,4 +31,4 @@ framework::WorkflowSpec getWorkflow(bool useMC) } // namespace track_writer_workflow } // namespace its -} // namespace o2 \ No newline at end of file +} // namespace o2 diff --git a/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx index 3d07048aaf1e6..bbafc48e931ed 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -15,6 +15,7 @@ #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" #include "Framework/DeviceSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "ITSWorkflow/TrackerSpec.h" #include "ITStracking/Definitions.h" #include "ITStracking/TrackingConfigParam.h" @@ -26,12 +27,13 @@ namespace its { TrackerDPL::TrackerDPL(std::shared_ptr gr, bool isMC, + bool doStag, int trgType, const TrackingMode::Type trMode, const bool overrBeamEst, o2::gpu::gpudatatypes::DeviceType dType) : mGGCCDBRequest(gr), mRecChain{o2::gpu::GPUReconstruction::CreateInstance(dType, true)}, - mITSTrackingInterface{isMC, trgType, overrBeamEst} + mITSTrackingInterface{isMC, doStag, trgType, overrBeamEst} { mITSTrackingInterface.setTrackingMode(trMode); } @@ -83,17 +85,25 @@ void TrackerDPL::endOfStream(EndOfStreamContext& ec) void TrackerDPL::end() { - mITSTrackingInterface.printSummary(); - LOGF(info, "ITS CA-Tracker total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); + if (static bool printOnce{false}; !printOnce) { + printOnce = true; + mITSTrackingInterface.printSummary(); + LOGF(info, "ITS CA-Tracker total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); + } } -DataProcessorSpec getTrackerSpec(bool useMC, bool useGeom, int trgType, TrackingMode::Type trMode, const bool overrBeamEst, o2::gpu::gpudatatypes::DeviceType dType) +DataProcessorSpec getTrackerSpec(bool useMC, bool doStag, bool useGeom, int trgType, TrackingMode::Type trMode, const bool overrBeamEst, o2::gpu::gpudatatypes::DeviceType dType) { + const int mLayers = doStag ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; std::vector inputs; - - inputs.emplace_back("compClusters", "ITS", "COMPCLUSTERS", 0, Lifetime::Timeframe); - inputs.emplace_back("patterns", "ITS", "PATTERNS", 0, Lifetime::Timeframe); - inputs.emplace_back("ROframes", "ITS", "CLUSTERSROF", 0, Lifetime::Timeframe); + for (int iLayer = 0; iLayer < mLayers; ++iLayer) { + inputs.emplace_back("compClusters", "ITS", "COMPCLUSTERS", iLayer, Lifetime::Timeframe); + inputs.emplace_back("patterns", "ITS", "PATTERNS", iLayer, Lifetime::Timeframe); + inputs.emplace_back("ROframes", "ITS", "CLUSTERSROF", iLayer, Lifetime::Timeframe); + if (useMC) { + inputs.emplace_back("itsmclabels", "ITS", "CLUSTERSMCTR", iLayer, Lifetime::Timeframe); + } + } if (trgType == 1) { inputs.emplace_back("phystrig", "ITS", "PHYSTRIG", 0, Lifetime::Timeframe); } else if (trgType == 2) { @@ -123,30 +133,24 @@ DataProcessorSpec getTrackerSpec(bool useMC, bool useGeom, int trgType, Tracking outputs.emplace_back("ITS", "VERTICES", 0, Lifetime::Timeframe); outputs.emplace_back("ITS", "VERTICESROF", 0, Lifetime::Timeframe); outputs.emplace_back("ITS", "IRFRAMES", 0, Lifetime::Timeframe); - if (useMC) { - inputs.emplace_back("itsmclabels", "ITS", "CLUSTERSMCTR", 0, Lifetime::Timeframe); - inputs.emplace_back("ITSMC2ROframes", "ITS", "CLUSTERSMC2ROF", 0, Lifetime::Timeframe); outputs.emplace_back("ITS", "VERTICESMCTR", 0, Lifetime::Timeframe); outputs.emplace_back("ITS", "VERTICESMCPUR", 0, Lifetime::Timeframe); outputs.emplace_back("ITS", "TRACKSMCTR", 0, Lifetime::Timeframe); - outputs.emplace_back("ITS", "ITSTrackMC2ROF", 0, Lifetime::Timeframe); - if (VertexerParamConfig::Instance().outputContLabels) { - outputs.emplace_back("ITS", "VERTICESMCTRCONT", 0, Lifetime::Timeframe); - } } return DataProcessorSpec{ - "its-tracker", - inputs, - outputs, - AlgorithmSpec{adaptFromTask(ggRequest, - useMC, - trgType, - trMode, - overrBeamEst, - dType)}, - Options{}}; + .name = "its-tracker", + .inputs = inputs, + .outputs = outputs, + .algorithm = AlgorithmSpec{adaptFromTask(ggRequest, + useMC, + doStag, + trgType, + trMode, + overrBeamEst, + dType)}, + .options = Options{}}; } } // namespace its diff --git a/Detectors/ITSMFT/ITS/workflow/src/its-cluster-reader-workflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/its-cluster-reader-workflow.cxx index da843526f9296..cbbb4bea09f4b 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/its-cluster-reader-workflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/its-cluster-reader-workflow.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "Framework/ConfigParamSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "CommonUtils/ConfigurableParam.h" #include "DetectorsRaw/HBFUtilsInitializer.h" #include "Framework/CallbacksPolicy.h" @@ -47,6 +48,7 @@ void customize(std::vector& workflowOptions) VariantType::String, "", {"Semicolon separated key=value strings"}}); + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(workflowOptions); o2::raw::HBFUtilsInitializer::addConfigOption(workflowOptions); } @@ -60,8 +62,9 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cc) auto withTriggers = !cc.options().get("suppress-triggers-output"); auto withMC = cc.options().get("with-mc"); auto withPatterns = !cc.options().get("without-patterns"); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(cc); - specs.emplace_back(o2::itsmft::getITSClusterReaderSpec(withMC, withPatterns, withTriggers)); + specs.emplace_back(o2::itsmft::getITSClusterReaderSpec(withMC, doStag, withPatterns, withTriggers)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit o2::raw::HBFUtilsInitializer hbfIni(cc, specs); diff --git a/Detectors/ITSMFT/ITS/workflow/src/its-cluster-writer-workflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/its-cluster-writer-workflow.cxx index ad3d8eea6e636..6c46f6b038571 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/its-cluster-writer-workflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/its-cluster-writer-workflow.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "ITSWorkflow/ClusterWriterWorkflow.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "Framework/ConfigParamSpec.h" #include "Framework/CompletionPolicyHelpers.h" @@ -29,13 +30,22 @@ void customize(std::vector& workflowOptions) o2::framework::VariantType::Bool, false, {"disable MC propagation even if available"}}); + workflowOptions.push_back( + ConfigParamSpec{ + "cluster-rof-branch-only", + o2::framework::VariantType::Bool, + false, + {"writer will store only ClustersROF brunch"}}); + + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(workflowOptions); } #include "Framework/runDataProcessing.h" -#include "Framework/Logger.h" WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { auto useMC = !configcontext.options().get("disable-mc"); - return std::move(o2::its::cluster_writer_workflow::getWorkflow(useMC)); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); + auto clrofOnly = configcontext.options().get("cluster-rof-branch-only"); + return std::move(o2::its::cluster_writer_workflow::getWorkflow(useMC, doStag, clrofOnly)); } diff --git a/Detectors/ITSMFT/ITS/workflow/src/its-reco-workflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/its-reco-workflow.cxx index 8080883888d40..f1d60b8ac2c9b 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/its-reco-workflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/its-reco-workflow.cxx @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "ITSWorkflow/RecoWorkflow.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "CommonUtils/ConfigurableParam.h" #include "ITStracking/Configuration.h" #include "DetectorsRaw/HBFUtilsInitializer.h" @@ -49,7 +50,9 @@ void customize(std::vector& workflowOptions) {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, {"use-full-geometry", o2::framework::VariantType::Bool, false, {"use full geometry instead of the light-weight ITS part"}}, {"use-gpu-workflow", o2::framework::VariantType::Bool, false, {"use GPU workflow (default: false)"}}, - {"gpu-device", o2::framework::VariantType::Int, 1, {"use gpu device: CPU=1,CUDA=2,HIP=3 (default: CPU)"}}}; + {"gpu-device", o2::framework::VariantType::Int, 1, {"use gpu device: CPU=1,CUDA=2,HIP=3 (default: CPU)"}}, + {"cluster-rof-branch-only", o2::framework::VariantType::Bool, false, {"writer will store only ClustersROF brunch"}}}; + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -72,6 +75,8 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto extClusters = configcontext.options().get("clusters-from-upstream"); auto disableRootOutput = configcontext.options().get("disable-root-output"); auto useGeom = configcontext.options().get("use-full-geometry"); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(configcontext); + auto clrofOnly = configcontext.options().get("cluster-rof-branch-only"); if (configcontext.options().get("disable-tracking")) { trmode = "off"; } @@ -87,16 +92,19 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) LOG(fatal) << "Unknown trigger type requested for events prescaling: " << selTrig; } } - auto wf = o2::its::reco_workflow::getWorkflow(useMC, - o2::its::TrackingMode::fromString(trmode), - beamPosOVerride, - extDigits, - extClusters, - disableRootOutput, - useGeom, - trType, - useGpuWF, - gpuDevice); + auto wf = o2::its::reco_workflow::getWorkflow( + useMC, + doStag, + o2::its::TrackingMode::fromString(trmode), + beamPosOVerride, + extDigits, + extClusters, + clrofOnly, + disableRootOutput, + useGeom, + trType, + useGpuWF, + gpuDevice); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit o2::raw::HBFUtilsInitializer hbfIni(configcontext, wf); diff --git a/Detectors/ITSMFT/ITS/workflow/src/its-track-writer-workflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/its-track-writer-workflow.cxx index d06ab366ef54c..ebd10ab3b16ce 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/its-track-writer-workflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/its-track-writer-workflow.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -27,7 +27,6 @@ void customize(std::vector& workflowOptions) } #include "Framework/runDataProcessing.h" -#include "Framework/Logger.h" WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { diff --git a/Detectors/ITSMFT/MFT/calibration/src/NoiseCalibratorSpec.cxx b/Detectors/ITSMFT/MFT/calibration/src/NoiseCalibratorSpec.cxx index 86107106dc2ba..e55e822847177 100644 --- a/Detectors/ITSMFT/MFT/calibration/src/NoiseCalibratorSpec.cxx +++ b/Detectors/ITSMFT/MFT/calibration/src/NoiseCalibratorSpec.cxx @@ -18,7 +18,7 @@ #include "DataFormatsITSMFT/Digit.h" #include "DataFormatsITSMFT/CompCluster.h" #include "DataFormatsITSMFT/ROFRecord.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "ITSMFTReconstruction/ClustererParam.h" #include diff --git a/Detectors/ITSMFT/MFT/condition/include/MFTCondition/DCSConfigReader.h b/Detectors/ITSMFT/MFT/condition/include/MFTCondition/DCSConfigReader.h index efae3104279e1..110465bb92757 100644 --- a/Detectors/ITSMFT/MFT/condition/include/MFTCondition/DCSConfigReader.h +++ b/Detectors/ITSMFT/MFT/condition/include/MFTCondition/DCSConfigReader.h @@ -14,7 +14,7 @@ #include "Rtypes.h" #include "DataFormatsITSMFT/NoiseMap.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "MFTCondition/DCSConfigInfo.h" #include "MFTCondition/DCSConfigUtils.h" #include diff --git a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/RecoWorkflow.h b/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/RecoWorkflow.h index 0e0b8af1da70a..73ffc744f915b 100644 --- a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/RecoWorkflow.h +++ b/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/RecoWorkflow.h @@ -25,9 +25,11 @@ namespace reco_workflow { framework::WorkflowSpec getWorkflow( bool useMC, + bool doStag, bool useGeom, bool upstreamDigits, bool upstreamClusters, + bool clrofOnly, bool disableRootOutput, bool runAssessment, bool processGen, diff --git a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/TrackerSpec.h b/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/TrackerSpec.h index 4274710b23867..8bd290caf5a41 100644 --- a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/TrackerSpec.h +++ b/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/TrackerSpec.h @@ -16,7 +16,7 @@ #include "MFTTracking/Tracker.h" #include "DetectorsBase/GRPGeomHelper.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "Framework/DataProcessorSpec.h" #include "MFTTracking/TrackCA.h" diff --git a/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx b/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx index 5d85c0ef81670..178c1dd50f4df 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx @@ -32,9 +32,11 @@ namespace reco_workflow framework::WorkflowSpec getWorkflow( bool useMC, + bool doStag, bool useGeom, bool upstreamDigits, bool upstreamClusters, + bool clrofOnly, bool disableRootOutput, bool runAssessment, bool processGen, @@ -45,17 +47,17 @@ framework::WorkflowSpec getWorkflow( framework::WorkflowSpec specs; if (!(upstreamDigits || upstreamClusters)) { - specs.emplace_back(o2::itsmft::getMFTDigitReaderSpec(useMC, false, true, "mftdigits.root")); + specs.emplace_back(o2::itsmft::getMFTDigitReaderSpec(useMC, doStag, false, true, "mftdigits.root")); auto& trackingParam = MFTTrackingParam::Instance(); if (trackingParam.irFramesOnly) { specs.emplace_back(o2::globaltracking::getIRFrameReaderSpec("ITS", 0, "its-irframe-reader", "o2_its_irframe.root")); } } if (!upstreamClusters) { - specs.emplace_back(o2::itsmft::getMFTClustererSpec(useMC)); + specs.emplace_back(o2::itsmft::getMFTClustererSpec(useMC, doStag)); } - if (!disableRootOutput) { - specs.emplace_back(o2::itsmft::getMFTClusterWriterSpec(useMC)); + if (!disableRootOutput || clrofOnly) { + specs.emplace_back(o2::itsmft::getMFTClusterWriterSpec(useMC, doStag, clrofOnly)); } if (runTracking) { diff --git a/Detectors/ITSMFT/MFT/workflow/src/TrackWriterSpec.cxx b/Detectors/ITSMFT/MFT/workflow/src/TrackWriterSpec.cxx index d9c132c97abdf..f8a848f6fde32 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/TrackWriterSpec.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/TrackWriterSpec.cxx @@ -24,7 +24,6 @@ using namespace o2::framework; using LabelsType = std::vector; -using ROFRecLblT = std::vector; namespace o2 { @@ -44,7 +43,7 @@ DataProcessorSpec getTrackWriterSpec(bool useMC) *tracksSize = tracks.size(); }; auto logger = [tracksSize](std::vector const& rofs) { - LOG(debug) << "MFTTrackWriter pulled " << *tracksSize << " tracks, in " << rofs.size() << " RO frames"; + LOG(info) << "MFTTrackWriter pulled " << *tracksSize << " tracks, in " << rofs.size() << " RO frames"; }; return MakeRootTreeWriterSpec("mft-track-writer", "mfttracks.root", @@ -54,15 +53,11 @@ DataProcessorSpec getTrackWriterSpec(bool useMC) tracksSizeGetter}, BranchDefinition>{InputSpec{"trackClIdx", "MFT", "TRACKCLSID", 0}, "MFTTrackClusIdx"}, - BranchDefinition{InputSpec{"labels", "MFT", "TRACKSMCTR", 0}, - "MFTTrackMCTruth", - (useMC ? 1 : 0), // one branch if mc labels enabled - ""}, BranchDefinition>{InputSpec{"ROframes", "MFT", "MFTTrackROF", 0}, "MFTTracksROF", logger}, - BranchDefinition{InputSpec{"MC2ROframes", "MFT", "TRACKSMC2ROF", 0}, - "MFTTracksMC2ROF", + BranchDefinition{InputSpec{"labels", "MFT", "TRACKSMCTR", 0}, + "MFTTrackMCTruth", (useMC ? 1 : 0), // one branch if mc labels enabled ""})(); } diff --git a/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx b/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx index 3e726fe37c38c..6ceb04b3c4df6 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx @@ -45,7 +45,7 @@ namespace o2 { namespace mft { -//#define _TIMING_ +// #define _TIMING_ void TrackerDPL::init(InitContext& ic) { @@ -98,12 +98,6 @@ void TrackerDPL::run(ProcessingContext& pc) } const dataformats::MCTruthContainer* labels = mUseMC ? pc.inputs().get*>("labels").release() : nullptr; - gsl::span mc2rofs; - if (mUseMC) { - // get the array as read-only span, a snapshot of the object is sent forward - mc2rofs = pc.inputs().get>("MC2ROframes"); - LOG(info) << labels->getIndexedSize() << " MC label objects , in " << mc2rofs.size() << " MC events"; - } auto& allClusIdx = pc.outputs().make>(Output{"MFT", "TRACKCLSID", 0}); std::vector trackLabels; @@ -261,7 +255,7 @@ void TrackerDPL::run(ProcessingContext& pc) rof->setFirstEntry(firstROFTrackEntry); rof->setNEntries(ntracksROF); - *rof++; + rof++; roFrameId++; } } @@ -319,17 +313,16 @@ void TrackerDPL::run(ProcessingContext& pc) copyTracks(tracksL, allTracksMFT, allClusIdx); rof->setFirstEntry(firstROFTrackEntry); rof->setNEntries(ntracksROF); - *rof++; + rof++; roFrameId++; } } } - LOG(info) << "MFTTracker pushed " << allTracksMFT.size() << " tracks"; + LOG(info) << "MFTTracker pushed " << allTracksMFT.size() << " tracks in " << nROFs << " rofs"; if (mUseMC) { pc.outputs().snapshot(Output{"MFT", "TRACKSMCTR", 0}, allTrackLabels); - pc.outputs().snapshot(Output{"MFT", "TRACKSMC2ROF", 0}, mc2rofs); } static bool first = true; @@ -466,9 +459,7 @@ DataProcessorSpec getTrackerSpec(bool useMC, bool useGeom, int nThreads) if (useMC) { inputs.emplace_back("labels", "MFT", "CLUSTERSMCTR", 0, Lifetime::Timeframe); - inputs.emplace_back("MC2ROframes", "MFT", "CLUSTERSMC2ROF", 0, Lifetime::Timeframe); outputs.emplace_back("MFT", "TRACKSMCTR", 0, Lifetime::Timeframe); - outputs.emplace_back("MFT", "TRACKSMC2ROF", 0, Lifetime::Timeframe); } return DataProcessorSpec{ diff --git a/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-reader-workflow.cxx b/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-reader-workflow.cxx index 9907705fb1e7c..eaa525345fd9f 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-reader-workflow.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-reader-workflow.cxx @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "Framework/ConfigParamSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "DetectorsRaw/HBFUtilsInitializer.h" #include "Framework/CallbacksPolicy.h" @@ -41,6 +42,7 @@ void customize(std::vector& workflowOptions) false, {"do not propagate pixel patterns"}}); workflowOptions.push_back(ConfigParamSpec{"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}); + o2::itsmft::DPLAlpideParamInitializer::addMFTConfigOption(workflowOptions); o2::raw::HBFUtilsInitializer::addConfigOption(workflowOptions); } @@ -53,7 +55,8 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cc) auto withTriggers = !cc.options().get("suppress-triggers-output"); auto withMC = cc.options().get("with-mc"); auto withPatterns = !cc.options().get("without-patterns"); - specs.emplace_back(o2::itsmft::getMFTClusterReaderSpec(withMC, withPatterns, withTriggers)); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(cc); + specs.emplace_back(o2::itsmft::getMFTClusterReaderSpec(withMC, doStag, withPatterns, withTriggers)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit o2::raw::HBFUtilsInitializer hbfIni(cc, specs); diff --git a/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-writer-workflow.cxx b/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-writer-workflow.cxx index b656970693808..99aad4d8c57f4 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-writer-workflow.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/mft-cluster-writer-workflow.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "ITSMFTWorkflow/ClusterWriterSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "Framework/ConfigParamSpec.h" #include "Framework/CompletionPolicyHelpers.h" @@ -23,17 +24,19 @@ void customize(std::vector& policies) void customize(std::vector& workflowOptions) { - workflowOptions.push_back( - ConfigParamSpec{"disable-mc", o2::framework::VariantType::Bool, false, {"disable MC propagation even if available"}}); + workflowOptions.push_back(ConfigParamSpec{"disable-mc", o2::framework::VariantType::Bool, false, {"disable MC propagation even if available"}}); + workflowOptions.push_back(ConfigParamSpec{"cluster-rof-branch-only", o2::framework::VariantType::Bool, false, {"writer will store only ClustersROF brunch"}}); + o2::itsmft::DPLAlpideParamInitializer::addMFTConfigOption(workflowOptions); } #include "Framework/runDataProcessing.h" -#include "Framework/Logger.h" WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { auto useMC = !configcontext.options().get("disable-mc"); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(configcontext); + auto clrofOnly = configcontext.options().get("cluster-rof-branch-only"); WorkflowSpec specs; - specs.emplace_back(o2::itsmft::getMFTClusterWriterSpec(useMC)); + specs.emplace_back(o2::itsmft::getMFTClusterWriterSpec(useMC, doStag, clrofOnly)); return specs; } diff --git a/Detectors/ITSMFT/MFT/workflow/src/mft-reco-workflow.cxx b/Detectors/ITSMFT/MFT/workflow/src/mft-reco-workflow.cxx index 19e41ed984f11..494d36cc609ec 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/mft-reco-workflow.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/mft-reco-workflow.cxx @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "MFTWorkflow/RecoWorkflow.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "CommonUtils/ConfigurableParam.h" #include "DetectorsRaw/HBFUtilsInitializer.h" #include "Framework/CallbacksPolicy.h" @@ -43,8 +44,10 @@ void customize(std::vector& workflowOptions) {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, {"nThreads", VariantType::Int, 1, {"Number of threads"}}, {"use-full-geometry", o2::framework::VariantType::Bool, false, {"use full geometry instead of the light-weight MFT part"}}, - {"run-tracks2records", o2::framework::VariantType::Bool, false, {"run MFT alignment tracks to records workflow"}}}; + {"run-tracks2records", o2::framework::VariantType::Bool, false, {"run MFT alignment tracks to records workflow"}}, + {"cluster-rof-branch-only", o2::framework::VariantType::Bool, false, {"writer will store only ClustersROF brunch"}}}; o2::raw::HBFUtilsInitializer::addConfigOption(options); + o2::itsmft::DPLAlpideParamInitializer::addMFTConfigOption(options); std::swap(workflowOptions, options); } @@ -67,12 +70,16 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto nThreads = configcontext.options().get("nThreads"); auto runTracks2Records = configcontext.options().get("run-tracks2records"); auto useGeom = configcontext.options().get("use-full-geometry"); + auto doStag = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(configcontext); + auto clrofOnly = configcontext.options().get("cluster-rof-branch-only"); auto wf = o2::mft::reco_workflow::getWorkflow( useMC, + doStag, useGeom, extDigits, extClusters, + clrofOnly, disableRootOutput, runAssessment, processGen, diff --git a/Detectors/ITSMFT/common/base/CMakeLists.txt b/Detectors/ITSMFT/common/base/CMakeLists.txt index a3e0718d64a6b..43d60f6d2b11d 100644 --- a/Detectors/ITSMFT/common/base/CMakeLists.txt +++ b/Detectors/ITSMFT/common/base/CMakeLists.txt @@ -11,12 +11,10 @@ o2_add_library(ITSMFTBase SOURCES src/SegmentationAlpide.cxx - src/GeometryTGeo.cxx src/DPLAlpideParam.cxx PUBLIC_LINK_LIBRARIES O2::MathUtils O2::DetectorsCommonDataFormats O2::SimConfig) o2_target_root_dictionary(ITSMFTBase HEADERS include/ITSMFTBase/SegmentationAlpide.h - include/ITSMFTBase/GeometryTGeo.h - include/ITSMFTBase/DPLAlpideParam.h) + include/ITSMFTBase/GeometryTGeo.h) diff --git a/Detectors/ITSMFT/common/base/src/ITSMFTBaseLinkDef.h b/Detectors/ITSMFT/common/base/src/ITSMFTBaseLinkDef.h index 6202f372cf2d3..9296c21e81cae 100644 --- a/Detectors/ITSMFT/common/base/src/ITSMFTBaseLinkDef.h +++ b/Detectors/ITSMFT/common/base/src/ITSMFTBaseLinkDef.h @@ -17,11 +17,6 @@ #pragma link C++ class o2::itsmft::SegmentationAlpide + ; -#pragma link C++ class o2::itsmft::DPLAlpideParam < o2::detectors::DetID::ITS> + ; -#pragma link C++ class o2::itsmft::DPLAlpideParam < o2::detectors::DetID::MFT> + ; -#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::itsmft::DPLAlpideParam < o2::detectors::DetID::ITS>> + ; -#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::itsmft::DPLAlpideParam < o2::detectors::DetID::MFT>> + ; - #pragma link C++ class o2::itsmft::GeometryTGeo; #endif diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/CTFCoder.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/CTFCoder.h index 7e266052efb3c..76ac8878562de 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/CTFCoder.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/CTFCoder.h @@ -27,6 +27,7 @@ #include "ITSMFTReconstruction/LookUp.h" #include "ITSMFTReconstruction/PixelData.h" #include "ITSMFTReconstruction/Clusterer.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsBase/CTFCoderBase.h" @@ -39,19 +40,22 @@ namespace o2 namespace itsmft { +template class CTFCoder final : public o2::ctf::CTFCoderBase { public: + static constexpr o2::detectors::DetID ID{N == o2::detectors::DetID::ITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT}; + using PMatrix = std::array, ClusterPattern::MaxColSpan + 2>; using RowColBuff = std::vector; - CTFCoder(o2::ctf::CTFCoderBase::OpType op, o2::detectors::DetID det, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), det, 1.f, ctfdictOpt) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, bool doStag, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), ID, 1.f, ctfdictOpt), mDoStaggering(doStag) {} ~CTFCoder() final = default; /// entropy-encode clusters to buffer with CTF template o2::ctf::CTFIOSize encode(VEC& buff, const gsl::span& rofRecVec, const gsl::span& cclusVec, - const gsl::span& pattVec, const LookUp& clPattLookup, int strobeLength); + const gsl::span& pattVec, const LookUp& clPattLookup, int layer); /// entropy decode clusters from buffer with CTF template @@ -79,16 +83,21 @@ class CTFCoder final : public o2::ctf::CTFCoderBase template void decompress(const CompressedClusters& compCl, VROF& rofRecVec, VDIG& digVec, const NoiseMap* noiseMap, const LookUp& clPattLookup); - void appendToTree(TTree& tree, CTF& ec); - void readFromTree(TTree& tree, int entry, std::vector& rofRecVec, std::vector& cclusVec, std::vector& pattVec, const NoiseMap* noiseMap, const LookUp& clPattLookup); + void appendToTree(TTree& tree, CTF& ec, int id = -1); + void readFromTree(TTree& tree, int entry, int id, std::vector& rofRecVec, std::vector& cclusVec, std::vector& pattVec, const NoiseMap* noiseMap, const LookUp& clPattLookup); + + bool mDoStaggering{false}; }; /// entropy-encode clusters to buffer with CTF +template template -o2::ctf::CTFIOSize CTFCoder::encode(VEC& buff, const gsl::span& rofRecVec, const gsl::span& cclusVec, - const gsl::span& pattVec, const LookUp& clPattLookup, int strobeLength) +o2::ctf::CTFIOSize CTFCoder::encode(VEC& buff, const gsl::span& rofRecVec, const gsl::span& cclusVec, + const gsl::span& pattVec, const LookUp& clPattLookup, int layer) { using MD = o2::ctf::Metadata::OptStore; + const auto& par = DPLAlpideParam::Instance(); + int strobeLength = mDoStaggering ? par.roFrameLayerLengthInBC[layer] : par.roFrameLengthInBC; // what to do which each field: see o2::ctd::Metadata explanation constexpr MD optField[CTF::getNBlocks()] = { MD::EENCODE_OR_PACK, // BLCfirstChipROF @@ -104,6 +113,8 @@ o2::ctf::CTFIOSize CTFCoder::encode(VEC& buff, const gsl::span& }; CompressedClusters compCl; compress(compCl, rofRecVec, cclusVec, pattVec, clPattLookup, strobeLength); + compCl.header.maxStreams = mDoStaggering ? par.getNLayers() : 1; + compCl.header.streamID = mDoStaggering ? layer : 0; // book output size with some margin auto szIni = estimateCompressedSize(compCl); buff.resize(szIni); @@ -136,19 +147,26 @@ o2::ctf::CTFIOSize CTFCoder::encode(VEC& buff, const gsl::span& } /// decode entropy-encoded clusters to standard compact clusters +template template -o2::ctf::CTFIOSize CTFCoder::decode(const CTF::base& ec, VROF& rofRecVec, VCLUS& cclusVec, VPAT& pattVec, const NoiseMap* noiseMap, const LookUp& clPattLookup) +o2::ctf::CTFIOSize CTFCoder::decode(const CTF::base& ec, VROF& rofRecVec, VCLUS& cclusVec, VPAT& pattVec, const NoiseMap* noiseMap, const LookUp& clPattLookup) { o2::ctf::CTFIOSize iosize; auto compCl = decodeCompressedClusters(ec, iosize); + const auto& par = DPLAlpideParam::Instance(); + uint32_t nLayers = mDoStaggering ? par.getNLayers() : 1; + if (compCl.header.maxStreams != nLayers) { + throw std::runtime_error(fmt::format("header maxStreams={} is not the same as NStreams={} in {}staggered mode", compCl.header.maxStreams, nLayers, mDoStaggering ? "" : "non-")); + } decompress(compCl, rofRecVec, cclusVec, pattVec, noiseMap, clPattLookup); iosize.rawIn = rofRecVec.size() * sizeof(ROFRecord) + cclusVec.size() * sizeof(CompClusterExt) + pattVec.size() * sizeof(unsigned char); return iosize; } /// decode entropy-encoded clusters to digits +template template -o2::ctf::CTFIOSize CTFCoder::decode(const CTF::base& ec, VROF& rofRecVec, VDIG& digVec, const NoiseMap* noiseMap, const LookUp& clPattLookup) +o2::ctf::CTFIOSize CTFCoder::decode(const CTF::base& ec, VROF& rofRecVec, VDIG& digVec, const NoiseMap* noiseMap, const LookUp& clPattLookup) { o2::ctf::CTFIOSize iosize; auto compCl = decodeCompressedClusters(ec, iosize); @@ -158,8 +176,9 @@ o2::ctf::CTFIOSize CTFCoder::decode(const CTF::base& ec, VROF& rofRecVec, VDIG& } /// decompress compressed clusters to standard compact clusters +template template -void CTFCoder::decompress(const CompressedClusters& compCl, VROF& rofRecVec, VCLUS& cclusVec, VPAT& pattVec, const NoiseMap* noiseMap, const LookUp& clPattLookup) +void CTFCoder::decompress(const CompressedClusters& compCl, VROF& rofRecVec, VCLUS& cclusVec, VPAT& pattVec, const NoiseMap* noiseMap, const LookUp& clPattLookup) { PMatrix pmat{}; RowColBuff firedPixBuff{}, maskedPixBuff{}; @@ -337,14 +356,17 @@ void CTFCoder::decompress(const CompressedClusters& compCl, VROF& rofRecVec, VCL assert(chipCount == compCl.header.nChips); if (clCount != compCl.header.nClusters) { - LOG(error) << "expected " << compCl.header.nClusters << " but counted " << clCount << " in ROFRecords"; - throw std::runtime_error("mismatch between expected and counter number of clusters"); + LOGP(error, "expected {} but counted {} clusters in {} ROFRecords", compCl.header.nClusters, clCount, compCl.header.nROFs); + if (clCount > compCl.header.nClusters) { + throw std::runtime_error("mismatch between expected and counter number of clusters"); + } } } /// decompress compressed clusters to digits +template template -void CTFCoder::decompress(const CompressedClusters& compCl, VROF& rofRecVec, VDIG& digVec, const NoiseMap* noiseMap, const LookUp& clPattLookup) +void CTFCoder::decompress(const CompressedClusters& compCl, VROF& rofRecVec, VDIG& digVec, const NoiseMap* noiseMap, const LookUp& clPattLookup) { rofRecVec.resize(compCl.header.nROFs); digVec.reserve(compCl.header.nClusters * 2); @@ -436,8 +458,10 @@ void CTFCoder::decompress(const CompressedClusters& compCl, VROF& rofRecVec, VDI assert(chipCount == compCl.header.nChips); if (clCount != compCl.header.nClusters) { - LOG(error) << "expected " << compCl.header.nClusters << " but counted " << clCount << " in ROFRecords"; - throw std::runtime_error("mismatch between expected and counter number of clusters"); + LOGP(error, "expected {} but counted {} clusters in {} ROFRecords", compCl.header.nClusters, clCount, compCl.header.nROFs); + if (clCount > compCl.header.nClusters) { + throw std::runtime_error("mismatch between expected and counter number of clusters"); + } } } diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingITS.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingITS.h index 45668ca507280..6110a8492d416 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingITS.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingITS.h @@ -310,6 +310,9 @@ class ChipMappingITS std::vector getOverlapsInfo() const; + ///< Collect all FEEIDs for one layer (lr>=0) or all (lr==-1) + std::vector getLayer2FEEIDs(int lr); + // sub-barrel types, their number, N layers, Max N GBT Links per RU static constexpr int IB = 0, MB = 1, OB = 2, NSubB = 3, NLayers = 7, NLinks = 3; @@ -395,7 +398,7 @@ class ChipMappingITS std::vector mCablePos[NSubB]; ///< table of cables positions in the ActiveLanes mask for each RU type (sequential numbering) std::vector mCableHWFirstChip[NSubB]; ///< 1st chip of module (relative to the 1st chip of the stave) served by each cable - std::array mCablesOnStaveSB = {0}; ///< pattern of cables per stave of sub-barrel + std::array mCablesOnStaveSB = {0}; ///< pattern of cables per stave of sub-barrel std::array, MaxHWCableID[MB] + 1> HWCableHWChip2ChipOnRU_MB; // mapping from HW cable ID / HW chip ID to Chip on RU, 255 means NA std::array, MaxHWCableID[OB] + 1> HWCableHWChip2ChipOnRU_OB; // mapping from HW cable ID / HW chip ID to Chip on RU, 255 means NA diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingMFT.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingMFT.h index 3fa94c2628f3a..63d37a25ffbc9 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingMFT.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ChipMappingMFT.h @@ -73,16 +73,15 @@ class ChipMappingMFT ///< total number of RUs static constexpr Int_t getNRUs() { return NRUs; } - ///< get FEEId of the RU (software id of the RU), read via given link + ///< get software id of the RU, from first 8 bits of FEEID (HW id of RU) uint8_t FEEId2RUSW(uint16_t hw) const { return mFEEId2RUSW[hw & 0xff]; } - ///< get HW id of the RU (software id of the RU) + ///< get FEEID, from software id of the RU and link number uint16_t RUSW2FEEId(uint16_t sw, uint16_t linkID = 0) const { return ((linkID << 8) + mRUInfo[sw].idHW); } ///< compose FEEid for given stave (ru) relative to layer and link, see documentation in the constructor uint16_t composeFEEId(uint16_t layer, uint16_t ruOnLayer, uint16_t link) const { - // only one link is used // ruOnLayer is 0, 1, 2, 3 for half = 0 // 4, 5, 6, 7 1 auto dhalf = std::div(ruOnLayer, 4); @@ -114,7 +113,7 @@ class ChipMappingMFT face = (feeID >> 2) & 0x1; } - ///< get info on sw RU + ///< get info on sw RU corresponding to given FEEID const RUInfo* getRUInfoFEEId(Int_t feeID) const { return &mRUInfo[FEEId2RUSW(feeID)]; } ///< get number of chips served by single cable on given RU type @@ -123,13 +122,13 @@ class ChipMappingMFT return ((0x1 << 7) + (cableHW & 0x1f)); } - ///< convert HW cable ID to its position on the ActiveLanes word in the GBT.header for given RU type + ///< convert HW cable ID to its position on the ActiveLanes word in the GBT.header for given RU type (note: this position is equal to the HW cable ID) uint8_t cableHW2Pos(uint8_t ruType, uint8_t hwid) const { return mCableHW2Pos[ruType][hwid]; } ///< convert HW cable ID to SW ID for give RU type uint8_t cableHW2SW(uint8_t ruType, uint8_t hwid) const { return hwid < mCableHW2SW[ruType].size() ? mCableHW2SW[ruType][hwid] : 0xff; } - ///< convert cable iterator ID to its position on the ActiveLanes word in the GBT.header for given RU type + ///< convert cable iterator ID (i.e. chipOnModule) to its position on the ActiveLanes word in the GBT.header for given RU type (note: this position is equal to the HW cable ID) uint8_t cablePos(uint8_t ruType, uint8_t id) const { return mCablePos[ruType][id]; } ///< get chipID on module from chip global SW ID, cable SW ID and stave (RU) info @@ -139,7 +138,7 @@ class ChipMappingMFT return 0xffff; } - ///< get chip global SW ID from chipID on module, cable SW ID and stave (RU) info + ///< get chip global SW ID from cable HW ID and stave (RU) info (note: chOnModuleHW is unused) uint16_t getGlobalChipID(uint16_t chOnModuleHW, int cableHW, const RUInfo& ruInfo) const { auto chipOnRU = cableHW2SW(ruInfo.ruType, cableHW); @@ -266,6 +265,9 @@ class ChipMappingMFT const auto& getModuleMappingData() const { return ModuleMappingData; } + ///< Collect all FEEIDs for one layer (lr>=0) or all (lr==-1) + std::vector getLayer2FEEIDs(int lr); + void print() const; ///< LayerID of each MFT chip @@ -390,11 +392,11 @@ class ChipMappingMFT private: Int_t invalid() const; - static constexpr Int_t NRUs = NLayers * NZonesPerLayer; + static constexpr Int_t NRUs = NLayers * NZonesPerLayer; // 10 layers * 8 zones per layer static constexpr Int_t NModules = 280; static constexpr Int_t NChipsInfo = 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 16 + 17 + 18 + 19 + 14; static constexpr Int_t NChipsPerCable = 1; - static constexpr Int_t NLinks = 1; + static constexpr Int_t NLinks = 3; static constexpr Int_t NConnectors = 5; static constexpr Int_t NMaxChipsPerLadder = 5; static constexpr Int_t NRUCables = 25; diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/Clusterer.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/Clusterer.h index 0bdbb701a9356..dd3052e2cc5bd 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/Clusterer.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/Clusterer.h @@ -236,6 +236,8 @@ class Clusterer ///< load the dictionary of cluster topologies void loadDictionary(const std::string& fileName) { mPattIdConverter.loadDictionary(fileName); } void setDictionary(const TopologyDictionary* dict) { mPattIdConverter.setDictionary(dict); } + const TopologyDictionary& getDictionary() const { return mPattIdConverter.getDictionary(); } + auto& getPattIdConverter() const { return mPattIdConverter; } TStopwatch& getTimer() { return mTimer; } // cannot be const TStopwatch& getTimerMerge() { return mTimerMerge; } // cannot be const diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/LookUp.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/LookUp.h index 3537a1f408886..4f84a838efc70 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/LookUp.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/LookUp.h @@ -43,7 +43,7 @@ class LookUp bool isGroup(int id) const { return mDictionary.isGroup(id); } int size() const { return mDictionary.getSize(); } auto getPattern(int id) const { return mDictionary.getPattern(id); } - auto getDictionaty() const { return mDictionary; } + auto& getDictionary() const { return mDictionary; } private: TopologyDictionary mDictionary; diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/PixelReader.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/PixelReader.h index 80ef5ed7abec8..b98abf1d9b2d4 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/PixelReader.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/PixelReader.h @@ -50,11 +50,6 @@ class PixelReader { return nullptr; } - const o2::InteractionRecord& getInteractionRecordHB() const - { - return mInteractionRecordHB; - } - const o2::InteractionRecord& getInteractionRecord() const { return mInteractionRecord; @@ -70,8 +65,7 @@ class PixelReader // protected: // - o2::InteractionRecord mInteractionRecord = {}; // interation record for the trigger - o2::InteractionRecord mInteractionRecordHB = {}; // interation record for the HB + o2::InteractionRecord mInteractionRecord = {}; // interation record for the trigger uint32_t mTrigger = 0; bool mDecodeNextAuto = true; // try to fetch/decode next trigger when getNextChipData does not see any decoded data diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/RawPixelDecoder.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/RawPixelDecoder.h index 3a53253da2b42..b10f60c749f7c 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/RawPixelDecoder.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/RawPixelDecoder.h @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -14,9 +14,11 @@ #ifndef ALICEO2_ITSMFT_RAWPIXELDECODER_H_ #define ALICEO2_ITSMFT_RAWPIXELDECODER_H_ +#include #include #include #include "Framework/Logger.h" +#include "Framework/InputSpec.h" #include "ITSMFTReconstruction/ChipMappingITS.h" #include "ITSMFTReconstruction/ChipMappingMFT.h" #include "DetectorsRaw/HBFUtils.h" @@ -29,7 +31,6 @@ #include "DataFormatsITSMFT/ROFRecord.h" #include "ITSMFTReconstruction/PixelData.h" #include "ITSMFTReconstruction/GBTWord.h" -#include namespace o2 { @@ -91,6 +92,9 @@ class RawPixelDecoder final : public PixelReader void setVerbosity(int v); int getVerbosity() const { return mVerbosity; } + void setInputFilter(std::vector filter) { mInputFilter = std::move(filter); } + const auto& getInputFilter() const noexcept { return mInputFilter; } + void setAlwaysParseTrigger(bool v) { mAlwaysParseTrigger = v; } bool getAlwaysParseTrigger() const { return mAlwaysParseTrigger; } @@ -138,7 +142,7 @@ class RawPixelDecoder final : public PixelReader void reset(); private: - void setupLinks(o2::framework::InputRecord& inputs); + void setupLinks(o2::framework::InputRecord& inputsm); int getRUEntrySW(int ruSW) const { return mRUEntry[ruSW]; } RUDecodeData* getRUDecode(int ruSW) { return &mRUDecodeVec[mRUEntry[ruSW]]; } GBTLink* getGBTLink(int i) { return i < 0 ? nullptr : &mGBTLinks[i]; } @@ -146,6 +150,7 @@ class RawPixelDecoder final : public PixelReader static constexpr uint16_t NORUDECODED = 0xffff; // this must be > than max N RUs + std::vector mInputFilter; // input spec filter std::vector mGBTLinks; // active links pool std::unordered_map mSubsSpec2LinkID; // link subspec to link entry in the pool mapping std::vector mRUDecodeVec; // set of active RUs diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/RawPixelReader.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/RawPixelReader.h index 97716059f12d6..ce6582853788d 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/RawPixelReader.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/RawPixelReader.h @@ -53,8 +53,8 @@ namespace o2 namespace itsmft { -constexpr int MaxGBTPacketBytes = 8 * 1024; // Max size of GBT packet in bytes (8KB) -constexpr int NCRUPagesPerSuperpage = 256; // Expected max number of CRU pages per superpage +constexpr int MaxGBTPacketBytes = 8 * 1024; // Max size of GBT packet in bytes (8KB) +constexpr int NCRUPagesPerSuperpage = 256; // Expected max number of CRU pages per superpage using RDHUtils = o2::raw::RDHUtils; struct RawDecodingStat { @@ -633,7 +633,6 @@ class RawPixelReader : public PixelReader const auto rdh = reinterpret_cast(link->data.getPtr()); mInteractionRecord = RDHUtils::getTriggerIR(rdh); mTrigger = RDHUtils::getTriggerType(rdh); - mInteractionRecordHB = RDHUtils::getHeartBeatIR(rdh); break; } } @@ -674,7 +673,7 @@ class RawPixelReader : public PixelReader } } if (ruDecData.ruInfo->nCables) { // there are cables with data to decode - decodeAlpideData(ruDecData); // decode Alpide data from the compressed RU Data + decodeAlpideData(ruDecData); // decode Alpide data from the compressed RU Data } return res; } diff --git a/Detectors/ITSMFT/common/reconstruction/src/CTFCoder.cxx b/Detectors/ITSMFT/common/reconstruction/src/CTFCoder.cxx index ec0ee9e3f0f24..4a0c83fd0c859 100644 --- a/Detectors/ITSMFT/common/reconstruction/src/CTFCoder.cxx +++ b/Detectors/ITSMFT/common/reconstruction/src/CTFCoder.cxx @@ -14,35 +14,38 @@ /// \brief class for entropy encoding/decoding of ITS/MFT compressmed clusters data #include "ITSMFTReconstruction/CTFCoder.h" -#include "CommonUtils/StringUtils.h" #include -using namespace o2::itsmft; +namespace o2::itsmft +{ ///___________________________________________________________________________________ // Register encoded data in the tree (Fill is not called, will be done by caller) -void CTFCoder::appendToTree(TTree& tree, CTF& ec) +template +void CTFCoder::appendToTree(TTree& tree, CTF& ec, int id) { - ec.appendToTree(tree, mDet.getName()); + ec.appendToTree(tree, id >= 0 ? fmt::format("{}_{}", mDet.getName(), id) : mDet.getName()); } ///___________________________________________________________________________________ // extract and decode data from the tree -void CTFCoder::readFromTree(TTree& tree, int entry, std::vector& rofRecVec, - std::vector& cclusVec, std::vector& pattVec, const NoiseMap* noiseMap, const LookUp& clPattLookup) +template +void CTFCoder::readFromTree(TTree& tree, int entry, int id, std::vector& rofRecVec, + std::vector& cclusVec, std::vector& pattVec, const NoiseMap* noiseMap, const LookUp& clPattLookup) { assert(entry >= 0 && entry < tree.GetEntries()); CTF ec; - ec.readFromTree(tree, mDet.getName(), entry); + ec.readFromTree(tree, id >= 0 ? fmt::format("{}_{}", mDet.getName(), id) : mDet.getName(), entry); decode(ec, rofRecVec, cclusVec, pattVec, noiseMap, clPattLookup); } ///________________________________ -void CTFCoder::compress(CompressedClusters& cc, - const gsl::span& rofRecVec, - const gsl::span& cclusVec, - const gsl::span& pattVec, - const LookUp& clPattLookup, int strobeLength) +template +void CTFCoder::compress(CompressedClusters& cc, + const gsl::span& rofRecVec, + const gsl::span& cclusVec, + const gsl::span& pattVec, + const LookUp& clPattLookup, int strobeLength) { // store in the header the orbit of 1st ROF cc.clear(); @@ -191,11 +194,12 @@ void CTFCoder::compress(CompressedClusters& cc, } ///________________________________ -void CTFCoder::createCoders(const std::vector& bufVec, o2::ctf::CTFCoderBase::OpType op) +template +void CTFCoder::createCoders(const std::vector& bufVec, o2::ctf::CTFCoderBase::OpType op) { const auto ctf = CTF::getImage(bufVec.data()); CompressedClusters cc; // just to get member types -#define MAKECODER(part, slot) createCoder(op, std::get>(ctf.getDictionary(slot, mANSVersion)), int(slot)) +#define MAKECODER(part, slot) createCoder(op, std::get>(ctf.getDictionary(slot, mANSVersion)), int(slot)) // clang-format off MAKECODER(cc.firstChipROF, CTF::BLCfirstChipROF); MAKECODER(cc.bcIncROF, CTF::BLCbcIncROF ); @@ -212,7 +216,8 @@ void CTFCoder::createCoders(const std::vector& bufVec, o2::ctf::CTFCoderBa } ///________________________________ -size_t CTFCoder::estimateCompressedSize(const CompressedClusters& cc) +template +size_t CTFCoder::estimateCompressedSize(const CompressedClusters& cc) { size_t sz = 0; // RS FIXME this is very crude estimate, instead, an empirical values should be used @@ -234,7 +239,8 @@ size_t CTFCoder::estimateCompressedSize(const CompressedClusters& cc) } ///________________________________ -CompressedClusters CTFCoder::decodeCompressedClusters(const CTF::base& ec, o2::ctf::CTFIOSize& iosize) +template +CompressedClusters CTFCoder::decodeCompressedClusters(const CTF::base& ec, o2::ctf::CTFIOSize& iosize) { CompressedClusters cc; cc.header = ec.getHeader(); @@ -256,3 +262,7 @@ CompressedClusters CTFCoder::decodeCompressedClusters(const CTF::base& ec, o2::c // clang-format on return cc; } + +template class CTFCoder; +template class CTFCoder; +} // namespace o2::itsmft diff --git a/Detectors/ITSMFT/common/reconstruction/src/ChipMappingITS.cxx b/Detectors/ITSMFT/common/reconstruction/src/ChipMappingITS.cxx index 7d9733554ef12..f143e4bb23f3d 100644 --- a/Detectors/ITSMFT/common/reconstruction/src/ChipMappingITS.cxx +++ b/Detectors/ITSMFT/common/reconstruction/src/ChipMappingITS.cxx @@ -78,14 +78,14 @@ ChipMappingITS::ChipMappingITS() cInfo.moduleSW = 0; cInfo.chipOnModuleSW = i; cInfo.chipOnModuleHW = i; - cInfo.cableHW = i; //1-to-1 mapping - cInfo.cableHWPos = i; //1-to-1 mapping - cInfo.cableSW = i; //1-to-1 mapping - cInfo.chipOnCable = 0; // every chip is master + cInfo.cableHW = i; // 1-to-1 mapping + cInfo.cableHWPos = i; // 1-to-1 mapping + cInfo.cableSW = i; // 1-to-1 mapping + cInfo.chipOnCable = 0; // every chip is master mCableHW2SW[IB][cInfo.cableHW] = cInfo.cableSW; mCableHW2Pos[IB][cInfo.cableHW] = cInfo.cableHWPos; mCablesOnStaveSB[IB] |= 0x1 << cInfo.cableHWPos; // account in lanes pattern - mCableHWFirstChip[IB][i] = 0; // stave and module are the same + mCableHWFirstChip[IB][i] = 0; // stave and module are the same } // [i][j] gives lane id for lowest(i=0) and highest(i=1) 7 chips of HW module (j+1) (1-4 for ML, 1-7 for OL) @@ -289,3 +289,17 @@ std::vector ChipMappingITS::getOverlapsInfo() const } return v; } + +//_____________________________________________________________________________ +std::vector ChipMappingITS::getLayer2FEEIDs(int lr) +{ + std::vector feeIDs; + for (int ilr = (lr >= 0 ? lr : 0); ilr < (lr >= 0 ? lr + 1 : NLayers); ++ilr) { + for (int ist = 0; ist < NStavesOnLr[ilr]; ++ist) { + for (int lnk = 0; lnk < NLinks; ++lnk) { + feeIDs.push_back(composeFEEId(ilr, ist, lnk)); + } + } + } + return feeIDs; +} diff --git a/Detectors/ITSMFT/common/reconstruction/src/ChipMappingMFT.cxx b/Detectors/ITSMFT/common/reconstruction/src/ChipMappingMFT.cxx index 259df62921c8f..b79c529bef803 100644 --- a/Detectors/ITSMFT/common/reconstruction/src/ChipMappingMFT.cxx +++ b/Detectors/ITSMFT/common/reconstruction/src/ChipMappingMFT.cxx @@ -1624,7 +1624,7 @@ ChipMappingMFT::ChipMappingMFT() { // init chips info - uint32_t maxRUHW = composeFEEId(NLayers - 1, NZonesPerLayer - 1, NLinks - 1); // Max possible FEE ID + uint32_t maxRUHW = composeFEEId(NLayers - 1, NZonesPerLayer - 1, 0); // Max possible RU HW ID (first 8 bits of max FEEID, while link stored in 9th and 10th bit of FEEID) mFEEId2RUSW.resize(maxRUHW + 1, 0xff); int curLayer = -1, curZone = -1, curHalf = -1; @@ -1698,8 +1698,8 @@ ChipMappingMFT::ChipMappingMFT() auto& ruInfo = mRUInfo[ctrRU]; ruInfo.idSW = ctrRU++; - // map FEEIds (RU read out by at most 3 GBT links) to SW ID - ruInfo.idHW = composeFEEId(iLayer, iZone, 0); // FEEId for link 0 + // map RU HW ID (RU read out by at most 3 GBT links) to SW ID + ruInfo.idHW = composeFEEId(iLayer, iZone, 0); // RU HW ID (first 8 bits of FEEID) mFEEId2RUSW[ruInfo.idHW] = ruInfo.idSW; ruInfo.layer = iLayer; ruInfo.ruType = ZoneRUType[iZone % 4][iLayer / 2]; @@ -1753,3 +1753,17 @@ void ChipMappingMFT::print() const ChipMappingData[iChip].chipOnRU); } } + +//_____________________________________________________________________________ +std::vector ChipMappingMFT::getLayer2FEEIDs(int lr) +{ + std::vector feeIDs; + for (int ilr = (lr >= 0 ? lr : 0); ilr < (lr >= 0 ? lr + 1 : NLayers); ++ilr) { + for (int iz = 0; iz < NZonesPerLayer; ++iz) { + for (int lnk = 0; lnk < NLinks; ++lnk) { + feeIDs.push_back(composeFEEId(ilr, iz, lnk)); + } + } + } + return feeIDs; +} diff --git a/Detectors/ITSMFT/common/reconstruction/src/Clusterer.cxx b/Detectors/ITSMFT/common/reconstruction/src/Clusterer.cxx index 3c741321e7780..dcc268a4504a9 100644 --- a/Detectors/ITSMFT/common/reconstruction/src/Clusterer.cxx +++ b/Detectors/ITSMFT/common/reconstruction/src/Clusterer.cxx @@ -479,7 +479,7 @@ void Clusterer::print(bool showsTiming) const } else { LOGP(info, "Clusterizer squashes overflow pixels <= {} in row/col", mMaxRowColDiffToMask); for (size_t i{0}; i < mSquashingLayerDepth.size(); ++i) { - LOGP(info, "\tlay:{} separated by {} BC seeking down to {} neighbour ROFs", i, mMaxBCSeparationToSquashLayer[i], mSquashingLayerDepth[i]); + LOGP(info, "\tClusterizer on layer {} separated by {} BC seeking down to {} neighbour ROFs", i, mMaxBCSeparationToSquashLayer[i], mSquashingLayerDepth[i]); } } LOGP(info, "Clusterizer masks overflow pixels separated by < {} BC and <= {} in row/col", mMaxBCSeparationToMask, mMaxRowColDiffToMask); diff --git a/Detectors/ITSMFT/common/reconstruction/src/GBTLink.cxx b/Detectors/ITSMFT/common/reconstruction/src/GBTLink.cxx index af4c8de5caf39..4d336d9adb1ee 100644 --- a/Detectors/ITSMFT/common/reconstruction/src/GBTLink.cxx +++ b/Detectors/ITSMFT/common/reconstruction/src/GBTLink.cxx @@ -41,7 +41,7 @@ GBTLink::GBTLink(uint16_t _cru, uint16_t _fee, uint8_t _ep, uint8_t _idInCru, ui /// create string describing the link std::string GBTLink::describe() const { - std::string ss = fmt::format("link cruID:{:#06x}/lID{} feeID:{:#06x}", cruID, int(idInCRU), feeID); + std::string ss = fmt::format("link cruID:{:#06x}/lID{:02} feeID:{:#06x}", cruID, int(idInCRU), feeID); if (lanes) { ss += fmt::format(" lanes {}", std::bitset<28>(lanes).to_string()); } diff --git a/Detectors/ITSMFT/common/reconstruction/src/RawPixelDecoder.cxx b/Detectors/ITSMFT/common/reconstruction/src/RawPixelDecoder.cxx index dc61bea9f406e..df877cd38c578 100644 --- a/Detectors/ITSMFT/common/reconstruction/src/RawPixelDecoder.cxx +++ b/Detectors/ITSMFT/common/reconstruction/src/RawPixelDecoder.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -40,7 +40,8 @@ RawPixelDecoder::RawPixelDecoder() mTimerDecode.Stop(); mTimerFetchData.Stop(); mSelfName = o2::utils::Str::concat_string(Mapping::getName(), "Decoder"); - DPLRawParser<>::setCheckIncompleteHBF(false); // Disable incomplete HBF checking, see ErrPacketCounterJump check in GBTLink.cxx + DPLRawParser<>::setCheckIncompleteHBF(false); // Disable incomplete HBF checking, see ErrPacketCounterJump check in GBTLink.cxx + mInputFilter = {InputSpec{"filter", ConcreteDataTypeMatcher{Mapping::getOrigin(), o2::header::gDataDescriptionRawData}}}; // by default take all raw data } ///______________________________________________________________ @@ -102,8 +103,7 @@ int RawPixelDecoder::decodeNextTrigger() } #ifdef WITH_OPENMP -#pragma omp parallel for schedule(dynamic) num_threads(mNThreads) reduction(+ \ - : mNChipsFiredROF, mNPixelsFiredROF) +#pragma omp parallel for schedule(dynamic) num_threads(mNThreads) reduction(+ : mNChipsFiredROF, mNPixelsFiredROF) #endif for (int iru = 0; iru < nru; iru++) { auto& ru = mRUDecodeVec[iru]; @@ -186,6 +186,9 @@ bool RawPixelDecoder::doIRMajorityPoll() if (link.statusInTF == GBTLink::DataSeen) { if (link.status == GBTLink::DataSeen || link.status == GBTLink::CachedDataExist) { mIRPoll[link.ir]++; + if (mVerbosity >= GBTLink::Verbosity::VerboseHeaders) { + LOGP(info, "doIRMajorityPoll: {} contributes to poll {}", link.describe(), link.ir.asString()); + } } else if (link.status == GBTLink::StoppedOnEndOfData || link.status == GBTLink::AbortedOnError) { link.statusInTF = GBTLink::StoppedOnEndOfData; if (mVerbosity >= GBTLink::Verbosity::VerboseHeaders) { @@ -195,6 +198,12 @@ bool RawPixelDecoder::doIRMajorityPoll() } } } + if (mNLinksDone == mNLinksInTF) { + if (mVerbosity >= GBTLink::Verbosity::VerboseHeaders) { + LOGP(info, "doIRMajorityPoll: All {} links registered in TF are done", mNLinksInTF); + } + return false; + } int majIR = -1; for (const auto& entIR : mIRPoll) { if (entIR.second > majIR) { @@ -202,16 +211,14 @@ bool RawPixelDecoder::doIRMajorityPoll() mInteractionRecord = entIR.first; } } - mInteractionRecordHB = mInteractionRecord; if (mInteractionRecord.isDummy()) { if (mVerbosity >= GBTLink::Verbosity::VerboseHeaders) { LOG(info) << "doIRMajorityPoll: did not find any valid IR"; } return false; } - mInteractionRecordHB.bc = 0; if (mVerbosity >= GBTLink::Verbosity::VerboseHeaders) { - LOG(info) << "doIRMajorityPoll: " << mInteractionRecordHB.asString() << " majority = " << majIR << " for " << mNLinksInTF << " links seen, LinksDone = " << mNLinksDone; + LOG(info) << "doIRMajorityPoll: " << mInteractionRecord.asString() << " majority = " << majIR << " for " << mNLinksInTF << " links seen, LinksDone = " << mNLinksDone; } return true; } @@ -228,8 +235,6 @@ void RawPixelDecoder::setupLinks(InputRecord& inputs) auto nLinks = mGBTLinks.size(); auto origin = (mUserDataOrigin == o2::header::gDataOriginInvalid) ? mMAP.getOrigin() : mUserDataOrigin; auto datadesc = (mUserDataDescription == o2::header::gDataDescriptionInvalid) ? o2::header::gDataDescriptionRawData : mUserDataDescription; - std::vector filter{InputSpec{"filter", ConcreteDataTypeMatcher{origin, datadesc}}}; - // if we see requested data type input with 0xDEADBEEF subspec and 0 payload this means that the "delayed message" // mechanism created it in absence of real data from upstream. Processor should send empty output to not block the workflow { @@ -251,28 +256,31 @@ void RawPixelDecoder::setupLinks(InputRecord& inputs) contDeadBeef = 0; // if good data, reset the counter } mROFRampUpStage = false; - DPLRawParser parser(inputs, filter, o2::conf::VerbosityConfig::Instance().rawParserSeverity); + DPLRawParser parser(inputs, mInputFilter, o2::conf::VerbosityConfig::Instance().rawParserSeverity); parser.setMaxFailureMessages(o2::conf::VerbosityConfig::Instance().maxWarnRawParser); static size_t cntParserFailures = 0; parser.setExtFailureCounter(&cntParserFailures); uint32_t currSSpec = 0xffffffff; // dummy starting subspec int linksAdded = 0; + uint16_t lr, dummy; // extraxted info from FEEId for (auto it = parser.begin(); it != parser.end(); ++it) { auto const* dh = it.o2DataHeader(); auto& lnkref = mSubsSpec2LinkID[dh->subSpecification]; const auto& rdh = *reinterpret_cast(it.raw()); // RSTODO this is a hack in absence of generic header getter + const auto feeID = RDHUtils::getFEEID(rdh); + mMAP.expandFEEId(feeID, lr, dummy, dummy); if (lnkref.entry == -1) { // new link needs to be added lnkref.entry = int(mGBTLinks.size()); - auto& lnk = mGBTLinks.emplace_back(RDHUtils::getCRUID(rdh), RDHUtils::getFEEID(rdh), RDHUtils::getEndPointID(rdh), RDHUtils::getLinkID(rdh), lnkref.entry); + auto& lnk = mGBTLinks.emplace_back(RDHUtils::getCRUID(rdh), feeID, RDHUtils::getEndPointID(rdh), RDHUtils::getLinkID(rdh), lnkref.entry); lnk.subSpec = dh->subSpecification; lnk.wordLength = (lnk.expectPadding = (RDHUtils::getDataFormat(rdh) == 0)) ? o2::itsmft::GBTPaddedWordLength : o2::itsmft::GBTWordLength; - getCreateRUDecode(mMAP.FEEId2RUSW(RDHUtils::getFEEID(rdh))); // make sure there is a RU for this link + getCreateRUDecode(mMAP.FEEId2RUSW(feeID)); // make sure there is a RU for this link lnk.verbosity = GBTLink::Verbosity(mVerbosity); lnk.alwaysParseTrigger = mAlwaysParseTrigger; if (mVerbosity >= GBTLink::Verbosity::VerboseHeaders) { - LOG(info) << mSelfName << " registered new link " << lnk.describe() << " RUSW=" << int(mMAP.FEEId2RUSW(lnk.feeID)); + LOG(info) << mSelfName << " registered new " << lnk.describe() << " RUSW=" << int(mMAP.FEEId2RUSW(lnk.feeID)); } linksAdded++; } @@ -330,7 +338,7 @@ void RawPixelDecoder::setupLinks(InputRecord& inputs) mMAP.expandFEEId(link.feeID, lr, ruOnLr, linkInRU); if (newLinkAdded) { if (mVerbosity >= GBTLink::Verbosity::VerboseHeaders) { - LOG(info) << mSelfName << " Attaching " << link.describe() << " to RU#" << int(mMAP.FEEId2RUSW(link.feeID)) << " (stave " << ruOnLr << " of layer " << lr << ')'; + LOGP(info, "{} Attaching {} to RU#{:02} (stave {:02} of layer {})", mSelfName, link.describe(), int(mMAP.FEEId2RUSW(link.feeID)), ruOnLr, lr); } } link.idInRU = linkInRU; diff --git a/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/DigiParams.h b/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/DigiParams.h index fa75a65728675..f4482c651b090 100644 --- a/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/DigiParams.h +++ b/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/DigiParams.h @@ -19,7 +19,7 @@ #include #include #include "ITSMFTSimulation/AlpideSignalTrapezoid.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" //////////////////////////////////////////////////////////// // // diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterReaderSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterReaderSpec.h index 82e3890de7475..9d58b6fde16c1 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterReaderSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterReaderSpec.h @@ -14,8 +14,10 @@ #ifndef O2_ITSMFT_CLUSTERREADER #define O2_ITSMFT_CLUSTERREADER -#include "TFile.h" -#include "TTree.h" +#include + +#include +#include #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" @@ -23,7 +25,7 @@ #include "DataFormatsITSMFT/CompCluster.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITSMFT/ROFRecord.h" #include "DetectorsCommonDataFormats/DetID.h" @@ -38,10 +40,9 @@ class ClusterReader : public Task public: static constexpr o2::detectors::DetID ID{N == o2::detectors::DetID::ITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT}; static constexpr o2::header::DataOrigin Origin{(N == o2::detectors::DetID::ITS) ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; - static constexpr int NLayers{o2::itsmft::DPLAlpideParam::supportsStaggering() ? o2::itsmft::DPLAlpideParam::getNLayers() : 1}; ClusterReader() = delete; - ClusterReader(bool useMC, bool usePatterns = true, bool triggers = true); + ClusterReader(bool useMC = true, bool doStag = false, bool usePatterns = true, bool triggers = true); ~ClusterReader() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -52,18 +53,19 @@ class ClusterReader : public Task void setBranchAddress(const std::string& base, Ptr& addr, int layer); std::string getBranchName(const std::string& base, int index) const; - std::array*, NLayers> mClusROFRec; - std::array*, NLayers> mClusterCompArray; - std::array*, NLayers> mPatternsArray; - std::array*, NLayers> mClusterMCTruth; - std::array*, NLayers> mClusMC2ROFs; + std::vector*> mClusROFRec{nullptr}; + std::vector*> mClusterCompArray{nullptr}; + std::vector*> mPatternsArray{nullptr}; + std::vector*> mClusterMCTruth{nullptr}; std::unique_ptr mFile; std::unique_ptr mTree; - bool mUseMC = true; // use MC truth - bool mUsePatterns = true; // send patterns - bool mTriggerOut = true; // send dummy triggers vector + int mLayers = 1; + bool mUseMC = true; // use MC truth + bool mDoStaggering = false; // read staggered data + bool mUsePatterns = true; // send patterns + bool mTriggerOut = true; // send dummy triggers vector std::string mDetName; std::string mDetNameLC; @@ -73,27 +75,26 @@ class ClusterReader : public Task std::string mClusterPattBranchName = "ClusterPatt"; std::string mClusterCompBranchName = "ClusterComp"; std::string mClustMCTruthBranchName = "ClusterMCTruth"; - std::string mClustMC2ROFBranchName = "ClustersMC2ROF"; }; class ITSClusterReader : public ClusterReader { public: - ITSClusterReader(bool useMC = true, bool usePatterns = true, bool triggerOut = true) - : ClusterReader(useMC, usePatterns, triggerOut) {} + ITSClusterReader(bool useMC = true, bool doStag = false, bool usePatterns = true, bool triggerOut = true) + : ClusterReader(useMC, doStag, usePatterns, triggerOut) {} }; class MFTClusterReader : public ClusterReader { public: - MFTClusterReader(bool useMC = true, bool usePatterns = true, bool triggerOut = true) - : ClusterReader(useMC, usePatterns, triggerOut) {} + MFTClusterReader(bool useMC = true, bool doStag = false, bool usePatterns = true, bool triggerOut = true) + : ClusterReader(useMC, doStag, usePatterns, triggerOut) {} }; /// create a processor spec /// read ITS/MFT cluster data from a root file -framework::DataProcessorSpec getITSClusterReaderSpec(bool useMC = true, bool usePatterns = true, bool useTriggers = true); -framework::DataProcessorSpec getMFTClusterReaderSpec(bool useMC = true, bool usePatterns = true, bool useTriggers = true); +framework::DataProcessorSpec getITSClusterReaderSpec(bool useMC = true, bool doStag = false, bool usePatterns = true, bool useTriggers = true); +framework::DataProcessorSpec getMFTClusterReaderSpec(bool useMC = true, bool doStag = false, bool usePatterns = true, bool useTriggers = true); } // namespace o2::itsmft diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h index 5ae371e7e09c4..e43e5def18482 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h @@ -20,9 +20,9 @@ namespace o2::itsmft { template -framework::DataProcessorSpec getClusterWriterSpec(bool useMC); -framework::DataProcessorSpec getITSClusterWriterSpec(bool useMC); -framework::DataProcessorSpec getMFTClusterWriterSpec(bool useMC); +framework::DataProcessorSpec getClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly = false); +framework::DataProcessorSpec getITSClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly = false); +framework::DataProcessorSpec getMFTClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly = false); } // namespace o2::itsmft diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClustererSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClustererSpec.h index b6ebc282c2a27..5535ecb42d645 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClustererSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClustererSpec.h @@ -18,7 +18,6 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" #include "ITSMFTReconstruction/Clusterer.h" -#include "ITSMFTBase/DPLAlpideParam.h" using namespace o2::framework; @@ -30,10 +29,9 @@ class ClustererDPL : public Task { static constexpr o2::detectors::DetID ID{N == o2::detectors::DetID::ITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT}; static constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; - static constexpr int NLayers{o2::itsmft::DPLAlpideParam::supportsStaggering() ? o2::itsmft::DPLAlpideParam::getNLayers() : 1}; public: - ClustererDPL(std::shared_ptr gr, bool useMC) : mGGCCDBRequest(gr), mUseMC(useMC) {} + ClustererDPL(std::shared_ptr gr, bool useMC, bool doStag); ~ClustererDPL() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -48,12 +46,13 @@ class ClustererDPL : public Task int mNThreads = 1; std::unique_ptr mClusterer = nullptr; std::shared_ptr mGGCCDBRequest; - int mLayers{NLayers}; + bool mDoStaggering = false; + int mLayers = 1; std::vector mFilter; }; -framework::DataProcessorSpec getITSClustererSpec(bool useMC); -framework::DataProcessorSpec getMFTClustererSpec(bool useMC); +framework::DataProcessorSpec getITSClustererSpec(bool useMC, bool doStag); +framework::DataProcessorSpec getMFTClustererSpec(bool useMC, bool doStag); } // namespace o2::itsmft diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/DigitReaderSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/DigitReaderSpec.h index 348ba76468144..2954c27af886e 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/DigitReaderSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/DigitReaderSpec.h @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -14,9 +14,12 @@ #ifndef O2_ITSMFT_DIGITREADER #define O2_ITSMFT_DIGITREADER -#include "TFile.h" -#include "TTree.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include + +#include +#include + +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITSMFT/Digit.h" #include "DataFormatsITSMFT/GBTCalibData.h" #include "DataFormatsITSMFT/ROFRecord.h" @@ -41,11 +44,9 @@ class DigitReader : public Task public: static constexpr o2::detectors::DetID ID{N == o2::detectors::DetID::ITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT}; static constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; - static constexpr int NLayers{o2::itsmft::DPLAlpideParam::getNLayers()}; - static constexpr int RLayers = o2::itsmft::DPLAlpideParam::supportsStaggering() ? NLayers : 1; DigitReader() = delete; - DigitReader(bool useMC, bool useCalib, bool triggerOut); + DigitReader(bool useMC, bool doStag, bool useCalib, bool triggerOut); ~DigitReader() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -56,22 +57,23 @@ class DigitReader : public Task void setBranchAddress(const std::string& base, Ptr& addr, int layer = -1); std::string getBranchName(const std::string& base, int index); - std::array*, NLayers> mDigits; + std::vector*> mDigits{nullptr}; std::vector mCalib, *mCalibPtr = &mCalib; - std::array*, NLayers> mDigROFRec; - std::array*, NLayers> mDigMC2ROFs; - std::array, NLayers> mConstLabels; - std::array mPLabels; + std::vector*> mDigROFRec{nullptr}; + std::vector> mConstLabels{}; + std::vector mPLabels{nullptr}; std::unique_ptr mFile; std::unique_ptr mTree; - bool mUseMC = true; // use MC truth - bool mUseCalib = true; // send calib data - bool mTriggerOut = true; // send dummy triggers vector - bool mUseIRFrames = false; // selected IRFrames modes + bool mUseMC = true; // use MC truth + bool mDoStaggering = false; // read staggered data + bool mUseCalib = true; // send calib data + bool mTriggerOut = true; // send dummy triggers vector + bool mUseIRFrames = false; // selected IRFrames modes int mROFBiasInBC = 0; int mROFLengthInBC = 0; int mNRUs = 0; + int mLayers = 1; std::string mDetName; std::string mDetNameLC; std::string mFileName; @@ -81,27 +83,26 @@ class DigitReader : public Task std::string mCalibBranchName = "Calib"; std::string mDigitMCTruthBranchName = "DigitMCTruth"; - std::string mDigitMC2ROFBranchName = "DigitMC2ROF"; }; class ITSDigitReader : public DigitReader { public: - ITSDigitReader(bool useMC = true, bool useCalib = false, bool useTriggers = true) - : DigitReader(useMC, useCalib, useTriggers) {} + ITSDigitReader(bool useMC = true, bool doStag = false, bool useCalib = false, bool useTriggers = true) + : DigitReader(useMC, doStag, useCalib, useTriggers) {} }; class MFTDigitReader : public DigitReader { public: - MFTDigitReader(bool useMC = true, bool useCalib = false, bool useTriggers = true) - : DigitReader(useMC, useCalib, useTriggers) {} + MFTDigitReader(bool useMC = true, bool doStag = false, bool useCalib = false, bool useTriggers = true) + : DigitReader(useMC, doStag, useCalib, useTriggers) {} }; /// create a processor spec /// read ITS/MFT Digit data from a root file -framework::DataProcessorSpec getITSDigitReaderSpec(bool useMC = true, bool useCalib = false, bool useTriggers = true, std::string defname = "o2_itsdigits.root"); -framework::DataProcessorSpec getMFTDigitReaderSpec(bool useMC = true, bool useCalib = false, bool useTriggers = true, std::string defname = "o2_mftdigits.root"); +framework::DataProcessorSpec getITSDigitReaderSpec(bool useMC = true, bool doStag = false, bool useCalib = false, bool useTriggers = true, std::string defname = "itsdigits.root"); +framework::DataProcessorSpec getMFTDigitReaderSpec(bool useMC = true, bool doStag = false, bool useCalib = false, bool useTriggers = true, std::string defname = "mftdigits.root"); } // namespace itsmft } // namespace o2 diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/DigitWriterSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/DigitWriterSpec.h index 7bef1643ddcbb..6fde609f1ccb5 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/DigitWriterSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/DigitWriterSpec.h @@ -19,8 +19,8 @@ namespace o2 namespace itsmft { -o2::framework::DataProcessorSpec getITSDigitWriterSpec(bool mctruth = true, bool dec = false, bool calib = false); -o2::framework::DataProcessorSpec getMFTDigitWriterSpec(bool mctruth = true, bool dec = false, bool calib = false); +o2::framework::DataProcessorSpec getITSDigitWriterSpec(bool mctruth = true, bool doStag = false, bool dec = false, bool calib = false); +o2::framework::DataProcessorSpec getMFTDigitWriterSpec(bool mctruth = true, bool doStag = false, bool dec = false, bool calib = false); } // end namespace itsmft } // end namespace o2 diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/EntropyDecoderSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/EntropyDecoderSpec.h index a64f2bf8c063c..6862e96c17afe 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/EntropyDecoderSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/EntropyDecoderSpec.h @@ -29,38 +29,39 @@ namespace o2 namespace itsmft { +template class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(o2::header::DataOrigin orig, int verbosity, bool getDigits = false, const std::string& ctfdictOpt = "none"); + EntropyDecoderSpec(int verbosity, bool doStag, bool getDigits = false, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void init(o2::framework::InitContext& ic) final; void run(o2::framework::ProcessingContext& pc) final; void endOfStream(o2::framework::EndOfStreamContext& ec) final; void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) final; - static auto getName(o2::header::DataOrigin orig) { return std::string{orig == o2::header::gDataOriginITS ? ITSDeviceName : MFTDeviceName}; } + static std::string getBinding(const std::string& name, int spec); + static constexpr o2::detectors::DetID ID{N == o2::detectors::DetID::ITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT}; + static constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; private: void updateTimeDependentParams(o2::framework::ProcessingContext& pc); - static constexpr std::string_view ITSDeviceName = "its-entropy-decoder"; - static constexpr std::string_view MFTDeviceName = "mft-entropy-decoder"; - o2::header::DataOrigin mOrigin = o2::header::gDataOriginInvalid; - o2::itsmft::CTFCoder mCTFCoder; + o2::itsmft::CTFCoder mCTFCoder; const NoiseMap* mNoiseMap = nullptr; LookUp mPattIdConverter; + bool mDoStaggering{false}; bool mGetDigits{false}; bool mMaskNoise{false}; bool mUseClusterDictionary{true}; - std::string mDetPrefix{}; std::string mCTFDictPath{}; TStopwatch mTimer; }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(o2::header::DataOrigin orig, int verbosity, bool getDigits, unsigned int sspec, const std::string& ctfdictOpt); +framework::DataProcessorSpec getITSEntropyDecoderSpec(int verbosity, bool doStag, bool getDigits, unsigned int sspec, const std::string& ctfdictOpt); +framework::DataProcessorSpec getMFTEntropyDecoderSpec(int verbosity, bool doStag, bool getDigits, unsigned int sspec, const std::string& ctfdictOpt); } // namespace itsmft } // namespace o2 diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/EntropyEncoderSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/EntropyEncoderSpec.h index 588cae6339489..597c0ca63f489 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/EntropyEncoderSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/EntropyEncoderSpec.h @@ -27,10 +27,12 @@ namespace o2 namespace itsmft { +template class EntropyEncoderSpec : public o2::framework::Task { + public: - EntropyEncoderSpec(o2::header::DataOrigin orig, bool selIR, const std::string& ctfdictOpt = "none"); + EntropyEncoderSpec(bool doStag, bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -38,17 +40,21 @@ class EntropyEncoderSpec : public o2::framework::Task void updateTimeDependentParams(o2::framework::ProcessingContext& pc); void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) final; + static std::string getBinding(const std::string& name, int spec); + static constexpr o2::detectors::DetID ID{N == o2::detectors::DetID::ITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT}; + static constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; + private: - o2::header::DataOrigin mOrigin = o2::header::gDataOriginInvalid; - o2::itsmft::CTFCoder mCTFCoder; + o2::itsmft::CTFCoder mCTFCoder; LookUp mPattIdConverter; - int mStrobeLength = 0; bool mSelIR = false; + bool mDoStaggering = false; TStopwatch mTimer; }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(o2::header::DataOrigin orig, bool selIR = false, const std::string& ctfdictOpt = "none"); +framework::DataProcessorSpec getITSEntropyEncoderSpec(bool doStag = false, bool selIR = false, const std::string& ctfdictOpt = "none"); +framework::DataProcessorSpec getMFTEntropyEncoderSpec(bool doStag = false, bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace itsmft } // namespace o2 diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h index a6876c456842d..8c16759e16726 100644 --- a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/STFDecoderSpec.h @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -16,12 +16,14 @@ #ifndef O2_ITSMFT_STFDECODER_ #define O2_ITSMFT_STFDECODER_ +#include +#include +#include #include #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" -#include -#include -#include +#include "DataFormatsITSMFT/DPLAlpideParam.h" +#include "DataFormatsITSMFT/ROFRecord.h" #include "ITSMFTReconstruction/ChipMappingITS.h" #include "ITSMFTReconstruction/ChipMappingMFT.h" #include "ITSMFTReconstruction/RawPixelDecoder.h" @@ -44,6 +46,7 @@ struct STFDecoderInp { bool doDigits = false; bool doCalib = false; bool doSquashing = false; + bool doStaggering = false; bool askSTFDist = true; bool allowReporting = true; bool verifyDecoder = false; @@ -55,6 +58,8 @@ struct STFDecoderInp { template class STFDecoder : public Task { + using AlpideParam = DPLAlpideParam; + public: STFDecoder(const STFDecoderInp& inp, std::shared_ptr gr); STFDecoder() = default; @@ -70,11 +75,16 @@ class STFDecoder : public Task void finalize(); void reset(); std::unique_ptr setupClusterer(const std::string& dictName); + bool ensureContinuousROF(const std::vector& in, std::vector& out, int lr, int nROFsTF, const char* name); + void rectifyDigits(std::vector& rofVec, std::vector& digVec); + void rectifyClusters(std::vector& rofVec, std::vector& clusVec, std::vector& pattVec); + TStopwatch mTimer; bool mDoClusters = false; bool mDoPatterns = false; bool mDoDigits = false; bool mDoCalibData = false; + bool mDoStaggering = false; bool mUnmutExtraLanes = false; bool mFinalizeDone = false; bool mAllowReporting = true; @@ -82,21 +92,25 @@ class STFDecoder : public Task bool mUseClusterDictionary = true; bool mVerifyDecoder = false; bool mDumpFrom1stPipeline = false; + bool mRunEnsureContinuousROF = true; + bool mDisableRectifyContinuousROF = false; int mDumpOnError = 0; int mNThreads = 1; int mVerbosity = 0; + int mLayers = 1; long mROFErrRepIntervalMS = 0; size_t mTFCounter = 0; - size_t mEstNDig = 0; - size_t mEstNClus = 0; - size_t mEstNClusPatt = 0; - size_t mEstNCalib = 0; - size_t mEstNROF = 0; + uint32_t mFirstTFOrbit = 0; + o2::InteractionRecord mFirstIR; + std::vector mEstNDig{0}; + std::vector mEstNClus{0}; + std::vector mEstNClusPatt{0}; + std::vector mEstNCalib{0}; size_t mMaxRawDumpsSize = 0; size_t mRawDumpedSize = 0; std::string mInputSpec; std::string mSelfName; - std::unique_ptr> mDecoder; + std::vector>> mDecoder; std::unique_ptr mClusterer; std::shared_ptr mGGCCDBRequest; }; diff --git a/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx index bc6418a077810..6174938171336 100644 --- a/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx @@ -20,7 +20,7 @@ #include "Framework/ConfigParamRegistry.h" #include "Framework/Logger.h" #include "ITSMFTWorkflow/ClusterReaderSpec.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITSMFT/PhysTrigger.h" #include "CommonUtils/NameConf.h" @@ -33,15 +33,16 @@ namespace itsmft { template -ClusterReader::ClusterReader(bool useMC, 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), mUsePatterns(usePatterns), mTriggerOut(triggerOut), mDetName(Origin.as()), mDetNameLC(mDetName) { std::transform(mDetNameLC.begin(), mDetNameLC.end(), mDetNameLC.begin(), ::tolower); - - mClusROFRec.fill(nullptr); - mClusterCompArray.fill(nullptr); - mPatternsArray.fill(nullptr); - mClusterMCTruth.fill(nullptr); - mClusMC2ROFs.fill(nullptr); + if (doStag) { + mLayers = DPLAlpideParam::getNLayers(); + mClusROFRec.resize(mLayers, nullptr); + mClusterCompArray.resize(mLayers, nullptr); + mPatternsArray.resize(mLayers, nullptr); + mClusterMCTruth.resize(mLayers, nullptr); + } } template @@ -59,8 +60,8 @@ void ClusterReader::run(ProcessingContext& pc) assert(ent < mTree->GetEntries()); // this should not happen mTree->GetEntry(ent); - for (uint32_t iLayer = 0; iLayer < NLayers; ++iLayer) { - LOG(info) << mDetName << "ClusterReader:" << iLayer << " pushes " << mClusROFRec[iLayer]->size() << " ROFRecords, " << mClusterCompArray[iLayer]->size() << " compact clusters at entry " << ent; + for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) { + LOG(info) << mDetName << "ClusterReader" << (mDoStaggering ? std::format(" on layer {}", iLayer) : "") << " pushes " << mClusROFRec[iLayer]->size() << " ROFRecords, " << mClusterCompArray[iLayer]->size() << " compact clusters at entry " << ent; pc.outputs().snapshot(Output{Origin, "CLUSTERSROF", iLayer}, *mClusROFRec[iLayer]); pc.outputs().snapshot(Output{Origin, "COMPCLUSTERS", iLayer}, *mClusterCompArray[iLayer]); if (mUsePatterns) { @@ -68,7 +69,9 @@ void ClusterReader::run(ProcessingContext& pc) } if (mUseMC) { pc.outputs().snapshot(Output{Origin, "CLUSTERSMCTR", iLayer}, *mClusterMCTruth[iLayer]); - pc.outputs().snapshot(Output{Origin, "CLUSTERSMC2ROF", iLayer}, *mClusMC2ROFs[iLayer]); + // read dummy MC2ROF vector to keep writer/readers backward compatible + static std::vector dummyMC2ROF; + pc.outputs().snapshot(Output{Origin, "CLUSTERSMC2ROF", iLayer}, dummyMC2ROF); } } if (mTriggerOut) { @@ -90,17 +93,15 @@ void ClusterReader::connectTree(const std::string& filename) mTree.reset((TTree*)mFile->Get(mClusTreeName.c_str())); assert(mTree); - for (uint32_t iLayer = 0; iLayer < NLayers; ++iLayer) { + for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) { setBranchAddress(mClusROFBranchName, mClusROFRec[iLayer], iLayer); setBranchAddress(mClusterCompBranchName, mClusterCompArray[iLayer], iLayer); if (mUsePatterns) { setBranchAddress(mClusterPattBranchName, mPatternsArray[iLayer], iLayer); } if (mUseMC) { - if (mTree->GetBranch(getBranchName(mClustMCTruthBranchName, iLayer).c_str()) && - mTree->GetBranch(getBranchName(mClustMC2ROFBranchName, iLayer).c_str())) { + if (mTree->GetBranch(getBranchName(mClustMCTruthBranchName, iLayer).c_str())) { setBranchAddress(mClustMCTruthBranchName, mClusterMCTruth[iLayer], iLayer); - setBranchAddress(mClustMC2ROFBranchName, mClusMC2ROFs[iLayer], iLayer); } else { LOG(info) << "MC-truth is missing"; mUseMC = false; @@ -113,7 +114,7 @@ void ClusterReader::connectTree(const std::string& filename) template std::string ClusterReader::getBranchName(const std::string& base, int index) const { - if constexpr (o2::itsmft::DPLAlpideParam::supportsStaggering()) { + if (mDoStaggering) { return mDetName + base + "_" + std::to_string(index); } return mDetName + base; @@ -132,10 +133,10 @@ void ClusterReader::setBranchAddress(const std::string& base, Ptr& addr, int namespace { template -std::vector makeOutChannels(o2::header::DataOrigin detOrig, bool mctruth, bool usePatterns, bool triggerOut) +std::vector makeOutChannels(o2::header::DataOrigin detOrig, bool mctruth, bool doStag, bool usePatterns, bool triggerOut) { std::vector outputs; - for (uint32_t iLayer = 0; iLayer < ((o2::itsmft::DPLAlpideParam::supportsStaggering()) ? o2::itsmft::DPLAlpideParam::getNLayers() : 1); ++iLayer) { + for (uint32_t iLayer = 0; iLayer < ((doStag) ? o2::itsmft::DPLAlpideParam::getNLayers() : 1); ++iLayer) { outputs.emplace_back(detOrig, "CLUSTERSROF", iLayer, Lifetime::Timeframe); outputs.emplace_back(detOrig, "COMPCLUSTERS", iLayer, Lifetime::Timeframe); if (usePatterns) { @@ -153,25 +154,25 @@ std::vector makeOutChannels(o2::header::DataOrigin detOrig, bool mct } } // namespace -DataProcessorSpec getITSClusterReaderSpec(bool useMC, bool usePatterns, bool triggerOut) +DataProcessorSpec getITSClusterReaderSpec(bool useMC, bool doStag, bool usePatterns, bool triggerOut) { return DataProcessorSpec{ .name = "its-cluster-reader", .inputs = Inputs{}, - .outputs = makeOutChannels("ITS", useMC, usePatterns, triggerOut), - .algorithm = AlgorithmSpec{adaptFromTask(useMC, usePatterns, triggerOut)}, + .outputs = makeOutChannels("ITS", useMC, doStag, usePatterns, triggerOut), + .algorithm = AlgorithmSpec{adaptFromTask(useMC, doStag, usePatterns, triggerOut)}, .options = Options{ {"its-cluster-infile", VariantType::String, "o2clus_its.root", {"Name of the input cluster file"}}, {"input-dir", VariantType::String, "none", {"Input directory"}}}}; } -DataProcessorSpec getMFTClusterReaderSpec(bool useMC, bool usePatterns, bool triggerOut) +DataProcessorSpec getMFTClusterReaderSpec(bool useMC, bool doStag, bool usePatterns, bool triggerOut) { return DataProcessorSpec{ .name = "mft-cluster-reader", .inputs = Inputs{}, - .outputs = makeOutChannels("MFT", useMC, usePatterns, triggerOut), - .algorithm = AlgorithmSpec{adaptFromTask(useMC, usePatterns, triggerOut)}, + .outputs = makeOutChannels("MFT", useMC, doStag, usePatterns, triggerOut), + .algorithm = AlgorithmSpec{adaptFromTask(useMC, doStag, usePatterns, triggerOut)}, .options = Options{ {"mft-cluster-infile", VariantType::String, "mftclusters.root", {"Name of the input cluster file"}}, {"input-dir", VariantType::String, "none", {"Input directory"}}}}; diff --git a/Detectors/ITSMFT/common/workflow/src/ClusterWriterSpec.cxx b/Detectors/ITSMFT/common/workflow/src/ClusterWriterSpec.cxx index c1900c346133b..b189e9c644e27 100644 --- a/Detectors/ITSMFT/common/workflow/src/ClusterWriterSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/ClusterWriterSpec.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -18,7 +18,7 @@ #include #include "Framework/ConcreteDataMatcher.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "ITSMFTWorkflow/ClusterWriterSpec.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" #include "DataFormatsITSMFT/CompCluster.h" @@ -41,67 +41,92 @@ using ROFRecLblT = std::vector; using namespace o2::header; template -DataProcessorSpec getClusterWriterSpec(bool useMC) +DataProcessorSpec getClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly) { static constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; - constexpr int NLayers = (DPLAlpideParam::supportsStaggering()) ? DPLAlpideParam::getNLayers() : 1; + const int nLayers = (doStag) ? DPLAlpideParam::getNLayers() : 1; const auto detName = Origin.as(); // Spectators for logging - auto compClusterSizes = std::make_shared>(); + auto compClusterSizes = std::make_shared>(nLayers, 0); auto compClustersSizeGetter = [compClusterSizes](CompClusType const& compClusters, DataRef const& ref) { auto const* dh = DataRefUtils::getHeader(ref); (*compClusterSizes)[dh->subSpecification] = compClusters.size(); }; - auto logger = [detName, compClusterSizes](std::vector const& rofs, DataRef const& ref) { + auto logger = [detName, compClusterSizes, doStag](std::vector const& rofs, DataRef const& ref) { auto const* dh = DataRefUtils::getHeader(ref); const auto i = dh->subSpecification; - LOG(info) << detName << "ClusterWriter:" << i << " pulled " << (*compClusterSizes)[i] << " clusters, in " << rofs.size() << " RO frames"; + LOG(info) << detName << "ClusterWriter" << ((doStag) ? std::format(" on layer {}", i) : "") + << " pulled " << (*compClusterSizes)[i] << " clusters, in " << rofs.size() << " RO frames"; }; auto getIndex = [](DataRef const& ref) -> size_t { auto const* dh = DataRefUtils::getHeader(ref); return static_cast(dh->subSpecification); }; - auto getName = [](std::string base, size_t index) -> std::string { - if constexpr (DPLAlpideParam::supportsStaggering()) { + auto getName = [doStag](std::string base, size_t index) -> std::string { + if (doStag) { return base += "_" + std::to_string(index); } return base; }; auto detNameLC = detName; std::transform(detNameLC.begin(), detNameLC.end(), detNameLC.begin(), [](unsigned char c) { return std::tolower(c); }); + std::vector vecInpSpecClus, vecInpSpecPatt, vecInpSpecROF, vecInpSpecLbl; + vecInpSpecClus.reserve(nLayers); + vecInpSpecPatt.reserve(nLayers); + vecInpSpecROF.reserve(nLayers); + vecInpSpecLbl.reserve(nLayers); + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + vecInpSpecClus.emplace_back(getName("compclus", iLayer), Origin, "COMPCLUSTERS", iLayer); + vecInpSpecPatt.emplace_back(getName("patterns", iLayer), Origin, "PATTERNS", iLayer); + vecInpSpecROF.emplace_back(getName("ROframes", iLayer), Origin, "CLUSTERSROF", iLayer); + vecInpSpecLbl.emplace_back(getName("labels", iLayer), Origin, "CLUSTERSMCTR", iLayer); + } + + if (clusterROFOnly) { + return MakeRootTreeWriterSpec(std::format("{}-cluster-writer", detNameLC).c_str(), + (o2::detectors::DetID::ITS == N) ? "o2clus_its.root" : "mftclusters.root", + MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = std::format("Tree with {} cluster ROFs only", detName)}, + BranchDefinition{vecInpSpecROF, + (detName + "ClustersROF").c_str(), "cluster-rof-branch", + nLayers, + logger, + getIndex, + getName})(); + } + return MakeRootTreeWriterSpec(std::format("{}-cluster-writer", detNameLC).c_str(), (o2::detectors::DetID::ITS == N) ? "o2clus_its.root" : "mftclusters.root", MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = std::format("Tree with {} clusters", detName)}, - BranchDefinition{InputSpec{"compclus", ConcreteDataTypeMatcher{Origin, "COMPCLUSTERS"}}, + BranchDefinition{vecInpSpecClus, (detName + "ClusterComp").c_str(), "compact-cluster-branch", - NLayers, + nLayers, compClustersSizeGetter, getIndex, getName}, - BranchDefinition{InputSpec{"patterns", ConcreteDataTypeMatcher{Origin, "PATTERNS"}}, + BranchDefinition{vecInpSpecPatt, (detName + "ClusterPatt").c_str(), "cluster-pattern-branch", - NLayers, + nLayers, getIndex, getName}, - BranchDefinition{InputSpec{"ROframes", ConcreteDataTypeMatcher{Origin, "CLUSTERSROF"}}, + BranchDefinition{vecInpSpecROF, (detName + "ClustersROF").c_str(), "cluster-rof-branch", - NLayers, + nLayers, logger, getIndex, getName}, - BranchDefinition{InputSpec{"labels", ConcreteDataTypeMatcher{Origin, "CLUSTERSMCTR"}}, + BranchDefinition{vecInpSpecLbl, (detName + "ClusterMCTruth").c_str(), "cluster-label-branch", - (useMC ? NLayers : 0), + (useMC ? nLayers : 0), getIndex, getName}, BranchDefinition{InputSpec{"MC2ROframes", ConcreteDataTypeMatcher{Origin, "CLUSTERSMC2ROF"}}, (detName + "ClustersMC2ROF").c_str(), "cluster-mc2rof-branch", - (useMC ? NLayers : 0), + (useMC ? nLayers : 0), getIndex, getName})(); } -framework::DataProcessorSpec getITSClusterWriterSpec(bool useMC) { return getClusterWriterSpec(useMC); } -framework::DataProcessorSpec getMFTClusterWriterSpec(bool useMC) { return getClusterWriterSpec(useMC); } +framework::DataProcessorSpec getITSClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly) { return getClusterWriterSpec(useMC, doStag, clusterROFOnly); } +framework::DataProcessorSpec getMFTClusterWriterSpec(bool useMC, bool doStag, bool clusterROFOnly) { return getClusterWriterSpec(useMC, doStag, clusterROFOnly); } } // namespace o2::itsmft diff --git a/Detectors/ITSMFT/common/workflow/src/ClustererSpec.cxx b/Detectors/ITSMFT/common/workflow/src/ClustererSpec.cxx index fc0dd5dbae7da..b3954c5c22ad1 100644 --- a/Detectors/ITSMFT/common/workflow/src/ClustererSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/ClustererSpec.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -12,6 +12,7 @@ /// @file ClustererSpec.cxx #include +#include #include "ITSMFTWorkflow/ClustererSpec.h" #include "Framework/ControlService.h" @@ -29,7 +30,7 @@ #include "DataFormatsParameters/GRPObject.h" #include "ITSMFTReconstruction/DigitPixelReader.h" #include "DetectorsBase/GeometryManager.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "CommonConstants/LHCConstants.h" #include "DetectorsCommonDataFormats/DetectorNameConf.h" #include "ITSMFTReconstruction/ClustererParam.h" @@ -37,6 +38,14 @@ namespace o2::itsmft { +template +ClustererDPL::ClustererDPL(std::shared_ptr gr, bool useMC, bool doStag) : mGGCCDBRequest(gr), mUseMC(useMC), mDoStaggering(doStag) +{ + if (mDoStaggering) { + mLayers = DPLAlpideParam::getNLayers(); + } +} + template void ClustererDPL::init(InitContext& ic) { @@ -48,12 +57,11 @@ void ClustererDPL::init(InitContext& ic) mDetName = Origin.as(); // prepare data filter - for (int iLayer = 0; iLayer < NLayers; ++iLayer) { + for (int iLayer = 0; iLayer < mLayers; ++iLayer) { mFilter.emplace_back("digits", Origin, "DIGITS", iLayer, Lifetime::Timeframe); mFilter.emplace_back("ROframe", Origin, "DIGITSROF", iLayer, Lifetime::Timeframe); if (mUseMC) { mFilter.emplace_back("labels", Origin, "DIGITSMCTR", iLayer, Lifetime::Timeframe); - mFilter.emplace_back("MC2ROframes", Origin, "DIGITSMC2ROF", iLayer, Lifetime::Timeframe); } } } @@ -64,10 +72,9 @@ void ClustererDPL::run(ProcessingContext& pc) updateTimeDependentParams(pc); // filter input and compose - std::array, NLayers> digits; - std::array, NLayers> rofs; - std::array, NLayers> labelsbuffer; - std::array, NLayers> mc2rofs; + std::vector> digits(mLayers); + std::vector> rofs(mLayers); + std::vector> labelsbuffer(mLayers); for (const DataRef& ref : InputRecordWalker{pc.inputs(), mFilter}) { auto const* dh = DataRefUtils::getHeader(ref); if (DataRefUtils::match(ref, {"digits", ConcreteDataTypeMatcher{Origin, "DIGITS"}})) { @@ -79,9 +86,6 @@ void ClustererDPL::run(ProcessingContext& pc) if (DataRefUtils::match(ref, {"labels", ConcreteDataTypeMatcher{Origin, "DIGITSMCTR"}})) { labelsbuffer[dh->subSpecification] = pc.inputs().get>(ref); } - if (DataRefUtils::match(ref, {"MC2ROframes", ConcreteDataTypeMatcher{Origin, "DIGITSMC2ROF"}})) { - mc2rofs[dh->subSpecification] = pc.inputs().get>(ref); - } } // query the first orbit in this TF @@ -93,10 +97,10 @@ void ClustererDPL::run(ProcessingContext& pc) uint64_t nClusters{0}; TStopwatch sw; o2::itsmft::DigitPixelReader reader; - for (uint32_t iLayer{0}; iLayer < NLayers; ++iLayer) { - int layer = (DPLAlpideParam::supportsStaggering()) ? iLayer : -1; + for (uint32_t iLayer{0}; iLayer < mLayers; ++iLayer) { + int layer = (mDoStaggering) ? iLayer : -1; sw.Start(); - LOG(info) << mDetName << "Clusterer:" << layer << " pulled " << digits[iLayer].size() << " digits, in " << rofs[iLayer].size() << " RO frames"; + LOG(info) << mDetName << "Clusterer" << ((mDoStaggering) ? std::format(" on layer {}", layer) : "") << " pulled " << digits[iLayer].size() << " digits, in " << rofs[iLayer].size() << " RO frames"; mClusterer->setMaxROFDepthToSquash(mClusterer->getMaxROFDepthToSquash(layer)); o2::dataformats::ConstMCTruthContainerView labels(labelsbuffer[iLayer]); @@ -106,8 +110,7 @@ void ClustererDPL::run(ProcessingContext& pc) reader.setDigits(digits[iLayer]); reader.setROFRecords(rofs[iLayer]); if (mUseMC) { - reader.setMC2ROFRecords(mc2rofs[iLayer]); - LOG(info) << mDetName << "Clusterer:" << layer << " pulled " << labels.getNElements() << " labels "; + LOG(info) << mDetName << "Clusterer" << ((mDoStaggering) ? std::format(" on layer {}", layer) : "") << " pulled " << labels.getNElements() << " labels "; reader.setDigitsMCTruth(labels.getIndexedSize() > 0 ? &labels : nullptr); } reader.init(); @@ -131,7 +134,7 @@ void ClustererDPL::run(ProcessingContext& pc) for (int iROF{0}; iROF < nROFsTF; ++iROF) { auto& rof = expClusRofVec[iROF]; int orb = iROF * par.getROFLengthInBC(iLayer) / o2::constants::lhc::LHCMaxBunches + firstTForbit; - int bc = iROF * par.getROFLengthInBC(iLayer) % o2::constants::lhc::LHCMaxBunches; + int bc = iROF * par.getROFLengthInBC(iLayer) % o2::constants::lhc::LHCMaxBunches + par.getROFDelayInBC(iLayer); o2::InteractionRecord ir(bc, orb); rof.setBCData(ir); rof.setROFrame(iROF); @@ -142,13 +145,18 @@ void ClustererDPL::run(ProcessingContext& pc) for (const auto& rof : clusROFVec) { const auto& ir = rof.getBCData(); if (ir < firstIR) { - LOGP(warn, "Discard ROF {} preceding TF 1st orbit {}, layer:{}", ir.asString(), firstTForbit, iLayer); + LOGP(warn, "Discard ROF {} preceding TF 1st orbit {}{}", ir.asString(), firstTForbit, ((mDoStaggering) ? std::format(" on layer {}", layer) : "")); + continue; + } + auto irToFirst = ir - firstIR; + if (irToFirst.toLong() - par.getROFDelayInBC(iLayer) < 0) { + LOGP(warn, "Discard ROF {} preceding TF 1st orbit {} due to imposed ROF delay{}", ir.asString(), firstTForbit, ((mDoStaggering) ? std::format(" on layer {}", iLayer) : "")); continue; } - const auto irToFirst = ir - firstIR; + irToFirst -= par.getROFDelayInBC(iLayer); const long irROF = irToFirst.toLong() / par.getROFLengthInBC(iLayer); if (irROF >= nROFsTF) { - LOGP(warn, "Discard ROF {} exceding TF orbit range, layer:{}", ir.asString(), iLayer); + LOGP(warn, "Discard ROF {} exceeding TF orbit range{}", ir.asString(), ((mDoStaggering) ? std::format(" on layer {}", layer) : "")); continue; } auto& expROF = expClusRofVec[irROF]; @@ -157,11 +165,11 @@ void ClustererDPL::run(ProcessingContext& pc) expROF.setNEntries(rof.getNEntries()); } else { if (expROF.getNEntries() < rof.getNEntries()) { - LOGP(warn, "Repeating ROF {} with {} clusters, prefer to already processed instance with {} clusters", rof.asString(), rof.getNEntries(), expROF.getNEntries()); + LOGP(warn, "Repeating {} with {} clusters, prefer to already processed instance with {} clusters{}", rof.asString(), rof.getNEntries(), expROF.getNEntries(), ((mDoStaggering) ? std::format(" on layer {}", layer) : "")); expROF.setFirstEntry(rof.getFirstEntry()); expROF.setNEntries(rof.getNEntries()); } else { - LOGP(warn, "Repeating ROF {} with {} clusters, discard preferring already processed instance with {} clusters", rof.asString(), rof.getNEntries(), expROF.getNEntries()); + LOGP(warn, "Repeating {} with {} clusters, discard preferring already processed instance with {} clusters{}", rof.asString(), rof.getNEntries(), expROF.getNEntries(), ((mDoStaggering) ? std::format(" on layer {}", layer) : "")); } } } @@ -182,18 +190,14 @@ void ClustererDPL::run(ProcessingContext& pc) if (mUseMC) { pc.outputs().snapshot(Output{Origin, "CLUSTERSMCTR", iLayer}, *clusterLabels); // at the moment requires snapshot - std::vector clusterMC2ROframes(mc2rofs[iLayer].size()); - for (int i = mc2rofs[iLayer].size(); i--;) { - clusterMC2ROframes[i] = mc2rofs[iLayer][i]; // Simply, replicate it from digits ? - } - pc.outputs().snapshot(Output{Origin, "CLUSTERSMC2ROF", iLayer}, clusterMC2ROframes); + // write dummy MC2ROF vector to keep writer/readers backward compatible + static std::vector dummyMC2ROF; + pc.outputs().snapshot(Output{Origin, "CLUSTERSMC2ROF", iLayer}, dummyMC2ROF); } reader.reset(); - // TODO: in principle, after masking "overflow" pixels the MC2ROFRecord maxROF supposed to change, nominally to minROF - // -> consider recalculationg maxROF sw.Stop(); - LOG(info) << mDetName << "Clusterer:" << layer << " pushed " << clusCompVec.size() << " clusters, in " << nROFs << " RO frames in " << sw.RealTime() << " s"; + LOG(info) << mDetName << "Clusterer" << ((mDoStaggering) ? std::format(": {}", iLayer) : "") << " pushed " << clusCompVec.size() << " clusters, in " << nROFs << " RO frames in " << sw.RealTime() << " s"; } LOG(info) << mDetName << "Clusterer produced " << nClusters << " clusters"; @@ -230,9 +234,9 @@ void ClustererDPL::updateTimeDependentParams(ProcessingContext& pc) nROFsToSquash = 2 + int(clParams.maxSOTMUS / (rofBC * o2::constants::lhc::LHCBunchSpacingMUS)); // use squashing } mClusterer->setMaxROFDepthToSquash(nROFsToSquash); - if constexpr (DPLAlpideParam::supportsStaggering()) { + if (mDoStaggering) { if (mClusterer->isContinuousReadOut()) { - for (int iLayer{0}; iLayer < NLayers; ++iLayer) { + for (int iLayer{0}; iLayer < mLayers; ++iLayer) { mClusterer->addMaxBCSeparationToSquash(alpParams.getROFLengthInBC(iLayer) + clParams.getMaxBCDiffToSquashBias(iLayer)); mClusterer->addMaxROFDepthToSquash((clParams.getMaxBCDiffToSquashBias(iLayer) > 0) ? 2 + int(clParams.maxSOTMUS / (alpParams.getROFLengthInBC(iLayer) * o2::constants::lhc::LHCBunchSpacingMUS)) : 0); } @@ -275,17 +279,16 @@ void ClustererDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) namespace { template -DataProcessorSpec getClustererSpec(bool useMC) +DataProcessorSpec getClustererSpec(bool useMC, bool doStag) { constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; std::vector inputs; - constexpr uint32_t nLayers = (DPLAlpideParam::supportsStaggering()) ? DPLAlpideParam::getNLayers() : 1; + uint32_t nLayers = doStag ? DPLAlpideParam::getNLayers() : 1; for (uint32_t iLayer = 0; iLayer < nLayers; ++iLayer) { inputs.emplace_back("digits", Origin, "DIGITS", iLayer, Lifetime::Timeframe); inputs.emplace_back("ROframes", Origin, "DIGITSROF", iLayer, Lifetime::Timeframe); if (useMC) { inputs.emplace_back("labels", Origin, "DIGITSMCTR", iLayer, Lifetime::Timeframe); - inputs.emplace_back("MC2ROframes", Origin, "DIGITSMC2ROF", iLayer, Lifetime::Timeframe); } } inputs.emplace_back("cldict", Origin, "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec(Origin.as() + "/Calib/ClusterDictionary")); @@ -313,21 +316,21 @@ DataProcessorSpec getClustererSpec(bool useMC) .name = (N == o2::detectors::DetID::ITS) ? "its-clusterer" : "mft-clusterer", .inputs = inputs, .outputs = outputs, - .algorithm = AlgorithmSpec{adaptFromTask>(ggRequest, useMC)}, + .algorithm = AlgorithmSpec{adaptFromTask>(ggRequest, useMC, doStag)}, .options = Options{ {"ignore-cluster-dictionary", VariantType::Bool, false, {"do not use cluster dictionary, always store explicit patterns"}}, {"nthreads", VariantType::Int, 1, {"Number of clustering threads"}}}}; } } // namespace -framework::DataProcessorSpec getITSClustererSpec(bool useMC) +framework::DataProcessorSpec getITSClustererSpec(bool useMC, bool doStag) { - return getClustererSpec(useMC); + return getClustererSpec(useMC, doStag); } -framework::DataProcessorSpec getMFTClustererSpec(bool useMC) +framework::DataProcessorSpec getMFTClustererSpec(bool useMC, bool doStag) { - return getClustererSpec(useMC); + return getClustererSpec(useMC, doStag); } } // namespace o2::itsmft diff --git a/Detectors/ITSMFT/common/workflow/src/DigitReaderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/DigitReaderSpec.cxx index ec86da4833a0d..b6c3ab5386179 100644 --- a/Detectors/ITSMFT/common/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/DigitReaderSpec.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -13,14 +13,15 @@ #include #include +#include -#include "TTree.h" +#include #include "Framework/ControlService.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/Logger.h" #include "ITSMFTWorkflow/DigitReaderSpec.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "ITSMFTReconstruction/ChipMappingITS.h" #include "ITSMFTReconstruction/ChipMappingMFT.h" #include "SimulationDataFormat/MCCompLabel.h" @@ -41,22 +42,21 @@ namespace itsmft { template -DigitReader::DigitReader(bool useMC, bool useCalib, bool triggerOut) : mUseMC(useMC), mUseCalib(useCalib), mTriggerOut(triggerOut), mDetNameLC(mDetName = ID.getName()), mDigTreeName("o2sim") +DigitReader::DigitReader(bool useMC, bool doStag, bool useCalib, bool triggerOut) : mUseMC(useMC), mDoStaggering(doStag), mUseCalib(useCalib), mTriggerOut(triggerOut), mDetNameLC(mDetName = ID.getName()), mDigTreeName("o2sim") { mDigitBranchName = mDetName + mDigitBranchName; mDigitROFBranchName = mDetName + mDigitROFBranchName; mCalibBranchName = mDetName + mCalibBranchName; mDigitMCTruthBranchName = mDetName + mDigitMCTruthBranchName; - mDigitMC2ROFBranchName = mDetName + mDigitMC2ROFBranchName; std::transform(mDetNameLC.begin(), mDetNameLC.end(), mDetNameLC.begin(), ::tolower); - for (uint32_t i = 0; i < NLayers; ++i) { - mDigits[i] = nullptr; - mDigROFRec[i] = nullptr; - mDigMC2ROFs[i] = nullptr; - mPLabels[i] = nullptr; + if (mDoStaggering) { + mLayers = DPLAlpideParam::getNLayers(); + mDigits.resize(mLayers, nullptr); + mDigROFRec.resize(mLayers, nullptr); + mPLabels.resize(mLayers, nullptr); } } @@ -103,8 +103,8 @@ void DigitReader::run(ProcessingContext& pc) ent++; assert(ent < mTree->GetEntries()); // this should not happen mTree->GetEntry(ent); - for (uint32_t iLayer = 0; iLayer < RLayers; ++iLayer) { - LOG(info) << mDetName << "DigitReader:" << iLayer << " pushes " << mDigROFRec[iLayer]->size() << " ROFRecords, " << mDigits[iLayer]->size() << " digits at entry " << ent; + for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) { + LOG(info) << mDetName << "DigitReader" << ((mDoStaggering) ? std::format(": {}", iLayer) : "") << " pushes " << mDigROFRec[iLayer]->size() << " ROFRecords, " << mDigits[iLayer]->size() << " digits at entry " << ent; pc.outputs().snapshot(Output{Origin, "DIGITSROF", iLayer}, *mDigROFRec[iLayer]); pc.outputs().snapshot(Output{Origin, "DIGITS", iLayer}, *mDigits[iLayer]); if (mUseMC) { @@ -112,7 +112,9 @@ void DigitReader::run(ProcessingContext& pc) mPLabels[iLayer]->copyandflatten(sharedlabels); delete mPLabels[iLayer]; mPLabels[iLayer] = nullptr; - pc.outputs().snapshot(Output{Origin, "DIGITSMC2ROF", iLayer}, *mDigMC2ROFs[iLayer]); + // read dummy MC2ROF vector to keep writer/readers backward compatible + static std::vector dummyMC2ROF; + pc.outputs().snapshot(Output{Origin, "DIGITSMC2ROF", iLayer}, dummyMC2ROF); } } if (mUseCalib) { @@ -131,7 +133,6 @@ void DigitReader::run(ProcessingContext& pc) std::vector digitsSel; std::vector calibSel; std::vector digROFRecSel; - std::vector digMC2ROFsSel; o2::dataformats::MCTruthContainer digitLabelsSel; if (irFrames.size()) { // we assume the IRFrames are in the increasing order @@ -181,26 +182,6 @@ void DigitReader::run(ProcessingContext& pc) } } } - if (mUseMC) { - digMC2ROFsSel = *mDigMC2ROFs[0]; - for (auto& mc2rof : digMC2ROFsSel) { - if (mc2rof.rofRecordID < 0) { - continue; // did not contribute even to the original data - } - unsigned int mn = 0xffff, mx = 0; - for (int ir = mc2rof.minROF; ir <= mc2rof.maxROF; ir++) { - if (rofOld2New[ir] >= 0) { // used - mx = rofOld2New[ir]; - if (mn > mx) { - mn = mx; - } - } - } - mc2rof.rofRecordID = mn == 0xffff ? -1 : int(mn); - mc2rof.minROF = mn; - mc2rof.maxROF = mx; - } - } if (mDigROFRec[0]->back().getBCData() + mROFLengthInBC - 1 < irMax) { // need to check the next entry ent++; continue; @@ -220,7 +201,6 @@ void DigitReader::run(ProcessingContext& pc) if (mUseMC) { auto& sharedlabels = pc.outputs().make>(Output{Origin, "DIGITSMCTR", 0}); digitLabelsSel.flatten_to(sharedlabels); - pc.outputs().snapshot(Output{Origin, "DIGITSMC2ROF", 0}, digMC2ROFsSel); } if (!irFrames.size() || irFrames.back().isLast()) { @@ -238,14 +218,13 @@ void DigitReader::connectTree(const std::string& filename) assert(mFile && !mFile->IsZombie()); mTree.reset((TTree*)mFile->Get(mDigTreeName.c_str())); assert(mTree); - for (uint32_t iLayer = 0; iLayer < RLayers; ++iLayer) { + for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) { setBranchAddress(mDigitROFBranchName, mDigROFRec[iLayer], iLayer); setBranchAddress(mDigitBranchName, mDigits[iLayer], iLayer); if (mUseMC) { - if (!mTree->GetBranch(getBranchName(mDigitMC2ROFBranchName, iLayer).c_str()) || !mTree->GetBranch(getBranchName(mDigitMCTruthBranchName, iLayer).c_str())) { + if (!mTree->GetBranch(getBranchName(mDigitMCTruthBranchName, iLayer).c_str())) { throw std::runtime_error("MC data requested but not found in the tree"); } - setBranchAddress(mDigitMC2ROFBranchName, mDigMC2ROFs[iLayer], iLayer); if (!mPLabels[iLayer]) { setBranchAddress(mDigitMCTruthBranchName, mPLabels[iLayer], iLayer); } @@ -263,10 +242,10 @@ void DigitReader::connectTree(const std::string& filename) template std::string DigitReader::getBranchName(const std::string& base, int index) { - if constexpr (!o2::itsmft::DPLAlpideParam::supportsStaggering()) { - return base; + if (mDoStaggering) { + return base + "_" + std::to_string(index); } - return base + "_" + std::to_string(index); + return base; } template @@ -282,12 +261,12 @@ void DigitReader::setBranchAddress(const std::string& base, Ptr& addr, int la namespace { template -std::vector makeOutChannels(bool mctruth, bool useCalib) +std::vector makeOutChannels(bool mctruth, bool doStag, bool useCalib) { constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; std::vector outputs; - static constexpr int RLayers = o2::itsmft::DPLAlpideParam::supportsStaggering() ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; - for (int iLayer = 0; iLayer < RLayers; ++iLayer) { + int nLayers = doStag ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { outputs.emplace_back(Origin, "DIGITS", iLayer, Lifetime::Timeframe); outputs.emplace_back(Origin, "DIGITSROF", iLayer, Lifetime::Timeframe); if (mctruth) { @@ -303,25 +282,25 @@ std::vector makeOutChannels(bool mctruth, bool useCalib) } } // namespace -DataProcessorSpec getITSDigitReaderSpec(bool useMC, bool useCalib, bool useTriggers, std::string defname) +DataProcessorSpec getITSDigitReaderSpec(bool useMC, bool doStag, bool useCalib, bool useTriggers, std::string defname) { return DataProcessorSpec{ .name = "its-digit-reader", .inputs = Inputs{}, - .outputs = makeOutChannels(useMC, useCalib), - .algorithm = AlgorithmSpec{adaptFromTask(useMC, useCalib)}, + .outputs = makeOutChannels(useMC, doStag, useCalib), + .algorithm = AlgorithmSpec{adaptFromTask(useMC, doStag, useCalib, useTriggers)}, .options = Options{ {"its-digit-infile", VariantType::String, defname, {"Name of the input digit file"}}, {"input-dir", VariantType::String, "none", {"Input directory"}}}}; } -DataProcessorSpec getMFTDigitReaderSpec(bool useMC, bool useCalib, bool useTriggers, std::string defname) +DataProcessorSpec getMFTDigitReaderSpec(bool useMC, bool doStag, bool useCalib, bool useTriggers, std::string defname) { return DataProcessorSpec{ .name = "mft-digit-reader", .inputs = Inputs{}, - .outputs = makeOutChannels(useMC, useCalib), - .algorithm = AlgorithmSpec{adaptFromTask(useMC, useCalib)}, + .outputs = makeOutChannels(useMC, doStag, useCalib), + .algorithm = AlgorithmSpec{adaptFromTask(useMC, doStag, useCalib, useTriggers)}, .options = Options{ {"mft-digit-infile", VariantType::String, defname, {"Name of the input digit file"}}, {"input-dir", VariantType::String, "none", {"Input directory"}}}}; diff --git a/Detectors/ITSMFT/common/workflow/src/DigitWriterSpec.cxx b/Detectors/ITSMFT/common/workflow/src/DigitWriterSpec.cxx index c4f1e336180c7..944432196881e 100644 --- a/Detectors/ITSMFT/common/workflow/src/DigitWriterSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/DigitWriterSpec.cxx @@ -14,7 +14,7 @@ #include "ITSMFTWorkflow/DigitWriterSpec.h" #include "Framework/ConcreteDataMatcher.h" #include "Framework/DataRef.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" #include "DataFormatsITSMFT/Digit.h" #include "DataFormatsITSMFT/GBTCalibData.h" @@ -43,20 +43,20 @@ using MCCont = o2::dataformats::ConstMCTruthContainer; /// create the processor spec /// describing a processor receiving digits for ITS/MFT and writing them to file template -DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib) +DataProcessorSpec getDigitWriterSpec(bool mctruth, bool doStag, bool dec, bool calib) { static constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; - constexpr int NLayers = o2::itsmft::DPLAlpideParam::supportsStaggering() ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; + int mLayers = doStag ? o2::itsmft::DPLAlpideParam::getNLayers() : 1; std::string detStr = o2::detectors::DetID::getName(N); std::string detStrL = dec ? "o2_" : ""; // for decoded digits prepend by o2 detStrL += detStr; std::transform(detStrL.begin(), detStrL.end(), detStrL.begin(), ::tolower); - auto digitSizes = std::make_shared>(); + auto digitSizes = std::make_shared>(mLayers, 0); auto digitSizeGetter = [digitSizes](std::vector const& inDigits, DataRef const& ref) { auto const* dh = DataRefUtils::getHeader(ref); (*digitSizes)[dh->subSpecification] = inDigits.size(); }; - auto rofSizes = std::make_shared>(); + auto rofSizes = std::make_shared>(mLayers, 0); auto rofSizeGetter = [rofSizes](std::vector const& inROFs, DataRef const& ref) { auto const* dh = DataRefUtils::getHeader(ref); (*rofSizes)[dh->subSpecification] = inROFs.size(); @@ -84,11 +84,11 @@ DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib) // handler for labels // This is necessary since we can't store the original label buffer in a ROOT entry -- as is -- if it exceeds a certain size. // We therefore convert it to a special split class. - auto fillLabels = [digitSizes, rofSizes](TBranch& branch, std::vector const& labelbuffer, DataRef const& ref) { + auto fillLabels = [detStr, doStag, digitSizes, rofSizes](TBranch& branch, std::vector const& labelbuffer, DataRef const& ref) { o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); auto const* dh = DataRefUtils::getHeader(ref); auto layer = static_cast(dh->subSpecification); - LOG(info) << "WRITING " << labels.getNElements() << " LABELS FOR " << layer << " WITH " << (*digitSizes)[layer] << " DIGITS IN " << (*rofSizes)[layer] << " ROFS"; + LOG(info) << detStr << ": WRITING " << labels.getNElements() << " LABELS" << (doStag ? std::format(" FOR LAYER {}", layer) : "") << " WITH " << (*digitSizes)[layer] << " DIGITS IN " << (*rofSizes)[layer] << " ROFS"; o2::dataformats::IOMCTruthContainerView outputcontainer; auto ptr = &outputcontainer; @@ -102,37 +102,48 @@ DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib) auto const* dh = DataRefUtils::getHeader(ref); return static_cast(dh->subSpecification); }; - auto getName = [](std::string base, size_t index) -> std::string { - if constexpr (o2::itsmft::DPLAlpideParam::supportsStaggering()) { + auto getName = [doStag](std::string base, size_t index) -> std::string { + if (doStag) { return base += "_" + std::to_string(index); } return base; }; + + std::vector vecInpSpecDig, vecInpSpecROF, vecInpSpecLbl; + vecInpSpecDig.reserve(mLayers); + vecInpSpecROF.reserve(mLayers); + vecInpSpecLbl.reserve(mLayers); + for (int iLayer = 0; iLayer < mLayers; iLayer++) { + vecInpSpecDig.emplace_back(getName(detStr + "digits", iLayer), Origin, "DIGITS", iLayer); + vecInpSpecROF.emplace_back(getName(detStr + "digitsROF", iLayer), Origin, "DIGITSROF", iLayer); + vecInpSpecLbl.emplace_back(getName(detStr + "_digitsMCTR", iLayer), Origin, "DIGITSMCTR", iLayer); + } + return MakeRootTreeWriterSpec((detStr + "DigitWriter" + (dec ? "_dec" : "")).c_str(), (detStrL + "digits.root").c_str(), MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = detStr + " Digits tree"}, MakeRootTreeWriterSpec::CustomClose(finishWriting), - BranchDefinition>{InputSpec{detStr + "digits", ConcreteDataTypeMatcher{Origin, "DIGITS"}}, + BranchDefinition>{vecInpSpecDig, detStr + "Digit", "digit-branch", - NLayers, + mLayers, digitSizeGetter, getIndex, getName}, - BranchDefinition>{InputSpec{detStr + "digitsROF", ConcreteDataTypeMatcher{Origin, "DIGITSROF"}}, + BranchDefinition>{vecInpSpecROF, detStr + "DigitROF", "digit-rof-branch", - NLayers, + mLayers, rofSizeGetter, getIndex, getName}, - BranchDefinition>{InputSpec{detStr + "_digitsMCTR", ConcreteDataTypeMatcher{Origin, "DIGITSMCTR"}}, + BranchDefinition>{vecInpSpecLbl, detStr + "DigitMCTruth", "digit-mctruth-branch", - (mctruth ? NLayers : 0), + (mctruth ? mLayers : 0), fillLabels, getIndex, getName}, BranchDefinition>{InputSpec{detStr + "_digitsMC2ROF", ConcreteDataTypeMatcher{Origin, "DIGITSMC2ROF"}}, detStr + "DigitMC2ROF", "digit-mc2rof-branch", - (mctruth ? NLayers : 0), + (mctruth ? mLayers : 0), getIndex, getName}, BranchDefinition>{InputSpec{detStr + "calib", ConcreteDataTypeMatcher{Origin, "GBTCALIB"}}, @@ -140,14 +151,14 @@ DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib) (calib ? 1 : 0)})(); } -DataProcessorSpec getITSDigitWriterSpec(bool mctruth, bool dec, bool calib) +DataProcessorSpec getITSDigitWriterSpec(bool mctruth, bool doStag, bool dec, bool calib) { - return getDigitWriterSpec(mctruth, dec, calib); + return getDigitWriterSpec(mctruth, doStag, dec, calib); } -DataProcessorSpec getMFTDigitWriterSpec(bool mctruth, bool dec, bool calib) +DataProcessorSpec getMFTDigitWriterSpec(bool mctruth, bool doStag, bool dec, bool calib) { - return getDigitWriterSpec(mctruth, dec, calib); + return getDigitWriterSpec(mctruth, doStag, dec, calib); } } // end namespace itsmft diff --git a/Detectors/ITSMFT/common/workflow/src/EntropyDecoderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/EntropyDecoderSpec.cxx index f90b708af1996..1107ca2fd34f6 100644 --- a/Detectors/ITSMFT/common/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/EntropyDecoderSpec.cxx @@ -20,6 +20,7 @@ #include "ITSMFTWorkflow/EntropyDecoderSpec.h" #include "ITSMFTReconstruction/ClustererParam.h" #include "DetectorsCommonDataFormats/DetectorNameConf.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITSMFT/PhysTrigger.h" using namespace o2::framework; @@ -28,25 +29,33 @@ namespace o2 { namespace itsmft { -EntropyDecoderSpec::EntropyDecoderSpec(o2::header::DataOrigin orig, int verbosity, bool getDigits, const std::string& ctfdictOpt) - : mOrigin(orig), mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, orig == o2::header::gDataOriginITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT, ctfdictOpt), mGetDigits(getDigits) + +template +std::string EntropyDecoderSpec::getBinding(const std::string& name, int spec) +{ + return fmt::format("{}_{}", name, spec); +} + +template +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, bool doStag, bool getDigits, const std::string& ctfdictOpt) + : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, doStag, ctfdictOpt), mDoStaggering(doStag), mGetDigits(getDigits) { - assert(orig == o2::header::gDataOriginITS || orig == o2::header::gDataOriginMFT); - mDetPrefix = orig == o2::header::gDataOriginITS ? "_ITS" : "_MFT"; mTimer.Stop(); mTimer.Reset(); mCTFCoder.setVerbosity(verbosity); - mCTFCoder.setDictBinding(std::string("ctfdict") + mDetPrefix); + mCTFCoder.setDictBinding(std::string("ctfdict_") + ID.getName()); } -void EntropyDecoderSpec::init(o2::framework::InitContext& ic) +template +void EntropyDecoderSpec::init(o2::framework::InitContext& ic) { - mCTFCoder.init(ic); + mCTFCoder.template init(ic); mMaskNoise = ic.options().get("mask-noise"); mUseClusterDictionary = !ic.options().get("ignore-cluster-dictionary"); } -void EntropyDecoderSpec::run(ProcessingContext& pc) +template +void EntropyDecoderSpec::run(ProcessingContext& pc) { if (pc.services().get().globalRunNumberChanged) { mTimer.Reset(); @@ -54,105 +63,142 @@ void EntropyDecoderSpec::run(ProcessingContext& pc) auto cput = mTimer.CpuTime(); mTimer.Start(false); o2::ctf::CTFIOSize iosize; + size_t ndigcl = 0, nrofs = 0; updateTimeDependentParams(pc); - auto buff = pc.inputs().get>(std::string("ctf") + mDetPrefix); - // since the buff is const, we cannot use EncodedBlocks::relocate directly, instead we wrap its data to another flat object - // const auto ctfImage = o2::itsmft::CTF::getImage(buff.data()); - - // this produces weird memory problems in unrelated devices, to be understood - // auto& trigs = pc.outputs().make>(OutputRef{"phystrig"}); // dummy output - - auto& rofs = pc.outputs().make>(OutputRef{"ROframes"}); - if (mGetDigits) { - auto& digits = pc.outputs().make>(OutputRef{"Digits"}); - if (buff.size()) { - iosize = mCTFCoder.decode(o2::itsmft::CTF::getImage(buff.data()), rofs, digits, mNoiseMap, mPattIdConverter); + std::string nm = ID.getName(); + uint32_t nLayers = mDoStaggering ? DPLAlpideParam::getNLayers() : 1; + for (uint32_t iLayer = 0; iLayer < nLayers; iLayer++) { + auto buff = pc.inputs().get>(getBinding(nm + "CTF", iLayer)); + // since the buff is const, we cannot use EncodedBlocks::relocate directly, instead we wrap its data to another flat object + // const auto ctfImage = o2::itsmft::CTF::getImage(buff.data()); + const auto& ctf = o2::itsmft::CTF::getImage(buff.data()); + if (ctf.getHeader().maxStreams != nLayers) { + LOGP(fatal, "Number of streams {} in the CTF header is not equal to NLayers {} from AlpideParam in {}staggered mode", + ctf.getHeader().maxStreams, nLayers, mDoStaggering ? "" : "non-"); } - mTimer.Stop(); - LOG(info) << "Decoded " << digits.size() << " digits in " << rofs.size() << " RO frames, (" << iosize.asString() << ") in " << mTimer.CpuTime() - cput << " s"; - } else { - auto& compcl = pc.outputs().make>(OutputRef{"compClusters"}); - auto& patterns = pc.outputs().make>(OutputRef{"patterns"}); - if (buff.size()) { - iosize = mCTFCoder.decode(o2::itsmft::CTF::getImage(buff.data()), rofs, compcl, patterns, mNoiseMap, mPattIdConverter); + // this produces weird memory problems in unrelated devices, to be understood + // auto& trigs = pc.outputs().make>(OutputRef{"phystrig"}); // dummy output + auto& rofs = pc.outputs().make>(OutputRef{nm + "ROframes", iLayer}); + if (mGetDigits) { + auto& digits = pc.outputs().make>(OutputRef{nm + "Digits", iLayer}); + if (buff.size()) { + iosize += mCTFCoder.decode(ctf, rofs, digits, mNoiseMap, mPattIdConverter); + } + ndigcl += digits.size(); + nrofs += rofs.size(); + } else { + auto& compcl = pc.outputs().make>(OutputRef{nm + "compClusters", iLayer}); + auto& patterns = pc.outputs().make>(OutputRef{nm + "patterns", iLayer}); + if (buff.size()) { + iosize += mCTFCoder.decode(ctf, rofs, compcl, patterns, mNoiseMap, mPattIdConverter); + } + ndigcl += compcl.size(); } - mTimer.Stop(); - LOG(info) << "Decoded " << compcl.size() << " clusters in " << rofs.size() << " RO frames, (" << iosize.asString() << ") in " << mTimer.CpuTime() - cput << " s"; } - pc.outputs().snapshot({"ctfrep", 0}, iosize); + pc.outputs().snapshot({nm + "ctfrep", 0}, iosize); + mTimer.Stop(); + LOGP(info, "Decoded {} {} in {} ROFs of {} streams ({}) in {}staggerd mode in {} s", ndigcl, mGetDigits ? "digits" : "clusters", + nrofs, nLayers, iosize.asString(), mDoStaggering ? "" : "non-", mTimer.CpuTime() - cput); } -void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) +template +void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) { - LOGF(info, "%s Entropy Decoding total timing: Cpu: %.3e Real: %.3e s in %d slots", - mOrigin.as(), mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); + LOGP(info, "{} Entropy Decoding total timing: Cpu: {:.3e} Real: {:.3e} s in {} slots", + Origin.as(), mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -void EntropyDecoderSpec::updateTimeDependentParams(ProcessingContext& pc) +template +void EntropyDecoderSpec::updateTimeDependentParams(ProcessingContext& pc) { + std::string nm = ID.getName(); if (pc.services().get().globalRunNumberChanged) { // this params need to be queried only once if (mMaskNoise) { - pc.inputs().get(std::string("noise") + mDetPrefix); + pc.inputs().get(nm + "noise"); } if (mGetDigits || mMaskNoise) { - pc.inputs().get(std::string("cldict") + mDetPrefix); + pc.inputs().get(nm + "cldict"); } } + pc.inputs().get*>(nm + "alppar"); mCTFCoder.updateTimeDependentParams(pc, true); } -void EntropyDecoderSpec::finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) +template +void EntropyDecoderSpec::finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) { - if (matcher == ConcreteDataMatcher(mOrigin, "NOISEMAP", 0)) { + if (matcher == ConcreteDataMatcher(Origin, "NOISEMAP", 0)) { mNoiseMap = (o2::itsmft::NoiseMap*)obj; - LOG(info) << mOrigin.as() << " noise map updated"; + LOG(info) << Origin.as() << " noise map updated"; return; } - if (matcher == ConcreteDataMatcher(mOrigin, "CLUSDICT", 0)) { - LOG(info) << mOrigin.as() << " cluster dictionary updated" << (!mUseClusterDictionary ? " but its using is disabled" : ""); + if (matcher == ConcreteDataMatcher(Origin, "CLUSDICT", 0)) { + LOG(info) << Origin.as() << " cluster dictionary updated" << (!mUseClusterDictionary ? " but its using is disabled" : ""); mPattIdConverter.setDictionary((const TopologyDictionary*)obj); return; } - if (mCTFCoder.finaliseCCDB(matcher, obj)) { + if (matcher == ConcreteDataMatcher(Origin, "ALPIDEPARAM", 0)) { + LOG(info) << "Alpide param updated"; + return; + } + if (mCTFCoder.template finaliseCCDB(matcher, obj)) { return; } } -DataProcessorSpec getEntropyDecoderSpec(o2::header::DataOrigin orig, int verbosity, bool getDigits, unsigned int sspec, const std::string& ctfdictOpt) +template +DataProcessorSpec getEntropyDecoderSpec(int verbosity, bool doStag, bool getDigits, unsigned int sspec, const std::string& ctfdictOpt) { + constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; + constexpr o2::detectors::DetID ID{N == o2::detectors::DetID::ITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT}; + uint32_t nLayers = doStag ? DPLAlpideParam::getNLayers() : 1; + + std::vector inputs; std::vector outputs; - // this is a special dummy input which makes sense only in sync workflows // this produces weird memory problems in unrelated devices, to be understood - // outputs.emplace_back(OutputSpec{{"phystrig"}, orig, "PHYSTRIG", 0, Lifetime::Timeframe}); - - if (getDigits) { - outputs.emplace_back(OutputSpec{{"Digits"}, orig, "DIGITS", 0, Lifetime::Timeframe}); - outputs.emplace_back(OutputSpec{{"ROframes"}, orig, "DIGITSROF", 0, Lifetime::Timeframe}); - } else { - outputs.emplace_back(OutputSpec{{"compClusters"}, orig, "COMPCLUSTERS", 0, Lifetime::Timeframe}); - outputs.emplace_back(OutputSpec{{"ROframes"}, orig, "CLUSTERSROF", 0, Lifetime::Timeframe}); - outputs.emplace_back(OutputSpec{{"patterns"}, orig, "PATTERNS", 0, Lifetime::Timeframe}); + // outputs.emplace_back(OutputSpec{{"phystrig"}, Origin, "PHYSTRIG", 0, Lifetime::Timeframe}); + std::string nm = ID.getName(); + for (uint32_t iLayer = 0; iLayer < nLayers; ++iLayer) { + if (getDigits) { + outputs.emplace_back(OutputSpec{{nm + "Digits"}, Origin, "DIGITS", iLayer, Lifetime::Timeframe}); + outputs.emplace_back(OutputSpec{{nm + "ROframes"}, Origin, "DIGITSROF", iLayer, Lifetime::Timeframe}); + } else { + outputs.emplace_back(OutputSpec{{nm + "compClusters"}, Origin, "COMPCLUSTERS", iLayer, Lifetime::Timeframe}); + outputs.emplace_back(OutputSpec{{nm + "ROframes"}, Origin, "CLUSTERSROF", iLayer, Lifetime::Timeframe}); + outputs.emplace_back(OutputSpec{{nm + "patterns"}, Origin, "PATTERNS", iLayer, Lifetime::Timeframe}); + } + inputs.emplace_back(EntropyDecoderSpec::getBinding(nm + "CTF", iLayer), Origin, "CTFDATA", sspec * 100 + iLayer, Lifetime::Timeframe); } - outputs.emplace_back(OutputSpec{{"ctfrep"}, orig, "CTFDECREP", 0, Lifetime::Timeframe}); - std::string nm = orig == o2::header::gDataOriginITS ? "_ITS" : "_MFT"; - std::vector inputs; - inputs.emplace_back(std::string("ctf") + nm, orig, "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back(std::string("noise") + nm, orig, "NOISEMAP", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/NoiseMap", orig.as()))); - inputs.emplace_back(std::string("cldict") + nm, orig, "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/ClusterDictionary", orig.as()))); + outputs.emplace_back(OutputSpec{{nm + "ctfrep"}, Origin, "CTFDECREP", 0, Lifetime::Timeframe}); + + inputs.emplace_back(nm + "alppar", Origin, "ALPIDEPARAM", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Config/AlpideParam", Origin.as()))); + inputs.emplace_back(nm + "noise", Origin, "NOISEMAP", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/NoiseMap", Origin.as()))); + inputs.emplace_back(nm + "cldict", Origin, "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/ClusterDictionary", Origin.as()))); if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { - inputs.emplace_back(std::string("ctfdict") + nm, orig, "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/CTFDictionaryTree", orig.as()))); + inputs.emplace_back(std::string{"ctfdict_"} + ID.getName(), Origin, "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/CTFDictionaryTree", Origin.as()))); } - inputs.emplace_back(std::string("trigoffset"), "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); + inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ - EntropyDecoderSpec::getName(orig), + Origin == o2::header::gDataOriginITS ? "its-entropy-decoder" : "mft-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(orig, verbosity, getDigits, ctfdictOpt)}, + AlgorithmSpec{adaptFromTask>(verbosity, doStag, getDigits, ctfdictOpt)}, Options{{"mask-noise", VariantType::Bool, false, {"apply noise mask to digits or clusters (involves reclusterization)"}}, {"ignore-cluster-dictionary", VariantType::Bool, false, {"do not use cluster dictionary, always store explicit patterns"}}, {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } + +framework::DataProcessorSpec getITSEntropyDecoderSpec(int verbosity, bool doStag, bool getDigits, unsigned int sspec, const std::string& ctfdictOpt) +{ + return getEntropyDecoderSpec(verbosity, doStag, getDigits, sspec, ctfdictOpt); +} + +framework::DataProcessorSpec getMFTEntropyDecoderSpec(int verbosity, bool doStag, bool getDigits, unsigned int sspec, const std::string& ctfdictOpt) +{ + return getEntropyDecoderSpec(verbosity, doStag, getDigits, sspec, ctfdictOpt); +} + } // namespace itsmft } // namespace o2 diff --git a/Detectors/ITSMFT/common/workflow/src/EntropyEncoderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/EntropyEncoderSpec.cxx index a824184330547..f80555efed384 100644 --- a/Detectors/ITSMFT/common/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/EntropyEncoderSpec.cxx @@ -18,7 +18,7 @@ #include "Framework/CCDBParamSpec.h" #include "DataFormatsITSMFT/CompCluster.h" #include "ITSMFTWorkflow/EntropyEncoderSpec.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsCommonDataFormats/DetID.h" using namespace o2::framework; @@ -27,20 +27,31 @@ namespace o2 { namespace itsmft { -EntropyEncoderSpec::EntropyEncoderSpec(o2::header::DataOrigin orig, bool selIR, const std::string& ctfdictOpt) - : mOrigin(orig), mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, orig == o2::header::gDataOriginITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT, ctfdictOpt), mSelIR(selIR) + +template +std::string EntropyEncoderSpec::getBinding(const std::string& name, int spec) +{ + return fmt::format("{}_{}", name, spec); +} + +template +EntropyEncoderSpec::EntropyEncoderSpec(bool doStag, bool selIR, const std::string& ctfdictOpt) + : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, doStag, ctfdictOpt), + mSelIR(selIR), + mDoStaggering(doStag) { - assert(orig == o2::header::gDataOriginITS || orig == o2::header::gDataOriginMFT); mTimer.Stop(); mTimer.Reset(); } -void EntropyEncoderSpec::init(o2::framework::InitContext& ic) +template +void EntropyEncoderSpec::init(o2::framework::InitContext& ic) { - mCTFCoder.init(ic); + mCTFCoder.template init(ic); } -void EntropyEncoderSpec::run(ProcessingContext& pc) +template +void EntropyEncoderSpec::run(ProcessingContext& pc) { if (pc.services().get().globalRunNumberChanged) { mTimer.Reset(); @@ -49,14 +60,20 @@ void EntropyEncoderSpec::run(ProcessingContext& pc) mTimer.Start(false); updateTimeDependentParams(pc); - auto compClusters = pc.inputs().get>("compClusters"); - auto pspan = pc.inputs().get>("patterns"); - auto rofs = pc.inputs().get>("ROframes"); + uint32_t nLayers = mDoStaggering ? DPLAlpideParam::getNLayers() : 1; + if (mSelIR) { mCTFCoder.setSelectedIRFrames(pc.inputs().get>("selIRFrames")); } - auto& buffer = pc.outputs().make>(Output{mOrigin, "CTFDATA", 0}); - auto iosize = mCTFCoder.encode(buffer, rofs, compClusters, pspan, mPattIdConverter, mStrobeLength); + o2::ctf::CTFIOSize iosize{}; + for (uint32_t iLayer = 0; iLayer < nLayers; iLayer++) { + auto compClusters = pc.inputs().get>(getBinding("compClusters", iLayer)); + auto pspan = pc.inputs().get>(getBinding("patterns", iLayer)); + auto rofs = pc.inputs().get>(getBinding("ROframes", iLayer)); + + auto& buffer = pc.outputs().make>(Output{Origin, "CTFDATA", iLayer}); + iosize += mCTFCoder.encode(buffer, rofs, compClusters, pspan, mPattIdConverter, iLayer); + } pc.outputs().snapshot({"ctfrep", 0}, iosize); if (mSelIR) { mCTFCoder.getIRFramesSelector().clear(); @@ -65,77 +82,90 @@ void EntropyEncoderSpec::run(ProcessingContext& pc) LOG(info) << iosize.asString() << " in " << mTimer.CpuTime() - cput << " s"; } -void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) +template +void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) { - LOGF(info, "%s Entropy Encoding total timing: Cpu: %.3e Real: %.3e s in %d slots", - mOrigin.as(), mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); + LOGP(info, "{} Entropy Encoding total timing: Cpu: {:.3e} Real: {:.3e} s in {} slots", + Origin.as(), mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -void EntropyEncoderSpec::updateTimeDependentParams(ProcessingContext& pc) +template +void EntropyEncoderSpec::updateTimeDependentParams(ProcessingContext& pc) { mCTFCoder.updateTimeDependentParams(pc, true); if (pc.services().get().globalRunNumberChanged) { // this params need to be queried only once if (mSelIR) { pc.inputs().get("cldict"); - if (mOrigin == o2::header::gDataOriginITS) { - pc.inputs().get*>("alppar"); - } else { - pc.inputs().get*>("alppar"); - } } } + pc.inputs().get*>("alppar"); } -void EntropyEncoderSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) +template +void EntropyEncoderSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) { - if (matcher == ConcreteDataMatcher(mOrigin, "CLUSDICT", 0)) { - LOG(info) << mOrigin.as() << " cluster dictionary updated"; + if (matcher == ConcreteDataMatcher(Origin, "CLUSDICT", 0)) { + LOG(info) << Origin.as() << " cluster dictionary updated"; mPattIdConverter.setDictionary((const TopologyDictionary*)obj); return; } // Note: strictly speaking, for Configurable params we don't need finaliseCCDB check, the singletons are updated at the CCDB fetcher level - if (matcher == ConcreteDataMatcher(mOrigin, "ALPIDEPARAM", 0)) { + if (matcher == ConcreteDataMatcher(Origin, "ALPIDEPARAM", 0)) { LOG(info) << "Alpide param updated"; - if (mOrigin == o2::header::gDataOriginITS) { - const auto& par = DPLAlpideParam::Instance(); - mStrobeLength = par.roFrameLengthInBC; - } else { - const auto& par = DPLAlpideParam::Instance(); - mStrobeLength = par.roFrameLengthInBC; - } return; } - if (mCTFCoder.finaliseCCDB(matcher, obj)) { + if (mCTFCoder.template finaliseCCDB(matcher, obj)) { return; } } -DataProcessorSpec getEntropyEncoderSpec(o2::header::DataOrigin orig, bool selIR, const std::string& ctfdictOpt) +template +DataProcessorSpec getEntropyEncoderSpec(bool doStag, bool selIR, const std::string& ctfdictOpt) { + constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; + constexpr o2::detectors::DetID ID{N == o2::detectors::DetID::ITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT}; + const auto& par = DPLAlpideParam::Instance(); + uint32_t nLayers = doStag ? DPLAlpideParam::getNLayers() : 1; + std::vector inputs; - inputs.emplace_back("compClusters", orig, "COMPCLUSTERS", 0, Lifetime::Timeframe); - inputs.emplace_back("patterns", orig, "PATTERNS", 0, Lifetime::Timeframe); - inputs.emplace_back("ROframes", orig, "CLUSTERSROF", 0, Lifetime::Timeframe); + std::vector outputs; + for (uint32_t iLayer = 0; iLayer < nLayers; ++iLayer) { + inputs.emplace_back(EntropyEncoderSpec::getBinding("compClusters", iLayer), Origin, "COMPCLUSTERS", iLayer, Lifetime::Timeframe); + inputs.emplace_back(EntropyEncoderSpec::getBinding("patterns", iLayer), Origin, "PATTERNS", iLayer, Lifetime::Timeframe); + inputs.emplace_back(EntropyEncoderSpec::getBinding("ROframes", iLayer), Origin, "CLUSTERSROF", iLayer, Lifetime::Timeframe); + outputs.emplace_back(Origin, "CTFDATA", iLayer, Lifetime::Timeframe); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); - inputs.emplace_back("cldict", orig, "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/ClusterDictionary", orig.as()))); - inputs.emplace_back("alppar", orig, "ALPIDEPARAM", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Config/AlpideParam", orig.as()))); + inputs.emplace_back("cldict", Origin, "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/ClusterDictionary", Origin.as()))); } + inputs.emplace_back("alppar", Origin, "ALPIDEPARAM", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Config/AlpideParam", Origin.as()))); if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { - inputs.emplace_back("ctfdict", orig, "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/CTFDictionaryTree", orig.as()))); + inputs.emplace_back("ctfdict", Origin, "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/CTFDictionaryTree", Origin.as()))); } + outputs.emplace_back(OutputSpec{{"ctfrep"}, Origin, "CTFENCREP", 0, Lifetime::Timeframe}); return DataProcessorSpec{ - orig == o2::header::gDataOriginITS ? "its-entropy-encoder" : "mft-entropy-encoder", + Origin == o2::header::gDataOriginITS ? "its-entropy-encoder" : "mft-entropy-encoder", inputs, - Outputs{{orig, "CTFDATA", 0, Lifetime::Timeframe}, - {{"ctfrep"}, orig, "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(orig, selIR, ctfdictOpt)}, + outputs, + AlgorithmSpec{adaptFromTask>(doStag, selIR, ctfdictOpt)}, Options{{"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, {"irframe-margin-fwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame upper boundary when selection is requested"}}, {"mem-factor", VariantType::Float, 1.f, {"Memory allocation margin factor"}}, {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } + +framework::DataProcessorSpec getITSEntropyEncoderSpec(bool doStag, bool selIR, const std::string& ctfdictOpt) +{ + return getEntropyEncoderSpec(doStag, selIR, ctfdictOpt); +} + +framework::DataProcessorSpec getMFTEntropyEncoderSpec(bool doStag, bool selIR, const std::string& ctfdictOpt) +{ + return getEntropyEncoderSpec(doStag, selIR, ctfdictOpt); +} + } // namespace itsmft } // namespace o2 diff --git a/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx index da1af34376ff1..79372160c6ade 100644 --- a/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/STFDecoderSpec.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -17,7 +17,6 @@ #include "Framework/WorkflowSpec.h" #include "Framework/ConfigParamRegistry.h" -#include "Framework/ControlService.h" #include "Framework/DeviceSpec.h" #include "Framework/CCDBParamSpec.h" #include "DataFormatsITSMFT/Digit.h" @@ -28,8 +27,7 @@ #include "ITSMFTReconstruction/ClustererParam.h" #include "ITSMFTReconstruction/GBTLink.h" #include "ITSMFTWorkflow/STFDecoderSpec.h" -#include "DetectorsCommonDataFormats/DetectorNameConf.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITSMFT/CompCluster.h" #include "DetectorsCommonDataFormats/DetID.h" #include "CommonUtils/StringUtils.h" @@ -47,20 +45,27 @@ using namespace o2::framework; ///_______________________________________ template STFDecoder::STFDecoder(const STFDecoderInp& inp, std::shared_ptr gr) - : mDoClusters(inp.doClusters), mDoPatterns(inp.doPatterns), mDoDigits(inp.doDigits), mDoCalibData(inp.doCalib), mAllowReporting(inp.allowReporting), mVerifyDecoder(inp.verifyDecoder), mInputSpec(inp.inputSpec), mGGCCDBRequest(gr) + : mDoClusters(inp.doClusters), mDoPatterns(inp.doPatterns), mDoDigits(inp.doDigits), mDoCalibData(inp.doCalib), mDoStaggering(inp.doStaggering), mAllowReporting(inp.allowReporting), mVerifyDecoder(inp.verifyDecoder), mInputSpec(inp.inputSpec), mGGCCDBRequest(gr) { mSelfName = o2::utils::Str::concat_string(Mapping::getName(), "STFDecoder"); mTimer.Stop(); mTimer.Reset(); + if (mDoStaggering) { + mLayers = Mapping::NLayers; + mEstNDig.resize(mLayers, 0); + mEstNClus.resize(mLayers, 0); + mEstNClusPatt.resize(mLayers, 0); + mEstNCalib.resize(mLayers, 0); + } } ///_______________________________________ template void STFDecoder::init(InitContext& ic) { + int lane = ic.services().get().inputTimesliceId; o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); try { - mDecoder = std::make_unique>(); auto v0 = o2::utils::Str::tokenize(mInputSpec, ':'); auto v1 = o2::utils::Str::tokenize(v0[1], '/'); auto v2 = o2::utils::Str::tokenize(v1[1], '?'); @@ -68,9 +73,23 @@ void STFDecoder::init(InitContext& ic) header::DataDescription dataDesc; dataOrig.runtimeInit(v1[0].c_str()); dataDesc.runtimeInit(v2[0].c_str()); - mDecoder->setUserDataOrigin(dataOrig); - mDecoder->setUserDataDescription(dataDesc); - mDecoder->init(); // is this no-op? + Mapping map; + for (int iLayer{0}; iLayer < mLayers; ++iLayer) { + auto& dec = mDecoder.emplace_back(std::make_unique>()); + dec->setUserDataOrigin(dataOrig); + dec->setUserDataDescription(dataDesc); + dec->init(); // is this no-op? + + if (mDoStaggering) { + std::vector filter; + for (const auto feeID : map.getLayer2FEEIDs(iLayer)) { + filter.emplace_back("filter", ConcreteDataMatcher{dataOrig, dataDesc, (o2::header::DataHeader::SubSpecificationType)feeID}); + } + dec->setInputFilter(filter); + } else { + dec->setInputFilter({InputSpec{"filter", ConcreteDataTypeMatcher(dataOrig, dataDesc)}}); + } + } } catch (const std::exception& e) { LOG(error) << "exception was thrown in decoder creation: " << e.what(); throw; @@ -81,10 +100,9 @@ void STFDecoder::init(InitContext& ic) mApplyNoiseMap = !ic.options().get("ignore-noise-map"); mUseClusterDictionary = !ic.options().get("ignore-cluster-dictionary"); try { - float fr = ic.options().get("rof-lenght-error-freq"); + float fr = ic.options().get("rof-length-error-freq"); mROFErrRepIntervalMS = fr <= 0. ? -1 : long(fr * 1e3); mNThreads = std::max(1, ic.options().get("nthreads")); - mDecoder->setNThreads(mNThreads); mUnmutExtraLanes = ic.options().get("unmute-extra-lanes"); mVerbosity = ic.options().get("decoder-verbosity"); auto dmpSz = ic.options().get("stop-raw-data-dumps-after-size"); @@ -103,13 +121,16 @@ void STFDecoder::init(InitContext& ic) if (mDumpOnError != int(GBTLink::RawDataDumps::DUMP_NONE) && (!dumpDir.empty() && !o2::utils::Str::pathIsDirectory(dumpDir))) { throw std::runtime_error(fmt::format("directory {} for raw data dumps does not exist", dumpDir)); } - mDecoder->setAlwaysParseTrigger(ic.options().get("always-parse-trigger")); - mDecoder->setAllowEmptyROFs(ic.options().get("allow-empty-rofs")); - mDecoder->setRawDumpDirectory(dumpDir); - mDecoder->setFillCalibData(mDoCalibData); - mDecoder->setVerifyDecoder(mVerifyDecoder); - bool ignoreRampUp = !ic.options().get("accept-rof-rampup-data"); - mDecoder->setSkipRampUpData(ignoreRampUp); + for (int iLayer{0}; iLayer < mLayers; ++iLayer) { + mDecoder[iLayer]->setNThreads(mNThreads); + mDecoder[iLayer]->setAlwaysParseTrigger(ic.options().get("always-parse-trigger")); + mDecoder[iLayer]->setAllowEmptyROFs(ic.options().get("allow-empty-rofs")); + mDecoder[iLayer]->setRawDumpDirectory(dumpDir); + mDecoder[iLayer]->setFillCalibData(mDoCalibData); + mDecoder[iLayer]->setVerifyDecoder(mVerifyDecoder); + bool ignoreRampUp = !ic.options().get("accept-rof-rampup-data"); + mDecoder[iLayer]->setSkipRampUpData(ignoreRampUp); + } } catch (const std::exception& e) { LOG(error) << "exception was thrown in decoder configuration: " << e.what(); throw; @@ -117,6 +138,31 @@ void STFDecoder::init(InitContext& ic) LOG(error) << "non-std::exception was thrown in decoder configuration"; throw; } + if (mDoCalibData) { + std::string warnMsg; + bool enforceEnsureContinuousROFinCalib = ic.options().get("enforce-continuous-rof-with-calib"); + if (ic.options().get("enforce-continuous-rof-with-calib")) { + warnMsg = "Calibration data requested but the ensureContinuousROF is explicitly enforced!"; + } else { + mRunEnsureContinuousROF = false; + warnMsg = "Calibration data requested, disabling ensureContinuousROF!"; + } + if (lane == 0) { + LOGP(alarm, "{}", warnMsg); + } else { + LOGP(info, "{}", warnMsg); + } + } + + mDisableRectifyContinuousROF = ic.options().get("disable-rectify-continuous-rof"); + if (mDisableRectifyContinuousROF && mRunEnsureContinuousROF) { + std::string warnMsg = "Rectification of clusters/digits is explicitly disabled after the ensureContinuousROF!"; + if (lane == 0) { + LOGP(alarm, "{}", warnMsg); + } else { + LOGP(info, "{}", warnMsg); + } + } if (mDoClusters) { mClusterer = std::make_unique(); @@ -135,141 +181,155 @@ void STFDecoder::run(ProcessingContext& pc) } if (firstCall) { firstCall = false; - mDecoder->setInstanceID(pc.services().get().inputTimesliceId); - mDecoder->setNInstances(pc.services().get().maxInputTimeslices); - mDecoder->setVerbosity(mDecoder->getInstanceID() == 0 ? mVerbosity : (mUnmutExtraLanes ? mVerbosity : -1)); - mAllowReporting &= (mDecoder->getInstanceID() == 0) || mUnmutExtraLanes; + for (int iLayer{0}; iLayer < mLayers; ++iLayer) { + mDecoder[iLayer]->setInstanceID(pc.services().get().inputTimesliceId); + mDecoder[iLayer]->setNInstances(pc.services().get().maxInputTimeslices); + mDecoder[iLayer]->setVerbosity(mDecoder[iLayer]->getInstanceID() == 0 ? mVerbosity : (mUnmutExtraLanes ? mVerbosity : -1)); + } + mAllowReporting &= (mDecoder[0]->getInstanceID() == 0) || mUnmutExtraLanes; } int nSlots = pc.inputs().getNofParts(0); double timeCPU0 = mTimer.CpuTime(), timeReal0 = mTimer.RealTime(); mTimer.Start(false); auto orig = Mapping::getOrigin(); - std::vector clusCompVec; - std::vector clusROFVec; - std::vector clusPattVec; - std::vector digVec; - std::vector calVec; - std::vector digROFVec; + // these are accumulated from each layer auto& chipStatus = pc.outputs().make>(Output{orig, "CHIPSSTATUS", 0}, (size_t)Mapping::getNChips()); + auto& linkErrors = pc.outputs().make>(Output{orig, "LinkErrors", 0}); + auto& decErrors = pc.outputs().make>(Output{orig, "ChipErrors", 0}); + auto& errMessages = pc.outputs().make>(Output{orig, "ErrorInfo", 0}); + auto& physTriggers = pc.outputs().make>(Output{orig, "PHYSTRIG", 0}); - try { - mDecoder->startNewTF(pc.inputs()); + for (uint32_t iLayer{0}; iLayer < mLayers; ++iLayer) { + const auto& par = AlpideParam::Instance(); + const int nROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / par.getROFLengthInBC(iLayer); + const int nROFsTF = nROFsPerOrbit * o2::base::GRPGeomHelper::getNHBFPerTF(); + int nLayer = mDoStaggering ? iLayer : -1; + std::vector clusCompVec; + std::vector clusROFVec; + std::vector clusPattVec; + std::vector digVec; + std::vector calVec; + std::vector digROFVec; if (mDoDigits) { - digVec.reserve(mEstNDig); - digROFVec.reserve(mEstNROF); + digVec.reserve(mEstNDig[iLayer]); + digROFVec.reserve(nROFsTF); } if (mDoClusters) { - clusCompVec.reserve(mEstNClus); - clusROFVec.reserve(mEstNROF); - clusPattVec.reserve(mEstNClusPatt); + clusCompVec.reserve(mEstNClus[iLayer]); + clusROFVec.reserve(nROFsTF); + clusPattVec.reserve(mEstNClusPatt[iLayer]); } if (mDoCalibData) { - calVec.reserve(mEstNCalib); - } - - mDecoder->setDecodeNextAuto(false); - o2::InteractionRecord lastIR{}, firstIR{0, pc.services().get().firstTForbit}; - int nTriggersProcessed = mDecoder->getNROFsProcessed(); - static long lastErrReportTS = 0; - while (mDecoder->decodeNextTrigger() >= 0) { - if ((!lastIR.isDummy() && lastIR >= mDecoder->getInteractionRecord()) || firstIR > mDecoder->getInteractionRecord()) { - const int MaxErrLog = 2; - static int errLocCount = 0; - if (errLocCount++ < MaxErrLog) { - LOGP(warn, "Impossible ROF IR {}, previous was {}, TF 1st IR was {}, discarding in decoding", mDecoder->getInteractionRecord().asString(), lastIR.asString(), firstIR.asString()); + calVec.reserve(mEstNCalib[iLayer]); + } + + try { + mDecoder[iLayer]->startNewTF(pc.inputs()); + mDecoder[iLayer]->setDecodeNextAuto(false); + + o2::InteractionRecord lastIR{}; + int nTriggersProcessed = mDecoder[iLayer]->getNROFsProcessed(); + static long lastErrReportTS = 0; + while (mDecoder[iLayer]->decodeNextTrigger() >= 0) { + if ((!lastIR.isDummy() && lastIR >= mDecoder[iLayer]->getInteractionRecord()) || mFirstIR > mDecoder[iLayer]->getInteractionRecord()) { + const int MaxErrLog = 2; + static int errLocCount = 0; + if (errLocCount++ < MaxErrLog) { + LOGP(warn, "Impossible ROF IR {}{}, previous was {}, TF 1st IR was {}, discarding in decoding", mDecoder[iLayer]->getInteractionRecord().asString(), ((mDoStaggering) ? std::format(" on layer {}", iLayer) : ""), lastIR.asString(), mFirstIR.asString()); + } + nTriggersProcessed = 0x7fffffff; // to account for a problem with event + continue; + } + lastIR = mDecoder[iLayer]->getInteractionRecord(); + mDecoder[iLayer]->fillChipsStatus(chipStatus); + if (mDoDigits || mClusterer->getMaxROFDepthToSquash(nLayer)) { // call before clusterization, since the latter will hide the digits + mDecoder[iLayer]->fillDecodedDigits(digVec, digROFVec); // lot of copying involved + if (mDoCalibData) { + mDecoder[iLayer]->fillCalibData(calVec); + } + } + if (mDoClusters && !mClusterer->getMaxROFDepthToSquash(nLayer)) { // !!! THREADS !!! + mClusterer->process(mNThreads, *mDecoder[iLayer].get(), &clusCompVec, mDoPatterns ? &clusPattVec : nullptr, &clusROFVec); } - nTriggersProcessed = 0x7fffffff; // to account for a problem with event - continue; } - lastIR = mDecoder->getInteractionRecord(); - mDecoder->fillChipsStatus(chipStatus); - if (mDoDigits || mClusterer->getMaxROFDepthToSquash()) { // call before clusterization, since the latter will hide the digits - mDecoder->fillDecodedDigits(digVec, digROFVec); // lot of copying involved - if (mDoCalibData) { - mDecoder->fillCalibData(calVec); + nTriggersProcessed = mDecoder[iLayer]->getNROFsProcessed() - nTriggersProcessed - 1; + + if ((nROFsTF != nTriggersProcessed) && mROFErrRepIntervalMS > 0 && mTFCounter > 1 && nTriggersProcessed > 0) { + long currTS = std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count(); + if (currTS - lastErrReportTS > mROFErrRepIntervalMS) { + LOGP(critical, "Inconsistent number of ROF per TF {}{} from parameters. Received {} from readout (muting further reporting for {} ms)", nROFsTF, ((mDoStaggering) ? std::format(" on layer {}", iLayer) : ""), nTriggersProcessed, mROFErrRepIntervalMS); + lastErrReportTS = currTS; } } - if (mDoClusters && !mClusterer->getMaxROFDepthToSquash()) { // !!! THREADS !!! - mClusterer->process(mNThreads, *mDecoder.get(), &clusCompVec, mDoPatterns ? &clusPattVec : nullptr, &clusROFVec); + if (mDoClusters && mClusterer->getMaxROFDepthToSquash(nLayer)) { + // Digits squashing require to run on a batch of digits and uses a digit reader, cannot (?) run with decoder + // - Setup decoder for running on a batch of digits + o2::itsmft::DigitPixelReader reader; + reader.setSquashingDepth(mClusterer->getMaxROFDepthToSquash(nLayer)); + reader.setSquashingDist(mClusterer->getMaxRowColDiffToMask()); // Sharing same parameter/logic with masking + reader.setMaxBCSeparationToSquash(mClusterer->getMaxBCSeparationToSquash(nLayer)); + reader.setDigits(digVec); + reader.setROFRecords(digROFVec); + reader.init(); + mClusterer->setMaxROFDepthToSquash(mClusterer->getMaxROFDepthToSquash(nLayer)); + mClusterer->process(mNThreads, reader, &clusCompVec, mDoPatterns ? &clusPattVec : nullptr, &clusROFVec); + } + } catch (const std::exception& e) { + static size_t nErr = 0; + auto maxWarn = o2::conf::VerbosityConfig::Instance().maxWarnRawParser; + if (++nErr < maxWarn) { + LOGP(alarm, "EXCEPTION {} in raw decoder{}, abandoning TF decoding {}", e.what(), ((mDoStaggering) ? std::format(" on layer {}", iLayer) : ""), nErr == maxWarn ? "(will mute further warnings)" : ""); } } - nTriggersProcessed = mDecoder->getNROFsProcessed() - nTriggersProcessed - 1; - - const auto& alpParams = o2::itsmft::DPLAlpideParam::Instance(); - int expectedTFSize = static_cast(o2::constants::lhc::LHCMaxBunches * o2::base::GRPGeomHelper::instance().getGRPECS()->getNHBFPerTF() / alpParams.roFrameLengthInBC); // 3564*32 / ROF Length in BS = number of ROFs per TF - if ((expectedTFSize != nTriggersProcessed) && mROFErrRepIntervalMS > 0 && mTFCounter > 1 && nTriggersProcessed > 0) { - long currTS = std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count(); - if (currTS - lastErrReportTS > mROFErrRepIntervalMS) { - LOGP(critical, "Inconsistent number of ROF per TF. From parameters: {} from readout: {} (muting further reporting for {} ms)", expectedTFSize, nTriggersProcessed, mROFErrRepIntervalMS); - lastErrReportTS = currTS; + if (mDoDigits) { + std::vector expDigRofVec; + if (ensureContinuousROF(digROFVec, expDigRofVec, iLayer, nROFsTF, "digits") && !mDisableRectifyContinuousROF) { + auto oldNDig = digVec.size(); + rectifyDigits(expDigRofVec, digVec); + LOGP(warn, "Rectified {} digits out of original {} on layer {} following ensureContinuousROF", digVec.size(), oldNDig, iLayer); + } + pc.outputs().snapshot(Output{orig, "DIGITS", iLayer}, digVec); + pc.outputs().snapshot(Output{orig, "DIGITSROF", iLayer}, expDigRofVec); + mEstNDig[iLayer] = std::max(mEstNDig[iLayer], size_t(digVec.size() * 1.2)); + if (mDoCalibData) { + pc.outputs().snapshot(Output{orig, "GBTCALIB", iLayer}, calVec); + mEstNCalib[iLayer] = std::max(mEstNCalib[iLayer], size_t(calVec.size() * 1.2)); } + LOG(debug) << mSelfName << " Decoded " << digVec.size() << " Digits in " << expDigRofVec.size() << " ROFs" << ((mDoStaggering) ? std::format(" on layer {}", iLayer) : ""); } - if (mDoClusters && mClusterer->getMaxROFDepthToSquash()) { - // Digits squashing require to run on a batch of digits and uses a digit reader, cannot (?) run with decoder - // - Setup decoder for running on a batch of digits - o2::itsmft::DigitPixelReader reader; - reader.setSquashingDepth(mClusterer->getMaxROFDepthToSquash()); - reader.setSquashingDist(mClusterer->getMaxRowColDiffToMask()); // Sharing same parameter/logic with masking - reader.setMaxBCSeparationToSquash(mClusterer->getMaxBCSeparationToSquash()); - reader.setDigits(digVec); - reader.setROFRecords(digROFVec); - reader.init(); - mClusterer->process(mNThreads, reader, &clusCompVec, mDoPatterns ? &clusPattVec : nullptr, &clusROFVec); - } - } catch (const std::exception& e) { - static size_t nErr = 0; - auto maxWarn = o2::conf::VerbosityConfig::Instance().maxWarnRawParser; - if (++nErr < maxWarn) { - LOGP(alarm, "EXCEPTION {} in raw decoder, abandoning TF decoding {}", e.what(), nErr == maxWarn ? "(will mute further warnings)" : ""); - } - } - if (mDoDigits) { - pc.outputs().snapshot(Output{orig, "DIGITS", 0}, digVec); - pc.outputs().snapshot(Output{orig, "DIGITSROF", 0}, digROFVec); - mEstNDig = std::max(mEstNDig, size_t(digVec.size() * 1.2)); - mEstNROF = std::max(mEstNROF, size_t(digROFVec.size() * 1.2)); - if (mDoCalibData) { - pc.outputs().snapshot(Output{orig, "GBTCALIB", 0}, calVec); - mEstNCalib = std::max(mEstNCalib, size_t(calVec.size() * 1.2)); + if (mDoClusters) { // we are not obliged to create vectors which are not requested, but other devices might not know the options of this one + std::vector expClusRofVec; + if (ensureContinuousROF(clusROFVec, expClusRofVec, iLayer, nROFsTF, "clusters") && !mDisableRectifyContinuousROF) { + auto oldNClus = clusCompVec.size(), oldNPatt = clusPattVec.size(); + rectifyClusters(expClusRofVec, clusCompVec, clusPattVec); + LOGP(warn, "Rectified {} clusters and {} patterns out of original {} and {} on layer {} following ensureContinuousROF", clusCompVec.size(), clusPattVec.size(), oldNClus, oldNPatt, iLayer); + } + pc.outputs().snapshot(Output{orig, "COMPCLUSTERS", iLayer}, clusCompVec); + pc.outputs().snapshot(Output{orig, "PATTERNS", iLayer}, clusPattVec); + pc.outputs().snapshot(Output{orig, "CLUSTERSROF", iLayer}, expClusRofVec); + mEstNClus[iLayer] = std::max(mEstNClus[iLayer], size_t(clusCompVec.size() * 1.2)); + mEstNClusPatt[iLayer] = std::max(mEstNClusPatt[iLayer], size_t(clusPattVec.size() * 1.2)); + LOG(info) << mSelfName << " Built " << clusCompVec.size() << " clusters in " << expClusRofVec.size() << " ROFs" << ((mDoStaggering) ? std::format(" on layer {}", iLayer) : ""); } - } - - if (mDoClusters) { // we are not obliged to create vectors which are not requested, but other devices might not know the options of this one - pc.outputs().snapshot(Output{orig, "COMPCLUSTERS", 0}, clusCompVec); - pc.outputs().snapshot(Output{orig, "PATTERNS", 0}, clusPattVec); - pc.outputs().snapshot(Output{orig, "CLUSTERSROF", 0}, clusROFVec); - mEstNClus = std::max(mEstNClus, size_t(clusCompVec.size() * 1.2)); - mEstNClusPatt = std::max(mEstNClusPatt, size_t(clusPattVec.size() * 1.2)); - mEstNROF = std::max(mEstNROF, size_t(clusROFVec.size() * 1.2)); - } - auto& linkErrors = pc.outputs().make>(Output{orig, "LinkErrors", 0}); - auto& decErrors = pc.outputs().make>(Output{orig, "ChipErrors", 0}); - auto& errMessages = pc.outputs().make>(Output{orig, "ErrorInfo", 0}); - mDecoder->collectDecodingErrors(linkErrors, decErrors, errMessages); - pc.outputs().snapshot(Output{orig, "PHYSTRIG", 0}, mDecoder->getExternalTriggers()); + mDecoder[iLayer]->collectDecodingErrors(linkErrors, decErrors, errMessages); + physTriggers.insert(physTriggers.end(), mDecoder[iLayer]->getExternalTriggers().begin(), mDecoder[iLayer]->getExternalTriggers().end()); - if (mDumpOnError != int(GBTLink::RawDataDumps::DUMP_NONE) && - (!mDumpFrom1stPipeline || pc.services().get().inputTimesliceId == 0)) { - mRawDumpedSize += mDecoder->produceRawDataDumps(mDumpOnError, pc.services().get()); - if (mRawDumpedSize > mMaxRawDumpsSize && mMaxRawDumpsSize > 0) { - LOGP(info, "Max total dumped size {} MB exceeded allowed limit, disabling further dumping", mRawDumpedSize / (1024 * 1024)); - mDumpOnError = int(GBTLink::RawDataDumps::DUMP_NONE); + if (mDumpOnError != int(GBTLink::RawDataDumps::DUMP_NONE) && + (!mDumpFrom1stPipeline || pc.services().get().inputTimesliceId == 0)) { + mRawDumpedSize += mDecoder[iLayer]->produceRawDataDumps(mDumpOnError, pc.services().get()); + if (mRawDumpedSize > mMaxRawDumpsSize && mMaxRawDumpsSize > 0) { + LOGP(info, "Max total dumped size {} MB exceeded allowed limit, disabling further dumping", mRawDumpedSize / (1024 * 1024)); + mDumpOnError = int(GBTLink::RawDataDumps::DUMP_NONE); + } } } - if (mDoClusters) { - LOG(debug) << mSelfName << " Built " << clusCompVec.size() << " clusters in " << clusROFVec.size() << " ROFs"; - } - if (mDoDigits) { - LOG(debug) << mSelfName << " Decoded " << digVec.size() << " Digits in " << digROFVec.size() << " ROFs"; - } mTimer.Stop(); auto tfID = pc.services().get().tfCounter; - LOG(debug) << mSelfName << " Total time for TF " << tfID << '(' << mTFCounter << ") : CPU: " << mTimer.CpuTime() - timeCPU0 << " Real: " << mTimer.RealTime() - timeReal0; mTFCounter++; } @@ -285,8 +345,11 @@ void STFDecoder::finalize() LOGF(info, "%s statistics:", mSelfName); LOGF(info, "%s Total STF decoding%s timing (w/o disk IO): Cpu: %.3e Real: %.3e s in %d slots", mSelfName, mDoClusters ? "/clustering" : "", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); - if (mDecoder && mAllowReporting) { - mDecoder->printReport(); + for (int iLayer{0}; iLayer < mLayers && mAllowReporting; ++iLayer) { + if (mDecoder[iLayer]) { + LOG_IF(info, mDoStaggering) << "Report for decoder of layer " << iLayer; + mDecoder[iLayer]->printReport(); + } } if (mClusterer) { mClusterer->print(); @@ -326,9 +389,17 @@ void STFDecoder::updateTimeDependentParams(ProcessingContext& pc) nROFsToSquash = 2 + int(clParams.maxSOTMUS / (rofBC * o2::constants::lhc::LHCBunchSpacingMUS)); // use squashing } mClusterer->setMaxROFDepthToSquash(clParams.maxBCDiffToSquashBias > 0 ? nROFsToSquash : 0); - mClusterer->print(); + if (mDoStaggering) { + for (int iLayer{0}; iLayer < mLayers; ++iLayer) { + mClusterer->addMaxBCSeparationToSquash(alpParams.getROFLengthInBC(iLayer) + clParams.getMaxBCDiffToSquashBias(iLayer)); + mClusterer->addMaxROFDepthToSquash((clParams.getMaxBCDiffToSquashBias(iLayer) > 0) ? 2 + int(clParams.maxSOTMUS / (alpParams.getROFLengthInBC(iLayer) * o2::constants::lhc::LHCBunchSpacingMUS)) : 0); + } + } + mClusterer->print(false); } } + mFirstTFOrbit = pc.services().get().firstTForbit; + mFirstIR = o2::InteractionRecord(0, mFirstTFOrbit); } ///_______________________________________ @@ -367,36 +438,178 @@ void STFDecoder::reset() mFinalizeDone = false; mTFCounter = 0; mTimer.Reset(); - if (mDecoder) { - mDecoder->reset(); + for (int iLayer{0}; iLayer < mLayers; ++iLayer) { + if (mDecoder[iLayer]) { + mDecoder[iLayer]->reset(); + } } if (mClusterer) { mClusterer->reset(); } } +///_______________________________________ +template +bool STFDecoder::ensureContinuousROF(const std::vector& rofVec, std::vector& expROFVec, int lr, int nROFsTF, const char* name) +{ + if (!mRunEnsureContinuousROF) { + expROFVec = rofVec; + return false; + } + const auto& par = AlpideParam::Instance(); + // ensure that the rof output is continuous + // we will preserve the digits/clusters as they are but the stray ROFs will be removed (leaving their clusters/digits unaddressed). + expROFVec.clear(); + expROFVec.resize(nROFsTF); + for (int iROF{0}; iROF < nROFsTF; ++iROF) { + auto& rof = expROFVec[iROF]; + int orb = iROF * par.getROFLengthInBC(lr) / o2::constants::lhc::LHCMaxBunches + mFirstTFOrbit; + int bc = iROF * par.getROFLengthInBC(lr) % o2::constants::lhc::LHCMaxBunches + par.getROFDelayInBC(lr); + o2::InteractionRecord ir(bc, orb); + rof.setBCData(ir); + rof.setROFrame(iROF); + rof.setNEntries(0); + rof.setFirstEntry(-1); + } + uint32_t prevEntry{0}; + for (const auto& rof : rofVec) { + const auto& ir = rof.getBCData(); + if (ir < mFirstIR) { + LOGP(warn, "Discard ROF {} preceding TF 1st orbit {}{}", ir.asString(), mFirstTFOrbit, ((mDoStaggering) ? std::format(" on layer {}", lr) : "")); + continue; + } + auto irToFirst = ir - mFirstIR; + if (irToFirst.toLong() - par.getROFDelayInBC(lr) < 0) { + LOGP(warn, "Discard ROF {} preceding TF 1st orbit {} due to imposed ROF delay{}", ir.asString(), mFirstTFOrbit, ((mDoStaggering) ? std::format(" on layer {}", lr) : "")); + continue; + } + irToFirst -= par.getROFDelayInBC(lr); + const long irROF = irToFirst.toLong() / par.getROFLengthInBC(lr); + if (irROF >= nROFsTF) { + LOGP(warn, "Discard ROF {} exceeding TF orbit range{}", ir.asString(), ((mDoStaggering) ? std::format(" on layer {}", lr) : "")); + continue; + } + auto& expROF = expROFVec[irROF]; + if (expROF.getNEntries() == 0) { + expROF.setFirstEntry(rof.getFirstEntry()); + expROF.setNEntries(rof.getNEntries()); + } else { + if (expROF.getNEntries() < rof.getNEntries()) { + LOGP(warn, "Repeating {} with {} {}, prefer to already processed instance with {} {}{}", rof.asString(), rof.getNEntries(), name, expROF.getNEntries(), name, ((mDoStaggering) ? std::format(" on layer {}", lr) : "")); + expROF.setFirstEntry(rof.getFirstEntry()); + expROF.setNEntries(rof.getNEntries()); + } else { + LOGP(warn, "Repeating {} with {} {}, discard preferring already processed instance with {} {}{}", rof.asString(), rof.getNEntries(), name, expROF.getNEntries(), name, ((mDoStaggering) ? std::format(" on layer {}", lr) : "")); + } + } + } + int prevLast{0}; + bool reReference = false; // in case a non-last ROF with non-0 entries is removed, ROF references need to be shifted and clusters/digits rewritten + for (auto& rof : expROFVec) { + if (rof.getFirstEntry() < 0) { + rof.setFirstEntry(prevLast); + } else if (rof.getFirstEntry() != prevLast) { + reReference = true; // there is jump + } + prevLast = rof.getFirstEntry() + rof.getNEntries(); + } + return reReference; +} + +///_______________________________________ +template +void STFDecoder::rectifyDigits(std::vector& rofVec, std::vector& digVec) +{ + // following ensureContinuousROF call some old ROFs might have been dropped, need to rebuild digits vector and rereference ROF + std::vector digVecTmp; + digVecTmp.reserve(digVec.size()); + auto beg0 = digVec.begin(); + for (auto& rof : rofVec) { + int firstEntry = digVecTmp.size(); + if (rof.getNEntries()) { + auto beg = beg0 + rof.getFirstEntry(), end = beg + rof.getNEntries(); + std::copy(beg, end, std::back_inserter(digVecTmp)); + } + rof.setFirstEntry(firstEntry); + } + digVec.swap(digVecTmp); +} + +///_______________________________________ +template +void STFDecoder::rectifyClusters(std::vector& rofVec, std::vector& clusVec, std::vector& pattVec) +{ + // following ensureContinuousROF call some old ROFs might have been dropped, need to rebuild clusters and patterns vectors and rereference ROF + std::vector clusVecTmp; + clusVecTmp.reserve(clusVec.size()); + std::vector pattVecTmp; + pattVecTmp.reserve(pattVec.size()); + const auto& dict = mClusterer->getDictionary(); + auto begCl0 = clusVec.begin(), begClForPatt = begCl0; + auto pattIt = pattVec.begin(); + + auto skipToLastPattern = [&begClForPatt, &pattIt, &dict](const decltype(begCl0) tgt) { + while (begClForPatt < tgt) { // iterate clusters skipping their patterns until we reach targed cluster + const auto& clp = *begClForPatt; + auto pattID = clp.getPatternID(); + if (pattID == itsmft::CompCluster::InvalidPatternID || dict.isGroup(pattID)) { + ClusterPattern::skipPattern(pattIt); + } + begClForPatt++; + } + }; + + for (auto& rof : rofVec) { + int firstEntry = clusVecTmp.size(); + if (rof.getNEntries()) { + auto begClROF = begCl0 + rof.getFirstEntry(), endClROF = begClROF + rof.getNEntries(); // clusters to copy start/end here + if (mDoPatterns) { + if (begClForPatt > begClROF) { // normally should no happen unless original ROFs were not ordered + begClForPatt = begCl0; // start from the beginning + } + skipToLastPattern(begClROF); // iterate clusters skipping their patterns until we reach the 1st cluster to be copied + auto begPattToCopy = pattIt; // the 1st pattern corresponding to the needed ROF + skipToLastPattern(endClROF); // iterate clusters skipping their patterns until we reach the last cluster to be copied + std::copy(begPattToCopy, pattIt, std::back_inserter(pattVecTmp)); + } + std::copy(begClROF, endClROF, std::back_inserter(clusVecTmp)); + } + // copy patterns corresponding to this ROF + rof.setFirstEntry(firstEntry); + } + clusVec.swap(clusVecTmp); + pattVec.swap(pattVecTmp); +} + ///_______________________________________ DataProcessorSpec getSTFDecoderSpec(const STFDecoderInp& inp) { std::vector outputs; auto inputs = o2::framework::select(inp.inputSpec.c_str()); - if (inp.doDigits) { - outputs.emplace_back(inp.origin, "DIGITS", 0, Lifetime::Timeframe); - outputs.emplace_back(inp.origin, "DIGITSROF", 0, Lifetime::Timeframe); - if (inp.doCalib) { - outputs.emplace_back(inp.origin, "GBTCALIB", 0, Lifetime::Timeframe); + uint32_t nLayers = 1; + if (inp.origin == o2::header::gDataOriginITS && inp.doStaggering) { + nLayers = DPLAlpideParam::getNLayers(); + } else if (inp.origin == o2::header::gDataOriginMFT && inp.doStaggering) { + nLayers = DPLAlpideParam::getNLayers(); + } + for (uint32_t iLayer = 0; iLayer < nLayers; ++iLayer) { + if (inp.doDigits) { + outputs.emplace_back(inp.origin, "DIGITS", iLayer, Lifetime::Timeframe); + outputs.emplace_back(inp.origin, "DIGITSROF", iLayer, Lifetime::Timeframe); + } + if (inp.doClusters) { + outputs.emplace_back(inp.origin, "COMPCLUSTERS", iLayer, Lifetime::Timeframe); + outputs.emplace_back(inp.origin, "CLUSTERSROF", iLayer, Lifetime::Timeframe); + // in principle, we don't need to open this input if we don't need to send real data, + // but other devices expecting it do not know about options of this device: problem? + // if (doClusters && doPatterns) + outputs.emplace_back(inp.origin, "PATTERNS", iLayer, Lifetime::Timeframe); } } - if (inp.doClusters) { - outputs.emplace_back(inp.origin, "COMPCLUSTERS", 0, Lifetime::Timeframe); - outputs.emplace_back(inp.origin, "CLUSTERSROF", 0, Lifetime::Timeframe); - // in principle, we don't need to open this input if we don't need to send real data, - // but other devices expecting it do not know about options of this device: problem? - // if (doClusters && doPatterns) - outputs.emplace_back(inp.origin, "PATTERNS", 0, Lifetime::Timeframe); + if (inp.doDigits && inp.doCalib) { + outputs.emplace_back(inp.origin, "GBTCALIB", 0, Lifetime::Timeframe); } outputs.emplace_back(inp.origin, "PHYSTRIG", 0, Lifetime::Timeframe); - outputs.emplace_back(inp.origin, "LinkErrors", 0, Lifetime::Timeframe); outputs.emplace_back(inp.origin, "ChipErrors", 0, Lifetime::Timeframe); outputs.emplace_back(inp.origin, "ErrorInfo", 0, Lifetime::Timeframe); @@ -424,11 +637,11 @@ DataProcessorSpec getSTFDecoderSpec(const STFDecoderInp& inp) true); // query only once all objects except mag.field return DataProcessorSpec{ - inp.deviceName, - inputs, - outputs, - inp.origin == o2::header::gDataOriginITS ? AlgorithmSpec{adaptFromTask>(inp, ggRequest)} : AlgorithmSpec{adaptFromTask>(inp, ggRequest)}, - Options{ + .name = inp.deviceName, + .inputs = inputs, + .outputs = outputs, + .algorithm = inp.origin == o2::header::gDataOriginITS ? AlgorithmSpec{adaptFromTask>(inp, ggRequest)} : AlgorithmSpec{adaptFromTask>(inp, ggRequest)}, + .options = Options{ {"nthreads", VariantType::Int, 1, {"Number of decoding/clustering threads"}}, {"decoder-verbosity", VariantType::Int, 0, {"Verbosity level (-1: silent, 0: errors, 1: headers, 2: data, 3: raw data dump) of 1st lane"}}, {"always-parse-trigger", VariantType::Bool, false, {"parse trigger word even if flags continuation of old trigger"}}, @@ -438,8 +651,10 @@ DataProcessorSpec getSTFDecoderSpec(const STFDecoderInp& inp) {"unmute-extra-lanes", VariantType::Bool, false, {"allow extra lanes to be as verbose as 1st one"}}, {"allow-empty-rofs", VariantType::Bool, false, {"record ROFs w/o any hit"}}, {"ignore-noise-map", VariantType::Bool, false, {"do not mask pixels flagged in the noise map"}}, + {"enforce-continuous-rof-with-calib", VariantType::Bool, false, {"enforce ensureContinuousROF call even when calibration data is requested (not recommended)"}}, + {"disable-rectify-continuous-rof", VariantType::Bool, false, {"do not rectify clusters and digits after ensureContinuousROF (not recommended)"}}, {"accept-rof-rampup-data", VariantType::Bool, false, {"do not discard data during ROF ramp up"}}, - {"rof-lenght-error-freq", VariantType::Float, 60.f, {"do not report ROF lenght error more frequently than this value, disable if negative"}}, + {"rof-length-error-freq", VariantType::Float, 60.f, {"do not report ROF length error more frequently than this value, disable if negative"}}, {"ignore-cluster-dictionary", VariantType::Bool, false, {"do not use cluster dictionary, always store explicit patterns"}}}}; } diff --git a/Detectors/ITSMFT/common/workflow/src/digit-reader-workflow.cxx b/Detectors/ITSMFT/common/workflow/src/digit-reader-workflow.cxx index 71b4b82a14126..f44a730525016 100644 --- a/Detectors/ITSMFT/common/workflow/src/digit-reader-workflow.cxx +++ b/Detectors/ITSMFT/common/workflow/src/digit-reader-workflow.cxx @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "ITSMFTWorkflow/DigitReaderSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "CommonUtils/ConfigurableParam.h" #include "Framework/ConfigParamSpec.h" #include "Framework/CallbacksPolicy.h" @@ -34,6 +35,7 @@ void customize(std::vector& workflowOptions) ConfigParamSpec{"runmft", VariantType::Bool, false, {"expect MFT data"}}, ConfigParamSpec{"suppress-triggers-output", VariantType::Bool, false, {"suppress dummy triggers output"}}, ConfigParamSpec{"configKeyValues", VariantType::String, "", {"semicolon separated key=value strings"}}}; + o2::itsmft::DPLAlpideParamInitializer::addConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -52,9 +54,11 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); if (cfgc.options().get("runmft")) { - wf.emplace_back(o2::itsmft::getMFTDigitReaderSpec(useMC, calib, withTriggers)); + bool doStag = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(cfgc); + wf.emplace_back(o2::itsmft::getMFTDigitReaderSpec(useMC, doStag, calib, withTriggers)); } else { - wf.emplace_back(o2::itsmft::getITSDigitReaderSpec(useMC, calib, withTriggers)); + bool doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(cfgc); + wf.emplace_back(o2::itsmft::getITSDigitReaderSpec(useMC, doStag, calib, withTriggers)); } o2::raw::HBFUtilsInitializer hbfIni(cfgc, wf); return wf; diff --git a/Detectors/ITSMFT/common/workflow/src/digit-writer-workflow.cxx b/Detectors/ITSMFT/common/workflow/src/digit-writer-workflow.cxx index 2d4fbea9aef6c..98391846c49c8 100644 --- a/Detectors/ITSMFT/common/workflow/src/digit-writer-workflow.cxx +++ b/Detectors/ITSMFT/common/workflow/src/digit-writer-workflow.cxx @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "ITSMFTWorkflow/DigitWriterSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "CommonUtils/ConfigurableParam.h" #include "Framework/ConfigParamSpec.h" #include "Framework/CompletionPolicyHelpers.h" @@ -32,7 +33,7 @@ void customize(std::vector& workflowOptions) ConfigParamSpec{"enable-calib-data", VariantType::Bool, false, {"enable writing GBT calibration data"}}, ConfigParamSpec{"runmft", VariantType::Bool, false, {"expect MFT data"}}, ConfigParamSpec{"configKeyValues", VariantType::String, "", {"semicolon separated key=value strings"}}}; - + o2::itsmft::DPLAlpideParamInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -49,9 +50,11 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); if (cfgc.options().get("runmft")) { - wf.emplace_back(o2::itsmft::getMFTDigitWriterSpec(useMC, true, calib)); + bool doStag = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(cfgc); + wf.emplace_back(o2::itsmft::getMFTDigitWriterSpec(useMC, doStag, true, calib)); } else { - wf.emplace_back(o2::itsmft::getITSDigitWriterSpec(useMC, true, calib)); + bool doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(cfgc); + wf.emplace_back(o2::itsmft::getITSDigitWriterSpec(useMC, doStag, true, calib)); } return wf; } diff --git a/Detectors/ITSMFT/common/workflow/src/entropy-encoder-workflow.cxx b/Detectors/ITSMFT/common/workflow/src/entropy-encoder-workflow.cxx index e0fc23ec70128..fed7268100428 100644 --- a/Detectors/ITSMFT/common/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/ITSMFT/common/workflow/src/entropy-encoder-workflow.cxx @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "ITSMFTWorkflow/EntropyEncoderSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "CommonUtils/ConfigurableParam.h" #include "Framework/ConfigParamSpec.h" @@ -26,7 +27,7 @@ void customize(std::vector& workflowOptions) ConfigParamSpec{"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, ConfigParamSpec{"ctf-dict", VariantType::String, "none", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, ConfigParamSpec{"select-ir-frames", VariantType::Bool, false, {"Subscribe and filter according to external IR Frames"}}}; - + o2::itsmft::DPLAlpideParamInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -41,9 +42,11 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); bool selIR = cfgc.options().get("select-ir-frames"); if (cfgc.options().get("runmft")) { - wf.emplace_back(o2::itsmft::getEntropyEncoderSpec("MFT", selIR, cfgc.options().get("ctf-dict"))); + bool doStag = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(cfgc); + wf.emplace_back(o2::itsmft::getMFTEntropyEncoderSpec(doStag, selIR, cfgc.options().get("ctf-dict"))); } else { - wf.emplace_back(o2::itsmft::getEntropyEncoderSpec("ITS", selIR, cfgc.options().get("ctf-dict"))); + bool doStag = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(cfgc); + wf.emplace_back(o2::itsmft::getITSEntropyEncoderSpec(doStag, selIR, cfgc.options().get("ctf-dict"))); } return wf; } diff --git a/Detectors/ITSMFT/common/workflow/src/stf-decoder-workflow.cxx b/Detectors/ITSMFT/common/workflow/src/stf-decoder-workflow.cxx index 7b1b97ec0c4f5..219e8915e11f3 100644 --- a/Detectors/ITSMFT/common/workflow/src/stf-decoder-workflow.cxx +++ b/Detectors/ITSMFT/common/workflow/src/stf-decoder-workflow.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -12,6 +12,7 @@ #include "ITSMFTWorkflow/STFDecoderSpec.h" #include "CommonUtils/ConfigurableParam.h" #include "Framework/ConfigParamSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; @@ -33,7 +34,7 @@ void customize(std::vector& workflowOptions) ConfigParamSpec{"dataspec", VariantType::String, "", {"selection string for the input data, if not provided Raw:/RAWDATA with DET=ITS or MFT will be used"}}, ConfigParamSpec{"report-dds-collection-index", VariantType::Int, -1, {"number of dpl collection allowed to produce decoding report (-1 means no limit)"}}, ConfigParamSpec{"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; - + o2::itsmft::DPLAlpideParamInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -53,6 +54,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) inp.askSTFDist = !cfgc.options().get("ignore-dist-stf"); inp.verifyDecoder = cfgc.options().get("verify"); inp.inputSpec = cfgc.options().get("dataspec"); + // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); @@ -62,12 +64,14 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) } inp.origin = o2::header::gDataOriginMFT; inp.deviceName = "mft-stf-decoder"; + inp.doStaggering = o2::itsmft::DPLAlpideParamInitializer::isMFTStaggeringEnabled(cfgc); } else { if (inp.inputSpec.empty()) { inp.inputSpec = "itsRAW:ITS/RAWDATA"; } inp.origin = o2::header::gDataOriginITS; inp.deviceName = "its-stf-decoder"; + inp.doStaggering = o2::itsmft::DPLAlpideParamInitializer::isITSStaggeringEnabled(cfgc); } inp.allowReporting = true; diff --git a/Detectors/MUON/MCH/Base/include/MCHBase/Trackable.h b/Detectors/MUON/MCH/Base/include/MCHBase/Trackable.h index a862be411cb35..ef556d49a9201 100644 --- a/Detectors/MUON/MCH/Base/include/MCHBase/Trackable.h +++ b/Detectors/MUON/MCH/Base/include/MCHBase/Trackable.h @@ -38,11 +38,21 @@ bool isTrackable(std::array itemsPerChamber, /** Return the number of items per chamber. * * @tparam T the type of items : implementation exists so far - * only for mch::Digit (clusters and pre-clusters to come next) + * for deIds (int) and mch::Digit */ template std::array perChamber(gsl::span items); +/** Return the number of items per chamber. + * + * @tparam T1 the type of items : implementation exists so far + * for mch::PreCluster + * @tparam T2 the type of subitems pointed to by items, + * e.g. mch::Digit attached to mch::PreCluster + */ +template +std::array perChamber(gsl::span items, gsl::span subitems); + /** Return the number of items per station (1 station==2 chambers). */ template std::array perStation(gsl::span items) diff --git a/Detectors/MUON/MCH/Base/src/Trackable.cxx b/Detectors/MUON/MCH/Base/src/Trackable.cxx index c25b12945cb90..0545f7cb1eac5 100644 --- a/Detectors/MUON/MCH/Base/src/Trackable.cxx +++ b/Detectors/MUON/MCH/Base/src/Trackable.cxx @@ -10,7 +10,9 @@ // or submit itself to any jurisdiction. #include "MCHBase/Trackable.h" + #include "DataFormatsMCH/Digit.h" +#include "MCHBase/PreCluster.h" namespace o2::mch { @@ -59,7 +61,27 @@ std::array perChamber(gsl::span digits) for (const auto& digit : digits) { nofDigits[digit.getDetID() / 100 - 1]++; } + // do not count isolated digits (at least 2 are required for a cluster) + for (auto i = 0; i < 10; ++i) { + if (nofDigits[i] == 1) { + nofDigits[i] = 0; + } + } return nofDigits; } +/** Specialization of perChamber for PreClusters */ +template <> +std::array perChamber(gsl::span preclusters, gsl::span digits) +{ + std::array nofPreclusters{}; + for (const auto& precluster : preclusters) { + // only consider preclusters made of at least 2 digits + if (precluster.nDigits > 1) { + nofPreclusters[digits[precluster.firstDigit].getDetID() / 100 - 1]++; + } + } + return nofPreclusters; +} + } // namespace o2::mch diff --git a/Detectors/MUON/MCH/Clustering/include/MCHClustering/ClusterizerParam.h b/Detectors/MUON/MCH/Clustering/include/MCHClustering/ClusterizerParam.h index a24a8543af2cb..8f6f28a1f45d7 100644 --- a/Detectors/MUON/MCH/Clustering/include/MCHClustering/ClusterizerParam.h +++ b/Detectors/MUON/MCH/Clustering/include/MCHClustering/ClusterizerParam.h @@ -37,6 +37,8 @@ struct ClusterizerParam : public o2::conf::ConfigurableParamHelper ROFFilter createTrackableFilter(gsl::span items, @@ -46,6 +45,33 @@ ROFFilter }; } +/** Returns a ROFRecord filter that selects ROFs that are trackable. + * + * The returned filter is a function that takes a ROFRecord and returns + * a boolean. + * + * @param items : the items "pointed to" by the ROFRecords (preclusters, ...) + * @param subitems : the subitems "pointed to" by the items (digits, ...) + * + * @param requestStation : @ref isTrackable + * @param moreCandidates : @ref isTrackable + * + * @tparam T1 : the type of the items pointed to by the ROFRecords + * @tparam T2 : the type of the subitems pointed to by the items + */ +template +ROFFilter + createTrackableFilter(gsl::span items, + gsl::span subitems, + std::array requestStation = {true, true, true, true, true}, + bool moreCandidates = false) +{ + return [items, subitems, requestStation, moreCandidates](const ROFRecord& rof) { + std::array nofItemsPerChamber = perChamber(items.subspan(rof.getFirstIdx(), rof.getNEntries()), subitems); + return isTrackable(nofItemsPerChamber, requestStation, moreCandidates); + }; +} + } // namespace o2::mch #endif diff --git a/Detectors/MUON/MCH/Workflow/CMakeLists.txt b/Detectors/MUON/MCH/Workflow/CMakeLists.txt index e0fce7d103df7..f97c78526f21e 100644 --- a/Detectors/MUON/MCH/Workflow/CMakeLists.txt +++ b/Detectors/MUON/MCH/Workflow/CMakeLists.txt @@ -30,6 +30,7 @@ o2_add_library(MCHWorkflow O2::MCHPreClustering O2::MCHRawCommon O2::MCHRawDecoder + O2::MCHROFFiltering ROOT::TreePlayer ) diff --git a/Detectors/MUON/MCH/Workflow/src/ClusterFinderOriginalSpec.cxx b/Detectors/MUON/MCH/Workflow/src/ClusterFinderOriginalSpec.cxx index 8344d2837b814..e369e514b0f2e 100644 --- a/Detectors/MUON/MCH/Workflow/src/ClusterFinderOriginalSpec.cxx +++ b/Detectors/MUON/MCH/Workflow/src/ClusterFinderOriginalSpec.cxx @@ -35,13 +35,16 @@ #include "Framework/Logger.h" #include "CommonUtils/ConfigurableParam.h" -#include "DataFormatsMCH/ROFRecord.h" +#include "DataFormatsMCH/Cluster.h" #include "DataFormatsMCH/Digit.h" +#include "DataFormatsMCH/ROFRecord.h" #include "MCHBase/Error.h" #include "MCHBase/ErrorMap.h" #include "MCHBase/PreCluster.h" -#include "DataFormatsMCH/Cluster.h" +#include "MCHBase/TrackerParam.h" #include "MCHClustering/ClusterFinderOriginal.h" +#include "MCHClustering/ClusterizerParam.h" +#include "MCHROFFiltering/TrackableFilter.h" namespace o2 { @@ -94,11 +97,35 @@ class ClusterFinderOriginalTask auto& clusters = pc.outputs().make>(OutputRef{"clusters"}); auto& usedDigits = pc.outputs().make>(OutputRef{"clusterdigits"}); + // create the trackable ROF filtering if needed + ROFFilter trackable{}; + if (ClusterizerParam::Instance().onlyTrackable) { + const auto& trackerParam = TrackerParam::Instance(); + std::array requestStation{ + trackerParam.requestStation[0], + trackerParam.requestStation[1], + trackerParam.requestStation[2], + trackerParam.requestStation[3], + trackerParam.requestStation[4]}; + trackable = createTrackableFilter(preClusters, digits, requestStation, trackerParam.moreCandidates); + } + clusterROFs.reserve(preClusterROFs.size()); auto& errorMap = mClusterFinder.getErrorMap(); errorMap.clear(); + int nFilteredRofs = 0; + int nFilteredPreClusters = 0; for (const auto& preClusterROF : preClusterROFs) { + // filter out non-trackable ROFs if requested + if (ClusterizerParam::Instance().onlyTrackable && !trackable(preClusterROF)) { + // create an empty cluster ROF + clusterROFs.emplace_back(preClusterROF.getBCData(), clusters.size(), 0, preClusterROF.getBCWidth()); + continue; + } + ++nFilteredRofs; + nFilteredPreClusters += preClusterROF.getNEntries(); + // prepare to clusterize the current ROF auto clusterOffset = clusters.size(); mClusterFinder.reset(); @@ -137,8 +164,8 @@ class ClusterFinderOriginalTask }); mErrorMap.add(errorMap); - LOGP(info, "Found {:4d} clusters from {:4d} preclusters in {:2d} ROFs", - clusters.size(), preClusters.size(), preClusterROFs.size()); + LOGP(info, "Found {:4d} clusters from {:4d} preclusters (out of {:4d}) in {:2d} filtered ROFs (out of {:2d})", + clusters.size(), nFilteredPreClusters, preClusters.size(), nFilteredRofs, preClusterROFs.size()); } private: diff --git a/Detectors/PHOS/calib/src/PHOSEnergyCalibDevice.cxx b/Detectors/PHOS/calib/src/PHOSEnergyCalibDevice.cxx index 979ca690c03e0..51956a4dbf96e 100644 --- a/Detectors/PHOS/calib/src/PHOSEnergyCalibDevice.cxx +++ b/Detectors/PHOS/calib/src/PHOSEnergyCalibDevice.cxx @@ -133,6 +133,7 @@ void PHOSEnergyCalibDevice::run(o2::framework::ProcessingContext& pc) LOG(warning) << "LHCPeriod is not available, using current month " << mLHCPeriod; } } + o2::base::TFIDInfoHelper::fillTFIDInfo(pc, mCalibrator->getCurrentTFInfo()); mCalibrator->process(tfcounter, clusters, cluelements, cluTR, mOutputDigits); fillOutputTree(); diff --git a/Detectors/PHOS/calib/src/PHOSL1phaseCalibDevice.cxx b/Detectors/PHOS/calib/src/PHOSL1phaseCalibDevice.cxx index c50a3faff4b01..baade755f2adf 100644 --- a/Detectors/PHOS/calib/src/PHOSL1phaseCalibDevice.cxx +++ b/Detectors/PHOS/calib/src/PHOSL1phaseCalibDevice.cxx @@ -30,7 +30,7 @@ void PHOSL1phaseCalibDevice::init(o2::framework::InitContext& ic) void PHOSL1phaseCalibDevice::run(o2::framework::ProcessingContext& pc) { - + o2::base::TFIDInfoHelper::fillTFIDInfo(pc, mCalibrator->getCurrentTFInfo()); o2::base::GRPGeomHelper::instance().checkUpdates(pc); auto crTime = pc.services().get().creation; if (mRunStartTime == 0 || crTime < mRunStartTime) { diff --git a/Detectors/PHOS/calib/src/PHOSRunbyrunCalibDevice.cxx b/Detectors/PHOS/calib/src/PHOSRunbyrunCalibDevice.cxx index cf767cb76c7ad..3c59ed8477940 100644 --- a/Detectors/PHOS/calib/src/PHOSRunbyrunCalibDevice.cxx +++ b/Detectors/PHOS/calib/src/PHOSRunbyrunCalibDevice.cxx @@ -54,6 +54,7 @@ void PHOSRunbyrunCalibDevice::run(o2::framework::ProcessingContext& pc) auto tfcounter = o2::header::get(pc.inputs().get("clusters").header)->tfCounter; auto clusters = pc.inputs().get>("clusters"); auto cluTR = pc.inputs().get>("cluTR"); + o2::base::TFIDInfoHelper::fillTFIDInfo(pc, mCalibrator->getCurrentTFInfo()); LOG(detail) << "Processing TF with " << clusters.size() << " clusters and " << cluTR.size() << " TriggerRecords"; mCalibrator->process(tfcounter, clusters, cluTR); } diff --git a/Detectors/PHOS/calib/src/PHOSTurnonCalibDevice.cxx b/Detectors/PHOS/calib/src/PHOSTurnonCalibDevice.cxx index 52ec8cef0b438..c2b04aea381a3 100644 --- a/Detectors/PHOS/calib/src/PHOSTurnonCalibDevice.cxx +++ b/Detectors/PHOS/calib/src/PHOSTurnonCalibDevice.cxx @@ -49,7 +49,7 @@ void PHOSTurnonCalibDevice::run(o2::framework::ProcessingContext& pc) auto cellTR = pc.inputs().get>("cellTriggerRecords"); auto clusters = pc.inputs().get>("clusters"); auto cluTR = pc.inputs().get>("clusterTriggerRecords"); - + o2::base::TFIDInfoHelper::fillTFIDInfo(pc, mCalibrator->getCurrentTFInfo()); LOG(detail) << "[PHOSTurnonCalibDevice - run] Received " << cells.size() << " cells and " << clusters.size() << " clusters, running calibration"; mCalibrator->process(tfcounter, cells, cellTR, clusters, cluTR); diff --git a/Detectors/Raw/README.md b/Detectors/Raw/README.md index 557245030b980..1fece239723ec 100644 --- a/Detectors/Raw/README.md +++ b/Detectors/Raw/README.md @@ -548,6 +548,87 @@ list of detectors for which raw outputs are discarded. The raw data will be propagated (if present) only if the detector is selected in `--onlyDet` and `NOT` selected in `--non-raw-only-det`. The non-raw data will be propagated (if defined for the given detector and present in the file) only if the detector is selected in `--onlyDet` and `NOT` selected in `--raw-only-det`. +## Raw TF (DD format) dumping workflow + +Use `o2-raw-tf-dump-workflow` to dump raw TF data in DD format. The options are: +``` +--dataspec arg (=tst:TST/A) +``` +Optional selection string for the data to be dumped, e.g. the same string supplied to the input raw proxy +``` +--triggerspec arg (="") +``` +Selection string for the external trigger to dump particular TF. Must be contained in the `--dataspec`. The workflow will loop over all available trigger inputs, interpreting them as span: any `span[0]==true` will trigger writing process (modulo throttling). +``` +--include-deadbeef (false) +``` +Include data with DPL-generated 0xdeadbeef subspecs (for data missing in the original TF). +``` +--exclude-trigger-specs (="") + +``` +Ignore trigger seen in these inputs of triggerspec (e.g. to suppress noisy trigger inputs) +``` +--max-dump-rate arg (=0) +``` +Fraction in (`%`) of TFs to dump. W/o external trigger: random(>0) or periodic(<0) rejection. With external trigger: throttle dumping to have the lowest estimated acceptance rate compatible with this rate. +``` +--rate-est-conf-limit arg (=0.05) +``` +Quantile for the lowest rate estimate confidence limit +``` +--max-warn arg (=5) +``` +If throttling, max allowed warnings +``` +--mute-warn-period arg (=100) +``` +Mute warnings about throttling for this number of TFs +``` +--output-dir arg (=none) +``` +Dumped TFs output directory, must exist. `none` means current dir., `/dev/null`: ignort writing (dry run) +``` +--meta-output-dir arg (=/dev/null) +``` +TF metadata output directory, must exist (if not /dev/null, in which case the metadata will not be created) +``` +--md5-for-meta (false) +``` +Fill CTF file MD5 sum in the metadata file +``` +--min-file-size arg (=0) +``` +Accumulate TFs until given file size reached +``` +--max-file-size arg (=0) +``` +If > 0, try to avoid exceeding given file size, also used for space check +``` +--max-tf-per-file arg (=0) +``` +If > 0, avoid storing more than requested CTFs per file +``` +--require-free-disk arg (=0) +``` +Pause writing op. if available disk space is below this margin, in bytes if >0, as a fraction of total if <0 +``` +--wait-for-free-disk arg (=10) +``` +If paused due to the low disk space, recheck after this time (in s) +``` +--max-wait-for-free-disk arg (=60) +``` +Produce fatal if paused due to the low disk space for more than time in seconds. +``` +--verbosity-level (=0) +``` +Verbose mode: 1: decision on every TF, 2: details of saved TF, 3: more details. +``` +--ignore-partition-run-dir +``` +Do not creare partition-run directory in output-dir + ## TF rate limiting To apply TF rate limiting (i.e. make sure that no more than N TFs are in processing) provide `--timeframes-rate-limit --timeframes-rate-limit-ipcid ` diff --git a/Detectors/Raw/TFReaderDD/CMakeLists.txt b/Detectors/Raw/TFReaderDD/CMakeLists.txt index 12ecc9ca8795d..f87d1b5a7704e 100644 --- a/Detectors/Raw/TFReaderDD/CMakeLists.txt +++ b/Detectors/Raw/TFReaderDD/CMakeLists.txt @@ -26,3 +26,10 @@ o2_add_executable(tf-reader-workflow SOURCES src/TFReaderSpec.cxx src/tf-reader-workflow.cxx PUBLIC_LINK_LIBRARIES O2::TFReaderDD) + + +o2_add_executable(tf-dump-workflow + COMPONENT_NAME raw + SOURCES src/RawTFDumpSpec.cxx + src/tf-data-dump-workflow.cxx + PUBLIC_LINK_LIBRARIES O2::TFReaderDD) diff --git a/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFile.h b/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFile.h index 340027642b74c..eeabf8e8d4117 100644 --- a/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFile.h +++ b/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFile.h @@ -21,6 +21,8 @@ #include #include +#include "Framework/DataSpecUtils.h" +#include "Framework/OutputSpec.h" #include "Framework/Logger.h" namespace o2 @@ -151,13 +153,13 @@ struct SubTimeFrameFileMeta { /// std::uint64_t mWriteTimeMs; - auto getTimePoint() + auto getTimePoint() const { using namespace std::chrono; return time_point{milliseconds{mWriteTimeMs}}; } - std::string getTimeString() + std::string getTimeString() const { using namespace std::chrono; std::time_t lTime = system_clock::to_time_t(getTimePoint()); @@ -167,6 +169,11 @@ struct SubTimeFrameFileMeta { return lTimeStream.str(); } + const std::string info() const + { + return fmt::format("Size in file: {} Time: {} Version: {}", mStfSizeInFile, getTimeString(), mStfFileVersion); + } + SubTimeFrameFileMeta(const std::uint64_t pStfSize) : SubTimeFrameFileMeta() { @@ -220,6 +227,11 @@ struct SubTimeFrameFileDataIndex { static_assert(sizeof(DataIndexElem) == 48, "DataIndexElem changed -> Binary compatibility is lost!"); } + + const std::string info() const + { + return fmt::format("DH: {} Cnt:{} Size:{} Offset:{}", o2::framework::DataSpecUtils::describe(o2::framework::OutputSpec{mDataOrigin, mDataDescription, mSubSpecification}), mDataBlockCnt, mSize, mOffset); + } }; SubTimeFrameFileDataIndex() = default; @@ -240,6 +252,8 @@ struct SubTimeFrameFileDataIndex { return sizeof(o2::header::DataHeader) + (sizeof(DataIndexElem) * mDataIndex.size()); } + const std::vector& getDataIndex() const { return mDataIndex; } + friend std::ostream& operator<<(std::ostream& pStream, const SubTimeFrameFileDataIndex& pIndex); private: diff --git a/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFileReader.h b/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFileReader.h index 3b926e0a79206..2b7d2b7ab8e74 100644 --- a/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFileReader.h +++ b/Detectors/Raw/TFReaderDD/include/TFReaderDD/SubTimeFrameFileReader.h @@ -46,11 +46,11 @@ class SubTimeFrameFileReader public: SubTimeFrameFileReader() = delete; - SubTimeFrameFileReader(const std::string& pFileName, o2::detectors::DetID::mask_t detMask); + SubTimeFrameFileReader(const std::string& pFileName, o2::detectors::DetID::mask_t detMask, int verb, bool sup0xccdb, bool repaireHeaders, bool rejectDistSTF); ~SubTimeFrameFileReader(); /// Read a single TF from the file - std::unique_ptr read(fair::mq::Device* device, const std::vector& outputRoutes, const std::string& rawChannel, size_t slice, bool sup0xccdb, int verbosity); + std::unique_ptr read(fair::mq::Device* device, const std::vector& outputRoutes, const std::string& rawChannel, size_t slice); /// Tell the current position of the file inline std::uint64_t position() const { return mFileMapOffset; } @@ -76,6 +76,13 @@ class SubTimeFrameFileReader std::uint64_t mFileMapOffset = 0; std::uint64_t mFileSize = 0; + int mVerbosity = 0; + bool mSup0xccdb = true; + bool mRepaireHeaders = true; + bool mRejectDistSTF = true; + + const std::string describeHeader(const o2::header::DataHeader& hd, bool full = false) const; + // helper to make sure written chunks are buffered, only allow pointers template ::value>> diff --git a/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx new file mode 100644 index 0000000000000..03bd26ae0deb9 --- /dev/null +++ b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.cxx @@ -0,0 +1,611 @@ +// 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. + +#include "Framework/WorkflowSpec.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/RawDeviceService.h" +#include "Framework/DataProcessingHelpers.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/Task.h" +#include "Framework/DataTakingContext.h" +#include "Framework/TimingInfo.h" +#include "DataFormatsParameters/GRPECSObject.h" +#include "DetectorsCommonDataFormats/FileMetaData.h" +#include "RawTFDumpSpec.h" +#include "TFReaderDD/SubTimeFrameFile.h" +#include "CommonUtils/NameConf.h" +#include "CommonUtils/FileSystemUtils.h" +#include "CommonUtils/StringUtils.h" +#include "Algorithm/RangeTokenizer.h" +#include +#include +#include +#include +#include + +namespace o2::rawdd +{ +namespace o2h = o2::header; +using namespace o2::framework; +using DataHeader = o2::header::DataHeader; +using DetID = o2::detectors::DetID; +using ios = std::ios_base; + +class RawTFDump : public Task +{ + public: + static constexpr o2h::DataDescription DESCRaw{"RAWDATA"}, DESCCRaw{"CRAWDATA"}; + + RawTFDump(const std::string& trigger); + void init(InitContext& ic) final; + void run(ProcessingContext& pc) final; + void endOfStream(EndOfStreamContext& ec) final; + + private: + bool triggerTF(ProcessingContext& pc); + void updateTimeDependentParams(ProcessingContext& pc); + void prepareTFForWriting(ProcessingContext& pc); + size_t getTFSizeInFile() const; + size_t getCurrentFileSize(); + void prepareTFFile(); + void closeTFFile(); + bool checkFreeSpace(ProcessingContext& pc); + std::string reportRates() const; + + SubTimeFrameFileDataIndex mTFDataIndex; + std::vector> mTFData; + std::map> mDataMap; + std::vector mFilter{}; + std::vector mTriggerFilter{}; + std::vector mExclTriggerFilter{}; + + size_t mTFSize = 0; + size_t mMinFileSize = 0; // if > 0, accumulate TFs in the same file until the total size exceeds this minimum + size_t mMaxFileSize = 0; // if > MinSize, and accumulated size will exceed this value, stop accumulation (even if mMinFileSize is not reached) + + int mNTFsSeen = 0; // total number of TFs seen + int mNTFsExtTrig = 0; // total nunber of TFs externally triggered + int mNTFsAccepted = 0; // total number of TFs written + int mNTFsInFile = 0; // total number of TFs accumulated in the current file + int mNTFFiles = 0; // total number of TF files written + int mLastWarned = 0; // TF when last warned about throttling + int mMaxTFPerFile = 0; // max TFs per files to store + int mNWarnThrottle = 0; // number of times we warned about the throttling + int mMaxWarnThrottle = 0; // max allowed warnings about the throttling + int mWarnThrottleTF = 0; // min period (in TFs) between the warnings about the throttling + int mWaitDiskFull = 0; // if mCheckDiskFull triggers, pause for this amount of ms before new attempt + int mWaitDiskFullMax = -1; // produce fatal mCheckDiskFull block the workflow for more than this time (in ms) + float mCheckDiskFull = 0.; // wait for if available abs. disk space is < mCheckDiskFull (if >0) or if its fraction is < -mCheckDiskFull (if <0) + float mMaxAccRate = 0.f; // max acceptance rate + float mConfLim = 0.05f; // confidence limit for rate esimate (lower quantile) + float mRateEstAccLow = 0.f; // lower limit on accepted TFs rate + float mRateEstAccUpp = 0.f; // upper limit on accepted TFs rate + float mRateEstTrgLow = 0.f; // lower limit on triggered TFs rate + float mRateEstTrgUpp = 0.f; // upper limit on triggered TFs rate + + bool mFillMD5 = false; + bool mWriteTF = true; // for dry run + bool mStoreMetaFile = false; + bool mCreateRunEnvDir = true; + bool mAcceptCurrentTF = false; + bool mRejectDEADBEEF = false; + bool mRejectDistSTF = true; + int mVerbose = 0; + std::vector mTFOrbits{}; // 1st orbits of TF accumulated in current file + o2::framework::DataTakingContext mDataTakingContext{}; + o2::framework::TimingInfo mTimingInfo{}; + + std::string mTrigger{}; // external trigger input + std::string mExclTriggerSpecs{}; // trigger specs to ignore + std::string mHostName{}; + std::string mTFDir{}; + std::string mTFMetaFileDir = "/dev/null"; + std::string mCurrentTFFileName{}; + std::string mCurrentTFFileNameFull{}; + std::string mCurrentTFFileNameFullTmp{}; + std::string mMetaDataType{}; + + static constexpr size_t MiB = 1ul << 20; + static constexpr std::streamsize sBuffSize = MiB; // 1 MiB + static constexpr std::streamsize sChunkSize = 512; + static const std::string TMPFileEnding; + std::unique_ptr mFileBuf; + std::ofstream mFile; + std::uniform_real_distribution mUniformDist{0.0, 100.0}; + std::default_random_engine mRGen; + + // helper to make sure the written blocks are buffered + template < + typename pointer, + typename std::enable_if< + std::is_pointer::value && // pointers only + (std::is_void>::value || // void* or standard layout! + std::is_standard_layout>::value)>::type* = nullptr> + void buffered_write(const pointer p, std::streamsize pCount) + { + // make sure we're not doing a short write + assert((pCount % sizeof(std::conditional_t>::value, + char, std::remove_pointer_t>) == + 0) && + "Performing short write?"); + + const char* lPtr = reinterpret_cast(p); + // avoid the optimization if the write is large enough + if (pCount >= sBuffSize) { + mFile.write(lPtr, pCount); + } else { + // split the write to smaller chunks + while (pCount > 0) { + const auto lToWrite = std::min(pCount, sChunkSize); + assert(lToWrite > 0 && lToWrite <= sChunkSize && lToWrite <= pCount); + + mFile.write(lPtr, lToWrite); + lPtr += lToWrite; + pCount -= lToWrite; + } + } + } +}; + +const std::string RawTFDump::TMPFileEnding{".part"}; + +//________________________________________ +RawTFDump::RawTFDump(const std::string& trigger) : mTrigger{trigger} +{ + mTriggerFilter = select(trigger.c_str()); + mFileBuf = std::make_unique(sBuffSize); + mFile.rdbuf()->pubsetbuf(mFileBuf.get(), sBuffSize); + mFile.clear(); + mFile.exceptions(std::fstream::failbit | std::fstream::badbit); +} + +//________________________________________ +void RawTFDump::init(InitContext& ic) +{ + mRGen = std::default_random_engine(getpid()); + mTFMetaFileDir = ic.options().get("meta-output-dir"); + if (mTFMetaFileDir != "/dev/null") { + mTFMetaFileDir = o2::utils::Str::rectifyDirectory(mTFMetaFileDir); + mStoreMetaFile = true; + mFillMD5 = ic.options().get("md5-for-meta"); + } + + mTFDir = ic.options().get("output-dir"); + if (mTFDir != "/dev/null") { + mTFDir = o2::utils::Str::rectifyDirectory(mTFDir); + mWriteTF = true; + } else { + mWriteTF = false; + mStoreMetaFile = false; + } + mRejectDistSTF = !ic.options().get("include-dist-stf"); + mRejectDEADBEEF = !ic.options().get("include-deadbeef"); + mCreateRunEnvDir = !ic.options().get("ignore-partition-run-dir"); + mMinFileSize = ic.options().get("min-file-size"); + mMaxFileSize = ic.options().get("max-file-size"); + mMaxTFPerFile = ic.options().get("max-tf-per-file"); + mMaxAccRate = ic.options().get("max-dump-rate"); + float cl = ic.options().get("rate-est-conf-limit"); + if (mConfLim < 0.001 || mConfLim > 0.32) { + LOGP(warn, "Bad confidence limit {} for rate estimate, setting to default {}", cl, mConfLim); + } else { + mConfLim = cl; + } + mMaxWarnThrottle = ic.options().get("max-warn"); + mWarnThrottleTF = ic.options().get("mute-warn-period"); + + mVerbose = ic.options().get("verbosity-level"); + mExclTriggerSpecs = ic.options().get("exclude-trigger-specs"); + if (!mExclTriggerSpecs.empty()) { + mExclTriggerFilter = select(mExclTriggerSpecs.c_str()); + } + if (mTrigger.empty()) { + if (mMaxAccRate >= 0.f) { + LOGP(info, "Will accept randomly {}% of TFs", mMaxAccRate); + } else { + LOGP(info, "Will accept every {}-th TF", int(std::ceil(-100.f / mMaxAccRate))); + } + } else { + mMaxAccRate = std::abs(mMaxAccRate); + LOGP(info, "Will limit TFs triggered with {} by {}% at most", mTrigger, mMaxAccRate); + if (!mExclTriggerFilter.empty()) { + LOGP(info, "Inputs excluded from the trigger: {}", mExclTriggerSpecs); + } + } + + if (mWriteTF) { + if (mMinFileSize > 0) { + LOGP(info, "Multiple TFs will be accumulated in the file until its size exceeds {}{}", + mMinFileSize, mMaxFileSize > mMinFileSize ? fmt::format(" but does not exceed {} B", mMaxFileSize) : std::string{}); + } + } + + mCheckDiskFull = ic.options().get("require-free-disk"); + mWaitDiskFull = 1000 * ic.options().get("wait-for-free-disk"); + mWaitDiskFullMax = 1000 * ic.options().get("max-wait-for-free-disk"); + + char hostname[_POSIX_HOST_NAME_MAX]; + gethostname(hostname, _POSIX_HOST_NAME_MAX); + mHostName = hostname; + mHostName = mHostName.substr(0, mHostName.find('.')); +} + +//________________________________________ +void RawTFDump::run(ProcessingContext& pc) +{ + mNTFsSeen++; + updateTimeDependentParams(pc); + mAcceptCurrentTF = triggerTF(pc); + if (mAcceptCurrentTF) { + prepareTFForWriting(pc); + } else { + return; + } + + prepareTFFile(); + if (mWriteTF && checkFreeSpace(pc)) { // write data + try { + size_t lTFSizeInFile = getTFSizeInFile(); + SubTimeFrameFileMeta lTFFileMeta(lTFSizeInFile); + lTFFileMeta.mWriteTimeMs = mTimingInfo.creation; + + mFile << lTFFileMeta; // Write DataHeader + SubTimeFrameFileMeta + mFile << mTFDataIndex; // Write DataHeader + SubTimeFrameFileDataIndex + + for (const auto& eqEntry : mDataMap) { + auto& [lSize, lCnt, lEntry] = eqEntry.second; + for (size_t part = 0; part < lCnt; part++) { + const auto& dataPtr = mTFData[lEntry + part]; + DataHeader hdToWrite = *reinterpret_cast(dataPtr.first); // make a local DataHeader copy to clear flagsNextHeader bit + hdToWrite.flagsNextHeader = 0; + hdToWrite.splitPayloadIndex = part; + if (mVerbose > 2) { + LOGP(info, "Writing part:{}/{} of {} | TFCounter:{} part{}/{}", part, lCnt, DataSpecUtils::describe(OutputSpec{hdToWrite.dataOrigin, hdToWrite.dataDescription, hdToWrite.subSpecification}), hdToWrite.firstTForbit, hdToWrite.splitPayloadIndex, hdToWrite.splitPayloadParts); + } + buffered_write(reinterpret_cast(&hdToWrite), sizeof(DataHeader)); + buffered_write(dataPtr.second, hdToWrite.payloadSize); + } + } + mFile.flush(); // flush the buffer and check the state + mTFOrbits.push_back(mTimingInfo.firstTForbit); + mNTFsInFile++; + } catch (const std::ios_base::failure& eFailExc) { + LOGP(error, "Writing of TF {} to file {} failed. error={}", mTimingInfo.tfCounter, mCurrentTFFileNameFullTmp, eFailExc.what()); + } + } + // cleanup + mTFData.clear(); + mDataMap.clear(); + mTFDataIndex.clear(); + mTFSize = 0; +} + +//____________________________________________________________ +void RawTFDump::endOfStream(EndOfStreamContext&) +{ + closeTFFile(); + LOGP(info, "Dumped {} TFs to {} files", mNTFsAccepted, mNTFFiles); + if (!mTriggerFilter.empty()) { + LOGP(info, "External trigger summary: {}", reportRates()); + } +} + +//________________________________________ +size_t RawTFDump::getTFSizeInFile() const +{ + return SubTimeFrameFileMeta::getSizeInFile() + mTFDataIndex.getSizeInFile() + mTFSize; +} + +//________________________________________ +size_t RawTFDump::getCurrentFileSize() +{ + return mFile.is_open() ? size_t(mFile.tellp()) : 0; +} + +//___________________________________________________________________ +void RawTFDump::prepareTFFile() +{ + if (!mWriteTF) { + return; + } + bool needToOpen; + if (!mFile.is_open()) { + needToOpen = true; + } else { + auto currSize = getCurrentFileSize(); + if ((mNTFsInFile >= mMaxTFPerFile) || + (currSize >= mMinFileSize) || // min size exceeded, may close the file. + (currSize && mMaxFileSize > mMinFileSize && ((currSize + mTFSize) > mMaxFileSize))) { // this is not the 1st TF in the file and the new size will exceed allowed max + needToOpen = true; + } else { + LOGP(info, "Will add new TF of size {} to existing file of size {} with {} TFs", mTFSize, currSize, mNTFsInFile); + needToOpen = false; + } + } + if (needToOpen) { + closeTFFile(); + auto TFDir = mTFDir.empty() ? o2::utils::Str::rectifyDirectory("./") : mTFDir; + if (mCreateRunEnvDir && !mDataTakingContext.envId.empty() && (mDataTakingContext.envId != o2::framework::DataTakingContext::UNKNOWN)) { + TFDir += fmt::format("{}_{}tf/", mDataTakingContext.envId, mDataTakingContext.runNumber); + if (!TFDir.empty()) { + o2::utils::createDirectoriesIfAbsent(TFDir); + LOGP(info, "Created {} directory for TFs output", TFDir); + } + } + mCurrentTFFileName = o2::base::NameConf::getRawTFFileName(mTimingInfo.runNumber, mTimingInfo.firstTForbit, mTimingInfo.tfCounter, mHostName); + mCurrentTFFileNameFull = fmt::format("{}{}", TFDir, mCurrentTFFileName); + mCurrentTFFileNameFullTmp = TMPFileEnding.empty() ? mCurrentTFFileNameFull : o2::utils::Str::concat_string(mCurrentTFFileNameFull, TMPFileEnding); + mFile.open(mCurrentTFFileNameFullTmp.c_str(), ios::binary | ios::trunc | ios::out | ios::ate); + LOGP(info, "Opened new raw-tf dump file {}[{}]", mCurrentTFFileNameFull, TMPFileEnding); + mNTFFiles++; + } +} + +//___________________________________________________________________ +void RawTFDump::updateTimeDependentParams(ProcessingContext& pc) +{ + namespace GRPECS = o2::parameters::GRPECS; + mTimingInfo = pc.services().get(); + if (mTimingInfo.globalRunNumberChanged) { + mDataTakingContext = pc.services().get(); + // determine the output type for the TF metadata + mMetaDataType = GRPECS::getRawDataPersistencyMode(mDataTakingContext.runType, mDataTakingContext.forcedRaw); + } +} + +//___________________________________________________________________ +void RawTFDump::closeTFFile() +{ + if (!mFile.is_open()) { + return; + } + try { + LOGP(info, "Closing output file {}[{}]", mCurrentTFFileNameFull, TMPFileEnding); + mFile.close(); + // write TF file metaFile data + if (mStoreMetaFile) { + o2::dataformats::FileMetaData TFMetaData; + if (!TFMetaData.fillFileData(mCurrentTFFileNameFullTmp, mFillMD5, TMPFileEnding)) { + throw std::runtime_error("metadata file was requested but not created"); + } + TFMetaData.setDataTakingContext(mDataTakingContext); + TFMetaData.type = mMetaDataType; + TFMetaData.priority = "high"; + TFMetaData.tfOrbits.swap(mTFOrbits); + auto metaFileNameTmp = fmt::format("{}{}.tmp", mTFMetaFileDir, mCurrentTFFileName); + auto metaFileName = fmt::format("{}{}.done", mTFMetaFileDir, mCurrentTFFileName); + try { + std::ofstream metaFileOut(metaFileNameTmp); + metaFileOut << TFMetaData; + metaFileOut.close(); + if (!TMPFileEnding.empty()) { + std::filesystem::rename(mCurrentTFFileNameFullTmp, mCurrentTFFileNameFull); + } + std::filesystem::rename(metaFileNameTmp, metaFileName); + LOGP(info, "wrote meta file {}", metaFileName); + } catch (std::exception const& e) { + LOGP(error, "Failed to store TF meta data file {}, reason {}", metaFileName, e.what()); + } + } else if (!TMPFileEnding.empty()) { + std::filesystem::rename(mCurrentTFFileNameFullTmp, mCurrentTFFileNameFull); + } + } catch (std::exception const& e) { + LOGP(error, "Failed to finalize TF file {}, reason: ", mCurrentTFFileNameFull, e.what()); + } + mTFOrbits.clear(); + mNTFsInFile = 0; +} + +//________________________________________ +bool RawTFDump::checkFreeSpace(ProcessingContext& pc) +{ + int totalWait = 0, nwaitCycles = 0; + while (mCheckDiskFull) { + constexpr int showFirstN = 10, prsecaleWarnings = 50; + try { + const auto si = std::filesystem::space(mCurrentTFFileNameFullTmp); + std::string wmsg{}; + if (mCheckDiskFull > 0.f && si.available < mCheckDiskFull) { + nwaitCycles++; + wmsg = fmt::format("Disk has {} MiB available while at least {} MiB is requested, wait for {} ms (on top of {} ms)", si.available / MiB, size_t(mCheckDiskFull) / MiB, mWaitDiskFull, totalWait); + } else if (mCheckDiskFull < 0.f && float(si.available) / si.capacity < -mCheckDiskFull) { // relative margin requested + nwaitCycles++; + wmsg = fmt::format("Disk has {:.3f}% available while at least {:.3f}% is requested, wait for {} ms (on top of {} ms)", si.capacity ? float(si.available) / si.capacity * 100.f : 0., -mCheckDiskFull, mWaitDiskFull, totalWait); + } else { + nwaitCycles = 0; + } + if (nwaitCycles) { + if (mWaitDiskFullMax > 0 && totalWait > mWaitDiskFullMax) { + closeTFFile(); // try to save whatever we have + LOGP(fatal, "Disk has {} MiB available out of {} MiB after waiting for {} ms", si.available / MiB, si.capacity / MiB, mWaitDiskFullMax); + } + if (nwaitCycles < showFirstN + 1 || (prsecaleWarnings && (nwaitCycles % prsecaleWarnings) == 0)) { + LOGP(alarm, "{}", wmsg); + } + pc.services().get().waitFor((unsigned int)(mWaitDiskFull)); + totalWait += mWaitDiskFull; + continue; + } + } catch (std::exception const& e) { + LOGP(fatal, "unable to query disk space info for path {}, reason {}", mCurrentTFFileNameFull, e.what()); // do we want this? + } + break; + } + return true; +} + +//________________________________________ +bool RawTFDump::triggerTF(ProcessingContext& pc) +{ + bool trig = false; + if (mTrigger.empty()) { // random + if (mMaxAccRate > 0.f) { + trig = (mUniformDist(mRGen) <= mMaxAccRate); + } else if (mMaxAccRate < 0.f) { + trig = (mTimingInfo.tfCounter % int(std::ceil(-100.f / mMaxAccRate))) == 0; + } + } else { + for (auto const& ref : InputRecordWalker(pc.inputs(), mTriggerFilter)) { + auto const* dh = DataRefUtils::getHeader(ref); + if (!dh) { + LOGP(error, "Failed to extract header for trigger input"); + continue; + } + auto extTrig = DataRefUtils::as(ref); + if (mVerbose > 0) { + LOGP(info, "trigger input {}, part: {} of {}, payload {}, 1stTFOrbit: {} TF: {} | span size: {} span[0]={}", + DataSpecUtils::describe(OutputSpec{dh->dataOrigin, dh->dataDescription, dh->subSpecification}), + dh->splitPayloadIndex, dh->splitPayloadParts, dh->payloadSize, dh->firstTForbit, dh->tfCounter, extTrig.size(), extTrig.size() > 0 ? extTrig[0] : false); + } + if (extTrig.size() && extTrig[0]) { + // is the input with this trigger vetoed? + bool veto = false; + for (const auto& excl : mExclTriggerFilter) { + if (DataRefUtils::match(ref, excl)) { + if (mVerbose > 0) { + LOGP(info, "ignoring trigger from black-listed {}", DataSpecUtils::describe(OutputSpec{dh->dataOrigin, dh->dataDescription, dh->subSpecification})); + } + veto = true; + break; + } + } + if (veto) { + continue; + } + trig = true; + break; + } + } + if (trig) { // do we need to throttle? + mNTFsExtTrig++; + mRateEstTrgLow = TMath::ChisquareQuantile(mConfLim, 2 * (mNTFsExtTrig)) / (2 * mNTFsSeen); + mRateEstTrgUpp = TMath::ChisquareQuantile(1. - mConfLim, 2 * (mNTFsExtTrig + 1)) / (2 * mNTFsSeen); + mRateEstAccLow = TMath::ChisquareQuantile(mConfLim, 2 * (mNTFsAccepted)) / (2 * mNTFsSeen); + mRateEstAccUpp = TMath::ChisquareQuantile(1. - mConfLim, 2 * (mNTFsAccepted + 1)) / (2 * mNTFsSeen); + if (mRateEstAccLow > 0.01 * mMaxAccRate) { // current lowest estimate on the acceptance rate exceeds desired limit -> ignore trigger + trig = false; + // do we need to warn? + if ((mNTFsSeen - mLastWarned) > mWarnThrottleTF && ((mNWarnThrottle < mMaxWarnThrottle) || mMaxWarnThrottle < 0)) { + mLastWarned = mNTFsSeen; + std::string swarn = reportRates(); + if (++mNWarnThrottle == mMaxWarnThrottle) { + swarn += " Will not warn anymore."; + } else { + swarn += fmt::format(" Will suppress this warnings for {} TFs", mWarnThrottleTF); + } + LOGP(alarm, "Ignoring TF triggered for dumping: {}", swarn); + } + } + } + } + if (trig) { + mNTFsAccepted++; + } + if (mVerbose > 0) { + LOGP(info, "TF#{} (slice#{}) will{} be written, {}", mTimingInfo.tfCounter, mTimingInfo.timeslice, trig ? "" : " not", reportRates()); + } + return trig; +} + +//________________________________________ +void RawTFDump::prepareTFForWriting(ProcessingContext& pc) +{ + for (auto const& ref : InputRecordWalker(pc.inputs(), mFilter)) { + auto const* dh = DataRefUtils::getHeader(ref); + if (!dh) { + LOGP(error, "Failed to extract header"); + continue; + } + if ((dh->subSpecification == 0xdeadbeef && mRejectDEADBEEF) || + (dh->dataOrigin == o2::header::gDataOriginFLP && dh->dataDescription == o2::header::gDataDescriptionDISTSTF && mRejectDistSTF)) { + if (mVerbose > 2) { + LOGP(info, "Rejecting {}", DataSpecUtils::describe(OutputSpec{dh->dataOrigin, dh->dataDescription, dh->subSpecification})); + } + continue; + } + const auto lHdrDataSize = sizeof(DataHeader) + dh->payloadSize; + mTFSize += lHdrDataSize; + + auto& [lSize, lCnt, lEntry] = mDataMap[EquipmentIdentifier(*dh)]; + if (!lCnt) { + lEntry = mTFData.size(); // flag where the data of this spec starts + } + lSize += lHdrDataSize; + lCnt++; + mTFData.push_back({ref.header, ref.payload}); + if (mVerbose > 2) { + const auto* dph = DataRefUtils::getHeader(ref); + LOGP(info, "{}, part: {} of {}, payload {}, 1stTFOrbit: {} TF: {}, creation: {} | counter:{} size:{} entry:{}", + DataSpecUtils::describe(OutputSpec{dh->dataOrigin, dh->dataDescription, dh->subSpecification}), + dh->splitPayloadIndex, dh->splitPayloadParts, dh->payloadSize, dh->firstTForbit, dh->tfCounter, dph ? dph->creation : -1UL, lCnt, lSize, lEntry); + } + } + + // build the index + { + LOGP(info, "Creating dump image for TF {} of run {}, starting orbit {}, size = {}", mTimingInfo.tfCounter, mTimingInfo.runNumber, mTimingInfo.firstTForbit, mTFSize); + std::uint64_t lCurrOff = 0; + for (const auto& eqEntry : mDataMap) { + const auto& eq = eqEntry.first; + auto& [lSize, lCnt, lEntry] = eqEntry.second; + assert(lSize > sizeof(DataHeader)); + + OutputSpec spec{eq.mDataOrigin, eq.mDataDescription, eq.mSubSpecification}; + if (mVerbose > 1) { + LOGP(info, "{} : {} parts of size {} entry {}| offset: {}", DataSpecUtils::describe(spec), lCnt, lSize, lEntry, lCurrOff); + } + mTFDataIndex.AddStfElement(eq, lCnt, lCurrOff, lSize); + lCurrOff += lSize; + } + } +} + +//____________________________________________________________ +std::string RawTFDump::reportRates() const +{ + std::string rep = fmt::format("{} TFs seen, {} accepted", mNTFsSeen, mNTFsAccepted); + if (!mTrigger.empty()) { + rep += fmt::format(", {} ext.triggered, est.rate: [{:.2e}:{:.2e}]/[{:.2e}:{:.2e}].", mNTFsExtTrig, mRateEstAccLow, mRateEstAccUpp, mRateEstTrgLow, mRateEstTrgUpp); + } + return rep; +} + +//__________________________________________________________ +DataProcessorSpec getRawTFDumpSpec(const std::string& inpconfig, const std::string& trigger) +{ + std::vector inputs = select(inpconfig.c_str()); + return DataProcessorSpec{ + "raw-tf-dump", + inputs, + {}, + AlgorithmSpec{adaptFromTask(trigger)}, + Options{ + {"include-deadbeef", VariantType::Bool, false, {"Include DPL-generated 0xdeadbeef subspecs for missing data"}}, + {"include-dist-stf", VariantType::Bool, false, {"Include FLP/DISTSUBTIMEFRAME input"}}, + {"exclude-trigger-specs", VariantType::String, "", {"Ignore trigger seen in these inputs of triggerspec"}}, + {"max-dump-rate", VariantType::Float, 0.f, {"%-age of TFs to dump. W/o external trigger: random(>0) or periodic(<0) rejection, with: max limit"}}, + {"rate-est-conf-limit", VariantType::Float, 0.05f, {"quantile for the lowest rate estimate confidence limit"}}, + {"max-warn", VariantType::Int, 5, {"max allowed warnings on throttling"}}, + {"mute-warn-period", VariantType::Int, 100, {"mute warnings on throttling for this number of TFs"}}, + {"output-dir", VariantType::String, "none", {"TF output directory, must exist"}}, + {"meta-output-dir", VariantType::String, "/dev/null", {"TF metadata output directory, must exist (if not /dev/null)"}}, + {"md5-for-meta", VariantType::Bool, false, {"fill CTF file MD5 sum in the metadata file"}}, + {"min-file-size", VariantType::Int64, 0l, {"accumulate TFs until given file size reached"}}, + {"max-file-size", VariantType::Int64, 0l, {"if > 0, try to avoid exceeding given file size, also used for space check"}}, + {"max-tf-per-file", VariantType::Int, 0, {"if > 0, avoid storing more than requested CTFs per file"}}, + {"require-free-disk", VariantType::Float, 0.f, {"pause writing op. if available disk space is below this margin, in bytes if >0, as a fraction of total if <0"}}, + {"wait-for-free-disk", VariantType::Float, 10.f, {"if paused due to the low disk space, recheck after this time (in s)"}}, + {"max-wait-for-free-disk", VariantType::Float, 60.f, {"produce fatal if paused due to the low disk space for more than this amount in s."}}, + {"verbosity-level", VariantType::Int, 0, {"Verbose mode: 1: decision on every TF, 2: details of saved TF, 3: more details"}}, + {"ignore-partition-run-dir", VariantType::Bool, false, {"Do not creare partition-run directory in output-dir"}}}}; +} + +} // namespace o2::rawdd diff --git a/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h new file mode 100644 index 0000000000000..a39cfb026ed52 --- /dev/null +++ b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h @@ -0,0 +1,23 @@ +// 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. + +#ifndef O2_RAW_TF_DUMP_SPEC_ +#define O2_RAW_TF_DUMP_SPEC_ + +#include "DetectorsCommonDataFormats/DetID.h" +#include "Framework/DeviceSpec.h" + +namespace o2::rawdd +{ +o2::framework::DataProcessorSpec getRawTFDumpSpec(const std::string& inpconfig, const std::string& trigger); +} + +#endif diff --git a/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx b/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx index f227390e67ef3..c8bc6ff374ead 100644 --- a/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx +++ b/Detectors/Raw/TFReaderDD/src/SubTimeFrameFileReader.cxx @@ -45,8 +45,8 @@ namespace o2f = o2::framework; /// SubTimeFrameFileReader //////////////////////////////////////////////////////////////////////////////// -SubTimeFrameFileReader::SubTimeFrameFileReader(const std::string& pFileName, o2::detectors::DetID::mask_t detMask) - : mFileName(pFileName) +SubTimeFrameFileReader::SubTimeFrameFileReader(const std::string& pFileName, o2::detectors::DetID::mask_t detMask, int verb, bool sup0xccdb, bool repaireHeaders, bool rejectDistSTF) + : mFileName(pFileName), mVerbosity(verb), mSup0xccdb(sup0xccdb), mRepaireHeaders(repaireHeaders), mRejectDistSTF(rejectDistSTF) { mFileMap.open(mFileName); if (!mFileMap.is_open()) { @@ -115,7 +115,7 @@ std::size_t SubTimeFrameFileReader::getHeaderStackSize() // throws ios_base::fai LOGP(error, "FileReader: Reached max number of headers allowed: {}.", cMaxHeaders); return 0; } - + LOGP(debug, "getHeaderStackSize, pos = {}, size = {}", lFilePosStart, lStackSize); return lStackSize; } @@ -178,13 +178,21 @@ Stack SubTimeFrameFileReader::getHeaderStack(std::size_t& pOrigsize) return Stack(lStackMem); } +const std::string SubTimeFrameFileReader::describeHeader(const o2::header::DataHeader& hd, bool full) const +{ + std::string res = fmt::format("{}", o2f::DataSpecUtils::describe(o2::framework::OutputSpec{hd.dataOrigin, hd.dataDescription, hd.subSpecification})); + if (full) { + res += fmt::format(" part:{}/{} sz:{} TF:{} Orb:{} Run:{}", hd.splitPayloadIndex, hd.splitPayloadParts, hd.payloadSize, hd.tfCounter, hd.firstTForbit, hd.runNumber); + } + return res; +} + std::uint32_t sRunNumber = 0; // TODO: add id to files metadata std::uint32_t sFirstTForbit = 0; // TODO: add id to files metadata std::uint64_t sCreationTime = 0; std::mutex stfMtx; -std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* device, const std::vector& outputRoutes, - const std::string& rawChannel, size_t slice, bool sup0xccdb, int verbosity) +std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* device, const std::vector& outputRoutes, const std::string& rawChannel, size_t slice) { std::unique_ptr messagesPerRoute = std::make_unique(); auto& msgMap = *messagesPerRoute.get(); @@ -252,9 +260,15 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* return nullptr; } lStfMetaDataHdr = o2::header::DataHeader::Get(lMetaHdrStack.first()); + if (mVerbosity > 0) { + LOGP(info, "read filemeta, pos = {}, size = {}", position(), sizeof(SubTimeFrameFileMeta)); + } if (!read_advance(&lStfFileMeta, sizeof(SubTimeFrameFileMeta))) { return nullptr; } + if (mVerbosity > 0) { + LOGP(info, "TFMeta : {}", lStfFileMeta.info()); + } if (lStfFileMeta.mWriteTimeMs == 0 && creationFallBack != 0) { if (!creation0Notified) { creation0Notified = true; @@ -318,9 +332,9 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* std::int64_t lLeftToRead = lStfDataSize; STFHeader stfHeader{tfID, -1u, -1u}; + DataHeader prevHeader; // read pairs while (lLeftToRead > 0) { - // allocate and read the Headers std::size_t lDataHeaderStackSize = 0; Stack lDataHeaderStack = getHeaderStack(lDataHeaderStackSize); @@ -335,6 +349,25 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* return nullptr; } DataHeader locDataHeader(*lDataHeader); + + if (mRepaireHeaders) { + if (locDataHeader == prevHeader) { + if (prevHeader.tfCounter == locDataHeader.tfCounter && (prevHeader.splitPayloadIndex + 1) != locDataHeader.splitPayloadIndex) { + if (mVerbosity > 3) { + LOGP(warn, "Repairing wrong part index for {} to {}", describeHeader(locDataHeader, true), (prevHeader.splitPayloadIndex + 1) % prevHeader.splitPayloadParts); + } + locDataHeader.splitPayloadIndex = (++prevHeader.splitPayloadIndex) % prevHeader.splitPayloadParts; + } + } else { // new header + if (locDataHeader.splitPayloadIndex != 0) { + if (mVerbosity > 2) { + LOGP(warn, "Repairing wrong part index for new {} to {}", describeHeader(locDataHeader, true), (prevHeader.splitPayloadIndex + 1) % prevHeader.splitPayloadParts); + } + locDataHeader.splitPayloadIndex = 0; + } + } + prevHeader = locDataHeader; + } // sanity check if (int(locDataHeader.firstTForbit) == -1) { if (!negativeOrbitNotified) { @@ -350,6 +383,18 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* } locDataHeader.runNumber = runNumberFallBack; } + const std::uint64_t lDataSize = locDataHeader.payloadSize; + + if (locDataHeader.dataOrigin == o2::header::gDataOriginFLP && locDataHeader.dataDescription == o2::header::gDataDescriptionDISTSTF && mRejectDistSTF) { + if (mVerbosity > 0) { + LOGP(warn, "Ignoring stored {}", describeHeader(locDataHeader)); + } + if (!ignore_nbytes(lDataSize)) { + return nullptr; + } + lLeftToRead -= (lDataHeaderStackSize + lDataSize); // update the counter + continue; + } o2::header::Stack headerStack{locDataHeader, o2f::DataProcessingHeader{tfID, 1, lStfFileMeta.mWriteTimeMs}}; if (stfHeader.runNumber == -1) { stfHeader.id = locDataHeader.tfCounter; @@ -359,8 +404,6 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* sRunNumber = stfHeader.runNumber; sFirstTForbit = stfHeader.firstOrbit; } - - const std::uint64_t lDataSize = locDataHeader.payloadSize; // do we accept these data? auto detOrigStatus = mDetOrigMap.find(locDataHeader.dataOrigin); if (detOrigStatus != mDetOrigMap.end() && !detOrigStatus->second) { // this is a detector data and we don't want to read it @@ -398,14 +441,15 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* msgSW.Stop(); #endif memcpy(lHdrStackMsg->GetData(), headerStack.data(), headerStack.size()); + LOGP(debug, "read data, pos = {}, size = {} leftToRead {}", position(), lDataSize, lLeftToRead); if (!read_advance(lDataMsg->GetData(), lDataSize)) { return nullptr; } - if (verbosity > 0) { - if (verbosity > 1 || locDataHeader.splitPayloadIndex == 0) { + if (mVerbosity > 0) { + if (mVerbosity > 1 || locDataHeader.splitPayloadIndex == 0) { printStack(headerStack); - if (o2::raw::RDHUtils::checkRDH(lDataMsg->GetData()) && verbosity > 2) { + if (o2::raw::RDHUtils::checkRDH(lDataMsg->GetData()) && mVerbosity > 2) { o2::raw::RDHUtils::printRDH(lDataMsg->GetData()); } } @@ -413,6 +457,9 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* #ifdef _RUN_TIMING_MEASUREMENT_ addPartSW.Start(false); #endif + if (mVerbosity > 2) { + LOGP(info, "addPart {} to {} | HdrSize:{} DataSize:{}", describeHeader(locDataHeader, true), fmqChannel, lHdrStackMsg->GetSize(), lDataMsg->GetSize()); + } addPart(std::move(lHdrStackMsg), std::move(lDataMsg), fmqChannel); #ifdef _RUN_TIMING_MEASUREMENT_ addPartSW.Stop(); @@ -434,7 +481,7 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* } unsigned stfSS[2] = {0, 0xccdb}; - for (int iss = 0; iss < (sup0xccdb ? 1 : 2); iss++) { + for (int iss = 0; iss < (mSup0xccdb ? 1 : 2); iss++) { o2::header::DataHeader stfDistDataHeader(o2::header::gDataDescriptionDISTSTF, o2::header::gDataOriginFLP, stfSS[iss], sizeof(STFHeader), 0, 1); stfDistDataHeader.payloadSerializationMethod = o2::header::gSerializationMethodNone; stfDistDataHeader.firstTForbit = stfHeader.firstOrbit; @@ -444,7 +491,7 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* if (!fmqChannel.empty()) { // no output channel auto fmqFactory = device->GetChannel(fmqChannel, 0).Transport(); o2::header::Stack headerStackSTF{stfDistDataHeader, o2f::DataProcessingHeader{tfID, 1, lStfFileMeta.mWriteTimeMs}}; - if (verbosity > 0) { + if (mVerbosity > 0) { printStack(headerStackSTF); } auto hdMessageSTF = fmqFactory->CreateMessage(headerStackSTF.size(), fair::mq::Alignment{64}); @@ -454,6 +501,9 @@ std::unique_ptr SubTimeFrameFileReader::read(fair::mq::Device* #ifdef _RUN_TIMING_MEASUREMENT_ addPartSW.Start(false); #endif + if (mVerbosity > 2) { + LOGP(info, "addPart forced {} to {} | HdrSize:{} DataSize:{}", describeHeader(stfDistDataHeader, true), fmqChannel, hdMessageSTF->GetSize(), plMessageSTF->GetSize()); + } addPart(std::move(hdMessageSTF), std::move(plMessageSTF), fmqChannel); #ifdef _RUN_TIMING_MEASUREMENT_ addPartSW.Stop(); diff --git a/Detectors/Raw/TFReaderDD/src/TFReaderSpec.cxx b/Detectors/Raw/TFReaderDD/src/TFReaderSpec.cxx index 2b8090af42648..d0de5fb893e3d 100644 --- a/Detectors/Raw/TFReaderDD/src/TFReaderSpec.cxx +++ b/Detectors/Raw/TFReaderDD/src/TFReaderSpec.cxx @@ -118,10 +118,13 @@ void TFReaderSpec::init(o2f::InitContext& ic) mInput.maxTFsPerFile = mInput.maxTFsPerFile > 0 ? mInput.maxTFsPerFile : 0x7fffffff; mInput.maxTFCache = std::max(1, ic.options().get("max-cached-tf")); mInput.maxFileCache = std::max(1, ic.options().get("max-cached-files")); + mInput.repairHeaders = !ic.options().get("ignore-repair-headers"); + mInput.rejectDistSTF = !ic.options().get("read-dist-stf"); + if (!mInput.fileRunTimeSpans.empty()) { loadRunTimeSpans(mInput.fileRunTimeSpans); } - mFileFetcher = std::make_unique(mInput.inpdata, mInput.tffileRegex, mInput.remoteRegex, mInput.copyCmd); + mFileFetcher = std::make_unique(mInput.inpdata, mInput.tffileRegex, mInput.remoteRegex, mInput.copyCmd, mInput.copyDir); mFileFetcher->setMaxFilesInQueue(mInput.maxFileCache); mFileFetcher->setMaxLoops(mInput.maxLoops); mFileFetcher->setFailThreshold(ic.options().get("fetch-failure-threshold")); @@ -263,7 +266,11 @@ void TFReaderSpec::run(o2f::ProcessingContext& ctx) setTimingInfo(*tfPtr.get()); size_t nparts = 0, dataSize = 0; if (mInput.sendDummyForMissing) { + int cntAck = 0; for (auto& msgIt : *tfPtr.get()) { // complete with empty output for the specs which were requested but were not seen in the data + if (mInput.verbosity > 0) { + LOGP(info, "acknowledgeOutput {}", cntAck++); + } acknowledgeOutput(*msgIt.second.get(), true); } addMissingParts(*tfPtr.get()); @@ -409,7 +416,7 @@ void TFReaderSpec::TFBuilder() } LOG(info) << "Processing file " << tfFileName; - SubTimeFrameFileReader reader(tfFileName, mInput.detMask); + SubTimeFrameFileReader reader(tfFileName, mInput.detMask, mInput.verbosity, mInput.sup0xccdb, mInput.repairHeaders, mInput.rejectDistSTF); size_t locID = 0; // try { @@ -421,7 +428,7 @@ void TFReaderSpec::TFBuilder() std::this_thread::sleep_for(sleepTime); continue; } - auto tf = reader.read(mDevice, mOutputRoutes, mInput.rawChannelConfig, mAccTFCounter, mInput.sup0xccdb, mInput.verbosity); + auto tf = reader.read(mDevice, mOutputRoutes, mInput.rawChannelConfig, mAccTFCounter); bool acceptTF = true; if (tf) { if (mRunTimeRanges.size()) { @@ -675,6 +682,8 @@ o2f::DataProcessorSpec o2::rawdd::getTFReaderSpec(o2::rawdd::TFReaderInp& rinp) } spec.options.emplace_back(o2f::ConfigParamSpec{"select-tf-ids", o2f::VariantType::String, "", {"comma-separated list TF IDs to inject (from cumulative counter of TFs seen)"}}); spec.options.emplace_back(o2f::ConfigParamSpec{"fetch-failure-threshold", o2f::VariantType::Float, 0.f, {"Fatil if too many failures( >0: fraction, <0: abs number, 0: no threshold)"}}); + spec.options.emplace_back(o2f::ConfigParamSpec{"ignore-repair-headers", o2f::VariantType::Bool, false, {"do not check/repair headers"}}); + spec.options.emplace_back(o2f::ConfigParamSpec{"read-dist-stf", o2f::VariantType::Bool, false, {"do not ignore stored FLP/DISTSUBTIMEFRAME (will clash with injected one)"}}); spec.options.emplace_back(o2f::ConfigParamSpec{"max-tf", o2f::VariantType::Int, -1, {"max TF ID to process (<= 0 : infinite)"}}); spec.options.emplace_back(o2f::ConfigParamSpec{"max-tf-per-file", o2f::VariantType::Int, -1, {"max TFs to process per raw-tf file (<= 0 : infinite)"}}); spec.options.emplace_back(o2f::ConfigParamSpec{"max-cached-tf", o2f::VariantType::Int, 3, {"max TFs to cache in memory"}}); diff --git a/Detectors/Raw/TFReaderDD/src/TFReaderSpec.h b/Detectors/Raw/TFReaderDD/src/TFReaderSpec.h index 9db18768c1bfe..6ecce0d032c06 100644 --- a/Detectors/Raw/TFReaderDD/src/TFReaderSpec.h +++ b/Detectors/Raw/TFReaderDD/src/TFReaderSpec.h @@ -29,6 +29,7 @@ struct TFReaderInp { std::string detListNonRawOnly{}; std::string rawChannelConfig{}; std::string copyCmd{}; + std::string copyDir{}; std::string tffileRegex{}; std::string remoteRegex{}; std::string metricChannel{}; @@ -48,6 +49,8 @@ struct TFReaderInp { bool sendDummyForMissing = true; bool sup0xccdb = false; bool invertIRFramesSelection = false; + bool repairHeaders = true; + bool rejectDistSTF = true; std::vector hdVec; std::vector tfIDs{}; }; diff --git a/Detectors/Raw/TFReaderDD/src/tf-data-dump-workflow.cxx b/Detectors/Raw/TFReaderDD/src/tf-data-dump-workflow.cxx new file mode 100644 index 0000000000000..fbade100d202f --- /dev/null +++ b/Detectors/Raw/TFReaderDD/src/tf-data-dump-workflow.cxx @@ -0,0 +1,46 @@ +// 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. + +#include "CommonUtils/ConfigurableParam.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/CompletionPolicy.h" +#include "Framework/CompletionPolicyHelpers.h" + +using namespace o2::framework; + +void customize(std::vector& workflowOptions) +{ + std::vector options; + options.push_back(ConfigParamSpec{"dataspec", VariantType::String, "tst:TST/A", {"selection string for the data to be proxied"}}); + options.push_back(ConfigParamSpec{"triggerspec", VariantType::String, "", {"selection string for the trigger input (must be also in dataspec if non-empty)"}}); + options.push_back(ConfigParamSpec{"configKeyValues", VariantType::String, "", {"semicolon separated key=value strings"}}); + std::swap(workflowOptions, options); +} + +void customize(std::vector& policies) +{ + policies.push_back({CompletionPolicyHelpers::consumeWhenPastOldestPossibleTimeframe("raw-tf-dump", [](auto const&) -> bool { return true; })}); + // policies.push_back({CompletionPolicyHelpers::consumeWhenAllOrdered("raw-tf-dump", [](auto const&) -> bool { return true; })}); // RSTOREM +} + +// ------------------------------------------------------------------ + +#include "Framework/runDataProcessing.h" +#include "RawTFDumpSpec.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) +{ + o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); + auto inpconfig = configcontext.options().get("dataspec"); + auto trigger = configcontext.options().get("triggerspec"); + WorkflowSpec specs{o2::rawdd::getRawTFDumpSpec(inpconfig, trigger)}; + return specs; +} diff --git a/Detectors/Raw/TFReaderDD/src/tf-reader-workflow.cxx b/Detectors/Raw/TFReaderDD/src/tf-reader-workflow.cxx index bc682127b0d3f..a29b4dadfdb25 100644 --- a/Detectors/Raw/TFReaderDD/src/tf-reader-workflow.cxx +++ b/Detectors/Raw/TFReaderDD/src/tf-reader-workflow.cxx @@ -31,9 +31,10 @@ void customize(std::vector& workflowOptions) options.push_back(ConfigParamSpec{"loop", VariantType::Int, 0, {"loop N times (-1 = infinite)"}}); options.push_back(ConfigParamSpec{"delay", VariantType::Float, 0.f, {"delay in seconds between consecutive TFs sending"}}); options.push_back(ConfigParamSpec{"copy-cmd", VariantType::String, "alien_cp ?src file://?dst", {"copy command for remote files"}}); // Use "XrdSecPROTOCOL=sss,unix xrdcp -N root://eosaliceo2.cern.ch/?src ?dst" for direct EOS access + options.push_back(ConfigParamSpec{"copy-dir", VariantType::String, "/tmp/", {"copy base directory for remote files"}}); options.push_back(ConfigParamSpec{"tf-file-regex", VariantType::String, ".+\\.tf$", {"regex string to identify TF files"}}); options.push_back(ConfigParamSpec{"remote-regex", VariantType::String, "^(alien://|)/alice/data/.+", {"regex string to identify remote files"}}); // Use "^/eos/aliceo2/.+" for direct EOS access - options.push_back(ConfigParamSpec{"tf-reader-verbosity", VariantType::Int, 0, {"verbosity level (1 or 2: check RDH, print DH/DPH for 1st or all slices, >2 print RDH)"}}); + options.push_back(ConfigParamSpec{"tf-reader-verbosity", VariantType::Int, 0, {"verbosity level (1 or 2: check RDH, print DH/DPH for 1st or all slices, >2 print RDH), report repairs"}}); options.push_back(ConfigParamSpec{"raw-channel-config", VariantType::String, "", {"optional raw FMQ channel for non-DPL output"}}); options.push_back(ConfigParamSpec{"send-diststf-0xccdb", VariantType::Bool, false, {"send explicit FLP/DISTSUBTIMEFRAME/0xccdb output"}}); options.push_back(ConfigParamSpec{"disable-dummy-output", VariantType::Bool, false, {"Disable sending empty output if corresponding data is not found in the data"}}); @@ -71,6 +72,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) rinp.delay_us = uint64_t(1e6 * configcontext.options().get("delay")); // delay in microseconds rinp.verbosity = configcontext.options().get("tf-reader-verbosity"); rinp.copyCmd = configcontext.options().get("copy-cmd"); + rinp.copyDir = configcontext.options().get("copy-dir"); rinp.tffileRegex = configcontext.options().get("tf-file-regex"); rinp.remoteRegex = configcontext.options().get("remote-regex"); rinp.sendDummyForMissing = !configcontext.options().get("disable-dummy-output"); diff --git a/Detectors/TOF/base/include/TOFBase/WindowFiller.h b/Detectors/TOF/base/include/TOFBase/WindowFiller.h index 77827ee5e7057..5c9abca6da0ea 100644 --- a/Detectors/TOF/base/include/TOFBase/WindowFiller.h +++ b/Detectors/TOF/base/include/TOFBase/WindowFiller.h @@ -96,7 +96,7 @@ class WindowFiller } std::vector& getPatterns() { return mPatterns; } - void addPattern(const uint32_t val, int icrate, int orbit, int bc) { mCratePatterns.emplace_back(val, icrate, orbit * 3 + (bc + 100) / Geo::BC_IN_WINDOW); } + void addPattern(const uint32_t val, int icrate, int orbit, int bc) { mCratePatterns.emplace_back(val, icrate, ((unsigned long)orbit) * 3 + (bc + 100) / Geo::BC_IN_WINDOW); } void addCrateHeaderData(unsigned long orbit, int crate, int32_t bc, uint32_t eventCounter); Diagnostic& getDiagnosticFrequency() { return mDiagnosticFrequency; } diff --git a/Detectors/TOF/base/src/WindowFiller.cxx b/Detectors/TOF/base/src/WindowFiller.cxx index 0362222b55bf5..35ec27070bda1 100644 --- a/Detectors/TOF/base/src/WindowFiller.cxx +++ b/Detectors/TOF/base/src/WindowFiller.cxx @@ -194,9 +194,9 @@ void WindowFiller::fillOutputContainer(std::vector& digits) int npatterns = 0; // check if patterns are in the current row - unsigned int initrow = mFirstIR.orbit * Geo::NWINDOW_IN_ORBIT; + unsigned long initrow = ((unsigned long)mFirstIR.orbit) * Geo::NWINDOW_IN_ORBIT; for (std::vector::reverse_iterator it = mCratePatterns.rbegin(); it != mCratePatterns.rend(); ++it) { - unsigned int irow = it->row; + unsigned long irow = it->row; // printf("pattern row=%ld (%u - %u) current=%ld\n",irow - initrow,irow,initrow,mReadoutWindowCurrent); if (irow - initrow > mReadoutWindowCurrent) { diff --git a/Detectors/TOF/calibration/include/TOFCalibration/TOFDiagnosticCalibrator.h b/Detectors/TOF/calibration/include/TOFCalibration/TOFDiagnosticCalibrator.h index 7b23df6040d3d..fccfcf51fdaa9 100644 --- a/Detectors/TOF/calibration/include/TOFCalibration/TOFDiagnosticCalibrator.h +++ b/Detectors/TOF/calibration/include/TOFCalibration/TOFDiagnosticCalibrator.h @@ -31,9 +31,9 @@ class TOFDiagnosticCalibrator final : public o2::calibration::TimeSlotCalibratio int mRunNumber = -1; public: - TOFDiagnosticCalibrator() = default; + TOFDiagnosticCalibrator(int minROwin = 100) : mMinROwin(minROwin) {} ~TOFDiagnosticCalibrator() final = default; - bool hasEnoughData(const Slot& slot) const final { return true; } + bool hasEnoughData(const Slot& slot) const final; void initOutput() final; void finalizeSlot(Slot& slot) final; Slot& emplaceNewSlot(bool front, TFType tstart, TFType tend) final; @@ -43,12 +43,15 @@ class TOFDiagnosticCalibrator final : public o2::calibration::TimeSlotCalibratio const std::vector& getDiagnosticVector() const { return mDiagnosticVector; } const CcdbObjectInfoVector& getDiagnosticInfoVector() const { return mccdbInfoVector; } CcdbObjectInfoVector& getDiagnosticInfoVector() { return mccdbInfoVector; } + int getMinROwin() const { return mMinROwin; } + void setMinROwin(int rowin) { mMinROwin = rowin; } private: CcdbObjectInfoVector mccdbInfoVector; std::vector mDiagnosticVector; + int mMinROwin; // minimal number of readout windows needed to finalize the object - ClassDefOverride(TOFDiagnosticCalibrator, 1); + ClassDefOverride(TOFDiagnosticCalibrator, 2); }; } // end namespace tof diff --git a/Detectors/TOF/calibration/src/TOFDiagnosticCalibrator.cxx b/Detectors/TOF/calibration/src/TOFDiagnosticCalibrator.cxx index 9a4118dbba493..f238d69bb75ed 100644 --- a/Detectors/TOF/calibration/src/TOFDiagnosticCalibrator.cxx +++ b/Detectors/TOF/calibration/src/TOFDiagnosticCalibrator.cxx @@ -28,7 +28,17 @@ void TOFDiagnosticCalibrator::initOutput() mccdbInfoVector.clear(); mDiagnosticVector.clear(); } +//---------------------------------------------------------- +bool TOFDiagnosticCalibrator::hasEnoughData(const Slot& slot) const +{ + const Diagnostic* diag = slot.getContainer(); + if (diag->getFrequencyROW() < mMinROwin) { + return false; + } + + return true; +} //---------------------------------------------------------- void TOFDiagnosticCalibrator::finalizeSlot(Slot& slot) { diff --git a/Detectors/TOF/calibration/testWorkflow/TOFDiagnosticCalibratorSpec.h b/Detectors/TOF/calibration/testWorkflow/TOFDiagnosticCalibratorSpec.h index 7887ff848d544..91b931f3b96b3 100644 --- a/Detectors/TOF/calibration/testWorkflow/TOFDiagnosticCalibratorSpec.h +++ b/Detectors/TOF/calibration/testWorkflow/TOFDiagnosticCalibratorSpec.h @@ -37,7 +37,7 @@ namespace calibration class TOFDiagnosticCalibDevice : public o2::framework::Task { public: - TOFDiagnosticCalibDevice(std::shared_ptr req, int runnumber = -1) : mCCDBRequest(req), mRunNumber(runnumber) {} + TOFDiagnosticCalibDevice(std::shared_ptr req, int runnumber = -1, int rowinMin = 100000) : mCCDBRequest(req), mRunNumber(runnumber), mMinROwin(rowinMin) {} void init(o2::framework::InitContext& ic) final { o2::base::GRPGeomHelper::instance().setRequest(mCCDBRequest); @@ -47,6 +47,7 @@ class TOFDiagnosticCalibDevice : public o2::framework::Task mCalibrator->setSlotLength(slotL); mCalibrator->setMaxSlotsDelay(delay); mCalibrator->setRunNumber(mRunNumber); + mCalibrator->setMinROwin(mMinROwin); } void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) final @@ -75,6 +76,7 @@ class TOFDiagnosticCalibDevice : public o2::framework::Task std::unique_ptr mCalibrator; std::shared_ptr mCCDBRequest; int mRunNumber = -1; + int mMinROwin = 100000; //________________________________________________________________ void sendOutput(DataAllocator& output) @@ -104,7 +106,7 @@ class TOFDiagnosticCalibDevice : public o2::framework::Task namespace framework { -DataProcessorSpec getTOFDiagnosticCalibDeviceSpec(int runnumber) +DataProcessorSpec getTOFDiagnosticCalibDeviceSpec(int runnumber, int rowinMin) { using device = o2::calibration::TOFDiagnosticCalibDevice; using clbUtils = o2::calibration::Utils; @@ -125,7 +127,7 @@ DataProcessorSpec getTOFDiagnosticCalibDeviceSpec(int runnumber) "tof-diagnostic-calibration", inputs, outputs, - AlgorithmSpec{adaptFromTask(ccdbRequest, runnumber)}, + AlgorithmSpec{adaptFromTask(ccdbRequest, runnumber, rowinMin)}, Options{ {"tf-per-slot", VariantType::UInt32, 5u, {"number of TFs per calibration time slot"}}, {"max-delay", VariantType::UInt32, 3u, {"number of slots in past to consider"}}}}; diff --git a/Detectors/TOF/calibration/testWorkflow/tof-diagnostic-workflow.cxx b/Detectors/TOF/calibration/testWorkflow/tof-diagnostic-workflow.cxx index 3cde7be96867a..b45fcc2b5498c 100644 --- a/Detectors/TOF/calibration/testWorkflow/tof-diagnostic-workflow.cxx +++ b/Detectors/TOF/calibration/testWorkflow/tof-diagnostic-workflow.cxx @@ -19,6 +19,7 @@ void customize(std::vector& workflowOptions) { // option allowing to set parameters workflowOptions.push_back(ConfigParamSpec{"tof-dia-run-number", o2::framework::VariantType::Int, -1, {"run number"}}); + workflowOptions.push_back(ConfigParamSpec{"tof-dia-min-rowin", o2::framework::VariantType::Int, 100000, {"min number of TOF Readout Windows, def=100k (3 s)"}}); } // ------------------------------------------------------------------ @@ -29,6 +30,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { WorkflowSpec specs; auto runnumber = configcontext.options().get("tof-dia-run-number"); - specs.emplace_back(getTOFDiagnosticCalibDeviceSpec(runnumber)); + auto rowinMin = configcontext.options().get("tof-dia-min-rowin"); + specs.emplace_back(getTOFDiagnosticCalibDeviceSpec(runnumber, rowinMin)); return specs; } diff --git a/Detectors/TPC/base/include/TPCBase/Mapper.h b/Detectors/TPC/base/include/TPCBase/Mapper.h index f2ff425675df6..6731637e2a08c 100644 --- a/Detectors/TPC/base/include/TPCBase/Mapper.h +++ b/Detectors/TPC/base/include/TPCBase/Mapper.h @@ -755,4 +755,8 @@ inline bool Mapper::isOutOfSector(GlobalPosition3D posEle, const Sector& sector, } // namespace tpc } // namespace o2 +#ifdef GPUCA_STANDALONE +#error TPC Mapper must not be used for Run2 Data +#endif + #endif diff --git a/Detectors/TPC/base/include/TPCBase/ParameterDetector.h b/Detectors/TPC/base/include/TPCBase/ParameterDetector.h index 2762f6ff67d31..e557a174ec70a 100644 --- a/Detectors/TPC/base/include/TPCBase/ParameterDetector.h +++ b/Detectors/TPC/base/include/TPCBase/ParameterDetector.h @@ -18,8 +18,7 @@ #include #include "DataFormatsTPC/Defs.h" -#include "CommonUtils/ConfigurableParam.h" -#include "CommonUtils/ConfigurableParamHelper.h" +#include "GPUCommonConfigurableParam.h" namespace o2 { diff --git a/Detectors/TPC/base/include/TPCBase/ParameterElectronics.h b/Detectors/TPC/base/include/TPCBase/ParameterElectronics.h index a44410d29ae15..8ada729f36103 100644 --- a/Detectors/TPC/base/include/TPCBase/ParameterElectronics.h +++ b/Detectors/TPC/base/include/TPCBase/ParameterElectronics.h @@ -17,9 +17,8 @@ #define ALICEO2_TPC_ParameterElectronics_H_ #include -#include "CommonUtils/ConfigurableParam.h" -#include "CommonUtils/ConfigurableParamHelper.h" #include "CommonConstants/LHCConstants.h" +#include "GPUCommonConfigurableParam.h" namespace o2::tpc { diff --git a/Detectors/TPC/base/include/TPCBase/ParameterGas.h b/Detectors/TPC/base/include/TPCBase/ParameterGas.h index d9bc65a08173a..210d8dbd14867 100644 --- a/Detectors/TPC/base/include/TPCBase/ParameterGas.h +++ b/Detectors/TPC/base/include/TPCBase/ParameterGas.h @@ -17,8 +17,7 @@ #define ALICEO2_TPC_ParameterGas_H_ #include -#include "CommonUtils/ConfigurableParam.h" -#include "CommonUtils/ConfigurableParamHelper.h" +#include "GPUCommonConfigurableParam.h" namespace o2 { diff --git a/Detectors/TPC/base/include/TPCBase/RDHUtils.h b/Detectors/TPC/base/include/TPCBase/RDHUtils.h index adfd94cf6b703..71b5d16b85702 100644 --- a/Detectors/TPC/base/include/TPCBase/RDHUtils.h +++ b/Detectors/TPC/base/include/TPCBase/RDHUtils.h @@ -13,7 +13,7 @@ #define AliceO2_TPC_RDHUtils_H #include "DetectorsRaw/RDHUtils.h" -//#include "Headers/RAWDataHeader.h" +// #include "Headers/RAWDataHeader.h" namespace o2 { @@ -28,6 +28,7 @@ static constexpr FEEIDType UserLogicLinkID = 15; ///< virtual link ID for ZS dat static constexpr FEEIDType IDCLinkID = 20; ///< Identifier for integrated digital currents static constexpr FEEIDType ILBZSLinkID = 21; ///< Identifier for improved link-based ZS static constexpr FEEIDType DLBZSLinkID = 22; ///< Identifier for dense link-based ZS +static constexpr FEEIDType CMVLinkID = 23; ///< Identifier for common mode values static constexpr FEEIDType SACLinkID = 25; ///< Identifier for sampled analog currents /// compose feeid from cru, endpoint and link diff --git a/Detectors/TPC/base/src/Mapper.cxx b/Detectors/TPC/base/src/Mapper.cxx index 2796d488f014d..75db269ee85f4 100644 --- a/Detectors/TPC/base/src/Mapper.cxx +++ b/Detectors/TPC/base/src/Mapper.cxx @@ -17,7 +17,7 @@ #include #include "TPCBase/Mapper.h" -#include "Framework/Logger.h" +#include "GPUCommonLogger.h" namespace o2 { diff --git a/Detectors/TPC/calibration/CMakeLists.txt b/Detectors/TPC/calibration/CMakeLists.txt index 27f7f0200bb92..6aeb497c1cf23 100644 --- a/Detectors/TPC/calibration/CMakeLists.txt +++ b/Detectors/TPC/calibration/CMakeLists.txt @@ -48,7 +48,7 @@ o2_add_library(TPCCalibration src/SACParameter.cxx src/SACDrawHelper.cxx src/VDriftHelper.cxx - src/CorrectionMapsLoader.cxx + src/CorrectionMapsOptions.cxx src/SACCCDBHelper.cxx src/TPCFastSpaceChargeCorrectionHelper.cxx src/CalculatedEdx.cxx @@ -58,6 +58,9 @@ o2_add_library(TPCCalibration src/DigitAdd.cxx src/CorrectdEdxDistortions.cxx src/PressureTemperatureHelper.cxx + src/CMVContainer.cxx + src/CorrectionMapsLoader.cxx + src/CMVHelper.cxx PUBLIC_LINK_LIBRARIES O2::DataFormatsTPC O2::TPCBaseRecSim O2::TPCReconstruction ROOT::Minuit Microsoft.GSL::GSL @@ -115,7 +118,10 @@ o2_target_root_dictionary(TPCCalibration include/TPCCalibration/TPCMShapeCorrection.h include/TPCCalibration/DigitAdd.h include/TPCCalibration/CorrectdEdxDistortions.h - include/TPCCalibration/PressureTemperatureHelper.h) + include/TPCCalibration/PressureTemperatureHelper.h + include/TPCCalibration/CMVContainer.h + include/TPCCalibration/CorrectionMapsLoader.h + include/TPCCalibration/CMVHelper.h) o2_add_test_root_macro(macro/comparePedestalsAndNoise.C PUBLIC_LINK_LIBRARIES O2::TPCBaseRecSim @@ -153,6 +159,10 @@ o2_add_test_root_macro(macro/prepareITFiles.C COMPILE_ONLY PUBLIC_LINK_LIBRARIES O2::TPCCalibration LABELS tpc) +o2_add_test_root_macro(macro/drawCMV.C + COMPILE_ONLY + PUBLIC_LINK_LIBRARIES O2::TPCCalibration O2::TPCBase + LABELS tpc) o2_add_test(IDCFourierTransform COMPONENT_NAME calibration diff --git a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackResiduals.h b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackResiduals.h index e4d0a3a053728..c9226589ec703 100644 --- a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackResiduals.h +++ b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackResiduals.h @@ -318,9 +318,14 @@ class TrackResiduals void getVoxelCoordinates(int isec, int ix, int ip, int iz, float& x, float& p, float& z) const; /// Calculates the x-coordinate for given x bin. - /// \param i Bin index + /// \param ix Bin index in x /// \return Coordinate in X - float getX(int i) const; + float getX(int ix) const; + + /// Calculates the max y/x-coordinate for given x bin taking the dead zone into account. + /// \param ix Bin index in x + /// \return Max coordinate in Y/X + float getMaxY2X(int ix) const; /// Calculates the y/x-coordinate. /// \param ix Bin index in X @@ -443,6 +448,12 @@ class TrackResiduals /// output tree TTree* getOutputTree() { return mTreeOut.get(); } + /// Ad-hoc radial scaling factor A/C-Side + void setAdhocScalingFactorX(const std::array& scaling) { mAdhocScalingX = scaling; } + + /// Ad-hoc correction of Z/X + void doAdhocCorrectionZ2X(bool corr) { mDoAdhocCorrectionZ2X = corr; } + private: std::bitset mInitResultsContainer{}; @@ -502,6 +513,8 @@ class TrackResiduals std::array, SECTORSPERSIDE * SIDES> mVoxelResults{}; ///< results per sector and per voxel for 3-D distortions VoxRes mVoxelResultsOut{}; ///< the results from mVoxelResults are copied in here to be able to stream them VoxRes* mVoxelResultsOutPtr{&mVoxelResultsOut}; ///< pointer to set the branch address to for the output + std::array mAdhocScalingX{0, 0}; ///< Ad-hoc radial scaling factor + bool mDoAdhocCorrectionZ2X{false}; ///< If to do ad-hoc correction for Z/X ClassDefNV(TrackResiduals, 3); }; @@ -552,9 +565,15 @@ inline float TrackResiduals::getDXI(int ix) const } //_____________________________________________________ -inline float TrackResiduals::getX(int i) const +inline float TrackResiduals::getX(int ix) const +{ + return mUniformBins[VoxX] ? param::MinX + (ix + 0.5) * mDX : param::RowX[ix]; +} + +//_____________________________________________________ +inline float TrackResiduals::getMaxY2X(int ix) const { - return mUniformBins[VoxX] ? param::MinX + (i + 0.5) * mDX : param::RowX[i]; + return mMaxY2X[ix]; } //_____________________________________________________ diff --git a/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx b/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx index 539ae25862865..76daab93dd8e0 100644 --- a/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx +++ b/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx @@ -820,7 +820,7 @@ void TrackInterpolation::interpolateTrack(int iSeed) float xv = vtx.X() * cs + vtx.Y() * sn, yv = -vtx.X() * sn + vtx.Y() * cs, zv = vtx.Z(); auto dy = yv - trkWorkITS.getY(); auto dz = zv - trkWorkITS.getZ(); - if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(trkWorkITS.getY()) < param::MaxY) && (std::abs(trkWorkITS.getZ()) < param::MaxZ) && abs(xv) < param::MaxVtxX) { + if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(trkWorkITS.getY()) < param::MaxY) && (std::abs(trkWorkITS.getZ()) < param::MaxZ) && std::abs(xv) < param::MaxVtxX) { short compXV = static_cast(xv * 0x7fff / param::MaxVtxX); mClRes.emplace_back(dy, dz, alpha / TMath::Pi(), trkWorkITS.getY(), trkWorkITS.getZ(), 190, -1, compXV); if (!gidTable[GTrackID::ITSTPC].isIndexSet()) { @@ -1168,7 +1168,7 @@ void TrackInterpolation::extrapolateTrack(int iSeed) float xv = vtx.X() * cs + vtx.Y() * sn, yv = -vtx.X() * sn + vtx.Y() * cs, zv = vtx.Z(); auto dy = yv - trkWorkITS.getY(); auto dz = zv - trkWorkITS.getZ(); - if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(trkWorkITS.getY()) < param::MaxY) && (std::abs(trkWorkITS.getZ()) < param::MaxZ) && abs(xv) < param::MaxVtxX) { + if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(trkWorkITS.getY()) < param::MaxY) && (std::abs(trkWorkITS.getZ()) < param::MaxZ) && std::abs(xv) < param::MaxVtxX) { short compXV = static_cast(xv * 0x7fff / param::MaxVtxX); mClRes.emplace_back(dy, dz, alpha / TMath::Pi(), trkWorkITS.getY(), trkWorkITS.getZ(), 190, -1, compXV); if (!gidTableFull[GTrackID::ITSTPC].isIndexSet()) { diff --git a/Detectors/TPC/calibration/SpacePoints/src/TrackResiduals.cxx b/Detectors/TPC/calibration/SpacePoints/src/TrackResiduals.cxx index 45d7a6ae3c231..d3db11daf9e87 100644 --- a/Detectors/TPC/calibration/SpacePoints/src/TrackResiduals.cxx +++ b/Detectors/TPC/calibration/SpacePoints/src/TrackResiduals.cxx @@ -719,8 +719,26 @@ void TrackResiduals::smooth(int iSec) if (!(resVox.flags & SmoothDone)) { continue; } - resVox.DS[ResZ] += resVox.stat[VoxZ] * resVox.DS[ResX]; // remove slope*dX contribution from dZ - resVox.D[ResZ] += resVox.stat[VoxZ] * resVox.DS[ResX]; // remove slope*dX contribution from dZ + // TODO: Usage of Z/X is bug??? + float z2x = resVox.stat[VoxZ]; + if (mDoAdhocCorrectionZ2X) { + // + const float z = z2x * resVox.stat[VoxX] - resVox.DS[ResZ]; + const float x = resVox.stat[VoxX] - resVox.DS[ResX]; // is subration of DS[ResX] correct? + z2x = z / x; + } + resVox.DS[ResZ] += z2x * resVox.DS[ResX]; // remove slope*dX contribution from dZ + resVox.D[ResZ] += z2x * resVox.DS[ResX]; // remove slope*dX contribution from dZ + // + if (mAdhocScalingX[iSec >= 18] != 0) { + const float aDX = resVox.DS[ResX] * mAdhocScalingX[iSec >= 18]; + resVox.D[ResX] += aDX; + resVox.DS[ResX] += aDX; + resVox.D[ResY] += aDX * resVox.stat[VoxF]; + resVox.DS[ResY] += aDX * resVox.stat[VoxF]; + resVox.D[ResZ] += aDX * z2x; + resVox.DS[ResZ] += aDX * z2x; + } } } } diff --git a/Detectors/TPC/calibration/include/TPCCalibration/CMVContainer.h b/Detectors/TPC/calibration/include/TPCCalibration/CMVContainer.h new file mode 100644 index 0000000000000..6f69a928d29ec --- /dev/null +++ b/Detectors/TPC/calibration/include/TPCCalibration/CMVContainer.h @@ -0,0 +1,141 @@ +// 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 CMVContainer.h +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief Structs for storing CMVs to the CCDB + +#ifndef ALICEO2_TPC_CMVCONTAINER_H_ +#define ALICEO2_TPC_CMVCONTAINER_H_ + +#include +#include +#include +#include + +#include "TTree.h" +#include "TPCBase/CRU.h" +#include "DataFormatsTPC/CMV.h" + +namespace o2::tpc +{ + +struct CMVPerTF; // forward declaration +struct CMVPerTFCompressed; // forward declaration + +/// Bitmask flags describing which encoding stages are applied in CMVPerTFCompressed +struct CMVEncoding { + static constexpr uint8_t kNone = 0x00; ///< No compression — raw uint16 values stored flat + static constexpr uint8_t kSparse = 0x01; ///< Non-zero positions stored sparsely (varint-encoded deltas) + static constexpr uint8_t kDelta = 0x02; ///< Delta coding between consecutive values (dense only) + static constexpr uint8_t kZigzag = 0x04; ///< Zigzag encoding of deltas or signed values + static constexpr uint8_t kVarint = 0x08; ///< Varint compression of the value stream + static constexpr uint8_t kHuffman = 0x10; ///< Canonical Huffman compression of the value stream +}; + +/// Single compressed representation for one TF across all CRUs, stored in a TTree +/// mFlags is a bitmask of CMVEncoding values that fully describes the encoding pipeline +/// mData holds the encoded payload whose binary layout depends on mFlags: +/// +/// Dense path (!kSparse): +/// kZigzag absent → N × uint16_t LE (raw values, CRU-major order) +/// kZigzag + kVarint → N × varint(zigzag(delta(signed(raw)))) +/// kZigzag + kHuffman → [Huffman table] + [bitstream] of zigzag(delta(signed(raw))) +/// +/// Sparse path (kSparse): +/// 4 bytes LE uint32_t : posStreamSize +/// posStream: for each CRU: varint(N), N × varint(tb_delta) +/// valStream (one entry per non-zero): +/// default → uint16_t LE raw value +/// kZigzag + kVarint → varint(zigzag(signed(raw))) +/// kZigzag + kHuffman → [Huffman table] + [bitstream] of zigzag(signed(raw)) +struct CMVPerTFCompressed { + uint32_t firstOrbit{0}; ///< First orbit of this TF + uint32_t firstOrbitDPL{0}; ///< First orbit of this TF + uint8_t mFlags{0}; ///< Bitmask of CMVEncoding values + + std::vector mData; ///< Encoded payload + + /// Restore a CMVPerTF from this compressed object into *cmv (must not be null) + void decompress(CMVPerTF* cmv) const; + + /// Serialise into a TTree; each Fill() call appends one entry (one TF) + std::unique_ptr toTTree() const; + + private: + /// Decode the sparse position stream; advances ptr past the position block + /// Returns (cru, timeBin) pairs for every non-zero entry, in CRU-major order + static std::vector> decodeSparsePositions(const uint8_t*& ptr, const uint8_t* end); + + /// Decode the value stream into raw uint32_t symbols + /// Dispatches to Huffman, varint, or raw uint16 based on flags + static std::vector decodeValueStream(const uint8_t*& ptr, const uint8_t* end, uint32_t N, uint8_t flags); + + /// Apply inverse zigzag and scatter decoded values into the sparse positions of *cmv + static void decodeSparseValues(const std::vector& symbols, + const std::vector>& positions, + uint8_t flags, CMVPerTF* cmv); + + /// Apply inverse zigzag and inverse delta, then fill the full dense CMV array in *cmv + static void decodeDenseValues(const std::vector& symbols, uint8_t flags, CMVPerTF* cmv); + + public: + ClassDefNV(CMVPerTFCompressed, 2) +}; + +/// CMV data for one TF across all CRUs +/// Raw 16-bit CMV values are stored in a flat C array indexed as [cru * NTimeBinsPerTF + timeBin] +struct CMVPerTF { + uint32_t firstOrbit{0}; ///< First orbit of this TF, from heartbeatOrbit of the first CMV packet + uint32_t firstOrbitDPL{0}; ///< First orbit of this TF, from DPL + + // Raw 16-bit CMV values, flat array indexed as [cru * NTimeBinsPerTF + timeBin] + uint16_t mDataPerTF[CRU::MaxCRU * cmv::NTimeBinsPerTF]{}; + + /// Return the raw 16-bit CMV value for a given CRU and timebin within this TF + uint16_t getCMV(const int cru, const int timeBin) const; + + /// Return the float CMV value for a given CRU and timebin within this TF + float getCMVFloat(const int cru, const int timeBin) const; + + /// Zero out raw CMV values whose float magnitude is below threshold + void zeroSmallValues(float threshold = 1.0f); + + /// Round values to the nearest integer ADC for all values whose rounded magnitude is <= threshold + void roundToIntegers(uint16_t threshold); + + /// Quantise |v| with a Gaussian-CDF recovery profile: + /// Coarse decimal-style precision below and around mean, then a smooth return to the full native I8F7 precision as the magnitude increases with width sigma + void trimGaussianPrecision(float mean, float sigma); + + /// Compress this object into a CMVPerTFCompressed using the encoding pipeline described by flags + /// Quantisation (trimGaussianPrecision / roundToIntegers / zeroSmallValues) should be applied to this object before calling compress(); it is not part of the flags pipeline + CMVPerTFCompressed compress(uint8_t flags) const; + + /// Serialise into a TTree; each Fill() call appends one entry (one TF) + std::unique_ptr toTTree() const; + + /// Write the TTree to a ROOT file + static void writeToFile(const std::string& filename, const std::unique_ptr& tree); + + private: + static int32_t cmvToSigned(uint16_t raw); ///< Sign-magnitude uint16_t → signed integer + static uint16_t quantizeBelowThreshold(uint16_t raw, float quantizationMean, float quantizationSigma); ///< Quantise sub-threshold values with a Gaussian-shaped recovery to full precision + static uint32_t zigzagEncode(int32_t value); ///< Zigzag encode + static void encodeVarintInto(uint32_t value, std::vector& out); ///< Varint encode + + public: + ClassDefNV(CMVPerTF, 2) +}; + +} // namespace o2::tpc + +#endif // ALICEO2_TPC_CMVCONTAINER_H_ diff --git a/Detectors/TPC/calibration/include/TPCCalibration/CMVHelper.h b/Detectors/TPC/calibration/include/TPCCalibration/CMVHelper.h new file mode 100644 index 0000000000000..d687c6872b8df --- /dev/null +++ b/Detectors/TPC/calibration/include/TPCCalibration/CMVHelper.h @@ -0,0 +1,52 @@ +// 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 CMVHelper.h +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief Helper utilities for reading CMV ROOT files + +#ifndef ALICEO2_TPC_CMVHELPER_H_ +#define ALICEO2_TPC_CMVHELPER_H_ + +#include + +#include "TFile.h" +#include "TTree.h" + +namespace o2::tpc +{ + +struct CMVPerTF; +struct CMVPerTFCompressed; + +struct CMVFileHandle { + TFile* file{nullptr}; + TTree* tree{nullptr}; + bool isCompressed{false}; + CMVPerTFCompressed* tfCompressed{nullptr}; + CMVPerTF* tfRaw{nullptr}; + CMVPerTF* tfDecoded{nullptr}; ///< scratch buffer used when decompressing + long firstTFInTree{-1}; ///< first global TF index from tree UserInfo ("firstTF"); -1 if absent + long lastTFInTree{-1}; ///< last global TF index from tree UserInfo ("lastTF"); -1 if absent + + /// Open path and set up branch addresses. Returns false on any error + bool open(const std::string& path); + + /// Load entry iEntry and return a pointer to the decoded CMVPerTF, or nullptr on error + const CMVPerTF* getEntry(long long iEntry); + + /// Release all resources + void close(); +}; + +} // namespace o2::tpc + +#endif // ALICEO2_TPC_CMVHELPER_H_ diff --git a/Detectors/TPC/calibration/include/TPCCalibration/CalculatedEdx.h b/Detectors/TPC/calibration/include/TPCCalibration/CalculatedEdx.h index 3a744d2b1cfb4..4d8c4e89322a8 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/CalculatedEdx.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/CalculatedEdx.h @@ -22,10 +22,9 @@ #include "DataFormatsTPC/dEdxInfo.h" #include "GPUO2InterfaceRefit.h" #include "CalibdEdxContainer.h" -#include "CorrectionMapsHelper.h" #include "CommonUtils/TreeStreamRedirector.h" #include "TPCCalibration/CorrectdEdxDistortions.h" - +#include "TPCFastTransformPOD.h" #include namespace o2::tpc @@ -225,10 +224,11 @@ class CalculatedEdx unsigned int getOccupancy(const o2::tpc::ClusterNative& cl) const; private: - std::vector* mTracks{nullptr}; ///< vector containing the tpc tracks which will be processed - std::vector* mTPCTrackClIdxVecInput{nullptr}; ///< input vector with TPC tracks cluster indicies - const o2::tpc::ClusterNativeAccess* mClusterIndex{nullptr}; ///< needed to access clusternative with tpctracks - o2::gpu::CorrectionMapsHelper mTPCCorrMapsHelper; ///< cluster correction maps helper + std::vector* mTracks{nullptr}; ///< vector containing the tpc tracks which will be processed + std::vector* mTPCTrackClIdxVecInput{nullptr}; ///< input vector with TPC tracks cluster indicies + const o2::tpc::ClusterNativeAccess* mClusterIndex{nullptr}; ///< needed to access clusternative with tpctracks + const o2::gpu::TPCFastTransformPOD* mTPCCorrMap{nullptr}; ///< cluster correction maps helper + o2::gpu::aligned_unique_buffer_ptr mTPCCorrMapBuffer; std::vector mTPCRefitterShMap; ///< externally set TPC clusters sharing map std::vector mTPCRefitterOccMap; ///< externally set TPC clusters occupancy map std::unique_ptr mRefit{nullptr}; ///< TPC refitter used for TPC tracks refit during the reconstruction @@ -247,4 +247,4 @@ class CalculatedEdx } // namespace o2::tpc -#endif \ No newline at end of file +#endif diff --git a/Detectors/TPC/calibration/include/TPCCalibration/CalibPadGainTracks.h b/Detectors/TPC/calibration/include/TPCCalibration/CalibPadGainTracks.h index b3fd532c06b8e..f8c7a4e1894b1 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/CalibPadGainTracks.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/CalibPadGainTracks.h @@ -22,7 +22,6 @@ #include "TPCBase/CalDet.h" #include "TPCCalibration/CalibPadGainTracksBase.h" #include "CalibdEdxTrackTopologyPol.h" -#include "TPCFastTransform.h" #include #include @@ -36,8 +35,8 @@ namespace o2 namespace gpu { class GPUO2InterfaceRefit; -class CorrectionMapsHelper; -} +class TPCFastTransformPOD; +} // namespace gpu namespace tpc { @@ -214,7 +213,7 @@ class CalibPadGainTracks : public CalibPadGainTracksBase void setTPCVDrift(const o2::tpc::VDriftCorrFact& v); /// set cluster correction maps helper - void setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph); + void setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph); private: gsl::span* mTracks{nullptr}; ///> mDEdxBuffer{}; ///> mClTrk; /// mDedxTmp{}; /// #include -#endif #include "CorrectionMapsHelper.h" +#include "CorrectionMapsTypes.h" namespace o2 { @@ -30,26 +28,12 @@ class ProcessingContext; class ConcreteDataMatcher; class InputSpec; class ConfigParamSpec; -class ConfigParamRegistry; class InitContext; } // namespace framework namespace tpc { -struct CorrectionMapsLoaderGloOpts { - int lumiType = 0; ///< what estimator to used for corrections scaling: 0: no scaling, 1: CTP, 2: IDC - int lumiMode = 0; ///< what corrections method to use: 0: classical scaling, 1: Using of the derivative map, 2: Using of the derivative map for MC - bool enableMShapeCorrection = false; - bool requestCTPLumi = true; //< request CTP Lumi regardless of what is used for corrections scaling - bool checkCTPIDCconsistency = true; //< check the selected CTP or IDC scaling source being consistent with mean scaler of the map - - bool needTPCScalersWorkflow() const - { - return lumiType == 2 || enableMShapeCorrection; - } -}; - class CorrectionMapsLoader : public o2::gpu::CorrectionMapsHelper { public: @@ -57,30 +41,20 @@ class CorrectionMapsLoader : public o2::gpu::CorrectionMapsHelper ~CorrectionMapsLoader() = default; CorrectionMapsLoader(const CorrectionMapsLoader&) = delete; -#ifndef GPUCA_GPUCODE_DEVICE bool accountCCDBInputs(const o2::framework::ConcreteDataMatcher& matcher, void* obj); - void extractCCDBInputs(o2::framework::ProcessingContext& pc); - void updateVDrift(float vdriftCorr, float vdrifRef, float driftTimeOffset = 0); - void init(o2::framework::InitContext& ic); - void copySettings(const CorrectionMapsLoader& src); - void updateInverse(); /// recalculate inverse correction + void extractCCDBInputs(o2::framework::ProcessingContext& pc, float tpcScaler = -1.f); + void init(o2::framework::InitContext& ic, bool idcsAvailable); void checkMeanScaleConsistency(float meanLumi, float threshold) const; - float getMapMeanRate(const o2::gpu::TPCFastTransform* mp, bool lumiOverridden) const; - static void requestCCDBInputs(std::vector& inputs, std::vector& options, const CorrectionMapsLoaderGloOpts& gloOpts); - static void addGlobalOptions(std::vector& options); - static void addOptions(std::vector& options); - static CorrectionMapsLoaderGloOpts parseGlobalOptions(const o2::framework::ConfigParamRegistry& opts); + static void requestCCDBInputs(std::vector& inputs, const o2::tpc::CorrectionMapsGloOpts& gloOpts); protected: static void addOption(std::vector& options, o2::framework::ConfigParamSpec&& osp); static void addInput(std::vector& inputs, o2::framework::InputSpec&& isp); - float mInstLumiCTPFactor = 1.0; // multiplicative factor for inst. lumi - int mLumiCTPSource = 0; // 0: main, 1: alternative CTP lumi source - std::unique_ptr mCorrMapMShape{nullptr}; + float mInstLumiCTPFactor = 1.0; // multiplicative factor for inst. lumi + int mLumiCTPSource = 0; // 0: main, 1: alternative CTP lumi source bool mIDC2CTPFallbackActive = false; // flag indicating that fallback from IDC to CTP scaling is active -#endif }; } // namespace tpc diff --git a/Detectors/TPC/calibration/include/TPCCalibration/CorrectionMapsOptions.h b/Detectors/TPC/calibration/include/TPCCalibration/CorrectionMapsOptions.h new file mode 100644 index 0000000000000..18bde0174a939 --- /dev/null +++ b/Detectors/TPC/calibration/include/TPCCalibration/CorrectionMapsOptions.h @@ -0,0 +1,56 @@ +// 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 CorrectionMapsOptions.h +/// \brief Helper class to parse options for correction maps +/// \author matthias.kleiner@cern.ch + +#ifndef TPC_CORRECTION_MAPS_OPTIONS_H_ +#define TPC_CORRECTION_MAPS_OPTIONS_H_ + +#ifndef GPUCA_GPUCODE_DEVICE +#include +#include +#endif +#include "CorrectionMapsTypes.h" + +namespace o2 +{ +namespace framework +{ +class ConfigParamRegistry; +class ConfigParamSpec; +} // namespace framework + +namespace tpc +{ + +class CorrectionMapsOptions +{ + public: + CorrectionMapsOptions() = default; + ~CorrectionMapsOptions() = default; + CorrectionMapsOptions(const CorrectionMapsOptions&) = delete; + +#ifndef GPUCA_GPUCODE_DEVICE + static CorrectionMapsGloOpts parseGlobalOptions(const o2::framework::ConfigParamRegistry& opts); + static void addGlobalOptions(std::vector& options); + + protected: + static void addOption(std::vector& options, o2::framework::ConfigParamSpec&& osp); +#endif +}; + +} // namespace tpc + +} // namespace o2 + +#endif diff --git a/Detectors/TPC/calibration/include/TPCCalibration/TPCFastSpaceChargeCorrectionHelper.h b/Detectors/TPC/calibration/include/TPCCalibration/TPCFastSpaceChargeCorrectionHelper.h index eff4972679ed8..40c5634b4f1e8 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/TPCFastSpaceChargeCorrectionHelper.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/TPCFastSpaceChargeCorrectionHelper.h @@ -85,16 +85,28 @@ class TPCFastSpaceChargeCorrectionHelper const int nKnotsY = 10, const int nKnotsZ = 20); /// Create SpaceCharge correction out of the voxel tree + /// \param trackResiduals TrackResiduals object + /// \param voxResTree TTree with voxel residuals + /// \param voxResTreeInverse TTree with inverse voxel residuals + /// \param useSmoothed if true, use smoothed residuals + /// \param invertSigns if true, invert the signs of the residuals + /// \param fitPointsDirect debug: pointer to the data used for the direct correction + /// \param fitPointsInverse debug: pointer to the data used for the inverse correction + /// \return pointer to the created TPCFastSpaceChargeCorrection object + /// \note voxel trees wont be changed. They are read as non-const because of the ROOT::TTreeProcessorMT interface + /// std::unique_ptr createFromTrackResiduals( - const o2::tpc::TrackResiduals& trackResiduals, TTree* voxResTree, bool useSmoothed = false, bool invertSigns = false); + const o2::tpc::TrackResiduals& trackResiduals, TTree* voxResTree, TTree* voxResTreeInverse, // + bool useSmoothed, bool invertSigns, // + TPCFastSpaceChargeCorrectionMap* fitPointsDirect = nullptr, + TPCFastSpaceChargeCorrectionMap* fitPointsInverse = nullptr); + /// _______________ Utilities ________________________ const TPCFastTransformGeo& getGeometry() { return mGeo; } TPCFastSpaceChargeCorrectionMap& getCorrectionMap() { return mCorrectionMap; } - void fillSpaceChargeCorrectionFromMap(TPCFastSpaceChargeCorrection& correction); - void testGeometry(const TPCFastTransformGeo& geo) const; /// initialise inverse transformation @@ -103,15 +115,39 @@ class TPCFastSpaceChargeCorrectionHelper /// initialise inverse transformation from linear combination of several input corrections void initInverse(std::vector& corrections, const std::vector& scaling, bool prn); + /// merge several corrections + /// \param mainCorrection main correction + /// \param scale scaling factor for the main correction + /// \param additionalCorrections vector of pairs of additional corrections and their scaling factors + /// \param prn printout flag + /// \return main correction merged with additional corrections + void mergeCorrections( + o2::gpu::TPCFastSpaceChargeCorrection& mainCorrection, float scale, + const std::vector>& additionalCorrections, bool prn); + + /// how far the voxel mean is allowed to be outside of the voxel (1.1 means 10%) + void setVoxelMeanValidityRange(double range) + { + mVoxelMeanValidityRange = range; + } + + double getVoxelMeanValidityRange() const { return mVoxelMeanValidityRange; } + + /// debug: if true, use voxel centers instead of the fitted positions for correction + void setDebugUseVoxelCenters(); + + bool isDebugUseVoxelCenters() const { return mDebugUseVoxelCenters; } + + /// debug: if true, mirror the data from the A side to the C side of the TPC + void setDebugMirrorAdata2C(); + + bool isDebugMirrorAdata2C() const { return mDebugMirrorAdata2C; } + private: /// geometry initialization void initGeometry(); - /// get space charge correction in internal TPCFastTransform coordinates u,v->dx,du,dv - void getSpaceChargeCorrection(const TPCFastSpaceChargeCorrection& correction, int slice, int row, o2::gpu::TPCFastSpaceChargeCorrectionMap::CorrectionPoint p, double& su, double& sv, double& dx, double& du, double& dv); - - /// initialise max drift length - void initMaxDriftLength(o2::gpu::TPCFastSpaceChargeCorrection& correction, bool prn); + void fillSpaceChargeCorrectionFromMap(TPCFastSpaceChargeCorrection& correction, bool processingInverseCorrection); static TPCFastSpaceChargeCorrectionHelper* sInstance; ///< singleton instance bool mIsInitialized = 0; ///< initialization flag @@ -120,6 +156,11 @@ class TPCFastSpaceChargeCorrectionHelper TPCFastSpaceChargeCorrectionMap mCorrectionMap{0, 0}; + double mVoxelMeanValidityRange{1.1}; ///< debug: how far the voxel mean is allowed to be outside of the voxel (1.1 means 10%) + + bool mDebugUseVoxelCenters{false}; ///< debug: if true, use voxel centers instead of the fitted positions for correction + bool mDebugMirrorAdata2C{false}; ///< debug: if true, mirror the data from the A side to the C side of the TPC + ClassDefNV(TPCFastSpaceChargeCorrectionHelper, 0); }; diff --git a/Detectors/TPC/calibration/include/TPCCalibration/TrackDump.h b/Detectors/TPC/calibration/include/TPCCalibration/TrackDump.h index 34ad5b85a1eed..adbf3ecf5a299 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/TrackDump.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/TrackDump.h @@ -24,7 +24,7 @@ #include "SimulationDataFormat/MCCompLabel.h" #include "CommonUtils/TreeStreamRedirector.h" #include "DataFormatsTPC/Constants.h" -#include "CorrectionMapsHelper.h" +#include "TPCFastTransformPOD.h" /// \file TrackDump.h /// \author Jens Wiechula (Jens.Wiechula@ikf.uni-frankfurt.de) @@ -77,14 +77,15 @@ class TrackDump float gyc(float vertexTime = 0) const; float zc(float vertexTime = 0) const; - static gpu::CorrectionMapsHelper sCorrHelper; + inline static o2::gpu::aligned_unique_buffer_ptr corrMapBuffer; // buffer for owning the correction map in case of update during runtime + inline static const o2::gpu::TPCFastTransformPOD* corrMap{nullptr}; // local copy of the correction map for quick access to the transform functions static void loadCorrMaps(std::string_view corrMapFile, std::string_view corrMapFileRef = ""); ClassDefNV(ClusterNativeAdd, 1); }; struct TrackInfo : public TrackTPC { TrackInfo() = default; - TrackInfo(const TrackTPC& track) : TrackTPC(track){}; + TrackInfo(const TrackTPC& track) : TrackTPC(track) {}; TrackInfo(const TrackInfo&) = default; ~TrackInfo() = default; diff --git a/Detectors/TPC/calibration/macro/drawCMV.C b/Detectors/TPC/calibration/macro/drawCMV.C new file mode 100644 index 0000000000000..78e951fcfd676 --- /dev/null +++ b/Detectors/TPC/calibration/macro/drawCMV.C @@ -0,0 +1,150 @@ +// 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. + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include + +#include "TTree.h" +#include "TH1F.h" +#include "TH2F.h" +#include "TCanvas.h" + +#include "TPCBase/Utils.h" +#include "TPCCalibration/CMVContainer.h" +#include "TPCCalibration/CMVHelper.h" + +#endif + +using namespace o2::tpc; + +/// Draw CMV (Common Mode Values) vs timebin from a CCDB TTree file +/// \param filename input ROOT file containing the ccdb_object TTree +/// \param outDir output directory for saved plots; nothing is saved if empty +/// \return array of canvases +TObjArray* drawCMV(std::string_view filename, std::string_view outDir, std::string_view rootFileName = "CMVCanvases.root") +{ + TObjArray* arrCanvases = new TObjArray; + arrCanvases->SetName("CMV"); + + // open file + CMVFileHandle fh; + if (!fh.open(std::string(filename))) { + fmt::print("ERROR: cannot open '{}'\n", filename); + return arrCanvases; + } + fmt::print("Opened file: {}\n", filename); + fmt::print("Tree 'ccdb_object' found, entries: {}\n", fh.tree->GetEntries()); + + fmt::print("firstTF: {}, lastTF: {}\n", fh.firstTFInTree, fh.lastTFInTree); + + const int nEntries = fh.tree->GetEntries(); + if (nEntries == 0) { + fmt::print("ERROR: no entries in tree\n"); + fh.close(); + return arrCanvases; + } + + constexpr int nCRUs = CRU::MaxCRU; + constexpr int nTimeBins = cmv::NTimeBinsPerTF; + + TH2F* h2d = new TH2F("hCMVvsTimeBin", ";Timebin (200 ns);Common Mode Values (ADC)", + 100, 0, nTimeBins, + 110, -100.5, 9.5); + h2d->SetDirectory(nullptr); + h2d->SetStats(1); + TH1F* h1d = new TH1F("hCMV", ";Common Mode Values (ADC);Counts", + 110, -100.5, 9.5); + h1d->SetDirectory(nullptr); + h1d->SetStats(1); + + TH1F* h1dCRU = new TH1F("hCRU", ";CRU;Counts", + 360, -0.5, 359.5); + h1dCRU->SetDirectory(nullptr); + h1dCRU->SetStats(1); + TH2F* h2dCRU = new TH2F("hCMVvsCRU", ";CRU;Common Mode Values (ADC)", + 360, -0.5, 359.5, + 110, -100.5, 9.5); + h2dCRU->SetDirectory(nullptr); + h2dCRU->SetStats(0); + + fmt::print("Branch format: {}\n", fh.isCompressed ? "CMVPerTFCompressed" : "CMVPerTF (raw)"); + + long firstOrbit = -1; + long firstOrbitDPL = -1; + + // Pre-allocate fill arrays once; x-values (timebins) are constant across entries and CRUs + const int fillsPerEntry = nCRUs * nTimeBins; + std::vector xArr(fillsPerEntry), yArr(fillsPerEntry), wArr(fillsPerEntry, 1.0), cruArr(fillsPerEntry); + for (int cru = 0; cru < nCRUs; ++cru) { + for (int tb = 0; tb < nTimeBins; ++tb) { + xArr[cru * nTimeBins + tb] = tb; + cruArr[cru * nTimeBins + tb] = cru; + } + } + + for (int i = 0; i < nEntries; ++i) { + const CMVPerTF* tf = fh.getEntry(i); + if (!tf) { + continue; + } + + firstOrbit = tf->firstOrbit; + firstOrbitDPL = tf->firstOrbitDPL; + + fmt::print("Entry {}: firstOrbit: {}, firstOrbitDPL: {}\n", i, firstOrbit, firstOrbitDPL); + + for (int cru = 0; cru < nCRUs; ++cru) { + for (int tb = 0; tb < nTimeBins; ++tb) { + yArr[cru * nTimeBins + tb] = tf->getCMVFloat(cru, tb); + // fmt::print("Entry {}: cru: {}, tb: {}, cmv: {}\n", i, cru, tb, tf->getCMVFloat(cru, tb)); + } + } + h2d->FillN(fillsPerEntry, xArr.data(), yArr.data(), wArr.data()); + h1d->FillN(fillsPerEntry, yArr.data(), wArr.data()); + h2dCRU->FillN(fillsPerEntry, cruArr.data(), yArr.data(), wArr.data()); + h1dCRU->FillN(fillsPerEntry, cruArr.data(), wArr.data()); + } + + fh.close(); + + // draw + auto* c = new TCanvas("cCMVvsTimeBin", ""); + c->SetLogz(); + h2d->Draw("colz"); + + arrCanvases->Add(c); + + auto* c1 = new TCanvas("cCMVDistribution", ""); + c1->SetLogy(); + h1d->Draw(); + + arrCanvases->Add(c1); + + auto* c2 = new TCanvas("cCRUDistribution", ""); + h1dCRU->Draw(); + + arrCanvases->Add(c2); + + auto* c3 = new TCanvas("cCMVvsCRU", ""); + c3->SetLogz(); + h2dCRU->Draw("colz"); + + arrCanvases->Add(c3); + + if (outDir.size()) { + utils::saveCanvases(*arrCanvases, outDir, "", rootFileName); + } + + return arrCanvases; +} diff --git a/Detectors/TPC/calibration/src/CMVContainer.cxx b/Detectors/TPC/calibration/src/CMVContainer.cxx new file mode 100644 index 0000000000000..0e02d32e754d5 --- /dev/null +++ b/Detectors/TPC/calibration/src/CMVContainer.cxx @@ -0,0 +1,738 @@ +// 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 CMVContainer.cxx +/// @author Tuba Gündem, tuba.gundem@cern.ch + +#include +#include +#include +#include +#include +#include + +#include "TFile.h" + +#include "TPCCalibration/CMVContainer.h" +#include "TPCBase/CRU.h" +#include "DataFormatsTPC/CMV.h" + +namespace o2::tpc +{ + +// CMVPerTF private helpers + +int32_t CMVPerTF::cmvToSigned(uint16_t raw) +{ + const int32_t mag = raw & 0x7FFF; + return (raw >> 15) ? mag : -mag; +} + +uint16_t CMVPerTF::quantizeBelowThreshold(uint16_t raw, float quantizationMean, float quantizationSigma) +{ + if (raw == 0u) { + return raw; + } + + if (quantizationSigma <= 0.f) { + return raw; + } + + const float adc = (raw & 0x7FFFu) / 128.f; + const float distance = (adc - quantizationMean) / quantizationSigma; + const float lossStrength = std::exp(-0.5f * distance * distance); + + // A true Gaussian bell: strongest trimming around the mean, then gradual recovery away from it + float quantizedADC = adc; + if (lossStrength > 0.85f) { + quantizedADC = std::round(adc * 10.f) / 10.f; + } else if (lossStrength > 0.60f) { + quantizedADC = std::round(adc * 100.f) / 100.f; + } else if (lossStrength > 0.30f) { + quantizedADC = std::round(adc * 1000.f) / 1000.f; + } else if (lossStrength > 0.12f) { + quantizedADC = std::round(adc * 10000.f) / 10000.f; + } else if (lossStrength > 0.03f) { + quantizedADC = std::round(adc * 1000000.f) / 1000000.f; + } + + // Snap the chosen decimal-style value back to the nearest raw I8F7 level + const uint16_t quantizedMagnitude = static_cast(std::clamp(std::lround(quantizedADC * 128.f), 0l, 0x7FFFl)); + return static_cast((raw & 0x8000u) | quantizedMagnitude); +} + +uint32_t CMVPerTF::zigzagEncode(int32_t value) +{ + return (static_cast(value) << 1) ^ static_cast(value >> 31); +} + +void CMVPerTF::encodeVarintInto(uint32_t value, std::vector& out) +{ + while (value > 0x7F) { + out.push_back(static_cast((value & 0x7F) | 0x80)); + value >>= 7; + } + out.push_back(static_cast(value)); +} + +// Shared file-local helpers + +namespace +{ + +int32_t zigzagDecodeLocal(uint32_t value) +{ + return static_cast((value >> 1) ^ -(value & 1)); +} + +uint16_t signedToCmvLocal(int32_t val) +{ + const uint16_t mag = static_cast(std::abs(val)) & 0x7FFF; + return static_cast((val >= 0 ? 0x8000u : 0u) | mag); +} + +uint32_t decodeVarintLocal(const uint8_t*& data, const uint8_t* end) +{ + uint32_t value = 0; + int shift = 0; + while (data < end && (*data & 0x80)) { + value |= static_cast(*data & 0x7F) << shift; + shift += 7; + ++data; + } + if (data >= end) { + throw std::runtime_error("decodeVarintLocal: unexpected end of varint data"); + } + value |= static_cast(*data) << shift; + ++data; + return value; +} + +/// Build and serialise a canonical Huffman table + bitstream over `symbols` into `buf` +/// Format: +/// 4 bytes LE uint32_t : numSymbols +/// numSymbols × 5 bytes: symbol (4 bytes LE) + code length (1 byte) +/// 8 bytes LE uint64_t : totalBits +/// ceil(totalBits/8) bytes: MSB-first bitstream +void huffmanEncode(const std::vector& symbols, std::vector& buf) +{ + if (symbols.empty()) { + // Write a valid empty Huffman stream: numSymbols=0, totalBits=0. + // The decoder handles this correctly (returns an empty symbol vector). + for (int i = 0; i < 12; ++i) { + buf.push_back(0); + } + return; + } + + // Frequency count + std::map freq; + for (const uint32_t z : symbols) { + ++freq[z]; + } + + // Build tree using index-based min-heap + struct HNode { + uint64_t freq{0}; + uint32_t sym{0}; + int left{-1}, right{-1}; + bool isLeaf{true}; + }; + std::vector nodes; + nodes.reserve(freq.size() * 2); + for (const auto& [sym, f] : freq) { + nodes.push_back({f, sym, -1, -1, true}); + } + + auto cmp = [&](int a, int b) { + return nodes[a].freq != nodes[b].freq ? nodes[a].freq > nodes[b].freq : nodes[a].sym > nodes[b].sym; + }; + std::vector heap; + heap.reserve(nodes.size()); + for (int i = 0; i < static_cast(nodes.size()); ++i) { + heap.push_back(i); + } + std::make_heap(heap.begin(), heap.end(), cmp); + + while (heap.size() > 1) { + std::pop_heap(heap.begin(), heap.end(), cmp); + const int a = heap.back(); + heap.pop_back(); + std::pop_heap(heap.begin(), heap.end(), cmp); + const int b = heap.back(); + heap.pop_back(); + nodes.push_back({nodes[a].freq + nodes[b].freq, 0, a, b, false}); + heap.push_back(static_cast(nodes.size()) - 1); + std::push_heap(heap.begin(), heap.end(), cmp); + } + + // Assign code lengths via iterative DFS + std::map codeLens; + { + const int root = heap[0]; + std::vector> stack; + stack.push_back({root, 0}); + while (!stack.empty()) { + auto [idx, depth] = stack.back(); + stack.pop_back(); + if (nodes[idx].isLeaf) { + codeLens[nodes[idx].sym] = static_cast(depth == 0 ? 1 : depth); + } else { + stack.push_back({nodes[idx].left, depth + 1}); + stack.push_back({nodes[idx].right, depth + 1}); + } + } + } + + // Sort by (codeLen ASC, symbol ASC) for canonical assignment + struct SymLen { + uint32_t sym; + uint8_t len; + }; + std::vector symLens; + symLens.reserve(codeLens.size()); + for (const auto& [sym, len] : codeLens) { + symLens.push_back({sym, len}); + } + std::sort(symLens.begin(), symLens.end(), [](const SymLen& a, const SymLen& b) { + return a.len != b.len ? a.len < b.len : a.sym < b.sym; + }); + + // Assign canonical codes + std::map> codeTable; + { + uint32_t code = 0; + uint8_t prevLen = 0; + for (const auto& sl : symLens) { + if (prevLen != 0) { + code = (code + 1) << (sl.len - prevLen); + } + codeTable[sl.sym] = {code, sl.len}; + prevLen = sl.len; + } + } + + // Serialise table header + buf.reserve(buf.size() + 4 + symLens.size() * 5 + 8 + (symbols.size() / 8 + 1)); + const uint32_t numSym = static_cast(symLens.size()); + for (int i = 0; i < 4; ++i) { + buf.push_back(static_cast((numSym >> (8 * i)) & 0xFF)); + } + for (const auto& sl : symLens) { + for (int i = 0; i < 4; ++i) { + buf.push_back(static_cast((sl.sym >> (8 * i)) & 0xFF)); + } + buf.push_back(sl.len); + } + + // Placeholder for totalBits + const size_t totalBitsOffset = buf.size(); + for (int i = 0; i < 8; ++i) { + buf.push_back(0); + } + + // Encode bitstream (MSB-first) + uint64_t totalBits = 0; + uint8_t curByte = 0; + int bitsInByte = 0; + for (const uint32_t z : symbols) { + const auto& [code, len] = codeTable.at(z); + for (int b = static_cast(len) - 1; b >= 0; --b) { + curByte = static_cast(curByte | (((code >> b) & 1u) << (7 - bitsInByte))); + ++bitsInByte; + ++totalBits; + if (bitsInByte == 8) { + buf.push_back(curByte); + curByte = 0; + bitsInByte = 0; + } + } + } + if (bitsInByte > 0) { + buf.push_back(curByte); + } + + // Backfill totalBits + for (int i = 0; i < 8; ++i) { + buf[totalBitsOffset + i] = static_cast((totalBits >> (8 * i)) & 0xFF); + } +} + +/// Decode `N` symbols from a canonical Huffman payload at [ptr, end) +/// `ptr` must point to the start of the Huffman table header (numSymbols field) +/// After return, `ptr` is advanced past the bitstream +std::vector huffmanDecode(const uint8_t*& ptr, const uint8_t* end, uint32_t N) +{ + auto readU32 = [&]() -> uint32_t { + if (ptr + 4 > end) { + throw std::runtime_error("huffmanDecode: unexpected end reading uint32"); + } + const uint32_t v = static_cast(ptr[0]) | (static_cast(ptr[1]) << 8) | + (static_cast(ptr[2]) << 16) | (static_cast(ptr[3]) << 24); + ptr += 4; + return v; + }; + + const uint32_t numSym = readU32(); + struct SymLen { + uint32_t sym; + uint8_t len; + }; + std::vector symLens(numSym); + for (uint32_t i = 0; i < numSym; ++i) { + symLens[i].sym = readU32(); + if (ptr >= end) { + throw std::runtime_error("huffmanDecode: unexpected end reading code length"); + } + symLens[i].len = *ptr++; + } + + std::map firstCode; + std::map> symsByLen; + { + uint32_t code = 0; + uint8_t prevLen = 0; + for (const auto& sl : symLens) { + if (prevLen != 0) { + code = (code + 1) << (sl.len - prevLen); + } + if (!firstCode.count(sl.len)) { + firstCode[sl.len] = code; + } + symsByLen[sl.len].push_back(sl.sym); + prevLen = sl.len; + } + } + + if (ptr + 8 > end) { + throw std::runtime_error("huffmanDecode: unexpected end reading totalBits"); + } + uint64_t totalBits = 0; + for (int i = 0; i < 8; ++i) { + totalBits |= static_cast(ptr[i]) << (8 * i); + } + ptr += 8; + + const uint8_t minLen = symLens.empty() ? 1 : symLens.front().len; + const uint8_t maxLen = symLens.empty() ? 1 : symLens.back().len; + uint64_t bitsRead = 0; + uint8_t curByte = 0; + int bitPos = -1; + + auto nextBit = [&]() -> int { + if (bitPos < 0) { + if (ptr >= end) { + throw std::runtime_error("huffmanDecode: unexpected end of bitstream"); + } + curByte = *ptr++; + bitPos = 7; + } + const int bit = (curByte >> bitPos) & 1; + --bitPos; + return bit; + }; + + std::vector out; + out.reserve(N); + while (out.size() < N) { + uint32_t accum = 0; + bool found = false; + for (uint8_t curLen = 1; curLen <= maxLen; ++curLen) { + if (bitsRead >= totalBits) { + throw std::runtime_error("huffmanDecode: bitstream exhausted before all symbols decoded"); + } + accum = (accum << 1) | static_cast(nextBit()); + ++bitsRead; + if (curLen < minLen) { + continue; + } + const auto fcIt = firstCode.find(curLen); + if (fcIt == firstCode.end()) { + continue; + } + if (accum >= fcIt->second) { + const uint32_t idx = accum - fcIt->second; + const auto& sv = symsByLen.at(curLen); + if (idx < sv.size()) { + out.push_back(sv[idx]); + found = true; + break; + } + } + } + if (!found) { + throw std::runtime_error("huffmanDecode: invalid Huffman code in bitstream"); + } + } + return out; +} + +} // anonymous namespace + +// CMVPerTF public methods + +uint16_t CMVPerTF::getCMV(const int cru, const int timeBin) const +{ + if (cru < 0 || cru >= static_cast(CRU::MaxCRU)) { + throw std::out_of_range(fmt::format("CMVPerTF::getCMV: cru {} out of range [0, {})", cru, static_cast(CRU::MaxCRU))); + } + if (timeBin < 0 || static_cast(timeBin) >= cmv::NTimeBinsPerTF) { + throw std::out_of_range(fmt::format("CMVPerTF::getCMV: timeBin {} out of range [0, {})", timeBin, static_cast(cmv::NTimeBinsPerTF))); + } + return mDataPerTF[cru * cmv::NTimeBinsPerTF + timeBin]; +} + +float CMVPerTF::getCMVFloat(const int cru, const int timeBin) const +{ + const uint16_t raw = getCMV(cru, timeBin); + const uint16_t mag = raw & 0x7FFF; + if (mag == 0) { + return 0.0f; // 0x0000 and 0x8000 both represent zero; return +0 to avoid -0 display + } + const bool positive = (raw >> 15) & 1; // bit 15: sign (1=positive, 0=negative) + return positive ? mag / 128.f : -mag / 128.f; +} + +void CMVPerTF::zeroSmallValues(float threshold) +{ + if (threshold <= 0.f) { + return; + } + for (uint32_t i = 0; i < static_cast(CRU::MaxCRU) * cmv::NTimeBinsPerTF; ++i) { + const float mag = (mDataPerTF[i] & 0x7FFF) / 128.f; + if (mag < threshold) { + mDataPerTF[i] = 0; + } + } +} + +void CMVPerTF::roundToIntegers(uint16_t threshold) +{ + if (threshold == 0) { + return; + } + for (uint32_t i = 0; i < static_cast(CRU::MaxCRU) * cmv::NTimeBinsPerTF; ++i) { + const uint16_t raw = mDataPerTF[i]; + if (raw == 0) { + continue; + } + const uint16_t rounded = static_cast(((raw & 0x7FFFu) + 64u) >> 7); + if (rounded > threshold) { + continue; // above range: keep full precision + } + mDataPerTF[i] = (rounded == 0) ? 0 : static_cast((raw & 0x8000u) | (rounded << 7)); + } +} + +void CMVPerTF::trimGaussianPrecision(float mean, float sigma) +{ + if (sigma <= 0.f) { + return; + } + + for (uint32_t i = 0; i < static_cast(CRU::MaxCRU) * cmv::NTimeBinsPerTF; ++i) { + mDataPerTF[i] = quantizeBelowThreshold(mDataPerTF[i], mean, sigma); + } +} + +CMVPerTFCompressed CMVPerTF::compress(uint8_t flags) const +{ + CMVPerTFCompressed out; + out.firstOrbit = firstOrbit; + out.firstOrbitDPL = firstOrbitDPL; + out.mFlags = flags; + + if (flags & CMVEncoding::kSparse) { + // --- Sparse path: position stream + value stream --- + + // Single pass per CRU: build the position stream and collect raw non-zero values. + std::vector posStream; + std::vector rawValues; + + for (int cru = 0; cru < static_cast(CRU::MaxCRU); ++cru) { + struct Entry { + uint32_t tb; + uint16_t val; + }; + std::vector entries; + for (uint32_t tb = 0; tb < cmv::NTimeBinsPerTF; ++tb) { + const uint16_t val = mDataPerTF[cru * cmv::NTimeBinsPerTF + tb]; + if (val != 0) { + entries.push_back({tb, val}); + } + } + + encodeVarintInto(static_cast(entries.size()), posStream); + uint32_t prevTB = 0; + bool first = true; + for (const auto& e : entries) { + encodeVarintInto(first ? e.tb : (e.tb - prevTB), posStream); + rawValues.push_back(e.val); + prevTB = e.tb; + first = false; + } + } + + // Encode the value stream based on flags. + std::vector valStream; + if (flags & CMVEncoding::kZigzag) { + std::vector zigzags; + zigzags.reserve(rawValues.size()); + for (const uint16_t v : rawValues) { + zigzags.push_back(zigzagEncode(cmvToSigned(v))); + } + if (flags & CMVEncoding::kHuffman) { + huffmanEncode(zigzags, valStream); + } else { // kVarint + for (const uint32_t z : zigzags) { + encodeVarintInto(z, valStream); + } + } + } else { + // Raw uint16 LE + for (const uint16_t v : rawValues) { + valStream.push_back(static_cast(v & 0xFF)); + valStream.push_back(static_cast(v >> 8)); + } + } + + // Assemble: [4 bytes posStreamSize][posStream][valStream] + const uint32_t posStreamSize = static_cast(posStream.size()); + out.mData.reserve(4 + posStream.size() + valStream.size()); + for (int i = 0; i < 4; ++i) { + out.mData.push_back(static_cast((posStreamSize >> (8 * i)) & 0xFF)); + } + out.mData.insert(out.mData.end(), posStream.begin(), posStream.end()); + out.mData.insert(out.mData.end(), valStream.begin(), valStream.end()); + + } else { + // --- Dense path: all CRU * TimeBin values --- + const uint32_t total = static_cast(CRU::MaxCRU) * cmv::NTimeBinsPerTF; + + if (!(flags & CMVEncoding::kZigzag)) { + // No encoding: raw uint16 LE + out.mData.reserve(total * 2); + for (uint32_t i = 0; i < total; ++i) { + out.mData.push_back(static_cast(mDataPerTF[i] & 0xFF)); + out.mData.push_back(static_cast(mDataPerTF[i] >> 8)); + } + } else { + // Zigzag + optional delta (CRU-major, time-minor) + const bool useDelta = (flags & CMVEncoding::kDelta) != 0; + std::vector zigzags; + zigzags.reserve(total); + for (int cru = 0; cru < static_cast(CRU::MaxCRU); ++cru) { + int32_t prev = 0; + for (uint32_t tb = 0; tb < cmv::NTimeBinsPerTF; ++tb) { + const int32_t val = cmvToSigned(mDataPerTF[cru * cmv::NTimeBinsPerTF + tb]); + const int32_t encoded = useDelta ? (val - prev) : val; + if (useDelta) { + prev = val; + } + zigzags.push_back(zigzagEncode(encoded)); + } + } + + if (flags & CMVEncoding::kHuffman) { + huffmanEncode(zigzags, out.mData); + } else { // kVarint + for (const uint32_t z : zigzags) { + encodeVarintInto(z, out.mData); + } + } + } + } + + return out; +} + +// CMVPerTFCompressed::decompress staged pipeline + +std::vector> CMVPerTFCompressed::decodeSparsePositions(const uint8_t*& ptr, const uint8_t* end) +{ + // Read 4-byte LE posStreamSize + if (ptr + 4 > end) { + throw std::runtime_error("CMVPerTFCompressed::decompress: truncated position header"); + } + const uint32_t posStreamSize = static_cast(ptr[0]) | (static_cast(ptr[1]) << 8) | + (static_cast(ptr[2]) << 16) | (static_cast(ptr[3]) << 24); + ptr += 4; + + const uint8_t* posEnd = ptr + posStreamSize; + if (posEnd > end) { + throw std::runtime_error("CMVPerTFCompressed::decompress: posStream overflows payload"); + } + + // Decode per-CRU varint(N) + N×varint(tb_delta) + std::vector> positions; + const uint8_t* p = ptr; + for (int cru = 0; cru < static_cast(CRU::MaxCRU); ++cru) { + const uint32_t count = decodeVarintLocal(p, posEnd); + uint32_t tb = 0; + bool first = true; + for (uint32_t i = 0; i < count; ++i) { + const uint32_t delta = decodeVarintLocal(p, posEnd); + tb = first ? delta : (tb + delta); + first = false; + positions.emplace_back(cru, tb); + } + } + ptr = posEnd; // advance past the entire position block + return positions; +} + +std::vector CMVPerTFCompressed::decodeValueStream(const uint8_t*& ptr, const uint8_t* end, uint32_t N, uint8_t flags) +{ + if (flags & CMVEncoding::kHuffman) { + // Huffman-encoded symbols + return huffmanDecode(ptr, end, N); + } + + if (flags & CMVEncoding::kVarint) { + // Varint-encoded symbols + std::vector out; + out.reserve(N); + for (uint32_t i = 0; i < N; ++i) { + out.push_back(decodeVarintLocal(ptr, end)); + } + return out; + } + + // Raw uint16 LE (no value encoding) + std::vector out; + out.reserve(N); + for (uint32_t i = 0; i < N; ++i) { + if (ptr + 2 > end) { + throw std::runtime_error("CMVPerTFCompressed::decompress: unexpected end in raw value stream"); + } + const uint16_t v = static_cast(ptr[0]) | (static_cast(ptr[1]) << 8); + ptr += 2; + out.push_back(v); + } + return out; +} + +void CMVPerTFCompressed::decodeSparseValues(const std::vector& symbols, + const std::vector>& positions, + uint8_t flags, CMVPerTF* cmv) +{ + const bool useZigzag = (flags & CMVEncoding::kZigzag) != 0; + for (uint32_t i = 0; i < static_cast(positions.size()); ++i) { + uint16_t raw; + if (useZigzag) { + raw = signedToCmvLocal(zigzagDecodeLocal(symbols[i])); + } else { + raw = static_cast(symbols[i]); + } + cmv->mDataPerTF[positions[i].first * cmv::NTimeBinsPerTF + positions[i].second] = raw; + } +} + +void CMVPerTFCompressed::decodeDenseValues(const std::vector& symbols, uint8_t flags, CMVPerTF* cmv) +{ + const bool useZigzag = (flags & CMVEncoding::kZigzag) != 0; + const bool useDelta = (flags & CMVEncoding::kDelta) != 0; + + if (!useZigzag) { + // Symbols are raw uint16 values; write directly + for (uint32_t i = 0; i < static_cast(symbols.size()); ++i) { + cmv->mDataPerTF[i] = static_cast(symbols[i]); + } + return; + } + + // Inverse zigzag + optional inverse delta (CRU-major, time-minor) + uint32_t s = 0; + for (int cru = 0; cru < static_cast(CRU::MaxCRU); ++cru) { + int32_t prev = 0; + for (uint32_t tb = 0; tb < cmv::NTimeBinsPerTF; ++tb, ++s) { + int32_t val = zigzagDecodeLocal(symbols[s]); + if (useDelta) { + val += prev; + prev = val; + } + cmv->mDataPerTF[s] = signedToCmvLocal(val); + } + } +} + +void CMVPerTFCompressed::decompress(CMVPerTF* cmv) const +{ + if (!cmv) { + throw std::invalid_argument("CMVPerTFCompressed::decompress: cmv pointer is null"); + } + cmv->firstOrbit = firstOrbit; + cmv->firstOrbitDPL = firstOrbitDPL; + std::fill(std::begin(cmv->mDataPerTF), std::end(cmv->mDataPerTF), uint16_t(0)); + + const uint8_t* ptr = mData.data(); + const uint8_t* end = ptr + mData.size(); + + if (mFlags & CMVEncoding::kSparse) { + // Stage 1: decode position stream + auto positions = decodeSparsePositions(ptr, end); + const uint32_t N = static_cast(positions.size()); + + // Stage 2: decode value stream (Huffman / varint / raw) + auto symbols = decodeValueStream(ptr, end, N, mFlags); + + // Stage 3: inverse zigzag and scatter into CMV array + decodeSparseValues(symbols, positions, mFlags, cmv); + } else { + const uint32_t N = static_cast(CRU::MaxCRU) * cmv::NTimeBinsPerTF; + + // Stage 1: decode value stream (Huffman / varint / raw) + auto symbols = decodeValueStream(ptr, end, N, mFlags); + + // Stage 2: inverse zigzag, inverse delta, fill CMV array + decodeDenseValues(symbols, mFlags, cmv); + } +} + +std::unique_ptr CMVPerTF::toTTree() const +{ + auto tree = std::make_unique("ccdb_object", "ccdb_object"); + tree->SetAutoSave(0); + tree->SetDirectory(nullptr); + + const CMVPerTF* ptr = this; + tree->Branch("CMVPerTF", &ptr); + tree->Fill(); + + tree->ResetBranchAddresses(); + return tree; +} + +std::unique_ptr CMVPerTFCompressed::toTTree() const +{ + auto tree = std::make_unique("ccdb_object", "ccdb_object"); + tree->SetAutoSave(0); + tree->SetDirectory(nullptr); + + const CMVPerTFCompressed* ptr = this; + tree->Branch("CMVPerTFCompressed", &ptr); + tree->Fill(); + + tree->ResetBranchAddresses(); + return tree; +} + +void CMVPerTF::writeToFile(const std::string& filename, const std::unique_ptr& tree) +{ + TFile f(filename.c_str(), "RECREATE"); + if (f.IsZombie()) { + throw std::runtime_error(fmt::format("CMVPerTF::writeToFile: cannot open '{}'", filename)); + } + tree->Write(); + f.Close(); +} + +} // namespace o2::tpc diff --git a/Detectors/TPC/calibration/src/CMVHelper.cxx b/Detectors/TPC/calibration/src/CMVHelper.cxx new file mode 100644 index 0000000000000..abcbd977a9acb --- /dev/null +++ b/Detectors/TPC/calibration/src/CMVHelper.cxx @@ -0,0 +1,98 @@ +// 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 CMVHelper.cxx +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief Helper utilities for reading CMV ROOT files + +#include "TPCCalibration/CMVHelper.h" + +#include + +#include "TPCCalibration/CMVContainer.h" +#include "TParameter.h" + +namespace o2::tpc +{ + +bool CMVFileHandle::open(const std::string& path) +{ + file = TFile::Open(path.c_str()); + if (!file || file->IsZombie()) { + std::cerr << "CMVFileHandle: failed to open: " << path << "\n"; + return false; + } + file->GetObject("ccdb_object", tree); + if (!tree) { + std::cerr << "CMVFileHandle: TTree 'ccdb_object' not found in: " << path << "\n"; + close(); + return false; + } + + // Extract firstTF / lastTF from UserInfo if stored by the aggregation workflow + if (auto* ui = tree->GetUserInfo()) { + if (auto* p = dynamic_cast*>(ui->FindObject("firstTF"))) { + firstTFInTree = p->GetVal(); + } + if (auto* p = dynamic_cast*>(ui->FindObject("lastTF"))) { + lastTFInTree = p->GetVal(); + } + } + + isCompressed = (tree->GetBranch("CMVPerTFCompressed") != nullptr); + const bool isRaw = (tree->GetBranch("CMVPerTF") != nullptr); + if (!isCompressed && !isRaw) { + std::cerr << "CMVFileHandle: no recognised branch (CMVPerTFCompressed / CMVPerTF) in: " + << path << "\n"; + close(); + return false; + } + + if (isCompressed) { + tree->SetBranchAddress("CMVPerTFCompressed", &tfCompressed); + tfDecoded = new CMVPerTF(); + } else { + tree->SetBranchAddress("CMVPerTF", &tfRaw); + } + return true; +} + +const CMVPerTF* CMVFileHandle::getEntry(long long iEntry) +{ + tree->GetEntry(iEntry); + if (isCompressed) { + if (!tfCompressed) { + return nullptr; + } + tfCompressed->decompress(tfDecoded); + return tfDecoded; + } + return tfRaw; +} + +void CMVFileHandle::close() +{ + if (tree) { + tree->ResetBranchAddresses(); + tree = nullptr; + } + tfCompressed = nullptr; + tfRaw = nullptr; + delete tfDecoded; + tfDecoded = nullptr; + if (file) { + file->Close(); + delete file; + file = nullptr; + } +} + +} // namespace o2::tpc diff --git a/Detectors/TPC/calibration/src/CalculatedEdx.cxx b/Detectors/TPC/calibration/src/CalculatedEdx.cxx index 478acda1189c2..396214775eb76 100644 --- a/Detectors/TPC/calibration/src/CalculatedEdx.cxx +++ b/Detectors/TPC/calibration/src/CalculatedEdx.cxx @@ -32,8 +32,10 @@ using namespace o2::tpc; CalculatedEdx::CalculatedEdx() { - mTPCCorrMapsHelper.setOwner(true); - mTPCCorrMapsHelper.setCorrMap(TPCFastTransformHelperO2::instance()->create(0)); + gpu::aligned_unique_buffer_ptr buffer; + gpu::TPCFastTransformPOD::create(buffer, *TPCFastTransformHelperO2::instance()->create(0)); + mTPCCorrMapBuffer = std::move(buffer); + mTPCCorrMap = mTPCCorrMapBuffer.get(); } void CalculatedEdx::setMembers(std::vector* tpcTrackClIdxVecInput, const o2::tpc::ClusterNativeAccess& clIndex, std::vector* vTPCTracksArrayInp) @@ -50,7 +52,7 @@ void CalculatedEdx::setRefit(const unsigned int nHbfPerTf) mTPCRefitterOccMap.resize(sizeOcc); std::fill(mTPCRefitterOccMap.begin(), mTPCRefitterOccMap.end(), 0); o2::gpu::GPUO2InterfaceRefit::fillSharedClustersAndOccupancyMap(mClusterIndex, *mTracks, mTPCTrackClIdxVecInput->data(), mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), nHbfPerTf); - mRefit = std::make_unique(mClusterIndex, &mTPCCorrMapsHelper, mFieldNominalGPUBz, mTPCTrackClIdxVecInput->data(), nHbfPerTf, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size()); + mRefit = std::make_unique(mClusterIndex, mTPCCorrMap, mFieldNominalGPUBz, mTPCTrackClIdxVecInput->data(), nHbfPerTf, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size()); } void CalculatedEdx::fillMissingClusters(int missingClusters[4], float minChargeTot, float minChargeMax, int method, std::array, 5>& chargeTotROC, std::array, 5>& chargeMaxROC) diff --git a/Detectors/TPC/calibration/src/CalibPadGainTracks.cxx b/Detectors/TPC/calibration/src/CalibPadGainTracks.cxx index 094de6b830272..37400a28e4670 100644 --- a/Detectors/TPC/calibration/src/CalibPadGainTracks.cxx +++ b/Detectors/TPC/calibration/src/CalibPadGainTracks.cxx @@ -19,7 +19,7 @@ #include "TPCBase/ROC.h" #include "TPCBase/Mapper.h" #include "TPCCalibration/IDCDrawHelper.h" -#include "CorrectionMapsHelper.h" +#include "TPCFastTransformPOD.h" #include "TPCReconstruction/TPCFastTransformHelperO2.h" #include "GPUO2InterfaceRefit.h" #include "GPUO2ExternalUser.h" @@ -37,7 +37,7 @@ void CalibPadGainTracks::processTracks(const int nMaxTracks) { std::unique_ptr refit; if (!mPropagateTrack) { - refit = std::make_unique(mClusterIndex, mTPCCorrMapsHelper, mFieldNominalGPUBz, mTPCTrackClIdxVecInput->data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size()); + refit = std::make_unique(mClusterIndex, mTPCCorrMaps, mFieldNominalGPUBz, mTPCTrackClIdxVecInput->data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size()); } const size_t loopEnd = (nMaxTracks < 0) ? mTracks->size() : ((nMaxTracks > mTracks->size()) ? mTracks->size() : size_t(nMaxTracks)); @@ -407,7 +407,7 @@ void CalibPadGainTracks::setTPCVDrift(const o2::tpc::VDriftCorrFact& v) } //______________________________________________ -void CalibPadGainTracks::setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph) +void CalibPadGainTracks::setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph) { - mTPCCorrMapsHelper = maph; + mTPCCorrMaps = maph; } diff --git a/Detectors/TPC/calibration/src/CorrectdEdxDistortions.cxx b/Detectors/TPC/calibration/src/CorrectdEdxDistortions.cxx index 73599e744483c..fc60c422d83f4 100644 --- a/Detectors/TPC/calibration/src/CorrectdEdxDistortions.cxx +++ b/Detectors/TPC/calibration/src/CorrectdEdxDistortions.cxx @@ -87,7 +87,7 @@ float o2::tpc::CorrectdEdxDistortions::getCorrection(const float time, unsigned const float ly = mTPCGeometry.LinearPad2Y(sector, padrow, pad); // get correction at "pad + 0.5*padlength" pos1 and dont extrapolate/interpolate across GEM gaps - const int row1 = ((padrow == mTPCGeometry.EndIROC() - 1) || (padrow == mTPCGeometry.EndOROC1() - 1) || (padrow == mTPCGeometry.EndOROC2() - 1)) ? padrow : std::clamp(padrow + 1, 0, GPUCA_ROW_COUNT - 1); + const int row1 = ((padrow == mTPCGeometry.EndIROC() - 1) || (padrow == mTPCGeometry.EndOROC1() - 1) || (padrow == mTPCGeometry.EndOROC2() - 1)) ? padrow : std::clamp(padrow + 1, 0, o2::tpc::constants::MAXGLOBALPADROW - 1); float lxT_1 = 0; float lyT_1 = 0; @@ -101,7 +101,7 @@ float o2::tpc::CorrectdEdxDistortions::getCorrection(const float time, unsigned const float r_1_f = std::sqrt(lxT_1 * lxT_1 + lyT_1 * lyT_1); // get correction at "pad - 0.5*padlength" pos0 and dont extrapolate/interpolate across GEM gaps - const int row0 = ((padrow == mTPCGeometry.EndIROC()) || (padrow == mTPCGeometry.EndOROC1()) || (padrow == mTPCGeometry.EndOROC2())) ? padrow : std::clamp(padrow - 1, 0, GPUCA_ROW_COUNT - 1); + const int row0 = ((padrow == mTPCGeometry.EndIROC()) || (padrow == mTPCGeometry.EndOROC1()) || (padrow == mTPCGeometry.EndOROC2())) ? padrow : std::clamp(padrow - 1, 0, o2::tpc::constants::MAXGLOBALPADROW - 1); // check if previous pad row has enough pads const unsigned char pad0 = std::clamp(static_cast(pad), 0, mTPCGeometry.NPads(row0) - 1); diff --git a/Detectors/TPC/calibration/src/CorrectionMapsLoader.cxx b/Detectors/TPC/calibration/src/CorrectionMapsLoader.cxx index 038fe3c34e140..c8bdfa0f99350 100644 --- a/Detectors/TPC/calibration/src/CorrectionMapsLoader.cxx +++ b/Detectors/TPC/calibration/src/CorrectionMapsLoader.cxx @@ -11,50 +11,36 @@ #include "TPCCalibration/CorrectionMapsLoader.h" #include "TPCCalibration/CorrMapParam.h" -#include "TPCReconstruction/TPCFastTransformHelperO2.h" -#include "TPCBaseRecSim/CDBInterface.h" +#include "TPCBaseRecSim/CDBTypes.h" #include "Framework/Logger.h" #include "Framework/ProcessingContext.h" #include "Framework/CCDBParamSpec.h" -#include "Framework/InputRecord.h" #include "Framework/ConfigParamSpec.h" #include "Framework/ConcreteDataMatcher.h" #include "Framework/InitContext.h" #include "Framework/DeviceSpec.h" -#include "Framework/ConfigParamRegistry.h" #include "DataFormatsCTP/LumiInfo.h" -#include "TPCCalibration/TPCFastSpaceChargeCorrectionHelper.h" using namespace o2::tpc; using namespace o2::framework; -#ifndef GPUCA_GPUCODE_DEVICE - //________________________________________________________ -void CorrectionMapsLoader::updateVDrift(float vdriftCorr, float vdrifRef, float driftTimeOffset) +void CorrectionMapsLoader::extractCCDBInputs(ProcessingContext& pc, float tpcScaler) { - o2::tpc::TPCFastTransformHelperO2::instance()->updateCalibration(*mCorrMap, 0, vdriftCorr, vdrifRef, driftTimeOffset); - if (mCorrMapRef) { - o2::tpc::TPCFastTransformHelperO2::instance()->updateCalibration(*mCorrMapRef, 0, vdriftCorr, vdrifRef, driftTimeOffset); + pc.inputs().get("tpcCorrPar"); + const auto lumiMode = getLumiScaleMode(); + if (lumiMode != LumiScaleMode::NoCorrection && lumiMode != LumiScaleMode::StaticMapOnly) { + pc.inputs().get("tpcCorrMap"); } - if (mCorrMapMShape) { - o2::tpc::TPCFastTransformHelperO2::instance()->updateCalibration(*mCorrMapMShape, 0, vdriftCorr, vdrifRef, driftTimeOffset); + if (lumiMode != LumiScaleMode::NoCorrection) { + pc.inputs().get("tpcCorrMapRef"); } -} - -//________________________________________________________ -void CorrectionMapsLoader::extractCCDBInputs(ProcessingContext& pc) -{ - pc.inputs().get("tpcCorrPar"); - pc.inputs().get("tpcCorrMap"); - pc.inputs().get("tpcCorrMapRef"); const int maxDumRep = 5; int dumRep = 0; o2::ctp::LumiInfo lumiObj; static o2::ctp::LumiInfo lumiPrev; - if (getLumiScaleType() == 2 || mIDC2CTPFallbackActive) { - float tpcScaler = pc.inputs().get("tpcscaler"); + if (getLumiScaleType() == LumiScaleType::TPCScaler || mIDC2CTPFallbackActive) { // check if tpcScaler is valid and CTP fallback is allowed if (tpcScaler == -1.f) { const bool canUseCTPScaling = mCorrMap && mCorrMapRef && mCorrMap->isIDCSet() && mCorrMapRef->isIDCSet() && mCorrMap->isLumiSet() && mCorrMapRef->isLumiSet(); @@ -63,7 +49,7 @@ void CorrectionMapsLoader::extractCCDBInputs(ProcessingContext& pc) mIDC2CTPFallbackActive = true; setMeanLumi(mCorrMap->getLumi(), false); setMeanLumiRef(mCorrMapRef->getLumi()); - setLumiScaleType(1); + setLumiScaleType(LumiScaleType::CTPLumi); } else if (mCorrMap) { // CTP scaling is not possible, dont do any scaling to avoid applying wrong corrections const float storedIDC = mCorrMap->getIDC(); @@ -77,7 +63,7 @@ void CorrectionMapsLoader::extractCCDBInputs(ProcessingContext& pc) mIDC2CTPFallbackActive = false; setMeanLumi(mCorrMap->getIDC(), false); setMeanLumiRef(mCorrMapRef->getIDC()); - setLumiScaleType(2); + setLumiScaleType(LumiScaleType::TPCScaler); } // correct IDC received setInstLumi(tpcScaler); @@ -94,41 +80,32 @@ void CorrectionMapsLoader::extractCCDBInputs(ProcessingContext& pc) lumiObj = lumiPrev; } setInstLumiCTP(mInstLumiCTPFactor * (mLumiCTPSource == 0 ? lumiObj.getLumi() : lumiObj.getLumiAlt())); - if (getLumiScaleType() == 1) { + if (getLumiScaleType() == LumiScaleType::CTPLumi) { setInstLumi(getInstLumiCTP()); } } - if (getUseMShapeCorrection()) { - LOGP(info, "Setting M-Shape map"); - const auto mapMShape = pc.inputs().get("mshape"); - const_cast(mapMShape.get())->rectifyAfterReadingFromFile(); - mCorrMapMShape = std::unique_ptr(new TPCFastTransform); - mCorrMapMShape->cloneFromObject(*(mapMShape.get()), nullptr); - setCorrMapMShape(mCorrMapMShape.get()); - setUpdatedMapMShape(); - } - - // update inverse in case it is requested - if (!mScaleInverse) { - updateInverse(); - } reportScaling(); } //________________________________________________________ -void CorrectionMapsLoader::requestCCDBInputs(std::vector& inputs, std::vector& options, const CorrectionMapsLoaderGloOpts& gloOpts) +void CorrectionMapsLoader::requestCCDBInputs(std::vector& inputs, const CorrectionMapsGloOpts& gloOpts) { - if (gloOpts.lumiMode == 0) { + LOGP(info, "Requesting CCDB inputs for TPC correction maps with lumiType={} and lumiMode={}", static_cast(gloOpts.lumiType), static_cast(gloOpts.lumiMode)); + if (gloOpts.lumiMode == LumiScaleMode::Linear) { addInput(inputs, {"tpcCorrMap", "TPC", "CorrMap", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalCorrMap), {}, 1)}); // time-dependent addInput(inputs, {"tpcCorrMapRef", "TPC", "CorrMapRef", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalCorrMapRef), {}, 0)}); // load once - } else if (gloOpts.lumiMode == 1) { + } else if (gloOpts.lumiMode == LumiScaleMode::DerivativeMap) { addInput(inputs, {"tpcCorrMap", "TPC", "CorrMap", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalCorrMap), {}, 1)}); // time-dependent addInput(inputs, {"tpcCorrMapRef", "TPC", "CorrMapRef", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalCorrDerivMap), {}, 1)}); // time-dependent - } else if (gloOpts.lumiMode == 2) { + } else if (gloOpts.lumiMode == LumiScaleMode::DerivativeMapMC) { // for MC corrections addInput(inputs, {"tpcCorrMap", "TPC", "CorrMap", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalCorrMapMC), {}, 1)}); // time-dependent addInput(inputs, {"tpcCorrMapRef", "TPC", "CorrMapRef", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalCorrDerivMapMC), {}, 1)}); // time-dependent + } else if (gloOpts.lumiMode == LumiScaleMode::NoCorrection) { + // no correction maps needed — a dummy map is created at runtime + } else if (gloOpts.lumiMode == LumiScaleMode::StaticMapOnly) { + addInput(inputs, {"tpcCorrMapRef", "TPC", "CorrMapRef", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalCorrMapRef), {}, 0)}); // load once } else { LOG(fatal) << "Correction mode unknown! Choose either 0 (default) or 1 (derivative map) for flag corrmap-lumi-mode."; } @@ -137,51 +114,7 @@ void CorrectionMapsLoader::requestCCDBInputs(std::vector& inputs, std addInput(inputs, {"CTPLumi", "CTP", "LUMI", 0, Lifetime::Timeframe}); } - if (gloOpts.lumiType == 2) { - addInput(inputs, {"tpcscaler", o2::header::gDataOriginTPC, "TPCSCALER", 0, Lifetime::Timeframe}); - } - addInput(inputs, {"tpcCorrPar", "TPC", "CorrMapParam", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CorrMapParam), {}, 0)}); // load once - - if (gloOpts.enableMShapeCorrection) { - addInput(inputs, {"mshape", o2::header::gDataOriginTPC, "TPCMSHAPE", 0, Lifetime::Timeframe}); - } - addOptions(options); -} - -//________________________________________________________ -void CorrectionMapsLoader::addOptions(std::vector& options) -{ - // these are options which should be added at the level of device using TPC corrections - // At the moment - nothing, all options are moved to configurable param CorrMapParam - addOption(options, ConfigParamSpec{"recalculate-inverse-correction", o2::framework::VariantType::Bool, false, {"recalculate the inverse correction in case lumi mode 1 or 2 is used"}}); - addOption(options, ConfigParamSpec{"nthreads-inverse-correction", o2::framework::VariantType::Int, 4, {"Number of threads used for calculating the inverse correction (-1=all threads)"}}); -} - -//________________________________________________________ -void CorrectionMapsLoader::addGlobalOptions(std::vector& options) -{ - // these are options which should be added at the workflow level, since they modify the inputs of the devices - addOption(options, ConfigParamSpec{"lumi-type", o2::framework::VariantType::Int, 0, {"1 = use CTP lumi for TPC correction scaling, 2 = use TPC scalers for TPC correction scaling"}}); - addOption(options, ConfigParamSpec{"corrmap-lumi-mode", o2::framework::VariantType::Int, 0, {"scaling mode: (default) 0 = static + scale * full; 1 = full + scale * derivative; 2 = full + scale * derivative (for MC)"}}); - addOption(options, ConfigParamSpec{"enable-M-shape-correction", o2::framework::VariantType::Bool, false, {"Enable M-shape distortion correction"}}); - addOption(options, ConfigParamSpec{"disable-ctp-lumi-request", o2::framework::VariantType::Bool, false, {"do not request CTP lumi (regardless what is used for corrections)"}}); - addOption(options, ConfigParamSpec{"disable-lumi-type-consistency-check", o2::framework::VariantType::Bool, false, {"disable check of selected CTP or IDC scaling source being consistent with the map"}}); -} - -//________________________________________________________ -CorrectionMapsLoaderGloOpts CorrectionMapsLoader::parseGlobalOptions(const o2::framework::ConfigParamRegistry& opts) -{ - CorrectionMapsLoaderGloOpts tpcopt; - tpcopt.lumiType = opts.get("lumi-type"); - tpcopt.lumiMode = opts.get("corrmap-lumi-mode"); - tpcopt.enableMShapeCorrection = opts.get("enable-M-shape-correction"); - tpcopt.requestCTPLumi = !opts.get("disable-ctp-lumi-request"); - tpcopt.checkCTPIDCconsistency = !opts.get("disable-lumi-type-consistency-check"); - if (!tpcopt.requestCTPLumi && tpcopt.lumiType == 1) { - LOGP(fatal, "Scaling with CTP Lumi is requested but this input is disabled"); - } - return tpcopt; } //________________________________________________________ @@ -208,18 +141,18 @@ bool CorrectionMapsLoader::accountCCDBInputs(const ConcreteDataMatcher& matcher, mCorrMap->rectifyAfterReadingFromFile(); mCorrMap->setCTP2IDCFallBackThreshold(o2::tpc::CorrMapParam::Instance().CTP2IDCFallBackThreshold); if (getMeanLumiOverride() != 0) { - if (getLumiScaleType() == 1) { + if (getLumiScaleType() == LumiScaleType::CTPLumi) { mCorrMap->setLumi(getMeanLumiOverride()); LOGP(info, "CorrMap mean lumi rate is overridden to {}", mCorrMap->getLumi()); - } else if (getLumiScaleType() == 2) { + } else if (getLumiScaleType() == LumiScaleType::TPCScaler) { mCorrMap->setIDC(getMeanLumiOverride()); LOGP(info, "CorrMap mean IDC rate is overridden to {}", mCorrMap->getIDC()); } } float mapMeanRate = 0; - if (getLumiScaleType() == 1) { + if (getLumiScaleType() == LumiScaleType::CTPLumi) { mapMeanRate = mCorrMap->getLumi(); - } else if (getLumiScaleType() == 2) { + } else if (getLumiScaleType() == LumiScaleType::TPCScaler) { mapMeanRate = mCorrMap->getIDC(); } if (mCheckCTPIDCConsistency) { @@ -237,18 +170,18 @@ bool CorrectionMapsLoader::accountCCDBInputs(const ConcreteDataMatcher& matcher, mCorrMapRef->rectifyAfterReadingFromFile(); mCorrMapRef->setCTP2IDCFallBackThreshold(o2::tpc::CorrMapParam::Instance().CTP2IDCFallBackThreshold); if (getMeanLumiRefOverride() != 0) { - if (getLumiScaleType() == 1) { + if (getLumiScaleType() == LumiScaleType::CTPLumi) { mCorrMapRef->setLumi(getMeanLumiRefOverride()); LOGP(info, "CorrMapRef mean lumi rate is overridden to {}", mCorrMapRef->getLumi()); - } else if (getLumiScaleType() == 2) { + } else if (getLumiScaleType() == LumiScaleType::TPCScaler) { mCorrMapRef->setIDC(getMeanLumiRefOverride()); LOGP(info, "CorrMapRef mean IDC rate is overridden to {}", mCorrMapRef->getIDC()); } } float mapRefMeanRate = 0; - if (getLumiScaleType() == 1) { + if (getLumiScaleType() == LumiScaleType::CTPLumi) { mapRefMeanRate = mCorrMapRef->getLumi(); - } else if (getLumiScaleType() == 2) { + } else if (getLumiScaleType() == LumiScaleType::TPCScaler) { mapRefMeanRate = mCorrMapRef->getIDC(); } if (mCheckCTPIDCConsistency) { @@ -277,12 +210,12 @@ bool CorrectionMapsLoader::accountCCDBInputs(const ConcreteDataMatcher& matcher, } if (mInstCTPLumiOverride != 0.) { setInstLumiCTP(mInstCTPLumiOverride * mInstLumiCTPFactor); - if (getLumiScaleType() == 1) { + if (getLumiScaleType() == LumiScaleType::CTPLumi) { setInstLumi(getInstLumiCTP(), false); } } setUpdatedLumi(); - int scaleType = getLumiScaleType(); + int scaleType = static_cast(getLumiScaleType()); const std::array lumiS{"OFF", "CTP", "TPC scaler"}; if (scaleType >= lumiS.size()) { LOGP(fatal, "Wrong corrmap-lumi-mode provided!"); @@ -290,92 +223,39 @@ bool CorrectionMapsLoader::accountCCDBInputs(const ConcreteDataMatcher& matcher, LOGP(info, "TPC correction map params updated: SP corrections: {} (corr.map scaling type={}, override values: lumiMean={} lumiRefMean={} lumiScaleMode={}), CTP Lumi: source={} lumiInstOverride={} , LumiInst scale={} ", canUseCorrections() ? "ON" : "OFF", - lumiS[scaleType], mMeanLumiOverride, mMeanLumiRefOverride, mLumiScaleMode, mLumiCTPSource, mInstCTPLumiOverride, mInstLumiCTPFactor); + lumiS[scaleType], mMeanLumiOverride, mMeanLumiRefOverride, static_cast(getLumiScaleMode()), mLumiCTPSource, mInstCTPLumiOverride, mInstLumiCTPFactor); } return false; } //________________________________________________________ -void CorrectionMapsLoader::init(o2::framework::InitContext& ic) +void CorrectionMapsLoader::init(o2::framework::InitContext& ic, bool idcsAvailable) { - if (getLumiScaleMode() < 0) { + if (getLumiScaleMode() == LumiScaleMode::Unset) { LOGP(fatal, "TPC correction lumi scaling mode is not set"); } const auto& inputRouts = ic.services().get().inputs; - bool foundCTP = false, foundTPCScl = false, foundMShape = false; + bool foundCTP = false; for (const auto& route : inputRouts) { if (route.matcher == InputSpec{"CTPLumi", "CTP", "LUMI", 0, Lifetime::Timeframe}) { foundCTP = true; - } else if (route.matcher == InputSpec{"tpcscaler", o2::header::gDataOriginTPC, "TPCSCALER", 0, Lifetime::Timeframe}) { - foundTPCScl = true; - } else if (route.matcher == InputSpec{"mshape", o2::header::gDataOriginTPC, "TPCMSHAPE", 0, Lifetime::Timeframe}) { - foundMShape = true; } } setLumiCTPAvailable(foundCTP); - enableMShapeCorrection(foundMShape); - if ((getLumiScaleType() == 1 && !foundCTP) || (getLumiScaleType() == 2 && !foundTPCScl)) { - LOGP(fatal, "Lumi scaling source {}({}) is not available for TPC correction", getLumiScaleType(), getLumiScaleType() == 1 ? "CTP" : "TPCScaler"); - } - - if ((getLumiScaleMode() == 1) || (getLumiScaleMode() == 2)) { - mScaleInverse = !(ic.options().get("recalculate-inverse-correction")); - } else { - mScaleInverse = true; - } - const int nthreadsInv = (ic.options().get("nthreads-inverse-correction")); - (nthreadsInv < 0) ? TPCFastSpaceChargeCorrectionHelper::instance()->setNthreadsToMaximum() : TPCFastSpaceChargeCorrectionHelper::instance()->setNthreads(nthreadsInv); -} - -//________________________________________________________ -void CorrectionMapsLoader::copySettings(const CorrectionMapsLoader& src) -{ - setInstLumi(src.getInstLumi(), false); - setInstLumiCTP(src.getInstLumiCTP()); - setMeanLumi(src.getMeanLumi(), false); - setLumiCTPAvailable(src.getLumiCTPAvailable()); - setMeanLumiRef(src.getMeanLumiRef()); - setLumiScaleType(src.getLumiScaleType()); - setMeanLumiOverride(src.getMeanLumiOverride()); - setMeanLumiRefOverride(src.getMeanLumiRefOverride()); - setInstCTPLumiOverride(src.getInstCTPLumiOverride()); - setLumiScaleMode(src.getLumiScaleMode()); - enableMShapeCorrection(src.getUseMShapeCorrection()); - mInstLumiCTPFactor = src.mInstLumiCTPFactor; - mLumiCTPSource = src.mLumiCTPSource; - mLumiScaleMode = src.mLumiScaleMode; - mScaleInverse = src.getScaleInverse(); - mIDC2CTPFallbackActive = src.mIDC2CTPFallbackActive; -} - -void CorrectionMapsLoader::updateInverse() -{ - if (mLumiScaleMode == 1 || mLumiScaleMode == 2) { - LOGP(info, "Recalculating the inverse correction"); - setUpdatedMap(); - std::vector scaling{1, mLumiScale}; - std::vector corr{&(mCorrMap->getCorrection()), &(mCorrMapRef->getCorrection())}; - if (mCorrMapMShape) { - scaling.emplace_back(1); - corr.emplace_back(&(mCorrMapMShape->getCorrection())); - } - TPCFastSpaceChargeCorrectionHelper::instance()->initInverse(corr, scaling, false); - } else { - LOGP(info, "Reinitializing inverse correction with lumi scale mode {} not supported for now", mLumiScaleMode); + if ((getLumiScaleType() == LumiScaleType::CTPLumi && !foundCTP) || (getLumiScaleType() == LumiScaleType::TPCScaler && !idcsAvailable)) { + LOGP(fatal, "Lumi scaling source {}({}) is not available for TPC correction", static_cast(getLumiScaleType()), getLumiScaleType() == LumiScaleType::CTPLumi ? "CTP" : "TPCScaler"); } } void CorrectionMapsLoader::checkMeanScaleConsistency(float meanLumi, float threshold) const { - if (getLumiScaleType() == 1) { + if (getLumiScaleType() == LumiScaleType::CTPLumi) { if (meanLumi < threshold) { LOGP(fatal, "CTP Lumi scaling source is requested, but the map mean scale {} is below the threshold {}", meanLumi, threshold); } - } else if (getLumiScaleType() == 2) { + } else if (getLumiScaleType() == LumiScaleType::TPCScaler) { if (meanLumi > threshold) { LOGP(fatal, "IDC scaling source is requested, but the map mean scale {} is above the threshold {}", meanLumi, threshold); } } } - -#endif // #ifndef GPUCA_GPUCODE_DEVICE diff --git a/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx b/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx new file mode 100644 index 0000000000000..5518d680420ca --- /dev/null +++ b/Detectors/TPC/calibration/src/CorrectionMapsOptions.cxx @@ -0,0 +1,59 @@ +// 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. + +#include "TPCCalibration/CorrectionMapsOptions.h" +#include "Framework/Logger.h" +#include "Framework/ConfigParamSpec.h" +#include "Framework/ConfigParamRegistry.h" +using namespace o2::tpc; +using namespace o2::framework; + +//________________________________________________________ +CorrectionMapsGloOpts CorrectionMapsOptions::parseGlobalOptions(const o2::framework::ConfigParamRegistry& opts) +{ + CorrectionMapsGloOpts tpcopt; + auto lumiTypeVal = opts.get("lumi-type"); + if (lumiTypeVal < static_cast(LumiScaleType::Unset) || lumiTypeVal >= static_cast(LumiScaleType::Count)) { + LOGP(fatal, "Invalid lumi-type value: {}", lumiTypeVal); + } + tpcopt.lumiType = static_cast(lumiTypeVal); + + auto lumiModeVal = opts.get("corrmap-lumi-mode"); + if (lumiModeVal < static_cast(LumiScaleMode::Unset) || lumiModeVal >= static_cast(LumiScaleMode::Count)) { + LOGP(fatal, "Invalid corrmap-lumi-mode value: {}", lumiModeVal); + } + tpcopt.lumiMode = static_cast(lumiModeVal); + + tpcopt.enableMShapeCorrection = opts.get("enable-M-shape-correction"); + tpcopt.requestCTPLumi = !opts.get("disable-ctp-lumi-request"); + tpcopt.checkCTPIDCconsistency = !opts.get("disable-lumi-type-consistency-check"); + if (!tpcopt.requestCTPLumi && tpcopt.lumiType == LumiScaleType::CTPLumi) { + LOGP(fatal, "Scaling with CTP Lumi is requested but this input is disabled"); + } + return tpcopt; +} + +void CorrectionMapsOptions::addGlobalOptions(std::vector& options) +{ + // these are options which should be added at the workflow level, since they modify the inputs of the devices + addOption(options, ConfigParamSpec{"lumi-type", o2::framework::VariantType::Int, 0, {"1 = use CTP lumi for TPC correction scaling, 2 = use TPC scalers for TPC correction scaling"}}); + addOption(options, ConfigParamSpec{"corrmap-lumi-mode", o2::framework::VariantType::Int, 0, {"scaling mode: (default) 0 = static + scale * full; 1 = full + scale * derivative; 2 = full + scale * derivative (for MC); 3 = no correction; 4 = static only"}}); + addOption(options, ConfigParamSpec{"enable-M-shape-correction", o2::framework::VariantType::Bool, false, {"Enable M-shape distortion correction"}}); + addOption(options, ConfigParamSpec{"disable-ctp-lumi-request", o2::framework::VariantType::Bool, false, {"do not request CTP lumi (regardless what is used for corrections)"}}); + addOption(options, ConfigParamSpec{"disable-lumi-type-consistency-check", o2::framework::VariantType::Bool, false, {"disable check of selected CTP or IDC scaling source being consistent with the map"}}); +} + +void CorrectionMapsOptions::addOption(std::vector& options, ConfigParamSpec&& osp) +{ + if (std::find(options.begin(), options.end(), osp) == options.end()) { + options.emplace_back(osp); + } +} diff --git a/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h b/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h index 6e15e2dd0427a..847ae5ad7d788 100644 --- a/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h +++ b/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h @@ -123,4 +123,9 @@ #pragma link C++ class o2::tpc::DigitAdd + ; #pragma link C++ class std::vector < o2::tpc::DigitAdd> + ; #pragma link C++ class o2::tpc::PressureTemperatureHelper + ; + +#pragma link C++ struct o2::tpc::CMVFileHandle + ; +#pragma link C++ class o2::tpc::CMVPerTF + ; +#pragma link C++ class o2::tpc::CMVPerTFCompressed + ; + #endif diff --git a/Detectors/TPC/calibration/src/TPCFastSpaceChargeCorrectionHelper.cxx b/Detectors/TPC/calibration/src/TPCFastSpaceChargeCorrectionHelper.cxx index e2960c73e4d50..5a26dabaa2db5 100644 --- a/Detectors/TPC/calibration/src/TPCFastSpaceChargeCorrectionHelper.cxx +++ b/Detectors/TPC/calibration/src/TPCFastSpaceChargeCorrectionHelper.cxx @@ -29,6 +29,11 @@ #include #include #include "TStopwatch.h" +#include "TTreeReader.h" +#include "TTreeReaderValue.h" +#include "ROOT/TTreeProcessorMT.hxx" +#include +#include using namespace o2::gpu; @@ -60,12 +65,8 @@ void TPCFastSpaceChargeCorrectionHelper::initGeometry() mGeo.startConstruction(nRows); auto& detParam = ParameterDetector::Instance(); - float tpcZlengthSideA = detParam.TPClength; - float tpcZlengthSideC = detParam.TPClength; - mGeo.setTPCzLength(tpcZlengthSideA, tpcZlengthSideC); - - mGeo.setTPCalignmentZ(0.); + mGeo.setTPCzLength(detParam.TPClength); for (int iRow = 0; iRow < mGeo.getNumberOfRows(); iRow++) { Sector sector = 0; @@ -113,11 +114,13 @@ void TPCFastSpaceChargeCorrectionHelper::setNthreadsToMaximum() } } -void TPCFastSpaceChargeCorrectionHelper::fillSpaceChargeCorrectionFromMap(TPCFastSpaceChargeCorrection& correction) +void TPCFastSpaceChargeCorrectionHelper::fillSpaceChargeCorrectionFromMap(TPCFastSpaceChargeCorrection& correction, bool processingInverseCorrection) { // calculate correction map: dx,du,dv = ( origTransform() -> x,u,v) - fastTransformNominal:x,u,v // for the future: switch TOF correction off for a while + TStopwatch watch; + if (!mIsInitialized) { initGeometry(); } @@ -129,36 +132,61 @@ void TPCFastSpaceChargeCorrectionHelper::fillSpaceChargeCorrectionFromMap(TPCFas LOG(info) << "fast space charge correction helper: init from data points"; - for (int slice = 0; slice < correction.getGeometry().getNumberOfSlices(); slice++) { + for (int sector = 0; sector < correction.getGeometry().getNumberOfSectors(); sector++) { auto myThread = [&](int iThread) { for (int row = iThread; row < correction.getGeometry().getNumberOfRows(); row += mNthreads) { - TPCFastSpaceChargeCorrection::SplineType& spline = correction.getSpline(slice, row); + TPCFastSpaceChargeCorrection::SplineType& spline = correction.getSplineForRow(row); Spline2DHelper helper; - float* splineParameters = correction.getSplineData(slice, row); - const std::vector& data = mCorrectionMap.getPoints(slice, row); + std::vector splineParameters; + splineParameters.resize(spline.getNumberOfParameters()); + + const std::vector& data = mCorrectionMap.getPoints(sector, row); int nDataPoints = data.size(); + if (nDataPoints >= 4) { - std::vector pointSU(nDataPoints); - std::vector pointSV(nDataPoints); + std::vector pointGU(nDataPoints); + std::vector pointGV(nDataPoints); + std::vector pointWeight(nDataPoints); std::vector pointCorr(3 * nDataPoints); // 3 dimensions for (int i = 0; i < nDataPoints; ++i) { - double su, sv, dx, du, dv; - getSpaceChargeCorrection(correction, slice, row, data[i], su, sv, dx, du, dv); - pointSU[i] = su; - pointSV[i] = sv; - pointCorr[3 * i + 0] = dx; - pointCorr[3 * i + 1] = du; - pointCorr[3 * i + 2] = dv; + o2::gpu::TPCFastSpaceChargeCorrectionMap::CorrectionPoint p = data[i]; + // not corrected grid coordinates + float gu, gv, scale; + correction.convLocalToGrid(sector, row, p.mY, p.mZ, gu, gv, scale); + if (scale - 1.f > 1.e-6) { // point is outside the grid + continue; + } + pointGU[i] = gu; + pointGV[i] = gv; + pointWeight[i] = p.mWeight; + pointCorr[3 * i + 0] = p.mDx; + pointCorr[3 * i + 1] = p.mDy; + pointCorr[3 * i + 2] = p.mDz; } - helper.approximateDataPoints(spline, splineParameters, 0., spline.getGridX1().getNumberOfKnots() - 1, 0., spline.getGridX2().getNumberOfKnots() - 1, &pointSU[0], - &pointSV[0], &pointCorr[0], nDataPoints); + helper.approximateDataPoints(spline, splineParameters.data(), 0., spline.getGridX1().getUmax(), 0., spline.getGridX2().getUmax(), pointGU.data(), + pointGV.data(), pointCorr.data(), pointWeight.data(), nDataPoints); } else { for (int i = 0; i < spline.getNumberOfParameters(); i++) { splineParameters[i] = 0.f; } } + + if (processingInverseCorrection) { + float* splineX = correction.getCorrectionDataInvX(sector, row); + float* splineYZ = correction.getCorrectionDataInvYZ(sector, row); + for (int i = 0; i < spline.getNumberOfParameters() / 3; i++) { + splineX[i] = splineParameters[3 * i + 0]; + splineYZ[2 * i + 0] = splineParameters[3 * i + 1]; + splineYZ[2 * i + 1] = splineParameters[3 * i + 2]; + } + } else { + float* splineXYZ = correction.getCorrectionData(sector, row); + for (int i = 0; i < spline.getNumberOfParameters(); i++) { + splineXYZ[i] = splineParameters[i]; + } + } } // row }; // thread @@ -174,53 +202,30 @@ void TPCFastSpaceChargeCorrectionHelper::fillSpaceChargeCorrectionFromMap(TPCFas th.join(); } - } // slice + } // sector - initInverse(correction, 0); -} + watch.Stop(); -void TPCFastSpaceChargeCorrectionHelper::getSpaceChargeCorrection(const TPCFastSpaceChargeCorrection& correction, int slice, int row, o2::gpu::TPCFastSpaceChargeCorrectionMap::CorrectionPoint p, - double& su, double& sv, double& dx, double& du, double& dv) -{ - // get space charge correction in internal TPCFastTransform coordinates su,sv->dx,du,dv - - if (!mIsInitialized) { - initGeometry(); - } - - // not corrected coordinates in u,v - float u = 0.f, v = 0.f, fsu = 0.f, fsv = 0.f; - mGeo.convLocalToUV(slice, p.mY, p.mZ, u, v); - correction.convUVtoGrid(slice, row, u, v, fsu, fsv); - // mGeo.convUVtoScaledUV(slice, row, u, v, fsu, fsv); - su = fsu; - sv = fsv; - // corrected coordinates in u,v - float u1 = 0.f, v1 = 0.f; - mGeo.convLocalToUV(slice, p.mY + p.mDy, p.mZ + p.mDz, u1, v1); - - dx = p.mDx; - du = u1 - u; - dv = v1 - v; -} + LOGP(info, "Space charge correction tooks: {}s", watch.RealTime()); +} // fillSpaceChargeCorrectionFromMap std::unique_ptr TPCFastSpaceChargeCorrectionHelper::createFromGlobalCorrection( - std::function correctionGlobal, const int nKnotsY, const int nKnotsZ) { /// creates TPCFastSpaceChargeCorrection object from a continious space charge correction in global coordinates - auto correctionLocal = [&](int roc, int irow, double ly, double lz, + auto correctionLocal = [&](int sector, int irow, double ly, double lz, double& dlx, double& dly, double& dlz) { double lx = mGeo.getRowInfo(irow).x; float gx, gy, gz; - mGeo.convLocalToGlobal(roc, lx, ly, lz, gx, gy, gz); + mGeo.convLocalToGlobal(sector, lx, ly, lz, gx, gy, gz); double dgx, dgy, dgz; - correctionGlobal(roc, gx, gy, gz, dgx, dgy, dgz); + correctionGlobal(sector, gx, gy, gz, dgx, dgy, dgz); float lx1, ly1, lz1; - mGeo.convGlobalToLocal(roc, gx + dgx, gy + dgy, gz + dgz, lx1, ly1, lz1); + mGeo.convGlobalToLocal(sector, gx + dgx, gy + dgy, gz + dgz, lx1, ly1, lz1); dlx = lx1 - lx; dly = ly1 - ly; dlz = lz1 - lz; @@ -229,7 +234,7 @@ std::unique_ptr TPCFastSpaceChargeCorrectionHelper } std::unique_ptr TPCFastSpaceChargeCorrectionHelper::createFromLocalCorrection( - std::function correctionLocal, + std::function correctionLocal, const int nKnotsY, const int nKnotsZ) { /// creates TPCFastSpaceChargeCorrection object from a continious space charge correction in local coordinates @@ -274,28 +279,25 @@ std::unique_ptr TPCFastSpaceChargeCorrectionHelper /// set space charge correction in the local coordinates /// as a continious function - int nRocs = mGeo.getNumberOfSlices(); + int nSectors = mGeo.getNumberOfSectors(); int nRows = mGeo.getNumberOfRows(); - mCorrectionMap.init(nRocs, nRows); + mCorrectionMap.init(nSectors, nRows); - for (int iRoc = 0; iRoc < nRocs; iRoc++) { + for (int iSector = 0; iSector < nSectors; iSector++) { auto myThread = [&](int iThread) { for (int iRow = iThread; iRow < nRows; iRow += mNthreads) { const auto& info = mGeo.getRowInfo(iRow); - double vMax = mGeo.getTPCzLength(iRoc); - double dv = vMax / (6. * (nKnotsZ - 1)); - + double dl = mGeo.getTPCzLength() / (6. * (nKnotsZ - 1)); double dpad = info.maxPad / (6. * (nKnotsY - 1)); for (double pad = 0; pad < info.maxPad + .5 * dpad; pad += dpad) { - float u = mGeo.convPadToU(iRow, pad); - for (double v = 0.; v < vMax + .5 * dv; v += dv) { - float ly, lz; - mGeo.convUVtoLocal(iRoc, u, v, ly, lz); + for (double l = 0.; l < mGeo.getTPCzLength() + .5 * dl; l += dl) { + float y, z; + mGeo.convPadDriftLengthToLocal(iSector, iRow, pad, l, y, z); double dx, dy, dz; - correctionLocal(iRoc, iRow, ly, lz, dx, dy, dz); - mCorrectionMap.addCorrectionPoint(iRoc, iRow, - ly, lz, dx, dy, dz); + correctionLocal(iSector, iRow, y, z, dx, dy, dz); + mCorrectionMap.addCorrectionPoint(iSector, iRow, + y, z, dx, dy, dz, 1.); } } } // row @@ -313,20 +315,21 @@ std::unique_ptr TPCFastSpaceChargeCorrectionHelper th.join(); } - } // roc + } // sector - fillSpaceChargeCorrectionFromMap(correction); + fillSpaceChargeCorrectionFromMap(correction, false); + initInverse(correction, false); } return std::move(correctionPtr); -} +} // createFromLocalCorrection void TPCFastSpaceChargeCorrectionHelper::testGeometry(const TPCFastTransformGeo& geo) const { const Mapper& mapper = Mapper::instance(); - if (geo.getNumberOfSlices() != Sector::MAXSECTOR) { - LOG(fatal) << "Wrong number of sectors :" << geo.getNumberOfSlices() << " instead of " << Sector::MAXSECTOR << std::endl; + if (geo.getNumberOfSectors() != Sector::MAXSECTOR) { + LOG(fatal) << "Wrong number of sectors :" << geo.getNumberOfSectors() << " instead of " << Sector::MAXSECTOR << std::endl; } if (geo.getNumberOfRows() != mapper.getNumberOfRows()) { @@ -350,15 +353,15 @@ void TPCFastSpaceChargeCorrectionHelper::testGeometry(const TPCFastTransformGeo& for (int pad = 0; pad < nPads; pad++) { const GlobalPadNumber p = mapper.globalPadNumber(PadPos(row, pad)); const PadCentre& c = mapper.padCentre(p); - double u = geo.convPadToU(row, pad); - + float y, z; + geo.convPadDriftLengthToLocal(0, row, pad, 0., y, z); const double dx = x - c.X(); - const double dy = u - (-c.Y()); // diferent sign convention for Y coordinate in the map + const double dy = y - (-c.Y()); // diferent sign convention for Y coordinate in the map if (fabs(dx) >= 1.e-6 || fabs(dy) >= 1.e-5) { LOG(warning) << "wrong calculated pad position:" << " row " << row << " pad " << pad << " x calc " << x << " x in map " << c.X() << " dx " << (x - c.X()) - << " y calc " << u << " y in map " << -c.Y() << " dy " << dy << std::endl; + << " y calc " << y << " y in map " << -c.Y() << " dy " << dy << std::endl; } if (fabs(maxDx) < fabs(dx)) { maxDx = dx; @@ -376,50 +379,98 @@ void TPCFastSpaceChargeCorrectionHelper::testGeometry(const TPCFastTransformGeo& } std::unique_ptr TPCFastSpaceChargeCorrectionHelper::createFromTrackResiduals( - const o2::tpc::TrackResiduals& trackResiduals, TTree* voxResTree, bool useSmoothed, bool invertSigns) + const o2::tpc::TrackResiduals& trackResiduals, TTree* voxResTree, TTree* voxResTreeInverse, bool useSmoothed, bool invertSigns, + TPCFastSpaceChargeCorrectionMap* fitPointsDirect, + TPCFastSpaceChargeCorrectionMap* fitPointsInverse) { // create o2::gpu::TPCFastSpaceChargeCorrection from o2::tpc::TrackResiduals::VoxRes voxel tree - LOG(info) << "fast space charge correction helper: create correction using " << mNthreads << " threads"; + LOG(info) << "fast space charge correction helper: create correction from track residuals using " << mNthreads << " threads"; + + TStopwatch watch, watch1; std::unique_ptr correctionPtr(new o2::gpu::TPCFastSpaceChargeCorrection); o2::gpu::TPCFastSpaceChargeCorrection& correction = *correctionPtr; - // o2::tpc::TrackResiduals::VoxRes* v = nullptr; - // voxResTree->SetBranchAddress("voxRes", &v); - - o2::tpc::TrackResiduals::VoxRes* v = nullptr; - TBranch* branch = voxResTree->GetBranch("voxRes"); - branch->SetAddress(&v); - branch->SetAutoDelete(kTRUE); - auto* helper = o2::tpc::TPCFastSpaceChargeCorrectionHelper::instance(); const o2::gpu::TPCFastTransformGeo& geo = helper->getGeometry(); - o2::gpu::TPCFastSpaceChargeCorrectionMap& map = helper->getCorrectionMap(); - map.init(geo.getNumberOfSlices(), geo.getNumberOfRows()); - int nY2Xbins = trackResiduals.getNY2XBins(); int nZ2Xbins = trackResiduals.getNZ2XBins(); - int nKnotsY = nY2Xbins / 2; - int nKnotsZ = nZ2Xbins / 2; + std::vector knotsDouble[2]; + + knotsDouble[0].reserve(nY2Xbins); + knotsDouble[1].reserve(nZ2Xbins); + + // to get enouth measurements, make a spline knot at every second bin. Boundary bins are always included. + + for (int i = 0, j = nY2Xbins - 1; i <= j; i += 2, j -= 2) { + knotsDouble[0].push_back(trackResiduals.getY2X(0, i)); + if (j >= i + 1) { + knotsDouble[0].push_back(trackResiduals.getY2X(0, j)); + } + } - if (nKnotsY < 2) { - nKnotsY = 2; + for (int i = 0, j = nZ2Xbins - 1; i <= j; i += 2, j -= 2) { + knotsDouble[1].push_back(trackResiduals.getZ2X(i)); + if (j >= i + 1) { + knotsDouble[1].push_back(trackResiduals.getZ2X(j)); + } } - if (nKnotsZ < 2) { - nKnotsZ = 2; + std::vector knotsInt[2]; + + for (int dim = 0; dim < 2; dim++) { + auto& knotsD = knotsDouble[dim]; + std::sort(knotsD.begin(), knotsD.end()); + + double pitch = knotsD[1] - knotsD[0]; // min distance between the knots + for (int i = 2; i < knotsD.size(); i++) { + double d = knotsD[i] - knotsD[i - 1]; + if (d < pitch) { + pitch = d; + } + } + // spline knots must be positioned on the grid with an integer internal coordinate + // we set the knot positioning accuracy to 0.1*pitch + pitch = 0.1 * pitch; + auto& knotsI = knotsInt[dim]; + knotsI.reserve(knotsD.size()); + double u0 = knotsD[0]; + double u1 = knotsD[knotsD.size() - 1]; + for (auto& u : knotsD) { + u -= u0; + int iu = int(u / pitch + 0.5); + knotsI.push_back(iu); + // debug printout: corrected vs original knot positions, scaled to [-1,1] interval + double uorig = u / (u1 - u0) * 2 - 1.; + u = (iu * pitch) / (u1 - u0) * 2 - 1.; + LOG(info) << "TPC SC splines: convert " << (dim == 0 ? "y" : (dim == 1 ? "z" : "-z")) << " bin to the knot: " << uorig << " -> " << u << " -> " << iu; + } + + if (knotsI.size() < 2) { // minimum 2 knots + knotsI.clear(); + knotsI.push_back(0); + knotsI.push_back(1); + } } + auto& yKnotsInt = knotsInt[0]; + auto& zKnotsInt = knotsInt[1]; + + int nKnotsY = yKnotsInt.size(); + int nKnotsZ = zKnotsInt.size(); + // std::cout << "n knots Y: " << nKnotsY << std::endl; - // std::cout << "n knots Z: " << nKnotsZ << std::endl; + // std::cout << "n knots Z: " << nKnotsZA << ", " << nKnotsZC << std::endl; + + const int nRows = geo.getNumberOfRows(); + const int nSectors = geo.getNumberOfSectors(); { // create the correction object - const int nRows = geo.getNumberOfRows(); const int nCorrectionScenarios = 1; correction.startConstruction(geo, nCorrectionScenarios); @@ -428,229 +479,403 @@ std::unique_ptr TPCFastSpaceChargeCorrect for (int row = 0; row < geo.getNumberOfRows(); row++) { correction.setRowScenarioID(row, 0); } + { // init spline scenario TPCFastSpaceChargeCorrection::SplineType spline; - spline.recreate(nKnotsY, nKnotsZ); + spline.recreate(nKnotsY, &yKnotsInt[0], nKnotsZ, &zKnotsInt[0]); correction.setSplineScenario(0, spline); } correction.finishConstruction(); } // .. create the correction object - // set the grid borders in Z to Z/X==1 - for (int iRoc = 0; iRoc < geo.getNumberOfSlices(); iRoc++) { - for (int iRow = 0; iRow < geo.getNumberOfRows(); iRow++) { - auto rowInfo = geo.getRowInfo(iRow); - o2::gpu::TPCFastSpaceChargeCorrection::SliceRowInfo& info = correction.getSliceRowInfo(iRoc, iRow); - double len = geo.getTPCzLength(iRoc); - info.gridV0 = len - rowInfo.x; - if (info.gridV0 < 0.) { - info.gridV0 = 0.; - } - } + // set the grid borders + for (int iRow = 0; iRow < geo.getNumberOfRows(); iRow++) { + auto& info = correction.getRowInfo(iRow); + const auto& spline = correction.getSplineForRow(iRow); + double rowX = geo.getRowInfo(iRow).x; + double yMin = rowX * trackResiduals.getY2X(iRow, 0); + double yMax = rowX * trackResiduals.getY2X(iRow, trackResiduals.getNY2XBins() - 1); + double zMin = rowX * trackResiduals.getZ2X(0); + double zMax = rowX * trackResiduals.getZ2X(trackResiduals.getNZ2XBins() - 1); + double zOut = zMax; + info.gridMeasured.set(yMin, spline.getGridX1().getUmax() / (yMax - yMin), // y + zMin, spline.getGridX2().getUmax() / (zMax - zMin), // z + zOut, geo.getTPCzLength()); // correction scaling region + + info.gridReal = info.gridMeasured; + + // std::cout << " iSector " << iSector << " iRow " << iRow << " uMin: " << uMin << " uMax: " << uMax << " vMin: " << vMin << " vMax: " << vMax + //<< " grid scale u "<< info.scaleUtoGrid << " grid scale v "<< info.scaleVtoGrid<< std::endl; } - LOG(info) << "fast space charge correction helper: fill data points from track residuals"; + LOG(info) << "fast space charge correction helper: preparation took " << watch1.RealTime() << "s"; - for (int iVox = 0; iVox < voxResTree->GetEntriesFast(); iVox++) { + for (int processingInverseCorrection = 0; processingInverseCorrection <= 1; processingInverseCorrection++) { - voxResTree->GetEntry(iVox); - auto xBin = - v->bvox[o2::tpc::TrackResiduals::VoxX]; // bin number in x (= pad row) - auto y2xBin = - v->bvox[o2::tpc::TrackResiduals::VoxF]; // bin number in y/x 0..14 - auto z2xBin = - v->bvox[o2::tpc::TrackResiduals::VoxZ]; // bin number in z/x 0..4 + TTree* currentTree = (processingInverseCorrection) ? voxResTreeInverse : voxResTree; - int iRoc = (int)v->bsec; - int iRow = (int)xBin; + if (!currentTree) { + continue; + } + const char* directionName = (processingInverseCorrection) ? "inverse" : "direct"; + LOG(info) << "\n fast space charge correction helper: Process " << directionName + << " correction: fill data points from track residuals.. "; - // x,y,z of the voxel in local TPC coordinates + TStopwatch watch3; + o2::gpu::TPCFastSpaceChargeCorrectionMap& map = helper->getCorrectionMap(); + map.init(geo.getNumberOfSectors(), geo.getNumberOfRows()); - double x = trackResiduals.getX(xBin); // radius of the pad row - double y2x = trackResiduals.getY2X( - xBin, y2xBin); // y/x coordinate of the bin ~-0.15 ... 0.15 - double z2x = - trackResiduals.getZ2X(z2xBin); // z/x coordinate of the bin 0.1 .. 0.9 - double y = x * y2x; - double z = x * z2x; + // read the data Sector by Sector - if (iRoc >= geo.getNumberOfSlicesA()) { - z = -z; - // y = -y; - } + // data in the tree is not sorted by row + // first find which data belong to which row - { - float sx, sy, sz; - trackResiduals.getVoxelCoordinates(iRoc, xBin, y2xBin, z2xBin, sx, sy, sz); - sy *= x; - sz *= x; - if (fabs(sx - x) + fabs(sy - y) + fabs(sz - z) > 1.e-4) { - std::cout << "wrong coordinates: " << x << " " << y << " " << z << " / " << sx << " " << sy << " " << sz << std::endl; - } + struct VoxelData { + int mNentries{0}; // number of entries + float mX, mY, mZ; // mean position in the local coordinates + float mCx, mCy, mCz; // corrections to the local coordinates + }; + + std::vector vSectorData[nRows * nSectors]; + for (int ir = 0; ir < nRows * nSectors; ir++) { + vSectorData[ir].resize(nY2Xbins * nZ2Xbins); } - // skip empty voxels - float voxEntries = v->stat[o2::tpc::TrackResiduals::VoxV]; - if (voxEntries < 1.) { // no statistics - continue; + { // read data from the tree to vSectorData + + ROOT::TTreeProcessorMT processor(*currentTree, mNthreads); + std::string errMsg = std::string("Error reading ") + directionName + " track residuals: "; + auto myThread = [&](TTreeReader& readerSubRange) { + TTreeReaderValue v(readerSubRange, "voxRes"); + while (readerSubRange.Next()) { + int iSector = (int)v->bsec; + if (iSector < 0 || iSector >= nSectors) { + LOG(fatal) << errMsg << "Sector number " << iSector << " is out of range"; + continue; + } + int iRow = (int)v->bvox[o2::tpc::TrackResiduals::VoxX]; // bin number in x (= pad row) + if (iRow < 0 || iRow >= nRows) { + LOG(fatal) << errMsg << "Row number " << iRow << " is out of range"; + } + double rowX = trackResiduals.getX(iRow); // X of the pad row + int iy = v->bvox[o2::tpc::TrackResiduals::VoxF]; // bin number in y/x 0..14 + int iz = v->bvox[o2::tpc::TrackResiduals::VoxZ]; // bin number in z/x 0..4 + auto& data = vSectorData[iSector * nRows + iRow][iy * nZ2Xbins + iz]; + data.mNentries = (int)v->stat[o2::tpc::TrackResiduals::VoxV]; + data.mX = v->stat[o2::tpc::TrackResiduals::VoxX]; + data.mY = v->stat[o2::tpc::TrackResiduals::VoxF] * rowX; + data.mZ = v->stat[o2::tpc::TrackResiduals::VoxZ] * rowX; + data.mCx = useSmoothed ? v->DS[o2::tpc::TrackResiduals::ResX] : v->D[o2::tpc::TrackResiduals::ResX]; + data.mCy = useSmoothed ? v->DS[o2::tpc::TrackResiduals::ResY] : v->D[o2::tpc::TrackResiduals::ResY]; + data.mCz = useSmoothed ? v->DS[o2::tpc::TrackResiduals::ResZ] : v->D[o2::tpc::TrackResiduals::ResZ]; + if (invertSigns) { + data.mCx *= -1.; + data.mCy *= -1.; + data.mCz *= -1.; + } + } + }; + processor.Process(myThread); } - // double statX = v->stat[o2::tpc::TrackResiduals::VoxX]; // weight - // double statY = v->stat[o2::tpc::TrackResiduals::VoxF]; // weight - // double statZ = v->stat[o2::tpc::TrackResiduals::VoxZ]; // weight - - // double dx = 1. / trackResiduals.getDXI(xBin); - double dy = x / trackResiduals.getDY2XI(xBin, y2xBin); - double dz = x * trackResiduals.getDZ2X(z2xBin); - - double correctionX = useSmoothed ? v->DS[o2::tpc::TrackResiduals::ResX] : v->D[o2::tpc::TrackResiduals::ResX]; - double correctionY = useSmoothed ? v->DS[o2::tpc::TrackResiduals::ResY] : v->D[o2::tpc::TrackResiduals::ResY]; - double correctionZ = useSmoothed ? v->DS[o2::tpc::TrackResiduals::ResZ] : v->D[o2::tpc::TrackResiduals::ResZ]; - if (invertSigns) { - correctionX *= -1.; - correctionY *= -1.; - correctionZ *= -1.; + // debug: mirror the data for TPC C side + + if (mDebugMirrorAdata2C) { + for (int iSector = 0; iSector < geo.getNumberOfSectorsA(); iSector++) { + for (int iRow = 0; iRow < nRows; iRow++) { + for (int iy = 0; iy < nY2Xbins; iy++) { + for (int iz = 0; iz < nZ2Xbins; iz++) { + auto& dataA = vSectorData[iSector * nRows + iRow][iy * nZ2Xbins + iz]; + auto& dataC = vSectorData[(iSector + geo.getNumberOfSectorsA()) * nRows + iRow][iy * nZ2Xbins + iz]; + dataC = dataA; // copy the data + dataC.mZ = -dataC.mZ; // mirror the Z coordinate + dataC.mCz = -dataC.mCz; // mirror the Z correction + } + } + } + } } - // add one point per voxel - // map.addCorrectionPoint(iRoc, iRow, y, z, correctionX, correctionY, - // correctionZ); + double maxError[3] = {0., 0., 0.}; + int nErrors = 0; + + for (int iSector = 0; iSector < nSectors; iSector++) { + + // now process the data row-by-row + + auto myThread = [&](int iThread, int nTreads) { + struct Voxel { + float mY, mZ; // non-distorted local coordinates + float mDy, mDz; // voxel size + int mSmoothingStep{100}; // is the voxel data original or smoothed at this step + }; + + std::vector vRowVoxels(nY2Xbins * nZ2Xbins); + + for (int iRow = iThread; iRow < nRows; iRow += nTreads) { + // LOG(info) << "Processing Sector " << iSector << " row " << iRow; + + // complete the voxel data + { + int xBin = iRow; + double x = trackResiduals.getX(xBin); // radius of the pad row + double dx = 1. / trackResiduals.getDXI(xBin); + bool isDataFound = false; + for (int iy = 0; iy < nY2Xbins; iy++) { + for (int iz = 0; iz < nZ2Xbins; iz++) { + auto& data = vSectorData[iSector * nRows + iRow][iy * nZ2Xbins + iz]; + auto& vox = vRowVoxels[iy * nZ2Xbins + iz]; + // y/x coordinate of the bin ~-0.15 ... 0.15 + double y2x = trackResiduals.getY2X(xBin, iy); + // z/x coordinate of the bin 0.1 .. 0.9 + double z2x = trackResiduals.getZ2X(iz); + vox.mY = x * y2x; + vox.mZ = x * z2x; + vox.mDy = x / trackResiduals.getDY2XI(xBin, iy); + vox.mDz = x * trackResiduals.getDZ2X(iz); + if (iSector >= geo.getNumberOfSectorsA()) { + vox.mZ = -vox.mZ; + } + if (data.mNentries > 0) { // voxel contains data + vox.mSmoothingStep = 0; // take original data + isDataFound = true; + + // correct the mean position if it is outside the voxel + std::stringstream msg; + if (fabs(x - data.mX) > mVoxelMeanValidityRange * dx / 2.) { + msg << "\n x: center " << x << " dx " << data.mX - x << " half bin size " << dx / 2; + } + + if (fabs(vox.mY - data.mY) > mVoxelMeanValidityRange * vox.mDy / 2.) { + msg << "\n y: center " << vox.mY << " dy " << data.mY - vox.mY << " half bin size " << vox.mDy / 2; + data.mY = vox.mY; + } + + if (fabs(vox.mZ - data.mZ) > mVoxelMeanValidityRange * vox.mDz / 2.) { + msg << "\n z: center " << vox.mZ << " dz " << data.mZ - vox.mZ << " half bin size " << vox.mDz / 2; + data.mZ = vox.mZ; + } + + if (!msg.str().empty()) { + bool isMaxErrorExceeded = (fabs(data.mX - x) / dx > maxError[0]) || + (fabs(data.mY - vox.mY) / vox.mDy > maxError[1]) || + (fabs(data.mZ - vox.mZ) / vox.mDz > maxError[2]); + static std::mutex mutex; + mutex.lock(); + nErrors++; + if (nErrors < 20 || isMaxErrorExceeded) { + LOG(warning) << directionName << " correction: error N " << nErrors << "fitted voxel position is outside the voxel: " + << " sector " << iSector << " row " << iRow << " bin y " << iy << " bin z " << iz + << msg.str(); + maxError[0] = GPUCommonMath::Max(maxError[0], fabs(data.mX - x) / dx); + maxError[1] = GPUCommonMath::Max(maxError[1], fabs(data.mY - vox.mY) / vox.mDy); + maxError[2] = GPUCommonMath::Max(maxError[2], fabs(data.mZ - vox.mZ) / vox.mDz); + } + mutex.unlock(); + } + + } else { // no data, take voxel center position + data.mCx = 0.; + data.mCy = 0.; + data.mCz = 0.; + data.mX = x; + data.mY = vox.mY; + data.mZ = vox.mZ; + vox.mSmoothingStep = 100; // fill this data point with smoothed values from the neighbours + } + if (mDebugUseVoxelCenters) { // debug: always use voxel center instead of the mean position + data.mY = vox.mY; + data.mZ = vox.mZ; + } + } + } - // add several points per voxel, - // extend values of the edge voxels to the edges of the TPC row - // + if (!isDataFound) { // fill everything with 0 + for (int iy = 0; iy < nY2Xbins; iy++) { + for (int iz = 0; iz < nZ2Xbins; iz++) { + vRowVoxels[iy * nZ2Xbins + iz].mSmoothingStep = 0; + } + } + } + } // complete the voxel data + + // repare the voxel data: fill empty voxels + + int nRepairs = 0; + + for (int ismooth = 1; ismooth <= 2; ismooth++) { + for (int iy = 0; iy < nY2Xbins; iy++) { + for (int iz = 0; iz < nZ2Xbins; iz++) { + auto& data = vSectorData[iSector * nRows + iRow][iy * nZ2Xbins + iz]; + auto& vox = vRowVoxels[iy * nZ2Xbins + iz]; + if (vox.mSmoothingStep <= ismooth) { // already filled + continue; + } + nRepairs++; + data.mCx = 0.; + data.mCy = 0.; + data.mCz = 0.; + double w = 0.; + bool filled = false; + auto update = [&](int iy1, int iz1) { + auto& data1 = vSectorData[iSector * nRows + iRow][iy1 * nZ2Xbins + iz1]; + auto& vox1 = vRowVoxels[iy1 * nZ2Xbins + iz1]; + if (vox1.mSmoothingStep >= ismooth) { + return false; + } + double w1 = 1. / (abs(iy - iy1) + abs(iz - iz1) + 1); + data.mCx += w1 * data1.mCx; + data.mCy += w1 * data1.mCy; + data.mCz += w1 * data1.mCz; + w += w1; + filled = true; + return true; + }; + + for (int iy1 = iy - 1; iy1 >= 0 && !update(iy1, iz); iy1--) { + } + for (int iy1 = iy + 1; iy1 < nY2Xbins && !update(iy1, iz); iy1++) { + } + for (int iz1 = iz - 1; iz1 >= 0 && !update(iy, iz1); iz1--) { + } + for (int iz1 = iz + 1; iz1 < nZ2Xbins && !update(iy, iz1); iz1++) { + } + + if (filled) { + data.mCx /= w; + data.mCy /= w; + data.mCz /= w; + vox.mSmoothingStep = ismooth; + } + } // iz + } // iy + } // ismooth + + if (nRepairs > 0) { + LOG(debug) << "Sector " << iSector << " row " << iRow << ": " << nRepairs << " voxel repairs for " << nY2Xbins * nZ2Xbins << " voxels"; + } - double yFirst = y - dy / 2.; - double yLast = y + dy / 2.; + // feed the row data to the helper - if (y2xBin == 0) { // extend value of the first Y bin to the row edge - float u, v; - if (iRoc < geo.getNumberOfSlicesA()) { - geo.convScaledUVtoUV(iRoc, iRow, 0., 0., u, v); - } else { - geo.convScaledUVtoUV(iRoc, iRow, 1., 0., u, v); - } - float py, pz; - geo.convUVtoLocal(iRoc, u, v, py, pz); - yFirst = py; - } + auto& info = correction.getRowInfo(iRow); + const auto& spline = correction.getSplineForRow(iRow); - if (y2xBin == trackResiduals.getNY2XBins() - 1) { // extend value of the last Y bin to the row edge - float u, v; - if (iRoc < geo.getNumberOfSlicesA()) { - geo.convScaledUVtoUV(iRoc, iRow, 1., 0., u, v); - } else { - geo.convScaledUVtoUV(iRoc, iRow, 0., 0., u, v); - } - float py, pz; - geo.convUVtoLocal(iRoc, u, v, py, pz); - yLast = py; - } + auto addVoxel = [&](int iy, int iz, double weight) { + auto& vox = vRowVoxels[iy * nZ2Xbins + iz]; + if (vox.mSmoothingStep > 2) { + LOG(fatal) << "empty voxel is not repared: y " << iy << " z " << iz; + } + auto& data = vSectorData[iSector * nRows + iRow][iy * nZ2Xbins + iz]; + map.addCorrectionPoint(iSector, iRow, data.mY, data.mZ, data.mCx, data.mCy, data.mCz, weight); + }; + + auto addEdge = [&](int iy1, int iz1, int iy2, int iz2, int nPoints) { + // add n points on the edge between two voxels excluding the voxel points + if (nPoints < 1) { + return; + } + if (iy1 < 0 || iy1 >= nY2Xbins || iz1 < 0 || iz1 >= nZ2Xbins) { + return; + } + if (iy2 < 0 || iy2 >= nY2Xbins || iz2 < 0 || iz2 >= nZ2Xbins) { + return; + } + auto& data1 = vSectorData[iSector * nRows + iRow][iy1 * nZ2Xbins + iz1]; + auto& vox1 = vRowVoxels[iy1 * nZ2Xbins + iz1]; + auto& data2 = vSectorData[iSector * nRows + iRow][iy2 * nZ2Xbins + iz2]; + auto& vox2 = vRowVoxels[iy2 * nZ2Xbins + iz2]; + double y1 = data1.mY; + double z1 = data1.mZ; + double cx1 = data1.mCx; + double cy1 = data1.mCy; + double cz1 = data1.mCz; + double y2 = data2.mY; + double z2 = data2.mZ; + double cx2 = data2.mCx; + double cy2 = data2.mCy; + double cz2 = data2.mCz; + + for (int is = 1; is <= nPoints; is++) { + double s2 = is / (double)(nPoints + 1); + double s1 = 1. - s2; + double y = s1 * y1 + s2 * y2; + double z = s1 * z1 + s2 * z2; + double cx = s1 * cx1 + s2 * cx2; + double cy = s1 * cy1 + s2 * cy2; + double cz = s1 * cz1 + s2 * cz2; + map.addCorrectionPoint(iSector, iRow, y, z, cx, cy, cz, 1.); + } + }; + + // original measurements weighted by 8 at each voxel and 8 additional artificial measurements around each voxel + // + // (y+1, z) 8 1 1 8 (y+1, z+1) + // 1 1 1 1 1 + // 1 1 1 1 1 + // (y,z) 8 1 1 8 1 + // 1 1 1 1 1 + + for (int iy = 0; iy < nY2Xbins; iy++) { + for (int iz = 0; iz < nZ2Xbins; iz++) { + addVoxel(iy, iz, 8); + addEdge(iy, iz, iy, iz + 1, 2); + addEdge(iy, iz, iy + 1, iz, 2); + addEdge(iy, iz, iy + 1, iz + 1, 2); + addEdge(iy + 1, iz, iy, iz + 1, 2); + } + } - double z0 = 0.; - if (iRoc < geo.getNumberOfSlicesA()) { - z0 = geo.getTPCzLengthA(); - } else { - z0 = -geo.getTPCzLengthC(); - } + } // iRow + }; // myThread + + // run n threads - double yStep = (yLast - yFirst) / 2; + int nThreads = mNthreads; + // nThreads = 1; - for (double py = yFirst; py <= yLast + yStep / 2.; py += yStep) { + std::vector threads(nThreads); - for (double pz = z - dz / 2.; pz <= z + dz / 2. + 1.e-4; pz += dz / 2.) { - map.addCorrectionPoint(iRoc, iRow, py, pz, correctionX, correctionY, - correctionZ); + for (int i = 0; i < nThreads; i++) { + threads[i] = std::thread(myThread, i, nThreads); } - if (z2xBin == trackResiduals.getNZ2XBins() - 1) { - // extend value of the first Z bin to the readout, linear decrease of all values to 0. - int nZsteps = 3; - for (int is = 0; is < nZsteps; is++) { - double pz = z + (z0 - z) * (is + 1.) / nZsteps; - double s = (nZsteps - 1. - is) / nZsteps; - map.addCorrectionPoint(iRoc, iRow, py, pz, s * correctionX, - s * correctionY, s * correctionZ); - } + // wait for the threads to finish + for (auto& th : threads) { + th.join(); } - } - } - helper->fillSpaceChargeCorrectionFromMap(correction); - return std::move(correctionPtr); -} + } // iSector -void TPCFastSpaceChargeCorrectionHelper::initMaxDriftLength(o2::gpu::TPCFastSpaceChargeCorrection& correction, bool prn) -{ - /// initialise max drift length + LOGP(info, "Reading & reparing of the track residuals tooks: {}s", watch3.RealTime()); - double tpcR2min = mGeo.getRowInfo(0).x - 1.; - tpcR2min = tpcR2min * tpcR2min; - double tpcR2max = mGeo.getRowInfo(mGeo.getNumberOfRows() - 1).x; - tpcR2max = tpcR2max / cos(2 * M_PI / mGeo.getNumberOfSlicesA() / 2) + 1.; - tpcR2max = tpcR2max * tpcR2max; + LOG(info) << "fast space charge correction helper: create space charge from the map of data points.."; - ChebyshevFit1D chebFitter; + TStopwatch watch4; - for (int slice = 0; slice < mGeo.getNumberOfSlices(); slice++) { - if (prn) { - LOG(info) << "init MaxDriftLength for slice " << slice; + if (!processingInverseCorrection && fitPointsDirect) { + *fitPointsDirect = helper->getCorrectionMap(); + } + if (processingInverseCorrection && fitPointsInverse) { + *fitPointsInverse = helper->getCorrectionMap(); } - double vLength = (slice < mGeo.getNumberOfSlicesA()) ? mGeo.getTPCzLengthA() : mGeo.getTPCzLengthC(); - TPCFastSpaceChargeCorrection::SliceInfo& sliceInfo = correction.getSliceInfo(slice); - sliceInfo.vMax = 0.f; - for (int row = 0; row < mGeo.getNumberOfRows(); row++) { - TPCFastSpaceChargeCorrection::RowActiveArea& area = correction.getSliceRowInfo(slice, row).activeArea; - area.cvMax = 0; - area.vMax = 0; - area.cuMin = mGeo.convPadToU(row, 0.f); - area.cuMax = -area.cuMin; - chebFitter.reset(4, 0., mGeo.getRowInfo(row).maxPad); - double x = mGeo.getRowInfo(row).x; - for (int pad = 0; pad < mGeo.getRowInfo(row).maxPad; pad++) { - float u = mGeo.convPadToU(row, (float)pad); - float v0 = 0; - float v1 = 1.1 * vLength; - float vLastValid = -1; - float cvLastValid = -1; - while (v1 - v0 > 0.1) { - float v = 0.5 * (v0 + v1); - float dx, du, dv; - correction.getCorrection(slice, row, u, v, dx, du, dv); - double cx = x + dx; - double cu = u + du; - double cv = v + dv; - double r2 = cx * cx + cu * cu; - if (cv < 0) { - v0 = v; - } else if (cv <= vLength && r2 >= tpcR2min && r2 <= tpcR2max) { - v0 = v; - vLastValid = v; - cvLastValid = cv; - } else { - v1 = v; - } - } - if (vLastValid > 0.) { - chebFitter.addMeasurement(pad, vLastValid); - } - if (area.vMax < vLastValid) { - area.vMax = vLastValid; - } - if (area.cvMax < cvLastValid) { - area.cvMax = cvLastValid; - } - } - chebFitter.fit(); - for (int i = 0; i < 5; i++) { - area.maxDriftLengthCheb[i] = chebFitter.getCoefficients()[i]; - } - if (sliceInfo.vMax < area.vMax) { - sliceInfo.vMax = area.vMax; - } - } // row - } // slice -} + helper->fillSpaceChargeCorrectionFromMap(correction, processingInverseCorrection); + + LOG(info) << "fast space charge correction helper: creation from the data map took " << watch4.RealTime() << "s"; + + } // processingInverseCorrection + + if (voxResTree && !voxResTreeInverse) { + LOG(info) << "fast space charge correction helper: init inverse correction from direct correction.."; + TStopwatch watch4; + helper->initInverse(correction, false); + LOG(info) << "fast space charge correction helper: init inverse correction took " << watch4.RealTime() << "s"; + } + + LOGP(info, "Creation from track residuals tooks in total: {}s", watch.RealTime()); + + return std::move(correctionPtr); + +} // createFromTrackResiduals void TPCFastSpaceChargeCorrectionHelper::initInverse(o2::gpu::TPCFastSpaceChargeCorrection& correction, bool prn) { @@ -670,128 +895,99 @@ void TPCFastSpaceChargeCorrectionHelper::initInverse(std::vector helper; std::vector splineParameters; - ChebyshevFit1D chebFitterX, chebFitterU, chebFitterV; for (int row = iThread; row < mGeo.getNumberOfRows(); row += mNthreads) { - TPCFastSpaceChargeCorrection::SplineType spline = correction.getSpline(slice, row); - helper.setSpline(spline, 10, 10); - std::vector dataPointCU, dataPointCV, dataPointF; + auto& rowInfo = correction.getRowInfo(row); - float u0, u1, v0, v1; - mGeo.convScaledUVtoUV(slice, row, 0., 0., u0, v0); - mGeo.convScaledUVtoUV(slice, row, 1., 1., u1, v1); - - double x = mGeo.getRowInfo(row).x; - int nPointsU = (spline.getGridX1().getNumberOfKnots() - 1) * 10; - int nPointsV = (spline.getGridX2().getNumberOfKnots() - 1) * 10; - - double stepU = (u1 - u0) / (nPointsU - 1); - double stepV = (v1 - v0) / (nPointsV - 1); + TPCFastSpaceChargeCorrection::SplineType spline = correction.getSplineForRow(row); + helper.setSpline(spline, 10, 10); - if (prn) { - LOG(info) << "u0 " << u0 << " u1 " << u1 << " v0 " << v0 << " v1 " << v1; - } - TPCFastSpaceChargeCorrection::RowActiveArea& area = correction.getSliceRowInfo(slice, row).activeArea; - area.cuMin = 1.e10; - area.cuMax = -1.e10; - - /* - v1 = area.vMax; - stepV = (v1 - v0) / (nPointsU - 1); - if (stepV < 1.f) { - stepV = 1.f; - } - */ - - for (double u = u0; u < u1 + stepU; u += stepU) { - for (double v = v0; v < v1 + stepV; v += stepV) { - float dx, du, dv; - correction.getCorrection(slice, row, u, v, dx, du, dv); - dx *= scaling[0]; - du *= scaling[0]; - dv *= scaling[0]; - // add remaining corrections - for (int i = 1; i < corrections.size(); ++i) { - float dxTmp, duTmp, dvTmp; - corrections[i]->getCorrection(slice, row, u, v, dxTmp, duTmp, dvTmp); - dx += dxTmp * scaling[i]; - du += duTmp * scaling[i]; - dv += dvTmp * scaling[i]; + std::vector gridU; + { + const auto& grid = spline.getGridX1(); + for (int i = 0; i < grid.getNumberOfKnots(); i++) { + if (i == grid.getNumberOfKnots() - 1) { + gridU.push_back(grid.getKnot(i).u); + break; } - double cx = x + dx; - double cu = u + du; - double cv = v + dv; - if (cu < area.cuMin) { - area.cuMin = cu; + for (double s = 1.; s > 0.; s -= 0.1) { + gridU.push_back(s * grid.getKnot(i).u + (1. - s) * grid.getKnot(i + 1).u); } - if (cu > area.cuMax) { - area.cuMax = cu; + } + } + std::vector gridV; + { + const auto& grid = spline.getGridX2(); + for (int i = 0; i < grid.getNumberOfKnots(); i++) { + if (i == grid.getNumberOfKnots() - 1) { + gridV.push_back(grid.getKnot(i).u); + break; } - - dataPointCU.push_back(cu); - dataPointCV.push_back(cv); - dataPointF.push_back(dx); - dataPointF.push_back(du); - dataPointF.push_back(dv); - - if (prn) { - LOG(info) << "measurement cu " << cu << " cv " << cv << " dx " << dx << " du " << du << " dv " << dv; + for (double s = 1.; s > 0.; s -= 0.1) { + gridV.push_back(s * grid.getKnot(i).u + (1. - s) * grid.getKnot(i + 1).u); } - } // v - } // u - - if (area.cuMax - area.cuMin < 0.2) { - area.cuMax = .1; - area.cuMin = -.1; - } - if (area.cvMax < 0.1) { - area.cvMax = .1; - } - if (prn) { - LOG(info) << "slice " << slice << " row " << row << " max drift L = " << correction.getMaxDriftLength(slice, row) - << " active area: cuMin " << area.cuMin << " cuMax " << area.cuMax << " vMax " << area.vMax << " cvMax " << area.cvMax; + } } - TPCFastSpaceChargeCorrection::SliceRowInfo& info = correction.getSliceRowInfo(slice, row); - info.gridCorrU0 = area.cuMin; - info.scaleCorrUtoGrid = spline.getGridX1().getUmax() / (area.cuMax - area.cuMin); - info.scaleCorrVtoGrid = spline.getGridX2().getUmax() / area.cvMax; - - info.gridCorrU0 = u0; - info.gridCorrV0 = info.gridV0; - info.scaleCorrUtoGrid = spline.getGridX1().getUmax() / (u1 - info.gridCorrU0); - info.scaleCorrVtoGrid = spline.getGridX2().getUmax() / (v1 - info.gridCorrV0); - - int nDataPoints = dataPointCU.size(); - for (int i = 0; i < nDataPoints; i++) { - dataPointCU[i] = (dataPointCU[i] - info.gridCorrU0) * info.scaleCorrUtoGrid; - dataPointCV[i] = (dataPointCV[i] - info.gridCorrV0) * info.scaleCorrVtoGrid; + std::vector dataPointGridU, dataPointGridV, dataPointF, dataPointWeight; + dataPointGridU.reserve(gridU.size() * gridV.size()); + dataPointGridV.reserve(gridU.size() * gridV.size()); + dataPointF.reserve(3 * gridU.size() * gridV.size()); + dataPointWeight.reserve(gridU.size() * gridV.size()); + + for (int iu = 0; iu < gridU.size(); iu++) { + for (int iv = 0; iv < gridV.size(); iv++) { + float y, z; + correction.convGridToLocal(sector, row, gridU[iu], gridV[iv], y, z); + double dx = 0, dy = 0, dz = 0; + + // add corrections + for (int i = 0; i < corrections.size(); ++i) { + float dxTmp, dyTmp, dzTmp; + corrections[i]->getCorrectionLocal(sector, row, y, z, dxTmp, dyTmp, dzTmp); + dx += dxTmp * scaling[i]; + dy += dyTmp * scaling[i]; + dz += dzTmp * scaling[i]; + } + float gridU, gridV, scale; + correction.convRealLocalToGrid(sector, row, y + dy, z + dz, gridU, gridV, scale); + dataPointGridU.push_back(gridU); + dataPointGridV.push_back(gridV); + dataPointF.push_back(scale * dx); + dataPointF.push_back(scale * dy); + dataPointF.push_back(scale * dz); + dataPointWeight.push_back(1.); + } } + int nDataPoints = dataPointGridU.size(); splineParameters.resize(spline.getNumberOfParameters()); helper.approximateDataPoints(spline, splineParameters.data(), 0., spline.getGridX1().getUmax(), 0., spline.getGridX2().getUmax(), - dataPointCU.data(), dataPointCV.data(), - dataPointF.data(), dataPointCU.size()); + dataPointGridU.data(), dataPointGridV.data(), + dataPointF.data(), dataPointWeight.data(), nDataPoints); - float* splineX = correction.getSplineData(slice, row, 1); - float* splineUV = correction.getSplineData(slice, row, 2); + float* splineX = correction.getCorrectionDataInvX(sector, row); + float* splineUV = correction.getCorrectionDataInvYZ(sector, row); for (int i = 0; i < spline.getNumberOfParameters() / 3; i++) { splineX[i] = splineParameters[3 * i + 0]; splineUV[2 * i + 0] = splineParameters[3 * i + 1]; @@ -812,9 +1008,162 @@ void TPCFastSpaceChargeCorrectionHelper::initInverse(std::vector>& additionalCorrections, bool /*prn*/) +{ + /// merge several corrections + + TStopwatch watch; + LOG(info) << "fast space charge correction helper: Merge corrections"; + + const auto& geo = mainCorrection.getGeometry(); + + for (int sector = 0; sector < geo.getNumberOfSectors(); sector++) { + + auto myThread = [&](int iThread) { + for (int row = iThread; row < geo.getNumberOfRows(); row += mNthreads) { + auto& rowInfo = mainCorrection.getRowInfo(row); + const auto& spline = mainCorrection.getSplineForRow(row); + + float* splineParameters = mainCorrection.getCorrectionData(sector, row); + float* splineParametersInvX = mainCorrection.getCorrectionDataInvX(sector, row); + float* splineParametersInvYZ = mainCorrection.getCorrectionDataInvYZ(sector, row); + + constexpr int nKnotPar1d = 4; + constexpr int nKnotPar2d = nKnotPar1d * 2; + constexpr int nKnotPar3d = nKnotPar1d * 3; + + { // scale the main correction + + double parscale[4] = {mainScale, mainScale, mainScale, mainScale * mainScale}; + for (int iknot = 0, ind = 0; iknot < spline.getNumberOfKnots(); iknot++) { + for (int ipar = 0; ipar < nKnotPar1d; ++ipar) { + for (int idim = 0; idim < 3; idim++, ind++) { + splineParameters[ind] *= parscale[ipar]; + } + } + } + for (int iknot = 0, ind = 0; iknot < spline.getNumberOfKnots(); iknot++) { + for (int ipar = 0; ipar < nKnotPar1d; ++ipar) { + for (int idim = 0; idim < 1; idim++, ind++) { + splineParametersInvX[ind] *= parscale[ipar]; + } + } + } + for (int iknot = 0, ind = 0; iknot < spline.getNumberOfKnots(); iknot++) { + for (int ipar = 0; ipar < nKnotPar1d; ++ipar) { + for (int idim = 0; idim < 2; idim++, ind++) { + splineParametersInvYZ[ind] *= parscale[ipar]; + } + } + } + } + + // add the other corrections + + const auto& gridU = spline.getGridX1(); + const auto& gridV = spline.getGridX2(); + + for (int icorr = 0; icorr < additionalCorrections.size(); ++icorr) { + const auto& corr = *(additionalCorrections[icorr].first); + double scale = additionalCorrections[icorr].second; + auto& linfo = corr.getRowInfo(row); + + double scaleU = rowInfo.gridMeasured.getYscale() / linfo.gridMeasured.getYscale(); + double scaleV = rowInfo.gridMeasured.getZscale() / linfo.gridMeasured.getZscale(); + double scaleRealU = rowInfo.gridReal.getYscale() / linfo.gridReal.getYscale(); + double scaleRealV = rowInfo.gridReal.getZscale() / linfo.gridReal.getZscale(); + + for (int iu = 0; iu < gridU.getNumberOfKnots(); iu++) { + double u = gridU.getKnot(iu).u; + for (int iv = 0; iv < gridV.getNumberOfKnots(); iv++) { + double v = gridV.getKnot(iv).u; + int knotIndex = spline.getKnotIndex(iu, iv); + float P[nKnotPar3d]; + + { // direct correction + float y, z; + mainCorrection.convGridToLocal(sector, row, u, v, y, z); + // return values: u, v, scaling factor + float lu, lv, ls; + corr.convLocalToGrid(sector, row, y, z, lu, lv, ls); + ls *= scale; + double parscale[4] = {ls, ls * scaleU, ls * scaleV, ls * ls * scaleU * scaleV}; + const auto& spl = corr.getSplineForRow(row); + spl.interpolateParametersAtU(corr.getCorrectionData(sector, row), lu, lv, P); + for (int ipar = 0, ind = 0; ipar < nKnotPar1d; ++ipar) { + for (int idim = 0; idim < 3; idim++, ind++) { + splineParameters[knotIndex * nKnotPar3d + ind] += parscale[ipar] * P[ind]; + } + } + } + + float y, z; + mainCorrection.convGridToRealLocal(sector, row, u, v, y, z); + // return values: u, v, scaling factor + float lu, lv, ls; + corr.convRealLocalToGrid(sector, row, y, z, lu, lv, ls); + ls *= scale; + double parscale[4] = {ls, ls * scaleRealU, ls * scaleRealV, ls * ls * scaleRealU * scaleRealV}; + + { // inverse X correction + corr.getSplineInvXforRow(row).interpolateParametersAtU(corr.getCorrectionDataInvX(sector, row), lu, lv, P); + for (int ipar = 0, ind = 0; ipar < nKnotPar1d; ++ipar) { + for (int idim = 0; idim < 1; idim++, ind++) { + splineParametersInvX[knotIndex * nKnotPar1d + ind] += parscale[ipar] * P[ind]; + } + } + } + + { // inverse YZ correction + corr.getSplineInvYZforRow(row).interpolateParametersAtU(corr.getCorrectionDataInvYZ(sector, row), lu, lv, P); + for (int ipar = 0, ind = 0; ipar < nKnotPar1d; ++ipar) { + for (int idim = 0; idim < 2; idim++, ind++) { + splineParametersInvYZ[knotIndex * nKnotPar2d + ind] += parscale[ipar] * P[ind]; + } + } + } + + } // iv + } // iu + } // corrections + + } // row + }; // thread + + std::vector threads(mNthreads); + + // run n threads + for (int i = 0; i < mNthreads; i++) { + threads[i] = std::thread(myThread, i); + } + + // wait for the threads to finish + for (auto& th : threads) { + th.join(); + } + + } // sector + float duration = watch.RealTime(); + LOGP(info, "Merge of corrections tooks: {}s", duration); +} + +void TPCFastSpaceChargeCorrectionHelper::setDebugUseVoxelCenters() +{ + LOG(info) << "fast space charge correction helper: use voxel centers for correction"; + mDebugUseVoxelCenters = true; +} + +void TPCFastSpaceChargeCorrectionHelper::setDebugMirrorAdata2C() +{ + LOG(info) << "fast space charge correction helper: mirror A data to C data"; + mDebugMirrorAdata2C = true; } } // namespace tpc diff --git a/Detectors/TPC/calibration/src/TrackDump.cxx b/Detectors/TPC/calibration/src/TrackDump.cxx index 421750a5cb22b..72042a537dc5f 100644 --- a/Detectors/TPC/calibration/src/TrackDump.cxx +++ b/Detectors/TPC/calibration/src/TrackDump.cxx @@ -24,8 +24,6 @@ using namespace o2::tpc; using namespace o2::tpc::constants; namespace fs = std::filesystem; -o2::gpu::CorrectionMapsHelper o2::tpc::TrackDump::ClusterNativeAdd::sCorrHelper{}; - void TrackDump::filter(const gsl::span tracks, ClusterNativeAccess const& clusterIndex, const gsl::span clRefs, const gsl::span mcLabels) { if (!mTreeDump && outputFileName.size()) { @@ -197,8 +195,8 @@ float TrackDump::ClusterNativeAdd::gy() const float TrackDump::ClusterNativeAdd::lxc(float vertexTime) const { float x{0.f}, y{0.f}, z{0.f}; - if (sCorrHelper.getCorrMap()) { - sCorrHelper.Transform(sector, padrow, getPad(), getTime(), x, y, z, vertexTime); + if (corrMap) { + corrMap->Transform(sector, padrow, getPad(), getTime(), x, y, z, vertexTime); } return x; } @@ -206,8 +204,8 @@ float TrackDump::ClusterNativeAdd::lxc(float vertexTime) const float TrackDump::ClusterNativeAdd::lyc(float vertexTime) const { float x{0.f}, y{0.f}, z{0.f}; - if (sCorrHelper.getCorrMap()) { - sCorrHelper.Transform(sector, padrow, getPad(), getTime(), x, y, z, vertexTime); + if (corrMap) { + corrMap->Transform(sector, padrow, getPad(), getTime(), x, y, z, vertexTime); } return y; } @@ -229,17 +227,17 @@ float TrackDump::ClusterNativeAdd::gyc(float vertexTime) const float TrackDump::ClusterNativeAdd::zc(float vertexTime) const { float x{0.f}, y{0.f}, z{0.f}; - if (sCorrHelper.getCorrMap()) { - sCorrHelper.Transform(sector, padrow, getPad(), getTime(), x, y, z, vertexTime); + if (corrMap) { + corrMap->Transform(sector, padrow, getPad(), getTime(), x, y, z, vertexTime); } return z; } void TrackDump::ClusterNativeAdd::loadCorrMaps(std::string_view corrMapFile, std::string_view corrMapFileRef) { - sCorrHelper.setOwner(true); - sCorrHelper.setCorrMap(gpu::TPCFastTransform::loadFromFile(corrMapFile.data())); - if (!corrMapFileRef.empty()) { - sCorrHelper.setCorrMapRef(gpu::TPCFastTransform::loadFromFile(corrMapFileRef.data())); - } + auto fastTransformTmp = gpu::TPCFastTransform::loadFromFile(corrMapFile.data()); + o2::gpu::aligned_unique_buffer_ptr buffer; + gpu::TPCFastTransformPOD::create(buffer, *fastTransformTmp); + corrMapBuffer = std::move(buffer); + corrMap = corrMapBuffer.get(); } diff --git a/Detectors/TPC/dcs/CMakeLists.txt b/Detectors/TPC/dcs/CMakeLists.txt index 31524dd5f2c2f..278b60f9965e0 100644 --- a/Detectors/TPC/dcs/CMakeLists.txt +++ b/Detectors/TPC/dcs/CMakeLists.txt @@ -18,7 +18,8 @@ o2_add_library(TPCdcs PUBLIC_LINK_LIBRARIES O2::Framework O2::DetectorsDCS O2::DataFormatsTPC - O2::TPCBase) + O2::TPCBase + O2::TPCBaseRecSim) o2_target_root_dictionary(TPCdcs HEADERS include/TPCdcs/DCSProcessor.h) diff --git a/Detectors/TPC/monitor/src/SimpleEventDisplayGUI.cxx b/Detectors/TPC/monitor/src/SimpleEventDisplayGUI.cxx index 5509aa7473fc8..0d032443c9754 100644 --- a/Detectors/TPC/monitor/src/SimpleEventDisplayGUI.cxx +++ b/Detectors/TPC/monitor/src/SimpleEventDisplayGUI.cxx @@ -1227,7 +1227,7 @@ void SimpleEventDisplayGUI::showClusters(int roc, int row) } if (fillSingleTB && std::abs(cl.getTime() - timeBin) < 2) { const auto ly = gpuGeom.LinearPad2Y(sector, irow, cl.getPad() + 0.5); - mClustersRowPad->SetNextPoint(gpuGeom.Row2X(irow), (sector >= GPUCA_NSECTORS / 2) ? -ly : ly); + mClustersRowPad->SetNextPoint(gpuGeom.Row2X(irow), (sector >= gpuGeom.NSECTORS / 2) ? -ly : ly); } } // fmt::print("\n"); diff --git a/Detectors/TPC/reconstruction/CMakeLists.txt b/Detectors/TPC/reconstruction/CMakeLists.txt index 29e6d692968b7..0045aad7aa4c7 100644 --- a/Detectors/TPC/reconstruction/CMakeLists.txt +++ b/Detectors/TPC/reconstruction/CMakeLists.txt @@ -96,6 +96,13 @@ o2_add_test(GPUCATracking SOURCES test/testGPUCATracking.cxx ENVIRONMENT O2_ROOT=${CMAKE_BINARY_DIR}/stage) +o2_add_test(GPUTPCGeometry + COMPONENT_NAME tpc + LABELS tpc + PUBLIC_LINK_LIBRARIES O2::TPCBase O2::GPUDataTypes + SOURCES test/testGPUGeometry.cxx + ENVIRONMENT O2_ROOT=${CMAKE_BINARY_DIR}/stage) + o2_add_test(HwClusterer COMPONENT_NAME tpc LABELS tpc diff --git a/Detectors/TPC/reconstruction/include/TPCReconstruction/TPCFastTransformHelperO2.h b/Detectors/TPC/reconstruction/include/TPCReconstruction/TPCFastTransformHelperO2.h index f94bff0acc076..943521e3b11ec 100644 --- a/Detectors/TPC/reconstruction/include/TPCReconstruction/TPCFastTransformHelperO2.h +++ b/Detectors/TPC/reconstruction/include/TPCReconstruction/TPCFastTransformHelperO2.h @@ -21,9 +21,8 @@ #ifndef ALICEO2_TPC_TPCFASTTRANSFORMHELPERO2_H_ #define ALICEO2_TPC_TPCFASTTRANSFORMHELPERO2_H_ -#include "TPCFastTransform.h" -#include "Rtypes.h" -#include +#include "TPCFastTransformPOD.h" +#include "GPUCommonRtypes.h" namespace o2 { @@ -55,14 +54,21 @@ class TPCFastTransformHelperO2 /// _______________ Main functionality ________________________ /// creates TPCFastTransform object - std::unique_ptr create(Long_t TimeStamp); + std::unique_ptr create(int64_t TimeStamp); /// creates TPCFastTransform object - std::unique_ptr create(Long_t TimeStamp, const TPCFastSpaceChargeCorrection& correction); + std::unique_ptr create(int64_t TimeStamp, const TPCFastSpaceChargeCorrection& correction); /// Updates the transformation with the new time stamp - int updateCalibration(TPCFastTransform& transform, Long_t TimeStamp, float vDriftFactor = 1.f, float vDriftRef = 0.f, float driftTimeOffset = 0.f); - + int updateCalibration(TPCFastTransform& fastTransform, int64_t TimeStamp, float vDriftFactor = 1.f, float vDriftRef = 0.f, float driftTimeOffset = 0.f) + { + return updateCalibrationImpl(fastTransform, TimeStamp, vDriftFactor, vDriftRef, driftTimeOffset); + } + + int updateCalibration(TPCFastTransformPOD& fastTransform, int64_t TimeStamp, float vDriftFactor = 1.f, float vDriftRef = 0.f, float driftTimeOffset = 0.f) + { + return updateCalibrationImpl(fastTransform, TimeStamp, vDriftFactor, vDriftRef, driftTimeOffset); + } /// _______________ Utilities ________________________ const TPCFastTransformGeo& getGeometry() { return mGeo; } @@ -73,6 +79,9 @@ class TPCFastTransformHelperO2 /// initialization void init(); + template + int updateCalibrationImpl(T& transform, int64_t TimeStamp, float vDriftFactor, float vDriftRef, float driftTimeOffset); + static TPCFastTransformHelperO2* sInstance; ///< singleton instance bool mIsInitialized = 0; ///< initialization flag TPCFastTransformGeo mGeo; ///< geometry parameters diff --git a/Detectors/TPC/reconstruction/macro/createTPCSpaceChargeCorrection.C b/Detectors/TPC/reconstruction/macro/createTPCSpaceChargeCorrection.C index 723cf2ee30491..af066598d1317 100644 --- a/Detectors/TPC/reconstruction/macro/createTPCSpaceChargeCorrection.C +++ b/Detectors/TPC/reconstruction/macro/createTPCSpaceChargeCorrection.C @@ -397,10 +397,9 @@ void debugInterpolation(utils::TreeStreamRedirector& pcstream, const o2::gpu::TPCFastTransformGeo& geo, TPCFastTransform* fastTransform) { - for (int slice = 0; slice < geo.getNumberOfSlices(); slice += 1) { - // for (int slice = 21; slice < 22; slice += 1) { - std::cout << "debug slice " << slice << " ... " << std::endl; - const o2::gpu::TPCFastTransformGeo::SliceInfo& sliceInfo = geo.getSliceInfo(slice); + for (int sector = 0; sector < geo.getNumberOfSectors(); sector += 1) { + // for (int sector = 21; sector < 22; sector += 1) { + std::cout << "debug sector " << sector << " ... " << std::endl; for (int row = 0; row < geo.getNumberOfRows(); row++) { int nPads = geo.getRowInfo(row).maxPad + 1; @@ -411,28 +410,28 @@ void debugInterpolation(utils::TreeStreamRedirector& pcstream, // non-corrected point fastTransform->setApplyCorrectionOff(); float lx, ly, lz; - fastTransform->Transform(slice, row, pad, time, lx, ly, lz); + fastTransform->Transform(sector, row, pad, time, lx, ly, lz); float gx, gy, gz, r, phi; - geo.convLocalToGlobal(slice, lx, ly, lz, gx, gy, gz); + geo.convLocalToGlobal(sector, lx, ly, lz, gx, gy, gz); r = std::sqrt(lx * lx + ly * ly); phi = std::atan2(gy, gx); fastTransform->setApplyCorrectionOn(); // fast transformation float lxT, lyT, lzT; - fastTransform->Transform(slice, row, pad, time, lxT, lyT, lzT); + fastTransform->Transform(sector, row, pad, time, lxT, lyT, lzT); float gxT, gyT, gzT, rT; - geo.convLocalToGlobal(slice, lxT, lyT, lzT, gxT, gyT, gzT); + geo.convLocalToGlobal(sector, lxT, lyT, lzT, gxT, gyT, gzT); rT = std::sqrt(lxT * lxT + lyT * lyT); // the original correction double gdC[3] = {0, 0, 0}; - Side side = slice < geo.getNumberOfSlicesA() ? Side::A : Side::C; + Side side = sector < geo.getNumberOfSectorsA() ? Side::A : Side::C; if (spaceCharge) { spaceCharge->getCorrections(gx, gy, gz, side, gdC[0], gdC[1], gdC[2]); } float ldxC, ldyC, ldzC; - geo.convGlobalToLocal(slice, gdC[0], gdC[1], gdC[2], ldxC, ldyC, ldzC); + geo.convGlobalToLocal(sector, gdC[0], gdC[1], gdC[2], ldxC, ldyC, ldzC); double rC = std::sqrt((gx + gdC[0]) * (gx + gdC[0]) + (gy + gdC[1]) * (gy + gdC[1])); @@ -466,7 +465,7 @@ void debugInterpolation(utils::TreeStreamRedirector& pcstream, if (spaceChargeExB) { double gdC_ExB[3] = {0, 0, 0}; spaceChargeExB->getCorrections(gx, gy, gz, side, gdC_ExB[0], gdC_ExB[1], gdC_ExB[2]); - geo.convGlobalToLocal(slice, gdC_ExB[0], gdC_ExB[1], gdC_ExB[2], ldxC_ExB, ldyC_ExB, ldzC_ExB); + geo.convGlobalToLocal(sector, gdC_ExB[0], gdC_ExB[1], gdC_ExB[2], ldxC_ExB, ldyC_ExB, ldzC_ExB); } // static distortions @@ -474,18 +473,18 @@ void debugInterpolation(utils::TreeStreamRedirector& pcstream, if (spaceChargeStack) { double gdC_static[3] = {0, 0, 0}; spaceChargeStack->getCorrections(gx, gy, gz, side, gdC_static[0], gdC_static[1], gdC_static[2]); - geo.convGlobalToLocal(slice, gdC_static[0], gdC_static[1], gdC_static[2], ldxC_static, ldyC_static, ldzC_static); + geo.convGlobalToLocal(sector, gdC_static[0], gdC_static[1], gdC_static[2], ldxC_static, ldyC_static, ldzC_static); } // get combined corrections double dx_comb = 0, dy_comb = 0, dz_comb = 0; - getGlobalSpaceChargeCorrectionLinearCombination(slice, gx, gy, gz, dx_comb, dy_comb, dz_comb); + getGlobalSpaceChargeCorrectionLinearCombination(sector, gx, gy, gz, dx_comb, dy_comb, dz_comb); float ldxC_comb, ldyC_comb, ldzC_comb; - geo.convGlobalToLocal(slice, dx_comb, dy_comb, dz_comb, ldxC_comb, ldyC_comb, ldzC_comb); + geo.convGlobalToLocal(sector, dx_comb, dy_comb, dz_comb, ldxC_comb, ldyC_comb, ldzC_comb); pcstream << "fastTransform" // internal coordinates - << "slice=" << slice + << "sector=" << sector << "row=" << row << "pad=" << pad << "time=" << time @@ -613,10 +612,9 @@ void debugGridpoints(utils::TreeStreamRedirector& pcstream, const o2::gpu::TPCFa break; } } - float u = 0.f, v = 0.f; - geo.convLocalToUV(sector, y0, z0, u, v); + float pad = 0.f, time = 0.f; - fastTransform->convUVtoPadTime(sector, row, u, v, pad, time, 0.f); + fastTransform->convLocalToPadTime(sector, row, y0, z0, pad, time, 0.f); if (pad < 0) { continue; } diff --git a/Detectors/TPC/reconstruction/src/TPCFastTransformHelperO2.cxx b/Detectors/TPC/reconstruction/src/TPCFastTransformHelperO2.cxx index 7db84f0e94968..c08444e0baab3 100644 --- a/Detectors/TPC/reconstruction/src/TPCFastTransformHelperO2.cxx +++ b/Detectors/TPC/reconstruction/src/TPCFastTransformHelperO2.cxx @@ -14,17 +14,18 @@ #include "TPCReconstruction/TPCFastTransformHelperO2.h" +#ifndef GPUCA_STANDALONE #include "TPCBase/Mapper.h" #include "TPCBase/PadRegionInfo.h" +#endif #include "TPCBase/ParameterDetector.h" #include "TPCBase/ParameterElectronics.h" #include "TPCBase/ParameterGas.h" #include "TPCBase/Sector.h" #include "DataFormatsTPC/Defs.h" #include "TPCFastTransform.h" -#include "Spline2DHelper.h" -#include "Riostream.h" -#include +#include "GPUTPCGeometry.h" +#include using namespace o2::gpu; @@ -49,48 +50,28 @@ void TPCFastTransformHelperO2::init() { // initialize geometry - const Mapper& mapper = Mapper::instance(); + const GPUTPCGeometry geo; - const int nRows = mapper.getNumberOfRows(); + const int nRows = geo.NROWS; mGeo.startConstruction(nRows); + mGeo.setTPCzLength(geo.TPCLength()); - auto& detParam = ParameterDetector::Instance(); - float tpcZlengthSideA = detParam.TPClength; - float tpcZlengthSideC = detParam.TPClength; - - mGeo.setTPCzLength(tpcZlengthSideA, tpcZlengthSideC); - - mGeo.setTPCalignmentZ(0.); - - for (int iRow = 0; iRow < mGeo.getNumberOfRows(); iRow++) { - Sector sector = 0; - int regionNumber = 0; - while (iRow >= mapper.getGlobalRowOffsetRegion(regionNumber) + mapper.getNumberOfRowsRegion(regionNumber)) { - regionNumber++; - } - - const PadRegionInfo& region = mapper.getPadRegionInfo(regionNumber); - - int nPads = mapper.getNumberOfPadsInRowSector(iRow); - float padWidth = region.getPadWidth(); - - const GlobalPadNumber pad = mapper.globalPadNumber(PadPos(iRow, nPads / 2)); - const PadCentre& padCentre = mapper.padCentre(pad); - float xRow = padCentre.X(); - - mGeo.setTPCrow(iRow, xRow, nPads, padWidth); + for (int iRow = 0; iRow < nRows; iRow++) { + mGeo.setTPCrow(iRow, geo.Row2X(iRow), geo.NPads(iRow), geo.PadWidth(iRow)); } mGeo.finishConstruction(); +#ifndef GPUCA_STANDALONE // check if calculated pad geometry is consistent with the map testGeometry(mGeo); +#endif mIsInitialized = 1; } -std::unique_ptr TPCFastTransformHelperO2::create(Long_t TimeStamp, const TPCFastSpaceChargeCorrection& correction) +std::unique_ptr TPCFastTransformHelperO2::create(int64_t TimeStamp, const TPCFastSpaceChargeCorrection& correction) { /// initializes TPCFastTransform object @@ -114,22 +95,18 @@ std::unique_ptr TPCFastTransformHelperO2::create(Long_t TimeSt // set some initial calibration values, will be reinitialised later int updateCalibration() const float t0 = 0.; const float vDrift = 0.f; - const float vdCorrY = 0.; - const float ldCorr = 0.; - const float tofCorr = 0.; - const float primVtxZ = 0.; const long int initTimeStamp = -1; - fastTransform.setCalibration(initTimeStamp, t0, vDrift, vdCorrY, ldCorr, tofCorr, primVtxZ); + fastTransform.setCalibration(initTimeStamp, t0, vDrift); fastTransform.finishConstruction(); } updateCalibration(fastTransform, TimeStamp); - return std::move(fastTransformPtr); + return fastTransformPtr; } -std::unique_ptr TPCFastTransformHelperO2::create(Long_t TimeStamp) +std::unique_ptr TPCFastTransformHelperO2::create(int64_t TimeStamp) { /// initializes TPCFastTransform object @@ -145,7 +122,8 @@ std::unique_ptr TPCFastTransformHelperO2::create(Long_t TimeSt return create(TimeStamp, correction); } -int TPCFastTransformHelperO2::updateCalibration(TPCFastTransform& fastTransform, Long_t TimeStamp, float vDriftFactor, float vDriftRef, float driftTimeOffset) +template +int TPCFastTransformHelperO2::updateCalibrationImpl(T& fastTransform, int64_t TimeStamp, float vDriftFactor, float vDriftRef, float driftTimeOffset) { // Update the calibration with the new time stamp LOGP(debug, "Updating calibration: timestamp:{} vdriftFactor:{} vdriftRef:{}", TimeStamp, vDriftFactor, vDriftRef); @@ -159,8 +137,6 @@ int TPCFastTransformHelperO2::updateCalibration(TPCFastTransform& fastTransform, // search for the calibration database ... - auto& detParam = ParameterDetector::Instance(); - auto& gasParam = ParameterGas::Instance(); auto& elParam = ParameterElectronics::Instance(); // start the initialization @@ -171,29 +147,24 @@ int TPCFastTransformHelperO2::updateCalibration(TPCFastTransform& fastTransform, const double vDrift = elParam.ZbinWidth * vDriftRef * vDriftFactor; // cm/timebin // fast transform formula: - // L = (t-t0)*(mVdrift + mVdriftCorrY*yLab ) + mLdriftCorr - // Z = Z(L) + tpcAlignmentZ + // L = (t-t0)*mVdrift + // Z = Z(L) // spline corrections for xyz - // Time-of-flight correction: ldrift += dist-to-vtx*tofCorr const double t0 = (driftTimeOffset + elParam.getAverageShapingTime()) / elParam.ZbinWidth; - const double vdCorrY = 0.; - const double ldCorr = 0.; - const double tofCorr = 0.; - const double primVtxZ = 0.; - - fastTransform.setCalibration(TimeStamp, t0, vDrift, vdCorrY, ldCorr, tofCorr, primVtxZ); + fastTransform.setCalibration(TimeStamp, t0, vDrift); return 0; } +#ifndef GPUCA_STANDALONE void TPCFastTransformHelperO2::testGeometry(const TPCFastTransformGeo& geo) const { const Mapper& mapper = Mapper::instance(); - if (geo.getNumberOfSlices() != Sector::MAXSECTOR) { - LOG(fatal) << "Wrong number of sectors :" << geo.getNumberOfSlices() << " instead of " << Sector::MAXSECTOR << std::endl; + if (geo.getNumberOfSectors() != Sector::MAXSECTOR) { + LOG(fatal) << "Wrong number of sectors :" << geo.getNumberOfSectors() << " instead of " << Sector::MAXSECTOR << std::endl; } if (geo.getNumberOfRows() != mapper.getNumberOfRows()) { @@ -217,15 +188,17 @@ void TPCFastTransformHelperO2::testGeometry(const TPCFastTransformGeo& geo) cons for (int pad = 0; pad < nPads; pad++) { const GlobalPadNumber p = mapper.globalPadNumber(PadPos(row, pad)); const PadCentre& c = mapper.padCentre(p); - double u = geo.convPadToU(row, pad); + + float y, z; + geo.convPadDriftLengthToLocal(0, row, pad, 0., y, z); const double dx = x - c.X(); - const double dy = u - (-c.Y()); // diferent sign convention for Y coordinate in the map + const double dy = y - (-c.Y()); // diferent sign convention for Y coordinate in the map if (fabs(dx) >= 1.e-6 || fabs(dy) >= 1.e-5) { LOG(warning) << "wrong calculated pad position:" << " row " << row << " pad " << pad << " x calc " << x << " x in map " << c.X() << " dx " << (x - c.X()) - << " y calc " << u << " y in map " << -c.Y() << " dy " << dy << std::endl; + << " y calc " << y << " y in map " << -c.Y() << " dy " << dy << std::endl; } if (fabs(maxDx) < fabs(dx)) { maxDx = dx; @@ -241,5 +214,10 @@ void TPCFastTransformHelperO2::testGeometry(const TPCFastTransformGeo& geo) cons << " max Dx " << maxDx << " max Dy " << maxDy << std::endl; } } +#endif + +template int TPCFastTransformHelperO2::updateCalibrationImpl(TPCFastTransform&, int64_t, float, float, float); +template int TPCFastTransformHelperO2::updateCalibrationImpl(TPCFastTransformPOD&, int64_t, float, float, float); + } // namespace tpc } // namespace o2 diff --git a/Detectors/TPC/reconstruction/test/testGPUCATracking.cxx b/Detectors/TPC/reconstruction/test/testGPUCATracking.cxx index 0debfa72dd7fa..20660473f4c37 100644 --- a/Detectors/TPC/reconstruction/test/testGPUCATracking.cxx +++ b/Detectors/TPC/reconstruction/test/testGPUCATracking.cxx @@ -24,8 +24,7 @@ #include "DataFormatsTPC/ClusterNativeHelper.h" #include "TPCReconstruction/TPCFastTransformHelperO2.h" -#include "CorrectionMapsHelper.h" -#include "TPCFastTransform.h" +#include "TPCFastTransformPOD.h" #include "GPUO2Interface.h" #include "GPUO2InterfaceUtils.h" #include "GPUO2InterfaceConfiguration.h" @@ -74,11 +73,11 @@ BOOST_AUTO_TEST_CASE(CATracking_test1) config.configWorkflow.inputs.set(gpudatatypes::InOutType::TPCClusters); config.configWorkflow.outputs.set(gpudatatypes::InOutType::TPCMergedTracks); - std::unique_ptr fastTransform(TPCFastTransformHelperO2::instance()->create(0)); - std::unique_ptr fastTransformHelper(new CorrectionMapsHelper()); - fastTransformHelper->setCorrMap(fastTransform.get()); - config.configCalib.fastTransform = fastTransform.get(); - config.configCalib.fastTransformHelper = fastTransformHelper.get(); + auto fastTransformTmp = TPCFastTransformHelperO2::instance()->create(0); + aligned_unique_buffer_ptr fastTransformBuf; + TPCFastTransformPOD::create(fastTransformBuf, *fastTransformTmp); + config.configCalib.fastTransform = fastTransformBuf.get(); + auto dEdxCalibContainer = GPUO2InterfaceUtils::getCalibdEdxContainerDefault(); config.configCalib.dEdxCalibContainer = dEdxCalibContainer.get(); std::unique_ptr gainCalib = GPUO2InterfaceUtils::getPadGainCalibDefault(); diff --git a/Detectors/TPC/reconstruction/test/testGPUGeometry.cxx b/Detectors/TPC/reconstruction/test/testGPUGeometry.cxx new file mode 100644 index 0000000000000..e7cfb25c56087 --- /dev/null +++ b/Detectors/TPC/reconstruction/test/testGPUGeometry.cxx @@ -0,0 +1,68 @@ +// 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 testGPUGeometry.cxx +/// \brief Compare GPUTPCGeometry.h to o2::tpc::Mapper +/// \author David Rohr + +#define BOOST_TEST_MODULE Test TPC GPUTPCGeometry +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include +#include "DataFormatsTPC/Constants.h" +#include "TPCBase/Mapper.h" +#include "TPCBase/PadRegionInfo.h" +#include "TPCBase/ParameterDetector.h" +#include "GPUTPCGeometry.h" + +using namespace o2::gpu; + +namespace o2 +{ +namespace tpc +{ +/// @brief Test 1 basic class IO tests +BOOST_AUTO_TEST_CASE(GPUTPCGeometry_test1) +{ + const Mapper& mapper = Mapper::instance(); + const GPUTPCGeometry geo; + const auto regions = mapper.getMapPadRegionInfo(); + + BOOST_CHECK_EQUAL(mapper.getNumberOfPadRegions(), geo.NRegions()); + BOOST_CHECK_EQUAL(mapper.NSECTORS, geo.NSECTORS); + BOOST_CHECK_EQUAL(mapper.PADROWS, geo.NROWS); + + auto& detParam = ParameterDetector::Instance(); + BOOST_CHECK_EQUAL(detParam.TPClength, geo.TPCLength()); + + /*for (unsigned int i = 0; i < mapper.NPARTITIONS; i++) { + BOOST_CHECK_EQUAL(???, geo.GetSectorFECOffset(i)); // TODO: Get value from mapper and compare! + }*/ + + for (unsigned int i = 0; i < mapper.getNumberOfPadRegions(); i++) { + BOOST_CHECK_EQUAL(mapper.ROWSPERREGION[i], geo.GetRegionRows(i)); + BOOST_CHECK_EQUAL(mapper.ROWOFFSET[i], geo.GetRegionStart(i)); + // BOOST_CHECK_EQUAL(???, geo.GetSampaMapping(i)); // TODO: Get value from mapper and compare! + // BOOST_CHECK_EQUAL(???, geo.GetChannelOffset(i)); // TODO: Get value from mapper and compare! + BOOST_CHECK_EQUAL(regions[i].getPadHeight(), geo.PadHeightByRegion(i)); + BOOST_CHECK_EQUAL(regions[i].getPadWidth(), geo.PadWidthByRegion(i)); + } + + for (unsigned int i = 0; i < mapper.PADROWS; i++) { + BOOST_CHECK_EQUAL(mapper.REGION[i], geo.GetRegion(i)); + unsigned int region = mapper.REGION[i]; + BOOST_CHECK_EQUAL(regions[region].getPadsInRowRegion(mapper.getLocalRowFromGlobalRow(i)), geo.NPads(i)); + const auto& pos = mapper.padCentre(mapper.getGlobalPadNumber(mapper.getLocalRowFromGlobalRow(i), 0, region)); + BOOST_CHECK_EQUAL(pos.x(), geo.Row2X(i)); + } +} +} // namespace tpc +} // namespace o2 diff --git a/Detectors/TPC/reconstruction/test/testTPCFastTransform.cxx b/Detectors/TPC/reconstruction/test/testTPCFastTransform.cxx index 5e37bd608c4a1..fee63e9e38bc2 100644 --- a/Detectors/TPC/reconstruction/test/testTPCFastTransform.cxx +++ b/Detectors/TPC/reconstruction/test/testTPCFastTransform.cxx @@ -53,7 +53,7 @@ BOOST_AUTO_TEST_CASE(FastTransform_test1) BOOST_CHECK_EQUAL(geo.test(), 0); - BOOST_CHECK_EQUAL(geo.getNumberOfSlices(), Sector::MAXSECTOR); + BOOST_CHECK_EQUAL(geo.getNumberOfSectors(), Sector::MAXSECTOR); BOOST_CHECK_EQUAL(geo.getNumberOfRows(), mapper.getNumberOfRows()); double maxDx = 0, maxDy = 0; @@ -71,15 +71,16 @@ BOOST_AUTO_TEST_CASE(FastTransform_test1) for (int pad = 0; pad < nPads; pad++) { const GlobalPadNumber p = mapper.globalPadNumber(PadPos(row, pad)); const PadCentre& c = mapper.padCentre(p); - float u = 0, v = 0; - fastTransform.convPadTimeToUV(0, row, pad, 0, u, v, 0.); - + float y = 0, z = 0; + int sector = 0; + float time = 0.; + fastTransform.convPadTimeToLocal(sector, row, pad, time, y, z, 0.); double dx = x - c.X(); - double dy = u - (-c.Y()); // diferent sign convention for Y coordinate in the map + double dy = y - (-c.Y()); // diferent sign convention for Y coordinate in the map BOOST_CHECK(fabs(dx) < 1.e-6); BOOST_CHECK(fabs(dy) < 1.e-5); if (fabs(dy) >= 1.e-5) { - std::cout << "row " << row << " pad " << pad << " y calc " << u << " y in map " << -c.Y() << " dy " << dy << std::endl; + std::cout << "row " << row << " pad " << pad << " y calc " << y << " y in map " << -c.Y() << " dy " << dy << std::endl; } if (fabs(maxDx) < fabs(dx)) { maxDx = dx; @@ -104,46 +105,46 @@ BOOST_AUTO_TEST_CASE(FastTransform_test_setSpaceChargeCorrection) std::unique_ptr fastTransform0(TPCFastTransformHelperO2::instance()->create(0)); const TPCFastTransformGeo& geo = fastTransform0->getGeometry(); - auto correctionUV = [&](int roc, int /*row*/, const double u, const double v, double& dX, double& dU, double& dV) { + auto correctionUV = [&](int sector, int /*row*/, const double u, const double v, double& dX, double& dU, double& dV) { // float lx = geo.getRowInfo(row).x; dX = 1. + 1 * u + 0.1 * u * u; dU = 2. + 0.2 * u + 0.002 * u * u; // + 0.001 * u * u * u; dV = 3. + 0.1 * v + 0.01 * v * v; //+ 0.0001 * v * v * v; }; - auto correctionLocal = [&](int roc, int row, double ly, double lz, + auto correctionLocal = [&](int sector, int row, double ly, double lz, double& dx, double& dly, double& dlz) { float u, v; - geo.convLocalToUV(roc, ly, lz, u, v); + geo.convLocalToUV(sector, ly, lz, u, v); double du, dv; - correctionUV(roc, row, u, v, dx, du, dv); + correctionUV(sector, row, u, v, dx, du, dv); float ly1, lz1; - geo.convUVtoLocal(roc, u + du, v + dv, ly1, lz1); + geo.convUVtoLocal(sector, u + du, v + dv, ly1, lz1); dly = ly1 - ly; dlz = lz1 - lz; }; - int nRocs = geo.getNumberOfSlices(); + int nSectors = geo.getNumberOfSectors(); int nRows = geo.getNumberOfRows(); TPCFastSpaceChargeCorrectionMap& scData = TPCFastTransformHelperO2::instance()->getCorrectionMap(); - scData.init(nRocs, nRows); + scData.init(nSectors, nRows); - for (int iRoc = 0; iRoc < nRocs; iRoc++) { + for (int iSector = 0; iSector < nSectors; iSector++) { for (int iRow = 0; iRow < nRows; iRow++) { double dsu = 1. / (3 * 8 - 3); double dsv = 1. / (3 * 20 - 3); for (double su = 0.f; su < 1.f + .5 * dsu; su += dsv) { for (double sv = 0.f; sv < 1.f + .5 * dsv; sv += dsv) { float ly = 0.f, lz = 0.f; - geo.convScaledUVtoLocal(iRoc, iRow, su, sv, ly, lz); + geo.convScaledUVtoLocal(iSector, iRow, su, sv, ly, lz); double dx, dy, dz; - correctionLocal(iRoc, iRow, ly, lz, dx, dy, dz); - scData.addCorrectionPoint(iRoc, iRow, + correctionLocal(iSector, iRow, ly, lz, dx, dy, dz); + scData.addCorrectionPoint(iSector, iRow, ly, lz, dx, dy, dz); } } } // row - } // slice + } // sector std::unique_ptr fastTransform(TPCFastTransformHelperO2::instance()->create(0)); @@ -158,12 +159,12 @@ BOOST_AUTO_TEST_CASE(FastTransform_test_setSpaceChargeCorrection) double statDiff = 0., statN = 0.; double statDiffFile = 0., statNFile = 0.; - for (int slice = 0; slice < geo.getNumberOfSlices(); slice += 1) { - //std::cout << "slice " << slice << " ... " << std::endl; + for (int sector = 0; sector < geo.getNumberOfSectors(); sector += 1) { + // std::cout << "sector " << sector << " ... " << std::endl; - const TPCFastTransformGeo::SliceInfo& sliceInfo = geo.getSliceInfo(slice); + const TPCFastTransformGeo::SectorInfo& sectorInfo = geo.getSectorInfo(sector); - float lastTimeBin = fastTransform->getMaxDriftTime(slice, 0.f); + float lastTimeBin = fastTransform->getMaxDriftTime(sector, 0.f); for (int row = 0; row < geo.getNumberOfRows(); row++) { @@ -172,31 +173,31 @@ BOOST_AUTO_TEST_CASE(FastTransform_test_setSpaceChargeCorrection) for (int pad = 0; pad < nPads; pad += 10) { for (float time = 0; time < lastTimeBin; time += 30) { - //std::cout<<"slice "<setApplyCorrectionOff(); float x0, y0, z0; - fastTransform->Transform(slice, row, pad, time, x0, y0, z0); + fastTransform->Transform(sector, row, pad, time, x0, y0, z0); - BOOST_CHECK_EQUAL(geo.test(slice, row, y0, z0), 0); + BOOST_CHECK_EQUAL(geo.test(sector, row, y0, z0), 0); fastTransform->setApplyCorrectionOn(); float x1, y1, z1; - fastTransform->Transform(slice, row, pad, time, x1, y1, z1); + fastTransform->Transform(sector, row, pad, time, x1, y1, z1); // local to UV float u0, v0, u1, v1; - geo.convLocalToUV(slice, y0, z0, u0, v0); - geo.convLocalToUV(slice, y1, z1, u1, v1); + geo.convLocalToUV(sector, y0, z0, u0, v0); + geo.convLocalToUV(sector, y1, z1, u1, v1); double dx, du, dv; - correctionUV(slice, row, u0, v0, dx, du, dv); + correctionUV(sector, row, u0, v0, dx, du, dv); statDiff += fabs((x1 - x0) - dx) + fabs((u1 - u0) - du) + fabs((v1 - v0) - dv); statN += 3; - //std::cout << (x1 - x0) - dx << " " << (u1 - u0) - du << " " << (v1 - v0) - dv << std::endl; //": v0 " << v0 <<" z0 "<Transform(slice, row, pad, time, x1f, y1f, z1f); + fromFile->Transform(sector, row, pad, time, x1f, y1f, z1f); statDiffFile += fabs(x1f - x1) + fabs(y1f - y1) + fabs(z1f - z1); statNFile += 3; } diff --git a/Detectors/TPC/workflow/CMakeLists.txt b/Detectors/TPC/workflow/CMakeLists.txt index 6930f332bfbf1..f64a223f683d8 100644 --- a/Detectors/TPC/workflow/CMakeLists.txt +++ b/Detectors/TPC/workflow/CMakeLists.txt @@ -25,6 +25,7 @@ o2_add_library(TPCWorkflow src/KryptonRawFilterSpec.cxx src/OccupancyFilterSpec.cxx src/SACProcessorSpec.cxx + src/CMVToVectorSpec.cxx src/IDCToVectorSpec.cxx src/CalibdEdxSpec.cxx src/CalibratordEdxSpec.cxx @@ -288,4 +289,29 @@ o2_add_executable(pressure-temperature SOURCES src/tpc-pressure-temperature.cxx PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) +o2_add_executable(cmv-to-vector + COMPONENT_NAME tpc + SOURCES src/tpc-cmv-to-vector.cxx + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + +o2_add_executable(cmv-flp + COMPONENT_NAME tpc + SOURCES src/tpc-flp-cmv.cxx + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + +o2_add_executable(cmv-distribute + COMPONENT_NAME tpc + SOURCES src/tpc-distribute-cmv.cxx + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + +o2_add_executable(cmv-aggregate + COMPONENT_NAME tpc + SOURCES src/tpc-aggregate-cmv.cxx + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + +o2_add_executable(cmv-trigger + COMPONENT_NAME tpc + SOURCES test/test_cmv-trigger.cxx + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + add_subdirectory(readers) diff --git a/Detectors/TPC/workflow/README.md b/Detectors/TPC/workflow/README.md index e34faa2813edf..5d2ccd3ac9166 100644 --- a/Detectors/TPC/workflow/README.md +++ b/Detectors/TPC/workflow/README.md @@ -274,3 +274,216 @@ To directly dump the digits to file for inspection use for the reco workflow ```bash | o2-tpc-reco-workflow --input-type digitizer --output-type digits --disable-mc ``` + +## TPC Common Mode Value (CMV) Workflows + +The CMV workflows parse raw TPC data, buffer Common Mode Values per CRU on FLPs, then merge and aggregate them on a calibration node before serializing the CMVContainer in a TTree. The resulting object can be uploaded to the CCDB or written to the disk. + +### Workflow components + +| Executable | Output | Description | +|---|---|---| +| `o2-tpc-cmv-to-vector` | `TPC/CMVVECTOR` | Parses raw TPC data and creates vectors of CMVs per CRU | +| `o2-tpc-cmv-flp` | `TPC/CMVGROUP` | Buffers N TFs per CRU on the FLP and groups them for forwarding | +| `o2-tpc-cmv-distribute` | `TPC/CMVAGG*` | Routes grouped CMV batches from the calibration node to the aggregate workflow while preserving buffered TF and lane handling | +| `o2-tpc-cmv-aggregate` | TTree / CCDB payload | Collects all CRUs for each aggregate lane, preprocesses and compresses CMVs per buffered TF slice, then writes the CMVContainer TTree to disk (`--output-dir`) and/or forwards it as a CCDB object (`--enable-CCDB-output`) | + +#### `o2-tpc-cmv-to-vector` + +| Option | Default | Description | +|---|---|---| +| `--input-spec` | `A:TPC/RAWDATA` | DPL input spec for raw TPC data | +| `--crus` | `0-359` | CRU range to process, comma-separated ranges | +| `--write-debug` | false | Write a debug output tree every TF | +| `--write-debug-on-error` | false | Write a debug output tree only when decoding errors occur | +| `--debug-file-name` | `/tmp/cmv_vector_debug.{run}.root` | Name of the debug output ROOT file | +| `--write-raw-data-on-error` | false | Dump raw data to file when decoding errors occur | +| `--raw-file-name` | `/tmp/cmv_debug.{run}.{raw_type}` | Name of the raw debug output file | +| `--raw-data-type` | 0 | Raw data format to dump on error: 0 = full TPC with DPL header, 1 = full TPC with DPL header (skip empty), 2 = full TPC no DPL header, 3 = full TPC no DPL header (skip empty), 4 = IDC raw only, 5 = CMV raw only | +| `--check-incomplete-hbf` | false | Check and report incomplete HBFs in the raw parser | + +#### `o2-tpc-cmv-flp` + +| Option | Default | Description | +|---|---|---| +| `--crus` | `0-359` | CRU range handled by this FLP | +| `--lanes` | hw_concurrency/2 | Parallel processing lanes (CRUs split per lane) | +| `--time-lanes` | 1 | Parallel lanes for time-frame splitting | +| `--n-TFs-buffer` | 1 | Number of TFs to buffer before forwarding | +| `--dump-cmvs-flp` | false | Dump raw CMV vectors per CRU to a ROOT file each TF (for debugging) | + +#### `o2-tpc-cmv-distribute` + +| Option | Default | Description | +|---|---|---| +| `--crus` | `0-359` | CRU range expected from upstream | +| `--timeframes` | 2000 | Number of TFs aggregated per calibration interval | +| `--firstTF` | -1 | First time frame index; -1 = auto-detect from first incoming TF; values < -1 set an offset of `\|firstTF\|+1` TFs before the first interval begins | +| `--lanes` | 1 | Number of parallel lanes (CRUs are split evenly across lanes) | +| `--output-lanes` | 1 | Number of aggregate pipelines downstream; these lanes rotate whole CMV aggregation intervals, not CRU subsets | +| `--n-TFs-buffer` | 1 | Number of TFs buffered per group in the upstream `o2-tpc-cmv-flp` (must match that workflow's setting) | +| `--send-precise-timestamp` | false | Forward orbit-reset timing information needed by the aggregate workflow for precise CCDB validity timestamps | +| `--drop-data-after-nTFs` | 0 | Drop data for a relative TF slot after this many TFs have passed without receiving all CRUs; 0 uses the default derived from `--check-data-every-n` | +| `--check-data-every-n` | 0 | Check for missing CRU data every N invocations of the run function; -1 disables checking, 0 uses the default (timeframes/2) | +| `--nFactorTFs` | 1000 | Number of TFs to skip before flushing the oldest incomplete aggregation interval | + +#### `o2-tpc-cmv-aggregate` + +> **Important:** `--n-TFs-buffer` must be set to the same value as in `o2-tpc-cmv-distribute` and `o2-tpc-cmv-flp`. Mismatched values will silently corrupt the relTF mapping and TTree entry count. + +| Option | Default | Description | +|---|---|---| +| `--crus` | `0-359` | Full CRU range expected for each aggregate interval | +| `--timeframes` | 2000 | Number of TFs aggregated per calibration interval | +| `--input-lanes` | 1 | Number of aggregate pipelines; must match `o2-tpc-cmv-distribute --output-lanes` | +| `--n-TFs-buffer` | 1 | Number of real TFs packed into one CMV batch from upstream; **must match** `o2-tpc-cmv-distribute --n-TFs-buffer` | +| `--enable-CCDB-output` | false | Forward the CMVContainer TTree as a CCDB object to `o2-calibration-ccdb-populator-workflow` | +| `--use-precise-timestamp` | false | Use orbit-reset timing forwarded by the distribute lane (requires `o2-tpc-cmv-distribute --send-precise-timestamp`) for precise CCDB validity start timestamps | +| `--output-dir` | `none` | Output directory for writing the CMVContainer ROOT file; must exist | +| `--nthreads` | 1 | Number of threads used for CMV preprocessing and compression; each thread processes a contiguous slice of buffered TFs | +| `--use-sparse` | false | Sparse encoding: skip zero time bins (raw uint16 values; combine with `--use-compression-varint` or `--use-compression-huffman` for compressed sparse output) | +| `--use-compression-varint` | false | Delta + zigzag + varint compression over all values; combined with `--use-sparse`: varint-encoded exact values at non-zero positions | +| `--use-compression-huffman` | false | Huffman encoding over all values; combined with `--use-sparse`: Huffman-encoded exact values at non-zero positions | +| `--cmv-zero-threshold` | 0 | Zero out CMV values whose magnitude is below this threshold (ADC) after optional rounding and before compression; 0 disables | +| `--cmv-round-integers-threshold` | 0 | Round values to nearest integer ADC for \|v\| ≤ N ADC before compression; 0 disables | +| `--cmv-dynamic-precision-mean` | 1.0 | Gaussian centre in \|CMV\| (ADC) where the strongest fractional-bit trimming is applied | +| `--cmv-dynamic-precision-sigma` | 0 | Gaussian width (ADC) for smooth CMV fractional-bit trimming; 0 disables | + +### Example 1 — Simple usage for testing + +```bash +#!/bin/bash + +hash="test" +MAX_TFS=1 +CRUS="0-359" + +ARGS_ALL="-b --session ${USER}.${hash} --shm-segment-size $((8<<30))" + +o2-raw-tf-reader-workflow $ARGS_ALL \ + --input-data tf.subset.list \ + --max-tf ${MAX_TFS} | +o2-tfidinfo-writer-workflow $ARGS_ALL \ + --early-forward-policy noraw \ + --fairmq-rate-logging 0 \ + --timeframes-rate-limit ${MAX_TFS} \ + --timeframes-rate-limit-ipcid 583693664 | +o2-tpc-cmv-to-vector $ARGS_ALL \ + --input-spec "A:TPC/RAWDATA" \ + --write-debug-on-error \ + --crus ${CRUS} | +o2-tpc-cmv-flp $ARGS_ALL \ + --crus ${CRUS} | +o2-tpc-cmv-distribute $ARGS_ALL \ + --crus ${CRUS} \ + --output-lanes 1 \ + --send-precise-timestamp \ +| +o2-tpc-cmv-aggregate $ARGS_ALL \ + --crus ${CRUS} \ + --output-dir ./ \ + --enable-CCDB-output \ + --cmv-zero-threshold 1.0 \ + --cmv-dynamic-precision-mean 1.0 \ + --cmv-dynamic-precision-sigma 8.0 \ + --use-sparse \ + --use-compression-huffman | +o2-calibration-ccdb-populator-workflow $ARGS_ALL \ + --ccdb-path ccdb-test.cern.ch:8080 +``` + +### Example 2 — Bash scripts for more realistic testing + +In a real online setup, multiple FLPs each process their own CRU subset and forward compressed CMV groups to a central aggregator node via ZeroMQ. + +**FLP side (`Send.sh`)** — run one instance per FLP (pass `N_FLPs` as first argument): + +```bash +#!/bin/bash + +# Number of FLPs (passed as first argument, default 1) +N_FLPs=${1:-1} + +hash="test" +MAX_TFS=1 + +minCRU=0 +maxCRU=360 + +ARGS_ALL="-b --shm-segment-size $((8<<30))" + +for ((i = 0; i < ${N_FLPs}; i++)); do + xpos_start=100 + xpos=$((xpos_start + 1000 * $i)) + + let diff=${maxCRU}-${minCRU} + let Start=${minCRU}+$i*${diff}/${N_FLPs} + let End=$Start+${diff}/${N_FLPs}-1 + + crus="$Start-$End" + echo "FLP $i: crus $crus" + + xterm -hold -geometry 150x41+$xpos+300 -e bash -c "unset PYTHONHOME PYTHONPATH; echo FLP $i; + o2-raw-tf-reader-workflow $ARGS_ALL \ + --session ${USER}.${hash}.send.$i \ + --input-data tf.subset.list \ + --max-tf ${MAX_TFS} | + o2-tfidinfo-writer-workflow $ARGS_ALL \ + --session ${USER}.${hash}.send.$i \ + --early-forward-policy noraw \ + --fairmq-rate-logging 0 \ + --timeframes-rate-limit ${MAX_TFS} \ + --timeframes-rate-limit-ipcid $((583693664 + $i)) | + o2-tpc-cmv-to-vector $ARGS_ALL \ + --session ${USER}.${hash}.send.$i \ + --input-spec 'A:TPC/RAWDATA' \ + --write-debug-on-error \ + --crus ${crus} | + o2-tpc-cmv-flp $ARGS_ALL \ + --session ${USER}.${hash}.send.$i \ + --crus ${crus} | + o2-dpl-output-proxy $ARGS_ALL \ + --session ${USER}.${hash}.send.$i \ + --sporadic-inputs \ + --channel-config 'name=downstream,method=connect,address=tcp://localhost:30453,type=push,transport=zeromq' \ + --dataspec 'downstream:TPC/CMVGROUP;downstream:TPC/CMVORBITINFO'; exec bash" & +done +``` + +Each FLP connects to the aggregator's pull socket on port `30453` and pushes `TPC/CMVGROUP` and `TPC/CMVORBITINFO` messages. The CRU range is automatically split evenly across `N_FLPs`. + +**Aggregator side (`Receive.sh`)**: + +```bash +#!/bin/bash + +hash="test" +CRUS="0-359" + +ARGS_ALL="-b --session ${USER}.${hash}.receive --shm-segment-size $((8<<30))" + +# ZeroMQ proxy: pull from all FLPs connecting on port 30453 +configProxy="name=readout-proxy,type=pull,method=bind,address=tcp://localhost:30453,rateLogging=1,transport=zeromq" + +o2-dpl-raw-proxy $ARGS_ALL \ + --channel-config "${configProxy}" \ + --dataspec "A:TPC/CMVGROUP;A:TPC/CMVORBITINFO" | +o2-tpc-cmv-distribute $ARGS_ALL \ + --crus ${CRUS} \ + --output-lanes 1 \ + --send-precise-timestamp \ +| +o2-tpc-cmv-aggregate $ARGS_ALL \ + --crus ${CRUS} \ + --output-dir ./ \ + --enable-CCDB-output \ + --cmv-zero-threshold 1.0 \ + --cmv-dynamic-precision-mean 1.0 \ + --cmv-dynamic-precision-sigma 8.0 \ + --use-sparse \ + --use-compression-huffman | +o2-calibration-ccdb-populator-workflow $ARGS_ALL \ + --ccdb-path ccdb-test.cern.ch:8080 +``` + +The aggregator binds the ZeroMQ pull socket and waits for all FLPs to connect. Once `TPC/CMVGROUP` and `TPC/CMVORBITINFO` data arrive, `o2-tpc-cmv-distribute` routes the grouped CMV batches, and `o2-tpc-cmv-aggregate` gathers the full CRU set for each interval, applies preprocessing and compression, writes the object to disk, and uploads it to the CCDB. diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/CMVToVectorSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/CMVToVectorSpec.h new file mode 100644 index 0000000000000..2f9209ee07da8 --- /dev/null +++ b/Detectors/TPC/workflow/include/TPCWorkflow/CMVToVectorSpec.h @@ -0,0 +1,30 @@ +// 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 CMVToVectorSpec.h +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief Processor to convert CMVs to a vector in a CRU + +#ifndef TPC_CMVToVectorSpec_H_ +#define TPC_CMVToVectorSpec_H_ + +#include "Framework/DataProcessorSpec.h" + +namespace o2::tpc +{ + +/// create a processor spec +/// convert CMV raw values to a vector in a CRU +o2::framework::DataProcessorSpec getCMVToVectorSpec(std::string const& inputSpec, std::vector const& crus); + +} // end namespace o2::tpc + +#endif // TPC_CMVToVectorSpec_H_ diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/RecoWorkflow.h b/Detectors/TPC/workflow/include/TPCWorkflow/RecoWorkflow.h index 8e8a6a96eed63..3526e9622b83c 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/RecoWorkflow.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/RecoWorkflow.h @@ -30,7 +30,7 @@ struct InputSpec; } namespace tpc { -struct CorrectionMapsLoaderGloOpts; +struct CorrectionMapsGloOpts; namespace reco_workflow { @@ -73,16 +73,16 @@ enum struct OutputType { Digits, using CompletionPolicyData = std::vector; /// create the workflow for TPC reconstruction -framework::WorkflowSpec getWorkflow(CompletionPolicyData* policyData, // - std::vector const& tpcSectors, // - unsigned long tpcSectorMask, // - std::vector const& laneConfiguration, // - const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, // - bool propagateMC = true, unsigned nLanes = 1, // - std::string const& cfgInput = "digitizer", // - std::string const& cfgOutput = "tracks", // - bool disableRootInput = false, // - int caClusterer = 0, // +framework::WorkflowSpec getWorkflow(CompletionPolicyData* policyData, // + std::vector const& tpcSectors, // + unsigned long tpcSectorMask, // + std::vector const& laneConfiguration, // + const o2::tpc::CorrectionMapsGloOpts& sclOpts, // + bool propagateMC = true, unsigned nLanes = 1, // + std::string const& cfgInput = "digitizer", // + std::string const& cfgOutput = "tracks", // + bool disableRootInput = false, // + int caClusterer = 0, // int zsOnTheFly = 0, bool askDISTSTF = true, const std::string& ctfdictOpt = "none", diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h new file mode 100644 index 0000000000000..3383da527cccf --- /dev/null +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCAggregateCMVSpec.h @@ -0,0 +1,717 @@ +// 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 TPCAggregateCMVSpec.h +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief TPC aggregation of distributed CMVs, including preprocessing, compression and CCDB output + +#ifndef O2_TPCAGGREGATECMVSPEC_H +#define O2_TPCAGGREGATECMVSPEC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "TMemFile.h" +#include "TParameter.h" +#include "Framework/Task.h" +#include "Framework/ControlService.h" +#include "Framework/Logger.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/DataTakingContext.h" +#include "Framework/DataRefUtils.h" +#include "Headers/DataHeader.h" +#include "Framework/ConfigParamRegistry.h" +#include "CommonDataFormat/Pair.h" +#include "CCDB/CcdbApi.h" +#include "CCDB/CcdbObjectInfo.h" +#include "DetectorsCalibration/Utils.h" +#include "TPCWorkflow/TPCDistributeCMVSpec.h" +#include "TPCWorkflow/ProcessingHelpers.h" +#include "TPCCalibration/CMVContainer.h" +#include "DataFormatsTPC/CMV.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include "MemoryResources/MemoryResources.h" +#include "CommonUtils/StringUtils.h" +#include "DetectorsCommonDataFormats/FileMetaData.h" + +namespace o2::tpc +{ + +class TPCAggregateCMVDevice : public o2::framework::Task +{ + public: + TPCAggregateCMVDevice(const int lane, + const std::vector& crus, + const unsigned int timeframes, + const bool sendCCDB, + const bool usePreciseTimestamp, + const int nTFsBuffer, + std::shared_ptr req) + : mLaneId{lane}, + mCRUs{crus}, + mTimeFrames{timeframes}, + mSendCCDB{sendCCDB}, + mUsePreciseTimestamp{usePreciseTimestamp}, + mNTFsBuffer{nTFsBuffer}, + mProcessedCRU(timeframes), + mProcessedCRUs(timeframes), + mRawCMVs(timeframes), + mOrbitInfo(timeframes), + mOrbitStep(timeframes), + mOrbitInfoSeen(timeframes, false), + mTFCompleted(timeframes, false), + mCCDBRequest(req) + { + std::sort(mCRUs.begin(), mCRUs.end()); + for (auto& crusMap : mProcessedCRUs) { + crusMap.reserve(mCRUs.size()); + for (const auto cruID : mCRUs) { + crusMap.emplace(cruID, false); + } + } + initIntervalTree(); + } + + void init(o2::framework::InitContext& ic) final + { + o2::base::GRPGeomHelper::instance().setRequest(mCCDBRequest); + mOutputDir = ic.options().get("output-dir"); + if (mOutputDir != "/dev/null") { + mOutputDir = o2::utils::Str::rectifyDirectory(mOutputDir); + } + mMetaFileDir = ic.options().get("meta-output-dir"); + if (mMetaFileDir != "/dev/null") { + mMetaFileDir = o2::utils::Str::rectifyDirectory(mMetaFileDir); + } + mUseCompressionVarint = ic.options().get("use-compression-varint"); + mUseSparse = ic.options().get("use-sparse"); + mUseCompressionHuffman = ic.options().get("use-compression-huffman"); + mRoundIntegersThreshold = static_cast(ic.options().get("cmv-round-integers-threshold")); + mZeroThreshold = ic.options().get("cmv-zero-threshold"); + mDynamicPrecisionMean = ic.options().get("cmv-dynamic-precision-mean"); + mDynamicPrecisionSigma = ic.options().get("cmv-dynamic-precision-sigma"); + mThreads = std::max(1, ic.options().get("nthreads-compression")); + LOGP(info, "CMV aggregation settings: output-dir={}, use-compression-varint={}, use-sparse={}, use-compression-huffman={}, cmv-round-integers-threshold={}, cmv-zero-threshold={}, cmv-dynamic-precision-mean={}, cmv-dynamic-precision-sigma={}, nthreads-compression={}", + mOutputDir, mUseCompressionVarint, mUseSparse, mUseCompressionHuffman, mRoundIntegersThreshold, mZeroThreshold, mDynamicPrecisionMean, mDynamicPrecisionSigma, mThreads); + initIntervalTree(); + } + + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) final + { + o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj); + } + + void run(o2::framework::ProcessingContext& pc) final + { + // Consume CCDB inputs; return early when they are the only valid inputs in this slot + int nCCDBInputs = 0; + if (pc.inputs().isValid("grpecs")) { + pc.inputs().get("grpecs"); + ++nCCDBInputs; + } + if (mUsePreciseTimestamp && pc.inputs().isValid("orbitreset")) { + mTFInfo = pc.inputs().get>("orbitreset"); + ++nCCDBInputs; + } + if (nCCDBInputs > 0 && pc.inputs().countValidInputs() == nCCDBInputs) { + return; + } + + if (mSetDataTakingCont) { + mDataTakingContext = pc.services().get(); + mSetDataTakingCont = false; + } + + if (!mRun) { + mRun = processing_helpers::getRunNumber(pc); + } + + const auto currTF = processing_helpers::getCurrentTF(pc); + + if (mTFFirst == -1) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFirstTFFilter)) { + mTFFirst = pc.inputs().get(ref); + mIntervalFirstTF = mTFFirst; + mHasIntervalFirstTF = true; + break; + } + } + + // EOS sentinel forwarded by the distribute lane for partial batches (n-TFs-buffer > actual TFs delivered) + if (currTF == std::numeric_limits::max()) { + if (mTimestampStart == 0) { + mTimestampStart = pc.services().get().creation; + } + collectEOSInputs(pc); + return; + } + + if (mTFFirst == -1) { + mTFFirst = currTF; + mIntervalFirstTF = mTFFirst; + mHasIntervalFirstTF = true; + LOGP(warning, "firstTF not found. Setting {} as first TF for aggregate lane {}", mTFFirst, mLaneId); + } + + const long relTF = (currTF - mTFFirst) / mNTFsBuffer; + if (relTF < 0) { + LOGP(warning, "relTF={} < 0 for TF {}, skipping", relTF, currTF); + return; + } + if (relTF >= static_cast(mTimeFrames)) { + // The distribute has advanced past this interval (empty CRU placeholders sent by checkMissingData + // arrive with the triggering TF's context, not the missing batch's context). + // Force-complete whatever was buffered so the next TF starts a fresh interval. + LOGP(warning, "relTF={} out of range [0, {}) for TF {}: force-completing stale interval and resetting", relTF, mTimeFrames, currTF); + if (mTimestampStart == 0) { + mTimestampStart = static_cast(pc.services().get().creation); + } + materializeBufferedTFs(true); + sendOutput(pc.outputs()); + // Advance mTFFirst to the interval containing currTF so that after reset() clears it to -1 + // we can restore a valid value. Without this, the distribute won't resend CMVFIRSTTF (it was + // already sent for the current interval), causing "firstTF not found" and further bad relTFs. + long nextFirst = mIntervalFirstTF + static_cast(mTimeFrames) * mNTFsBuffer; + while (static_cast(currTF) >= nextFirst + static_cast(mTimeFrames) * mNTFsBuffer) { + nextFirst += static_cast(mTimeFrames) * mNTFsBuffer; + } + reset(); + mTFFirst = nextFirst; + mIntervalFirstTF = nextFirst; + mHasIntervalFirstTF = true; + return; + } + + // Capture orbit info first so setTimestampCCDB can use the measured stride + if (!mOrbitInfoSeen[relTF]) { + // all CRUs within a batch carry identical timing, so the first one is sufficient + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mOrbitFilter)) { + mOrbitInfo[relTF] = pc.inputs().get(ref); + const auto batchFirstOrbit = static_cast(mOrbitInfo[relTF] >> 32); + // TimingInfo.firstTForbit is the orbit of the last real TF in the batch (the TF that triggered the FLP to send). + // The FLP provides the orbit of the first real TF. Interpolating between the two gives the true stride, + // independent of the GRPECS/config nHBFPerTF value. + const auto batchLastOrbit = static_cast(pc.services().get().firstTForbit); + const auto defaultOrbitStep = static_cast(o2::base::GRPGeomHelper::instance().getNHBFPerTF()); + mOrbitStep[relTF] = ((batchFirstOrbit > 0) && (mNTFsBuffer > 1) && (batchLastOrbit > batchFirstOrbit)) ? (batchLastOrbit - batchFirstOrbit) / static_cast(mNTFsBuffer - 1) : defaultOrbitStep; + mLastOrbitStep = mOrbitStep[relTF]; + mOrbitInfoSeen[relTF] = true; + break; + } + } + + if (mTimestampStart == 0) { + setTimestampCCDB(relTF, mOrbitStep[relTF], pc); + } + + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const unsigned int cru = hdr->subSpecification; + if (!(std::binary_search(mCRUs.begin(), mCRUs.end(), cru))) { + LOGP(debug, "Received CMV data from CRU {} which is not part of this aggregate lane", cru); + continue; + } + if (mProcessedCRUs[relTF][cru]) { + continue; + } + + auto cmvVec = pc.inputs().get>(ref); + mRawCMVs[relTF][cru] = std::vector(cmvVec.begin(), cmvVec.end()); + mProcessedCRUs[relTF][cru] = true; + ++mProcessedCRU[relTF]; + } + + if (mProcessedCRU[relTF] == mCRUs.size() && !mTFCompleted[relTF]) { + mTFCompleted[relTF] = true; + ++mProcessedTFs; + mLastSeenTF = currTF; + } + + if (mProcessedTFs == mTimeFrames) { + materializeBufferedTFs(false); + sendOutput(pc.outputs()); + reset(); + } + } + + void endOfStream(o2::framework::EndOfStreamContext& ec) final + { + materializeBufferedTFs(true); + materializeEOSBuffer(); + sendOutput(ec.outputs()); + ec.services().get().readyToQuit(o2::framework::QuitRequest::Me); + } + + static constexpr header::DataDescription getDataDescriptionCCDBCMV() { return header::DataDescription{"TPC_CMV"}; } + + private: + struct PreparedTF { + CMVPerTF tf{}; + CMVPerTFCompressed compressed{}; + }; + + const int mLaneId{0}; ///< aggregate lane index (matches the distribute output lane) + std::vector mCRUs{}; ///< CRUs expected on this lane (sorted for binary_search) + const unsigned int mTimeFrames{}; ///< number of CMV batches per calibration interval (= total TFs / nTFsBuffer) + const bool mSendCCDB{false}; ///< send serialised TTree to the CCDB populator + const bool mUsePreciseTimestamp{false}; ///< use orbit-reset info forwarded by the distribute lane for precise CCDB timestamps + const int mNTFsBuffer{1}; ///< number of real TFs packed into one CMV batch (must match TPCFLPCMVSpec) + std::string mOutputDir{}; ///< directory to write local ROOT files ("/dev/null" to disable) + std::string mMetaFileDir{}; ///< directory to write calibration metadata files ("/dev/null" to disable) + o2::framework::DataTakingContext mDataTakingContext{}; + bool mSetDataTakingCont{true}; ///< flag to capture DataTakingContext only once + bool mUseCompressionVarint{false}; ///< delta+zigzag+varint compression for all values (dense path); combined with mUseSparse → sparse+varint + bool mUseSparse{false}; ///< sparse encoding (skip zero time bins); alone = raw uint16; combined with varint/Huffman → sparse+compressed + bool mUseCompressionHuffman{false}; ///< Huffman encoding; combined with mUseSparse → sparse+Huffman + uint16_t mRoundIntegersThreshold{0}; ///< round values to nearest integer ADC for |v| <= N ADC before compression; 0 = disabled + float mZeroThreshold{0.f}; ///< zero out CMV values whose float magnitude is below this threshold; 0 = disabled + float mDynamicPrecisionMean{1.f}; ///< Gaussian centre in |CMV| ADC where the strongest fractional-bit trimming is applied + float mDynamicPrecisionSigma{0.f}; ///< Gaussian width in ADC for fractional-bit trimming; 0 disables + int mThreads{1}; ///< number of threads for CMV preprocessing and compression in appendBatchToTree() + long mTFFirst{-1}; ///< absolute TF index of the first real TF in the current interval (-1 = not yet received) + long mTimestampStart{0}; ///< CCDB validity start timestamp in ms (0 until set by setTimestampCCDB) + long mIntervalFirstTF{0}; ///< absolute TF counter stored in the TTree UserInfo as "firstTF" + bool mHasIntervalFirstTF{false}; ///< true once mIntervalFirstTF has been set for the current interval + unsigned int mProcessedTFs{0}; ///< number of completed CMV batches in the current interval + std::vector mProcessedCRU{}; ///< counter of received CRUs per relTF slot; triggers completion when it reaches mCRUs.size() + std::vector> mProcessedCRUs{}; ///< per-CRU received flag per relTF ([relTF][CRU]); prevents double-counting on retransmission + std::vector>> mRawCMVs{}; ///< buffered raw CMV data per (relTF, CRU); unpacked in appendBatchToTree() + std::vector mOrbitInfo{}; ///< packed (firstOrbit << 32 | firstBC) per relTF, forwarded by the distribute lane + std::vector mOrbitStep{}; ///< per-sub-TF orbit stride per relTF; derived from actual batch timing + std::vector mOrbitInfoSeen{}; ///< true once orbit/BC has been captured for each relTF slot + std::vector mTFCompleted{}; ///< true once all CRUs have been received for a given relTF slot + std::unordered_map> mEOSRawCMVs{}; ///< CMV data received during the EOS sentinel path (partial batch at end of run) + uint32_t mEOSFirstOrbit{0}; ///< firstOrbit captured from the FLP's EOS partial-buffer flush + uint16_t mEOSFirstBC{0}; ///< firstBC captured from the FLP's EOS partial-buffer flush + uint32_t mLastOrbitStep{0}; ///< cached orbit stride from the last complete batch; fallback for the EOS partial batch + uint32_t mLastSeenTF{0}; ///< last TF counter seen in run(); used to compute lastTF metadata in the TTree + unsigned int mIntervalTFCount{0}; ///< number of TTree entries filled for the current interval + uint64_t mRun{0}; ///< run number, captured once per run + uint32_t mIntervalFirstOrbit{0}; ///< first orbit of the first TF in the current interval + uint32_t mIntervalLastOrbit{0}; ///< first orbit of the last TF in the current interval + uint32_t mFirstOrbitDPL{0}; ///< first orbit of the first TF in the current interval + bool mIntervalOrbitSet{false}; ///< true once first orbit has been captured for the current interval + dataformats::Pair mTFInfo{}; ///< orbit-reset time (ms) and NHBFPerTF forwarded by distribute lane 0 for precise timestamps + std::shared_ptr mCCDBRequest; ///< GRPECS request so GRPGeomHelper::getNHBFPerTF() is valid in this process + std::unique_ptr mIntervalTree{}; ///< in-memory TTree accumulating one entry per real TF; serialised to CCDB/disk at interval end + CMVPerTF mCurrentTF{}; ///< staging object written to the TTree branch for the uncompressed path + CMVPerTFCompressed mCurrentCompressedTF{}; ///< staging object written to the TTree branch when any compression flags are set + const std::vector mFilter{ + {"cmvagg", + o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(mLaneId)}, + o2::framework::Lifetime::Sporadic}}; + const std::vector mOrbitFilter{ + {"cmvorbit", + o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(mLaneId), header::DataHeader::SubSpecificationType{static_cast(mLaneId)}}, + o2::framework::Lifetime::Sporadic}}; + const std::vector mFirstTFFilter{ + {"firstTF", + o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{static_cast(mLaneId)}}, + o2::framework::Lifetime::Sporadic}}; + + uint8_t buildCompressionFlags() const + { + uint8_t flags = CMVEncoding::kNone; + if (mUseSparse) { + flags |= CMVEncoding::kSparse; + } + if (mUseCompressionHuffman) { + flags |= CMVEncoding::kDelta | CMVEncoding::kZigzag | CMVEncoding::kHuffman; + } else if (mUseCompressionVarint) { + flags |= CMVEncoding::kDelta | CMVEncoding::kZigzag | CMVEncoding::kVarint; + } + return flags; + } + + /// Create a fresh in-memory TTree for the next aggregation interval + /// Uses a single CMVPerTFCompressed branch whenever any compression is active or a raw CMVPerTF branch when no compression flags are set. + void initIntervalTree() + { + mIntervalTree = std::make_unique("ccdb_object", "ccdb_object"); + mIntervalTree->SetAutoSave(0); + mIntervalTree->SetDirectory(nullptr); + if (buildCompressionFlags() != CMVEncoding::kNone) { + mIntervalTree->Branch("CMVPerTFCompressed", &mCurrentCompressedTF); + } else { + mIntervalTree->Branch("CMVPerTF", &mCurrentTF); + } + } + + /// Accumulate CMV data from the EOS sentinel (TF == UINT32_MAX), i.e. a partial batch forwarded by the distribute lane when n-TFs-buffer > number of TFs actually delivered + /// Orbit/BC is captured once; raw data is appended per CRU into mEOSRawCMVs + void collectEOSInputs(o2::framework::ProcessingContext& pc) + { + if (mEOSFirstOrbit == 0) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mOrbitFilter)) { + const auto orbitBC = pc.inputs().get(ref); + mEOSFirstOrbit = static_cast(orbitBC >> 32); + mEOSFirstBC = static_cast(orbitBC & 0xFFFFu); + break; + } + } + + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const unsigned int cru = hdr->subSpecification; + if (!(std::binary_search(mCRUs.begin(), mCRUs.end(), cru))) { + continue; + } + auto cmvVec = pc.inputs().get>(ref); + auto& buffer = mEOSRawCMVs[cru]; + buffer.insert(buffer.end(), cmvVec.begin(), cmvVec.end()); + } + } + + /// Set the CCDB validity start timestamp + /// When using precise timestamps, back-calculates the orbit-reset-referenced wall-clock time for the first real TF in the interval using the orbit-reset time forwarded by distribute lane 0. + /// orbitStep is the dynamically measured per-sub-TF stride; when non-zero it is preferred over the GRP NHBFPerTF for the orbit-offset calculation. + void setTimestampCCDB(const long relTF, const uint32_t orbitStep, o2::framework::ProcessingContext& pc) + { + const auto& tinfo = pc.services().get(); + if (mUsePreciseTimestamp && !mTFInfo.second) { + // Orbit-reset info (NHBFPerTF) not yet received from the distribute lane. + // Fall back to DPL wall-clock creation time so mTimestampStart is never + // left at 0, which would cause successive intervals to overwrite each other. + mTimestampStart = tinfo.creation; + LOGP(warning, "Orbit reset info not yet received; using DPL creation time {} ms as fallback timestamp for interval starting at TF {}", mTimestampStart, mTFFirst); + return; + } + // prefer the measured stride; fall back to NHBFPerTF from GRPECS + const int nHBFPerTF = (orbitStep > 0) ? static_cast(orbitStep) : o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + const auto nOrbitsOffset = (relTF * mNTFsBuffer + (mNTFsBuffer - 1)) * nHBFPerTF; + mFirstOrbitDPL = tinfo.firstTForbit - nOrbitsOffset; + mTimestampStart = mUsePreciseTimestamp ? (mTFInfo.first + (tinfo.firstTForbit - nOrbitsOffset) * o2::constants::lhc::LHCOrbitMUS * 0.001) : tinfo.creation; + LOGP(info, "Setting timestamp reset reference to: {}, at tfCounter: {}, firstTForbit: {}, NHBFPerTF: {}, relTF: {}, nOrbitsOffset: {}", + mTFInfo.first, tinfo.tfCounter, tinfo.firstTForbit, nHBFPerTF, relTF, nOrbitsOffset); + } + + /// Unpack and fill the TTree for all relTF slots that have been buffered during run(). + /// When includeIncomplete=false (normal interval end) only fully-received batches are filled. + /// When includeIncomplete=true (EOS flush) partial batches are also flushed with a warning. + void materializeBufferedTFs(const bool includeIncomplete) + { + for (unsigned int relTF = 0; relTF < mTimeFrames; ++relTF) { + if (mProcessedCRU[relTF] == 0) { + continue; + } + + if ((mProcessedCRU[relTF] != mCRUs.size()) && !includeIncomplete) { + continue; + } + + if ((mProcessedCRU[relTF] != mCRUs.size()) && includeIncomplete) { + LOGP(warning, "Aggregate lane {} flushing incomplete CMV batch relTF {} at EOS: received {} CRUs out of {}", mLaneId, relTF, mProcessedCRU[relTF], mCRUs.size()); + } + + if (!mHasIntervalFirstTF) { + mIntervalFirstTF = mTFFirst == -1 ? 0 : mTFFirst; + mHasIntervalFirstTF = true; + } + + // derive the actual number of sub-TFs from the buffer size; fall back to mNTFsBuffer if empty + const auto maxBufferSize = getMaxBufferSize(mRawCMVs[relTF]); + const int nTFsInBatch = maxBufferSize ? std::max(1, static_cast(maxBufferSize / cmv::NTimeBinsPerTF)) : mNTFsBuffer; + // fall back to GRP NHBFPerTF only if no orbit stride was measured for this relTF + const auto orbitStep = mOrbitStep[relTF] ? mOrbitStep[relTF] : static_cast(o2::base::GRPGeomHelper::instance().getNHBFPerTF()); + appendBatchToTree(mRawCMVs[relTF], mOrbitInfo[relTF], orbitStep, nTFsInBatch); + } + } + + /// Unpack and fill the TTree from the EOS partial-batch buffer (mEOSRawCMVs). + /// The number of real TFs is inferred from the raw buffer size divided by NTimeBinsPerTF. + /// Uses mLastOrbitStep from the last complete batch as the orbit stride fallback. + void materializeEOSBuffer() + { + if (mEOSRawCMVs.empty()) { + return; + } + + const auto maxBufferSize = getMaxBufferSize(mEOSRawCMVs); + const int nTFsInBatch = static_cast(maxBufferSize / cmv::NTimeBinsPerTF); + if (nTFsInBatch <= 0) { + return; + } + + if (!mHasIntervalFirstTF) { + mIntervalFirstTF = mLastSeenTF + 1; + mHasIntervalFirstTF = true; + } + + const uint64_t orbitInfo = (static_cast(mEOSFirstOrbit) << 32) | static_cast(mEOSFirstBC); + // use the actual stride seen in run(); fall back to GRP only if no complete batch was seen + const auto orbitStep = mLastOrbitStep ? mLastOrbitStep : static_cast(o2::base::GRPGeomHelper::instance().getNHBFPerTF()); + appendBatchToTree(mEOSRawCMVs, orbitInfo, orbitStep, nTFsInBatch); + mLastSeenTF += static_cast(nTFsInBatch); + } + + static size_t getMaxBufferSize(const std::unordered_map>& rawCMVs) + { + size_t maxBufferSize = 0; + for (const auto& [cru, values] : rawCMVs) { + maxBufferSize = std::max(maxBufferSize, values.size()); + } + return maxBufferSize; + } + + /// Unpack nTFsInBatch real TFs from rawCMVs, apply preprocessing (rounding, zeroing, trimming), + /// optionally compress them, and fill one TTree entry per real TF. + /// Processing is parallelised across nThreads workers using std::thread (each thread owns a disjoint chunk). + void appendBatchToTree(const std::unordered_map>& rawCMVs, const uint64_t orbitInfo, const uint32_t orbitStep, const int nTFsInBatch) + { + if (nTFsInBatch <= 0) { + return; + } + + const auto firstOrbit = static_cast(orbitInfo >> 32); + const auto firstBC = static_cast(orbitInfo & 0xFFFFu); + // Use the DPL-derived orbit as fallback when the FLP orbit info is missing (firstOrbit == 0) + const auto batchFirstOrbitDPL = (firstOrbit > 0) ? firstOrbit : mFirstOrbitDPL; + if (!mIntervalOrbitSet) { + mIntervalFirstOrbit = batchFirstOrbitDPL; + mIntervalOrbitSet = true; + } + mIntervalLastOrbit = batchFirstOrbitDPL + static_cast(nTFsInBatch - 1) * orbitStep; + const uint8_t flags = buildCompressionFlags(); + std::vector prepared(nTFsInBatch); + const int nThreads = std::max(1, std::min(mThreads, nTFsInBatch)); + const int chunkSize = (nTFsInBatch + nThreads - 1) / nThreads; + + auto worker = [&](const int iThread) { + const int beginTF = iThread * chunkSize; + const int endTF = std::min(nTFsInBatch, beginTF + chunkSize); + for (int tfIndex = beginTF; tfIndex < endTF; ++tfIndex) { + + auto& preparedTF = prepared[tfIndex]; + preparedTF.tf.firstOrbit = firstOrbit + static_cast(tfIndex) * orbitStep; + preparedTF.tf.firstOrbitDPL = batchFirstOrbitDPL + static_cast(tfIndex) * orbitStep; + + for (const auto& [cru, values] : rawCMVs) { + const uint32_t offset = static_cast(tfIndex) * cmv::NTimeBinsPerTF; + if (offset >= static_cast(values.size())) { + continue; + } + const uint32_t nBins = std::min(static_cast(values.size()) - offset, cmv::NTimeBinsPerTF); + for (uint32_t tb = 0; tb < nBins; ++tb) { + preparedTF.tf.mDataPerTF[cru * cmv::NTimeBinsPerTF + tb] = values[offset + tb]; + } + } + + preparedTF.tf.roundToIntegers(mRoundIntegersThreshold); + if (mZeroThreshold > 0.f) { + preparedTF.tf.zeroSmallValues(mZeroThreshold); + } + if (mDynamicPrecisionSigma > 0.f) { + preparedTF.tf.trimGaussianPrecision(mDynamicPrecisionMean, mDynamicPrecisionSigma); + } + if (flags != CMVEncoding::kNone) { + preparedTF.compressed = preparedTF.tf.compress(flags); + } + } + }; + + std::vector workers; + workers.reserve(nThreads - 1); + for (int iThread = 1; iThread < nThreads; ++iThread) { + workers.emplace_back(worker, iThread); + } + worker(0); + for (auto& thread : workers) { + thread.join(); + } + + for (int tfIndex = 0; tfIndex < nTFsInBatch; ++tfIndex) { + if (flags != CMVEncoding::kNone) { + mCurrentCompressedTF = std::move(prepared[tfIndex].compressed); + } else { + mCurrentTF = std::move(prepared[tfIndex].tf); + } + mIntervalTree->Fill(); + ++mIntervalTFCount; + } + } + + void sendOutput(o2::framework::DataAllocator& output) + { + using timer = std::chrono::high_resolution_clock; + + if (mIntervalTFCount == 0) { + LOGP(warning, "CMV interval is empty at sendOutput for lane {}, skipping", mLaneId); + return; + } + + const auto lastTF = mIntervalFirstTF + static_cast(mIntervalTFCount) - 1; + mIntervalTree->GetUserInfo()->Clear(); + mIntervalTree->GetUserInfo()->Add(new TParameter("firstTF", mIntervalFirstTF)); + mIntervalTree->GetUserInfo()->Add(new TParameter("lastTF", lastTF)); + + LOGP(info, "CMVPerTF TTree lane {}: {} entries, firstTF={}, lastTF={}", mLaneId, mIntervalTFCount, mIntervalFirstTF, lastTF); + auto start = timer::now(); + + const int nHBFPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + const long timeStampEnd = mTimestampStart + static_cast(mIntervalTFCount * nHBFPerTF * o2::constants::lhc::LHCOrbitMUS * 1e-3); + + if (mOutputDir != "/dev/null") { + const std::string calibFName = fmt::format("CMV_run_{}_orbit_{}_{}_timestamp_{}_{}.root", + mRun, mIntervalFirstOrbit, mIntervalLastOrbit, mTimestampStart, timeStampEnd); + try { + CMVPerTF::writeToFile(mOutputDir + calibFName, mIntervalTree); + LOGP(info, "CMV file written to {}", mOutputDir + calibFName); + } catch (const std::exception& e) { + LOGP(error, "Failed to write CMV file {}: {}", mOutputDir + calibFName, e.what()); + } + + if (mMetaFileDir != "/dev/null") { + o2::dataformats::FileMetaData calMetaData; + calMetaData.fillFileData(mOutputDir + calibFName); + calMetaData.setDataTakingContext(mDataTakingContext); + calMetaData.type = "calib"; + calMetaData.priority = "low"; + auto metaFileNameTmp = fmt::format("{}{}.tmp", mMetaFileDir, calibFName); + auto metaFileName = fmt::format("{}{}.done", mMetaFileDir, calibFName); + try { + std::ofstream metaFileOut(metaFileNameTmp); + metaFileOut << calMetaData; + metaFileOut.close(); + std::filesystem::rename(metaFileNameTmp, metaFileName); + } catch (std::exception const& e) { + LOG(error) << "Failed to store CMV meta data file " << metaFileName << ", reason: " << e.what(); + } + } + } + + if ((!mSendCCDB) && (mOutputDir == "/dev/null")) { + LOGP(warning, "Neither CCDB output nor output-dir is enabled for aggregate lane {}, skipping CMV export", mLaneId); + } + if (!mSendCCDB) { + return; + } + + if (timeStampEnd <= mTimestampStart) { + LOGP(warning, "Invalid CCDB timestamp range start:{} end:{}, skipping upload", mTimestampStart, timeStampEnd); + return; + } + + o2::ccdb::CcdbObjectInfo ccdbInfoCMV("TPC/Calib/CMV", "TTree", "CMV.root", {}, mTimestampStart, timeStampEnd); + auto image = o2::ccdb::CcdbApi::createObjectImage((mIntervalTree.get()), &ccdbInfoCMV); + // trim TMemFile zero-padding: GetSize() is block-rounded, GetEND() is the actual file end + { + TMemFile mf("trim", image->data(), static_cast(image->size()), "READ"); + image->resize(static_cast(mf.GetEND())); + mf.Close(); + } + + LOGP(info, "Sending object {} / {} of size {} bytes, valid for {} : {}", ccdbInfoCMV.getPath(), ccdbInfoCMV.getFileName(), image->size(), ccdbInfoCMV.getStartValidityTimestamp(), ccdbInfoCMV.getEndValidityTimestamp()); + output.snapshot(o2::framework::Output{o2::calibration::Utils::gDataOriginCDBPayload, getDataDescriptionCCDBCMV(), 0}, *image); + output.snapshot(o2::framework::Output{o2::calibration::Utils::gDataOriginCDBWrapper, getDataDescriptionCCDBCMV(), 0}, ccdbInfoCMV); + + auto stop = timer::now(); + std::chrono::duration elapsed = stop - start; + LOGP(info, "CMV CCDB serialisation time: {:.3f} s", elapsed.count()); + } + + /// Reset all per-interval state after a successful sendOutput(); prepares for the next interval + void reset() + { + mTFFirst = -1; + mTimestampStart = 0; + mIntervalFirstTF = 0; + mHasIntervalFirstTF = false; + mProcessedTFs = 0; + std::fill(mProcessedCRU.begin(), mProcessedCRU.end(), 0); + std::fill(mOrbitInfo.begin(), mOrbitInfo.end(), 0); + std::fill(mOrbitStep.begin(), mOrbitStep.end(), 0); + std::fill(mOrbitInfoSeen.begin(), mOrbitInfoSeen.end(), false); + std::fill(mTFCompleted.begin(), mTFCompleted.end(), false); + for (auto& processedMap : mProcessedCRUs) { + for (auto& [cru, seen] : processedMap) { + seen = false; + } + } + for (auto& rawPerTF : mRawCMVs) { + rawPerTF.clear(); + } + mEOSRawCMVs.clear(); + mEOSFirstOrbit = 0; + mEOSFirstBC = 0; + mLastOrbitStep = 0; + mLastSeenTF = 0; + mIntervalTFCount = 0; + mIntervalFirstOrbit = 0; + mIntervalLastOrbit = 0; + mFirstOrbitDPL = 0; + mIntervalOrbitSet = false; + mCurrentTF = CMVPerTF{}; + mCurrentCompressedTF = CMVPerTFCompressed{}; + initIntervalTree(); + } +}; + +/// Build a DataProcessorSpec for one aggregate lane +/// Each lane receives CMV data from one distribute output lane (matched by lane index) and expects the full CRU list — the distribute stage already routes per-CRU data to the correct lane +inline o2::framework::DataProcessorSpec getTPCAggregateCMVSpec(const int lane, + const std::vector& crus, + const unsigned int timeframes, + const bool sendCCDB, + const bool usePreciseTimestamp, + const int nTFsBuffer = 1) +{ + std::vector outputSpecs; + if (sendCCDB) { + outputSpecs.emplace_back(o2::framework::ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBPayload, TPCAggregateCMVDevice::getDataDescriptionCCDBCMV()}, o2::framework::Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBWrapper, TPCAggregateCMVDevice::getDataDescriptionCCDBCMV()}, o2::framework::Lifetime::Sporadic); + } + + std::vector inputSpecs; + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvagg", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(lane)}, o2::framework::Lifetime::Sporadic}); + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvorbit", o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(lane), header::DataHeader::SubSpecificationType{static_cast(lane)}, o2::framework::Lifetime::Sporadic}); + inputSpecs.emplace_back(o2::framework::InputSpec{"firstTF", o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{static_cast(lane)}, o2::framework::Lifetime::Sporadic}); + if (usePreciseTimestamp) { + inputSpecs.emplace_back(o2::framework::InputSpec{"orbitreset", o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{static_cast(lane)}, o2::framework::Lifetime::Sporadic}); + } + + // Request GRPECS from CCDB so that GRPGeomHelper::getNHBFPerTF() is valid in this (separate) process + auto ccdbRequest = std::make_shared(false, // orbitResetTime + true, // GRPECS (NHBFPerTF) + false, // GRPLHCIF + false, // GRPMagField + false, // askMatLUT + o2::base::GRPGeomRequest::None, // geometry + inputSpecs); + + o2::framework::DataProcessorSpec spec{ + fmt::format("tpc-aggregate-cmv-{:02}", lane).data(), + inputSpecs, + outputSpecs, + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(lane, crus, timeframes, sendCCDB, usePreciseTimestamp, nTFsBuffer, ccdbRequest)}, + o2::framework::Options{{"output-dir", o2::framework::VariantType::String, "/dev/null", {"CMV output directory, must exist (if not /dev/null)"}}, + {"meta-output-dir", o2::framework::VariantType::String, "/dev/null", {"calibration metadata output directory, must exist (if not /dev/null)"}}, + {"nthreads-compression", o2::framework::VariantType::Int, 1, {"Number of threads used for CMV per timeframe preprocessing and compression"}}, + {"use-sparse", o2::framework::VariantType::Bool, false, {"Sparse encoding (skip zero time bins). Alone: raw uint16 values. With --use-compression-varint: varint exact values. With --use-compression-huffman: Huffman exact values"}}, + {"use-compression-varint", o2::framework::VariantType::Bool, false, {"Delta+zigzag+varint compression (all values). Combined with --use-sparse: sparse positions + varint encoded exact CMV values"}}, + {"use-compression-huffman", o2::framework::VariantType::Bool, false, {"Huffman encoding. Combined with --use-sparse: sparse positions + Huffman-encoded exact CMV values"}}, + {"cmv-zero-threshold", o2::framework::VariantType::Float, 0.f, {"Zero out CMV values whose float magnitude is below this threshold after optional integer rounding and before compression; 0 disables"}}, + {"cmv-round-integers-threshold", o2::framework::VariantType::Int, 0, {"Round values to nearest integer ADC for |v| <= N ADC before compression; 0 disables"}}, + {"cmv-dynamic-precision-mean", o2::framework::VariantType::Float, 1.f, {"Gaussian centre in |CMV| ADC where the strongest fractional bit trimming is applied"}}, + {"cmv-dynamic-precision-sigma", o2::framework::VariantType::Float, 0.f, {"Gaussian width in ADC for smooth CMV fractional bit trimming; 0 disables"}}}}; + spec.rank = lane; + return spec; +} + +} // namespace o2::tpc + +#endif diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCCalibPadGainTracksSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCCalibPadGainTracksSpec.h index 516ea128acfe7..34f29f94dff4d 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCCalibPadGainTracksSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCCalibPadGainTracksSpec.h @@ -26,8 +26,7 @@ #include "TPCWorkflow/ProcessingHelpers.h" #include "Framework/CCDBParamSpec.h" #include "TPCBaseRecSim/CDBInterface.h" -#include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCFastTransformPOD.h" #include "DetectorsBase/GRPGeomHelper.h" #include "GPUO2InterfaceUtils.h" #include "DataFormatsGlobalTracking/RecoContainer.h" @@ -45,22 +44,18 @@ namespace tpc class TPCCalibPadGainTracksDevice : public o2::framework::Task { public: - TPCCalibPadGainTracksDevice(std::shared_ptr dr, std::shared_ptr req, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, const uint32_t publishAfterTFs, const bool debug, const bool useLastExtractedMapAsReference, const std::string polynomialsFile, const bool disablePolynomialsCCDB) : mDataRequest(dr), mPublishAfter(publishAfterTFs), mDebug(debug), mUseLastExtractedMapAsReference(useLastExtractedMapAsReference), mDisablePolynomialsCCDB(disablePolynomialsCCDB), mCCDBRequest(req) + TPCCalibPadGainTracksDevice(std::shared_ptr dr, std::shared_ptr req, const uint32_t publishAfterTFs, const bool debug, const bool useLastExtractedMapAsReference, const std::string polynomialsFile, const bool disablePolynomialsCCDB) : mDataRequest(dr), mPublishAfter(publishAfterTFs), mDebug(debug), mUseLastExtractedMapAsReference(useLastExtractedMapAsReference), mDisablePolynomialsCCDB(disablePolynomialsCCDB), mCCDBRequest(req) { if (!polynomialsFile.empty()) { LOGP(info, "Loading polynomials from file {}", polynomialsFile); mPadGainTracks.loadPolTopologyCorrectionFromFile(polynomialsFile.data()); mDisablePolynomialsCCDB = true; } - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); } void init(o2::framework::InitContext& ic) final { o2::base::GRPGeomHelper::instance().setRequest(mCCDBRequest); - mTPCCorrMapsLoader.init(ic); // setting up the histogram ranges const auto nBins = ic.options().get("nBins"); auto reldEdxMin = ic.options().get("reldEdxMin"); @@ -151,8 +146,6 @@ class TPCCalibPadGainTracksDevice : public o2::framework::Task LOGP(info, "Updating Q topology correction from CCDB"); const auto* topologyCorr = static_cast(obj); mPadGainTracks.setPolTopologyCorrectionFromContainer(*topologyCorr); - } else if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { - } else if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { } else if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { const auto field = o2::gpu::GPUO2InterfaceUtils::getNominalGPUBz(*o2::base::GRPGeomHelper::instance().getGRPMagField()); LOGP(info, "Setting magnetic field to {} kG", field); @@ -188,27 +181,10 @@ class TPCCalibPadGainTracksDevice : public o2::framework::Task LOGP(info, "fetching residual gain map"); pc.inputs().get>*>("tpcresidualgainmap"); } - mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); - bool updateMaps = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - mPadGainTracks.setTPCCorrMaps(&mTPCCorrMapsLoader); - if (mTPCVDriftHelper.isUpdated()) { - LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", - mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, - mTPCVDriftHelper.getVDriftObject().timeOffsetCorr, mTPCVDriftHelper.getVDriftObject().refTimeOffset, - mTPCVDriftHelper.getSourceName()); - mPadGainTracks.setTPCVDrift(mTPCVDriftHelper.getVDriftObject()); - mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); - } + auto const& raw = pc.inputs().get("corrMap"); + mTPCCorrMaps = &o2::gpu::TPCFastTransformPOD::get(raw); + mPadGainTracks.setTPCCorrMaps(mTPCCorrMaps); mPadGainTracks.setMembers(&tracks, &clRefs, clusters->clusterIndex, recoData.clusterShMapTPC, recoData.occupancyMapTPC); mPadGainTracks.processTracks(mMaxTracksPerTF); ++mProcessedTFs; @@ -224,21 +200,20 @@ class TPCCalibPadGainTracksDevice : public o2::framework::Task } private: - const uint32_t mPublishAfter{0}; ///< number of TFs after which to dump the calibration - const bool mDebug{false}; ///< create debug output - const bool mUseLastExtractedMapAsReference{false}; ///< using the last extracted gain map as the reference map which will be applied - bool mDisablePolynomialsCCDB{false}; ///< do not load the polynomials from the CCDB + const uint32_t mPublishAfter{0}; ///< number of TFs after which to dump the calibration + const bool mDebug{false}; ///< create debug output + const bool mUseLastExtractedMapAsReference{false}; ///< using the last extracted gain map as the reference map which will be applied + bool mDisablePolynomialsCCDB{false}; ///< do not load the polynomials from the CCDB std::shared_ptr mDataRequest; ///< reco container data request - std::shared_ptr mCCDBRequest; ///< for accessing the b-field - uint32_t mProcessedTFs{0}; ///< counter to keep track of the processed TFs - uint32_t mTFCounter{0}; ///< counter to keep track of the TFs - CalibPadGainTracks mPadGainTracks{false}; ///< class for creating the pad-by-pad gain map - bool mUsingDefaultGainMapForFirstIter{true}; ///< using no reference gain map for the first iteration - unsigned int mUseEveryNthTF{1}; ///< process every Nth TF only - unsigned int mFirstTFSend{1}; ///< first TF for which the data will be send (initialized randomly) - int mMaxTracksPerTF{-1}; ///< max number of tracks processed per TF - o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + std::shared_ptr mCCDBRequest; ///< for accessing the b-field + uint32_t mProcessedTFs{0}; ///< counter to keep track of the processed TFs + uint32_t mTFCounter{0}; ///< counter to keep track of the TFs + CalibPadGainTracks mPadGainTracks{false}; ///< class for creating the pad-by-pad gain map + bool mUsingDefaultGainMapForFirstIter{true}; ///< using no reference gain map for the first iteration + unsigned int mUseEveryNthTF{1}; ///< process every Nth TF only + unsigned int mFirstTFSend{1}; ///< first TF for which the data will be send (initialized randomly) + int mMaxTracksPerTF{-1}; ///< max number of tracks processed per TF + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps{nullptr}; void sendOutput(DataAllocator& output) { @@ -247,16 +222,16 @@ class TPCCalibPadGainTracksDevice : public o2::framework::Task } }; -DataProcessorSpec getTPCCalibPadGainTracksSpec(const uint32_t publishAfterTFs, const bool debug, const bool useLastExtractedMapAsReference, const std::string polynomialsFile, bool disablePolynomialsCCDB, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts) +DataProcessorSpec getTPCCalibPadGainTracksSpec(const uint32_t publishAfterTFs, const bool debug, const bool useLastExtractedMapAsReference, const std::string polynomialsFile, bool disablePolynomialsCCDB) { std::vector inputs; auto dataRequest = std::make_shared(); dataRequest->requestTracks(o2::dataformats::GlobalTrackID::getSourceMask(o2::dataformats::GlobalTrackID::TPC), false); dataRequest->requestClusters(o2::dataformats::GlobalTrackID::getSourceMask(o2::dataformats::GlobalTrackID::TPC), false); - if (sclOpts.lumiType == 1) { - dataRequest->inputs.emplace_back("CTPLumi", "CTP", "LUMI", 0, Lifetime::Timeframe); - } + // if (sclOpts.lumiType == 1) { + // dataRequest->inputs.emplace_back("CTPLumi", "CTP", "LUMI", 0, Lifetime::Timeframe); + // } if (!polynomialsFile.empty()) { disablePolynomialsCCDB = true; @@ -270,7 +245,6 @@ DataProcessorSpec getTPCCalibPadGainTracksSpec(const uint32_t publishAfterTFs, c dataRequest->inputs.emplace_back("tpcresidualgainmap", gDataOriginTPC, "RESIDUALGAINMAP", 0, Lifetime::Condition, ccdbParamSpec(CDBTypeMap.at(CDBType::CalPadGainResidual))); } - o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); Options opts{ {"nBins", VariantType::Int, 20, {"Number of bins per histogram"}}, {"reldEdxMin", VariantType::Int, 0, {"Minimum x coordinate of the histogram for Q/(dE/dx)"}}, @@ -293,7 +267,7 @@ DataProcessorSpec getTPCCalibPadGainTracksSpec(const uint32_t publishAfterTFs, c {"useEveryNthTF", VariantType::Int, 10, {"Using only a fraction of the data: 1: Use every TF, 10: Use only every tenth TF."}}, {"maxTracksPerTF", VariantType::Int, 10000, {"Maximum number of processed tracks per TF (-1 for processing all tracks)"}}, }; - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); auto ccdbRequest = std::make_shared(false, // orbitResetTime false, // GRPECS=true @@ -310,7 +284,7 @@ DataProcessorSpec getTPCCalibPadGainTracksSpec(const uint32_t publishAfterTFs, c "calib-tpc-gainmap-tracks", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ccdbRequest, sclOpts, publishAfterTFs, debug, useLastExtractedMapAsReference, polynomialsFile, disablePolynomialsCCDB)}, + AlgorithmSpec{adaptFromTask(dataRequest, ccdbRequest, publishAfterTFs, debug, useLastExtractedMapAsReference, polynomialsFile, disablePolynomialsCCDB)}, opts}; // end DataProcessorSpec } diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h new file mode 100644 index 0000000000000..af576b2f30a5b --- /dev/null +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCDistributeCMVSpec.h @@ -0,0 +1,476 @@ +// 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 TPCDistributeCMVSpec.h +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief TPC distribution of grouped CMVs towards the CMV aggregation workflow + +#ifndef O2_TPCDISTRIBUTECMVSPEC_H +#define O2_TPCDISTRIBUTECMVSPEC_H + +#include +#include +#include +#include +#include +#include +#include "Framework/Task.h" +#include "Framework/ControlService.h" +#include "Framework/Logger.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/DataTakingContext.h" +#include "Headers/DataHeader.h" +#include "Framework/ConfigParamRegistry.h" +#include "TPCWorkflow/TPCFLPCMVSpec.h" +#include "MemoryResources/MemoryResources.h" +#include "TPCWorkflow/ProcessingHelpers.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include "CommonDataFormat/Pair.h" + +namespace o2::tpc +{ + +class TPCDistributeCMVSpec : public o2::framework::Task +{ + public: + TPCDistributeCMVSpec(const std::vector& crus, const unsigned int timeframes, const int nTFsBuffer, const unsigned int outlanes, const int firstTF, std::shared_ptr req) + : mCRUs{crus}, + mTimeFrames{timeframes}, + mNTFsBuffer{nTFsBuffer}, + mOutLanes{outlanes}, + mProcessedCRU{{std::vector(timeframes), std::vector(timeframes)}}, + mTFStart{{firstTF, firstTF + static_cast(timeframes) * nTFsBuffer}}, + mTFEnd{{firstTF + static_cast(timeframes) * nTFsBuffer - 1, firstTF + 2LL * timeframes * nTFsBuffer - 1}}, + mCCDBRequest(req), + mSendCCDBOutputOrbitReset(outlanes), + mSendCCDBOutputGRPECS(outlanes), + mOrbitInfoForwarded{{std::vector(timeframes, false), std::vector(timeframes, false)}} + { + mDataDescrOut.reserve(mOutLanes); + mOrbitDescrOut.reserve(mOutLanes); + for (unsigned int i = 0; i < mOutLanes; ++i) { + mDataDescrOut.emplace_back(getDataDescriptionCMV(i)); + mOrbitDescrOut.emplace_back(getDataDescriptionCMVOrbitInfo(i)); + } + // sort vector for binary_search + std::sort(mCRUs.begin(), mCRUs.end()); + + for (auto& processedCRUbuffer : mProcessedCRUs) { + processedCRUbuffer.resize(mTimeFrames); + for (auto& crusMap : processedCRUbuffer) { + crusMap.reserve(mCRUs.size()); + for (const auto cruID : mCRUs) { + crusMap.emplace(cruID, false); + } + } + } + + mFilter.emplace_back(o2::framework::InputSpec{"cmvsgroup", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup()}, o2::framework::Lifetime::Sporadic}); + mOrbitFilter.emplace_back(o2::framework::InputSpec{"cmvorbit", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo()}, o2::framework::Lifetime::Sporadic}); + } + + void init(o2::framework::InitContext& ic) final + { + o2::base::GRPGeomHelper::instance().setRequest(mCCDBRequest); + mNFactorTFs = ic.options().get("nFactorTFs"); + mNTFsDataDrop = ic.options().get("drop-data-after-nTFs"); + mCheckEveryNData = ic.options().get("check-data-every-n"); + if (mCheckEveryNData == 0) { + mCheckEveryNData = mTimeFrames / 2; + if (mCheckEveryNData == 0) { + mCheckEveryNData = 1; + } + mNTFsDataDrop = mCheckEveryNData; + } + } + + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) final + { + o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj); + if (matcher == o2::framework::ConcreteDataMatcher("CTP", "ORBITRESET", 0)) { + LOGP(debug, "Updating ORBITRESET"); + std::fill(mSendCCDBOutputOrbitReset.begin(), mSendCCDBOutputOrbitReset.end(), true); + } else if (matcher == o2::framework::ConcreteDataMatcher("GLO", "GRPECS", 0)) { + // check if received object is valid + if (o2::base::GRPGeomHelper::instance().getGRPECS()->getRun() != 0) { + LOGP(debug, "Updating GRPECS"); + std::fill(mSendCCDBOutputGRPECS.begin(), mSendCCDBOutputGRPECS.end(), true); + } else { + LOGP(debug, "Detected default GRPECS object"); + } + } + } + + void run(o2::framework::ProcessingContext& pc) final + { + // capture orbit-reset info once for precise CCDB timestamp calculation + if (mCCDBRequest->askTime) { + const bool grpecsValid = pc.inputs().isValid("grpecs"); + const bool orbitResetValid = pc.inputs().isValid("orbitReset"); + if (grpecsValid) { + pc.inputs().get("grpecs"); + } + if (orbitResetValid) { + pc.inputs().get*>("orbitReset"); + } + if (pc.inputs().countValidInputs() == (grpecsValid + orbitResetValid)) { + return; + } + } + + const auto tf = processing_helpers::getCurrentTF(pc); + if (tf == std::numeric_limits::max()) { + forwardEOSData(pc); + return; + } + + // automatically detect firstTF in case firstTF was not specified + if (mTFStart.front() <= -1) { + const auto firstTFDetected = tf; + const long offsetTF = std::abs(mTFStart.front() + 1); + const auto nTotTFs = getNRealTFs(); + // tf is the batch TF counter (= last real TF in the first batch), subtract (mNTFsBuffer - 1) to recover the actual first real TF of the interval + const long firstRealTF = static_cast(firstTFDetected) - (mNTFsBuffer - 1) + offsetTF; + mTFStart = {firstRealTF, firstRealTF + nTotTFs}; + mTFEnd = {mTFStart[1] - 1, mTFStart[1] - 1 + nTotTFs}; + LOGP(detail, "Setting {} as first TF", mTFStart[0]); + LOGP(detail, "Using offset of {} TFs for setting the first TF", offsetTF); + } + + // check which buffer to use for current incoming data + const bool currentBuffer = (tf > mTFEnd[mBuffer]) ? !mBuffer : mBuffer; + if (mTFStart[currentBuffer] > tf) { + LOGP(detail, "All CRUs for current TF {} already received. Skipping this TF", tf); + return; + } + + const unsigned int currentOutLane = getOutLane(tf); + const unsigned int relTF = (tf - mTFStart[currentBuffer]) / mNTFsBuffer; + LOGP(debug, "Current TF: {}, relative TF: {}, current buffer: {}, current output lane: {}, mTFStart: {}", tf, relTF, currentBuffer, currentOutLane, mTFStart[currentBuffer]); + + if (relTF >= mProcessedCRU[currentBuffer].size()) { + LOGP(warning, "Skipping tf {}: relative tf {} is larger than size of buffer: {}", tf, relTF, mProcessedCRU[currentBuffer].size()); + // check number of processed CRUs for previous TFs. If CRUs are missing for them, they are probably lost/not received + mProcessedTotalData = mCheckEveryNData; + checkIntervalsForMissingData(pc, currentBuffer, relTF, currentOutLane, tf); + return; + } + + if (mProcessedCRU[currentBuffer][relTF] == mCRUs.size()) { + return; + } + + if (mSendOutputStartInfo[currentBuffer]) { + mSendOutputStartInfo[currentBuffer] = false; + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{currentOutLane}}, mTFStart[currentBuffer]); + } + + if (mSendCCDBOutputOrbitReset[currentOutLane] && mSendCCDBOutputGRPECS[currentOutLane]) { + mSendCCDBOutputOrbitReset[currentOutLane] = false; + mSendCCDBOutputGRPECS[currentOutLane] = false; + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{currentOutLane}}, dataformats::Pair{o2::base::GRPGeomHelper::instance().getOrbitResetTimeMS(), o2::base::GRPGeomHelper::instance().getNHBFPerTF()}); + } + + forwardOrbitInfo(pc, currentBuffer, relTF, currentOutLane); + + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { + auto const* tpcCRUHeader = o2::framework::DataRefUtils::getHeader(ref); + const unsigned int cru = tpcCRUHeader->subSpecification >> 7; + + // check if cru is specified in input cru list + if (!(std::binary_search(mCRUs.begin(), mCRUs.end(), cru))) { + LOGP(debug, "Received data from CRU: {} which was not specified as input. Skipping", cru); + continue; + } + + if (mProcessedCRUs[currentBuffer][relTF][cru]) { + continue; + } + // count total number of processed CRUs for given TF + ++mProcessedCRU[currentBuffer][relTF]; + // to keep track of processed CRUs + mProcessedCRUs[currentBuffer][relTF][cru] = true; + + sendOutput(pc, currentOutLane, cru, pc.inputs().get>(ref)); + } + + LOGP(detail, "Number of received CRUs for current TF: {} Needed a total number of processed CRUs of: {} Current TF: {}", mProcessedCRU[currentBuffer][relTF], mCRUs.size(), tf); + + // check for missing data if specified + if (mNTFsDataDrop > 0) { + checkIntervalsForMissingData(pc, currentBuffer, relTF, currentOutLane, tf); + } + + if (mProcessedCRU[currentBuffer][relTF] == mCRUs.size()) { + ++mProcessedTFs[currentBuffer]; + } + + if (mProcessedTFs[currentBuffer] == mTimeFrames) { + finishInterval(pc, currentOutLane, currentBuffer, tf); + } + } + + void endOfStream(o2::framework::EndOfStreamContext& ec) final { ec.services().get().readyToQuit(o2::framework::QuitRequest::Me); } + + /// Return data description for aggregated CMVs for a given lane + static header::DataDescription getDataDescriptionCMV(const unsigned int lane) + { + const std::string name = fmt::format("CMVAGG{}", lane); + header::DataDescription description; + description.runtimeInit(name.substr(0, 16).c_str()); + return description; + } + + /// Return data description for orbit/BC info for a given output lane + static header::DataDescription getDataDescriptionCMVOrbitInfo(const unsigned int lane) + { + const std::string name = fmt::format("CMVORB{}", lane); + header::DataDescription description; + description.runtimeInit(name.substr(0, 16).c_str()); + return description; + } + + static constexpr header::DataDescription getDataDescriptionCMVFirstTF() { return header::DataDescription{"CMVFIRSTTF"}; } + static constexpr header::DataDescription getDataDescriptionCMVOrbitReset() { return header::DataDescription{"CMVORBITRESET"}; } + + private: + std::vector mCRUs{}; ///< CRUs to process in this instance + const unsigned int mTimeFrames{}; ///< number of TFs per aggregation interval + const int mNTFsBuffer{1}; ///< number of TFs for which the CMVs will be buffered (must match TPCFLPCMVSpec) + const unsigned int mOutLanes{}; ///< number of parallel aggregate pipelines this distributor feeds + std::array mProcessedTFs{{0, 0}}; ///< number of processed timeframes per buffer; triggers sendOutput when it reaches mTimeFrames + std::array, 2> mProcessedCRU{}; ///< counter of received CRUs per (buffer, relTF); used to detect when a relTF is complete + std::array>, 2> mProcessedCRUs{}; ///< per-CRU received flag ([buffer][relTF][CRU]); prevents double-counting when a CRU re-sends + std::array mTFStart{}; ///< absolute TF counter of the first TF in each buffer interval + std::array mTFEnd{}; ///< absolute TF counter of the last TF in each buffer interval + std::array mSendOutputStartInfo{true, true}; ///< flag to send CMVFIRSTTF message once at the start of each buffer interval + std::shared_ptr mCCDBRequest; ///< info for CCDB request (orbit-reset and GRPECS, only on lane 0 when sendPreciseTimestamp=true) + std::vector mSendCCDBOutputOrbitReset{}; ///< per-output-lane flag: true when a fresh orbit-reset object has been received from CCDB + std::vector mSendCCDBOutputGRPECS{}; ///< per-output-lane flag: true when a fresh GRPECS object has been received from CCDB + unsigned int mCurrentOutLane{0}; ///< output lane currently being filled + bool mBuffer{false}; ///< double-buffer index (false = buffer 0, true = buffer 1) + int mNFactorTFs{0}; ///< number of TFs to skip when setting oldestForChannel; resets to 0 after first interval + int mNTFsDataDrop{0}; ///< delay (in relTF units) before declaring a relTF's missing CRUs as lost + std::array mStartNTFsDataDrop{0}; ///< first relative TF index to check for missing data in each buffer + long mProcessedTotalData{0}; ///< call counter used to throttle checkIntervalsForMissingData checks + int mCheckEveryNData{1}; ///< check for missing data every N run() calls (0 → default = mTimeFrames/2) + std::vector mFilter{}; ///< filter for looping over CMVGROUP input data from FLPs + std::vector mOrbitFilter{}; ///< filter for CMVORBITINFO input from FLPs + std::vector mDataDescrOut{}; ///< per-output-lane CMV data descriptions (CMVAGG0, CMVAGG1, …) + std::vector mOrbitDescrOut{}; ///< per-output-lane orbit-info data descriptions (CMVORB0, CMVORB1, …) + std::array, 2> mOrbitInfoForwarded{}; ///< tracks whether orbit/BC has been forwarded to the aggregate lane per (buffer, relTF) + + /// Returns the output aggregate lane for a given TF counter (advances when the current buffer interval has ended) + unsigned int getOutLane(const uint32_t tf) const { return (tf > mTFEnd[mBuffer]) ? (mCurrentOutLane + 1) % mOutLanes : mCurrentOutLane; } + /// Returns the total number of real TFs per buffer interval (= mNTFsBuffer * mTimeFrames) + unsigned int getNRealTFs() const { return mNTFsBuffer * mTimeFrames; } + + void sendOutput(o2::framework::ProcessingContext& pc, const unsigned int currentOutLane, const unsigned int cru, o2::pmr::vector cmvs) + { + pc.outputs().adoptContainer(o2::framework::Output{o2::header::gDataOriginTPC, mDataDescrOut[currentOutLane], header::DataHeader::SubSpecificationType{cru}}, std::move(cmvs)); + } + + void sendOrbitInfo(o2::framework::ProcessingContext& pc, const unsigned int outLane, const uint64_t orbitInfo) + { + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, mOrbitDescrOut[outLane], header::DataHeader::SubSpecificationType{outLane}}, orbitInfo); + } + + void forwardOrbitInfo(o2::framework::ProcessingContext& pc, const bool currentBuffer, const unsigned int relTF, const unsigned int currentOutLane) + { + if (mOrbitInfoForwarded[currentBuffer][relTF]) { + return; + } + + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mOrbitFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const unsigned int cru = hdr->subSpecification >> 7; + if (!std::binary_search(mCRUs.begin(), mCRUs.end(), cru)) { + continue; + } + + sendOrbitInfo(pc, currentOutLane, pc.inputs().get(ref)); + mOrbitInfoForwarded[currentBuffer][relTF] = true; + break; + } + } + + void forwardEOSData(o2::framework::ProcessingContext& pc) + { + const unsigned int currentOutLane = mCurrentOutLane; + + if (mSendOutputStartInfo[mBuffer] && (mTFStart[mBuffer] >= 0)) { + mSendOutputStartInfo[mBuffer] = false; + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{currentOutLane}}, mTFStart[mBuffer]); + } + + if (mSendCCDBOutputOrbitReset[currentOutLane] && mSendCCDBOutputGRPECS[currentOutLane]) { + mSendCCDBOutputOrbitReset[currentOutLane] = false; + mSendCCDBOutputGRPECS[currentOutLane] = false; + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{currentOutLane}}, dataformats::Pair{o2::base::GRPGeomHelper::instance().getOrbitResetTimeMS(), o2::base::GRPGeomHelper::instance().getNHBFPerTF()}); + } + + if (!mOrbitInfoForwarded[mBuffer].empty()) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mOrbitFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const unsigned int cru = hdr->subSpecification >> 7; + if (!std::binary_search(mCRUs.begin(), mCRUs.end(), cru)) { + continue; + } + sendOrbitInfo(pc, currentOutLane, pc.inputs().get(ref)); + break; + } + } + + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const unsigned int cru = hdr->subSpecification >> 7; + if (!std::binary_search(mCRUs.begin(), mCRUs.end(), cru)) { + continue; + } + sendOutput(pc, currentOutLane, cru, pc.inputs().get>(ref)); + } + } + + void clearBuffer(const bool currentBuffer) + { + // reset per-CRU received flags so the next interval can accept data from all CRUs again + for (auto& crusMap : mProcessedCRUs[currentBuffer]) { + for (auto& it : crusMap) { + it.second = false; + } + } + + mProcessedTFs[currentBuffer] = 0; + std::fill(mProcessedCRU[currentBuffer].begin(), mProcessedCRU[currentBuffer].end(), 0); + std::fill(mOrbitInfoForwarded[currentBuffer].begin(), mOrbitInfoForwarded[currentBuffer].end(), false); + + mTFStart[mBuffer] = mTFEnd[!mBuffer] + 1; + mTFEnd[mBuffer] = mTFStart[mBuffer] + getNRealTFs() - 1; + + // switch buffer and advance output lane + mBuffer = !mBuffer; + mCurrentOutLane = ++mCurrentOutLane % mOutLanes; + } + + void checkIntervalsForMissingData(o2::framework::ProcessingContext& pc, const bool currentBuffer, const long relTF, const unsigned int currentOutLane, const uint32_t tf) + { + if (!(mProcessedTotalData++ % mCheckEveryNData)) { + LOGP(detail, "Checking for dropped packages..."); + + // if the last buffer has a smaller time range than expected, flush its remaining uncompleted TFs + if ((mTFStart[currentBuffer] > mTFStart[!currentBuffer]) && (relTF > mNTFsDataDrop)) { + LOGP(warning, "Checking last buffer from {} to {}", mStartNTFsDataDrop[!currentBuffer], mProcessedCRU[!currentBuffer].size()); + const unsigned int lastLane = (currentOutLane == 0) ? (mOutLanes - 1) : (currentOutLane - 1); + checkMissingData(pc, !currentBuffer, mStartNTFsDataDrop[!currentBuffer], mProcessedCRU[!currentBuffer].size(), lastLane); + LOGP(detail, "All empty TFs for TF {} for current buffer filled with dummy and sent. Clearing buffer", tf); + finishInterval(pc, lastLane, !currentBuffer, tf); + } + + const int tfEndCheck = std::clamp(static_cast(relTF) - mNTFsDataDrop, 0, static_cast(mProcessedCRU[currentBuffer].size())); + LOGP(detail, "Checking current buffer from {} to {}", mStartNTFsDataDrop[currentBuffer], tfEndCheck); + checkMissingData(pc, currentBuffer, mStartNTFsDataDrop[currentBuffer], tfEndCheck, currentOutLane); + mStartNTFsDataDrop[currentBuffer] = tfEndCheck; + } + } + + void checkMissingData(o2::framework::ProcessingContext& pc, const bool currentBuffer, const int startTF, const int endTF, const unsigned int outLane) + { + for (int iTF = startTF; iTF < endTF; ++iTF) { + if (mProcessedCRU[currentBuffer][iTF] != mCRUs.size()) { + LOGP(warning, "CRUs for lane {} rel. TF: {} curr TF {} are missing! Processed {} CRUs out of {}", outLane, iTF, mTFStart[currentBuffer] + static_cast(iTF) * mNTFsBuffer, mProcessedCRU[currentBuffer][iTF], mCRUs.size()); + ++mProcessedTFs[currentBuffer]; + mProcessedCRU[currentBuffer][iTF] = mCRUs.size(); + + // send empty payloads for missing CRUs so the aggregate lane sees a complete set + for (auto& it : mProcessedCRUs[currentBuffer][iTF]) { + if (!it.second) { + it.second = true; + sendOutput(pc, outLane, it.first, o2::pmr::vector()); + } + } + + // send zero orbit placeholder for missing TF so the aggregate lane can still reconstruct timing + if (!mOrbitInfoForwarded[currentBuffer][iTF]) { + sendOrbitInfo(pc, outLane, 0); + mOrbitInfoForwarded[currentBuffer][iTF] = true; + } + } + } + } + + void finishInterval(o2::framework::ProcessingContext& pc, const unsigned int currentOutLane, const bool buffer, const uint32_t tf) + { + if (mNFactorTFs > 0) { + mNFactorTFs = 0; + // ToDo: Find better fix. Set oldestForChannel to a very large value so the DPL dispatcher does not block waiting for older TF data that will never arrive + for (unsigned int ilane = 0; ilane < mOutLanes; ++ilane) { + auto& deviceProxy = pc.services().get(); + auto& state = deviceProxy.getOutputChannelState({static_cast(ilane)}); + size_t oldest = std::numeric_limits::max() - 1; + state.oldestForChannel = {oldest}; + } + } + + LOGP(detail, "All TFs {} for current buffer received. Clearing buffer", tf); + clearBuffer(buffer); + mStartNTFsDataDrop[buffer] = 0; + mSendOutputStartInfo[buffer] = true; + } +}; + +o2::framework::DataProcessorSpec getTPCDistributeCMVSpec(const int ilane, const std::vector& crus, const unsigned int timeframes, const unsigned int outlanes, const int firstTF, const bool sendPrecisetimeStamp = false, const int nTFsBuffer = 1) +{ + std::vector inputSpecs; + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvsgroup", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup()}, o2::framework::Lifetime::Sporadic}); + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvorbit", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo()}, o2::framework::Lifetime::Sporadic}); + + std::vector outputSpecs; + outputSpecs.reserve(3 * outlanes); + for (unsigned int lane = 0; lane < outlanes; ++lane) { + outputSpecs.emplace_back(o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMV(lane)}, o2::framework::Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitInfo(lane), header::DataHeader::SubSpecificationType{lane}}, o2::framework::Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVFirstTF(), header::DataHeader::SubSpecificationType{lane}}, o2::framework::Lifetime::Sporadic); + } + + // Only lane 0 fetches CCDB orbit-reset/GRPECS objects and broadcasts them to all aggregate lanes, the other distribute lanes do not need them, avoiding redundant CCDB requests + bool fetchCCDB = false; + if (sendPrecisetimeStamp && (ilane == 0)) { + fetchCCDB = true; + for (unsigned int lane = 0; lane < outlanes; ++lane) { + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCDistributeCMVSpec::getDataDescriptionCMVOrbitReset(), header::DataHeader::SubSpecificationType{lane}}, o2::framework::Lifetime::Sporadic); + } + } + + auto ccdbRequest = std::make_shared(fetchCCDB, // orbitResetTime + fetchCCDB, // GRPECS=true + false, // GRPLHCIF + false, // GRPMagField + false, // askMatLUT + o2::base::GRPGeomRequest::None, // geometry + inputSpecs); + + const auto id = fmt::format("tpc-distribute-cmv-{:02}", ilane); + o2::framework::DataProcessorSpec spec{ + id.data(), + inputSpecs, + outputSpecs, + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(crus, timeframes, nTFsBuffer, outlanes, firstTF, ccdbRequest)}, + o2::framework::Options{{"drop-data-after-nTFs", o2::framework::VariantType::Int, 0, {"Number of TFs after which to drop the data."}}, + {"check-data-every-n", o2::framework::VariantType::Int, 0, {"Number of run function called after which to check for missing data (-1 for no checking, 0 for default checking)."}}, + {"nFactorTFs", o2::framework::VariantType::Int, 1000, {"Number of TFs to skip for sending oldest TF."}}}}; + spec.rank = ilane; + return spec; +} + +} // namespace o2::tpc + +#endif diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h new file mode 100644 index 0000000000000..d86356234a1c2 --- /dev/null +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPCMVSpec.h @@ -0,0 +1,246 @@ +// 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 TPCFLPCMVSpec.h +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief TPC device for processing CMVs on FLPs + +#ifndef O2_TPCFLPCMVSPEC_H +#define O2_TPCFLPCMVSPEC_H + +#include +#include +#include +#include "Framework/Task.h" +#include "Framework/ControlService.h" +#include "Framework/Logger.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/ConfigParamRegistry.h" +#include "Headers/DataHeader.h" +#include "TPCWorkflow/ProcessingHelpers.h" +#include "TPCBase/CRU.h" +#include "DataFormatsTPC/CMV.h" +#include "TFile.h" + +namespace o2::tpc +{ + +class TPCFLPCMVDevice : public o2::framework::Task +{ + public: + TPCFLPCMVDevice(const int lane, const std::vector& crus, const bool triggerPerFlp, const int nTFsBuffer) + : mLane{lane}, mCRUs{crus}, mTriggerPerFLP{triggerPerFlp}, mNTFsBuffer{nTFsBuffer} {} + + void init(o2::framework::InitContext& ic) final + { + mDumpCMVs = ic.options().get("dump-cmvs-flp"); + mEnableTrigger = ic.options().get("trigger"); + mTriggerThresholdCMV = ic.options().get("trigger-threshold-cmv"); + mTriggerThresholdMeanMax = ic.options().get("trigger-threshold-cmvMeanMax"); + mTriggerThresholdMeanMin = ic.options().get("trigger-threshold-cmvMeanMin"); + mTriggerTimebinMin = ic.options().get("trigger-threshold-timebinMin"); + mTriggerTimebinMax = ic.options().get("trigger-threshold-timebinMax"); + } + + void run(o2::framework::ProcessingContext& pc) final + { + LOGP(debug, "Processing CMVs for TF {} for CRUs {} to {}", processing_helpers::getCurrentTF(pc), mCRUs.front(), mCRUs.back()); + + ++mCountTFsForBuffer; + + // Capture heartbeatOrbit / heartbeatBC from the first TF in the buffer + if (mCountTFsForBuffer == 1) { + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mOrbitFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const uint32_t cru = hdr->subSpecification >> 7; + if (mFirstOrbitBC.find(cru) == mFirstOrbitBC.end()) { + auto orbitVec = pc.inputs().get>(ref); + if (!orbitVec.empty()) { + mFirstOrbitBC[cru] = orbitVec[0]; // packed: orbit<<32 | bc + } + } + } + } + + bool triggered = false; + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { + auto const* tpcCRUHeader = o2::framework::DataRefUtils::getHeader(ref); + const uint32_t cru = tpcCRUHeader->subSpecification >> 7; + auto vecCMVs = pc.inputs().get>(ref); + mCMVs[cru].insert(mCMVs[cru].end(), vecCMVs.begin(), vecCMVs.end()); + + const bool cruTriggered = mEnableTrigger && evaluateTrigger(vecCMVs); + if (!mTriggerPerFLP) { + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVTrigger(), tpcCRUHeader->subSpecification}, cruTriggered); + } else { + triggered |= cruTriggered; + } + } + if (mTriggerPerFLP) { + const header::DataHeader::SubSpecificationType trigSubSpec{mCRUs.front() << 7}; + pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVTrigger(), trigSubSpec}, triggered); + } + + if (mCountTFsForBuffer >= mNTFsBuffer) { + mCountTFsForBuffer = 0; + for (const auto cru : mCRUs) { + LOGP(debug, "Sending CMVs of size {} for TF {}", mCMVs[cru].size(), processing_helpers::getCurrentTF(pc)); + sendOutput(pc.outputs(), cru); + } + mFirstOrbitBC.clear(); + } + + if (mDumpCMVs) { + TFile fOut(fmt::format("CMVs_{}_tf_{}.root", mLane, processing_helpers::getCurrentTF(pc)).data(), "RECREATE"); + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { + auto const* tpcCRUHeader = o2::framework::DataRefUtils::getHeader(ref); + const int cru = tpcCRUHeader->subSpecification >> 7; + auto vec = pc.inputs().get>(ref); + fOut.WriteObject(&vec, fmt::format("CRU_{}", cru).data()); + } + } + } + + void endOfStream(o2::framework::EndOfStreamContext& ec) final + { + if (mCountTFsForBuffer > 0) { + LOGP(info, "Flushing remaining {} buffered TFs at end of stream", mCountTFsForBuffer); + for (const auto cru : mCRUs) { + sendOutput(ec.outputs(), cru); + } + } + ec.services().get().readyToQuit(o2::framework::QuitRequest::Me); + } + + static constexpr header::DataDescription getDataDescriptionCMVGroup() { return header::DataDescription{"CMVGROUP"}; } + + /// Data description for the packed (orbit<<32|bc) scalar forwarded alongside each CRU's CMVGROUP. + static constexpr header::DataDescription getDataDescriptionCMVOrbitInfo() { return header::DataDescription{"CMVORBITINFO"}; } + + /// Data description for the per-CRU per-TF trigger flag (empty span = not triggered or disabled; {1} = triggered). + static constexpr header::DataDescription getDataDescriptionCMVTrigger() { return header::DataDescription{"CMVTRIGGER"}; } + + private: + const int mLane{}; ///< lane number of processor + const std::vector mCRUs{}; ///< CRUs to process in this instance + int mNTFsBuffer{1}; ///< number of TFs to buffer before sending + bool mDumpCMVs{}; ///< dump CMVs to file for debugging + bool mTriggerPerFLP{false}; ///< send per-FLP trigger decision aggregated over CRUs + int mCountTFsForBuffer{0}; ///< counts TFs to track when to send output + std::unordered_map> mCMVs{}; ///< buffered raw 16-bit CMV values per CRU + std::unordered_map mFirstOrbitBC{}; ///< first packed orbit/BC per CRU for the current buffer window + bool mEnableTrigger{false}; ///< enable CMV trigger evaluation + float mTriggerThresholdCMV{-10.f}; ///< CMV value threshold: trigger sequence starts when value drops below this + float mTriggerThresholdMeanMax{-40.f}; ///< upper bound on trigger-sequence mean CMV value + float mTriggerThresholdMeanMin{-80.f}; ///< lower bound on trigger-sequence mean CMV value + int mTriggerTimebinMin{4}; ///< minimum trigger-sequence length (timebins) to accept + int mTriggerTimebinMax{-1}; ///< maximum trigger-sequence length (timebins) to accept; -1 disables + + /// Filter for CMV float vectors (one CMVVECTOR message per CRU per TF) + const std::vector mFilter = {{"cmvs", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "CMVVECTOR"}, o2::framework::Lifetime::Timeframe}}; + /// Filter for CMV packet timing info (one CMVORBITS message per CRU per TF, sent by CMVToVectorSpec) + const std::vector mOrbitFilter = {{"cmvorbits", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "CMVORBITS"}, o2::framework::Lifetime::Timeframe}}; + + // Scan a CRU's CMV vector for contiguous below-threshold sequences. + // Returns true as soon as one sequence satisfies both the length and mean criteria. + bool evaluateTrigger(const o2::pmr::vector& cmvs) const + { + float seqSum = 0.f; + int seqLen = 0; + + auto checkSequence = [&]() -> bool { + if (seqLen == 0) { + return false; + } + const float mean = seqSum / seqLen; + return (seqLen >= mTriggerTimebinMin) && + (mTriggerTimebinMax < 0 || seqLen <= mTriggerTimebinMax) && + (mean >= mTriggerThresholdMeanMin) && + (mean <= mTriggerThresholdMeanMax); + }; + + for (const auto raw : cmvs) { + const float val = cmv::Data{raw}.getCMVFloat(); + if (val < mTriggerThresholdCMV) { + seqSum += val; + ++seqLen; + } else { + if (checkSequence()) { + return true; + } + seqLen = 0; + seqSum = 0.f; + } + } + return checkSequence(); // trailing sequence that reached end of buffer + } + + void sendOutput(o2::framework::DataAllocator& output, const uint32_t cru) + { + const header::DataHeader::SubSpecificationType subSpec{cru << 7}; + + // Forward the first-TF orbit/BC for this CRU (0 if unavailable for any reason) + uint64_t orbitBC = 0; + if (auto it = mFirstOrbitBC.find(cru); it != mFirstOrbitBC.end()) { + orbitBC = it->second; + } + output.snapshot(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVOrbitInfo(), subSpec}, orbitBC); + + output.adoptContainer(o2::framework::Output{o2::header::gDataOriginTPC, getDataDescriptionCMVGroup(), subSpec}, std::move(mCMVs[cru])); + } +}; + +o2::framework::DataProcessorSpec getTPCFLPCMVSpec(const int ilane, const std::vector& crus, const bool triggerPerFlp, const int nTFsBuffer = 1) +{ + std::vector outputSpecs; + std::vector inputSpecs; + outputSpecs.reserve(crus.size() * 2 + 1); + inputSpecs.reserve(crus.size() * 2); + + for (const auto& cru : crus) { + const header::DataHeader::SubSpecificationType subSpec{cru << 7}; + + // Inputs from CMVToVectorSpec + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvs", o2::header::gDataOriginTPC, "CMVVECTOR", subSpec, o2::framework::Lifetime::Timeframe}); + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvorbits", o2::header::gDataOriginTPC, "CMVORBITS", subSpec, o2::framework::Lifetime::Timeframe}); + + // Outputs to TPCDistributeCMVSpec + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVGroup(), subSpec}, o2::framework::Lifetime::Sporadic); + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVOrbitInfo(), subSpec}, o2::framework::Lifetime::Sporadic); + + if (!triggerPerFlp) { + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVTrigger(), subSpec}, o2::framework::Lifetime::Timeframe); + } + } + if (triggerPerFlp) { // Single per-FLP trigger output, subspec keyed on the first CRU + const header::DataHeader::SubSpecificationType trigSubSpec{crus.front() << 7}; + outputSpecs.emplace_back(o2::framework::ConcreteDataMatcher{o2::header::gDataOriginTPC, TPCFLPCMVDevice::getDataDescriptionCMVTrigger(), trigSubSpec}, o2::framework::Lifetime::Timeframe); + } + + const auto id = fmt::format("tpc-flp-cmv-{:02}", ilane); + return o2::framework::DataProcessorSpec{ + id.data(), + inputSpecs, + outputSpecs, + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(ilane, crus, triggerPerFlp, nTFsBuffer)}, + o2::framework::Options{ + {"dump-cmvs-flp", o2::framework::VariantType::Bool, false, {"Dump CMVs to file"}}, + {"trigger", o2::framework::VariantType::Bool, false, {"Enable CMV trigger evaluation"}}, + {"trigger-threshold-cmv", o2::framework::VariantType::Float, -10.f, {"CMV threshold: sequence starts when value drops below this (ADC units)"}}, + {"trigger-threshold-cmvMeanMax", o2::framework::VariantType::Float, -40.f, {"Upper bound on trigger-sequence mean CMV value"}}, + {"trigger-threshold-cmvMeanMin", o2::framework::VariantType::Float, -80.f, {"Lower bound on trigger-sequence mean CMV value"}}, + {"trigger-threshold-timebinMin", o2::framework::VariantType::Int, 4, {"Minimum trigger-sequence length in timebins"}}, + {"trigger-threshold-timebinMax", o2::framework::VariantType::Int, -1, {"Maximum trigger-sequence length in timebins (-1 disables upper bound)"}}}}; +} + +} // namespace o2::tpc +#endif diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFourierTransformAggregatorSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFourierTransformAggregatorSpec.h index 35f51dd489115..7facee78fb3d6 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFourierTransformAggregatorSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFourierTransformAggregatorSpec.h @@ -64,6 +64,8 @@ class TPCFourierTransformAggregatorSpec : public o2::framework::Task mLengthIDCScalerSeconds = ic.options().get("tpcScalerLengthS"); mDisableScaler = ic.options().get("disable-scaler"); mEnableFFTCCDB = ic.options().get("enable-fft-CCDB"); + int nthreads = ic.options().get("nthreads"); + TPCFourierTransformAggregatorSpec::IDCFType::setNThreads(nthreads); resizeBuffer(mInputLanes); } @@ -448,7 +450,8 @@ DataProcessorSpec getTPCFourierTransformAggregatorSpec(const unsigned int rangeI {"dump-coefficients-agg", VariantType::Bool, false, {"Dump fourier coefficients to file"}}, {"tpcScalerLengthS", VariantType::Float, 300.f, {"Length of the TPC scalers in seconds"}}, {"disable-scaler", VariantType::Bool, false, {"Disable creation of IDC scaler"}}, - {"enable-fft-CCDB", VariantType::Bool, false, {"Enable writing of FFT coefficients to CCDB"}}}}; + {"enable-fft-CCDB", VariantType::Bool, false, {"Enable writing of FFT coefficients to CCDB"}}, + {"nthreads", VariantType::Int, 1, {"Number of threads which will be used during the calculation of the fourier coefficients."}}}}; } } // namespace o2::tpc diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCRefitter.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCRefitter.h index 31a5ce756142a..7add8aecbb85a 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCRefitter.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCRefitter.h @@ -15,15 +15,10 @@ #include "ReconstructionDataFormats/GlobalTrackID.h" #include "Framework/DataProcessorSpec.h" -namespace o2::tpc -{ -struct CorrectionMapsLoaderGloOpts; -} - namespace o2::trackstudy { /// create a processor spec -o2::framework::DataProcessorSpec getTPCRefitterSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, bool requestCosmics = false); +o2::framework::DataProcessorSpec getTPCRefitterSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, bool requestCosmics = false); } // namespace o2::trackstudy diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCScalerSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCScalerSpec.h index b85a882870ecb..1208ae4cd2144 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCScalerSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCScalerSpec.h @@ -13,13 +13,14 @@ #define O2_TPC_TPCSCALER_SPEC #include "Framework/DataProcessorSpec.h" +#include "TPCCalibration/CorrectionMapsLoader.h" namespace o2 { namespace tpc { -o2::framework::DataProcessorSpec getTPCScalerSpec(bool enableIDCs, bool enableMShape); +o2::framework::DataProcessorSpec getTPCScalerSpec(bool enableIDCs, bool enableMShape, const o2::tpc::CorrectionMapsGloOpts& sclOpts); } // end namespace tpc } // end namespace o2 diff --git a/Detectors/TPC/workflow/src/CMVToVectorSpec.cxx b/Detectors/TPC/workflow/src/CMVToVectorSpec.cxx new file mode 100644 index 0000000000000..86cf4ca97aa19 --- /dev/null +++ b/Detectors/TPC/workflow/src/CMVToVectorSpec.cxx @@ -0,0 +1,426 @@ +// 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 CMVToVectorSpec.cxx +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief Processor to convert CMVs to a vector in a CRU + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TFile.h" +#include "DetectorsRaw/RDHUtils.h" +#include "Framework/Task.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/Logger.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/WorkflowSpec.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/DataRefUtils.h" +#include "DPLUtils/RawParser.h" +#include "Headers/DataHeader.h" +#include "Headers/DataHeaderHelpers.h" +#include "CommonUtils/TreeStreamRedirector.h" + +#include "DataFormatsTPC/CMV.h" +#include "DataFormatsTPC/RawDataTypes.h" +#include "TPCBase/RDHUtils.h" +#include "TPCBase/Mapper.h" +#include "TPCWorkflow/ProcessingHelpers.h" + +using namespace o2::framework; +using o2::header::gDataOriginTPC; +using RDHUtils = o2::raw::RDHUtils; +using RawDataType = o2::tpc::raw_data_types::Type; + +namespace o2::tpc +{ + +class CMVToVectorDevice : public o2::framework::Task +{ + public: + using FEEIDType = rdh_utils::FEEIDType; + CMVToVectorDevice(const std::vector& crus) : mCRUs(crus) {} + + void init(o2::framework::InitContext& ic) final + { + // set up ADC value filling + mWriteDebug = ic.options().get("write-debug"); + mWriteDebugOnError = ic.options().get("write-debug-on-error"); + mWriteRawDataOnError = ic.options().get("write-raw-data-on-error"); + mRawDataType = ic.options().get("raw-data-type"); + o2::framework::RawParser<>::setCheckIncompleteHBF(ic.options().get("check-incomplete-hbf")); + + mDebugStreamFileName = ic.options().get("debug-file-name").data(); + mRawOutputFileName = ic.options().get("raw-file-name").data(); + + initCMV(); + } + + void run(o2::framework::ProcessingContext& pc) final + { + const auto runNumber = processing_helpers::getRunNumber(pc); + std::vector filter = {{"check", ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "RAWDATA"}, Lifetime::Timeframe}}; + + // open files if necessary + if ((mWriteDebug || mWriteDebugOnError) && !mDebugStream) { + const auto debugFileName = fmt::format(fmt::runtime(mDebugStreamFileName), fmt::arg("run", runNumber)); + LOGP(info, "Creating debug stream {}", debugFileName); + mDebugStream = std::make_unique(debugFileName.data(), "recreate"); + } + + if (mWriteRawDataOnError && !mRawOutputFile.is_open()) { + std::string_view rawType = (mRawDataType < 2) ? "tf" : "raw"; + if (mRawDataType == 5) { + rawType = "cmv.raw"; + } + const auto rawFileName = fmt::format(fmt::runtime(mRawOutputFileName), fmt::arg("run", runNumber), fmt::arg("raw_type", rawType)); + LOGP(info, "Creating raw debug file {}", rawFileName); + mRawOutputFile.open(rawFileName, std::ios::binary); + } + + uint32_t tfCounter = 0; + bool hasErrors = false; + + for (auto const& ref : InputRecordWalker(pc.inputs(), filter)) { + const auto* dh = DataRefUtils::getHeader(ref); + tfCounter = dh->tfCounter; + const auto subSpecification = dh->subSpecification; + auto payloadSize = DataRefUtils::getPayloadSize(ref); + LOGP(debug, "Processing TF {}, subSpecification {}, payloadSize {}", tfCounter, subSpecification, payloadSize); + + // ---| data loop |--- + const gsl::span raw = pc.inputs().get>(ref); + try { + o2::framework::RawParser parser(raw.data(), raw.size()); + size_t lastErrorCount = 0; + + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + const auto size = it.size(); + + if (parser.getNErrors() > lastErrorCount) { + lastErrorCount = parser.getNErrors(); + hasErrors = true; + } + + // skip empty packages (HBF open) + if (size == 0) { + continue; + } + + auto rdhPtr = reinterpret_cast(it.raw()); + const auto rdhVersion = RDHUtils::getVersion(rdhPtr); + if (!rdhPtr || rdhVersion < 6) { + throw std::runtime_error(fmt::format("could not get RDH from packet, or version {} < 6", rdhVersion).data()); + } + + // ---| extract hardware information to do the processing |--- + const auto feeId = (FEEIDType)RDHUtils::getFEEID(*rdhPtr); + const auto link = rdh_utils::getLink(feeId); + const uint32_t cruID = rdh_utils::getCRU(feeId); + const auto detField = RDHUtils::getDetectorField(*rdhPtr); + + LOGP(debug, "Detected CMV packet: CRU {}, link {}, feeId {}", cruID, link, feeId); + + if ((detField != (decltype(detField))RawDataType::CMV) || (link != rdh_utils::CMVLinkID)) { + LOGP(debug, "Skipping packet: detField {}, (expected RawDataType {}), link {}, (expected CMVLinkID {})", detField, (decltype(detField))RawDataType::CMV, link, rdh_utils::CMVLinkID); + continue; + } + + LOGP(debug, "Processing firstTForbit {:9}, tfCounter {:5}, run {:6}, feeId {:6}, cruID {:3}, link {:2}", dh->firstTForbit, dh->tfCounter, dh->runNumber, feeId, cruID, link); + + if (std::find(mCRUs.begin(), mCRUs.end(), cruID) == mCRUs.end()) { + // LOGP(debug, "CMV CRU {:3} not configured in CRUs, skipping", cruID); + continue; + } + + auto& cmvVec = mCMVvectors[cruID]; + auto& infoVec = mCMVInfos[cruID]; + + if (size != sizeof(cmv::Container)) { + LOGP(warning, "CMV packet size mismatch: got {} bytes, expected {} bytes (sizeof cmv::Container). Skipping package.", size, sizeof(cmv::Container)); + hasErrors = true; + continue; + } + auto data = it.data(); + auto& cmvs = *((cmv::Container*)(data)); + const uint32_t orbit = cmvs.header.heartbeatOrbit; + const uint16_t bc = cmvs.header.heartbeatBC; + + // record packet meta and append its CMV vector (3564 TB) + infoVec.emplace_back(orbit, bc); + cmvVec.reserve(cmvVec.size() + cmv::NTimeBinsPerPacket); + for (uint32_t tb = 0; tb < cmv::NTimeBinsPerPacket; ++tb) { + cmvVec.push_back(cmvs.getCMV(tb)); + // LOGP(debug, "For CRU {}, timebin {}, orbit {}, bc {}, appended CMV {} float: {}", cruID, tb, orbit, bc, cmvs.getCMV(tb), cmvs.getCMVFloat(tb)); + } + } + } catch (const std::exception& e) { + // error message throtteling + using namespace std::literals::chrono_literals; + static std::unordered_map nErrorPerSubspec; + static std::chrono::time_point lastReport = std::chrono::steady_clock::now(); + const auto now = std::chrono::steady_clock::now(); + static size_t reportedErrors = 0; + const size_t MAXERRORS = 10; + const auto sleepTime = 10min; + ++nErrorPerSubspec[subSpecification]; + + if ((now - lastReport) < sleepTime) { + if (reportedErrors < MAXERRORS) { + ++reportedErrors; + std::string sleepInfo; + if (reportedErrors == MAXERRORS) { + sleepInfo = fmt::format(", maximum error count ({}) reached, not reporting for the next {}", MAXERRORS, sleepTime); + } + LOGP(alarm, "EXCEPTION in processRawData: {} -> skipping part:{}/{} of spec:{}/{}/{}, size:{}, error count for subspec: {}{}", e.what(), dh->splitPayloadIndex, dh->splitPayloadParts, + dh->dataOrigin, dh->dataDescription, subSpecification, payloadSize, nErrorPerSubspec.at(subSpecification), sleepInfo); + lastReport = now; + } + } else { + lastReport = now; + reportedErrors = 0; + } + continue; + } + } + + hasErrors |= snapshotCMVs(pc.outputs()); + + if (mWriteDebug || (mWriteDebugOnError && hasErrors)) { + writeDebugOutput(tfCounter); + } + + if (mWriteRawDataOnError && hasErrors) { + writeRawData(pc.inputs()); + } + + // clear output + initCMV(); + } + + void closeFiles() + { + LOGP(info, "closeFiles"); + + if (mDebugStream) { + // set some default aliases + auto& stream = (*mDebugStream) << "cmvs"; + auto& tree = stream.getTree(); + tree.SetAlias("sector", "int(cru/10)"); + mDebugStream->Close(); + mDebugStream.reset(nullptr); + mRawOutputFile.close(); + } + } + + void stop() final + { + LOGP(info, "stop"); + closeFiles(); + } + + void endOfStream(o2::framework::EndOfStreamContext& ec) final + { + LOGP(info, "endOfStream"); + // ec.services().get().readyToQuit(QuitRequest::Me); + closeFiles(); + } + + private: + /// CMV information for each cru + struct CMVInfo { + CMVInfo() = default; + CMVInfo(const CMVInfo&) = default; + CMVInfo(uint32_t orbit, uint16_t bc) : heartbeatOrbit(orbit), heartbeatBC(bc) {} + + uint32_t heartbeatOrbit{0}; + uint16_t heartbeatBC{0}; + + bool operator==(const uint32_t orbit) const { return (heartbeatOrbit == orbit); } + bool operator==(const CMVInfo& inf) const { return (inf.heartbeatOrbit == heartbeatOrbit) && (inf.heartbeatBC == heartbeatBC); } + bool matches(uint32_t orbit, int16_t bc) const { return ((heartbeatOrbit == orbit) && (heartbeatBC == bc)); } + }; + + int mRawDataType{0}; ///< type of raw data to dump in case of errors + bool mWriteDebug{false}; ///< write a debug output + bool mWriteDebugOnError{false}; ///< write a debug output in case of errors + bool mWriteRawDataOnError{false}; ///< write raw data in case of errors + std::vector mCRUs; ///< CRUs expected for this device + std::unordered_map> mCMVvectors; ///< raw 16-bit CMV values per cru over all CMV packets in the TF + std::unordered_map> mCMVInfos; ///< CMV packet information within the TF + std::string mDebugStreamFileName; ///< name of the debug stream output file + std::unique_ptr mDebugStream; ///< debug output streamer + std::ofstream mRawOutputFile; ///< raw output file + std::string mRawOutputFileName; ///< name of the raw output file + + //____________________________________________________________________________ + bool snapshotCMVs(DataAllocator& output) + { + bool hasErrors = false; + + // send data per CRU with its own orbit/BC vector + for (auto& [cru, cmvVec] : mCMVvectors) { + const header::DataHeader::SubSpecificationType subSpec{cru << 7}; + const auto& infVec = mCMVInfos[cru]; + + if (infVec.size() != 4) { + // LOGP(error, "CRU {:3}: expected 4 packets per TF, got {}", cru, infVec.size()); + hasErrors = true; + } + if (cmvVec.size() != cmv::NTimeBinsPerPacket * infVec.size()) { + // LOGP(error, "CRU {:3}: vector size {} does not match expected {}", cru, cmvVec.size(), cmv::NTimeBinsPerPacket * infVec.size()); + hasErrors = true; + } + + std::vector orbitBCInfo; + orbitBCInfo.reserve(infVec.size()); + for (const auto& inf : infVec) { + orbitBCInfo.emplace_back((uint64_t(inf.heartbeatOrbit) << 32) + uint64_t(inf.heartbeatBC)); + } + + LOGP(debug, "Sending CMVs for CRU {} of size {} ({} packets)", cru, cmvVec.size(), infVec.size()); + output.snapshot(Output{gDataOriginTPC, "CMVVECTOR", subSpec}, cmvVec); + output.snapshot(Output{gDataOriginTPC, "CMVORBITS", subSpec}, orbitBCInfo); + } + + return hasErrors; + } + + //____________________________________________________________________________ + void initCMV() + { + for (const auto cruID : mCRUs) { + auto& cmvVec = mCMVvectors[cruID]; + cmvVec.clear(); + + auto& infosCRU = mCMVInfos[cruID]; + infosCRU.clear(); + } + } + + //____________________________________________________________________________ + void writeDebugOutput(uint32_t tfCounter) + { + mDebugStream->GetFile()->cd(); + auto& stream = (*mDebugStream) << "cmvs"; + + for (auto cru : mCRUs) { + if (mCMVInfos.find(cru) == mCMVInfos.end()) { + continue; + } + + auto& infos = mCMVInfos[cru]; + auto& cmvVec = mCMVvectors[cru]; + + stream << "cru=" << cru + << "tfCounter=" << tfCounter + << "nCMVs=" << cmvVec.size() + << "cmvs=" << cmvVec + << "\n"; + } + } + + void writeRawData(InputRecord& inputs) + { + if (!mRawOutputFile.is_open()) { + return; + } + + using DataHeader = o2::header::DataHeader; + + std::vector filter = {{"check", ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "RAWDATA"}, Lifetime::Timeframe}}; + for (auto const& ref : InputRecordWalker(inputs, filter)) { + auto dh = DataRefUtils::getHeader(ref); + // LOGP(info, "write header: {}/{}/{}, payload size: {} / {}", dh->dataOrigin, dh->dataDescription, dh->subSpecification, dh->payloadSize, ref.payloadSize); + if (((mRawDataType == 1) || (mRawDataType == 3)) && (dh->payloadSize == 2 * sizeof(o2::header::RAWDataHeader))) { + continue; + } + + if (mRawDataType < 2) { + mRawOutputFile.write(ref.header, sizeof(DataHeader)); + } + if (mRawDataType < 5) { + mRawOutputFile.write(ref.payload, ref.payloadSize); + } + + if (mRawDataType == 5) { + const gsl::span raw = inputs.get>(ref); + try { + o2::framework::RawParser parser(raw.data(), raw.size()); + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + const auto size = it.size(); + // skip empty packages (HBF open) + if (size == 0) { + continue; + } + + auto rdhPtr = reinterpret_cast(it.raw()); + const auto rdhVersion = RDHUtils::getVersion(rdhPtr); + if (!rdhPtr || rdhVersion < 6) { + throw std::runtime_error(fmt::format("could not get RDH from packet, or version {} < 6", rdhVersion).data()); + } + + // ---| extract hardware information to do the processing |--- + const auto feeId = (FEEIDType)RDHUtils::getFEEID(*rdhPtr); + const auto link = rdh_utils::getLink(feeId); + const auto detField = RDHUtils::getDetectorField(*rdhPtr); + + // only select CMVs + if ((detField != (decltype(detField))RawDataType::CMV) || (link != rdh_utils::CMVLinkID)) { + continue; + } + + // write out raw data + mRawOutputFile.write((const char*)it.raw(), RDHUtils::getMemorySize(rdhPtr)); + } + } catch (...) { + } + } + } + } +}; + +o2::framework::DataProcessorSpec getCMVToVectorSpec(std::string const& inputSpec, std::vector const& crus) +{ + using device = o2::tpc::CMVToVectorDevice; + + std::vector outputs; + for (const uint32_t cru : crus) { + const header::DataHeader::SubSpecificationType subSpec{cru << 7}; + outputs.emplace_back(gDataOriginTPC, "CMVVECTOR", subSpec, Lifetime::Timeframe); + outputs.emplace_back(gDataOriginTPC, "CMVORBITS", subSpec, Lifetime::Timeframe); + } + + return DataProcessorSpec{ + fmt::format("tpc-cmv-to-vector"), + select(inputSpec.data()), + outputs, + AlgorithmSpec{adaptFromTask(crus)}, + Options{ + {"write-debug", VariantType::Bool, false, {"write a debug output tree"}}, + {"write-debug-on-error", VariantType::Bool, false, {"write a debug output tree in case errors occurred"}}, + {"debug-file-name", VariantType::String, "/tmp/cmv_vector_debug.{run}.root", {"name of the debug output file"}}, + {"write-raw-data-on-error", VariantType::Bool, false, {"dump raw data in case errors occurred"}}, + {"raw-file-name", VariantType::String, "/tmp/cmv_debug.{run}.{raw_type}", {"name of the raw output file"}}, + {"raw-data-type", VariantType::Int, 0, {"Which raw data to dump: 0-full TPC with DH, 1-full TPC with DH skip empty, 2-full TPC no DH, 3-full TPC no DH skip empty, 4-IDC raw only 5-CMV raw only"}}, + {"check-incomplete-hbf", VariantType::Bool, false, {"false: don't check; true: check and report"}}, + } // end Options + }; // end DataProcessorSpec +} +} // namespace o2::tpc \ No newline at end of file diff --git a/Detectors/TPC/workflow/src/EntropyEncoderSpec.cxx b/Detectors/TPC/workflow/src/EntropyEncoderSpec.cxx index 73bdfa1905f3b..4de5665f1b9a0 100644 --- a/Detectors/TPC/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/TPC/workflow/src/EntropyEncoderSpec.cxx @@ -158,7 +158,7 @@ void EntropyEncoderSpec::run(ProcessingContext& pc) const auto& tinfo = pc.services().get(); const auto firstIR = o2::InteractionRecord(0, tinfo.firstTForbit); - const float totalT = std::max(mFastTransform->getMaxDriftTime(0), mFastTransform->getMaxDriftTime(GPUCA_NSECTORS / 2)); + const float totalT = std::max(mFastTransform->getMaxDriftTime(0), mFastTransform->getMaxDriftTime(GPUTPCGeometry::NSECTORS / 2)); unsigned int offset = 0, lasti = 0; const unsigned int maxTime = (mParam->continuousMaxTimeBin + 1) * o2::tpc::ClusterNative::scaleTimePacked - 1; @@ -205,23 +205,23 @@ void EntropyEncoderSpec::run(ProcessingContext& pc) } } offset = 0; - unsigned int offsets[GPUCA_NSECTORS][GPUCA_ROW_COUNT]; - for (unsigned int i = 0; i < GPUCA_NSECTORS; i++) { - for (unsigned int j = 0; j < GPUCA_ROW_COUNT; j++) { - if (i * GPUCA_ROW_COUNT + j >= clusters.nSliceRows) { + unsigned int offsets[GPUTPCGeometry::NSECTORS][GPUTPCGeometry::NROWS]; + for (unsigned int i = 0; i < GPUTPCGeometry::NSECTORS; i++) { + for (unsigned int j = 0; j < GPUTPCGeometry::NROWS; j++) { + if (i * GPUTPCGeometry::NROWS + j >= clusters.nSliceRows) { break; } offsets[i][j] = offset; - offset += (i * GPUCA_ROW_COUNT + j >= clusters.nSliceRows) ? 0 : clusters.nSliceRowClusters[i * GPUCA_ROW_COUNT + j]; + offset += (i * GPUTPCGeometry::NROWS + j >= clusters.nSliceRows) ? 0 : clusters.nSliceRowClusters[i * GPUTPCGeometry::NROWS + j]; } } #ifdef WITH_OPENMP -#pragma omp parallel for num_threads(mNThreads) schedule(static, (GPUCA_NSECTORS + mNThreads - 1) / mNThreads) // Static round-robin scheduling with one chunk per thread to ensure correct order of the final vector +#pragma omp parallel for num_threads(mNThreads) schedule(static, (GPUTPCGeometry::NSECTORS + mNThreads - 1) / mNThreads) // Static round-robin scheduling with one chunk per thread to ensure correct order of the final vector #endif for (unsigned int ii = 0; ii < clusters.nSliceRows; ii++) { - unsigned int i = ii / GPUCA_ROW_COUNT; - unsigned int j = ii % GPUCA_ROW_COUNT; + unsigned int i = ii / GPUTPCGeometry::NROWS; + unsigned int j = ii % GPUTPCGeometry::NROWS; o2::tpc::ClusterNative preCl; #ifdef WITH_OPENMP int myThread = omp_get_thread_num(); @@ -240,7 +240,7 @@ void EntropyEncoderSpec::run(ProcessingContext& pc) const bool reject = mCTFCoder.getIRFramesSelector().check(o2::dataformats::IRFrame(chkVal, chkVal + 1), chkExt, 0) < 0; if (reject) { rejectHits[k] = true; - clustersFiltered.nSliceRowClusters[i * GPUCA_ROW_COUNT + j]--; + clustersFiltered.nSliceRowClusters[i * GPUTPCGeometry::NROWS + j]--; static std::atomic_flag lock = ATOMIC_FLAG_INIT; while (lock.test_and_set(std::memory_order_acquire)) { } @@ -253,7 +253,7 @@ void EntropyEncoderSpec::run(ProcessingContext& pc) preCl = cl; } }; - unsigned int end = offsets[i][j] + clusters.nSliceRowClusters[i * GPUCA_ROW_COUNT + j]; + unsigned int end = offsets[i][j] + clusters.nSliceRowClusters[i * GPUTPCGeometry::NROWS + j]; o2::gpu::TPCClusterDecompressionCore::decompressHits(clusters, offsets[i][j], end, checker); } tmpBuffer[0].first.reserve(clustersFiltered.nUnattachedClusters); diff --git a/Detectors/TPC/workflow/src/RecoWorkflow.cxx b/Detectors/TPC/workflow/src/RecoWorkflow.cxx index 3054dd5d61519..355bd0cb290f7 100644 --- a/Detectors/TPC/workflow/src/RecoWorkflow.cxx +++ b/Detectors/TPC/workflow/src/RecoWorkflow.cxx @@ -100,7 +100,7 @@ const std::unordered_map OutputMap{ {"tpc-triggers", OutputType::TPCTriggers}}; framework::WorkflowSpec getWorkflow(CompletionPolicyData* policyData, std::vector const& tpcSectors, unsigned long tpcSectorMask, std::vector const& laneConfiguration, - const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, bool propagateMC, unsigned nLanes, std::string const& cfgInput, std::string const& cfgOutput, bool disableRootInput, + const o2::tpc::CorrectionMapsGloOpts& sclOpts, bool propagateMC, unsigned nLanes, std::string const& cfgInput, std::string const& cfgOutput, bool disableRootInput, int caClusterer, int zsOnTheFly, bool askDISTSTF, const std::string& ctfdictOpt, bool selIR, bool filteredInp, int deadMapSources, bool useMCTimeGain) { InputType inputType; @@ -201,9 +201,7 @@ framework::WorkflowSpec getWorkflow(CompletionPolicyData* policyData, std::vecto laneConfiguration, &hook}, propagateMC)); - if (sclOpts.needTPCScalersWorkflow()) { // for standalone tpc-reco workflow - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpts.lumiType == 2, sclOpts.enableMShapeCorrection)); - } + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpts.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpts.enableMShapeCorrection, sclOpts)); if (produceTracks && sclOpts.requestCTPLumi) { // need CTP digits (lumi) reader specs.emplace_back(o2::ctp::getDigitsReaderSpec(false)); } @@ -225,9 +223,7 @@ framework::WorkflowSpec getWorkflow(CompletionPolicyData* policyData, std::vecto if (!getenv("DPL_DISABLE_TPC_TRIGGER_READER") || atoi(getenv("DPL_DISABLE_TPC_TRIGGER_READER")) != 1) { specs.emplace_back(o2::tpc::getTPCTriggerReaderSpec()); } - if (sclOpts.needTPCScalersWorkflow()) { // for standalone tpc-reco workflow - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpts.lumiType == 2, sclOpts.enableMShapeCorrection)); - } + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpts.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpts.enableMShapeCorrection, sclOpts)); if (sclOpts.requestCTPLumi) { // need CTP digits (lumi) reader specs.emplace_back(o2::ctp::getDigitsReaderSpec(false)); } @@ -461,11 +457,6 @@ framework::WorkflowSpec getWorkflow(CompletionPolicyData* policyData, std::vecto if (runGPUReco) { o2::gpu::GPURecoWorkflowSpec::Config cfg; cfg.runTPCTracking = true; - cfg.lumiScaleType = sclOpts.lumiType; - cfg.lumiScaleMode = sclOpts.lumiMode; - cfg.checkCTPIDCconsistency = sclOpts.checkCTPIDCconsistency; - cfg.enableMShape = sclOpts.enableMShapeCorrection; - cfg.enableCTPLumi = sclOpts.requestCTPLumi; cfg.decompressTPC = decompressTPC; cfg.decompressTPCFromROOT = decompressTPC && inputType == InputType::CompClustersRoot; cfg.caClusterer = caClusterer; diff --git a/Detectors/TPC/workflow/src/TPCRefitter.cxx b/Detectors/TPC/workflow/src/TPCRefitter.cxx index 43a55526246fe..4b877f56c90fd 100644 --- a/Detectors/TPC/workflow/src/TPCRefitter.cxx +++ b/Detectors/TPC/workflow/src/TPCRefitter.cxx @@ -17,11 +17,12 @@ #include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" #include "SimulationDataFormat/MCCompLabel.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCFastTransformPOD.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DetectorsBase/Propagator.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/ControlService.h" +#include "Framework/DeviceSpec.h" #include "Framework/Task.h" #include "MathUtils/Tsallis.h" #include "DetectorsCommonDataFormats/DetID.h" @@ -63,13 +64,8 @@ class TPCRefitterSpec final : public Task Streamer = 0x1, ///< Write per track streamer information TFVectors = 0x2, ///< Writer vectors per TF }; - TPCRefitterSpec(std::shared_ptr dr, std::shared_ptr gr, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, GTrackID::mask_t src, bool useMC) - : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC) - { - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - } + TPCRefitterSpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool useMC) + : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC) {} ~TPCRefitterSpec() final = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -83,7 +79,7 @@ class TPCRefitterSpec final : public Task std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps{nullptr}; bool mUseMC{false}; ///< MC flag bool mUseGPUModel{false}; float mXRef = 83.; @@ -101,6 +97,8 @@ class TPCRefitterSpec final : public Task int mWriteTrackClusters = 0; ///< bitmask of which cluster information to dump to the tree: 0x1 = cluster native, 0x2 = corrected cluster positions, 0x4 = uncorrected cluster positions, 0x8 occupancy info bool mDoSampling{false}; ///< perform sampling of unbinned data bool mDoRefit{true}; ///< perform refit of TPC track + bool mIgnorLegsWOGoodTime{false}; ///< ignore cosmic legs w/o TRD or TOF constraint instead of using the time of other constraned leg + bool mUseCosmicLegTiming{false}; ///< use the timestamp from the cosmic track leg instead of using cosmic track timestamp std::vector mClusterOccupancy; ///< binned occupancy of all clusters std::vector mITSTPCTrackOccupanyTPCTime; ///< binned occupancy for ITS-TPC matched tracks using the TPC track time std::vector mITSTPCTrackOccupanyCombinedTime; ///< binned occupancy for ITS-TPC matched tracks using the combined track time @@ -157,6 +155,9 @@ void TPCRefitterSpec::init(InitContext& ic) mStudyType = ic.options().get("study-type"); mWriterType = ic.options().get("writer-type"); mWriteTrackClusters = ic.options().get("write-track-clusters"); + mIgnorLegsWOGoodTime = ic.options().get("ignore-legs-wo-outer-det"); + mUseCosmicLegTiming = ic.options().get("use-cosmic-leg-timing"); + const auto occBinsPerDrift = ic.options().get("occupancy-bins-per-drift"); mTimeBinsPerTF = (o2::raw::HBFUtils::Instance().nHBFPerTF * o2::constants::lhc::LHCMaxBunches) / 8 + 2 * mTimeBinsPerDrift; // add one drift before and after the TF mOccupancyBinsPerTF = static_cast(std::ceil(float(mTimeBinsPerTF * occBinsPerDrift) / mTimeBinsPerDrift)); @@ -165,26 +166,29 @@ void TPCRefitterSpec::init(InitContext& ic) mITSTPCTrackOccupanyCombinedTime.resize(mOccupancyBinsPerTF); LOGP(info, "Using {} bins for the occupancy per TF", mOccupancyBinsPerTF); + int lane = ic.services().get().inputTimesliceId; + int maxLanes = ic.services().get().maxInputTimeslices; + auto composeName = [maxLanes, lane](const std::string& seed) { return maxLanes > 1 ? fmt::format("{}_{}.root", seed, lane) : fmt::format("{}.root", seed); }; + if ((mWriterType & WriterType::Streamer) == WriterType::Streamer) { if ((mStudyType & StudyType::TPC) == StudyType::TPC) { - mDBGOutTPC = std::make_unique("tpctracks-study-streamer.root", "recreate"); + mDBGOutTPC = std::make_unique(composeName("tpctracks-study-streamer").c_str(), "recreate"); } if ((mStudyType & StudyType::ITSTPC) == StudyType::ITSTPC) { - mDBGOutITSTPC = std::make_unique("itstpctracks-study-streamer.root", "recreate"); + mDBGOutITSTPC = std::make_unique(composeName("itstpctracks-study-streamer").c_str(), "recreate"); } if ((mStudyType & StudyType::Cosmics) == StudyType::Cosmics) { - mDBGOutCosmics = std::make_unique("cosmics-study-streamer.root", "recreate"); + mDBGOutCosmics = std::make_unique(composeName("cosmics-study-streamer").c_str(), "recreate"); } } if (ic.options().get("dump-clusters")) { - mDBGOutCl = std::make_unique("tpc-trackStudy-cl.root", "recreate"); + mDBGOutCl = std::make_unique(composeName("tpc-trackStudy-cl").c_str(), "recreate"); } if (mXRef < 0.) { mXRef = 0.; } mGenerator = std::mt19937(std::random_device{}()); - mTPCCorrMapsLoader.init(ic); } void TPCRefitterSpec::run(ProcessingContext& pc) @@ -212,29 +216,8 @@ void TPCRefitterSpec::updateTimeDependentParams(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); - static bool initOnceDone = false; - if (!initOnceDone) { // this params need to be queried only once - initOnceDone = true; - // none at the moment - } - // we may have other params which need to be queried regularly - bool updateMaps = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateMaps = true; - } - if (mTPCVDriftHelper.isUpdated()) { - LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", - mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, - mTPCVDriftHelper.getVDriftObject().timeOffsetCorr, mTPCVDriftHelper.getVDriftObject().refTimeOffset, - mTPCVDriftHelper.getSourceName()); - mTPCVDriftHelper.acknowledgeUpdate(); - updateMaps = true; - } - if (updateMaps) { - mTPCCorrMapsLoader.updateVDrift(mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getVDriftObject().getTimeOffset()); - } + auto const& raw = pc.inputs().get("corrMap"); + mTPCCorrMaps = &o2::gpu::TPCFastTransformPOD::get(raw); } void TPCRefitterSpec::fillOccupancyVectors(o2::globaltracking::RecoContainer& recoData) @@ -335,14 +318,14 @@ void TPCRefitterSpec::process(o2::globaltracking::RecoContainer& recoData) mTPCTrkLabels = recoData.getTPCTracksMCLabels(); } - mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, &mTPCCorrMapsLoader, prop->getNominalBz(), mTPCTrackClusIdx.data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size(), nullptr, prop); + mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, mTPCCorrMaps, prop->getNominalBz(), mTPCTrackClusIdx.data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size(), nullptr, prop); mVdriftTB = mTPCVDriftHelper.getVDriftObject().getVDrift() * o2::tpc::ParameterElectronics::Instance().ZbinWidth; // VDrift expressed in cm/TimeBin mTPCTBBias = mTPCVDriftHelper.getVDriftObject().getTimeOffset() / (8 * o2::constants::lhc::LHCBunchSpacingMUS); auto dumpClusters = [this] { static int tf = 0; - const auto* corrMap = this->mTPCCorrMapsLoader.getCorrMap(); + const auto* corrMap = this->mTPCCorrMaps; for (int sector = 0; sector < 36; sector++) { float alp = ((sector % 18) * 20 + 10) * TMath::DegToRad(); float sn = TMath::Sin(alp), cs = TMath::Cos(alp); @@ -413,9 +396,6 @@ void TPCRefitterSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } } bool TPCRefitterSpec::getDCAs(const o2::track::TrackPar& track, float& dcar, float& dcaz) @@ -500,7 +480,7 @@ bool TPCRefitterSpec::processTPCTrack(o2::tpc::TrackTPC tr, o2::MCCompLabel lbl, // auto prepClus = [this, &tr, &clSector, &clRow, &clX, &clY, &clZ, &clXI, &clYI, &clZI, &clNative](float t) { // extract cluster info auto prepClus = [this, &tr, &clData](float t) { // extract cluster info int count = tr.getNClusters(); - const auto* corrMap = this->mTPCCorrMapsLoader.getCorrMap(); + const auto* corrMap = this->mTPCCorrMaps; const o2::tpc::ClusterNative* cl = nullptr; for (int ic = count; ic--;) { uint8_t sector, row; @@ -525,7 +505,7 @@ bool TPCRefitterSpec::processTPCTrack(o2::tpc::TrackTPC tr, o2::MCCompLabel lbl, clData.clZI.emplace_back(z); // transformation without distortions - mTPCCorrMapsLoader.Transform(sector, row, cl->getPad(), cl->getTime(), x, y, z, t); // nominal time of the track + mTPCCorrMaps->Transform(sector, row, cl->getPad(), cl->getTime(), x, y, z, t); // nominal time of the track clData.clX.emplace_back(x); clData.clY.emplace_back(y); clData.clZ.emplace_back(z); @@ -707,40 +687,41 @@ bool TPCRefitterSpec::processTPCTrack(o2::tpc::TrackTPC tr, o2::MCCompLabel lbl, void TPCRefitterSpec::processCosmics(o2::globaltracking::RecoContainer& recoData) { - auto tof = recoData.getTOFClusters(); const auto& par = o2::tpc::ParameterElectronics::Instance(); const auto invBinWidth = 1.f / par.ZbinWidth; for (const auto& cosmic : mCosmics) { // - const auto& gidtop = cosmic.getRefTop(); - const auto& gidbot = cosmic.getRefBottom(); - - // LOGP(info, "Sources: {} - {}", o2::dataformats::GlobalTrackID::getSourceName(gidtop.getSource()), o2::dataformats::GlobalTrackID::getSourceName(gidbot.getSource())); - - std::array contributorsGID[2] = {recoData.getSingleDetectorRefs(cosmic.getRefTop()), recoData.getSingleDetectorRefs(cosmic.getRefBottom())}; - const auto trackTime = cosmic.getTimeMUS().getTimeStamp() * invBinWidth; - - // check if track has TPC & TOF for top and bottom part - // loop over both parts - for (const auto& comsmicInfo : contributorsGID) { - auto& tpcGlobal = comsmicInfo[GTrackID::TPC]; - auto& tofGlobal = comsmicInfo[GTrackID::TOF]; - if (tpcGlobal.isIndexSet() && tofGlobal.isIndexSet()) { - const auto itrTPC = tpcGlobal.getIndex(); - const auto itrTOF = tofGlobal.getIndex(); - const auto& tofCl = tof[itrTOF]; - const auto tofTime = tofCl.getTime() * 1e-6 * invBinWidth; // ps -> us -> time bins - const auto tofTimeRaw = tofCl.getTimeRaw() * 1e-6 * invBinWidth; // ps -> us -> time bins - const auto& trackTPC = mTPCTracksArray[itrTPC]; - // LOGP(info, "Cosmic time: {}, TOF time: {}, TOF time raw: {}, TPC time: {}", trackTime, tofTime, tofTimeRaw, trackTPC.getTime0()); - processTPCTrack(trackTPC, mUseMC ? mTPCTrkLabels[itrTPC] : o2::MCCompLabel{}, mDBGOutCosmics.get(), nullptr, nullptr, false, tofTime); + const GTrackID gidTopBot[] = {cosmic.getRefTop(), cosmic.getRefBottom()}; + // LOGP(info, "Sources: {} - {}", o2::dataformats::GlobalTrackID::getSourceName(gidTopBot[0].getSource()), o2::dataformats::GlobalTrackID::getSourceName(gidTopBot[1].getSource())); + // Wequire at least one TRD of TOF contribution to constrain the timestamp + bool hasGoodTime[2] = {false, false}; + std::array contributorsGID[2]; + for (int i = 0; i < 2; i++) { + contributorsGID[i] = recoData.getSingleDetectorRefs(gidTopBot[i]); + hasGoodTime[i] = gidTopBot[i].includesDet(DetID::TOF) || gidTopBot[i].includesDet(DetID::TRD); + } + if (!hasGoodTime[0] && !hasGoodTime[1]) { + continue; + } + float trackTime = cosmic.getTimeMUS().getTimeStamp() * invBinWidth; // this time corresponds to the center of top/bottom legs time-brackers intersection, i.e. should be the most precise one + + for (int i = 0; i < 2; i++) { + if (!contributorsGID[i][GTrackID::TPC].isSourceSet() || (mIgnorLegsWOGoodTime && !hasGoodTime[i])) { + continue; } + const auto& trackTPC = mTPCTracksArray[contributorsGID[i][GTrackID::TPC]]; + float useTrackTime = trackTime, dummyError = 0.f; + if (mUseCosmicLegTiming && hasGoodTime[i]) { // track out time was requested (if available) + recoData.getTrackTime(gidTopBot[i], useTrackTime, dummyError); + useTrackTime *= invBinWidth; + } + processTPCTrack(trackTPC, mUseMC ? mTPCTrkLabels[contributorsGID[i][GTrackID::TPC]] : o2::MCCompLabel{}, mDBGOutCosmics.get(), nullptr, nullptr, false, useTrackTime); } } } -DataProcessorSpec getTPCRefitterSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, bool requestCosmics) +DataProcessorSpec getTPCRefitterSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, bool requestCosmics) { std::vector outputs; Options opts{ @@ -761,6 +742,8 @@ DataProcessorSpec getTPCRefitterSpec(GTrackID::mask_t srcTracks, GTrackID::mask_ {"study-type", VariantType::Int, 1, {"Bitmask of study type: 0x1 = TPC only, 0x2 = TPC + ITS, 0x4 = Cosmics"}}, {"writer-type", VariantType::Int, 1, {"Bitmask of writer type: 0x1 = per track streamer, 0x2 = per TF vectors"}}, {"occupancy-bins-per-drift", VariantType::UInt32, 31u, {"number of bin for occupancy histogram per drift time (500tb)"}}, + {"ignore-legs-wo-outer-det", VariantType::Bool, false, {"Ignore cosmic legs w/o TRD or TOF constraint even if other leg is well constrained"}}, + {"use-cosmic-leg-timing", VariantType::Bool, false, {"Use leg-specific timestamp instead of cosmic track final timestamp"}}, }; auto dataRequest = std::make_shared(); @@ -778,13 +761,12 @@ DataProcessorSpec getTPCRefitterSpec(GTrackID::mask_t srcTracks, GTrackID::mask_ dataRequest->inputs, true); o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(dataRequest->inputs, opts, sclOpts); - + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); return DataProcessorSpec{ "tpc-refitter", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, sclOpts, srcTracks, useMC)}, + AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, useMC)}, opts}; } diff --git a/Detectors/TPC/workflow/src/TPCScalerSpec.cxx b/Detectors/TPC/workflow/src/TPCScalerSpec.cxx index f185b5e08c7e7..1df192dd5ec00 100644 --- a/Detectors/TPC/workflow/src/TPCScalerSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCScalerSpec.cxx @@ -27,6 +27,8 @@ #include "TPCCalibration/TPCFastSpaceChargeCorrectionHelper.h" #include "TPCSpaceCharge/SpaceCharge.h" #include "CommonUtils/TreeStreamRedirector.h" +#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCCalibration/VDriftHelper.h" using namespace o2::framework; @@ -38,7 +40,12 @@ namespace tpc class TPCScalerSpec : public Task { public: - TPCScalerSpec(std::shared_ptr req, bool enableIDCs, bool enableMShape) : mCCDBRequest(req), mEnableIDCs(enableIDCs), mEnableMShape(enableMShape){}; + TPCScalerSpec(std::shared_ptr req, const o2::tpc::CorrectionMapsGloOpts& sclOpts, bool enableIDCs, bool enableMShape) : mCCDBRequest(req), mEnableIDCs(enableIDCs), mEnableMShape(enableMShape), mGlobOpts(sclOpts) + { + mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); + mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); + mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); + }; void init(framework::InitContext& ic) final { @@ -57,6 +64,7 @@ class TPCScalerSpec : public Task if (enableStreamer) { mStreamer = std::make_unique("M_Shape.root", "recreate"); } + mTPCCorrMapsLoader.init(ic, mEnableIDCs); } void endOfStream(EndOfStreamContext& eos) final @@ -69,6 +77,11 @@ class TPCScalerSpec : public Task void run(ProcessingContext& pc) final { o2::base::GRPGeomHelper::instance().checkUpdates(pc); + mTPCVDriftHelper.extractCCDBInputs(pc); + if (mTPCVDriftHelper.isUpdated()) { + mTPCVDriftHelper.acknowledgeUpdate(); + } + if (mEnableIDCs && pc.inputs().isValid("tpcscaler")) { pc.inputs().get("tpcscaler"); } @@ -122,12 +135,7 @@ class TPCScalerSpec : public Task std::unique_ptr spCorrection = TPCFastSpaceChargeCorrectionHelper::instance()->createFromGlobalCorrection(getCorrections, mKnotsYMshape, mKnotsZMshape); std::unique_ptr fastTransform(TPCFastTransformHelperO2::instance()->create(0, *spCorrection)); - pc.outputs().snapshot(Output{header::gDataOriginTPC, "TPCMSHAPE"}, *fastTransform); - } else { - // send empty dummy object - LOGP(info, "Sending default (no) M-shape correction"); - auto fastTransform = o2::tpc::TPCFastTransformHelperO2::instance()->create(0); - pc.outputs().snapshot(Output{header::gDataOriginTPC, "TPCMSHAPE"}, *fastTransform); + mTPCCorrMapsLoader.setCorrMapMShape(std::move(fastTransform)); } if (mStreamer) { @@ -140,6 +148,7 @@ class TPCScalerSpec : public Task } } + float tpcScaler = -1.f; if (mEnableIDCs) { static int runWarningIDC = -1; if (pc.services().get().runNumber != mTPCScaler.getRun() && runWarningIDC != currRun) { @@ -149,8 +158,7 @@ class TPCScalerSpec : public Task float scalerA = mTPCScaler.getMeanScaler(timestamp, o2::tpc::Side::A); float scalerC = mTPCScaler.getMeanScaler(timestamp, o2::tpc::Side::C); float meanScaler = (scalerA + scalerC) / 2; - LOGP(info, "Publishing TPC scaler: {} for timestamp: {}, firstTFOrbit: {}", meanScaler, timestamp, firstTFOrbit); - pc.outputs().snapshot(Output{header::gDataOriginTPC, "TPCSCALER"}, meanScaler); + tpcScaler = meanScaler; if (mStreamer) { (*mStreamer) << "treeIDC" << "scalerA=" << scalerA @@ -160,11 +168,75 @@ class TPCScalerSpec : public Task << "\n"; } } + // check for Maps update + mTPCCorrMapsLoader.extractCCDBInputs(pc, tpcScaler); + + if (mGlobOpts.requestCTPLumi) { + const float lumiCTP = mTPCCorrMapsLoader.getInstLumiCTP(); + // if CTP lumi was notrequest - defualt of 0 is published, otherwise the value is scaled with the provided factor + LOGP(info, "Publishing CTP Lumi: {} for timestamp: {}, firstTFOrbit: {}", lumiCTP, timestamp, firstTFOrbit); + pc.outputs().snapshot(Output{header::gDataOriginCTP, "LUMICTP"}, lumiCTP); + } + + buildMap(pc); + } + + void buildMap(ProcessingContext& pc) + { + const auto lumiMode = mTPCCorrMapsLoader.getLumiScaleMode(); + o2::gpu::TPCFastTransform finalMap; + std::vector> additionalCorrections; + + if (lumiMode == LumiScaleMode::NoCorrection) { + std::unique_ptr dummy(TPCFastTransformHelperO2::instance()->create(0)); + finalMap.cloneFromObject(*dummy, nullptr); + finalMap.setApplyCorrectionOff(); + } else { + auto* corrMap = mTPCCorrMapsLoader.getCorrMap(); + const auto* corrMapRef = mTPCCorrMapsLoader.getCorrMapRef(); + finalMap.cloneFromObject(lumiMode == LumiScaleMode::StaticMapOnly && corrMapRef ? *corrMapRef : *corrMap, nullptr); + finalMap.setApplyCorrectionOn(); + + const float lumiScale = mTPCCorrMapsLoader.getLumiScale(); + + // if standard scaling is used: map(lumi) = (mean_map - ref_map) * lumiScale + ref_map + if (lumiMode == LumiScaleMode::Linear) { + const std::vector> step0{{&(corrMapRef->getCorrection()), -1.f}}; + // finalMap = (mean_map - finalMap) + TPCFastSpaceChargeCorrectionHelper::instance()->mergeCorrections(finalMap.getCorrection(), 1, step0, true); + + // finalMap = finalMap * lumiScale + ref_map + const std::vector> step1{{&(corrMapRef->getCorrection()), 1.f}}; + TPCFastSpaceChargeCorrectionHelper::instance()->mergeCorrections(finalMap.getCorrection(), lumiScale, step1, true); + + } else if (lumiMode == LumiScaleMode::DerivativeMap || lumiMode == LumiScaleMode::DerivativeMapMC) { + additionalCorrections.emplace_back(&(corrMapRef->getCorrection()), lumiScale); + } + + // if mshape map valid + if (!mTPCCorrMapsLoader.isCorrMapMShapeDummy()) { + LOGP(info, "Adding M-shape correction to the final map with scaling factor {}", mMShapeScalingFac); + additionalCorrections.emplace_back(&(mTPCCorrMapsLoader.getCorrMapMShape()->getCorrection()), 1.f); + } + + if (!additionalCorrections.empty()) { + TPCFastSpaceChargeCorrectionHelper::instance()->mergeCorrections(finalMap.getCorrection(), 1, additionalCorrections, true); + } + } + + Output corrMapOutput{header::gDataOriginTPC, "TPCCORRMAP", 0}; + auto outputBuffer = o2::pmr::vector(pc.outputs().getMemoryResource(corrMapOutput)); + outputBuffer.resize(TPCFastTransformPOD::estimateSize(finalMap.getCorrection())); + auto* pod = TPCFastTransformPOD::create(outputBuffer.data(), outputBuffer.size(), finalMap); + const auto& vd = mTPCVDriftHelper.getVDriftObject(); + o2::tpc::TPCFastTransformHelperO2::instance()->updateCalibration(*pod, 0, vd.corrFact, vd.refVDrift, vd.getTimeOffset()); + pc.outputs().adoptContainer(corrMapOutput, std::move(outputBuffer)); } void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) final { o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj); + mTPCVDriftHelper.accountCCDBInputs(matcher, obj); if (matcher == ConcreteDataMatcher(o2::header::gDataOriginTPC, "TPCSCALERCCDB", 0)) { LOGP(info, "Updating TPC scaler"); mTPCScaler.setFromTree(*((TTree*)obj)); @@ -198,12 +270,16 @@ class TPCScalerSpec : public Task LOGP(info, "Loaded default M-Shape correction object from CCDB"); } } + if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { + return; + } } private: std::shared_ptr mCCDBRequest; ///< info for CCDB request const bool mEnableIDCs{true}; ///< enable IDCs const bool mEnableMShape{false}; ///< enable v shape scalers + const o2::tpc::CorrectionMapsGloOpts mGlobOpts; ///< global options for the correction map loader, needed to decide which maps to load from CCDB bool mEnableWeights{false}; ///< use weights for TPC scalers TPCScalerWeights mScalerWeights{}; ///< scaler weights float mIonDriftTimeMS{-1}; ///< ion drift time @@ -214,6 +290,8 @@ class TPCScalerSpec : public Task int mKnotsYMshape{4}; ///< number of knots used for the spline object for M-Shape distortions int mKnotsZMshape{4}; ///< number of knots used for the spline object for M-Shape distortions std::unique_ptr mStreamer; ///< streamer + o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + o2::tpc::VDriftHelper mTPCVDriftHelper{}; ///< helper for v-drift void overWriteIntegrationTime() { @@ -229,7 +307,7 @@ class TPCScalerSpec : public Task } }; -o2::framework::DataProcessorSpec getTPCScalerSpec(bool enableIDCs, bool enableMShape) +o2::framework::DataProcessorSpec getTPCScalerSpec(bool enableIDCs, bool enableMShape, const o2::tpc::CorrectionMapsGloOpts& sclOpts) { std::vector inputs; if (enableIDCs) { @@ -251,18 +329,18 @@ o2::framework::DataProcessorSpec getTPCScalerSpec(bool enableIDCs, bool enableMS inputs); std::vector outputs; - if (enableIDCs) { - outputs.emplace_back(o2::header::gDataOriginTPC, "TPCSCALER", 0, Lifetime::Timeframe); - } - if (enableMShape) { - outputs.emplace_back(o2::header::gDataOriginTPC, "TPCMSHAPE", 0, Lifetime::Timeframe); + outputs.emplace_back(o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); + if (sclOpts.requestCTPLumi) { + outputs.emplace_back(o2::header::gDataOriginCTP, "LUMICTP", 0, Lifetime::Timeframe); } + o2::tpc::VDriftHelper::requestCCDBInputs(inputs); + o2::tpc::CorrectionMapsLoader::requestCCDBInputs(inputs, sclOpts); return DataProcessorSpec{ "tpc-scaler", inputs, outputs, - AlgorithmSpec{adaptFromTask(ccdbRequest, enableIDCs, enableMShape)}, + AlgorithmSpec{adaptFromTask(ccdbRequest, sclOpts, enableIDCs, enableMShape)}, Options{ {"ion-drift-time", VariantType::Float, -1.f, {"Overwrite ion drift time if a value >0 is provided"}}, {"max-time-for-weights", VariantType::Float, 500.f, {"Maximum possible integration time in ms when weights are used"}}, diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index ee3acc808ccb7..0c0ae72056318 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -1377,7 +1377,7 @@ class TPCTimeSeries : public Task if (propTPCOk) { // store delta parameters deltaP0OuterITS = trackTmp.getParam(0) - trackTmpOut.getParam(0); - deltaP1OuterITS = trackTmp.getParam(1) - trackTmpOut.getParam(2); + deltaP1OuterITS = trackTmp.getParam(1) - trackTmpOut.getParam(1); deltaP2OuterITS = trackTmp.getParam(2) - trackTmpOut.getParam(2); deltaP3OuterITS = trackTmp.getParam(3) - trackTmpOut.getParam(3); deltaP4OuterITS = trackTmp.getParam(4) - trackTmpOut.getParam(4); @@ -1825,15 +1825,16 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter, auto dataRequest = std::make_shared(); bool useMC = false; GTrackID::mask_t srcTracks = GTrackID::getSourcesMask("TPC,ITS,ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF") & src; - srcTracks.set(GTrackID::TPC); // TPC must be always there dataRequest->requestTracks(srcTracks, useMC); - dataRequest->requestClusters(GTrackID::getSourcesMask("TPC"), useMC); + if (src[GTrackID::TPC]) { + dataRequest->requestClusters(GTrackID::getSourcesMask("TPC"), useMC); + } bool tpcOnly = srcTracks == GTrackID::getSourcesMask("TPC"); - if (!tpcOnly) { + if (srcTracks.any() && !tpcOnly) { dataRequest->requestFT0RecPoints(useMC); + dataRequest->requestPrimaryVertices(useMC); } - dataRequest->requestPrimaryVertices(useMC); const bool enableAskMatLUT = matType == o2::base::Propagator::MatCorrType::USEMatCorrLUT; auto ccdbRequest = std::make_shared(!disableWriter, // orbitResetTime diff --git a/Detectors/TPC/workflow/src/tpc-aggregate-cmv.cxx b/Detectors/TPC/workflow/src/tpc-aggregate-cmv.cxx new file mode 100644 index 0000000000000..32d2317c3b9b0 --- /dev/null +++ b/Detectors/TPC/workflow/src/tpc-aggregate-cmv.cxx @@ -0,0 +1,86 @@ +// 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. + +#include +#include +#include "Algorithm/RangeTokenizer.h" +#include "Framework/WorkflowSpec.h" +#include "Framework/ConfigParamSpec.h" +#include "CommonUtils/ConfigurableParam.h" +#include "TPCWorkflow/TPCAggregateCMVSpec.h" +#include "Framework/CompletionPolicyHelpers.h" + +using namespace o2::framework; + +// customize the completion policy +void customize(std::vector& policies) +{ + using o2::framework::CompletionPolicy; + policies.push_back(CompletionPolicyHelpers::defineByName("tpc-aggregate-*.*", CompletionPolicy::CompletionOp::Consume)); +} + +// we need to add workflow options before including Framework/runDataProcessing +void customize(std::vector& workflowOptions) +{ + const std::string cruDefault = "0-" + std::to_string(o2::tpc::CRU::MaxCRU - 1); + + std::vector options{ + {"configFile", VariantType::String, "", {"Configuration file for configurable parameters"}}, + {"timeframes", VariantType::Int, 2000, {"Number of TFs aggregated per calibration interval"}}, + {"crus", VariantType::String, cruDefault.c_str(), {"List of CRUs, comma-separated ranges, e.g. 0-3,7,9-15"}}, + {"input-lanes", VariantType::Int, 1, {"Number of aggregate pipelines set by --output-lanes in TPCDistributeCMVSpec"}}, + {"use-precise-timestamp", VariantType::Bool, false, {"Use precise timestamp metadata from distribute when writing to CCDB"}}, + {"enable-CCDB-output", VariantType::Bool, false, {"Send output to the CCDB populator"}}, + {"n-TFs-buffer", VariantType::Int, 1, {"Buffer size that was set in TPCFLPCMVSpec"}}, + {"configKeyValues", VariantType::String, "", {"Semicolon-separated key=value strings"}}}; + + std::swap(workflowOptions, options); +} + +#include "Framework/runDataProcessing.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& config) +{ + using namespace o2::tpc; + + // set up configuration + o2::conf::ConfigurableParam::updateFromFile(config.options().get("configFile")); + o2::conf::ConfigurableParam::updateFromString(config.options().get("configKeyValues")); + o2::conf::ConfigurableParam::writeINI("o2tpcaggregatecmv_configuration.ini"); + + const auto tpcCRUs = o2::RangeTokenizer::tokenize(config.options().get("crus")); + auto timeframes = static_cast(config.options().get("timeframes")); + int aggregateLanes = config.options().get("input-lanes"); + if (aggregateLanes <= 0) { + aggregateLanes = 1; + } + const bool usePreciseTimestamp = config.options().get("use-precise-timestamp"); + const bool sendCCDB = config.options().get("enable-CCDB-output"); + + int nTFsBuffer = config.options().get("n-TFs-buffer"); + if (nTFsBuffer <= 0) { + nTFsBuffer = 1; + } + + // convert total TFs per interval to number of buffered TFs + assert(timeframes >= static_cast(nTFsBuffer)); + timeframes /= static_cast(nTFsBuffer); + + const std::vector rangeCRUs(tpcCRUs.begin(), tpcCRUs.end()); + + WorkflowSpec workflow; + workflow.reserve(static_cast(aggregateLanes)); + LOGP(info, "Starting CMV aggregate with {} lanes, {} timeframes, {} n-TFs-buffer", aggregateLanes, timeframes, nTFsBuffer); + for (int ilane = 0; ilane < aggregateLanes; ++ilane) { + workflow.emplace_back(getTPCAggregateCMVSpec(ilane, rangeCRUs, timeframes, sendCCDB, usePreciseTimestamp, nTFsBuffer)); + } + return workflow; +} diff --git a/Detectors/TPC/workflow/src/tpc-calib-gainmap-tracks.cxx b/Detectors/TPC/workflow/src/tpc-calib-gainmap-tracks.cxx index 5475995437113..138968cd6b517 100644 --- a/Detectors/TPC/workflow/src/tpc-calib-gainmap-tracks.cxx +++ b/Detectors/TPC/workflow/src/tpc-calib-gainmap-tracks.cxx @@ -20,7 +20,7 @@ #include "CommonUtils/ConfigurableParam.h" #include "TPCWorkflow/TPCCalibPadGainTracksSpec.h" #include "TPCReaderWorkflow/TPCSectorCompletionPolicy.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCCalibration/CorrectionMapsOptions.h" #include "TPCWorkflow/TPCScalerSpec.h" using namespace o2::framework; @@ -44,7 +44,7 @@ void customize(std::vector& workflowOptions) {"polynomialsFile", VariantType::String, "", {"file containing the polynomials for the track topology correction"}}, {"disablePolynomialsCCDB", VariantType::Bool, false, {"Do not load the polynomials from the CCDB"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); std::swap(workflowOptions, options); } @@ -63,11 +63,9 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) const bool useLastExtractedMapAsReference = config.options().get("useLastExtractedMapAsReference"); const std::string polynomialsFile = config.options().get("polynomialsFile"); const auto disablePolynomialsCCDB = config.options().get("disablePolynomialsCCDB"); - const auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(config.options()); + const auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(config.options()); WorkflowSpec workflow; - if (sclOpt.needTPCScalersWorkflow()) { - workflow.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == 2, sclOpt.enableMShapeCorrection)); - } - workflow.emplace_back(o2::tpc::getTPCCalibPadGainTracksSpec(publishAfterTFs, debug, useLastExtractedMapAsReference, polynomialsFile, disablePolynomialsCCDB, sclOpt)); + workflow.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpt.enableMShapeCorrection, sclOpt)); + workflow.emplace_back(o2::tpc::getTPCCalibPadGainTracksSpec(publishAfterTFs, debug, useLastExtractedMapAsReference, polynomialsFile, disablePolynomialsCCDB)); return workflow; } diff --git a/Detectors/TPC/workflow/src/tpc-cmv-to-vector.cxx b/Detectors/TPC/workflow/src/tpc-cmv-to-vector.cxx new file mode 100644 index 0000000000000..1040b64f98d04 --- /dev/null +++ b/Detectors/TPC/workflow/src/tpc-cmv-to-vector.cxx @@ -0,0 +1,71 @@ +// 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. + +#include +#include +#include + +#include "Algorithm/RangeTokenizer.h" +#include "Framework/WorkflowSpec.h" +#include "Framework/ConfigParamSpec.h" +#include "Framework/CompletionPolicy.h" +#include "Framework/CompletionPolicyHelpers.h" +#include "CommonUtils/ConfigurableParam.h" +#include "TPCBase/CRU.h" +#include "TPCWorkflow/CMVToVectorSpec.h" + +using namespace o2::framework; +using namespace o2::tpc; + +// customize the completion policy +void customize(std::vector& policies) +{ + using o2::framework::CompletionPolicy; + policies.push_back(CompletionPolicyHelpers::defineByName("tpc-cmv-to-vector", CompletionPolicy::CompletionOp::Consume)); +} + +// we need to add workflow options before including Framework/runDataProcessing +void customize(std::vector& workflowOptions) +{ + std::string crusDefault = "0-" + std::to_string(CRU::MaxCRU - 1); + + std::vector options{ + {"input-spec", VariantType::String, "A:TPC/RAWDATA", {"selection string input specs"}}, + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings (e.g.: 'TPCCalibPedestal.FirstTimeBin=10;...')"}}, + {"configFile", VariantType::String, "", {"configuration file for configurable parameters"}}, + {"crus", VariantType::String, crusDefault.c_str(), {"List of TPC crus, comma separated ranges, e.g. 0-3,7,9-15"}}, + }; + + std::swap(workflowOptions, options); +} + +#include "Framework/runDataProcessing.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& config) +{ + + using namespace o2::tpc; + + // set up configuration + o2::conf::ConfigurableParam::updateFromFile(config.options().get("configFile")); + o2::conf::ConfigurableParam::updateFromString(config.options().get("configKeyValues")); + o2::conf::ConfigurableParam::writeINI("o2tpccmv_configuration.ini"); + + const std::string inputSpec = config.options().get("input-spec"); + + const auto crus = o2::RangeTokenizer::tokenize(config.options().get("crus")); + + WorkflowSpec workflow; + + workflow.emplace_back(getCMVToVectorSpec(inputSpec, crus)); + + return workflow; +} diff --git a/Detectors/TPC/workflow/src/tpc-distribute-cmv.cxx b/Detectors/TPC/workflow/src/tpc-distribute-cmv.cxx new file mode 100644 index 0000000000000..0fe780ebb16b3 --- /dev/null +++ b/Detectors/TPC/workflow/src/tpc-distribute-cmv.cxx @@ -0,0 +1,85 @@ +// 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. + +#include +#include +#include "Algorithm/RangeTokenizer.h" +#include "Framework/WorkflowSpec.h" +#include "Framework/ConfigParamSpec.h" +#include "CommonUtils/ConfigurableParam.h" +#include "TPCWorkflow/TPCDistributeCMVSpec.h" +#include "Framework/CompletionPolicyHelpers.h" + +using namespace o2::framework; + +// customize the completion policy +void customize(std::vector& policies) +{ + using o2::framework::CompletionPolicy; + policies.push_back(CompletionPolicyHelpers::defineByName("tpc-distribute-*.*", CompletionPolicy::CompletionOp::Consume)); +} + +// we need to add workflow options before including Framework/runDataProcessing +void customize(std::vector& workflowOptions) +{ + const std::string cruDefault = "0-" + std::to_string(o2::tpc::CRU::MaxCRU - 1); + + std::vector options{ + {"crus", VariantType::String, cruDefault.c_str(), {"List of CRUs, comma separated ranges, e.g. 0-3,7,9-15"}}, + {"timeframes", VariantType::Int, 2000, {"Number of TFs which will be aggregated per aggregation interval."}}, + {"firstTF", VariantType::Int, -1, {"First time frame index. (if set to -1 the first TF will be automatically detected. Values < -1 are setting an offset for skipping the first TFs)"}}, + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, + {"lanes", VariantType::Int, 1, {"Number of lanes of this device (CRUs are split per lane)"}}, + {"send-precise-timestamp", VariantType::Bool, false, {"Send precise timestamp information to the CMV aggregate workflow"}}, + {"n-TFs-buffer", VariantType::Int, 1, {"Buffer which was defined in the TPCFLPCMVSpec."}}, + {"output-lanes", VariantType::Int, 1, {"Number of parallel pipelines which will be used in the CMV aggregate device."}}}; + std::swap(workflowOptions, options); +} + +#include "Framework/runDataProcessing.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& config) +{ + using namespace o2::tpc; + + // set up configuration + o2::conf::ConfigurableParam::updateFromString(config.options().get("configKeyValues")); + o2::conf::ConfigurableParam::writeINI("o2tpcdistributecmv_configuration.ini"); + + const auto tpcCRUs = o2::RangeTokenizer::tokenize(config.options().get("crus")); + const auto nCRUs = tpcCRUs.size(); + auto timeframes = static_cast(config.options().get("timeframes")); + const auto outlanes = static_cast(config.options().get("output-lanes")); + const auto nLanes = static_cast(config.options().get("lanes")); + const auto firstTF = static_cast(config.options().get("firstTF")); + const bool sendPrecisetimeStamp = config.options().get("send-precise-timestamp"); + int nTFsBuffer = config.options().get("n-TFs-buffer"); + if (nTFsBuffer <= 0) { + nTFsBuffer = 1; + } + assert(timeframes >= static_cast(nTFsBuffer)); + timeframes /= static_cast(nTFsBuffer); + LOGP(info, "Using {} buffered CMV batches per interval with n-TFs-buffer={}", timeframes, nTFsBuffer); + + const auto crusPerLane = nCRUs / nLanes + ((nCRUs % nLanes) != 0); + WorkflowSpec workflow; + for (int ilane = 0; ilane < nLanes; ++ilane) { + const auto first = tpcCRUs.begin() + ilane * crusPerLane; + if (first >= tpcCRUs.end()) { + break; + } + const auto last = std::min(tpcCRUs.end(), first + crusPerLane); + const std::vector rangeCRUs(first, last); + workflow.emplace_back(getTPCDistributeCMVSpec(ilane, rangeCRUs, timeframes, outlanes, firstTF, sendPrecisetimeStamp, nTFsBuffer)); + } + + return workflow; +} diff --git a/Detectors/TPC/workflow/src/tpc-flp-cmv.cxx b/Detectors/TPC/workflow/src/tpc-flp-cmv.cxx new file mode 100644 index 0000000000000..b7734c5d0b24f --- /dev/null +++ b/Detectors/TPC/workflow/src/tpc-flp-cmv.cxx @@ -0,0 +1,74 @@ +// 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. + +#include +#include +#include +#include "CommonUtils/ConfigurableParam.h" +#include "Algorithm/RangeTokenizer.h" +#include "Framework/WorkflowSpec.h" +#include "Framework/ConfigParamSpec.h" +#include "TPCWorkflow/TPCFLPCMVSpec.h" +#include "TPCBase/CRU.h" + +using namespace o2::framework; + +void customize(std::vector& workflowOptions) +{ + const std::string cruDefault = "0-" + std::to_string(o2::tpc::CRU::MaxCRU - 1); + const int defaultlanes = std::max(1u, std::thread::hardware_concurrency() / 2); + + std::vector options{ + {"configFile", VariantType::String, "", {"configuration file for configurable parameters"}}, + {"lanes", VariantType::Int, defaultlanes, {"Number of parallel processing lanes (crus are split per device)"}}, + {"time-lanes", VariantType::Int, 1, {"Number of parallel processing lanes (timeframes are split per device)"}}, + {"crus", VariantType::String, cruDefault.c_str(), {"List of CRUs, comma separated ranges, e.g. 0-3,7,9-15"}}, + {"n-TFs-buffer", VariantType::Int, 1, {"Buffer n-TFs before sending output"}}, + {"trigger-per-flp", VariantType::Bool, false, {"Aggregate triggers of CRUs on FLP to a single trigger"}}, + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; + + std::swap(workflowOptions, options); +} + +#include "Framework/runDataProcessing.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& config) +{ + using namespace o2::tpc; + o2::conf::ConfigurableParam::updateFromString(config.options().get("configKeyValues")); + const auto tpcCRUs = o2::RangeTokenizer::tokenize(config.options().get("crus")); + const auto nCRUs = tpcCRUs.size(); + const auto nLanes = std::min(static_cast(config.options().get("lanes")), nCRUs); + const auto time_lanes = static_cast(config.options().get("time-lanes")); + const auto crusPerLane = nCRUs / nLanes + ((nCRUs % nLanes) != 0); + const bool triggerPerFLP = config.options().get("trigger-per-flp"); + const int nTFsBuffer = config.options().get("n-TFs-buffer"); + + o2::conf::ConfigurableParam::updateFromFile(config.options().get("configFile")); + o2::conf::ConfigurableParam::writeINI("o2tpcflp_configuration.ini"); + + WorkflowSpec workflow; + if (nLanes <= 0) { + return workflow; + } + + for (int ilane = 0; ilane < nLanes; ++ilane) { + const auto first = tpcCRUs.begin() + ilane * crusPerLane; + if (first >= tpcCRUs.end()) { + break; + } + const auto last = std::min(tpcCRUs.end(), first + crusPerLane); + const std::vector rangeCRUs(first, last); + workflow.emplace_back(timePipeline(getTPCFLPCMVSpec(ilane, rangeCRUs, triggerPerFLP, nTFsBuffer), time_lanes)); + } + + return workflow; +} diff --git a/Detectors/TPC/workflow/src/tpc-fouriertransform-aggregator.cxx b/Detectors/TPC/workflow/src/tpc-fouriertransform-aggregator.cxx index b0f09e02e627b..2f66a144251f1 100644 --- a/Detectors/TPC/workflow/src/tpc-fouriertransform-aggregator.cxx +++ b/Detectors/TPC/workflow/src/tpc-fouriertransform-aggregator.cxx @@ -26,7 +26,6 @@ void customize(std::vector& workflowOptions) std::vector options{ {"rangeIDC", VariantType::Int, 200, {"Number of 1D-IDCs which will be used for the calculation of the fourier coefficients. TODO ALREADY SET IN ABERAGEGROUP"}}, {"nFourierCoeff", VariantType::Int, 60, {"Number of fourier coefficients (real+imag) which will be stored in the CCDB. The maximum can be 'rangeIDC + 2'."}}, - {"nthreads", VariantType::Int, 1, {"Number of threads which will be used during the calculation of the fourier coefficients."}}, {"inputLanes", VariantType::Int, 2, {"Number of expected input lanes."}}, {"sendOutput", VariantType::Bool, false, {"send fourier coefficients"}}, {"use-naive-fft", VariantType::Bool, false, {"using naive fourier transform (true) or FFTW (false)"}}, @@ -51,8 +50,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) const bool processSACs = config.options().get("process-SACs"); const auto rangeIDC = static_cast(config.options().get("rangeIDC")); const auto nFourierCoeff = std::clamp(static_cast(config.options().get("nFourierCoeff")), static_cast(0), rangeIDC + 2); - const auto nthreadsFourier = static_cast(config.options().get("nthreads")); - TPCFourierTransformAggregatorSpec::IDCFType::setNThreads(nthreadsFourier); TPCFourierTransformAggregatorSpec::IDCFType::setFFT(!fft); const auto inputLanes = config.options().get("inputLanes"); WorkflowSpec workflow{getTPCFourierTransformAggregatorSpec(rangeIDC, nFourierCoeff, sendOutput, processSACs, inputLanes)}; diff --git a/Detectors/TPC/workflow/src/tpc-reco-workflow.cxx b/Detectors/TPC/workflow/src/tpc-reco-workflow.cxx index f3d4d639ddfd2..318c8372e7ad5 100644 --- a/Detectors/TPC/workflow/src/tpc-reco-workflow.cxx +++ b/Detectors/TPC/workflow/src/tpc-reco-workflow.cxx @@ -23,6 +23,7 @@ #include "Framework/ConcreteDataMatcher.h" #include "TPCWorkflow/RecoWorkflow.h" #include "TPCReaderWorkflow/TPCSectorCompletionPolicy.h" +#include "TPCCalibration/CorrectionMapsOptions.h" #include "TPCCalibration/CorrectionMapsLoader.h" #include "Framework/CustomWorkflowTerminationHook.h" #include "DataFormatsTPC/TPCSectorHeader.h" @@ -75,8 +76,8 @@ void customize(std::vector& workflowOptions) {"tpc-deadMap-sources", VariantType::Int, -1, {"Sources to consider for TPC dead channel map creation; -1=all, 0=deactivated"}}, {"tpc-mc-time-gain", VariantType::Bool, false, {"use time gain calibration for MC (true) or for data (false)"}}, }; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); std::swap(workflowOptions, options); } @@ -169,7 +170,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) gTpcSectorMask |= (1ul << s); } bool doMC = not cfgc.options().get("disable-mc"); - auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(cfgc.options()); + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(cfgc.options()); auto wf = o2::tpc::reco_workflow::getWorkflow(&gPolicyData, // tpcSectors, // sector configuration gTpcSectorMask, // same as bitmask diff --git a/Detectors/TPC/workflow/src/tpc-refitter-workflow.cxx b/Detectors/TPC/workflow/src/tpc-refitter-workflow.cxx index 78bf63a44d60f..567d9caf14bc6 100644 --- a/Detectors/TPC/workflow/src/tpc-refitter-workflow.cxx +++ b/Detectors/TPC/workflow/src/tpc-refitter-workflow.cxx @@ -18,10 +18,10 @@ #include "Framework/ConfigParamSpec.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" #include "ReconstructionDataFormats/GlobalTrackID.h" -#include "TPCCalibration/CorrectionMapsLoader.h" #include "TPCWorkflow/TPCRefitter.h" #include "TPCWorkflow/TPCScalerSpec.h" #include "DetectorsBase/DPLWorkflowUtils.h" +#include "TPCCalibration/CorrectionMapsOptions.h" using namespace o2::framework; using GID = o2::dataformats::GlobalTrackID; @@ -47,7 +47,7 @@ void customize(std::vector& workflowOptions) {"enable-M-shape-correction", VariantType::Bool, false, {"Enable M-shape distortion correction"}}, {"disable-IDC-scalers", VariantType::Bool, false, {"Disable TPC scalers for space-charge distortion fluctuation correction"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -63,14 +63,11 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); auto useMC = configcontext.options().get("use-mc"); - auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); const auto enableCosmics = configcontext.options().get("enable-cosmics"); - GID::mask_t allowedSourcesTrc = GID::getSourcesMask("ITS,TPC,ITS-TPC,TPC-TOF"); - GID::mask_t allowedSourcesClus = GID::getSourcesMask("TPC,TOF"); - if (enableCosmics) { - allowedSourcesTrc = allowedSourcesTrc | GID::getSourcesMask("ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); - } + GID::mask_t allowedSourcesTrc = GID::getSourcesMask("TPC,ITS-TPC,TPC-TOF,TPC-TRD,ITS-TPC-TRD,TPC-TRD-TOF,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); + GID::mask_t allowedSourcesClus = GID::getSourcesMask("TPC"); GID::mask_t srcTrc = allowedSourcesTrc & GID::getSourcesMask(configcontext.options().get("track-sources")); GID::mask_t srcCls = allowedSourcesClus & GID::getSourcesMask(configcontext.options().get("cluster-sources")); @@ -79,18 +76,16 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) srcCls = srcCls | GID::getSourcesMask("CTP"); } - if (sclOpt.lumiType == 2) { - const auto enableMShape = configcontext.options().get("enable-M-shape-correction"); - const auto enableIDCs = !configcontext.options().get("disable-IDC-scalers"); - specs.emplace_back(o2::tpc::getTPCScalerSpec(enableIDCs, enableMShape)); - } + const auto enableMShape = configcontext.options().get("enable-M-shape-correction"); + const auto enableIDCs = !configcontext.options().get("disable-IDC-scalers"); + specs.emplace_back(o2::tpc::getTPCScalerSpec(enableIDCs, enableMShape, sclOpt)); o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC); o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, useMC); // P-vertex is always needed if (enableCosmics) { o2::globaltracking::InputHelper::addInputSpecsCosmics(configcontext, specs, useMC); } - specs.emplace_back(o2::trackstudy::getTPCRefitterSpec(srcTrc, srcCls, useMC, sclOpt, enableCosmics)); + specs.emplace_back(o2::trackstudy::getTPCRefitterSpec(srcTrc, srcCls, useMC, enableCosmics)); // configure dpl timer to inject correct firstTForbit: start from the 1st orbit of TF containing 1st sampled orbit o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); diff --git a/Detectors/TPC/workflow/src/tpc-scaler.cxx b/Detectors/TPC/workflow/src/tpc-scaler.cxx index 598687c7dff41..d3893c0eafe84 100644 --- a/Detectors/TPC/workflow/src/tpc-scaler.cxx +++ b/Detectors/TPC/workflow/src/tpc-scaler.cxx @@ -15,6 +15,7 @@ #include "TPCWorkflow/TPCScalerSpec.h" #include "CommonUtils/ConfigurableParam.h" #include "Framework/ConfigParamSpec.h" +#include "TPCCalibration/CorrectionMapsOptions.h" using namespace o2::framework; @@ -25,7 +26,7 @@ void customize(std::vector& workflowOptions) ConfigParamSpec{"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, {"enable-M-shape-correction", VariantType::Bool, false, {"Enable M-shape distortion correction"}}, {"disable-IDC-scalers", VariantType::Bool, false, {"Disable TPC scalers for space-charge distortion fluctuation correction"}}}; - + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); std::swap(workflowOptions, options); } @@ -37,6 +38,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) o2::conf::ConfigurableParam::updateFromString(config.options().get("configKeyValues")); const auto enableMShape = config.options().get("enable-M-shape-correction"); const auto enableIDCs = !config.options().get("disable-IDC-scalers"); - workflow.emplace_back(o2::tpc::getTPCScalerSpec(enableIDCs, enableMShape)); + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(config.options()); + workflow.emplace_back(o2::tpc::getTPCScalerSpec(enableIDCs, enableMShape, sclOpt)); return workflow; } diff --git a/Detectors/TPC/workflow/test/test_cmv-trigger.cxx b/Detectors/TPC/workflow/test/test_cmv-trigger.cxx new file mode 100644 index 0000000000000..c102a5ae531f4 --- /dev/null +++ b/Detectors/TPC/workflow/test/test_cmv-trigger.cxx @@ -0,0 +1,85 @@ +// 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 test_cmv-trigger.cxx +/// @author Tuba Gündem, tuba.gundem@cern.ch +/// @brief Test workflow: reads CMVTRIGGER packets from tpc-flp-cmv and logs results + +#include +#include +#include "Framework/WorkflowSpec.h" +#include "Framework/ConfigParamSpec.h" +#include "Framework/Task.h" +#include "Framework/ControlService.h" +#include "Framework/Logger.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/DataRefUtils.h" +#include "Headers/DataHeader.h" +#include "TPCWorkflow/ProcessingHelpers.h" +#include "TPCWorkflow/TPCFLPCMVSpec.h" + +using namespace o2::framework; + +void customize(std::vector&) {} + +#include "Framework/runDataProcessing.h" + +namespace o2::tpc +{ + +class CMVTriggerDevice : public o2::framework::Task +{ + public: + void run(o2::framework::ProcessingContext& pc) final + { + const auto tf = processing_helpers::getCurrentTF(pc); + + for (auto& ref : o2::framework::InputRecordWalker(pc.inputs(), mFilter)) { + auto const* hdr = o2::framework::DataRefUtils::getHeader(ref); + const uint32_t firstCRU = hdr->subSpecification >> 7; + const bool triggered = pc.inputs().get(ref); + if (triggered) { + LOGP(info, "TF {:6} first CRU {:3}: {}", tf, firstCRU, "triggered"); + } + } + } + + void endOfStream(o2::framework::EndOfStreamContext& ec) final + { + ec.services().get().readyToQuit(o2::framework::QuitRequest::Me); + } + + private: + const std::vector mFilter = { + {"cmvtrigger", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, o2::tpc::TPCFLPCMVDevice::getDataDescriptionCMVTrigger()}, o2::framework::Lifetime::Timeframe}}; +}; + +o2::framework::DataProcessorSpec getCMVTriggerSpec() +{ + std::vector inputSpecs; + inputSpecs.emplace_back(o2::framework::InputSpec{"cmvtrigger", o2::framework::ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, o2::tpc::TPCFLPCMVDevice::getDataDescriptionCMVTrigger()}, o2::framework::Lifetime::Timeframe}); + + return o2::framework::DataProcessorSpec{ + "tpc-cmv-trigger", + inputSpecs, + {}, + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask()}}; +} + +} // namespace o2::tpc + +WorkflowSpec defineDataProcessing(ConfigContext const& config) +{ + WorkflowSpec workflow; + workflow.emplace_back(o2::tpc::getCMVTriggerSpec()); + return workflow; +} diff --git a/Detectors/TRD/calibration/src/DCSProcessor.cxx b/Detectors/TRD/calibration/src/DCSProcessor.cxx index f110ba844791e..6f719b71e10c3 100644 --- a/Detectors/TRD/calibration/src/DCSProcessor.cxx +++ b/Detectors/TRD/calibration/src/DCSProcessor.cxx @@ -382,7 +382,7 @@ bool DCSProcessor::updateGasDPsCCDB() } std::map md; md["responsible"] = "Ole Schmidt"; - o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSGas, mCcdbGasDPsInfo, "TRD/Calib/DCSDPsGas", md, mGasStartTS, mGasStartTS + 3 * o2::ccdb::CcdbObjectInfo::DAY); + o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSGas, mCcdbGasDPsInfo, "TRD/Calib/DCSDPsGas", md, mGasStartTS, mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } @@ -410,7 +410,7 @@ bool DCSProcessor::updateCurrentsDPsCCDB() } std::map md; md["responsible"] = "Ole Schmidt"; - o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSCurrents, mCcdbCurrentsDPsInfo, "TRD/Calib/DCSDPsI", md, mCurrentsStartTS, mCurrentsStartTS + 3 * o2::ccdb::CcdbObjectInfo::DAY); + o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSCurrents, mCcdbCurrentsDPsInfo, "TRD/Calib/DCSDPsI", md, mCurrentsStartTS, mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } @@ -437,7 +437,7 @@ bool DCSProcessor::updateVoltagesDPsCCDB() } std::map md; md["responsible"] = "Ole Schmidt"; - o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSVoltages, mCcdbVoltagesDPsInfo, "TRD/Calib/DCSDPsU", md, mVoltagesStartTS, mVoltagesStartTS + 7 * o2::ccdb::CcdbObjectInfo::DAY); + o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSVoltages, mCcdbVoltagesDPsInfo, "TRD/Calib/DCSDPsU", md, mVoltagesStartTS, mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } @@ -465,7 +465,7 @@ bool DCSProcessor::updateEnvDPsCCDB() } std::map md; md["responsible"] = "Leonardo Barreto"; - o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSEnv, mCcdbEnvDPsInfo, "TRD/Calib/DCSDPsEnv", md, mEnvStartTS, mEnvStartTS + 3 * o2::ccdb::CcdbObjectInfo::DAY); + o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSEnv, mCcdbEnvDPsInfo, "TRD/Calib/DCSDPsEnv", md, mEnvStartTS, mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } @@ -498,7 +498,7 @@ bool DCSProcessor::updateFedChamberStatusDPsCCDB() // LB: set start timestamp 30000 miliseconds before DPs are received o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSFedChamberStatus, mCcdbFedChamberStatusDPsInfo, "TRD/Calib/DCSDPsFedChamberStatus", md, mFedChamberStatusStartTS - 30000, - mFedChamberStatusStartTS + 3 * o2::ccdb::CcdbObjectInfo::DAY); + mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } @@ -531,7 +531,7 @@ bool DCSProcessor::updateFedCFGtagDPsCCDB() // LB: set start timestamp 30000 seconds before DPs are received o2::calibration::Utils::prepareCCDBobjectInfo(mTRDDCSFedCFGtag, mCcdbFedCFGtagDPsInfo, "TRD/Calib/DCSDPsFedCFGtag", md, mFedCFGtagStartTS - 30000, - mFedCFGtagStartTS + 3 * o2::ccdb::CcdbObjectInfo::DAY); + mCurrentTS + 14 * o2::ccdb::CcdbObjectInfo::DAY); return retVal; } diff --git a/Detectors/TRD/calibration/src/TrackBasedCalib.cxx b/Detectors/TRD/calibration/src/TrackBasedCalib.cxx index 0d551e7b5f33d..a0cbab6ac8bfa 100644 --- a/Detectors/TRD/calibration/src/TrackBasedCalib.cxx +++ b/Detectors/TRD/calibration/src/TrackBasedCalib.cxx @@ -22,6 +22,8 @@ #include "TRDBase/Geometry.h" #include "TRDBase/PadPlane.h" #include "CommonUtils/NameConf.h" +#include "DataFormatsTPC/TrackTPC.h" +#include "ReconstructionDataFormats/TrackTPCITS.h" #include using namespace o2::trd; diff --git a/Detectors/TRD/pid/include/TRDPID/LQND.h b/Detectors/TRD/pid/include/TRDPID/LQND.h index 7a898b25829c5..ca49a6c196186 100644 --- a/Detectors/TRD/pid/include/TRDPID/LQND.h +++ b/Detectors/TRD/pid/include/TRDPID/LQND.h @@ -27,6 +27,8 @@ #include "DetectorsBase/Propagator.h" #include "Framework/Logger.h" #include "ReconstructionDataFormats/TrackParametrization.h" +#include "DataFormatsTPC/TrackTPC.h" +#include "ReconstructionDataFormats/TrackTPCITS.h" #include #include diff --git a/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h b/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h index 93f07dd58445e..92c33d8c316b5 100644 --- a/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h +++ b/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h @@ -32,9 +32,8 @@ #include #include "DetectorsBase/GRPGeomHelper.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCFastTransformPOD.h" #include "GPUO2InterfaceRefit.h" -#include "TPCFastTransform.h" #include "DataFormatsTPC/TrackTPC.h" #include "DataFormatsITS/TrackITS.h" #include "DataFormatsITSMFT/TrkClusRef.h" @@ -52,13 +51,8 @@ namespace trd class TRDGlobalTracking : public o2::framework::Task { public: - TRDGlobalTracking(bool useMC, bool withPID, PIDPolicy policy, std::shared_ptr dataRequest, std::shared_ptr gr, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, - o2::dataformats::GlobalTrackID::mask_t src, bool trigRecFilterActive, bool strict) : mUseMC(useMC), mWithPID(withPID), mDataRequest(dataRequest), mGGCCDBRequest(gr), mTrkMask(src), mTrigRecFilter(trigRecFilterActive), mStrict(strict), mPolicy(policy) - { - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - mTPCCorrMapsLoader.setCheckCTPIDCConsistency(sclOpts.checkCTPIDCconsistency); - } + 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; void init(o2::framework::InitContext& ic) final; void fillMCTruthInfo(const TrackTRD& trk, o2::MCCompLabel lblSeed, std::vector& lblContainerTrd, std::vector& lblContainerMatch, const o2::dataformats::MCTruthContainer* trkltLabels) const; @@ -86,7 +80,7 @@ class TRDGlobalTracking : public o2::framework::Task std::shared_ptr mDataRequest; ///< seeding input (TPC-only, ITS-TPC or both) std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; - o2::tpc::CorrectionMapsLoader mTPCCorrMapsLoader{}; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps{nullptr}; o2::dataformats::GlobalTrackID::mask_t mTrkMask; ///< seeding track sources (TPC, ITS-TPC) bool mTrigRecFilter{false}; ///< if true, TRD trigger records without matching ITS IR are filtered out bool mStrict{false}; ///< preliminary matching in strict mode @@ -111,11 +105,12 @@ class TRDGlobalTracking : public o2::framework::Task std::array mCovDiagOuter{}; ///< total cov.matrix extra diagonal error from TrackTuneParams // PID PIDPolicy mPolicy{PIDPolicy::DEFAULT}; ///< Model to load an evaluate + bool mRequestCTPLumi{false}; ///< whether to request CTP lumi std::unique_ptr mBase; ///< PID engine }; /// 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*/, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts); +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); } // namespace trd } // namespace o2 diff --git a/Detectors/TRD/workflow/io/CMakeLists.txt b/Detectors/TRD/workflow/io/CMakeLists.txt index e91b5f5a30375..baf607a4a3ab3 100644 --- a/Detectors/TRD/workflow/io/CMakeLists.txt +++ b/Detectors/TRD/workflow/io/CMakeLists.txt @@ -23,7 +23,7 @@ o2_add_library(TRDWorkflowIO src/TRDCalibWriterSpec.cxx src/TRDPHReaderSpec.cxx include/TRDWorkflowIO/KrClusterWriterSpec.h - PUBLIC_LINK_LIBRARIES O2::DataFormatsTRD O2::SimulationDataFormat O2::DPLUtils O2::GPUDataTypes O2::DataFormatsTPC) + PUBLIC_LINK_LIBRARIES O2::DataFormatsTRD O2::SimulationDataFormat O2::DPLUtils O2::GPUDataTypes O2::DataFormatsTPC O2::DetectorsRaw) o2_add_executable(digit-reader-workflow diff --git a/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx b/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx index 0f578efd3aa5b..40521c5fd5ee9 100644 --- a/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx +++ b/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx @@ -82,7 +82,6 @@ using TrackTunePar = o2::globaltracking::TrackTuneParams; void TRDGlobalTracking::init(InitContext& ic) { o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); - mTPCCorrMapsLoader.init(ic); mTimer.Stop(); mTimer.Reset(); } @@ -91,7 +90,11 @@ void TRDGlobalTracking::updateTimeDependentParams(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); mTPCVDriftHelper.extractCCDBInputs(pc); - mTPCCorrMapsLoader.extractCCDBInputs(pc); + + auto const& raw = pc.inputs().get("corrMap"); + mTPCCorrMaps = &o2::gpu::TPCFastTransformPOD::get(raw); + float lumiCTP = mRequestCTPLumi ? pc.inputs().get("lumiCTP") : 0; + // pc.inputs().get("cldict"); // called by the RecoContainer to trigger finaliseCCDB static bool initOnceDone = false; if (!initOnceDone) { // this params need to be queried only once @@ -148,13 +151,9 @@ void TRDGlobalTracking::updateTimeDependentParams(ProcessingContext& pc) mBase->setLocalGainFactors(pc.inputs().get("localgainfactors").get()); } } - bool updateCalib = false; - if (mTPCCorrMapsLoader.isUpdated()) { - mTPCCorrMapsLoader.acknowledgeUpdate(); - updateCalib = true; - } + const auto& trackTune = TrackTuneParams::Instance(); - float scale = mTPCCorrMapsLoader.getInstLumiCTP(); + float scale = lumiCTP; if (scale < 0.f) { scale = 0.f; } @@ -173,11 +172,6 @@ void TRDGlobalTracking::updateTimeDependentParams(ProcessingContext& pc) mTracker->SetTPCVdrift(mTPCVdrift); mTracker->SetTPCTDriftOffset(mTPCTDriftOffset); mTPCVDriftHelper.acknowledgeUpdate(); - updateCalib = true; - } - if (updateCalib) { - auto& vd = mTPCVDriftHelper.getVDriftObject(); - mTPCCorrMapsLoader.updateVDrift(vd.corrFact, vd.refVDrift, vd.getTimeOffset()); } } @@ -189,9 +183,6 @@ void TRDGlobalTracking::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) { LOG(info) << "cluster dictionary updated"; mITSDict = (const o2::itsmft::TopologyDictionary*)obj; @@ -293,7 +284,7 @@ void TRDGlobalTracking::run(ProcessingContext& pc) mChainTracking->ClearIOPointers(); mTPCClusterIdxStruct = &inputTracks.inputsTPCclusters->clusterIndex; - mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, &mTPCCorrMapsLoader, o2::base::Propagator::Instance()->getNominalBz(), inputTracks.getTPCTracksClusterRefs().data(), 0, inputTracks.clusterShMapTPC.data(), inputTracks.occupancyMapTPC.data(), inputTracks.occupancyMapTPC.size(), nullptr, o2::base::Propagator::Instance()); + mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, mTPCCorrMaps, o2::base::Propagator::Instance()->getNominalBz(), inputTracks.getTPCTracksClusterRefs().data(), 0, inputTracks.clusterShMapTPC.data(), inputTracks.occupancyMapTPC.data(), inputTracks.occupancyMapTPC.size(), nullptr, o2::base::Propagator::Instance()); auto tmpInputContainer = getRecoInputContainer(pc, &mChainTracking->mIOPtrs, &inputTracks, mUseMC); auto tmpContainer = GPUWorkflowHelper::fillIOPtr(mChainTracking->mIOPtrs, inputTracks, mUseMC, nullptr, GTrackID::getSourcesMask("TRD"), mTrkMask, GTrackID::mask_t{GTrackID::MASK_NONE}); mTrackletsRaw = inputTracks.getTRDTracklets(); @@ -862,7 +853,7 @@ 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, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts) +DataProcessorSpec getTRDGlobalTrackingSpec(bool useMC, GTrackID::mask_t src, bool trigRecFilterActive, bool strict, bool withPID, PIDPolicy policy, bool requestCTPLumi) { std::vector outputs; uint32_t ss = o2::globaltracking::getSubSpec(strict ? o2::globaltracking::MatchingType::Strict : o2::globaltracking::MatchingType::Standard); @@ -899,7 +890,12 @@ DataProcessorSpec getTRDGlobalTrackingSpec(bool useMC, GTrackID::mask_t src, boo true); o2::tpc::VDriftHelper::requestCCDBInputs(inputs); Options opts; - o2::tpc::CorrectionMapsLoader::requestCCDBInputs(inputs, opts, sclOpts); + + dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); + + if (requestCTPLumi) { + dataRequest->inputs.emplace_back("lumiCTP", o2::header::gDataOriginCTP, "LUMICTP", 0, Lifetime::Timeframe); + } // Request PID policy data if (withPID) { @@ -962,7 +958,7 @@ DataProcessorSpec getTRDGlobalTrackingSpec(bool useMC, GTrackID::mask_t src, boo processorName, inputs, outputs, - AlgorithmSpec{adaptFromTask(useMC, withPID, policy, dataRequest, ggRequest, sclOpts, src, trigRecFilterActive, strict)}, + AlgorithmSpec{adaptFromTask(useMC, withPID, policy, dataRequest, ggRequest, src, trigRecFilterActive, strict, requestCTPLumi)}, opts}; } diff --git a/Detectors/TRD/workflow/src/trd-tracking-workflow.cxx b/Detectors/TRD/workflow/src/trd-tracking-workflow.cxx index 1d8243ff8cbc0..a3e57e67dbf8f 100644 --- a/Detectors/TRD/workflow/src/trd-tracking-workflow.cxx +++ b/Detectors/TRD/workflow/src/trd-tracking-workflow.cxx @@ -24,8 +24,9 @@ #include "TRDWorkflow/TRDGlobalTrackingQCSpec.h" #include "TRDWorkflow/TRDPulseHeightSpec.h" #include "GlobalTrackingWorkflowHelpers/InputHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCCalibration/CorrectionMapsOptions.h" #include "TPCWorkflow/TPCScalerSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" using namespace o2::framework; using GTrackID = o2::dataformats::GlobalTrackID; @@ -62,7 +63,8 @@ void customize(std::vector& workflowOptions) {"disable-ft0-pileup-tagging", VariantType::Bool, false, {"Do not request FT0 for pile-up determination"}}, {"policy", VariantType::String, "default", {"Pick PID policy (=default)"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; - o2::tpc::CorrectionMapsLoader::addGlobalOptions(options); + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); + o2::tpc::CorrectionMapsOptions::addGlobalOptions(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -86,7 +88,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto gain = configcontext.options().get("enable-gain-calib"); auto pulseHeight = configcontext.options().get("enable-ph"); auto digitsSpec = configcontext.options().get("trd-digits-spec"); - auto sclOpt = o2::tpc::CorrectionMapsLoader::parseGlobalOptions(configcontext.options()); + auto sclOpt = o2::tpc::CorrectionMapsOptions::parseGlobalOptions(configcontext.options()); 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()) { @@ -113,10 +115,10 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) // processing devices o2::framework::WorkflowSpec specs; - if (sclOpt.needTPCScalersWorkflow() && !configcontext.options().get("disable-root-input")) { - specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == 2, sclOpt.enableMShapeCorrection)); + if (!configcontext.options().get("disable-root-input")) { + specs.emplace_back(o2::tpc::getTPCScalerSpec(sclOpt.lumiType == o2::tpc::LumiScaleType::TPCScaler, sclOpt.enableMShapeCorrection, sclOpt)); } - specs.emplace_back(o2::trd::getTRDGlobalTrackingSpec(useMC, srcTRD, trigRecFilterActive, strict, pid, policy, sclOpt)); + specs.emplace_back(o2::trd::getTRDGlobalTrackingSpec(useMC, srcTRD, trigRecFilterActive, strict, pid, policy, sclOpt.requestCTPLumi)); if (vdexb || gain) { specs.emplace_back(o2::trd::getTRDTrackBasedCalibSpec(srcTRD, vdexb, gain)); } diff --git a/Detectors/Upgrades/ALICE3/CMakeLists.txt b/Detectors/Upgrades/ALICE3/CMakeLists.txt index 0335e85007c01..334bb13064783 100644 --- a/Detectors/Upgrades/ALICE3/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(Passive) add_subdirectory(TRK) +add_subdirectory(GlobalReconstruction) add_subdirectory(ECal) add_subdirectory(FD3) add_subdirectory(FT3) diff --git a/Detectors/Upgrades/ALICE3/FT3/README.md b/Detectors/Upgrades/ALICE3/FT3/README.md index 34a6782a2b0c2..c11352607db85 100644 --- a/Detectors/Upgrades/ALICE3/FT3/README.md +++ b/Detectors/Upgrades/ALICE3/FT3/README.md @@ -12,18 +12,22 @@ This is top page for the FT3 detector documentation. Configuration of the endcap disks can be done by setting values for the `FT3Base.layoutFT3` configurable, the available options are presented in the following Table: -| Option | Comments | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------- | -| `kSegmented` (default) | Currently, only OT disks have realistic implementation, for ML - simple trapezoids | -| `kTrapezoidal` | Simple trapezoisal disks (in both ML and OT), with `FT3Base.nTrapezoidalSegments=32` | -| `kCylindrical` | Simplest possible disks as TGeoTubes (ML and OT), bad for ACTS (wrong digi due to polar coorinates on disk sides) | +| Option | Comments | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `kSegmentedStave` | Segmentation of ML and OT disks: Modules are placed on staggered staves with user defined constants | +| `kSegmentedStaveOTOnly` (default) | Only OT disks are contain staves with modules, ML layers are segmented with strips of modules on front/back | +| `kSegmented` | Segmentation of ML and OT disk with strips of modules of chips on the front and back of a layer | +| `kTrapezoidal` | Simple trapezoidal disks (in both ML and OT), with `FT3Base.nTrapezoidalSegments=32` | +| `kCylindrical` | Simplest possible disks as TGeoTubes (ML and OT), bad for ACTS (wrong digi due to polar coorinates on disk sides) | + +Furthermore, there are more options in the case of stave segmentation -- for only OT or both. The user can set to cut the staves exactly on the nominal inner radii (true by default), and outer radii (false by default) of the disks. This exists since (planned) placements of sensors & staves often protrude out of the nominal radii to be more able to cover the nominal disk area. In addition, it is possible to draw reference circles in root for the stave segmented layouts for both the inner (red) and outer (blue) radii. This is off by default, yet can be toggled if the user wants to see how tight the tiling is to the nominal radii -- for visualisation purposes only. [ [Link to definitions](./base/include/FT3Base/FT3BaseParam.h) ] -For example, a geometry with the endcaps-only can be obtained by +For example, see the command below to generate a geometry with the endcaps only, all layers with the stave geometry, and including reference circles of nominal radii for visualisation. ```bash o2-sim-serial-run5 -n 1 -g pythia8hi -m FT3 \ - --configKeyValues "FT3Base.layoutFT3=kTrapezoidal" + --configKeyValues "FT3Base.layoutFT3=kSegmented; FT3Base.drawReferenceCircles=true" ``` start at the max of the two + y_top = std::max(absAllowedYRange.first, y_ranges.first.first); + } else { + // No inner minimum value, start at given value + y_top = y_ranges.first.first; + } + // fill positive y sensor positions + while ((y_top + sensorStackHeight) <= max_sensor_y_abs) { + y_positions.first.emplace_back(y_top, kSensorStack); + y_top += sensorAbsStackYShift; + } + + // now we do the same for the negative y positions + // they do not have to be exactly mirrored, hence done separately + double y_bottom; + if (!y_positions.second.empty()) { + // subtract instead to move further down + double previousStackHeight = Constants::getStackHeight(y_positions.second.back().second); + y_bottom = y_positions.second.back().first - previousStackHeight - Constants::stackGap; + } else if (absAllowedYRange.first > 0) { + // there is a minimum inner value --> start at the min of the two + y_bottom = std::min(-absAllowedYRange.first, y_ranges.second.first); + } else { + // No inner minimum value, start at given value + y_bottom = y_ranges.second.first; + } + // fill in the sensors on negative y + while ((y_bottom - sensorStackHeight) >= -max_sensor_y_abs) { + y_positions.second.emplace_back(y_bottom, kSensorStack); + y_bottom -= sensorAbsStackYShift; + } +} + +/* + * Create the vertices of the triangles that make up the stave cross section + * + * Each array of 3 corresponds to x or z values of the 3 triangle vertices, + * and the outer array corresponds to which triangle: + * + * [x_outer, z_outer, x_inner, z_inner], each of which has three values + */ +std::array, 4> buildStaveTriangle(int direction) +{ + // Set some constants for readability + double d = Constants::effectiveCarbonThickness_Stave; + double H = Constants::staveTriangleHeight; + /* + * Inner and outer vertices of the stave cross section triangle + * all vertices are at y_mid, we simply extend the triangle into y dir. + * We work in the local coordinate system of the stave, but still + * call the coordinates x and z for readability. + * + * 1. Get all local coordinates of the two triangle vertices + * 2. Extrude a volume from the subtracted triangle cross section area + * 3. Rotate the volume around the x-axis since it is by default in xy, + * and extruded in z. Rotate by -90 for xz -> xy, otherwise xz -> x(-y) + * 4. Translate the volume to the given position (arguments) + * + */ + std::array xv_inner, xv_outer, zv_inner, zv_outer; + // calculate the coordinates of the triangle vertices + // Top/bottom vertex (apex) + xv_outer[0] = 0; + zv_outer[0] = (direction == 1) ? -H + : H; + ; + // right + xv_outer[1] = Constants::sensor2x1_width / 2 + Constants::staveSensorGap; + zv_outer[1] = 0; + // left + xv_outer[2] = -xv_outer[1]; + zv_outer[2] = 0; + + // now get inner vertices, shifted inwards by effective carbon thickness + xv_inner[0] = xv_outer[0]; + double z_shift_inner = d / Constants::sinTheta; + zv_inner[0] = (direction == 1) ? zv_outer[0] + z_shift_inner + : zv_outer[0] - z_shift_inner; + // face vertices, first right + zv_inner[1] = (direction == 1) ? zv_outer[1] - d + : zv_outer[1] + d; + double x_shift_abs = d / TMath::Tan(Constants::alpha / 2); + xv_inner[1] = xv_outer[1] - x_shift_abs; + // left + zv_inner[2] = zv_inner[1]; + xv_inner[2] = -xv_inner[1]; + + return {xv_outer, zv_outer, xv_inner, zv_inner}; +} + +/* + * This function creates a carbon fibre volume for the stave, + * onto which the sensor and its support will be glued. + */ +void FT3Module::addStaveVolume( + TGeoVolume* motherVolume, std::string volumeName, int direction, + unsigned* volume_count, double staveLength, + std::array, 4> staveTriangles, + std::pair& absAllowedYRange, + double x_mid, double y_mid, double z_stave_shift_forward) +{ + // The allowed y range is assumed to be non-negative. + if (absAllowedYRange.first < 0 || absAllowedYRange.second < 0 || + absAllowedYRange.first >= absAllowedYRange.second) { + LOG(error) << "Invalid allowed y range in addStaveVolume(): (" + << absAllowedYRange.first << ", " << absAllowedYRange.second + << "). Both values must be non-negative and the first " + << "value must be less than the second value."; + return; + } + // Set the lower and upper y values of the stave: + double y_lower = y_mid - staveLength / 2; + double y_upper = y_mid + staveLength / 2; + bool splitStave = false; + if (y_lower > 0) { // This stave is fully above x-axis + y_lower = std::max(y_lower, absAllowedYRange.first); + y_upper = std::min(y_upper, absAllowedYRange.second); + } else if (y_upper < 0) { // stave entirely below x-axis + y_lower = std::max(y_lower, -absAllowedYRange.second); + y_upper = std::min(y_upper, -absAllowedYRange.first); + } else { // Full range stave that goes across x-axis + // Here we might have to cut the stave up into two pieces + if (absAllowedYRange.first > 0) { + // There is a minimum inner value --> Split stave + splitStave = true; + y_lower = absAllowedYRange.first; + } else { + // regular stave, use full length, but don't forget outer cut + y_lower = std::max(y_lower, -absAllowedYRange.second); + } + y_upper = std::min(y_upper, absAllowedYRange.second); + } + double staveLengthToUse = y_upper - y_lower; + /* + * create the extruded volumes from z=0 (later y=0 after rotation) to stave length + * and not from midpoint - staveLength/2 to midpoint + staveLength/2, translate later + * + * Note also that we first need to check if the length is allowed given the inner + * and outer radius of the layer. + */ + TGeoXtru* staveFull = new TGeoXtru(2); + staveFull->SetName((volumeName + "_Xtru_outer").c_str()); + staveFull->DefinePolygon(3, staveTriangles[0].data(), staveTriangles[1].data()); + staveFull->DefineSection(0, 0); + staveFull->DefineSection(1, staveLengthToUse); + + TGeoXtru* staveInner = new TGeoXtru(2); + staveInner->SetName((volumeName + "_Xtru_inner").c_str()); + staveInner->DefinePolygon(3, staveTriangles[2].data(), staveTriangles[3].data()); + staveInner->DefineSection(0, 0); + staveInner->DefineSection(1, staveLengthToUse); + + TGeoCompositeShape* staveShape = new TGeoCompositeShape( + (volumeName + "_shape").c_str(), + Form("%s - %s", staveFull->GetName(), staveInner->GetName())); + TGeoVolume* staveVolume = new TGeoVolume( + (volumeName).c_str(), + staveShape, + carbonFiberMed); + staveVolume->SetLineColor(Constants::carbonFiberColor); + staveVolume->SetFillColorAlpha(Constants::carbonFiberColor, 0.4); + + TGeoRotation* rot = new TGeoRotation(); + rot->RotateX(-90); // lift from xy plane into xz plane + /* + * After rotations the face of the stave lies in the xy-plane, + * facing downwards for direction == 1 and upwards for direction == 0. + * We still need to shift it in z to get the right staggered layout. + * This means moving the staves that must be shifted in the opposite + * direction they are facing: up for direction 1, and down for direction 0. + * + * Unlike a regular node placement, we have to put the stave at its + * starting point in y, not the midpoint. Hence, if we have the mirror, + * the starting point is the upper y value, since that is the bottom + * of the mirrored stave -- by the outer radius + */ + double z_shift = (direction == 1) ? z_stave_shift_forward : -z_stave_shift_forward; + TGeoCombiTrans* combiTrans = + new TGeoCombiTrans(x_mid, y_lower, z_shift, rot); + motherVolume->AddNode(staveVolume, + *volume_count, + combiTrans); + (*volume_count)++; + + // if the stave needs to be split, reuse the same volume on opposite side + if (splitStave) { + TGeoCombiTrans* combiTransSplit = + new TGeoCombiTrans(x_mid, -y_upper, z_shift, rot); + motherVolume->AddNode(staveVolume, + *volume_count, + combiTransSplit); + (*volume_count)++; + } +} + +/* + * Generic helper function that adds a box at the given position with + * the given dimensions to the given mother volume, with the given color and name. + */ + +void FT3Module::addDetectorVolume( + TGeoVolume* motherVolume, std::string volumeName, int color, + unsigned* volume_count, double x_mid, double y_mid, double z_mid, + double x_half_length, double y_half_length, double z_half_length) +{ + TGeoManager* geoManager = gGeoManager; + TGeoVolume* volume = geoManager->MakeBox(volumeName.c_str(), siliconMed, x_half_length, + y_half_length, z_half_length); + volume->SetLineColor(color); + volume->SetFillColorAlpha(color, 0.4); + motherVolume->AddNode( + volume, + *volume_count, + new TGeoTranslation( // midpoint of box to add + x_mid, + y_mid, + z_mid) // TGeoTranslation + ); // addNode + (*volume_count)++; +} + +/* + * This function adds a glue volume between two element layers, + * immediately for a whole 2x1 layout, under both the active and inactive region. + */ +void FT3Module::add2x1GlueVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid, + std::string element_glued_to) +{ + std::string glue_name = "FT3glue_" + element_glued_to + "_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); + addDetectorVolume( + motherVolume, glue_name, Constants::glueColor, volume_count, + x_mid, y_mid, z_mid, + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::epoxyThickness / 2); +} + +/* + * This function adds a copper volume onto which the silicon sensor is glued. + * As with the glue, this is a whole 2x1 layout volume. + */ +void FT3Module::add2x1CopperVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid) +{ + std::string copper_name = "FT3Copper_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); + addDetectorVolume( + motherVolume, copper_name, Constants::CuColor, volume_count, + x_mid, y_mid, z_mid, + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::copperThickness / 2); +} + +/* + * This function adds a kapton volume behind the copper, which represents the ??? + * As with copper and glue, this is a whole 2x1 layout volume. + */ +void FT3Module::add2x1KaptonVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid) +{ + std::string kapton_name = "FT3Kapton_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); + addDetectorVolume( + motherVolume, kapton_name, Constants::kaptonColor, volume_count, + x_mid, y_mid, z_mid, + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::kaptonThickness / 2); +} + +/* + * This function adds a single sensor (currently 2.5x3.2mm) to the given mother volume + * at the given (x,y,z) position of the module. + * + * Because the sensor has an inactive region of 0.2mm on one side, we also add a + * separate volume for the inactive region, which will be either on the left or + * or right dependent on the if the sensor is on the left or right in a 2x1 layout. + * See FT3Module.h for more details on the layout. + * + * Arguments: + * motherVolume: the volume to which the sensor volume will be added + * layerNumber: the layer number of the sensor, used for naming + * direction: the direction of the sensor (forward or backward eta), used for naming + * x_mid: the x position of the center of the sensor volume + * y_mid: the y position of the center of the sensor volume + * z_mid: the z position of the center of the sensor volume + * isLeft: whether the sensor is on the left or right in the 2x1 layout + */ +void FT3Module::addSingleSensorVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double active_x_mid, double y_mid, double z_mid, + bool isLeft) +{ + TGeoVolume* sensor; + TGeoManager* geoManager = gGeoManager; + // ACTIVE AREA + std::string sensor_name = "FT3Sensor_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); + sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, Constants::active_width / 2, + Constants::single_sensor_height / 2, Constants::siliconThickness / 2); + sensor->SetLineColor(Constants::SiColor); + sensor->SetFillColorAlpha(Constants::SiColor, 0.4); + motherVolume->AddNode( + sensor, + *volume_count, + new TGeoTranslation( // midpoint of box to add + active_x_mid, + y_mid, + z_mid) // TGeoTranslation + ); // addNode + (*volume_count)++; + // INACTIVE STRIP ON LEFT OR RIGHT + double inactive_x_mid = isLeft ? (active_x_mid - Constants::active_width / 2 - Constants::inactive_width / 2) + : (active_x_mid + Constants::active_width / 2 + Constants::inactive_width / 2); + std::string sensor_inactive_name = + "FT3Sensor_Inactive_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); + sensor = geoManager->MakeBox(sensor_inactive_name.c_str(), siliconMed, Constants::inactive_width / 2, + Constants::single_sensor_height / 2, Constants::siliconThickness / 2); + sensor->SetLineColor(Constants::SiInactiveColor); + sensor->SetFillColorAlpha(Constants::SiInactiveColor, 0.4); + motherVolume->AddNode( + sensor, + *volume_count, + new TGeoTranslation( // midpoint of box to add + inactive_x_mid, + y_mid, + z_mid) // TGeoTranslation + ); // addNode + (*volume_count)++; +} + +void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction, + double Rin, double Rout, double z_offset_local, + const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume) +{ + LOG(debug) << "FT3Module: create_layout_staveGeo - Layer " + << layerNumber << ", Direction " << direction; + + FT3Module::initialize_materials(); + auto& ft3Params = o2::ft3::FT3BaseParam::Instance(); + + // First let's define some constants used throughout + /* + * we build the volume from the outside in, starting with the silicon, + * then glue & materials towards the stave. Depending on direction, + * the distance from the center will be mirrored. + * + * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON STAVE | + * ----------------------------------------------------------------> z + * + * Naturally, this will be mirrored for layers in the backwards direction, + * such that the face of the sensors always face the interaction region. + * + * Currently, we stipulate that the default stave face is at local z=0, + * that is then shifted by the half air thickness encapsulating the layer + * to avoid overlaps with the air and services. All offsets are + * calculated for backward direction (since that is a positive shift), + * and then flipped for forward. At that point, the innermost/frontmost + * stave face is at the edge of the air volume, so we shift it back a little + * to make space for the sensor materials and a slight margin. + */ + double totalSensorMaterialThickness = + Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness + Constants::siliconThickness; + double z_offset_to_carbon_face = z_offset_local - totalSensorMaterialThickness - 0.1; + double z_offset_to_glue_Ka = + z_offset_to_carbon_face + Constants::epoxyThickness / 2; + double z_offset_to_kapton = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness / 2; + double z_offset_to_copper = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness + Constants::copperThickness / 2; + double z_offset_to_glue_Si = + z_offset_to_carbon_face + Constants::epoxyThickness + Constants::kaptonThickness + + Constants::copperThickness + Constants::epoxyThickness / 2; + double z_offset_to_silicon = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness + Constants::siliconThickness / 2; + + // initialise all y_positions, vector over all staves/columns + std::vector y_positionsPosNeg; + unsigned volume_count = 0; // give each subvolume a unique ID + // stave triangle cross sections are the same for every stave (direction based) + std::array, 4> staveTriangles = buildStaveTriangle(direction); + // Create the stave volumes and fill the y positions where to put sensors on the stave + for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) { + y_positionsPosNeg.emplace_back(PosNegPositionTypes{PositionTypes{}, PositionTypes{}}); + const int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size()); + + double y_midpoint = 0.; + bool mirrorStaveAroundX = false; + // default positive and negative starting points has a gap around x-axis for symmetry + double stave_half_length = staveConfig.y_lengths[i_stave] / 2; + PositionRangeType y_ranges; + if (ft3Params.placeSensorInMiddleOfStave) { + /* + * We want a sensor to cross over the x-axis for coverage at y=0 + * N.B. not necessarily exactly mirrored, only if stack gap is the same + * as the gap between sensors in a stack. + */ + y_ranges = {{-Constants::sensor2x1_height / 2, + stave_half_length}, + {-Constants::sensor2x1_height / 2 - Constants::stackGap, + -stave_half_length}}; + } else { + /* + * Otherwise have a gap around y=0, so sensors are not placed there. + * This means the stave is perfectly mirrored around the x-axis. + */ + y_ranges = {{Constants::stackGap / 2, stave_half_length}, + {-Constants::stackGap / 2, -stave_half_length}}; + } + auto y_midpoint_it = staveConfig.staveID_to_y_midpoint.find(staveID); + if (y_midpoint_it != staveConfig.staveID_to_y_midpoint.end()) { + // there is a defined midpoint for this stave, use this for starting points + y_midpoint = y_midpoint_it->second.first; // avoid double map lookup + mirrorStaveAroundX = y_midpoint_it->second.second; + y_ranges.first = {y_midpoint - stave_half_length, y_midpoint + stave_half_length}; + y_ranges.second = {-y_midpoint + stave_half_length, -y_midpoint - stave_half_length}; + } + + // Define tolerances for cutting staves and placing sensors + double tolerance_inner = -1000; // large negative number to allow given numbers + double tolerance_outer = -1000; + // cut staves on nominal inner radius if specified + if (ft3Params.cutStavesOnNominalRadius_inner) { + tolerance_inner = 0.; + } + if (ft3Params.cutStavesOnNominalRadius_outer) { + tolerance_outer = 0.; + } + + /* + * There are three cases in which we want to mirror the stave around the x-axis, + * which correspond to the stave not going fully from + to - Rout in y. + * + * (1) The inner tolerance is 0 (or positive) + * a) AND either x_left or x_right lies within the inner radius + * (2) The inner tolerance is large (allow stave placement as wished) + * a) AND the given stave midpoint is above the inner radius + */ + double x_left = staveConfig.x_midpoints[i_stave] - Constants::sensor2x1_width / 2; + double x_right = x_left + Constants::sensor2x1_width; + std::pair absAllowedYRange = + calculate_y_range(x_left, x_right, Rin, Rout); + + /* + * Shift allowed range by tolerance. Note that both values in the range must + * be non-negative, and if the inner is not, then set it to 0. This just means + * that there is no lower limit. The upper limit must however be larger than 0, + * if it is not, then skip this stave and give a warning. + */ + absAllowedYRange.first += tolerance_inner; + absAllowedYRange.second -= tolerance_outer; + + if (absAllowedYRange.first < 0) { + absAllowedYRange.first = 0; + } + if (absAllowedYRange.second <= 0) { + LOG(warning) << "For stave " << i_stave << " in layer " << layerNumber + << " with direction " << direction << ": no space to place sensors after applying tolerances, skipping stave."; + continue; + } + + // Get whether the stave is shifted backward or not before creating + double z_stave_shift_abs = staveConfig.staveOnFront[i_stave] ? 0 : Constants::z_offsetStave(staveConfig.x_midpoint_spacing); + double z_stave_shift_forward = // move staves more inward to fit in layer volume + -z_offset_to_carbon_face + z_stave_shift_abs; + std::string stave_volume_name = + "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + + "_" + std::to_string(direction); + addStaveVolume( + motherVolume, stave_volume_name, direction, &volume_count, + staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, + staveConfig.x_midpoints[i_stave], y_midpoint, z_stave_shift_forward); + // Now create the mirrored stave + if (mirrorStaveAroundX) { + addStaveVolume( + motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, + staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, + staveConfig.x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward); + } + + // now add the sensor positions on the stave + for (unsigned i_kSens = 0; i_kSens < Constants::kSensorsPerStack.size(); i_kSens++) { + fill_stave(y_positionsPosNeg.back(), Rin, Rout, x_left, + Constants::kSensorsPerStack[i_kSens], y_ranges, + absAllowedYRange); + } + } + + // Create volumes for the sensors and the support materials on top of the stave + for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) { + double x_mid = staveConfig.x_midpoints[i_stave]; + int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size()); + /* + * Declare an offset multiplier for the z offsets, used for distinguishing + * sensors facing either forward or backward. + * + * In the stave layout, all sensors face inward, and isFront + * refers to whether a stave is shifted backwards or not. Thus, + * we decide the offset multiplier only with direction, to + * keep the face facing inwards. + */ + bool isFront; + if (direction == 1) { // direction = 1 is forward + isFront = staveConfig.staveOnFront[i_stave]; + } else { + isFront = !(staveConfig.staveOnFront[i_stave]); + } + int z_offset_multiplier = (direction == 1) ? -1 : 1; + + // Get whether the stave is shifted for staggering or not + double z_stave_shift = 0; + if (!staveConfig.staveOnFront[i_stave]) { + // in forward direction, shifting backwards means +z shift + z_stave_shift = (direction == 1) ? Constants::z_offsetStave(staveConfig.x_midpoint_spacing) + : -Constants::z_offsetStave(staveConfig.x_midpoint_spacing); + } + + for (int y_sign = -1; y_sign < 2; y_sign += 2) { + // place sensors at positive and negative y + const auto& positions = (y_sign == 1) ? y_positionsPosNeg[i_stave].first + : y_positionsPosNeg[i_stave].second; + // define starting midpoint: y = y_start +- distance to middle of sensor + for (unsigned i_y_pos = 0; i_y_pos < positions.size(); i_y_pos++) { + double y_mid = positions[i_y_pos].first + y_sign * Constants::sensor2x1_height / 2; + for (unsigned i_sens = 0; i_sens < positions[i_y_pos].second; i_sens++) { + TGeoVolume* sensor; + // ------------ (1) Silicon sensor ------------ + // left single sensor of the 2x1 + double z_mid = z_offset_to_silicon * z_offset_multiplier + z_stave_shift; + addSingleSensorVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid - Constants::active_width / 2, y_mid, z_mid, true); + // right single sensor of the 2x1 + addSingleSensorVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid + Constants::active_width / 2, y_mid, z_mid, false); + // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ + z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift; + add2x1GlueVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid, y_mid, z_mid, "SiCu"); + // ------------ (3) Copper layer (FPC) ------------ + z_mid = z_offset_to_copper * z_offset_multiplier + z_stave_shift; + add2x1CopperVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid, y_mid, z_mid); + // ------------ (4) Kapton layer (FPC) ------------ + z_mid = z_offset_to_kapton * z_offset_multiplier + z_stave_shift; + add2x1KaptonVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid, y_mid, z_mid); + // ------------ (5) Epoxy glue layer between stave and Kapton ------------ + z_mid = z_offset_to_glue_Ka * z_offset_multiplier + z_stave_shift; + add2x1GlueVolume( + motherVolume, layerNumber, direction, i_stave, &volume_count, + x_mid, y_mid, z_mid, "CarbonKapton"); + // increment to next sensor: (height + gap of one sensor) + y_mid += y_sign * (Constants::sensor2x1_height + Constants::sensor2x1_gap); + } // sensors in stack + } // for y_sign (writing of positive or negative y positions) + } // i_y_pos + } // i_stave +} + void FT3Module::create_layout(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume) { @@ -124,7 +775,6 @@ void FT3Module::create_layout(double mZ, int layerNumber, int direction, double } else if (sensor_height == 19.2 && sensor_width == 5) { x_offset = 0.7; y_offset = 9; - } else { x_offset = sensor_width / 2; y_offset = sensor_height / 2; @@ -140,17 +790,18 @@ void FT3Module::create_layout(double mZ, int layerNumber, int direction, double double bottom_y_pos_value = 0; double bottom_y_neg_value = 0; + double Rin_offset = (sensor_height == 19.2) ? 1 : 0; + double Rout_offset = (sensor_height == 19.2) ? 1 : 0; + if (Rin == 7 && sensor_height == 9.6 && sensor_width == 5) { x_condition_min = -Rin - 2; x_condition_max = Rin; + dist_offset = 2; adjust_bottom_y_pos = true; adjust_bottom_y_neg = true; x_adjust_bottom_y_pos = 3.5; bottom_y_pos_value = 3.5; bottom_y_neg_value = -3.5; - - dist_offset = 2; - } else if (Rin == 5 && sensor_height == 9.6 && sensor_width == 5) { x_condition_min = -Rin - 6; x_condition_max = Rin; @@ -198,17 +849,32 @@ void FT3Module::create_layout(double mZ, int layerNumber, int direction, double x_adjust_bottom_y_pos = 5.5; bottom_y_pos_value = 3.5; bottom_y_neg_value = -3.5; + } else if (Rin == 10 && sensor_height == 9.6 && sensor_width == 5.0) { + x_condition_min = -Rin - 4; + x_condition_max = Rin; + dist_offset = 2; + adjust_bottom_y_pos = false; + adjust_bottom_y_neg = false; + x_adjust_bottom_y_pos = 3.5; + bottom_y_pos_value = 3.5; + bottom_y_neg_value = -3.5; + } else if (Rin == 20 && sensor_height == 9.6 && sensor_width == 5.0) { + x_condition_min = -Rin - 4; + x_condition_max = Rin; + dist_offset = 2; + adjust_bottom_y_pos = false; + adjust_bottom_y_neg = false; + x_adjust_bottom_y_pos = 3.5; + bottom_y_pos_value = 3.5; + bottom_y_neg_value = -3.5; } else { LOG(warning) << "Different config - to determine offsets needed for " << "Rin = " << Rin << " ; sensor_height = " << sensor_height << " ; sensor_width = " << sensor_width << " layer " << layerNumber; - x_condition_min = -Rin; + x_condition_min = -Rin - sensor_width; x_condition_max = Rin; adjust_bottom_y_pos = false; adjust_bottom_y_neg = false; } - double Rin_offset = (sensor_height == 19.2) ? 1 : 0; - double Rout_offset = (sensor_height == 19.2) ? 1 : 0; - offset_Rin_lower = Rin - Rin_offset; offset_Rin_upper = Rout + Rout_offset; @@ -235,13 +901,34 @@ void FT3Module::create_layout(double mZ, int layerNumber, int direction, double justSkipped1 = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}; } } else { - // filling for sensors with 2x width, each row skipped - if (face == "front") { - X_positions = {-63.4, -54.2, -45, -35.8, -26.6, -17.4, -8.2, 1., 10.2, 19.4, 28.6, 37.8, 47., 56.2, 65.4}; - justSkipped1 = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - } else if (face == "back") { - X_positions = {-58.8, -49.6, -40.4, -31.2, -22, -12.8, -3.6, 5.6, 14.8, 24, 33.2, 42.4, 51.6, 60.8}; - justSkipped1 = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + if (Rin == 10 || Rin == 20) { // v3 paving, rough attempt + float overlap = 0.3; + // NB: these are left edges + float X_start = -2.0 - 13.5 * (sensor_width - overlap); + float X_start_pos = 2.0 - 0.5 * (sensor_width - overlap); + if (face == "back") { + X_start += (sensor_width - overlap); + X_start_pos += (sensor_width - overlap); + } + while (X_start < -2) { + X_positions.push_back(X_start); + justSkipped1.push_back(1); + X_start += 2 * (sensor_width - overlap); + } + while (X_start_pos < Rout + x_offset - sensor_width) { + X_positions.push_back(X_start_pos); + justSkipped1.push_back(1); + X_start_pos += 2 * (sensor_width - overlap); + } + } else { + // filling for sensors with 2x width, each row skipped + if (face == "front") { + X_positions = {-63.4, -54.2, -45, -35.8, -26.6, -17.4, -8.2, 1., 10.2, 19.4, 28.6, 37.8, 47., 56.2, 65.4}; + justSkipped1 = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + } else if (face == "back") { + X_positions = {-58.8, -49.6, -40.4, -31.2, -22, -12.8, -3.6, 5.6, 14.8, 24, 33.2, 42.4, 51.6, 60.8}; + justSkipped1 = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + } } } @@ -704,3 +1391,15 @@ void FT3Module::createModule(double mZ, int layerNumber, int direction, double R create_layout(mZ, layerNumber, direction, Rin, Rout, overlap, face, layout_type, motherVolume); LOG(debug) << "FT3Module: done createModule"; } + +void FT3Module::createModule_staveGeo(double mZ, int layerNumber, int direction, + double Rin, double Rout, double z_offset_local, + const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume) +{ + LOG(debug) << "FT3Module: createModule_staveGeo - Layer " << layerNumber + << " at z=" << mZ << ", Direction " << direction; + create_layout_staveGeo(mZ, layerNumber, direction, Rin, Rout, + z_offset_local, staveConfig, motherVolume); + LOG(debug) << "FT3Module: done createModule_staveGeo"; +} diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt new file mode 100644 index 0000000000000..6b859412a0ff5 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/CMakeLists.txt @@ -0,0 +1,14 @@ +# 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. + +add_subdirectory(reconstruction) +add_subdirectory(workflow) +add_subdirectory(macros) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt new file mode 100644 index 0000000000000..8295e490f4d7d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt @@ -0,0 +1,21 @@ +# 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. + +o2_add_test_root_macro(CheckTracksALICE3.C + PUBLIC_LINK_LIBRARIES O2::DataFormatsITS + O2::DataFormatsTRK + O2::ITStracking + O2::SimulationDataFormat + O2::DetectorsBase + O2::TRKBase + O2::TRKSimulation + O2::Steer + LABELS trk COMPILE_ONLY) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C new file mode 100644 index 0000000000000..836327507018c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C @@ -0,0 +1,619 @@ +// 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 CheckTracksALICE3.C +/// \brief Quality assurance macro for TRK tracking + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DataFormatsITS/TrackITS.h" +#include "DataFormatsTRK/Cluster.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTrack.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "SimulationDataFormat/O2DatabasePDG.h" +#include "Steer/MCKinematicsReader.h" + +#endif + +using namespace std; +using namespace o2; + +struct ParticleClusterInfo { + std::bitset<11> layerClusters; + int nClusters = 0; + float pt = 0.0f; + + void addCluster(int layer) + { + if (!layerClusters[layer]) { + layerClusters[layer] = true; + nClusters++; + } + } + + bool hasConsecutiveLayers(int nConsecutive) const + { + for (int startLayer = 0; startLayer <= 11 - nConsecutive; ++startLayer) { + bool allSet = true; + for (int i = 0; i < nConsecutive; ++i) { + if (!layerClusters[startLayer + i]) { + allSet = false; + break; + } + } + if (allSet) { + return true; + } + } + return false; + } +}; + +void CheckTracksALICE3(std::string tracfile = "o2trac_trk.root", + std::string simprefix = "o2sim", + std::string clusfile = "o2clus_trk.root", + std::string outputfile = "trk_qa_output.root") +{ + gStyle->SetOptStat(0); + + std::cout << "=== Starting TRK Track Quality Assurance ===" << std::endl; + std::cout << "Input files:" << std::endl; + std::cout << " Tracks: " << tracfile << std::endl; + std::cout << " Sim prefix: " << simprefix << std::endl; + std::cout << " Clusters: " << clusfile << std::endl; + std::cout << " Output: " << outputfile << std::endl; + std::cout << std::endl; + + // MC kinematics reader + o2::steer::MCKinematicsReader kineReader(simprefix, o2::steer::MCKinematicsReader::Mode::kMCKine); + const int nEvents = kineReader.getNEvents(0); + std::cout << "Number of MC events: " << nEvents << std::endl; + + // Open clusters file to count cluster-associated layers per particle + TFile* clustersFile = TFile::Open(clusfile.c_str(), "READ"); + if (!clustersFile || clustersFile->IsZombie()) { + std::cerr << "ERROR: Cannot open clusters file: " << clusfile << std::endl; + return; + } + TTree* clusTree = clustersFile->Get("o2sim"); + if (!clusTree) { + std::cerr << "ERROR: Cannot find o2sim tree in clusters file" << std::endl; + return; + } + + // Open reconstructed tracks file + TFile* tracFile = TFile::Open(tracfile.c_str(), "READ"); + if (!tracFile || tracFile->IsZombie()) { + std::cerr << "ERROR: Cannot open tracks file: " << tracfile << std::endl; + return; + } + TTree* recTree = tracFile->Get("o2sim"); + if (!recTree) { + std::cerr << "ERROR: Cannot find o2sim tree in tracks file" << std::endl; + return; + } + + // Reconstructed tracks and labels + std::vector* recTracks = nullptr; + std::vector* trkLabels = nullptr; + recTree->SetBranchAddress("TRKTrack", &recTracks); + recTree->SetBranchAddress("TRKTrackMCTruth", &trkLabels); + + std::cout << "Reading tracks from tree..." << std::endl; + + // Analyze cluster tree to count cluster-associated layers per particle + std::cout << "Analyzing clusters from tree..." << std::endl; + std::unordered_map particleClusterMap; + + static constexpr int nTRKLayers = 11; + std::array*, nTRKLayers> clustersPerLayer{}; + std::array*, nTRKLayers> clusterLabelsPerLayer{}; + + for (int iLayer = 0; iLayer < nTRKLayers; ++iLayer) { + const std::string clusBranch = std::string("TRKClusterComp_") + std::to_string(iLayer); + const std::string truthBranch = std::string("TRKClusterMCTruth_") + std::to_string(iLayer); + if (!clusTree->GetBranch(clusBranch.c_str())) { + std::cerr << "WARNING: Missing cluster branch for layer " << iLayer << " (expected " << clusBranch << ")" << std::endl; + continue; + } + if (!clusTree->GetBranch(truthBranch.c_str())) { + std::cerr << "WARNING: Missing cluster MC-truth branch for layer " << iLayer << " (expected " << truthBranch << ")" << std::endl; + continue; + } + clusTree->SetBranchAddress(clusBranch.c_str(), &clustersPerLayer[iLayer]); + clusTree->SetBranchAddress(truthBranch.c_str(), &clusterLabelsPerLayer[iLayer]); + } + + Long64_t nClusEntries = clusTree->GetEntries(); + std::cout << "Processing " << nClusEntries << " cluster entries..." << std::endl; + + for (Long64_t iEntry = 0; iEntry < nClusEntries; ++iEntry) { + clusTree->GetEntry(iEntry); + for (int iLayer = 0; iLayer < nTRKLayers; ++iLayer) { + const auto* clusArr = clustersPerLayer[iLayer]; + const auto* clusLabArr = clusterLabelsPerLayer[iLayer]; + if (!clusArr || !clusLabArr) { + continue; + } + for (size_t iClus = 0; iClus < clusArr->size(); ++iClus) { + const auto labels = clusLabArr->getLabels(iClus); + if (labels.empty()) { + continue; + } + const auto& lab = labels[0]; + if (!lab.isValid() || lab.getSourceID() != 0 || !lab.isCorrect()) { + continue; + } + int trackID = -1, evID = -1, srcID = -1; + bool fake = false; + lab.get(trackID, evID, srcID, fake); + if (trackID < 0 || evID < 0) { + continue; + } + particleClusterMap[o2::MCCompLabel(trackID, evID, 0)].addCluster(iLayer); + } + } + } + + std::cout << "Found " << particleClusterMap.size() << " unique particles with clusters" << std::endl; + + // Store particle info and fill generated histograms + std::unordered_map particlePtMap; + + // Create histograms + constexpr int nb = 100; + double xbins[nb + 1], ptcutl = 0.05, ptcuth = 10.; + double a = std::log(ptcuth / ptcutl) / nb; + for (int i = 0; i <= nb; i++) + xbins[i] = ptcutl * std::exp(i * a); + + TH1D genParticlePtHist("genParticlePt", "Generated Particle p_{T} (All Layers); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + TH1D genParticlePt7LayersHist("genParticlePt7Layers", "Generated Particle p_{T} with clusters in at least 7 consecutive layers; #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + TH1D chargedPrimaryPtHist("chargedPrimaryPt", + "Charged primary particles |#eta| < 2; #it{p}_{T} (GeV/#it{c}); Counts", + nb, xbins); + TH1D goodTracks("goodTracks", "Good Tracks; p_{T} (GeV/c); Counts", nb, xbins); + TH1D fakeTracks("fakeTracks", "Fake Tracks; p_{T} (GeV/c); Counts", nb, xbins); + + std::array goodTracksMatching, fakeTracksMatching; + for (int i = 0; i < 5; ++i) { + goodTracksMatching[i] = TH1D(Form("goodTracksMatching_%dLayers", i + 7), + Form("Good Tracks with %d cluster layers; p_{T} (GeV/c); Counts", i + 7), + nb, xbins); + fakeTracksMatching[i] = TH1D(Form("fakeTracksMatching_%dLayers", i + 7), + Form("Fake Tracks with %d cluster layers; p_{T} (GeV/c); Counts", i + 7), + nb, xbins); + } + + TH1D numberOfClustersPerTrack("numberOfClustersPerTrack", + "Number of clusters per track; N_{clusters}; Counts", + 12, -0.5, 11.5); + TH1D cloneTracks("cloneTracks", "Clone Tracks; #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + + std::array duplicateTracksMatching; + for (int i = 0; i < 5; ++i) { + duplicateTracksMatching[i] = TH1D(Form("duplicateTracksMatching_%dLayers", i + 7), + Form("Duplicate Tracks with %d cluster layers; p_{T} (GeV/c); Counts", i + 7), + nb, xbins); + } + + TH1D genParticleEtaHist("genParticleEta", + "Generated Particle #eta (11 consec. layers, p_{T} > 1 GeV/c); #eta; Counts", + 100, -2.5, 2.5); + std::array goodTracksMatchingEta; + for (int i = 0; i < 5; ++i) { + goodTracksMatchingEta[i] = TH1D(Form("goodTracksMatchingEta_%dLayers", i + 7), + Form("Good Tracks #eta with %d cluster layers (p_{T} > 1 GeV/c); #eta; Counts", i + 7), + 100, -2.5, 2.5); + } + + // Numerators for summary efficiency/fake/duplicate vs 7-layer reference + TH1D goodTracks7("goodTracks7Layers", "Good Tracks (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + TH1D fakeTracks7("fakeTracks7Layers", "Fake Tracks (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + TH1D cloneTracks7("cloneTracks7Layers", "Clone Tracks (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + + // Deduplicated fake/clone numerators for 11-layer reference summary + TH1D fakeTracks11("fakeTracks11Layers", "Fake Tracks (11 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + TH1D cloneTracks11("cloneTracks11Layers", "Clone Tracks (11 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); + + // First pass: identify particles with full hit coverage from kinematics + std::cout << "Analyzing MC particles..." << std::endl; + for (int iEvent = 0; iEvent < nEvents; ++iEvent) { + const auto& mcTracks = kineReader.getTracks(iEvent); + for (size_t iTrack = 0; iTrack < mcTracks.size(); ++iTrack) { + const auto& mcTrack = mcTracks[iTrack]; + if (!mcTrack.isPrimary()) { + continue; + } + + // Create label for this particle + o2::MCCompLabel label(iTrack, iEvent, 0); + float pt = mcTrack.GetPt(); + + // Charged primary in |eta| < 2 + if (std::abs(mcTrack.GetEta()) < 2.f) { + auto* pdgPart = o2::O2DatabasePDG::Instance()->GetParticle(mcTrack.GetPdgCode()); + if (pdgPart != nullptr && pdgPart->Charge() != 0.) { + chargedPrimaryPtHist.Fill(pt); + } + } + + // Store particle info + particlePtMap[label] = pt; + + auto clusIt = particleClusterMap.find(label); + if (clusIt != particleClusterMap.end()) { + clusIt->second.pt = pt; + + if (clusIt->second.hasConsecutiveLayers(11)) { + genParticlePtHist.Fill(pt); + if (pt > 1.f) { + genParticleEtaHist.Fill(mcTrack.GetEta()); + } + } + + if (clusIt->second.hasConsecutiveLayers(7)) { + genParticlePt7LayersHist.Fill(pt); + } + } + } + } + + std::cout << "Generated particles with 11 cluster layers: " << genParticlePtHist.GetEntries() << std::endl; + std::cout << "Generated particles with 7+ consecutive cluster layers: " << genParticlePt7LayersHist.GetEntries() << std::endl; + + // Count how many reconstructed tracks point to each MC label (clone detection) + std::unordered_map labelRecoCount; + { + int nROFsTmp = recTree->GetEntries(); + for (int iROF = 0; iROF < nROFsTmp; ++iROF) { + recTree->GetEntry(iROF); + if (!trkLabels) { + continue; + } + for (const auto& lab : *trkLabels) { + if (!lab.isSet() || !lab.isValid() || lab.isFake()) { + continue; + } + int eventID = lab.getEventID(); + int trackID = lab.getTrackID(); + if (eventID < 0 || eventID >= nEvents) { + continue; + } + const auto& mcTracks = kineReader.getTracks(eventID); + if (trackID < 0 || trackID >= (int)mcTracks.size()) { + continue; + } + if (!mcTracks[trackID].isPrimary()) { + continue; + } + labelRecoCount[o2::MCCompLabel(lab.getTrackID(), lab.getEventID(), 0)]++; + } + } + } + + // Second pass: analyze reconstructed tracks + std::cout << "Analyzing reconstructed tracks..." << std::endl; + int nROFs = recTree->GetEntries(); + int totalTracks = 0; + int goodTracksCount = 0; + int fakeTracksCount = 0; + int cloneTracksCount = 0; + // Track which MC labels have already been filled per matching bin to avoid double-counting clones + std::array, 5> filledGoodLabels; + std::unordered_set filledGoodLabelsAny; + std::unordered_set filledGoodLabelsAny7; + std::unordered_set filledFakeLabelsAny11; + std::unordered_set filledCloneLabelsAny11; + + for (int iROF = 0; iROF < nROFs; ++iROF) { + recTree->GetEntry(iROF); + + if (!recTracks || !trkLabels) { + continue; + } + + totalTracks += recTracks->size(); + + for (size_t iTrack = 0; iTrack < recTracks->size(); ++iTrack) { + const auto& track = recTracks->at(iTrack); + const auto& label = trkLabels->at(iTrack); + + if (!label.isSet() || !label.isValid()) { + continue; + } + + int eventID = label.getEventID(); + int trackID = label.getTrackID(); + int nClusters = track.getNumberOfClusters(); + + // Get MC track info + if (eventID < 0 || eventID >= nEvents) { + continue; + } + + const auto& mcTracks = kineReader.getTracks(eventID); + if (trackID < 0 || trackID >= (int)mcTracks.size()) { + continue; + } + if (!mcTracks[trackID].isPrimary()) { + continue; + } + + float pt = mcTracks[trackID].GetPt(); + float eta = mcTracks[trackID].GetEta(); + + // Fill histograms + numberOfClustersPerTrack.Fill(nClusters); + + auto key = o2::MCCompLabel(trackID, eventID, 0); + if (particleClusterMap.find(key) != particleClusterMap.end() && particleClusterMap[key].hasConsecutiveLayers(11)) { + if (label.isFake()) { + fakeTracks.Fill(pt); + fakeTracksCount++; + if (nClusters >= 7 && nClusters <= 11) { + fakeTracksMatching[nClusters - 7].Fill(pt); + } + filledFakeLabelsAny11.insert(key); + } else { + if (filledGoodLabelsAny.insert(key).second) { + goodTracks.Fill(pt); + goodTracksCount++; + } + if (nClusters >= 7 && nClusters <= 11) { + int bin = nClusters - 7; + if (filledGoodLabels[bin].insert(key).second) { + goodTracksMatching[bin].Fill(pt); + if (pt > 1.f) { + goodTracksMatchingEta[bin].Fill(eta); + } + } else { + duplicateTracksMatching[bin].Fill(pt); + } + } + if (labelRecoCount[key] > 1) { + cloneTracks.Fill(pt); + cloneTracksCount++; + filledCloneLabelsAny11.insert(key); + } + } + } + + // Fill summary histograms vs 7-layer reference + auto clusIt7 = particleClusterMap.find(key); + if (clusIt7 != particleClusterMap.end() && clusIt7->second.hasConsecutiveLayers(7)) { + if (label.isFake()) { + fakeTracks7.Fill(pt); + } else { + if (filledGoodLabelsAny7.insert(key).second) { + goodTracks7.Fill(pt); + } + if (labelRecoCount[key] > 1) { + cloneTracks7.Fill(pt); + } + } + } + } + } + + // Create efficiency histograms + std::cout << "Total tracks: " << totalTracks << ". Out of those matching particles with 11 clusters, good: " << goodTracksCount + << ", fake: " << fakeTracksCount << ", clones: " << cloneTracksCount << std::endl; + + std::cout << "Computing efficiencies..." << std::endl; + + std::array efficiencyHistograms; + THStack* efficiencyStack = new THStack("efficiencyStack", + "Tracking Efficiency; #it{p}_{T} (GeV/#it{c}); Efficiency"); + + std::array efficiencyEtaHistograms; + THStack* efficiencyEtaStack = new THStack("efficiencyEtaStack", + "Tracking Efficiency vs #eta (p_{T} > 1 GeV/c); #eta; Efficiency"); + + int colors[5] = {kRed, kBlue, kGreen + 2, kMagenta, kOrange}; + for (int i = 0; i < 5; ++i) { + int nClusters = i + 7; + efficiencyHistograms[i] = TH1D(Form("efficiency_%dClusters", nClusters), + Form("Efficiency for %d cluster tracks; #it{p}_{T} (GeV/#it{c}); Efficiency", nClusters), + nb, xbins); + + efficiencyHistograms[i].Divide(&goodTracksMatching[i], &genParticlePtHist, 1, 1, "B"); + + efficiencyHistograms[i].SetLineColor(colors[i]); + efficiencyHistograms[i].SetFillColor(colors[i]); + efficiencyHistograms[i].SetLineWidth(2); + efficiencyHistograms[i].SetMarkerColor(colors[i]); + efficiencyHistograms[i].SetMarkerStyle(20 + i); + efficiencyStack->Add(&efficiencyHistograms[i]); + + efficiencyEtaHistograms[i] = TH1D(Form("efficiencyEta_%dClusters", nClusters), + Form("Efficiency vs #eta for %d cluster tracks (p_{T} > 1 GeV/c); #eta; Efficiency", nClusters), + 100, -2.5, 2.5); + efficiencyEtaHistograms[i].Divide(&goodTracksMatchingEta[i], &genParticleEtaHist, 1, 1, "B"); + efficiencyEtaHistograms[i].SetLineColor(colors[i]); + efficiencyEtaHistograms[i].SetFillColor(colors[i]); + efficiencyEtaHistograms[i].SetLineWidth(2); + efficiencyEtaHistograms[i].SetMarkerColor(colors[i]); + efficiencyEtaHistograms[i].SetMarkerStyle(20 + i); + efficiencyEtaStack->Add(&efficiencyEtaHistograms[i]); + } + + // Build summary efficiency/fake/duplicate vs 7-layer reference + TH1D effVs7("efficiencyVs7Layers", + "Tracking Efficiency (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + effVs7.Divide(&goodTracks7, &genParticlePt7LayersHist, 1, 1, "B"); + effVs7.SetLineColor(kBlue); + effVs7.SetLineWidth(2); + effVs7.SetMarkerColor(kBlue); + effVs7.SetMarkerStyle(20); + + TH1D fakeVs7("fakeRateVs7Layers", + "Fake Rate (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + fakeVs7.Divide(&fakeTracks7, &genParticlePt7LayersHist, 1, 1, "B"); + fakeVs7.SetLineColor(kRed); + fakeVs7.SetLineWidth(2); + fakeVs7.SetMarkerColor(kRed); + fakeVs7.SetMarkerStyle(21); + + TH1D dupVs7("duplicateRateVs7Layers", + "Duplicate Rate (7 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + dupVs7.Divide(&cloneTracks7, &genParticlePt7LayersHist, 1, 1, "B"); + dupVs7.SetLineColor(kGreen + 2); + dupVs7.SetLineWidth(2); + dupVs7.SetMarkerColor(kGreen + 2); + dupVs7.SetMarkerStyle(22); + + // Build summary efficiency/fake/duplicate vs 11-layer reference + // Fill deduplicated fake/clone histograms from the sets collected during the reco loop + for (const auto& [lbl, info] : particleClusterMap) { + if (!info.hasConsecutiveLayers(11)) { + continue; + } + auto ptIt = particlePtMap.find(lbl); + if (ptIt == particlePtMap.end()) { + continue; + } + float ptLbl = ptIt->second; + if (filledFakeLabelsAny11.count(lbl)) { + fakeTracks11.Fill(ptLbl); + } + if (filledCloneLabelsAny11.count(lbl)) { + cloneTracks11.Fill(ptLbl); + } + } + + TH1D effVs11("efficiencyVs11Layers", + "Tracking Efficiency (11 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + effVs11.Divide(&goodTracks, &genParticlePtHist, 1, 1, "B"); + effVs11.SetLineColor(kBlue); + effVs11.SetLineWidth(2); + effVs11.SetMarkerColor(kBlue); + effVs11.SetMarkerStyle(20); + + TH1D fakeVs11("fakeRateVs11Layers", + "Fake Rate (11 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + fakeVs11.Divide(&fakeTracks11, &genParticlePtHist, 1, 1, "B"); + fakeVs11.SetLineColor(kRed); + fakeVs11.SetLineWidth(2); + fakeVs11.SetMarkerColor(kRed); + fakeVs11.SetMarkerStyle(21); + + TH1D dupVs11("duplicateRateVs11Layers", + "Duplicate Rate (11 consec. layers ref.); #it{p}_{T} (GeV/#it{c}); Rate", + nb, xbins); + dupVs11.Divide(&cloneTracks11, &genParticlePtHist, 1, 1, "B"); + dupVs11.SetLineColor(kGreen + 2); + dupVs11.SetLineWidth(2); + dupVs11.SetMarkerColor(kGreen + 2); + dupVs11.SetMarkerStyle(22); + + // Summary canvas — 7-layer reference + TCanvas summaryCanvas("summaryCanvas7Layers", "TRK Tracking QA Summary (7 layers ref.)", 800, 600); + summaryCanvas.SetLogx(); + double ymax = std::max({effVs7.GetMaximum(), fakeVs7.GetMaximum(), dupVs7.GetMaximum()}); + effVs7.GetYaxis()->SetRangeUser(0., 1.1 * ymax + 0.05); + effVs7.Draw("E"); + fakeVs7.Draw("E SAME"); + dupVs7.Draw("E SAME"); + TLegend leg(0.65, 0.70, 0.88, 0.88); + leg.SetBorderSize(0); + leg.AddEntry(&effVs7, "Efficiency", "lp"); + leg.AddEntry(&fakeVs7, "Fake rate", "lp"); + leg.AddEntry(&dupVs7, "Duplicate rate", "lp"); + leg.Draw(); + + // Summary canvas — 11-layer reference + TCanvas summaryCanvas11("summaryCanvas11Layers", "TRK Tracking QA Summary (11 layers ref.)", 800, 600); + summaryCanvas11.SetLogx(); + double ymax11 = std::max({effVs11.GetMaximum(), fakeVs11.GetMaximum(), dupVs11.GetMaximum()}); + effVs11.GetYaxis()->SetRangeUser(0., 1.1 * ymax11 + 0.05); + effVs11.Draw("E"); + fakeVs11.Draw("E SAME"); + dupVs11.Draw("E SAME"); + TLegend leg11(0.65, 0.70, 0.88, 0.88); + leg11.SetBorderSize(0); + leg11.AddEntry(&effVs11, "Efficiency", "lp"); + leg11.AddEntry(&fakeVs11, "Fake rate", "lp"); + leg11.AddEntry(&dupVs11, "Duplicate rate", "lp"); + leg11.Draw(); + + // Write output + std::cout << "Writing output to " << outputfile << std::endl; + TFile outFile(outputfile.c_str(), "RECREATE"); + + // Top-level: summary plots + summaryCanvas.Write(); + effVs7.Write(); + fakeVs7.Write(); + dupVs7.Write(); + summaryCanvas11.Write(); + effVs11.Write(); + fakeVs11.Write(); + dupVs11.Write(); + + // Details directory: per-cluster-count breakdowns and raw counts + TDirectory* detDir = outFile.mkdir("details"); + detDir->cd(); + genParticlePtHist.Write(); + genParticlePt7LayersHist.Write(); + genParticleEtaHist.Write(); + chargedPrimaryPtHist.Write(); + goodTracks.Write(); + fakeTracks.Write(); + cloneTracks.Write(); + goodTracks7.Write(); + fakeTracks7.Write(); + cloneTracks7.Write(); + fakeTracks11.Write(); + cloneTracks11.Write(); + numberOfClustersPerTrack.Write(); + for (int i = 0; i < 5; ++i) { + goodTracksMatching[i].Write(); + fakeTracksMatching[i].Write(); + duplicateTracksMatching[i].Write(); + efficiencyHistograms[i].Write(); + goodTracksMatchingEta[i].Write(); + efficiencyEtaHistograms[i].Write(); + } + efficiencyStack->Write(); + efficiencyEtaStack->Write(); + + outFile.Close(); + + // Clean up + clustersFile->Close(); + tracFile->Close(); + delete efficiencyStack; + delete efficiencyEtaStack; + delete clustersFile; + delete tracFile; +} diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt new file mode 100644 index 0000000000000..1dfcb7a22f725 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt @@ -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. + +if(Acts_FOUND) + set(actsTarget Acts::Core) +endif() + +o2_add_library(ALICE3GlobalReconstruction + TARGETVARNAME targetName + SOURCES src/TimeFrame.cxx + $<$:src/TrackerACTS.cxx> + PUBLIC_LINK_LIBRARIES + O2::ITStracking + O2::GPUCommon + Microsoft.GSL::GSL + O2::CommonConstants + O2::DataFormatsITSMFT + O2::DataFormatsTRK + O2::SimulationDataFormat + O2::ITSBase + O2::ITSReconstruction + O2::ITSMFTReconstruction + O2::DataFormatsITS + O2::TRKBase + O2::TRKReconstruction + O2::TRKSimulation + nlohmann_json::nlohmann_json + ${actsTarget} + PRIVATE_LINK_LIBRARIES + O2::Steer + TBB::tbb) + +if(Acts_FOUND) + target_compile_definitions(${targetName} PUBLIC O2_WITH_ACTS) +endif() diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h new file mode 100644 index 0000000000000..e873931a5a46c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/GPUExternalAllocator.h @@ -0,0 +1,65 @@ +// 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. + +#ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_GPUEXTERNALALLOCATOR_H +#define ALICEO2_ALICE3GLOBALRECONSTRUCTION_GPUEXTERNALALLOCATOR_H + +#include "ITStracking/ExternalAllocator.h" + +#include +#include +#include +#include +#include +#include + +namespace o2::trk +{ + +class GPUExternalAllocator final : public o2::its::ExternalAllocator +{ + public: + GPUExternalAllocator() = default; + ~GPUExternalAllocator(); + + void* allocate(size_t size) override; + void deallocate(char* ptr, size_t size) override; + void pushTagOnStack(uint64_t tag) override; + void popTagOffStack(uint64_t tag) override; + + void releaseAll(); + + private: + enum class AllocationSpace { Host, + Device }; + + struct AllocationMeta { + AllocationSpace space; + uint64_t tag; + bool stacked; + }; + + using MemoryType = std::underlying_type_t; + + void* allocateHost(size_t size); + void* allocateDevice(size_t size); + void freeAllocation(void* ptr, AllocationSpace space); + void removeFromTagLocked(uint64_t tag, void* ptr); + + std::mutex mMutex; + std::vector mTagStack; + std::unordered_map> mTaggedAllocations; + std::unordered_map mAllocations; +}; + +} // namespace o2::trk + +#endif diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h new file mode 100644 index 0000000000000..6daefb2346e2c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrame.h @@ -0,0 +1,35 @@ +// 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 TimeFrame.h +/// \brief CPU TRK TimeFrame wrapper. +/// + +#ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H +#define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H + +#include "ALICE3GlobalReconstruction/TimeFrameMixin.h" +#include "ITStracking/TimeFrame.h" + +namespace o2::trk +{ + +template +class TimeFrame : public TimeFrameMixin> +{ + public: + TimeFrame() = default; + ~TimeFrame() override = default; +}; + +} // namespace o2::trk + +#endif // ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAME_H diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h new file mode 100644 index 0000000000000..744fca166489f --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameGPU.h @@ -0,0 +1,35 @@ +// 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 TimeFrameGPU.h +/// \brief GPU TRK TimeFrame wrapper. +/// + +#ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H +#define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H + +#include "ALICE3GlobalReconstruction/TimeFrameMixin.h" +#include "ITStrackingGPU/TimeFrameGPU.h" + +namespace o2::trk +{ + +template +class TimeFrameGPU : public TimeFrameMixin> +{ + public: + TimeFrameGPU() = default; + ~TimeFrameGPU() override = default; +}; + +} // namespace o2::trk + +#endif // ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEGPU_H diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h new file mode 100644 index 0000000000000..6e95be32dd0e1 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h @@ -0,0 +1,557 @@ +// 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 TimeFrameMixin.h +/// \brief Shared TRK TimeFrame helpers for CPU and GPU backends. +/// + +#ifndef ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEMIXIN_H +#define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEMIXIN_H + +#include "CommonDataFormat/InteractionRecord.h" +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" +#include "ITStracking/ROFLookupTables.h" +#include "ITStracking/TimeFrame.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCEventHeader.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "SimulationDataFormat/DigitizationContext.h" +#include "Steer/MCKinematicsReader.h" +#include "TRKReconstruction/Clusterer.h" +#include "TRKSimulation/Hit.h" +#include "TRKBase/GeometryTGeo.h" +#include "TRKBase/SegmentationChip.h" +#include "Framework/Logger.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace o2::trk +{ + +template +class TimeFrameMixin : public Base +{ + public: + TimeFrameMixin() = default; + ~TimeFrameMixin() override = default; + + int loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config); + + int loadROFrameData(const std::array, nLayers>& layerROFs, + const std::array, nLayers>& layerClusters, + const std::array, nLayers>& layerPatterns, + const std::array*, nLayers>* mcLabels = nullptr, + float yPlaneMLOT = 0.f); + + void getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup); + + void addTruthSeedingVertices(); + + void deriveAndInitTiming(const std::array, nLayers>& layerROFs); + + const o2::InteractionRecord& getTFAnchorIR() const noexcept { return mTFAnchorIR; } + + protected: + void initTimingTables(const std::array& timings); + void updateHostROFVertexLookupTable(); + + bool mTimingTablesInitialised{false}; + o2::InteractionRecord mTFAnchorIR{0, 0}; +}; + +template +void TimeFrameMixin::updateHostROFVertexLookupTable() +{ + static_cast*>(this)->updateROFVertexLookupTable(); +} + +template +void TimeFrameMixin::initTimingTables(const std::array& timings) +{ + if (mTimingTablesInitialised) { + return; + } + typename o2::its::TimeFrame::ROFOverlapTableN rofOverlapTable; + typename o2::its::TimeFrame::ROFVertexLookupTableN rofVertexLookupTable; + typename o2::its::TimeFrame::ROFMaskTableN rofMaskTable; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + rofOverlapTable.defineLayer(iLayer, timings[iLayer]); + rofVertexLookupTable.defineLayer(iLayer, timings[iLayer]); + rofMaskTable.defineLayer(iLayer, timings[iLayer]); + } + rofOverlapTable.init(); + rofVertexLookupTable.init(); + rofMaskTable.init(); + rofMaskTable.resetMask(1u); + this->setROFOverlapTable(std::move(rofOverlapTable)); + this->setROFVertexLookupTable(std::move(rofVertexLookupTable)); + this->setMultiplicityCutMask(std::move(rofMaskTable)); + this->useMultiplictyMask(); + mTimingTablesInitialised = true; + + const auto maskView = this->getROFMaskView(); + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + LOGP(info, "TRK timing initialised: layer {}: {}", iLayer, timings[iLayer].asString()); + LOGP(info, "TRK ROF mask: {}", maskView.asString(iLayer)); + } +} + +template +void TimeFrameMixin::deriveAndInitTiming(const std::array, nLayers>& layerROFs) +{ + if (mTimingTablesInitialised) { + return; + } + + o2::InteractionRecord anchor{0, 0}; + bool haveAnchor = false; + for (const auto& span : layerROFs) { + if (span.empty()) { + continue; + } + const auto& first = span.front().getBCData(); + if (!haveAnchor || first.toLong() < anchor.toLong()) { + anchor = first; + haveAnchor = true; + } + } + mTFAnchorIR = anchor; + const int64_t anchorBC = anchor.toLong(); + + std::array timings{}; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + const auto& span = layerROFs[iLayer]; + auto& t = timings[iLayer]; + t.mNROFsTF = static_cast(span.size()); + + if (span.size() >= 2) { + const int64_t delta = span[1].getBCData().toLong() - span[0].getBCData().toLong(); + if (delta > 0) { + t.mROFLength = static_cast(delta); + } else { + LOGP(warning, "TRK layer {}: non-positive BC delta between rofs[0] and rofs[1] ({}); falling back to mROFLength=1", iLayer, delta); + t.mROFLength = 1; + } + } else { + if (span.size() == 1) { + LOGP(warning, "TRK layer {}: only one input ROF — cannot derive mROFLength; falling back to mROFLength=1", iLayer); + } + t.mROFLength = 1; + } + + if (!span.empty()) { + const int64_t bias = span.front().getBCData().toLong() - anchorBC; + t.mROFBias = static_cast(bias); + } + t.mROFDelay = 0; + t.mROFAddTimeErr = 0; + } + + initTimingTables(timings); +} + +template +int TimeFrameMixin::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config) +{ + constexpr std::array startLayer{0, 3}; + const Long64_t nEvents = hitsTree->GetEntries(); + this->setIsStaggered(true); + + gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + + std::vector* trkHit = nullptr; + hitsTree->SetBranchAddress("TRKHit", &trkHit); + + const int inROFpileup{config.contains("inROFpileup") ? config["inROFpileup"].get() : 1}; + + const int nRofs = (nEvents + inROFpileup - 1) / inROFpileup; + std::array timings{}; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + timings[iLayer].mNROFsTF = static_cast(nRofs); + timings[iLayer].mROFLength = 1; + } + this->initTimingTables(timings); + const auto& timing = this->getROFOverlapTableView().getLayer(0); + if (timing.mNROFsTF != static_cast(nRofs)) { + LOGP(fatal, "TRK: inconsistent number of ROFs across TFs: timing has {}, hit-tree path produced {}", timing.mNROFsTF, nRofs); + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mMinR[iLayer] = std::numeric_limits::max(); + this->mMaxR[iLayer] = std::numeric_limits::lowest(); + this->mROFramesClusters[iLayer].clear(); + this->mROFramesClusters[iLayer].resize(nRofs + 1, 0); + this->mUnsortedClusters[iLayer].clear(); + this->mTrackingFrameInfo[iLayer].clear(); + this->mClusterExternalIndices[iLayer].clear(); + this->mClusterSize[iLayer].clear(); + } + + std::array clusterCountPerLayer{}; + for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { + hitsTree->GetEntry(iEvent); + for (const auto& hit : *trkHit) { + if (gman->getDisk(hit.GetDetectorID()) != -1) { + continue; + } + int subDetID = gman->getSubDetID(hit.GetDetectorID()); + const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); + if (layer >= nLayers) { + continue; + } + ++clusterCountPerLayer[layer]; + } + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); + this->mClusterSize[iLayer].reserve(clusterCountPerLayer[iLayer]); + } + + std::array resolution{0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004}; + if (config["geometry"]["pitch"].size() == nLayers) { + for (int iLayer{0}; iLayer < config["geometry"]["pitch"].size(); ++iLayer) { + LOGP(info, "Setting resolution for layer {} from config", iLayer); + LOGP(info, "Layer {} pitch {} cm", iLayer, config["geometry"]["pitch"][iLayer].get()); + resolution[iLayer] = config["geometry"]["pitch"][iLayer].get() / std::sqrt(12.f); + } + } + LOGP(info, "Number of active parts in VD: {}", gman->getNumberOfActivePartsVD()); + + std::array hitCounterPerLayer{}; + std::array*, nLayers> labels{}; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + labels[iLayer] = new dataformats::MCTruthContainer(); + this->mClusterLabels[iLayer] = labels[iLayer]; + } + + int iRof{0}; + for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { + hitsTree->GetEntry(iEvent); + + for (auto& hit : *trkHit) { + if (gman->getDisk(hit.GetDetectorID()) != -1) { + continue; + } + int subDetID = gman->getSubDetID(hit.GetDetectorID()); + const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); + + float alpha{0.f}; + o2::math_utils::Point3D gloXYZ; + o2::math_utils::Point3D trkXYZ; + float r{0.f}; + if (layer >= nLayers) { + continue; + } + if (layer >= 3) { + int chipID = hit.GetDetectorID(); + alpha = gman->getSensorRefAlphaMLOT(chipID); + const o2::math_utils::Transform3D& l2g = gman->getMatrixL2G(chipID); + auto locXYZ = l2g ^ (hit.GetPos()); + locXYZ.SetX(locXYZ.X() + gRandom->Gaus(0.0, resolution[layer])); + locXYZ.SetZ(locXYZ.Z() + gRandom->Gaus(0.0, resolution[layer])); + gloXYZ = gman->getMatrixL2G(chipID) * locXYZ; + trkXYZ = gman->getMatrixT2L(chipID - gman->getNumberOfActivePartsVD()) ^ locXYZ; + r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + } else { + const auto& hitPos = hit.GetPos(); + r = std::hypot(hitPos.X(), hitPos.Y()); + alpha = std::atan2(hitPos.Y(), hitPos.X()) + gRandom->Gaus(0.0, resolution[layer] / r); + o2::math_utils::bringTo02Pi(alpha); + gloXYZ.SetX(r * std::cos(alpha)); + gloXYZ.SetY(r * std::sin(alpha)); + gloXYZ.SetZ(hitPos.Z() + gRandom->Gaus(0.0, resolution[layer])); + trkXYZ.SetX(r); + trkXYZ.SetY(0.f); + trkXYZ.SetZ(gloXYZ.Z()); + } + this->mMinR[layer] = std::min(this->mMinR[layer], r); + this->mMaxR[layer] = std::max(this->mMaxR[layer], r); + this->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, + std::array{trkXYZ.y(), trkXYZ.z()}, + std::array{resolution[layer] * resolution[layer], 0., resolution[layer] * resolution[layer]}); + this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); + const int layerHitCounter = hitCounterPerLayer[layer]++; + this->addClusterExternalIndexToLayer(layer, layerHitCounter); + this->mClusterSize[layer].push_back(1); + MCCompLabel label{hit.GetTrackID(), static_cast(iEvent), 0}; + labels[layer]->addElement(layerHitCounter, label); + } + trkHit->clear(); + + if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { + iRof++; + for (unsigned int iLayer{0}; iLayer < this->mUnsortedClusters.size(); ++iLayer) { + this->mROFramesClusters[iLayer][iRof] = this->mUnsortedClusters[iLayer].size(); + } + } + } + return nRofs; +} + +template +int TimeFrameMixin::loadROFrameData(const std::array, nLayers>& layerROFs, + const std::array, nLayers>& layerClusters, + const std::array, nLayers>& layerPatterns, + const std::array*, nLayers>* mcLabels, + float yPlaneMLOT) +{ + constexpr std::array startLayer{0, 3}; + this->setIsStaggered(true); + GeometryTGeo* geom = GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + + if (!mTimingTablesInitialised) { + LOGP(fatal, "TRK::loadROFrameData: timing tables not initialised — call deriveAndInitTiming() first"); + } + int nRofs{0}; + for (const auto& rofs : layerROFs) { + nRofs = std::max(nRofs, static_cast(rofs.size())); + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + const auto& timing = this->getROFOverlapTableView().getLayer(iLayer); + if (timing.mNROFsTF != static_cast(layerROFs[iLayer].size())) { + LOGP(fatal, "TRK: inconsistent number of ROFs on layer {}: timing has {}, cluster path received {}", iLayer, timing.mNROFsTF, layerROFs[iLayer].size()); + } + this->mMinR[iLayer] = std::numeric_limits::max(); + this->mMaxR[iLayer] = std::numeric_limits::lowest(); + this->mROFramesClusters[iLayer].clear(); + this->mROFramesClusters[iLayer].resize(layerROFs[iLayer].size() + 1, 0); + this->mUnsortedClusters[iLayer].clear(); + this->mTrackingFrameInfo[iLayer].clear(); + this->mClusterExternalIndices[iLayer].clear(); + this->mClusterSize[iLayer].clear(); + this->mUnsortedClusters[iLayer].reserve(layerClusters[iLayer].size()); + this->mTrackingFrameInfo[iLayer].reserve(layerClusters[iLayer].size()); + this->mClusterExternalIndices[iLayer].reserve(layerClusters[iLayer].size()); + this->mClusterSize[iLayer].reserve(layerClusters[iLayer].size()); + } + + std::array, nLayers> patternOffsetsPerLayer; + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + auto& offsets = patternOffsetsPerLayer[iLayer]; + offsets.resize(layerClusters[iLayer].size(), std::numeric_limits::max()); + size_t pattPos = 0; + bool validPatterns = true; + for (size_t clusterId{0}; clusterId < layerClusters[iLayer].size(); ++clusterId) { + if (pattPos + 2 > layerPatterns[iLayer].size()) { + validPatterns = false; + break; + } + offsets[clusterId] = pattPos; + const uint8_t rowSpan = layerPatterns[iLayer][pattPos]; + const uint8_t colSpan = layerPatterns[iLayer][pattPos + 1]; + const size_t nBytes = (size_t(rowSpan) * colSpan + 7) / 8; + if (pattPos + 2 + nBytes > layerPatterns[iLayer].size()) { + validPatterns = false; + break; + } + pattPos += 2 + nBytes; + } + if (!validPatterns || pattPos != layerPatterns[iLayer].size()) { + LOGP(fatal, "Malformed TRK pattern stream for layer {}: {} bytes for {} clusters", + iLayer, layerPatterns[iLayer].size(), layerClusters[iLayer].size()); + } + } + + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + for (size_t iRof{0}; iRof < layerROFs[iLayer].size(); ++iRof) { + const auto& rof = layerROFs[iLayer][iRof]; + const int first = rof.getFirstEntry(); + const int last = first + rof.getNEntries(); + + for (int clusterId{first}; clusterId < last; ++clusterId) { + if (clusterId < 0 || clusterId >= static_cast(layerClusters[iLayer].size())) { + LOGP(warning, "Skipping out-of-range TRK cluster {} on layer {}", clusterId, iLayer); + continue; + } + + const auto& c = layerClusters[iLayer][clusterId]; + if (c.subDetID < 0 || c.subDetID > 1 || c.disk != -1) { + continue; + } + + const int clusterLayer = startLayer[c.subDetID] + c.layer; + if (clusterLayer != iLayer) { + LOGP(error, "Skipping cluster from layer {} found in TRK layer stream {}", clusterLayer, iLayer); + continue; + } + + const auto pattOffset = patternOffsetsPerLayer[iLayer][clusterId]; + const uint8_t* pattForCluster = layerPatterns[iLayer].data() + pattOffset; + auto locXYZ = Clusterer::getClusterLocalCoordinates(c, pattForCluster, yPlaneMLOT); + + const auto gloXYZ = geom->getMatrixL2G(c.chipID) * locXYZ; + + float alpha{0.f}; + o2::math_utils::Point3D trkXYZ; + if (c.subDetID == 1) { + alpha = geom->getSensorRefAlphaMLOT(c.chipID); + trkXYZ = geom->getMatrixT2L(c.chipID - geom->getNumberOfActivePartsVD()) ^ locXYZ; + } else { + const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + alpha = std::atan2(gloXYZ.Y(), gloXYZ.X()); + o2::math_utils::bringTo02Pi(alpha); + trkXYZ.SetX(r); + trkXYZ.SetY(0.f); + trkXYZ.SetZ(gloXYZ.Z()); + } + + const float r = std::hypot(gloXYZ.X(), gloXYZ.Y()); + this->mMinR[iLayer] = std::min(this->mMinR[iLayer], r); + this->mMaxR[iLayer] = std::max(this->mMaxR[iLayer], r); + + const float sigmaY2 = (c.subDetID == 0) + ? 0.25f * SegmentationChip::PitchRowVD * SegmentationChip::PitchRowVD + : 0.25f * SegmentationChip::PitchRowMLOT * SegmentationChip::PitchRowMLOT; + const float sigmaZ2 = (c.subDetID == 0) + ? 0.25f * SegmentationChip::PitchColVD * SegmentationChip::PitchColVD + : 0.25f * SegmentationChip::PitchColMLOT * SegmentationChip::PitchColMLOT; + + this->addTrackingFrameInfoToLayer(iLayer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, + std::array{trkXYZ.y(), trkXYZ.z()}, + std::array{sigmaY2, 0.f, sigmaZ2}); + this->addClusterToLayer(iLayer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[iLayer].size()); + this->addClusterExternalIndexToLayer(iLayer, clusterId); + this->mClusterSize[iLayer].push_back(std::clamp(static_cast(c.size), 0u, 255u)); + } + + this->mROFramesClusters[iLayer][iRof + 1] = this->mUnsortedClusters[iLayer].size(); + } + } + + for (auto i = 0; i < this->mNTrackletsPerCluster.size(); ++i) { + this->mNTrackletsPerCluster[i].resize(this->mUnsortedClusters[1].size()); + this->mNTrackletsPerClusterSum[i].resize(this->mUnsortedClusters[1].size() + 1); + } + + if (mcLabels != nullptr) { + for (int iLayer{0}; iLayer < nLayers; ++iLayer) { + this->mClusterLabels[iLayer] = (*mcLabels)[iLayer]; + } + } + + return nRofs; +} + +template +void TimeFrameMixin::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup) +{ + auto mcheader = new o2::dataformats::MCEventHeader; + mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); + + this->mPrimaryVertices.clear(); + this->mPrimaryVerticesLabels.clear(); + + const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); + const auto rofLength = clockLayer.mROFLength; + + int iRof{0}; + for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { + mcHeaderTree->GetEntry(iEvent); + o2::its::Vertex vertex; + vertex.setTimeStamp(o2::its::TimeEstBC{ + clockLayer.getROFStartInBC(iRof), + static_cast(rofLength)}); + vertex.setXYZ(mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); + vertex.setNContributors(30); + vertex.setChi2(0.f); + LOGP(debug, "ROF {}: Added primary vertex at ({}, {}, {})", iRof, mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); + this->addPrimaryVertex(vertex); + this->addPrimaryVertexLabel({o2::MCCompLabel{o2::MCCompLabel::maxTrackID(), static_cast(iEvent), 0, false}, 1.f}); + if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { + iRof++; + } + } + updateHostROFVertexLookupTable(); +} + +template +void TimeFrameMixin::addTruthSeedingVertices() +{ + LOGP(info, "TRK: using truth seeds as vertices from DigitizationContext"); + this->mPrimaryVertices.clear(); + this->mPrimaryVerticesLabels.clear(); + + const auto dc = o2::steer::DigitizationContext::loadFromFile("collisioncontext.root"); + const auto irs = dc->getEventRecords(); + o2::steer::MCKinematicsReader mcReader(dc); + + const int64_t anchorBC = mTFAnchorIR.toLong(); + const auto& clockLayer = this->getROFOverlapTableView().getClockLayer(); + const auto rofLength = clockLayer.mROFLength; + + using Vertex = o2::its::Vertex; + struct VertEntry { + int64_t bc; + Vertex vertex; + int event; + }; + std::vector entries; + + const int iSrc = 0; + auto eveId2colId = dc->getCollisionIndicesForSource(iSrc); + for (int iEve{0}; iEve < mcReader.getNEvents(iSrc); ++iEve) { + const auto& ir = irs[eveId2colId[iEve]]; + if (!ir.isDummy()) { + const auto& eve = mcReader.getMCEventHeader(iSrc, iEve); + const int64_t evBC = ir.toLong() - anchorBC; + if (evBC >= 0) { + Vertex vert; + vert.setTimeStamp(o2::its::TimeEstBC{ + static_cast(evBC), + static_cast(rofLength)}); + vert.setNContributors(std::max(1L, std::ranges::count_if( + mcReader.getTracks(iSrc, iEve), + [](const auto& trk) { + return trk.isPrimary() && trk.GetPt() > 0.05 && std::abs(trk.GetEta()) < 1.1; + }))); + vert.setXYZ((float)eve.GetX(), (float)eve.GetY(), (float)eve.GetZ()); + vert.setChi2(1); + constexpr float cov = 50e-9f; + vert.setCov(cov, cov, cov, cov, cov, cov); + entries.push_back({evBC, vert, iEve}); + } + } + mcReader.releaseTracksForSourceAndEvent(iSrc, iEve); + } + + // Sort by BC so the lookup table binary search works correctly + std::ranges::sort(entries, {}, &VertEntry::bc); + + for (const auto& e : entries) { + this->addPrimaryVertex(e.vertex); + o2::MCCompLabel lbl(o2::MCCompLabel::maxTrackID(), e.event, iSrc, false); + this->addPrimaryVertexLabel({lbl, 1.f}); + } + updateHostROFVertexLookupTable(); + LOGP(info, "TRK truth seeding: added {} vertices", entries.size()); +} + +} // namespace o2::trk + +#endif // ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEMIXIN_H diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TrackerACTS.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TrackerACTS.h new file mode 100644 index 0000000000000..ee69b32a23895 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TrackerACTS.h @@ -0,0 +1,189 @@ +// 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 TrackerACTS.h +/// \brief TRK tracker using ACTS seeding algorithm +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-04-01 +/// + +#ifndef ALICE3_GLOBALRECONSTRUCTION_INCLUDE_TRACKERACTS_H_ +#define ALICE3_GLOBALRECONSTRUCTION_INCLUDE_TRACKERACTS_H_ + +#include "Acts/Definitions/Units.hpp" +#include "Framework/Logger.h" + +#include "ITStracking/TimeFrame.h" +#include "TH2F.h" + +namespace o2::trk +{ + +/// Configuration for the ACTS-based tracker +struct TrackerACTSConfig { + // Seeding parameters + float minPt = 0.4f * Acts::UnitConstants::GeV; ///< Minimum pT for seeds + float maxImpactParameter = 10.f * Acts::UnitConstants::mm; ///< Maximum impact parameter + float cotThetaMax = std::sinh(4.0f); ///< Maximum cot(theta), corresponds to eta ~4 + + // Delta R cuts for doublet/triplet formation + float deltaRMinBottom = 5.f * Acts::UnitConstants::mm; ///< Min deltaR for bottom-middle + float deltaRMaxBottom = 200.f * Acts::UnitConstants::mm; ///< Max deltaR for bottom-middle + float deltaRMinTop = 5.f * Acts::UnitConstants::mm; ///< Min deltaR for middle-top + float deltaRMaxTop = 200.f * Acts::UnitConstants::mm; ///< Max deltaR for middle-top + + // Z cuts + float zMin = -3000.f * Acts::UnitConstants::mm; + float zMax = 3000.f * Acts::UnitConstants::mm; + + // Collision region + float collisionRegionMin = -150.f * Acts::UnitConstants::mm; + float collisionRegionMax = 150.f * Acts::UnitConstants::mm; + + // Quality cuts + float maxSeedsPerMiddleSP = 2; + float deltaPhiMax = 0.1f; ///< Maximum phi difference for doublets +}; + +/// Space point representation for tracking +struct SpacePoint { + float x{0.f}; + float y{0.f}; + float z{0.f}; + int layer{-1}; + int clusterId{-1}; + int rof{-1}; + + // Derived quantities + float r() const { return std::hypot(x, y); } + float radius() const { return r(); } // required by Acts::CylindricalGridElement concept + float phi() const { return std::atan2(y, x); } + + // Variance estimates (can be refined based on cluster properties) + float varianceR{0.01f}; // ~100 um resolution squared + float varianceZ{0.01f}; +}; + +/// Seed (triplet of space points) +struct SeedACTS { + const SpacePoint* bottom{nullptr}; + const SpacePoint* middle{nullptr}; + const SpacePoint* top{nullptr}; + float quality{0.f}; +}; + +/// TRK Tracker using ACTS algorithms for seeding and track finding +template +class TrackerACTS +{ + public: + TrackerACTS(); + ~TrackerACTS() + { + if (mHistSpacePoints) { + mHistSpacePoints->SaveAs("/tmp/mHistSpacePoints.C"); + delete mHistSpacePoints; + mHistSpacePoints = nullptr; + } + } + + /// Adopt a TimeFrame for processing + void adoptTimeFrame(o2::its::TimeFrame& tf); + + /// Main tracking entry point: convert clusters to tracks + void clustersToTracks(); + + /// Configuration + void setConfig(const TrackerACTSConfig& cfg) { mConfig = cfg; } + TrackerACTSConfig& getConfig() { return mConfig; } + const TrackerACTSConfig& getConfig() const { return mConfig; } + + /// Set the magnetic field strength + void setBz(float bz) { mBz = bz; } + + /// Get the magnetic field strength + float getBz() const { return mBz; } + + /// Print tracking summary + void printSummary() const; + + private: + TH2F* mHistSpacePoints = nullptr; + + /// Build space points from clusters in the TimeFrame + void buildSpacePoints(int rof); + + /// Create seeds (triplets) from space points using ACTS SeedFinder + void createSeeds(); + + /// Estimate track parameters from a seed using ACTS + bool estimateTrackParams(const SeedACTS& seed, o2::its::TrackITSExt& track) const; + + /// Run track finding from seeds + void findTracks(); + + /// Assign MC labels to tracks + void computeTracksMClabels(); + + /// Helper: time a task + template + float evaluateTask(Func&& task, std::string_view taskName); + + // Configuration + TrackerACTSConfig mConfig; + + // TimeFrame data + o2::its::TimeFrame* mTimeFrame = nullptr; + + // Space points built from clusters + std::vector mSpacePoints; + std::vector> mSpacePointsPerLayer; + + // Seeds + std::vector mSeeds; + + // Tracking state + float mBz{0.5f}; ///< Magnetic field in Tesla + unsigned int mTimeFrameCounter{0}; + double mTotalTime{0.}; + + // Tracking states for logging + enum State { + SpacePointBuilding = 0, + Seeding, + TrackFinding, + NStates, + }; + State mCurState{SpacePointBuilding}; + static constexpr std::array StateNames{ + "Space point building", + "ACTS seeding", + "Track finding"}; +}; + +template +template +float TrackerACTS::evaluateTask(Func&& task, std::string_view taskName) +{ + LOG(debug) << " + Starting " << taskName; + const auto start = std::chrono::high_resolution_clock::now(); + task(); + const auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff{end - start}; + + LOG(debug) << " - " << taskName << " completed in: " << std::fixed << std::setprecision(2) << diff.count() << " ms"; + return static_cast(diff.count()); +} + +} // namespace o2::trk + +#endif /* ALICE3_GLOBALRECONSTRUCTION_INCLUDE_TRACKERACTS_H_ */ diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cu b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cu new file mode 100644 index 0000000000000..c7b5f1cee50f5 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/GPUExternalAllocator.cu @@ -0,0 +1,177 @@ +// 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. + +#define GPUCA_GPUCODE_HOSTONLY + +#include + +#include "ALICE3GlobalReconstruction/GPUExternalAllocator.h" + +#include +#include +#include + +namespace +{ +void checkGpuError(cudaError_t error, const char* call) +{ + if (error != cudaSuccess) { + throw std::runtime_error(std::string(call) + ": " + cudaGetErrorString(error)); + } +} +} // namespace + +namespace o2::trk +{ + +GPUExternalAllocator::~GPUExternalAllocator() +{ + releaseAll(); +} + +void* GPUExternalAllocator::allocate(size_t size) +{ + const auto type = static_cast(getType()); + const bool useHost = (type & static_cast(o2::gpu::GPUMemoryResource::MEMORY_HOST)) != 0; + const bool useStack = (type & static_cast(o2::gpu::GPUMemoryResource::MEMORY_STACK)) != 0; + + void* ptr = useHost ? allocateHost(size) : allocateDevice(size); + + std::lock_guard guard(mMutex); + const uint64_t tag = (useStack && !mTagStack.empty()) ? mTagStack.back() : 0; + mAllocations.emplace(ptr, AllocationMeta{useHost ? AllocationSpace::Host : AllocationSpace::Device, tag, useStack}); + if (useStack) { + mTaggedAllocations[tag].push_back(ptr); + } + + return ptr; +} + +void GPUExternalAllocator::deallocate(char* ptr, size_t) +{ + if (!ptr) { + return; + } + + AllocationMeta meta; + { + std::lock_guard guard(mMutex); + const auto found = mAllocations.find(ptr); + if (found == mAllocations.end()) { + return; + } + meta = found->second; + mAllocations.erase(found); + if (meta.stacked) { + removeFromTagLocked(meta.tag, ptr); + } + } + + freeAllocation(ptr, meta.space); +} + +void GPUExternalAllocator::pushTagOnStack(uint64_t tag) +{ + std::lock_guard guard(mMutex); + mTagStack.push_back(tag); +} + +void GPUExternalAllocator::popTagOffStack(uint64_t tag) +{ + std::vector> toFree; + { + std::lock_guard guard(mMutex); + if (mTagStack.empty() || mTagStack.back() != tag) { + throw std::runtime_error("GPUExternalAllocator tag stack mismatch"); + } + + const auto tagged = mTaggedAllocations.find(tag); + if (tagged != mTaggedAllocations.end()) { + toFree.reserve(tagged->second.size()); + for (void* ptr : tagged->second) { + const auto found = mAllocations.find(ptr); + if (found != mAllocations.end()) { + toFree.emplace_back(ptr, found->second.space); + mAllocations.erase(found); + } + } + mTaggedAllocations.erase(tagged); + } + + mTagStack.pop_back(); + } + + for (const auto& [ptr, space] : toFree) { + freeAllocation(ptr, space); + } +} + +void GPUExternalAllocator::releaseAll() +{ + std::vector> toFree; + { + std::lock_guard guard(mMutex); + toFree.reserve(mAllocations.size()); + for (const auto& [ptr, meta] : mAllocations) { + toFree.emplace_back(ptr, meta.space); + } + mAllocations.clear(); + mTaggedAllocations.clear(); + mTagStack.clear(); + } + + for (const auto& [ptr, space] : toFree) { + freeAllocation(ptr, space); + } +} + +void* GPUExternalAllocator::allocateHost(size_t size) +{ + void* ptr = nullptr; + checkGpuError(cudaHostAlloc(&ptr, size, cudaHostAllocPortable), "cudaHostAlloc"); + return ptr; +} + +void* GPUExternalAllocator::allocateDevice(size_t size) +{ + void* ptr = nullptr; + checkGpuError(cudaMalloc(&ptr, size), "cudaMalloc"); + return ptr; +} + +void GPUExternalAllocator::freeAllocation(void* ptr, AllocationSpace space) +{ + if (!ptr) { + return; + } + + if (space == AllocationSpace::Host) { + checkGpuError(cudaFreeHost(ptr), "cudaFreeHost"); + } else { + checkGpuError(cudaFree(ptr), "cudaFree"); + } +} + +void GPUExternalAllocator::removeFromTagLocked(uint64_t tag, void* ptr) +{ + const auto tagged = mTaggedAllocations.find(tag); + if (tagged == mTaggedAllocations.end()) { + return; + } + + auto& entries = tagged->second; + entries.erase(std::remove(entries.begin(), entries.end(), ptr), entries.end()); + if (entries.empty()) { + mTaggedAllocations.erase(tagged); + } +} + +} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx new file mode 100644 index 0000000000000..1f7997b2e3968 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrame.cxx @@ -0,0 +1,25 @@ +// 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 TimeFrame.cxx +/// \brief Explicit instantiation of TimeFrameMixin and TimeFrame for the +/// ITS CPU base. Shared method bodies live in TimeFrameMixin.h. +/// + +#include "ALICE3GlobalReconstruction/TimeFrame.h" + +namespace o2::trk +{ + +template class TimeFrameMixin<11, o2::its::TimeFrame<11>>; +template class TimeFrame<11>; + +} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx new file mode 100644 index 0000000000000..714ead765b005 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TimeFrameGPU.cxx @@ -0,0 +1,25 @@ +// 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 TimeFrameGPU.cxx +/// \brief Explicit instantiation of TimeFrameMixin and TimeFrameGPU for the +/// ITS GPU base. Shared method bodies live in TimeFrameMixin.h. +/// + +#include "ALICE3GlobalReconstruction/TimeFrameGPU.h" + +namespace o2::trk +{ + +template class TimeFrameMixin<11, o2::its::gpu::TimeFrameGPU<11>>; +template class TimeFrameGPU<11>; + +} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx new file mode 100644 index 0000000000000..e870ee934816f --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx @@ -0,0 +1,306 @@ +// 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 TrackerACTS.cxx +/// \brief TRK tracker using ACTS seeding and track finding +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-04-01 +/// + +#include "ALICE3GlobalReconstruction/TrackerACTS.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::trk +{ + +template +TrackerACTS::TrackerACTS() +{ + // Initialize space points storage per layer + mSpacePointsPerLayer.resize(nLayers); + mHistSpacePoints = new TH2F("hSpacePoints", "Space points; x (cm); y (cm)", 200, -100, 100, 200, -100, 100); +} + +template +void TrackerACTS::adoptTimeFrame(o2::its::TimeFrame& tf) +{ + mTimeFrame = &tf; +} + +template +void TrackerACTS::buildSpacePoints(int rof) +{ + mSpacePoints.clear(); + for (auto& layerSPs : mSpacePointsPerLayer) { + layerSPs.clear(); + } + + // Get clusters from the TimeFrame and convert to space points + for (int layer = 0; layer < nLayers; ++layer) { + // For now we take unsorted clusters, as soon as the cluster trackin is in place we can piggy back on it and switch to the clusters + auto clusters = mTimeFrame->getUnsortedClusters()[layer]; + // Resize the clusters to the first 100 clusters for testing + // clusters = clusters.subspan(0, std::min(clusters.size(), 100)); + LOG(debug) << "ACTSTracker: got " << clusters.size() << " clusters"; + + for (size_t iCluster = 0; iCluster < clusters.size(); ++iCluster) { + const auto& cluster = clusters[iCluster]; + + SpacePoint sp; + // Check that these are in global coordinates + sp.x = cluster.xCoordinate * Acts::UnitConstants::cm; + sp.y = cluster.yCoordinate * Acts::UnitConstants::cm; + sp.z = cluster.zCoordinate * Acts::UnitConstants::cm; + + if (mHistSpacePoints) { + mHistSpacePoints->Fill(sp.x / Acts::UnitConstants::cm, sp.y / Acts::UnitConstants::cm); + } + sp.layer = layer; + sp.clusterId = static_cast(iCluster); + sp.rof = rof; + + // Position uncertainties (could be refined based on cluster properties) + sp.varianceR = 0.01f; // ~100 um resolution squared + sp.varianceZ = 0.01f; + + mSpacePoints.push_back(sp); + } + } + + // Build per-layer pointers for seeding + for (auto& sp : mSpacePoints) { + if (sp.layer >= 0 && sp.layer < nLayers) { + mSpacePointsPerLayer[sp.layer].push_back(&sp); + } + } +} + +template +void TrackerACTS::createSeeds() +{ + if (mSpacePoints.empty()) { + LOGF(info, "No space points available for seeding"); + return; + } + mSeeds.clear(); + + // Backend adaptor that exposes mSpacePoints to Acts::SpacePointContainer + struct SpacePointBackend { + using ValueType = SpacePoint; + explicit SpacePointBackend(const std::vector& sps) : m_sps{&sps} {} + std::size_t size_impl() const { return m_sps->size(); } + float x_impl(std::size_t i) const { return (*m_sps)[i].x; } + float y_impl(std::size_t i) const { return (*m_sps)[i].y; } + float z_impl(std::size_t i) const { return (*m_sps)[i].z; } + float varianceR_impl(std::size_t i) const { return (*m_sps)[i].varianceR; } + float varianceZ_impl(std::size_t i) const { return (*m_sps)[i].varianceZ; } + const SpacePoint& get_impl(std::size_t i) const { return (*m_sps)[i]; } + std::any component_impl(Acts::HashedString /*key*/, std::size_t /*i*/) const + { + LOG(fatal) << "No additional components available for space points"; + throw std::runtime_error("SpacePointBackend: no strip component available"); + } + const std::vector* m_sps; + }; + + // Wrap mSpacePoints in an Acts space-point container + SpacePointBackend backend{mSpacePoints}; + + // Configure the ACTS space point container + Acts::SpacePointContainerConfig spContainerConfig; + Acts::SpacePointContainerOptions spContainerOpts; + spContainerOpts.beamPos = {0.f, 0.f}; + Acts::SpacePointContainer spContainer{spContainerConfig, spContainerOpts, backend}; + + // Configure the ACTS seed finder + const unsigned int maxSeeds = static_cast(mConfig.maxSeedsPerMiddleSP); + Acts::SeedFilterConfig filterConfig; + filterConfig.maxSeedsPerSpM = maxSeeds; + + // ACTS requires minPt / bFieldInZ >= rMax / 2 (minHelixRadius >= rMax/2). + // Cap rMax so that the constraint is satisfied for the configured minPt and field. + const float bFieldInZ = mBz * Acts::UnitConstants::T; + const float safeRMax = 1.8f * mConfig.minPt / bFieldInZ; // 10% margin below the hard limit + + using SPProxy = typename Acts::SpacePointContainer::SpacePointProxyType; + Acts::SeedFinderConfig finderConfig; + finderConfig.rMin = 0.f; + finderConfig.rMax = 100.f * Acts::UnitConstants::cm; + finderConfig.zMin = mConfig.zMin; + finderConfig.zMax = mConfig.zMax; + finderConfig.deltaRMin = std::min(mConfig.deltaRMinBottom, mConfig.deltaRMinTop); + finderConfig.deltaRMax = std::max(mConfig.deltaRMaxBottom, mConfig.deltaRMaxTop); + finderConfig.deltaRMinBottomSP = mConfig.deltaRMinBottom; + finderConfig.deltaRMaxBottomSP = mConfig.deltaRMaxBottom; + finderConfig.deltaRMinTopSP = mConfig.deltaRMinTop; + finderConfig.deltaRMaxTopSP = mConfig.deltaRMaxTop; + finderConfig.collisionRegionMin = mConfig.collisionRegionMin; + finderConfig.collisionRegionMax = mConfig.collisionRegionMax; + finderConfig.cotThetaMax = mConfig.cotThetaMax; + finderConfig.minPt = mConfig.minPt; + finderConfig.impactMax = mConfig.maxImpactParameter; + finderConfig.maxSeedsPerSpM = maxSeeds; + finderConfig.sigmaScattering = 5.f; + finderConfig.radLengthPerSeed = 0.05f; + finderConfig.seedFilter = std::make_shared>(filterConfig); + finderConfig = finderConfig.calculateDerivedQuantities(); + Acts::SeedFinder> seedFinder{finderConfig, + Acts::getDefaultLogger("Finder", Acts::Logging::Level::VERBOSE)}; + + // Configure and create the cylindrical space-point grid + Acts::CylindricalSpacePointGridConfig gridConfig; + gridConfig.minPt = finderConfig.minPt; + gridConfig.rMin = finderConfig.rMin; + gridConfig.rMax = finderConfig.rMax; + gridConfig.zMin = finderConfig.zMin; + gridConfig.zMax = finderConfig.zMax; + gridConfig.deltaRMax = finderConfig.deltaRMax; + gridConfig.cotThetaMax = finderConfig.cotThetaMax; + gridConfig.impactMax = finderConfig.impactMax; + + Acts::CylindricalSpacePointGridOptions gridOpts; + gridOpts.bFieldInZ = bFieldInZ; + + Acts::SeedFinderOptions finderOpts; + finderOpts.beamPos = spContainerOpts.beamPos; + finderOpts.bFieldInZ = gridOpts.bFieldInZ; + try { + finderOpts = finderOpts.calculateDerivedQuantities(finderConfig); + } catch (const std::exception& e) { + LOG(fatal) << "Error in seed finder configuration: " << e.what(); + return; + } + + Acts::CylindricalSpacePointGrid grid = Acts::CylindricalSpacePointGridCreator::createGrid(gridConfig, gridOpts); + try { + Acts::CylindricalSpacePointGridCreator::fillGrid(finderConfig, finderOpts, grid, + spContainer.begin(), spContainer.end()); + } catch (const std::exception& e) { + LOG(fatal) << "Error during grid creation/filling: " << e.what(); + return; + } + LOG(debug) << "Grid created with " << grid.dimensions(); + + // Build the binned group and iterate over triplet combinations + Acts::GridBinFinder<3ul> bottomBinFinder{1, std::vector>{}, 0}; + Acts::GridBinFinder<3ul> topBinFinder{1, std::vector>{}, 0}; + Acts::CylindricalBinnedGroup spGroup{std::move(grid), bottomBinFinder, topBinFinder}; + + std::vector>> seedsPerGroup; + typename Acts::SeedFinder>::SeedingState seedingState; + seedingState.spacePointMutableData.resize(spContainer.size()); + const Acts::Range1D rMiddleSPRange; + for (auto [bottom, middle, top] : spGroup) { + auto& v = seedsPerGroup.emplace_back(); + try { + seedFinder.createSeedsForGroup(finderOpts, seedingState, spGroup.grid(), v, bottom, middle, top, rMiddleSPRange); + } catch (const std::exception& e) { + LOG(fatal) << "Error during seed finding for a group: " << e.what(); + return; + } + } + LOG(debug) << "Seed finding completed, found " << seedsPerGroup.size() << " groups with seeds"; + + // Convert Acts seeds to the internal SeedACTS representation + for (const auto& groupSeeds : seedsPerGroup) { + for (const auto& actsSeed : groupSeeds) { + SeedACTS seed; + seed.bottom = &actsSeed.sp()[0]->externalSpacePoint(); + seed.middle = &actsSeed.sp()[1]->externalSpacePoint(); + seed.top = &actsSeed.sp()[2]->externalSpacePoint(); + seed.quality = actsSeed.seedQuality(); + mSeeds.push_back(seed); + } + } + + LOGF(info, "Created %zu seeds from %zu space points", mSeeds.size(), mSpacePoints.size()); +} + +template +bool TrackerACTS::estimateTrackParams(const SeedACTS& seed, o2::its::TrackITSExt& track) const +{ + return true; +} + +template +void TrackerACTS::findTracks() +{ +} + +template +void TrackerACTS::computeTracksMClabels() +{ +} + +template +void TrackerACTS::clustersToTracks() +{ + if (!mTimeFrame) { + LOG(error) << "Cannot run TrackerACTS: No TimeFrame adopted"; + return; + } + + double totalTime = 0.; + LOG(info) << "==== TRK ACTS Tracking ===="; + LOG(info) << "Processing " << mTimeFrame->getNrof() << " ROFs with B = " << mBz << " T"; + + // Process each ROF + for (int iROF = 0; iROF < mTimeFrame->getNrof(); ++iROF) { + LOG(info) << "Processing ROF " << iROF; + // Build space points + mCurState = SpacePointBuilding; + totalTime += evaluateTask([this, iROF]() { buildSpacePoints(iROF); }, + StateNames[mCurState]); + + // Run seeding + mCurState = Seeding; + totalTime += evaluateTask([this]() { createSeeds(); }, + StateNames[mCurState]); + + // Find tracks + mCurState = TrackFinding; + totalTime += evaluateTask([this]() { findTracks(); }, + StateNames[mCurState]); + } + + // MC labeling + if (mTimeFrame->hasMCinformation()) { + computeTracksMClabels(); + } + + LOG(info) << "=== TimeFrame " << mTimeFrameCounter << " completed in: " << totalTime << " ms ==="; + + ++mTimeFrameCounter; + mTotalTime += totalTime; +} + +template +void TrackerACTS::printSummary() const +{ + float avgTF = mTimeFrameCounter > 0 ? static_cast(mTotalTime) / mTimeFrameCounter : 0.f; + LOGP(info, "TrackerACTS summary: Processed {} TFs in TOT={:.2f} ms, AVG/TF={:.2f} ms", + mTimeFrameCounter, mTotalTime, avgTF); +} + +// Explicit template instantiations +template class TrackerACTS<11>; +} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt new file mode 100644 index 0000000000000..6a4994e11467b --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt @@ -0,0 +1,69 @@ +# 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. + +o2_add_library(ALICE3GlobalReconstructionWorkflow + TARGETVARNAME targetName + SOURCES src/TrackerSpec.cxx + src/TrackWriterSpec.cxx + src/RecoWorkflow.cxx + PUBLIC_LINK_LIBRARIES O2::Framework + O2::GPUWorkflow + O2::SimConfig + O2::DataFormatsITSMFT + O2::DataFormatsTRK + O2::SimulationDataFormat + O2::DPLUtils + O2::TRKBase + O2::TRKSimulation + O2::ALICE3GlobalReconstruction + O2::CommonUtils + nlohmann_json::nlohmann_json) + +if(CUDA_ENABLED OR HIP_ENABLED) + target_compile_definitions(${targetName} PUBLIC TRK_HAS_GPU_TRACKING) +endif() + +if(CUDA_ENABLED) + find_package(CUDAToolkit REQUIRED) + target_compile_definitions(${targetName} PUBLIC TRK_HAS_CUDA_TRACKING) + o2_add_library(ALICE3GlobalReconstructionWorkflowCUDA + TARGETVARNAME cudaTargetName + SOURCES src/TrackerSpecGPU.cxx + ../reconstruction/src/TimeFrameGPU.cxx + ../reconstruction/src/GPUExternalAllocator.cu + PUBLIC_LINK_LIBRARIES + O2::ALICE3GlobalReconstructionWorkflow + O2::ITStrackingCUDA + PRIVATE_LINK_LIBRARIES + CUDA::cudart) + target_include_directories(${cudaTargetName} PRIVATE ${CUDAToolkit_INCLUDE_DIRS}) +endif() + +if(HIP_ENABLED) + target_compile_definitions(${targetName} PUBLIC TRK_HAS_HIP_TRACKING) + o2_add_hipified_library(ALICE3GlobalReconstructionWorkflowHIP + SOURCES src/TrackerSpecGPU.cxx + ../reconstruction/src/TimeFrameGPU.cxx + ../reconstruction/src/GPUExternalAllocator.cu + PUBLIC_LINK_LIBRARIES + O2::ALICE3GlobalReconstructionWorkflow + O2::ITStrackingHIP + PRIVATE_LINK_LIBRARIES + hip::host) +endif() + +o2_add_executable(reco-workflow + SOURCES src/alice3-global-reconstruction-workflow.cxx + COMPONENT_NAME alice3-global-reconstruction + PUBLIC_LINK_LIBRARIES O2::ALICE3GlobalReconstructionWorkflow + O2::TRKSimulation + O2::ALICE3GlobalReconstruction + O2::ITStracking) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md new file mode 100644 index 0000000000000..f22e95d6971db --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/README.md @@ -0,0 +1,133 @@ +# ALICE 3 Global Reconstruction Workflow + +This document describes how to run the ALICE 3 global reconstruction workflow and provides examples of configuration files. + +## Overview + +The global reconstruction workflow performs track reconstruction from simulated hits or TRK clusters, producing reconstructed tracks with MC truth labels. The workflow currently supports tracking using the Cellular Automaton (CA) algorithm. The output is stored to a ROOT file for offline analysis (example of QA macro provided in `TRK/macros/test/CheckTracksCA.C`). + +## Quick Start + +### Basic Command + +```bash +o2-alice3-global-reconstruction-reco-workflow --tracking-from-hits-config config_tracker.json -b +``` + +### Command Line Options + +- `--tracking-from-hits-config `: Path to tracking-from-hits configuration JSON file +- `--tracking-from-clusters-config `: Path to tracking-from-clusters configuration JSON file +- `--gpu-device `: Tracking device type (`1` CPU, `2` CUDA, `3` HIP) +- `-b`: Batch mode (no GUI) +- `--disable-root-output`: Skip writing tracks to ROOT file +- `--help`: Show all available options + +## Configuration File + +The tracking configuration is provided via a JSON file that specifies: +1. Input file paths +2. Geometry parameters (magnetic field, detector pitch) +3. Tracking algorithm parameters (can specify multiple iterations) + +### Example Configuration (`config_tracker.json`) + +```json +{ + "inputfiles": { + "hits": "o2sim_HitsTRK.root", + "geometry": "o2sim_geometry.root", + "mcHeader": "o2sim_MCHeader.root", + "kinematics": "o2sim_Kine.root" + }, + "geometry": { + "bz": 5.0, + "pitch": [0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004] + }, + "trackingparams": [{ + "NLayers": 11, + "DeltaROF": 0, + "LayerZ": [25.1, 25.1, 25.1, 64.2, 64.2, 64.2, 64.2, 64.2, 128.5, 128.5, 128.5], + "LayerRadii": [0.5, 1.2, 2.5, 7.05, 9.05, 12.05, 20.05, 30.05, 45.05, 60.5, 80.05], + "LayerxX0": [0.001, 0.001, 0.001, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], + "LayerResolution": [0.0003, 0.0003, 0.0003, 0.0003, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012], + "SystErrorY2": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + "SystErrorZ2": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + "AddTimeError": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "ZBins": 256, + "PhiBins": 128, + "nROFsPerIterations": -1, + "UseDiamond": false, + "Diamond": [0.0, 0.0, 0.0], + "AllowSharingFirstCluster": false, + "ClusterSharing": 0, + "MinTrackLength": 7, + "NSigmaCut": 10, + "PVres": 0.01, + "TrackletMinPt": 0.1, + "TrackletsPerClusterLimit": 2.0, + "CellDeltaTanLambdaSigma": 0.007, + "CellsPerClusterLimit": 2.0, + "MaxChi2ClusterAttachment": 60.0, + "MaxChi2NDF": 30.0, + "ReseedIfShorter": 6, + "MinPt": [0.0, 0.0, 0.0, 0.0, 0.0], + "StartLayerMask": 4095, + "RepeatRefitOut": false, + "ShiftRefToCluster": true, + "FindShortTracks": false, + "PerPrimaryVertexProcessing": false, + "SaveTimeBenchmarks": false, + "DoUPCIteration": false, + "FataliseUponFailure": true, + "UseTrackFollower": true, + "UseTrackFollowerTop": false, + "UseTrackFollowerBot": false, + "UseTrackFollowerMix": true, + "TrackFollowerNSigmaCutZ": 1.0, + "TrackFollowerNSigmaCutPhi": 1.0, + "createArtefactLabels": false, + "PrintMemory": false, + "DropTFUponFailure": false + }] +} +``` +Note that the `trackingparams` field can contain multiple sets of parameters for different iterations of the tracking algorithm. The example above shows a single iteration with 11 layers and it is **not** optimized. + +## Complete Workflow Example + +### 1. Run Simulation + +First, generate simulation data: + +```bash +o2-sim-serial-run5 -n 200 -g pythia8hi -m TRK --configKeyValues "Diamond.width[0]=0.01;Diamond.width[1]=0.01;Diamond.width[2]=5;TRKBase.layoutML=kTurboStaves;TRKBase.layoutOT=kStaggered;" +``` + +This produces, among other files: +- `o2sim_HitsTRK.root` +- `o2sim_geometry.root` +- `o2sim_MCHeader.root` +- `o2sim_Kine.root` +That will be used by the reconstruction as currently we do not have clusters. + +### 2. Run Reconstruction + +Execute the tracking workflow: + +```bash +o2-alice3-global-reconstruction-reco-workflow --tracking-from-hits-config config_tracker.json -b +``` + +This produces: +- `o2trac_trk.root`: Reconstructed tracks with MC labels + +### 3. Run Quality Assurance + +Analyze the tracking performance: + +```bash +root -l +.L CheckTracksCA.C+ +CheckTracksCA("o2trac_trk.root", "o2sim_Kine.root", "o2sim_HitsTRK.root", "trk_qa_output.root") +``` diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h new file mode 100644 index 0000000000000..98a5176d5db44 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h @@ -0,0 +1,30 @@ +// 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. + +#ifndef O2_ALICE3_GLOBALRECONSTRUCTION_RECOWORKFLOW_H +#define O2_ALICE3_GLOBALRECONSTRUCTION_RECOWORKFLOW_H + +#include "Framework/WorkflowSpec.h" +#include "GPUDataTypesConfig.h" +#include + +namespace o2::trk::global_reco_workflow +{ + +o2::framework::WorkflowSpec getWorkflow(bool useMC, + const std::string& hitRecoConfig, + const std::string& clusterRecoConfig, + bool disableRootOutput = false, + o2::gpu::gpudatatypes::DeviceType dType = o2::gpu::gpudatatypes::DeviceType::CPU); + +} // namespace o2::trk::global_reco_workflow + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackWriterSpec.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackWriterSpec.h rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackerSpec.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h similarity index 68% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackerSpec.h rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h index 33b25737bbc29..c1e7e051fb3f1 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackerSpec.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h @@ -22,6 +22,7 @@ #include #include "ITStracking/BoundedAllocator.h" +#include "ITStracking/ExternalAllocator.h" #include "ITStracking/TrackingInterface.h" #include "GPUDataTypesConfig.h" @@ -31,6 +32,10 @@ #include +#include +#include +#include + namespace o2::trk { class TrackerDPL : public framework::Task @@ -39,6 +44,7 @@ class TrackerDPL : public framework::Task TrackerDPL(std::shared_ptr gr, bool isMC, const std::string& hitRecoConfig, + const std::string& clusterRecoConfig, gpu::gpudatatypes::DeviceType dType = gpu::gpudatatypes::DeviceType::CPU); ~TrackerDPL() override = default; void init(framework::InitContext& ic) final; @@ -46,21 +52,34 @@ class TrackerDPL : public framework::Task void endOfStream(framework::EndOfStreamContext& ec) final; // void finaliseCCDB(framework::ConcreteDataMatcher& matcher, void* obj) final; void stop() final; + template + void runTracking(framework::ProcessingContext& pc, TimeFrameT& timeFrame, TrackerTraitsT& trackerTraits); + const std::shared_ptr& getGPUAllocator() const noexcept { return mGPUAllocator; } + void setGPUAllocator(std::shared_ptr allocator) { mGPUAllocator = std::move(allocator); } private: void updateTimeDependentParams(framework::ProcessingContext& pc); std::vector createTrackingParamsFromConfig(); + void runGPUTracking(framework::ProcessingContext& pc); // std::unique_ptr mRecChain = nullptr; // std::unique_ptr mChainITS = nullptr; // std::shared_ptr mGGCCDBRequest; // ITSTrackingInterface mITSTrackingInterface; + bool mIsMC{true}; + gpu::gpudatatypes::DeviceType mDeviceType{gpu::gpudatatypes::DeviceType::CPU}; std::shared_ptr mMemoryPool; + std::shared_ptr mGPUAllocator; std::shared_ptr mTaskArena; + std::vector mTrackingParams; nlohmann::json mHitRecoConfig; + nlohmann::json mClusterRecoConfig; TStopwatch mTimer; +#ifdef O2_WITH_ACTS + bool mUseACTS = false; +#endif }; -framework::DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, gpu::gpudatatypes::DeviceType dType = gpu::gpudatatypes::DeviceType::CPU); +framework::DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, const std::string& clusterRecoConfig, gpu::gpudatatypes::DeviceType dType = gpu::gpudatatypes::DeviceType::CPU); } // namespace o2::trk #endif /* O2_TRK_TRACKERDPL */ diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h new file mode 100644 index 0000000000000..f6221e485f369 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h @@ -0,0 +1,226 @@ +// 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. + +#ifndef O2_TRK_TRACKERSPECIMPL_H +#define O2_TRK_TRACKERSPECIMPL_H + +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" + +#include "CommonDataFormat/IRFrame.h" +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" +#include "DetectorsBase/GeometryManager.h" +#include "Field/MagFieldParam.h" +#include "Field/MagneticField.h" +#include "Framework/ControlService.h" +#include "ITStracking/Tracker.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCEventHeader.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "TRKBase/GeometryTGeo.h" +#include "TRKSimulation/Hit.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace o2::trk +{ + +template +void TrackerDPL::runTracking(framework::ProcessingContext& pc, TimeFrameT& timeFrame, TrackerTraitsT& trackerTraits) +{ + o2::its::Tracker<11> itsTracker(&trackerTraits); + timeFrame.setMemoryPool(mMemoryPool); + trackerTraits.setMemoryPool(mMemoryPool); + trackerTraits.setNThreads(mTaskArena->max_concurrency(), mTaskArena); + trackerTraits.adoptTimeFrame(static_cast*>(&timeFrame)); + itsTracker.adoptTimeFrame(timeFrame); + trackerTraits.updateTrackingParameters(mTrackingParams); + timeFrame.initTrackerTopologies(mTrackingParams, 11); + + int nRofs{0}; + if (!mHitRecoConfig.empty()) { + TFile hitsFile(mHitRecoConfig["inputfiles"]["hits"].get().c_str(), "READ"); + TFile mcHeaderFile(mHitRecoConfig["inputfiles"]["mcHeader"].get().c_str(), "READ"); + TTree* hitsTree = hitsFile.Get("o2sim"); + std::vector* trkHit = nullptr; + hitsTree->SetBranchAddress("TRKHit", &trkHit); + + TTree* mcHeaderTree = mcHeaderFile.Get("o2sim"); + auto mcheader = new o2::dataformats::MCEventHeader; + mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); + + o2::base::GeometryManager::loadGeometry(mHitRecoConfig["inputfiles"]["geometry"].get().c_str(), false, true); + auto* gman = o2::trk::GeometryTGeo::Instance(); + + const Long64_t nEvents{hitsTree->GetEntries()}; + LOGP(info, "Starting {} reconstruction from hits for {} events", trackerTraits.getName(), nEvents); + + trackerTraits.setBz(mHitRecoConfig["geometry"]["bz"].get()); + auto field = new field::MagneticField("ALICE3Mag", "ALICE 3 Magnetic Field", mHitRecoConfig["geometry"]["bz"].get() / 5.f, 0.0, o2::field::MagFieldParam::k5kGUniform); + TGeoGlobalMagField::Instance()->SetField(field); + TGeoGlobalMagField::Instance()->Lock(); + + nRofs = timeFrame.loadROFsFromHitTree(hitsTree, gman, mHitRecoConfig); + const int inROFpileup{mHitRecoConfig.contains("inROFpileup") ? mHitRecoConfig["inROFpileup"].get() : 1}; + timeFrame.getPrimaryVerticesFromMC(mcHeaderTree, nRofs, nEvents, inROFpileup); + } else if (!mClusterRecoConfig.empty()) { + LOGP(info, "Starting {} reconstruction from clusters", trackerTraits.getName()); + + o2::base::GeometryManager::loadGeometry(mClusterRecoConfig["inputfiles"]["geometry"].get().c_str(), false, true); + o2::trk::GeometryTGeo::Instance(); + + trackerTraits.setBz(mClusterRecoConfig["geometry"]["bz"].get()); + auto field = new field::MagneticField("ALICE3Mag", "ALICE 3 Magnetic Field", mClusterRecoConfig["geometry"]["bz"].get() / 5.f, 0.0, o2::field::MagFieldParam::k5kGUniform); + TGeoGlobalMagField::Instance()->SetField(field); + TGeoGlobalMagField::Instance()->Lock(); + + constexpr int nLayers{11}; + std::array, nLayers> layerClusters; + std::array, nLayers> layerPatterns; + std::array, nLayers> layerROFs; + std::array*, nLayers> layerLabels{}; + + size_t nInputRofs{0}; + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + layerClusters[iLayer] = pc.inputs().get>(std::format("compClusters_{}", iLayer)); + layerPatterns[iLayer] = pc.inputs().get>(std::format("patterns_{}", iLayer)); + layerROFs[iLayer] = pc.inputs().get>(std::format("ROframes_{}", iLayer)); + nInputRofs = std::max(nInputRofs, layerROFs[iLayer].size()); + if (mIsMC) { + layerLabels[iLayer] = pc.inputs().get*>(std::format("trkmclabels_{}", iLayer)).release(); + } + } + + timeFrame.deriveAndInitTiming(layerROFs); + + const float yPlaneMLOT = 0.0010f; + nRofs = timeFrame.loadROFrameData(layerROFs, layerClusters, layerPatterns, mIsMC ? &layerLabels : nullptr, yPlaneMLOT); + timeFrame.addTruthSeedingVertices(); + } + + const auto trackingLoopStart = std::chrono::steady_clock::now(); + for (size_t iter{0}; iter < mTrackingParams.size(); ++iter) { + LOGP(info, "{}", mTrackingParams[iter].asString()); + trackerTraits.initialiseTimeFrame(iter); + trackerTraits.computeLayerTracklets(iter, -1); + LOGP(info, "Number of tracklets in iteration {}: {}", iter, timeFrame.getNumberOfTracklets()); + trackerTraits.computeLayerCells(iter); + LOGP(info, "Number of cells in iteration {}: {}", iter, timeFrame.getNumberOfCells()); + trackerTraits.findCellsNeighbours(iter); + LOGP(info, "Number of cell neighbours in iteration {}: {}", iter, timeFrame.getNumberOfNeighbours()); + trackerTraits.findRoads(iter); + LOGP(info, "Number of roads in iteration {}: {}", iter, timeFrame.getNumberOfTracks()); + } + const auto trackingLoopElapsedMs = std::chrono::duration_cast(std::chrono::steady_clock::now() - trackingLoopStart).count(); + LOGP(info, "Tracking iterations block took {} ms", trackingLoopElapsedMs); + + if (mIsMC) { + itsTracker.computeTracksMClabels(); + } + + const auto& tracks = timeFrame.getTracks(); + const auto& labels = timeFrame.getTracksLabel(); + std::vector allTracks(tracks.begin(), tracks.end()); + std::vector allLabels; + + int totalTracks = allTracks.size(); + int goodTracks = 0; + int fakeTracks = 0; + + if (mIsMC) { + allLabels.assign(labels.begin(), labels.end()); + for (const auto& label : allLabels) { + if (label.isFake()) { + ++fakeTracks; + } else { + ++goodTracks; + } + } + } + + LOGP(info, "=== Tracking Summary ==="); + LOGP(info, "Total tracks reconstructed: {}", totalTracks); + LOGP(info, "Good tracks: {} ({:.1f}%)", goodTracks, totalTracks > 0 ? 100.0 * goodTracks / totalTracks : 0); + LOGP(info, "Fake tracks: {} ({:.1f}%)", fakeTracks, totalTracks > 0 ? 100.0 * fakeTracks / totalTracks : 0); + + const auto& rofView = timeFrame.getROFOverlapTableView(); + const auto& clockLayer = rofView.getClockLayer(); + const int clockLayerId = rofView.getClock(); + const int64_t anchorBC = timeFrame.getTFAnchorIR().toLong(); + + int highestROF = static_cast(clockLayer.mNROFsTF); + for (const auto& trc : allTracks) { + highestROF = std::max(highestROF, static_cast(clockLayer.getROF(trc.getTimeStamp()))); + } + for (const auto& vtx : timeFrame.getPrimaryVertices()) { + highestROF = std::max(highestROF, static_cast(clockLayer.getROF(vtx.getTimeStamp().lower()))); + } + + std::vector allTrackROFs(highestROF); + for (size_t iROF = 0; iROF < allTrackROFs.size(); ++iROF) { + auto& rof = allTrackROFs[iROF]; + o2::InteractionRecord ir; + ir.setFromLong(anchorBC + static_cast(clockLayer.getROFStartInBC(iROF))); + rof.setBCData(ir); + rof.setROFrame(iROF); + rof.setFirstEntry(0); + rof.setNEntries(0); + } + + std::vector rofEntries(highestROF + 1, 0); + for (const auto& trc : allTracks) { + const int rof = static_cast(clockLayer.getROF(trc.getTimeStamp())); + if (rof >= 0 && rof < highestROF) { + ++rofEntries[rof]; + } + } + std::exclusive_scan(rofEntries.begin(), rofEntries.end(), rofEntries.begin(), 0); + + std::vector irFrames; + irFrames.reserve(allTrackROFs.size()); + const auto& maskView = timeFrame.getROFMaskView(); + const auto rofLenMinus1 = clockLayer.mROFLength > 0 ? clockLayer.mROFLength - 1 : 0; + for (size_t iROF = 0; iROF < allTrackROFs.size(); ++iROF) { + allTrackROFs[iROF].setFirstEntry(rofEntries[iROF]); + allTrackROFs[iROF].setNEntries(rofEntries[iROF + 1] - rofEntries[iROF]); + if (maskView.isROFEnabled(clockLayerId, static_cast(iROF))) { + const auto& bcStart = allTrackROFs[iROF].getBCData(); + auto& irFrame = irFrames.emplace_back(bcStart, bcStart + rofLenMinus1); + irFrame.info = allTrackROFs[iROF].getNEntries(); + } + } + + pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKS", 0}, allTracks); + pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKSROF", 0}, allTrackROFs); + pc.outputs().snapshot(o2::framework::Output{"TRK", "IRFRAMES", 0}, irFrames); + if (mIsMC) { + pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKSMCTR", 0}, allLabels); + } + + LOGP(info, "TRK pushed {} tracks in {} ROFs and {} IR frames{}", + allTracks.size(), allTrackROFs.size(), irFrames.size(), + mIsMC ? " (with MC labels)" : ""); + + timeFrame.wipe(); +} + +} // namespace o2::trk + +#endif // O2_TRK_TRACKERSPECIMPL_H diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx new file mode 100644 index 0000000000000..024bd3b4425f8 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/RecoWorkflow.cxx @@ -0,0 +1,40 @@ +// 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. + +#include "ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h" +#include "Framework/Logger.h" + +namespace o2::trk::global_reco_workflow +{ + +framework::WorkflowSpec getWorkflow(bool useMC, + const std::string& hitRecoConfig, + const std::string& clusterRecoConfig, + bool disableRootOutput, + o2::gpu::gpudatatypes::DeviceType dtype) +{ + framework::WorkflowSpec specs; + + if (!hitRecoConfig.empty() || !clusterRecoConfig.empty()) { + LOG_IF(info, !hitRecoConfig.empty()) << "Using hit reco config from file " << hitRecoConfig; + LOG_IF(info, !clusterRecoConfig.empty()) << "Using cluster reco config from file " << clusterRecoConfig; + specs.emplace_back(o2::trk::getTrackerSpec(useMC, hitRecoConfig, clusterRecoConfig, dtype)); + if (!disableRootOutput) { + specs.emplace_back(o2::trk::getTrackWriterSpec(useMC)); + } + } + + return specs; +} + +} // namespace o2::trk::global_reco_workflow diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackWriterSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackWriterSpec.cxx similarity index 97% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackWriterSpec.cxx rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackWriterSpec.cxx index 1606c32a0ea78..9827c2fc2469d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackWriterSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackWriterSpec.cxx @@ -13,7 +13,7 @@ #include -#include "TRKWorkflow/TrackWriterSpec.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" #include "DataFormatsITS/TrackITS.h" #include "SimulationDataFormat/MCCompLabel.h" diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx new file mode 100644 index 0000000000000..070466ea8711d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -0,0 +1,381 @@ +// 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. + +#include +#include +#include +#include +#include +#include +#include + +#include "CommonUtils/DLLoaderBase.h" +#include "CommonDataFormat/IRFrame.h" +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" +#include "DetectorsBase/GeometryManager.h" +#include "ITStracking/TimeFrame.h" +#include "ITStracking/Configuration.h" +#include "Field/MagneticField.h" +#include "Field/MagFieldParam.h" +#include "Framework/ControlService.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/CCDBParamSpec.h" +#include "ITStracking/TrackingConfigParam.h" +#include "SimulationDataFormat/MCEventHeader.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "TRKBase/GeometryTGeo.h" +#include "TRKBase/SegmentationChip.h" +#include "TRKSimulation/Hit.h" +#include "ALICE3GlobalReconstruction/TimeFrame.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h" +#include + +#ifdef O2_WITH_ACTS +#include "ALICE3GlobalReconstruction/TrackerACTS.h" +#endif + +#include +#include + +namespace o2 +{ +using namespace framework; +namespace trk +{ +using Vertex = o2::dataformats::Vertex>; + +namespace +{ +class ALICE3TrackingBackendLoader : public o2::utils::DLLoaderBase +{ + O2DLLoaderDef(ALICE3TrackingBackendLoader) +}; + +O2DLLoaderImpl(ALICE3TrackingBackendLoader) + + constexpr const char* kGPUBackendFunction = "runALICE3GPUTracking"; +} // namespace + +TrackerDPL::TrackerDPL(std::shared_ptr gr, + bool isMC, + const std::string& hitRecoConfigFileName, + const std::string& clusterRecoConfigFileName, + o2::gpu::gpudatatypes::DeviceType dType) +{ + if (!hitRecoConfigFileName.empty()) { + std::ifstream configFile(hitRecoConfigFileName); + mHitRecoConfig = nlohmann::json::parse(configFile); + } + if (!clusterRecoConfigFileName.empty()) { + std::ifstream configFile(clusterRecoConfigFileName); + mClusterRecoConfig = nlohmann::json::parse(configFile); + } + mIsMC = isMC; + mDeviceType = dType; +} + +void TrackerDPL::init(InitContext& ic) +{ +#ifdef O2_WITH_ACTS + mUseACTS = ic.options().get("useACTS"); +#endif +} + +void TrackerDPL::stop() +{ + LOGF(info, "CPU Reconstruction total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); +} + +std::vector TrackerDPL::createTrackingParamsFromConfig() +{ + std::vector trackingParams; + auto loadTrackingParamsFromJson = [](std::vector& trackingParams, const nlohmann::json& paramConfigJson) { + for (const auto& paramConfig : paramConfigJson) { + o2::its::TrackingParameters params; + + if (paramConfig.contains("NLayers")) { + params.NLayers = paramConfig["NLayers"].get(); + } + if (paramConfig.contains("ZBins")) { + params.ZBins = paramConfig["ZBins"].get(); + } + if (paramConfig.contains("PhiBins")) { + params.PhiBins = paramConfig["PhiBins"].get(); + } + if (paramConfig.contains("SharedMaxClusters")) { + params.SharedMaxClusters = paramConfig["SharedMaxClusters"].get(); + } + if (paramConfig.contains("MinTrackLength")) { + params.MinTrackLength = paramConfig["MinTrackLength"].get(); + } + if (paramConfig.contains("ReseedIfShorter")) { + params.ReseedIfShorter = paramConfig["ReseedIfShorter"].get(); + } + if (paramConfig.contains("StartLayerMask")) { + params.StartLayerMask = paramConfig["StartLayerMask"].get(); + } + + if (paramConfig.contains("NSigmaCut")) { + params.NSigmaCut = paramConfig["NSigmaCut"].get(); + } + if (paramConfig.contains("PVres")) { + params.PVres = paramConfig["PVres"].get(); + } + if (paramConfig.contains("TrackletMinPt")) { + params.TrackletMinPt = paramConfig["TrackletMinPt"].get(); + } + if (paramConfig.contains("CellDeltaTanLambdaSigma")) { + params.CellDeltaTanLambdaSigma = paramConfig["CellDeltaTanLambdaSigma"].get(); + } + if (paramConfig.contains("MaxChi2ClusterAttachment")) { + params.MaxChi2ClusterAttachment = paramConfig["MaxChi2ClusterAttachment"].get(); + } + if (paramConfig.contains("MaxChi2NDF")) { + params.MaxChi2NDF = paramConfig["MaxChi2NDF"].get(); + } + + if (paramConfig.contains("UseDiamond")) { + params.UseDiamond = paramConfig["UseDiamond"].get(); + } + if (paramConfig.contains("AllowSharingFirstCluster")) { + params.AllowSharingFirstCluster = paramConfig["AllowSharingFirstCluster"].get(); + } + if (paramConfig.contains("RepeatRefitOut")) { + params.RepeatRefitOut = paramConfig["RepeatRefitOut"].get(); + } + if (paramConfig.contains("ShiftRefToCluster")) { + params.ShiftRefToCluster = paramConfig["ShiftRefToCluster"].get(); + } + if (paramConfig.contains("PerPrimaryVertexProcessing")) { + params.PerPrimaryVertexProcessing = paramConfig["PerPrimaryVertexProcessing"].get(); + } + if (paramConfig.contains("SaveTimeBenchmarks")) { + params.SaveTimeBenchmarks = paramConfig["SaveTimeBenchmarks"].get(); + } + if (paramConfig.contains("DoUPCIteration")) { + params.DoUPCIteration = paramConfig["DoUPCIteration"].get(); + } + if (paramConfig.contains("FataliseUponFailure")) { + params.FataliseUponFailure = paramConfig["FataliseUponFailure"].get(); + } + if (paramConfig.contains("CreateArtefactLabels")) { + params.CreateArtefactLabels = paramConfig["CreateArtefactLabels"].get(); + } + if (paramConfig.contains("PrintMemory")) { + params.PrintMemory = paramConfig["PrintMemory"].get(); + } + if (paramConfig.contains("DropTFUponFailure")) { + params.DropTFUponFailure = paramConfig["DropTFUponFailure"].get(); + } + + if (paramConfig.contains("LayerZ")) { + params.LayerZ = paramConfig["LayerZ"].get>(); + } + if (paramConfig.contains("LayerRadii")) { + params.LayerRadii = paramConfig["LayerRadii"].get>(); + } + if (paramConfig.contains("LayerxX0")) { + params.LayerxX0 = paramConfig["LayerxX0"].get>(); + } + if (paramConfig.contains("LayerResolution")) { + params.LayerResolution = paramConfig["LayerResolution"].get>(); + } + if (paramConfig.contains("SystErrorY2")) { + params.SystErrorY2 = paramConfig["SystErrorY2"].get>(); + } + if (paramConfig.contains("SystErrorZ2")) { + params.SystErrorZ2 = paramConfig["SystErrorZ2"].get>(); + } + if (paramConfig.contains("MinPt")) { + params.MinPt = paramConfig["MinPt"].get>(); + } + if (paramConfig.contains("AddTimeError")) { + params.AddTimeError = paramConfig["AddTimeError"].get>(); + } + + if (paramConfig.contains("Diamond") && paramConfig["Diamond"].is_array() && paramConfig["Diamond"].size() == 3) { + params.Diamond[0] = paramConfig["Diamond"][0].get(); + params.Diamond[1] = paramConfig["Diamond"][1].get(); + params.Diamond[2] = paramConfig["Diamond"][2].get(); + } + + if (paramConfig.contains("MaxMemory")) { + params.MaxMemory = paramConfig["MaxMemory"].get(); + } + + if (paramConfig.contains("CorrType")) { + int corrTypeInt = paramConfig["CorrType"].get(); + params.CorrType = static_cast::MatCorrType>(corrTypeInt); + } + + const auto nLayers = static_cast(params.NLayers); + LOG_IF(fatal, params.LayerZ.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter LayerZ: expected " << nLayers << " entries, got " << params.LayerZ.size(); + LOG_IF(fatal, params.LayerRadii.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter LayerRadii: expected " << nLayers << " entries, got " << params.LayerRadii.size(); + LOG_IF(fatal, params.LayerxX0.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter LayerxX0: expected " << nLayers << " entries, got " << params.LayerxX0.size(); + LOG_IF(fatal, params.LayerResolution.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter LayerResolution: expected " << nLayers << " entries, got " << params.LayerResolution.size(); + LOG_IF(fatal, params.SystErrorY2.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter SystErrorY2: expected " << nLayers << " entries, got " << params.SystErrorY2.size(); + LOG_IF(fatal, params.SystErrorZ2.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter SystErrorZ2: expected " << nLayers << " entries, got " << params.SystErrorZ2.size(); + LOG_IF(fatal, params.AddTimeError.size() != nLayers) << "Invalid ALICE3 TRK tracking parameter AddTimeError: expected " << nLayers << " entries, got " << params.AddTimeError.size(); + + LOG_IF(fatal, params.MinTrackLength > params.NLayers) << "Invalid ALICE3 TRK tracking parameter MinTrackLength: expected <= NLayers (" << params.NLayers << "), got " << params.MinTrackLength; + const auto minPtSize = static_cast(params.NLayers - params.MinTrackLength + 1); + LOG_IF(fatal, params.MinPt.size() != minPtSize) << "Invalid ALICE3 TRK tracking parameter MinPt: expected " << minPtSize << " entries, got " << params.MinPt.size(); + + trackingParams.push_back(params); + } + }; + + if (mHitRecoConfig.contains("trackingparams") && mHitRecoConfig["trackingparams"].is_array()) { + loadTrackingParamsFromJson(trackingParams, mHitRecoConfig["trackingparams"]); + } else if (mClusterRecoConfig.contains("trackingparams") && mClusterRecoConfig["trackingparams"].is_array()) { + loadTrackingParamsFromJson(trackingParams, mClusterRecoConfig["trackingparams"]); + } else { + LOGP(fatal, "No trackingparams field found in configuration or it is not an array. Returning empty vector."); + return trackingParams; + } + + LOGP(info, "Loaded {} tracking parameter sets from configuration", trackingParams.size()); + return trackingParams; +} + +void TrackerDPL::run(ProcessingContext& pc) +{ + if (mMemoryPool.get() == nullptr) { + mMemoryPool = std::make_shared(); + } + if (mTaskArena.get() == nullptr) { + mTaskArena = std::make_shared(1); /// TODO: make it configurable + } + + mTrackingParams = createTrackingParamsFromConfig(); + + auto cput = mTimer.CpuTime(); + auto realt = mTimer.RealTime(); + mTimer.Start(false); + + const bool useGPU = mDeviceType != o2::gpu::gpudatatypes::DeviceType::CPU; + + if (useGPU) { + runGPUTracking(pc); + } else { + o2::trk::TimeFrame<11> timeFrame; + o2::its::TrackerTraits<11> itsTrackerTraits; + runTracking(pc, timeFrame, itsTrackerTraits); + } + + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(framework::QuitRequest::Me); + + mTimer.Stop(); + LOGP(info, "CPU Reconstruction time for this TF {} s (cpu), {} s (wall)", mTimer.CpuTime() - cput, mTimer.RealTime() - realt); +} + +void TrackerDPL::runGPUTracking(ProcessingContext& pc) +{ + auto& loader = ALICE3TrackingBackendLoader::Instance(); + switch (mDeviceType) { + case o2::gpu::gpudatatypes::DeviceType::CUDA: +#ifdef TRK_HAS_CUDA_TRACKING + loader.executeFunctionAlias("O2ALICE3GlobalReconstructionWorkflowCUDA", kGPUBackendFunction, this, &pc); + return; +#else + LOGP(fatal, "CUDA TRK GPU tracking was requested but this build has no CUDA TRK GPU tracking backend"); +#endif + case o2::gpu::gpudatatypes::DeviceType::HIP: +#ifdef TRK_HAS_HIP_TRACKING + loader.executeFunctionAlias("O2ALICE3GlobalReconstructionWorkflowHIP", kGPUBackendFunction, this, &pc); + return; +#else + LOGP(fatal, "HIP TRK GPU tracking was requested but this build has no HIP TRK GPU tracking backend"); +#endif + default: + LOGP(fatal, "Unsupported TRK GPU device type {}", static_cast(mDeviceType)); + } +} + +void TrackerDPL::endOfStream(EndOfStreamContext& ec) +{ + LOGF(info, "TRK CA-Tracker total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); +} + +DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, const std::string& clusterRecoConfig, o2::gpu::gpudatatypes::DeviceType dType) +{ + std::vector inputs; + std::vector outputs; + outputs.emplace_back("TRK", "TRACKS", 0, Lifetime::Timeframe); + outputs.emplace_back("TRK", "TRACKSROF", 0, Lifetime::Timeframe); + outputs.emplace_back("TRK", "IRFRAMES", 0, Lifetime::Timeframe); + auto ggRequest = std::make_shared(false, // orbitResetTime + false, // GRPECS=true + false, // GRPLHCIF + false, // GRPMagField + false, // askMatLUT + o2::base::GRPGeomRequest::None, // geometry, but ignored until it will be put in the CCDB + inputs, + true); + + if (!hitRecoConfig.empty()) { + if (useMC) { + outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); + } + return DataProcessorSpec{ + "trk-hits-tracker", + {}, + outputs, + AlgorithmSpec{adaptFromTask(ggRequest, + useMC, + hitRecoConfig, + clusterRecoConfig, + dType)}, + Options{ConfigParamSpec{"max-loops", VariantType::Int, 1, {"max number of loops"}} +#ifdef O2_WITH_ACTS + , + {"useACTS", o2::framework::VariantType::Bool, false, {"Use ACTS for tracking"}} +#endif + }}; + } + + inputs.emplace_back("dummy", "TRK", "DUMMY", 0, Lifetime::Timeframe); + + if (!clusterRecoConfig.empty()) { + inputs.pop_back(); + constexpr int nLayers{11}; + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + inputs.emplace_back(std::format("compClusters_{}", iLayer), "TRK", "COMPCLUSTERS", iLayer, Lifetime::Timeframe); + inputs.emplace_back(std::format("patterns_{}", iLayer), "TRK", "PATTERNS", iLayer, Lifetime::Timeframe); + inputs.emplace_back(std::format("ROframes_{}", iLayer), "TRK", "CLUSTERSROF", iLayer, Lifetime::Timeframe); + if (useMC) { + inputs.emplace_back(std::format("trkmclabels_{}", iLayer), "TRK", "CLUSTERSMCTR", iLayer, Lifetime::Timeframe); + } + } + } + + if (useMC) { + outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); + } + + return DataProcessorSpec{ + "trk-tracker", + inputs, + outputs, + AlgorithmSpec{adaptFromTask(ggRequest, + useMC, + hitRecoConfig, + clusterRecoConfig, + dType)}, + Options{}}; +} + +} // namespace trk +} // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpecGPU.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpecGPU.cxx new file mode 100644 index 0000000000000..ea98ab3f852e5 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpecGPU.cxx @@ -0,0 +1,28 @@ +// 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. + +#include "ALICE3GlobalReconstruction/GPUExternalAllocator.h" +#include "ALICE3GlobalReconstruction/TimeFrameGPU.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" +#include "ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h" +#include "ITStrackingGPU/TrackerTraitsGPU.h" + +extern "C" int runALICE3GPUTracking(o2::trk::TrackerDPL* tracker, o2::framework::ProcessingContext* pc) +{ + o2::trk::TimeFrameGPU<11> timeFrame; + o2::its::TrackerTraitsGPU<11> itsTrackerTraits; + if (!tracker->getGPUAllocator()) { + tracker->setGPUAllocator(std::make_shared()); + } + timeFrame.setFrameworkAllocator(tracker->getGPUAllocator().get()); + tracker->runTracking(*pc, timeFrame, itsTrackerTraits); + return 0; +} diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx new file mode 100644 index 0000000000000..7e9950f4def2e --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/alice3-global-reconstruction-workflow.cxx @@ -0,0 +1,65 @@ +// 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. + +#include "ALICE3GlobalReconstructionWorkflow/RecoWorkflow.h" +#include "CommonUtils/ConfigurableParam.h" + +#include "Framework/CallbacksPolicy.h" +#include "Framework/ConfigContext.h" +#include "Framework/CompletionPolicyHelpers.h" + +#include +#include + +using namespace o2::framework; + +void customize(std::vector& policies) +{ + // o2::raw::HBFUtilsInitializer::addNewTimeSliceCallback(policies); +} + +void customize(std::vector& policies) +{ + policies.push_back(CompletionPolicyHelpers::consumeWhenAllOrdered(".*(?:TRK|trk).*[W,w]riter.*")); +} + +void customize(std::vector& workflowOptions) +{ + std::vector options{ + {"disable-root-output", VariantType::Bool, false, {"do not write output root files"}}, + {"disable-mc", VariantType::Bool, false, {"disable MC propagation even if available"}}, + {"tracking-from-hits-config", VariantType::String, "", {"JSON file with tracking from hits configuration"}}, + {"tracking-from-clusters-config", VariantType::String, "", {"JSON file with tracking from clusters configuration"}}, + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, + {"gpu-device", VariantType::Int, 1, {"use gpu device: CPU=1,CUDA=2,HIP=3 (default: CPU)"}}}; + std::swap(workflowOptions, options); +} + +#include "Framework/runDataProcessing.h" +#include "Framework/Logger.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) +{ + auto useMC = !configcontext.options().get("disable-mc"); + auto hitRecoConfig = configcontext.options().get("tracking-from-hits-config"); + auto clusterRecoConfig = configcontext.options().get("tracking-from-clusters-config"); + auto gpuDevice = static_cast(configcontext.options().get("gpu-device")); + auto disableRootOutput = configcontext.options().get("disable-root-output"); + o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); + + if (hitRecoConfig.empty() && clusterRecoConfig.empty()) { + throw std::invalid_argument("no reconstruction input configured: provide either --tracking-from-hits-config or --tracking-from-clusters-config "); + } + + o2::conf::ConfigurableParam::writeINI("o2alice3globalrecoflow_configuration.ini"); + + return o2::trk::global_reco_workflow::getWorkflow(useMC, hitRecoConfig, clusterRecoConfig, disableRootOutput, gpuDevice); +} diff --git a/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt index 83838a01d13f1..04288f205d8f4 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/IOTOF/CMakeLists.txt @@ -10,4 +10,6 @@ # or submit itself to any jurisdiction. add_subdirectory(base) -add_subdirectory(simulation) \ No newline at end of file +add_subdirectory(simulation) +add_subdirectory(DataFormatsIOTOF) +add_subdirectory(macros) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt new file mode 100644 index 0000000000000..534e6217807c5 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/CMakeLists.txt @@ -0,0 +1,22 @@ +# 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. + +o2_add_library(DataFormatsIOTOF + SOURCES src/Digit.cxx + # SOURCES src/MCLabel.cxx + # SOURCES src/Cluster.cxx + PUBLIC_LINK_LIBRARIES O2::DataFormatsITSMFT) + +o2_target_root_dictionary(DataFormatsIOTOF + HEADERS include/DataFormatsIOTOF/Digit.h + # HEADERS include/DataFormatsIOTOF/MCLabel.h + # HEADERS include/DataFormatsIOTOF/Cluster.h + ) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/include/DataFormatsIOTOF/Digit.h b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/include/DataFormatsIOTOF/Digit.h new file mode 100644 index 0000000000000..19b5dc3bcd72b --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/include/DataFormatsIOTOF/Digit.h @@ -0,0 +1,46 @@ +// 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 Digit.h +/// \brief Definition of IOTOF digit class +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-03-17 +/// + +#ifndef ALICEO2_IOTOF_DIGIT_H +#define ALICEO2_IOTOF_DIGIT_H + +#include "DataFormatsITSMFT/Digit.h" + +namespace o2::iotof +{ +class Digit : public o2::itsmft::Digit +{ + public: + Digit() = default; + ~Digit() = default; + Digit(UShort_t chipindex = 0, UShort_t row = 0, UShort_t col = 0, Int_t charge = 0, double time = 0.) + : o2::itsmft::Digit(chipindex, row, col, charge), mTime(time) {}; + + // Setters + void setTime(double time) { mTime = time; } + + // Getters + double getTime() const { return mTime; } + + private: + double mTime = 0.; ///< Measured time (ns) + ClassDefNV(Digit, 1); +}; + +} // namespace o2::iotof +#endif // ALICEO2_IOTOF_DIGIT_H diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TRKReconstructionLinkDef.h b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h similarity index 78% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TRKReconstructionLinkDef.h rename to Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h index 1f4c2193b91b1..8a167df4d6c7b 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TRKReconstructionLinkDef.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h @@ -15,11 +15,6 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::trk::TimeFrame < 11> + ; -#pragma link C++ class o2::trk::Clusterer + ; -#ifdef O2_WITH_ACTS -#pragma link C++ class o2::trk::ClustererACTS + ; - -#endif - +#pragma link C++ class o2::iotof::Digit + ; +// #pragma link C++ class std::vector < o2::iotof::Digit> + ; #endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/Digit.cxx b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/Digit.cxx new file mode 100644 index 0000000000000..b15ecd94cd9af --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/Digit.cxx @@ -0,0 +1,21 @@ +// 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 Digit.cxx +/// \brief Implementation of IOTOF digit class +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-03-17 +/// + +#include "DataFormatsIOTOF/Digit.h" + +using namespace o2::iotof; diff --git a/Detectors/Upgrades/ALICE3/IOTOF/README.md b/Detectors/Upgrades/ALICE3/IOTOF/README.md index fba4d12252af6..e52b5e2379e9c 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/README.md +++ b/Detectors/Upgrades/ALICE3/IOTOF/README.md @@ -14,16 +14,18 @@ Configurables for various sub-detectors are presented in the following Table: [link to definitions](./base/include/IOTOFBase/IOTOFBaseParam.h) -| Options | Choices | Comments | -| ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------- | -| `IOTOFBase.enableInnerTOF` | `true` (default), `false` | Enable inner TOF barrel layer | -| `IOTOFBase.enableOuterTOF` | `true` (default), `false` | Enable outer TOF barrel layer | -| `IOTOFBase.enableForwardTOF` | `true` (default), `false` | Enable forward TOF endcap | -| `IOTOFBase.enableBackwardTOF` | `true` (default), `false` | Enable backward TOF endcap | -| `IOTOFBase.segmentedInnerTOF` | `false` (default), `true` | Use segmented geometry for inner TOF | -| `IOTOFBase.segmentedOuterTOF` | `false` (default), `true` | Use segmented geometry for outer TOF | -| `IOTOFBase.detectorPattern` | ` ` (default), `v3b`, `v3b1a`, `v3b1b`, `v3b2a`, `v3b2b`, `v3b3` | Optional layout pattern | -| `IOTOFBase.x2x0` | `0.02` (default) | Chip thickness in fractions of the rad. lenght | +| Options | Choices | Comments | +| ----------------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------- | +| `IOTOFBase.enableInnerTOF` | `true` (default), `false` | Enable inner TOF barrel layer | +| `IOTOFBase.enableOuterTOF` | `true` (default), `false` | Enable outer TOF barrel layer | +| `IOTOFBase.enableForwardTOF` | `true` (default), `false` | Enable forward TOF endcap | +| `IOTOFBase.enableBackwardTOF` | `true` (default), `false` | Enable backward TOF endcap | +| `IOTOFBase.segmentedInnerTOF` | `false` (default), `true` | Use segmented geometry for inner TOF | +| `IOTOFBase.segmentedOuterTOF` | `false` (default), `true` | Use segmented geometry for outer TOF | +| `IOTOFBase.detectorPattern` | ` ` (default), `v3b`, `v3b1a`, `v3b1b`, `v3b2a`, `v3b2b`, `v3b3` | Optional layout pattern | +| `IOTOFBase.x2x0` | `0.02` (default) | Chip thickness in fractions of the rad. lenght | +| `IOTOFBase.sensorThickness` | `0.0050` (default) | Sensor thickness in cm, can be at maximum equivalent to the chip thickness | + For example, a geometry with fully cylindrical tracker barrel (for all layers in VD, ML and OT) can be obtained by diff --git a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/GeometryTGeo.h index 577bd1bcabaf1..b998619684b28 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/GeometryTGeo.h @@ -22,6 +22,8 @@ namespace iotof class GeometryTGeo : public o2::detectors::DetMatrixCache { public: + using DetMatrixCache::getMatrixL2G; + GeometryTGeo(bool build = false, int loadTrans = 0); void Build(int loadTrans); void fillMatrixCache(int mask); @@ -79,7 +81,25 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache static const char* composeBTOFSymNameChip(int d, int lr); static const char* composeBTOFSymNameSensor(int d, int layer); + int getIOTOFFirstChipIndex(int lay) const; + int getIOTOFLayer(int index) const; + int getIOTOFChipIndex(int lay, int sta, int mod, int chip) const; + bool getIOTOFChipId(int index, int& lay, int& sta, int& mod, int& chip) const; + + /// Get the transformation matrix of the SENSOR (not necessary the same as the chip) + /// for a given chip 'index' by querying the TGeoManager + TGeoHMatrix* extractMatrixSensor(int index) const; + + TString getMatrixPath(int index) const; + protected: + // Determine the number of active parts in the geometry + int extractNumberOfStavesIOTOF(int lay) const; + int extractNumberOfModulesIOTOF(int lay) const; + int extractNumberOfChipsPerModuleIOTOF(int lay) const; + int extractNumberOfChipsFTOF() const; + int extractNumberOfChipsBTOF() const; + // i/oTOF mother volume static std::string sIOTOFVolumeName; @@ -107,10 +127,24 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache static std::string sBTOFChipName; static std::string sBTOFSensorName; + // Inner/outer TOF + int mNumberOfStavesIOTOF[2]; + int mNumberOfModulesIOTOF[2]; + int mNumberOfChipsPerModuleIOTOF[2]; + int mNumberOfChipsPerStaveIOTOF[2]; + int mNumberOfChipsIOTOF[2]; + int mLastChipIndex[2]; + + // Forward TOF + int mNumberOfChipsFTOF; + + // Backward TOF + int mNumberOfChipsBTOF; + private: static std::unique_ptr sInstance; }; } // namespace iotof } // namespace o2 -#endif \ No newline at end of file +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h index 91d005415891d..c4cf5fd8844a8 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h @@ -20,6 +20,24 @@ namespace o2 namespace iotof { +struct ChipSpecifics { + int NCols = 0; + int NRows = 0; + float PitchCol = 0.; + float PitchRow = 0.; + float PassiveEdgeReadOut = 0.; + float PassiveEdgeTop = 0.; + float PassiveEdgeSide = 0.; + float SensorLayerThicknessEff = 0.; + float SensorLayerThickness = 0.; + + int NPixels() const { return NCols * NRows; } + float ActiveMatrixSizeCols() const { return PitchCol * NCols; } + float ActiveMatrixSizeRows() const { return PitchRow * NRows; } + float SensorSizeCols() const { return ActiveMatrixSizeCols() + 2 * PassiveEdgeSide; } + float SensorSizeRows() const { return ActiveMatrixSizeRows() + PassiveEdgeTop + PassiveEdgeReadOut; } +}; + struct IOTOFBaseParam : public o2::conf::ConfigurableParamHelper { bool enableInnerTOF = true; // Enable Inner TOF layer bool enableOuterTOF = true; // Enable Outer TOF layer @@ -29,6 +47,10 @@ struct IOTOFBaseParam : public o2::conf::ConfigurableParamHelper bool segmentedInnerTOF = false; // If the inner TOF layer is segmented bool segmentedOuterTOF = false; // If the outer TOF layer is segmented float x2x0 = 0.02f; // thickness expressed in radiation length, for all layers for the moment + float sensorThickness = 0.0050f; // thickness of the sensor in cm, for all layers for the moment, the default is set to 50 microns + + ChipSpecifics iTofChipSpecifics{258, 271, 250.00e-4, 100.00e-4, 0.00f, 0.00e-4, 0.00e-4, 50.e-4, 50.e-4}; + ChipSpecifics oTofChipSpecifics{251, 487, 250.00e-4, 100.00e-4, 0.00f, 0.00e-4, 106.48e-4, 50.e-4, 50.e-4}; O2ParamDef(IOTOFBaseParam, "IOTOFBase"); }; @@ -36,4 +58,4 @@ struct IOTOFBaseParam : public o2::conf::ConfigurableParamHelper } // namespace iotof } // end namespace o2 -#endif \ No newline at end of file +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/IOTOF/base/src/GeometryTGeo.cxx index f7d0eb135a27a..eb209931207e3 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/src/GeometryTGeo.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/src/GeometryTGeo.cxx @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include +#include #include namespace o2 @@ -55,6 +56,171 @@ GeometryTGeo::GeometryTGeo(bool build, int loadTrans) : DetMatrixCache() } } +int GeometryTGeo::extractNumberOfStavesIOTOF(int lay) const +{ + int numberOfStaves{0}; + + std::string layName = lay == 0 ? GeometryTGeo::getITOFLayerPattern() : GeometryTGeo::getOTOFLayerPattern(); + TGeoVolume* layV = gGeoManager->GetVolume(layName.c_str()); + if (layV == nullptr) { + LOG(fatal) << "Can't find volume " << layName; + return -1; + } + + TObjArray* nodes = layV->GetNodes(); + int nNodes = nodes->GetEntriesFast(); + + for (int j{0}; j < nNodes; ++j) { + if (strstr(nodes->At(j)->GetName(), lay == 0 ? GeometryTGeo::getITOFStavePattern() : GeometryTGeo::getOTOFStavePattern()) != nullptr) { + numberOfStaves++; + } + } + + return numberOfStaves; +} + +int GeometryTGeo::extractNumberOfModulesIOTOF(int lay) const +{ + int numberOfModules{0}; + + std::string staveName = lay == 0 ? GeometryTGeo::getITOFStavePattern() : GeometryTGeo::getOTOFStavePattern(); + TGeoVolume* staveV = gGeoManager->GetVolume(staveName.c_str()); + if (staveV == nullptr) { + LOG(fatal) << "Can't find volume " << staveName; + return -1; + } + + TObjArray* nodes = staveV->GetNodes(); + int nNodes = nodes->GetEntriesFast(); + + for (int j{0}; j < nNodes; ++j) { + if (strstr(nodes->At(j)->GetName(), lay == 0 ? GeometryTGeo::getITOFModulePattern() : GeometryTGeo::getOTOFModulePattern()) != nullptr) { + numberOfModules++; + } + } + + return numberOfModules; +} + +int GeometryTGeo::extractNumberOfChipsPerModuleIOTOF(int lay) const +{ + int numberOfChips{0}; + + std::string moduleName = lay == 0 ? GeometryTGeo::getITOFModulePattern() : GeometryTGeo::getOTOFModulePattern(); + TGeoVolume* moduleV = gGeoManager->GetVolume(moduleName.c_str()); + if (moduleV == nullptr) { + LOG(fatal) << "Can't find volume " << moduleName; + return -1; + } + + TObjArray* nodes = moduleV->GetNodes(); + int nNodes = nodes->GetEntriesFast(); + + for (int j{0}; j < nNodes; ++j) { + if (strstr(nodes->At(j)->GetName(), lay == 0 ? GeometryTGeo::getITOFChipPattern() : GeometryTGeo::getOTOFChipPattern()) != nullptr) { + numberOfChips++; + } + } + + return numberOfChips; +} + +int GeometryTGeo::extractNumberOfChipsFTOF() const +{ + return 0; +} + +int GeometryTGeo::extractNumberOfChipsBTOF() const +{ + return 0; +} + +int GeometryTGeo::getIOTOFFirstChipIndex(int lay) const +{ + return lay == 0 ? 0 : mLastChipIndex[0] + 1; +} + +int GeometryTGeo::getIOTOFLayer(int index) const +{ + if (index < 0 || index > mLastChipIndex[1]) { + LOG(fatal) << "Invalid chip index " << index; + return -1; + } + return index > mLastChipIndex[0] ? 1 : 0; +} + +int GeometryTGeo::getIOTOFChipIndex(int lay, int sta, int mod, int chip) const +{ + return getIOTOFFirstChipIndex(lay) + (sta - 1) * mNumberOfChipsPerStaveIOTOF[lay] + (mod - 1) * mNumberOfChipsPerModuleIOTOF[lay] + (chip - 1); +} + +bool GeometryTGeo::getIOTOFChipId(int index, int& lay, int& sta, int& mod, int& chip) const +{ + lay = getIOTOFLayer(index); + index -= getIOTOFFirstChipIndex(lay); + sta = mNumberOfStavesIOTOF[lay] > 0 ? index / mNumberOfChipsPerStaveIOTOF[lay] : -1; + index %= mNumberOfChipsPerStaveIOTOF[lay]; + mod = mNumberOfModulesIOTOF[lay] > 0 ? index / mNumberOfChipsPerModuleIOTOF[lay] : -1; + chip = index % mNumberOfChipsPerModuleIOTOF[lay]; + return true; +} + +TString GeometryTGeo::getMatrixPath(int index) const +{ + int lay, sta, mod, chip; + getIOTOFChipId(index, lay, sta, mod, chip); + + TString path = Form("/cave_1/barrel_1/%s_2/", GeometryTGeo::getIOTOFVolPattern()); + sta += 1; + mod += 1; + chip += 1; + + if (lay == 0) { + path += Form("%s_1/", GeometryTGeo::getITOFLayerPattern()); + if (mNumberOfStavesIOTOF[lay] > 0) + path += Form("%s_%d/", GeometryTGeo::getITOFStavePattern(), sta); + if (mNumberOfModulesIOTOF[lay] > 0) + path += Form("%s_%d/", GeometryTGeo::getITOFModulePattern(), mod); + if (mNumberOfChipsPerModuleIOTOF[lay] > 0) + path += Form("%s_%d/%s_1", GeometryTGeo::getITOFChipPattern(), chip, GeometryTGeo::getITOFSensorPattern()); + } else { + path += Form("%s_1/", GeometryTGeo::getOTOFLayerPattern()); + if (mNumberOfStavesIOTOF[lay] > 0) + path += Form("%s_%d/", GeometryTGeo::getOTOFStavePattern(), sta); + if (mNumberOfModulesIOTOF[lay] > 0) + path += Form("%s_%d/", GeometryTGeo::getOTOFModulePattern(), mod); + if (mNumberOfChipsPerModuleIOTOF[lay] > 0) + path += Form("%s_%d/%s_1", GeometryTGeo::getOTOFChipPattern(), chip, GeometryTGeo::getOTOFSensorPattern()); + } + + return path; +} + +TGeoHMatrix* GeometryTGeo::extractMatrixSensor(int index) const +{ + auto path = getMatrixPath(index); + + static TGeoHMatrix matTmp; + gGeoManager->PushPath(); + + if (!gGeoManager->cd(path.Data())) { + gGeoManager->PopPath(); + LOG(error) << "Error in cd-ing to " << path.Data(); + return nullptr; + } + + matTmp = *gGeoManager->GetCurrentMatrix(); + // LOG(info) << "Path = " << path.Data(); + + // Restore the modeler state + gGeoManager->PopPath(); + + // account for the difference between physical sensitive layer (where charge collection is simulated) and effective sensor thicknesses + // TODO: apply translation by the effective sensor thickness, not yet done (see ITS) + + return &matTmp; +} + void GeometryTGeo::Build(int loadTrans) { if (isBuilt()) { @@ -66,11 +232,58 @@ void GeometryTGeo::Build(int loadTrans) LOGP(fatal, "Geometry is not loaded"); } + auto& iotofPars = IOTOFBaseParam::Instance(); + if (!iotofPars.segmentedInnerTOF && !iotofPars.segmentedOuterTOF) { + return; + } + + // Inner/outer TOF + for (int j{0}; j < 2; ++j) { + mNumberOfStavesIOTOF[j] = extractNumberOfStavesIOTOF(j); + mNumberOfModulesIOTOF[j] = extractNumberOfModulesIOTOF(j); + mNumberOfChipsPerModuleIOTOF[j] = extractNumberOfChipsPerModuleIOTOF(j); + } + + // Forward TOF + mNumberOfChipsFTOF = extractNumberOfChipsFTOF(); + + // Backward TOF + mNumberOfChipsBTOF = extractNumberOfChipsBTOF(); + + int numberOfChips{0}; + for (int j{0}; j < 2; ++j) { + mNumberOfChipsPerStaveIOTOF[j] = mNumberOfModulesIOTOF[j] * mNumberOfChipsPerModuleIOTOF[j]; + mNumberOfChipsIOTOF[j] = mNumberOfStavesIOTOF[j] * mNumberOfChipsPerStaveIOTOF[j]; + numberOfChips += mNumberOfChipsIOTOF[j]; + mLastChipIndex[j] = numberOfChips - 1; + } + + LOG(info) << "numberOfChipsITOF = " << mNumberOfChipsIOTOF[0] << ", numberOfChipsOTOF = " << mNumberOfChipsIOTOF[1] << ", numberOfChips = " << numberOfChips << ", mNumberOfChipesPerStaveITOF" << mNumberOfChipsPerStaveIOTOF[0]; + + setSize(numberOfChips); fillMatrixCache(loadTrans); + // fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); } void GeometryTGeo::fillMatrixCache(int mask) { + if (mSize < 1) { + LOG(warning) << "The method Build was not called yet"; + Build(mask); + return; + } + + if ((mask & o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)) && !getCacheL2G().isFilled()) { + // Matrices for Local (Sensor!!! rather than the full chip) to Global frame transformation + LOG(info) << "Loading " << getName() << " L2G matrices from TGeo; there are " << mSize << " matrices"; + auto& cacheL2G = getCacheL2G(); + cacheL2G.setSize(mSize); + + for (int i = 0; i < mSize; i++) { + TGeoHMatrix* hm = extractMatrixSensor(i); + cacheL2G.setMatrix(o2::math_utils::Transform3D(*hm), i); + } + } } GeometryTGeo* GeometryTGeo::Instance() diff --git a/Utilities/Tools/cpulimit/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt similarity index 72% rename from Utilities/Tools/cpulimit/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt index f1109c65fdb69..41b800ed114b4 100644 --- a/Utilities/Tools/cpulimit/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/IOTOF/macros/CMakeLists.txt @@ -9,8 +9,8 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. -add_executable(cpulimit - cpulimit.c list.c process_group.c process_iterator.c) -target_compile_definitions(cpulimit PUBLIC _GNU_SOURCE) +o2_add_test_root_macro(defineIOTOFGeo.C + LABELS alice3) -install(TARGETS cpulimit DESTINATION share/scripts/) +o2_add_test_root_macro(drawTOFGeometry.C + LABELS alice3) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/macros/defineIOTOFGeo.C b/Detectors/Upgrades/ALICE3/IOTOF/macros/defineIOTOFGeo.C new file mode 100644 index 0000000000000..f096fc85aec7a --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/macros/defineIOTOFGeo.C @@ -0,0 +1,139 @@ +// 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. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void defineIOTOFGeo(const double rAvg = 21, // cm, average radius of the layer (used for stave size calculations) + const int nStaves = 24, // Number of staves + const double staveWidth = 5.42, // cm, Stave width (arc length at avg radius at 0 degrees) + const double staveHeightX2X0 = 0.02, // Stave height (radial at 0 degrees) + const double staveTilt = 10 // Stave tilt angle in degrees +) +{ + const double Si_X0 = 9.5f; // cm, radiation length of silicon + const double staveHeight = staveHeightX2X0 * Si_X0; + + // 1. Define inner and outer radii for the disk. + // The radius corresponds to the distance of the center of the stave to the origin + const double rInner = rAvg - staveHeight / 2.0; + const double rOuter = rAvg + staveHeight / 2.0; + + const double alpha = staveTilt * TMath::DegToRad(); // Tilt angle in radians + const double H = staveHeight; + const double W = staveWidth; + + // 2. Analytical calculation of Inscribed and Outscribed Radii + // We project the global origin (0,0) into the local, unrotated coordinate + // system of a single stave centered at (0,0). + const double u0 = -rAvg * TMath::Cos(alpha); + const double v0 = rAvg * TMath::Sin(alpha); + + // Inscribed Radius: Distance to the closest point on the stave rectangle + const double uc = std::max(-H / 2.0, std::min(H / 2.0, u0)); + const double vc = std::max(-W / 2.0, std::min(W / 2.0, v0)); + const double rInscribed = TMath::Sqrt((uc - u0) * (uc - u0) + (vc - v0) * (vc - v0)); + + // Outscribed Radius: Maximum distance to one of the 4 corners + double rOutscribed = 0; + const double uCorners[4] = {-H / 2.0, H / 2.0, H / 2.0, -H / 2.0}; + const double vCorners[4] = {-W / 2.0, -W / 2.0, W / 2.0, W / 2.0}; + for (int i = 0; i < 4; ++i) { + const double dist = std::hypot(uCorners[i] - u0, vCorners[i] - v0); + if (dist > rOutscribed) { + rOutscribed = dist; + } + } + + // 3. Visualization + new TCanvas("DiskWithStaves", "Disk with Staves", 800, 800); + gPad->SetGrid(); + gPad->SetLeftMargin(0.15); + gPad->SetBottomMargin(0.15); + gPad->SetRightMargin(0.05); + gPad->SetTopMargin(0.05); + + const double maxR = std::max(rOuter, rOutscribed) * 1.5; + gPad->DrawFrame(-maxR, -maxR, maxR, maxR, ";X (cm);Y (cm)"); + + // Draw Inner and Outer Disk Radii (Reference) + TArc* arcInner = new TArc(0, 0, rInner); + arcInner->SetLineStyle(2); + arcInner->SetLineColor(kGray + 1); + arcInner->SetFillStyle(0); + arcInner->Draw("same"); + + TArc* arcOuter = new TArc(0, 0, rOuter); + arcOuter->SetLineStyle(2); + arcOuter->SetLineColor(kGray + 1); + arcOuter->SetFillStyle(0); + arcOuter->Draw("same"); + + // Draw Inscribed and Outscribed circles + TArc* arcInscribed = new TArc(0, 0, rInscribed); + arcInscribed->SetLineColor(kBlue); + arcInscribed->SetLineWidth(2); + arcInscribed->SetFillStyle(0); + arcInscribed->Draw("same"); + + TArc* arcOutscribed = new TArc(0, 0, rOutscribed); + arcOutscribed->SetLineColor(kRed); + arcOutscribed->SetLineWidth(2); + arcOutscribed->SetFillStyle(0); + arcOutscribed->Draw("same"); + + // Generate and Draw Staves + for (int i = 0; i < nStaves; ++i) { + double phi = i * TMath::TwoPi() / nStaves; + double xPts[5], yPts[5]; + for (int j = 0; j < 4; ++j) { + double u = uCorners[j]; + double v = vCorners[j]; + // Apply stave tilt (alpha) around its own center + double uRot = u * TMath::Cos(alpha) - v * TMath::Sin(alpha); + double vRot = u * TMath::Sin(alpha) + v * TMath::Cos(alpha); + // Move stave to rAvg and apply azimuthal rotation (phi) + double x_phi0 = rAvg + uRot; + double y_phi0 = vRot; + xPts[j] = x_phi0 * TMath::Cos(phi) - y_phi0 * TMath::Sin(phi); + yPts[j] = x_phi0 * TMath::Sin(phi) + y_phi0 * TMath::Cos(phi); + } + // Close the geometric polygon + xPts[4] = xPts[0]; + yPts[4] = yPts[0]; + TGraph* gStave = new TGraph(5, xPts, yPts); + gStave->SetFillColorAlpha(kGreen + 2, 0.4); + gStave->SetLineColor(kBlack); + gStave->SetLineWidth(1); + gStave->Draw("f same"); // Fill + gStave->Draw("l same"); // Outline + } + + // 7. Add Legend / Parameter Text + TLatex* tex = new TLatex(); + tex->SetNDC(); + tex->SetTextSize(0.028); + tex->SetTextFont(42); + tex->SetTextColor(kBlack); + tex->DrawLatex(0.12, 0.88, Form("R_{inner} = %.1f, R_{outer} = %.1f", rInner, rOuter)); + tex->DrawLatex(0.12, 0.84, Form("Staves: %d, Tilt: %.1f#circ", nStaves, staveTilt)); + tex->SetTextColor(kBlue); + tex->DrawLatex(0.12, 0.80, Form("Inscribed Radius = %.2f", rInscribed)); + tex->SetTextColor(kRed); + tex->DrawLatex(0.12, 0.76, Form("Outscribed Radius = %.2f", rOutscribed)); +} diff --git a/Detectors/Upgrades/ALICE3/IOTOF/macros/drawTOFGeometry.C b/Detectors/Upgrades/ALICE3/IOTOF/macros/drawTOFGeometry.C new file mode 100644 index 0000000000000..4e58fb54fbf6e --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/macros/drawTOFGeometry.C @@ -0,0 +1,90 @@ +// 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. + +#include "IOTOFBase/GeometryTGeo.h" +#include "IOTOFSimulation/Layer.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +void ensureMedium(const char* name, int id, double a, double z, double density) +{ + if (!gGeoManager->GetMedium(name)) { + auto* mat = new TGeoMaterial(name, a, z, density); + new TGeoMedium(name, id, mat); + } +} + +void prepareMinimalMedia() +{ + ensureMedium("VACUUM$", 0, 1., 1., 1.e-16); + ensureMedium("TF3_AIR$", 1, 14.61, 7.3, 1.20479e-3); + ensureMedium("TF3_SILICON$", 3, 28.086, 14., 2.33); +} +} // namespace + +void drawTOFGeometry(double x2x0 = 0.02, + double sensorThickness = 0.005, + bool checkOverlaps = true, + double overlapToleranceCm = 0.01) +{ + gStyle->SetOptStat(0); + + if (gGeoManager) { + delete gGeoManager; + } + + auto* geo = new TGeoManager("IOTOFGeomFromLayer", "Geometry built from Layer.h classes"); + prepareMinimalMedia(); + + auto* top = geo->MakeBox("TOP", geo->GetMedium("VACUUM$"), 1200., 1200., 1200.); + geo->SetTopVolume(top); + + auto* mother = new TGeoVolumeAssembly("IOTOFMacroVol"); + top->AddNode(mother, 1, new TGeoTranslation(0., 0., 0.)); + + // Build using the same classes and createLayer() used by detector geometry code. + o2::iotof::ITOFLayer itof(o2::iotof::GeometryTGeo::getITOFLayerPattern(), + 21.f, 0.f, 129.f, 0.f, x2x0, + o2::iotof::Layer::kBarrelSegmented, + 24, 5.42, 3.0, 10, sensorThickness); + + o2::iotof::OTOFLayer otof(o2::iotof::GeometryTGeo::getOTOFLayerPattern(), + 92.f, 0.f, 680.f, 0.f, x2x0, + o2::iotof::Layer::kBarrelSegmented, + 62, 9.74, 5.0, 54, sensorThickness); + + itof.createLayer(mother); + otof.createLayer(mother); + + geo->CloseGeometry(); + + std::cout << "Built geometry from Layer.h classes with x2x0=" << x2x0 + << " and sensorThickness=" << sensorThickness << " cm\n"; + std::cout << "ITOF sensitive volumes: " << o2::iotof::ITOFLayer::mRegister.size() << "\n"; + std::cout << "OTOF sensitive volumes: " << o2::iotof::OTOFLayer::mRegister.size() << "\n"; + + if (checkOverlaps) { + std::cout << "Checking overlaps with tolerance=" << overlapToleranceCm << " cm\n"; + geo->CheckOverlaps(overlapToleranceCm); + geo->PrintOverlaps(); + } + + top->Draw("ogl"); +} diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/IOTOF/simulation/CMakeLists.txt index 5e7cbd87bfd35..25d623c0047a9 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/CMakeLists.txt @@ -12,11 +12,17 @@ o2_add_library(IOTOFSimulation SOURCES src/Layer.cxx src/Detector.cxx + src/Digitizer.cxx # src/IOTOFServices.cxx + src/Segmentation.cxx PUBLIC_LINK_LIBRARIES O2::IOTOFBase + O2::DataFormatsIOTOF O2::ITSMFTSimulation) o2_target_root_dictionary(IOTOFSimulation HEADERS include/IOTOFSimulation/Detector.h - include/IOTOFSimulation/Layer.h) - # include/IOTOFSimulation/IOTOFServices.h) \ No newline at end of file + include/IOTOFSimulation/Layer.h + include/IOTOFSimulation/Digitizer.h + # include/IOTOFSimulation/IOTOFServices.h + include/IOTOFSimulation/Segmentation.h + ) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Detector.h b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Detector.h index acf754e1b1fa8..34097020c42ff 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Detector.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Detector.h @@ -60,7 +60,7 @@ class Detector : public o2::base::DetImpl return nullptr; } - void configLayers(bool itof = true, bool otof = true, bool ftof = true, bool btof = true, std::string pattern = "", bool itofSegmented = false, bool otofSegmented = false, const float x2x0 = 0.02f); + void configLayers(bool itof = true, bool otof = true, bool ftof = true, bool btof = true, std::string pattern = "", bool itofSegmented = false, bool otofSegmented = false, const float x2x0 = 0.02f, const float sensorThickness = 0.0050f); void configServices(); void createMaterials(); @@ -104,4 +104,4 @@ struct UseShm { } // namespace base } // namespace o2 #endif -#endif \ No newline at end of file +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Digitizer.h b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Digitizer.h new file mode 100644 index 0000000000000..aae989248f07e --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Digitizer.h @@ -0,0 +1,114 @@ +// 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 Digitizer.h +/// \brief Definition of the ALICE3 TOF digitizer +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-03-17 +/// + +#ifndef ALICEO2_IOTOF_DIGITIZER_H +#define ALICEO2_IOTOF_DIGITIZER_H + +#include "ITSMFTSimulation/Hit.h" +#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsIOTOF/Digit.h" +#include "DataFormatsITSMFT/ROFRecord.h" +#include "CommonDataFormat/InteractionRecord.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "IOTOFBase/GeometryTGeo.h" +#include "IOTOFSimulation/Segmentation.h" + +namespace o2::iotof +{ + +/// \class Digitizer +/// \brief Digitizer for the ALICE3 Inner/Outer TOF detector +/// +/// Converts MC hits into detector digits by: +/// - Applying time smearing according to detector resolution +/// - Converting energy loss to charge +/// - Applying charge threshold +/// - Managing readout frames (ROF) +class Digitizer +{ + public: + void setDigits(std::vector* dig) { mDigits = dig; } + void setMCLabels(o2::dataformats::MCTruthContainer* mclb) { mMCLabels = mclb; } + void setROFRecords(std::vector* rec) { mROFRecords = rec; } + + /// Initialize the digitizer + void init(); + + /// Steer conversion of hits to digits + void process(const std::vector* hits, int evID, int srcID); + + /// Set the event time + void setEventTime(const o2::InteractionTimeRecord& irt) { mEventTime = irt; } + + /// Set continuous readout mode + void setContinuous(bool v) { mContinuous = v; } + bool isContinuous() const { return mContinuous; } + + /// Flush the output container + void fillOutputContainer(); + + // Provide the common iotof::GeometryTGeo to access matrices and segmentation + void setGeometry(const o2::iotof::GeometryTGeo* gm) { mGeometry = gm; } + + // Setters for digitization parameters + void setChargeThreshold(float thr) { mChargeThreshold = thr; } + void setTimeResolution(float res) { mTimeResolution = res; } + void setEfficiency(float eff) { mEfficiency = eff; } + void setEnergyToCharge(float e2c) { mEnergyToCharge = e2c; } + + // Getters + float getChargeThreshold() const { return mChargeThreshold; } + float getTimeResolution() const { return mTimeResolution; } + float getEfficiency() const { return mEfficiency; } + + private: + /// Process a single hit + void processHit(const o2::itsmft::Hit& hit, int evID, int srcID); + + /// Apply time smearing to simulate detector resolution + double smearTime(double time) const; + + /// Convert energy loss to charge + int energyToCharge(float energyLoss) const; + + /// Check if the hit passes efficiency cut + bool isEfficient() const; + + static constexpr float sec2ns = 1e9f; ///< seconds to nanoseconds conversion + + const o2::iotof::GeometryTGeo* mGeometry = nullptr; ///< IOTOF geometry + + std::vector* mDigits = nullptr; //! output digits + std::vector* mROFRecords = nullptr; //! output ROF records + o2::dataformats::MCTruthContainer* mMCLabels = nullptr; //! output labels + + o2::InteractionTimeRecord mEventTime; ///< global event time and interaction record + bool mContinuous = true; ///< continuous readout mode + + // Digitization parameters + float mChargeThreshold = 100.f; ///< charge threshold for digit creation (electrons) + float mTimeResolution = 0.020f; ///< time resolution sigma in ns (20 ps default) + float mEfficiency = 0.98f; ///< detection efficiency + float mEnergyToCharge = 3.6e-9f; ///< energy loss to electrons conversion (3.6 eV per e-h pair in Si) + + static o2::iotof::Segmentation* sSegmentation; ///< IOTOF segmentation instance (singleton) +}; +} // namespace o2::iotof + +#endif \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Layer.h b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Layer.h index 29542810b8021..dc9fedf439a11 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Layer.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Layer.h @@ -26,7 +26,7 @@ class Layer public: Layer() = default; Layer(std::string layerName, float rInn, float rOut, float zLength, float zOffset, float layerX2X0, - int layout = kBarrel, int nStaves = 0, float staveSize = 0.0, double staveTiltAngle = 0.0, int modulesPerStave = 0); + int layout = kBarrel, int nStaves = 0, float staveSize = 0.0, double staveTiltAngle = 0.0, int modulesPerStave = 0, float sensorThickness = 0.0f); ~Layer() = default; auto getInnerRadius() const { return mInnerRadius; } @@ -52,8 +52,9 @@ class Layer float mZLength; float mZOffset{0.f}; // Of use when fwd layers float mX2X0; - float mChipThickness; - int mLayout{kBarrel}; // Identifier of the type of layer layout (barrel, disk, barrel segmented, disk segmented) + float mChipThickness; // Thickness of the chip in cm, derived from mX2X0 and the radiation length of silicon + float mSensorThickness; // Thickness of the sensor in cm, to be subtracted from the chip thickness to get the total module thickness + int mLayout{kBarrel}; // Identifier of the type of layer layout (barrel, disk, barrel segmented, disk segmented) // To be used only in case of the segmented layout, to define the number of staves in phi (for barrel) or in r (for disk) std::pair mStaves{0, 0.0f}; // Number and size of staves in phi (for barrel) or in r (for disk) in case of segmented layout int mModulesPerStave{0}; // Number of modules along a stave @@ -92,4 +93,4 @@ class BTOFLayer : public Layer } // namespace iotof } // namespace o2 -#endif // ALICEO2_IOTOF_LAYER_H \ No newline at end of file +#endif // ALICEO2_IOTOF_LAYER_H diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Segmentation.h b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Segmentation.h new file mode 100644 index 0000000000000..cd0ab55bd03d7 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Segmentation.h @@ -0,0 +1,215 @@ +// 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 Segmentation.h +/// \brief Definition of the Segmentation class +/// \author Giorgio Alberto Lucia: giorgio.alberto.lucia@cern.ch + +#ifndef ALICEO2_IOTOF_SEGMENTATION_H +#define ALICEO2_IOTOF_SEGMENTATION_H + +#include +#include +#include "MathUtils/Cartesian.h" +#include "IOTOFBase/IOTOFBaseParam.h" + +namespace o2 +{ +namespace iotof +{ + +/// Segmentation and response for pixels in inner and outer TOF of the ALICE 3 apparatus +/// Questions to solve: +class Segmentation +{ + private: + Segmentation(); + static std::unique_ptr sInstance; + + public: + ChipSpecifics mITofSpecsConfig; + ChipSpecifics mOTofSpecsConfig; + static Segmentation* Instance(); + + ~Segmentation() = default; + + void configChip(const int nCols, const int nRows, const float pitchCol, const float pitchRow, const float passiveEdgeReadOut, const float passiveEdgeTop, + const float passiveEdgeSide, const float sensorLayerThicknessEff, const float sensorLayerThickness, const int subDetectorID); + void configChip(const ChipSpecifics& specsConfig, const int subDetectorID); + + /// Transformation from Geant detector centered local coordinates (cm) to + /// Pixel cell numbers iRow and iCol. + /// Returns kTRUE if point x,z is inside sensitive volume, kFALSE otherwise. + /// A value of -1 for iRow or iCol indicates that this point is outside of the + /// detector segmentation as defined. + /// \param float x Detector local coordinate x in cm with respect to + /// the center of the sensitive volume. + /// \param float z Detector local coordinate z in cm with respect to + /// the center of the sensitive volulme. + /// \param int iRow Detector x cell coordinate. Has the range 0 <= iRow < mNumberOfRows + /// \param int iCol Detector z cell coordinate. Has the range 0 <= iCol < mNumberOfColumns + bool localToDetector(float x, float z, int& iRow, int& iCol, const int subDetectorID); + /// same but w/o check for row/column range + void localToDetectorUnchecked(float xRow, float zCol, int& iRow, int& iCol, const int subDetectorID); + + /// Transformation from Detector cell coordiantes to Geant detector centered + /// local coordinates (cm) + /// \param int iRow Detector x cell coordinate. Has the range 0 <= iRow < mNumberOfRows + /// \param int iCol Detector z cell coordinate. Has the range 0 <= iCol < mNumberOfColumns + /// \param float x Detector local coordinate x in cm with respect to the + /// center of the sensitive volume. + /// \param float z Detector local coordinate z in cm with respect to the + /// center of the sensitive volulme. + /// If iRow and or iCol is outside of the segmentation range a value of -0.5*Dx() + /// or -0.5*Dz() is returned. + + // w/o check for row/col range + template + void detectorToLocalUnchecked(L row, L col, T& xRow, T& zCol, const int subDetectorID) + { + if (subDetectorID != 0 && subDetectorID != 1) { + row = col = -1; + return; + } + const ChipSpecifics& specsConfig = (subDetectorID == 0) ? mITofSpecsConfig : mOTofSpecsConfig; + xRow = getFirstRowCoordinate(subDetectorID) - row * specsConfig.PitchRow; + zCol = col * specsConfig.PitchCol + getFirstColCoordinate(subDetectorID); + } + template + void detectorToLocalUnchecked(L row, L col, math_utils::Point3D& loc, const int subDetectorID) + { + if (subDetectorID != 0 && subDetectorID != 1) { + row = col = -1; + return; + } + const ChipSpecifics& specsConfig = (subDetectorID == 0) ? mITofSpecsConfig : mOTofSpecsConfig; + loc.SetCoordinates(getFirstRowCoordinate(subDetectorID) - row * specsConfig.PitchRow, T(0.), col * specsConfig.PitchCol + getFirstColCoordinate(subDetectorID)); + } + template + void detectorToLocalUnchecked(L row, L col, std::array& loc, const int subDetectorID) + { + if (subDetectorID != 0 && subDetectorID != 1) { + row = col = -1; + return; + } + const ChipSpecifics& specsConfig = (subDetectorID == 0) ? mITofSpecsConfig : mOTofSpecsConfig; + loc[0] = getFirstRowCoordinate(subDetectorID) - row * specsConfig.PitchRow; + loc[1] = T(0); + loc[2] = col * specsConfig.PitchCol + getFirstColCoordinate(subDetectorID); + } + + // same but with check for row/col range + + template + bool detectorToLocal(L row, L col, T& xRow, T& zCol, const int subDetectorID) + { + if (subDetectorID != 0 && subDetectorID != 1) { + row = col = -1; + return false; + } + const ChipSpecifics& specsConfig = (subDetectorID == 0) ? mITofSpecsConfig : mOTofSpecsConfig; + if (row < 0 || row >= specsConfig.NRows || col < 0 || col >= specsConfig.NCols) { + return false; + } + detectorToLocalUnchecked(row, col, xRow, zCol, subDetectorID); + return true; + } + + template + bool detectorToLocal(L row, L col, math_utils::Point3D& loc, const int subDetectorID) + { + if (subDetectorID != 0 && subDetectorID != 1) { + row = col = -1; + return false; + } + const ChipSpecifics& specsConfig = (subDetectorID == 0) ? mITofSpecsConfig : mOTofSpecsConfig; + if (row < 0 || row >= specsConfig.NRows || col < 0 || col >= specsConfig.NCols) { + return false; + } + detectorToLocalUnchecked(row, col, loc, subDetectorID); + return true; + } + template + bool detectorToLocal(L row, L col, std::array& loc, const int subDetectorID) + { + if (subDetectorID != 0 && subDetectorID != 1) { + row = col = -1; + return false; + } + const ChipSpecifics& specsConfig = (subDetectorID == 0) ? mITofSpecsConfig : mOTofSpecsConfig; + if (row < 0 || row >= specsConfig.NRows || col < 0 || col >= specsConfig.NCols) { + return false; + } + detectorToLocalUnchecked(row, col, loc, subDetectorID); + return true; + } + + float getFirstRowCoordinate(const int subDetectorID) + { + const ChipSpecifics& specsConfig = (subDetectorID == 0) ? mITofSpecsConfig : mOTofSpecsConfig; + return 0.5 * ((specsConfig.ActiveMatrixSizeRows() - specsConfig.PassiveEdgeTop + specsConfig.PassiveEdgeReadOut) - specsConfig.PitchRow); + } + float getFirstColCoordinate(const int subDetectorID) + { + const ChipSpecifics& specsConfig = (subDetectorID == 0) ? mITofSpecsConfig : mOTofSpecsConfig; + return 0.5 * (specsConfig.PitchCol - specsConfig.ActiveMatrixSizeCols()); + } + + void print(); + + ClassDefNV(Segmentation, 1); // Segmentation class upgrade pixels +}; + +//_________________________________________________________________________________________________ +inline void Segmentation::localToDetectorUnchecked(float xRow, float zCol, int& iRow, int& iCol, const int subDetectorID) +{ + // convert to row/col w/o over/underflow check + if (subDetectorID != 0 && subDetectorID != 1) { + iRow = iCol = -1; + return; + } + const ChipSpecifics& specsConfig = (subDetectorID == 0) ? mITofSpecsConfig : mOTofSpecsConfig; + xRow = 0.5 * (specsConfig.ActiveMatrixSizeRows() - specsConfig.PassiveEdgeTop + specsConfig.PassiveEdgeReadOut) - xRow; // coordinate wrt top edge of Active matrix + zCol += 0.5 * specsConfig.ActiveMatrixSizeCols(); // coordinate wrt left edge of Active matrix + iRow = int(xRow / specsConfig.PitchRow); + iCol = int(zCol / specsConfig.PitchCol); + if (xRow < 0) { + iRow -= 1; + } + if (zCol < 0) { + iCol -= 1; + } +} + +//_________________________________________________________________________________________________ +inline bool Segmentation::localToDetector(float xRow, float zCol, int& iRow, int& iCol, const int subDetectorID) +{ + // convert to row/col + if (subDetectorID != 0 && subDetectorID != 1) { + iRow = iCol = -1; + return false; + } + const ChipSpecifics& specsConfig = (subDetectorID == 0) ? mITofSpecsConfig : mOTofSpecsConfig; + xRow = 0.5 * (specsConfig.ActiveMatrixSizeRows() - specsConfig.PassiveEdgeTop + specsConfig.PassiveEdgeReadOut) - xRow; // coordinate wrt top edge of Active matrix + zCol += 0.5 * specsConfig.ActiveMatrixSizeCols(); // coordinate wrt left edge of Active matrix + if (xRow < 0 || xRow >= specsConfig.ActiveMatrixSizeRows() || zCol < 0 || zCol >= specsConfig.ActiveMatrixSizeCols()) { + iRow = iCol = -1; + return false; + } + iRow = int(xRow / specsConfig.PitchRow); + iCol = int(zCol / specsConfig.PitchCol); + return true; +} + +} // namespace iotof +} // namespace o2 + +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx index 9b097a0243597..ab9a68bd401ec 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx @@ -58,7 +58,7 @@ void Detector::ConstructGeometry() } void Detector::configLayers(bool itof, bool otof, bool ftof, bool btof, std::string pattern, bool itofSegmented, bool otofSegmented, - const float x2x0) + const float x2x0, const float sensorThickness) { const std::pair dInnerTof = {21.f, 129.f}; // Radius and length @@ -96,13 +96,13 @@ void Detector::configLayers(bool itof, bool otof, bool ftof, bool btof, std::str } if (itof) { // iTOF const std::string name = GeometryTGeo::getITOFLayerPattern(); - const int nStaves = itofSegmented ? 24 : 0; // number of staves in segmented case - const double staveWidth = itofSegmented ? 5.42 : 0.0; // cm - const double staveTiltAngle = itofSegmented ? 10.0 : 0.0; // degrees - const int modulesPerStave = itofSegmented ? 10 : 0; // number of modules per stave in segmented case + const int nStaves = itofSegmented ? 24 : 0; // number of staves in segmented case + const double staveWidth = itofSegmented ? 5.42 : 0.0; // cm + const double staveTiltAngle = itofSegmented ? 3.0 : 0.0; // degrees + const int modulesPerStave = itofSegmented ? 10 : 0; // number of modules per stave in segmented case mITOFLayer = ITOFLayer(name, - dInnerTof.first, 0.f, dInnerTof.second, 0.f, x2x0, ITOFLayer::kBarrelSegmented, - nStaves, staveWidth, staveTiltAngle, modulesPerStave); + dInnerTof.first, 0.f, dInnerTof.second, 0.f, x2x0, itofSegmented ? ITOFLayer::kBarrelSegmented : ITOFLayer::kBarrel, + nStaves, staveWidth, staveTiltAngle, modulesPerStave, itofSegmented ? sensorThickness : 0.0f); } if (otof) { // oTOF const std::string name = GeometryTGeo::getOTOFLayerPattern(); @@ -111,8 +111,8 @@ void Detector::configLayers(bool itof, bool otof, bool ftof, bool btof, std::str const double staveTiltAngle = otofSegmented ? 5.0 : 0.0; // degrees const int modulesPerStave = otofSegmented ? 54 : 0; // number of modules per stave in segmented case mOTOFLayer = OTOFLayer(name, - dOuterTof.first, 0.f, dOuterTof.second, 0.f, x2x0, OTOFLayer::kBarrelSegmented, - nStaves, staveWidth, staveTiltAngle, modulesPerStave); + dOuterTof.first, 0.f, dOuterTof.second, 0.f, x2x0, otofSegmented ? OTOFLayer::kBarrelSegmented : OTOFLayer::kBarrel, + nStaves, staveWidth, staveTiltAngle, modulesPerStave, otofSegmented ? sensorThickness : 0.0f); } if (ftof) { const std::string name = GeometryTGeo::getFTOFLayerPattern(); @@ -200,28 +200,47 @@ void Detector::defineSensitiveVolumes() TGeoManager* geoManager = gGeoManager; TGeoVolume* v; - // The names of the IOTOF sensitive volumes have the format: IOTOFLayer(0...mLayers.size()-1) auto& iotofPars = IOTOFBaseParam::Instance(); - if (iotofPars.enableInnerTOF) { + const bool itof = iotofPars.enableInnerTOF; + const bool otof = iotofPars.enableOuterTOF; + bool ftof = iotofPars.enableForwardTOF; + bool btof = iotofPars.enableBackwardTOF; + const std::string pattern = iotofPars.detectorPattern; + if (pattern == "") { + LOG(info) << "Default pattern"; + } else if (pattern == "v3b") { + ftof = false; + btof = false; + } else if (pattern == "v3b1a") { + } else if (pattern == "v3b1b") { + } else if (pattern == "v3b2a") { + } else if (pattern == "v3b2b") { + } else if (pattern == "v3b3") { + } else { + LOG(fatal) << "IOTOF layer pattern " << pattern << " not recognized, exiting"; + } + + // The names of the IOTOF sensitive volumes have the format: IOTOFLayer(0...mLayers.size()-1) + if (itof) { for (const std::string& itofSensor : ITOFLayer::mRegister) { v = geoManager->GetVolume(itofSensor.c_str()); LOGP(info, "Adding IOTOF Sensitive Volume {}", v->GetName()); AddSensitiveVolume(v); } } - if (iotofPars.enableOuterTOF) { + if (otof) { for (const std::string& otofSensor : OTOFLayer::mRegister) { v = geoManager->GetVolume(otofSensor.c_str()); LOGP(info, "Adding IOTOF Sensitive Volume {}", v->GetName()); AddSensitiveVolume(v); } } - if (iotofPars.enableForwardTOF) { + if (ftof) { v = geoManager->GetVolume(GeometryTGeo::getFTOFSensorPattern()); LOGP(info, "Adding IOTOF Sensitive Volume {}", v->GetName()); AddSensitiveVolume(v); } - if (iotofPars.enableBackwardTOF) { + if (btof) { v = geoManager->GetVolume(GeometryTGeo::getBTOFSensorPattern()); LOGP(info, "Adding IOTOF Sensitive Volume {}", v->GetName()); AddSensitiveVolume(v); @@ -314,13 +333,29 @@ bool Detector::ProcessHits(FairVolume* vol) TLorentzVector positionStop; fMC->TrackPosition(positionStop); // Retrieve the indices with the volume path - int stave(0), halfstave(0), chipinmodule(0), module; + int stave(0), chipinmodule(0), module(0); fMC->CurrentVolOffID(1, chipinmodule); fMC->CurrentVolOffID(2, module); - fMC->CurrentVolOffID(3, halfstave); - fMC->CurrentVolOffID(4, stave); + fMC->CurrentVolOffID(3, stave); + + int sensorID = lay; + auto& iotofPars = IOTOFBaseParam::Instance(); + + int layN = -1; + if (strstr(vol->GetName(), GeometryTGeo::getITOFSensorPattern()) != nullptr) { + layN = 0; + } else if (strstr(vol->GetName(), GeometryTGeo::getOTOFSensorPattern())) { + layN = 1; + } + if (iotofPars.segmentedInnerTOF && iotofPars.segmentedOuterTOF) { + if (layN > -1) { + sensorID = mGeometryTGeo->getIOTOFChipIndex(layN, stave, module, chipinmodule); + } else { + sensorID += (mGeometryTGeo->getSize() - 1); // temporary as f/b tof is not yet segmented + } + } - o2::itsmft::Hit* p = addHit(stack->GetCurrentTrackNumber(), lay, mTrackData.mPositionStart.Vect(), positionStop.Vect(), + o2::itsmft::Hit* p = addHit(stack->GetCurrentTrackNumber(), sensorID, mTrackData.mPositionStart.Vect(), positionStop.Vect(), mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(), mTrackData.mEnergyLoss, mTrackData.mTrkStatusStart, status); diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx new file mode 100644 index 0000000000000..8e5e74dd1f0ca --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx @@ -0,0 +1,180 @@ +// 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 Digitizer.cxx +/// \brief Implementation of the ALICE3 TOF digitizer +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-03-17 +/// + +#include "IOTOFSimulation/Digitizer.h" +#include "DetectorsRaw/HBFUtils.h" + +#include +#include +#include +#include +#include +#include + +namespace o2::iotof +{ + +o2::iotof::Segmentation* Digitizer::sSegmentation = nullptr; + +//_______________________________________________________________________ +void Digitizer::init() +{ + LOG(info) << "Initializing IOTOF digitizer"; + LOG(info) << " Time resolution: " << mTimeResolution * 1e3 << " ps"; + LOG(info) << " Charge threshold: " << mChargeThreshold << " electrons"; + LOG(info) << " Detection efficiency: " << mEfficiency * 100 << " %"; + LOG(info) << " Continuous mode: " << (mContinuous ? "ON" : "OFF"); + sSegmentation = o2::iotof::Segmentation::Instance(); +} + +//_______________________________________________________________________ +void Digitizer::process(const std::vector* hits, int evID, int srcID) +{ + // Digitize hits from a single event + LOG(debug) << "Digitizing IOTOF hits: " << hits->size() << " hits from event " << evID << " source " << srcID; + + if (!hits || hits->empty()) { + return; + } + + // Sort hits by detector ID for better cache locality + std::vector hitIdx(hits->size()); + std::iota(hitIdx.begin(), hitIdx.end(), 0); + std::sort(hitIdx.begin(), hitIdx.end(), + [hits](int lhs, int rhs) { + return (*hits)[lhs].GetDetectorID() < (*hits)[rhs].GetDetectorID(); + }); + + // Process each hit + for (int i : hitIdx) { + processHit((*hits)[i], evID, srcID); + } + + // In triggered mode, flush output after each event + if (!mContinuous) { + fillOutputContainer(); + } +} + +//_______________________________________________________________________ +void Digitizer::processHit(const o2::itsmft::Hit& hit, int evID, int srcID) +{ + // Process a single hit and create a digit if it passes all cuts + + // Apply efficiency cut + if (!isEfficient()) { + LOG(debug) << "Hit rejected by efficiency cut"; + return; + } + + // Get detector element ID + int detID = hit.GetDetectorID(); + + // Convert energy loss to charge (number of electrons) + float energyLoss = hit.GetEnergyLoss(); // in GeV + int charge = energyToCharge(energyLoss); + + // Apply charge threshold + if (charge < mChargeThreshold) { + LOG(debug) << "Hit rejected by charge threshold: " << charge << " < " << mChargeThreshold; + return; + } + + // Get hit time and apply smearing + // Hit time is in seconds, convert to ns and add event time + double hitTime = hit.GetTime() * sec2ns; // convert to ns + double eventTimeNS = mEventTime.getTimeNS(); // event time since orbit 0 + double absoluteTime = hitTime + eventTimeNS; // absolute time + double smearedTime = smearTime(absoluteTime); // apply detector resolution + + // For now, use simple row/col mapping from detector ID + // TODO: Implement proper segmentation when geometry is finalized + uint16_t chipIndex = static_cast(detID); + + if (detID > mGeometry->getSize() || mGeometry->getSize() < 1) { + LOG(debug) << "Invalid detector ID: " << detID; + return; // invalid detector ID + } + const auto& matrix = mGeometry->getMatrixL2G(hit.GetDetectorID()); + + math_utils::Vector3D xyzPositionStart(matrix ^ (hit.GetPosStart())); // start position in sensor frame + // math_utils::Vector3D xyzPositionEnd(matrix ^ (hit.GetPos())); // end position in sensor frame + + int row = 0; // Will be determined from start hit position + int col = 0; // Will be determined from start hit position + + if (!sSegmentation->localToDetector(xyzPositionStart.X(), xyzPositionStart.Z(), row, col, mGeometry->getIOTOFLayer(detID))) { + LOG(debug) << "Hit position out of bounds for detector ID " << detID; + return; // hit is outside the active area + } + + // Create the digit with time information + int digID = mDigits->size(); + mDigits->emplace_back(chipIndex, static_cast(row), static_cast(col), charge, smearedTime); + + LOG(debug) << "Created digit #" << digID << " chip=" << chipIndex + << " charge=" << charge << " time=" << smearedTime << " ns"; + + // Add MC truth label + if (mMCLabels) { + o2::MCCompLabel lbl(hit.GetTrackID(), evID, srcID, false); + mMCLabels->addElement(digID, lbl); + } +} + +//_______________________________________________________________________ +double Digitizer::smearTime(double time) const +{ + // Apply Gaussian smearing to simulate detector time resolution + if (mTimeResolution > 0) { + return time + gRandom->Gaus(0, mTimeResolution); + } + return time; +} + +//_______________________________________________________________________ +int Digitizer::energyToCharge(float energyLoss) const +{ + // Convert energy loss (GeV) to number of electrons + // Typical value: 3.6 eV per electron-hole pair in silicon + // energyLoss is in GeV, mEnergyToCharge is GeV per electron + return static_cast(energyLoss / mEnergyToCharge); +} + +//_______________________________________________________________________ +bool Digitizer::isEfficient() const +{ + // Apply efficiency cut using random number + return gRandom->Uniform() < mEfficiency; +} + +//_______________________________________________________________________ +void Digitizer::fillOutputContainer() +{ + // Create ROF record for the current event + if (mROFRecords && mDigits && !mDigits->empty()) { + o2::itsmft::ROFRecord rof; + rof.setFirstEntry(0); + rof.setNEntries(mDigits->size()); + rof.setBCData(mEventTime); + mROFRecords->push_back(rof); + LOG(debug) << "Created ROF record with " << mDigits->size() << " digits"; + } +} + +} // namespace o2::iotof diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx index 0d0983958c46f..f2e42e1bce172 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx @@ -28,19 +28,20 @@ namespace o2 namespace iotof { Layer::Layer(std::string layerName, float rInn, float rOut, float zLength, float zOffset, float layerX2X0, - int layout, int nStaves, float staveSize, double staveTiltAngle, int modulesPerStave) + int layout, int nStaves, float staveSize, double staveTiltAngle, int modulesPerStave, float sensorThickness) : mLayerName(layerName), mInnerRadius(rInn), mOuterRadius(rOut), mZLength(zLength), mZOffset(zOffset), + mSensorThickness(sensorThickness), mX2X0(layerX2X0), mLayout(layout), mStaves(nStaves, staveSize), mModulesPerStave(modulesPerStave), mTiltAngle(staveTiltAngle) { - const float Si_X0 = 9.5f; + const float Si_X0 = 9.5f; // cm, radiation length of silicon mChipThickness = mX2X0 * Si_X0; std::string name = ""; switch (layout) { @@ -76,6 +77,12 @@ Layer::Layer(std::string layerName, float rInn, float rOut, float zLength, float if ((mTiltAngle < 0.0 || mTiltAngle > 90.0) && (layout == kBarrelSegmented || layout == kDiskSegmented)) { LOG(fatal) << "Invalid configuration: tilt angle " << mTiltAngle << " is too large, it must be between 0 and 90 degrees"; } + if (mSensorThickness < 0.0f || mSensorThickness > mChipThickness) { + LOG(fatal) << "Invalid configuration: sensor thickness " << mSensorThickness << " cm is out of range (0, " << mChipThickness << ") cm"; + } + if (sensorThickness > 0.0f && (layout == kBarrel || layout == kDisk)) { + LOG(fatal) << "Invalid configuration: sensor thickness " << mSensorThickness << " cm is set for non-segmented layout, it should be 0"; + } LOGP(info, "TOF: Creating {} layer: rInner: {} (cm) rOuter: {} (cm) zLength: {} (cm) zOffset: {} x2X0: {}", name.c_str(), mInnerRadius, mOuterRadius, mZLength, mZOffset, mX2X0); } @@ -155,11 +162,13 @@ void ITOFLayer::createLayer(TGeoVolume* motherVolume) case kBarrelSegmented: { // First we create the volume for the whole layer, which will be used as mother volume for the segments const double avgRadius = 0.5 * (mInnerRadius + mOuterRadius); - const double staveSizeX = mStaves.second; // cm - const double staveSizeY = mOuterRadius - mInnerRadius; // cm - const double staveSizeZ = mZLength; // cm - const double deltaForTilt = 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); // we increase the size of the layer to account for the tilt of the staves - TGeoTube* layer = new TGeoTube(mInnerRadius - deltaForTilt, mOuterRadius + deltaForTilt, mZLength / 2); + const double staveSizeX = mStaves.second; // cm + const double staveSizeY = mOuterRadius - mInnerRadius; // cm + const double staveSizeZ = mZLength; // cm + const double deltaForTilt = 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); // we increase the size of the layer to account for the tilt of the staves + const double radiusMax = std::sqrt(avgRadius * avgRadius + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY + avgRadius * 2. * deltaForTilt); // we increase the outer radius to account for the tilt of the staves + const double radiusMin = std::sqrt(avgRadius * avgRadius + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY - avgRadius * 2. * deltaForTilt); // we decrease the inner radius to account for the tilt of the staves + TGeoTube* layer = new TGeoTube(radiusMin - 0.05, radiusMax + 0.05, mZLength / 2); // cm, small margins to ensure staves are fully encapsulated in the layer volume TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); setLayerStyle(layerVol); @@ -188,10 +197,10 @@ void ITOFLayer::createLayer(TGeoVolume* motherVolume) setChipStyle(chipVol); // Finally we create the volume of the sensor, which is the same for all chips - const int sensorsPerChipX = 2; // we assume that each chip is divided in 2 sensors along the x direction - const int sensorsPerChipZ = 2; // we assume that each chip is divided in 2 sensors along the z direction + const int sensorsPerChipX = 1; // we assume that each chip is divided in 2 sensors along the x direction + const int sensorsPerChipZ = 1; // we assume that each chip is divided in 2 sensors along the z direction const double sensorSizeX = chipSizeX / sensorsPerChipX; // cm - const double sensorSizeY = chipSizeY; // cm + const double sensorSizeY = mSensorThickness; // cm const double sensorSizeZ = chipSizeZ / sensorsPerChipZ; // cm TGeoBBox* sensor = new TGeoBBox(sensorSizeX * 0.5, sensorSizeY * 0.5, sensorSizeZ * 0.5); TGeoVolume* sensVol = new TGeoVolume(sensName, sensor, medSi); @@ -287,11 +296,25 @@ void OTOFLayer::createLayer(TGeoVolume* motherVolume) case kBarrelSegmented: { // First we create the volume for the whole layer, which will be used as mother volume for the segments const double avgRadius = 0.5 * (mInnerRadius + mOuterRadius); - const double staveSizeX = mStaves.second; // cm - const double staveSizeY = mOuterRadius - mInnerRadius; // cm - const double staveSizeZ = mZLength; // cm - const double deltaForTilt = 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); // we increase the size of the layer to account for the tilt of the staves - TGeoTube* layer = new TGeoTube(mInnerRadius - deltaForTilt, mOuterRadius + deltaForTilt, mZLength / 2); + const double staveSizeX = mStaves.second; // cm, tangential stave size + const double staveSizeY = mOuterRadius - mInnerRadius; // cm, radial stave size + const double staveSizeZ = mZLength; // cm + + // Build the mother layer tube from the exact inscribed/outscribed radii of a tilted stave rectangle. + const double alpha = mTiltAngle * TMath::DegToRad(); + const double u0 = -avgRadius * std::cos(alpha); + const double v0 = avgRadius * std::sin(alpha); + const double uClamped = std::max(-0.5 * staveSizeY, std::min(0.5 * staveSizeY, u0)); + const double vClamped = std::max(-0.5 * staveSizeX, std::min(0.5 * staveSizeX, v0)); + const double radiusMin = std::hypot(uClamped - u0, vClamped - v0); + + const double uCorners[4] = {-0.5 * staveSizeY, 0.5 * staveSizeY, 0.5 * staveSizeY, -0.5 * staveSizeY}; + const double vCorners[4] = {-0.5 * staveSizeX, -0.5 * staveSizeX, 0.5 * staveSizeX, 0.5 * staveSizeX}; + double radiusMax = 0.0; + for (int i = 0; i < 4; ++i) { + radiusMax = std::max(radiusMax, std::hypot(uCorners[i] - u0, vCorners[i] - v0)); + } + TGeoTube* layer = new TGeoTube(radiusMin, radiusMax, mZLength / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); setLayerStyle(layerVol); @@ -301,10 +324,21 @@ void OTOFLayer::createLayer(TGeoVolume* motherVolume) setStaveStyle(staveVol); // Now we create the volume for a single module (sensor + chip) - const int modulesPerStaveX = 1; // we assume that each stave is divided in 2 modules along the x direction - const double moduleSizeX = staveSizeX / modulesPerStaveX; // cm - const double moduleSizeY = staveSizeY; // cm - const double moduleSizeZ = staveSizeZ / mModulesPerStave; // cm + // oTOF V2 is a 2xN matrix of modules per stave with overlap along z. + const int modulesPerStaveX = 2; + if (mModulesPerStave % modulesPerStaveX != 0) { + LOG(fatal) << "Invalid oTOF module layout: total modules per stave " << mModulesPerStave + << " is not divisible by modulesPerStaveX=" << modulesPerStaveX; + } + const int modulesPerStaveZ = mModulesPerStave / modulesPerStaveX; + const double moduleOverlapZ = 0.7; // cm, 7 mm longitudinal overlap from oTOF V2 specs + const double moduleSizeX = staveSizeX / modulesPerStaveX; + const double moduleSizeY = staveSizeY; + const double moduleSizeZ = (staveSizeZ + (modulesPerStaveZ - 1) * moduleOverlapZ) / modulesPerStaveZ; + const double modulePitchZ = moduleSizeZ - moduleOverlapZ; + if (modulePitchZ <= 0.0) { + LOG(fatal) << "Invalid oTOF module overlap " << moduleOverlapZ << " cm for module size " << moduleSizeZ << " cm"; + } TGeoBBox* module = new TGeoBBox(moduleSizeX * 0.5, moduleSizeY * 0.5, moduleSizeZ * 0.5); TGeoVolume* moduleVol = new TGeoVolume(moduleName, module, medAir); setModuleStyle(moduleVol); @@ -320,10 +354,10 @@ void OTOFLayer::createLayer(TGeoVolume* motherVolume) setChipStyle(chipVol); // Finally we create the volume of the sensor, which is the same for all chips - const int sensorsPerChipX = 2; // we assume that each chip is divided in 2 sensors along the x direction - const int sensorsPerChipZ = 2; // we assume that each chip is divided in 2 sensors along the z direction + const int sensorsPerChipX = 1; // we assume that each chip is divided in 2 sensors along the x direction + const int sensorsPerChipZ = 1; // we assume that each chip is divided in 2 sensors along the z direction const double sensorSizeX = chipSizeX / sensorsPerChipX; // cm - const double sensorSizeY = chipSizeY; // cm + const double sensorSizeY = mSensorThickness; // cm const double sensorSizeZ = chipSizeZ / sensorsPerChipZ; // cm TGeoBBox* sensor = new TGeoBBox(sensorSizeX * 0.5, sensorSizeY * 0.5, sensorSizeZ * 0.5); TGeoVolume* sensVol = new TGeoVolume(sensName, sensor, medSi); @@ -352,10 +386,12 @@ void OTOFLayer::createLayer(TGeoVolume* motherVolume) // Now we build a stave from modules for (int i = 0; i < modulesPerStaveX; ++i) { - for (int j = 0; j < mModulesPerStave; ++j) { - LOGP(info, "oTOF: Creating module {}/{} for stave {}/{}", i + 1, modulesPerStaveX, j + 1, mModulesPerStave); - auto* translation = new TGeoTranslation((i + 0.5) * moduleSizeX - 0.5 * staveSizeX, 0, (j + 0.5) * moduleSizeZ - 0.5 * staveSizeZ); - staveVol->AddNode(moduleVol, 1 + i * mModulesPerStave + j, translation); + for (int j = 0; j < modulesPerStaveZ; ++j) { + LOGP(info, "oTOF: Creating module {}/{} for stave {}/{}", i + 1, modulesPerStaveX, j + 1, modulesPerStaveZ); + const double tx = (i + 0.5) * moduleSizeX - 0.5 * staveSizeX; + const double tz = -0.5 * staveSizeZ + 0.5 * moduleSizeZ + j * modulePitchZ; + auto* translation = new TGeoTranslation(tx, 0, tz); + staveVol->AddNode(moduleVol, 1 + i * modulesPerStaveZ + j, translation); } } @@ -445,4 +481,4 @@ void BTOFLayer::createLayer(TGeoVolume* motherVolume) } } // namespace iotof -} // namespace o2 \ No newline at end of file +} // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx new file mode 100644 index 0000000000000..bbfb60234210d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx @@ -0,0 +1,90 @@ +// 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 Segmentation.cxx +/// \brief Implementation of the Segmentation class + +#include "IOTOFSimulation/Segmentation.h" +#include "IOTOFBase/IOTOFBaseParam.h" +#include + +namespace o2 +{ + +namespace iotof +{ + +std::unique_ptr Segmentation::sInstance; + +Segmentation* Segmentation::Instance() +{ + if (!sInstance) { + sInstance = std::unique_ptr(new Segmentation()); + } + return sInstance.get(); +} + +Segmentation::Segmentation() +{ + if (sInstance) { + printf("Invalid use of public constructor: o2::iotof::Segmentation instance exists\n"); + } else { + auto& iotofPars = IOTOFBaseParam::Instance(); + const ChipSpecifics& mITofChipPars = iotofPars.iTofChipSpecifics; + const ChipSpecifics& mOTofChipPars = iotofPars.oTofChipSpecifics; + + configChip(mITofChipPars, 0 /* subDetectorID for iTOF */); + configChip(mOTofChipPars, 1 /* subDetectorID for oTOF */); + } +} + +void Segmentation::configChip(const int nCols, const int nRows, const float pitchCol, const float pitchRow, const float passiveEdgeReadOut, + const float passiveEdgeTop, const float passiveEdgeSide, const float sensorLayerThicknessEff, const float sensorLayerThickness, const int subDetectorID) +{ + if (subDetectorID == 0) { + mITofSpecsConfig = ChipSpecifics(nCols, nRows, pitchCol, pitchRow, passiveEdgeReadOut, passiveEdgeTop, passiveEdgeSide, sensorLayerThicknessEff, sensorLayerThickness); + } else if (subDetectorID == 1) { + mOTofSpecsConfig = ChipSpecifics(nCols, nRows, pitchCol, pitchRow, passiveEdgeReadOut, passiveEdgeTop, passiveEdgeSide, sensorLayerThicknessEff, sensorLayerThickness); + } else { + printf("Invalid subDetectorID %d. Must be 0 (iTOF) or 1 (oTOF). No configuration applied.\n", subDetectorID); + } +} + +void Segmentation::configChip(const ChipSpecifics& specsConfig, const int subDetectorID) +{ + if (subDetectorID == 0) { + mITofSpecsConfig = specsConfig; + } else if (subDetectorID == 1) { + mOTofSpecsConfig = specsConfig; + } else { + printf("Invalid subDetectorID %d. Must be 0 (iTOF) or 1 (oTOF). No configuration applied.\n", subDetectorID); + } +} + +void Segmentation::print() +{ + // iTOF specs + printf("iTOF specs:\n"); + printf("Pixel size: %.2f (along %d rows) %.2f (along %d columns) microns\n", mITofSpecsConfig.PitchRow * 1e4, mITofSpecsConfig.NRows, mITofSpecsConfig.PitchCol * 1e4, mITofSpecsConfig.NCols); + printf("Passive edges: bottom: %.2f, top: %.2f, left/right: %.2f microns\n", mITofSpecsConfig.PassiveEdgeReadOut * 1e4, mITofSpecsConfig.PassiveEdgeTop * 1e4, mITofSpecsConfig.PassiveEdgeSide * 1e4); + printf("Active/Total size: %.6f/%.6f (rows) %.6f/%.6f (cols) cm\n", mITofSpecsConfig.ActiveMatrixSizeRows(), mITofSpecsConfig.SensorSizeRows(), mITofSpecsConfig.ActiveMatrixSizeCols(), mITofSpecsConfig.SensorSizeCols()); + + // oTOF specs + printf("oTOF specs:\n"); + printf("Pixel size: %.2f (along %d rows) %.2f (along %d columns) microns\n", mOTofSpecsConfig.PitchRow * 1e4, mOTofSpecsConfig.NRows, mOTofSpecsConfig.PitchCol * 1e4, mOTofSpecsConfig.NCols); + printf("Passive edges: bottom: %.2f, top: %.2f, left/right: %.2f microns\n", mOTofSpecsConfig.PassiveEdgeReadOut * 1e4, mOTofSpecsConfig.PassiveEdgeTop * 1e4, mOTofSpecsConfig.PassiveEdgeSide * 1e4); + printf("Active/Total size: %.6f/%.6f (rows) %.6f/%.6f (cols) cm\n", mOTofSpecsConfig.ActiveMatrixSizeRows(), mOTofSpecsConfig.SensorSizeRows(), mOTofSpecsConfig.ActiveMatrixSizeCols(), mOTofSpecsConfig.SensorSizeCols()); +} + +} // namespace iotof +} // namespace o2 + +ClassImp(o2::iotof::Segmentation); diff --git a/Detectors/Upgrades/ALICE3/TRK/README.md b/Detectors/Upgrades/ALICE3/TRK/README.md index a061a06be66f3..efe07ab092eb2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/README.md +++ b/Detectors/Upgrades/ALICE3/TRK/README.md @@ -15,7 +15,7 @@ Configurables for various sub-detectors are presented in the following Table: | Subsystem | Available options | Comments | | ------------------ | ------------------------------------------------------- | ---------------------------------------------------------------- | | `TRKBase.layoutVD` | `kIRIS4` (default), `kIRISFullCyl`, `kIRIS5`, `kIRIS4a` | [link to definitions](./base/include/TRKBase/TRKBaseParam.h) | -| `TRKBase.layoutMLOT` | `kCylindrical`, `kSegmented` (default) | `kSegmented` produced a Turbo layout for ML and a Staggered layout for OT | +| `TRKBase.layoutMLOT` | `kCylindrical`, `kSegmented` (default) | `kSegmented` produces a Turbo layout for ML and a Staggered layout for OT | | `TRKBase.layoutSRV` | `kPeacockv1` (default), `kLOISymm` | `kLOISymm` produces radially symmetric service volumes, as used in the LoI | For example, a geometry with fully cylindrical tracker barrel (for all layers in VD, ML and OT) can be obtained by @@ -24,5 +24,70 @@ o2-sim-serial-run5 -n 1 -g pythia8hi -m A3IP TRK FT3 TF3 \ --configKeyValues "TRKBase.layoutVD=kIRISFullCyl;TRKBase.layoutMLOT=kCylindrical" ``` +## Custom Geometry Configuration + +The geometry of the ML and OT layers can be overridden by providing a custom plain-text configuration file via `TRKBase.configFile=filename.txt`. The parser interprets the file differently depending on the active `TRKBase.layoutMLOT` setting (`kCylindrical` or `kSegmented`). + +### General Syntax Rules +* **Separators:** All columns **must** be separated by a single TAB (`\t`). Using spaces will result in a parsing error. +* **Comments:** Any line starting with a forward slash (`/`) is treated as a comment and ignored. +* **Layer Count:** The parser reads valid lines sequentially. The first valid line corresponds to Layer 0, the second to Layer 1, and so on. +* **Material Budget Mode:** All layer definitions accept an optional `matBudgetMode` parameter at the end of the line (e.g., `0` = Thickness, `1` = X2X0). If omitted, it defaults to `Thickness`. + +### 1. Cylindrical Layout (`kCylindrical`) + +When `TRKBase.layoutMLOT=kCylindrical` is used, each layer requires a minimum of 3 parameters to define the `TRKCylindricalLayer`. + +* **Format:** `rInn` \t `length` \t `thick` \t `[optional_mode]` +* *(Note: `rInn`, `length`, and `thick` map directly to the constructor arguments for the cylindrical layer, typically corresponding to Radius, Length, and Thickness).* + +**Example for `kCylindrical`:** +```text +/ Configuration for kCylindrical layout - ALICE3 TRK +/ rInn length thick [optional_mode] +7.0 127.985 0.1 +9.0 127.985 0.1 +12.0 127.985 0.1 +20.0 127.985 0.1 +30.0 127.985 0.1 +45.0 255.9 0.1 +60.0 255.9 0.1 +80.0 255.9 0.1 +``` + +### 2. Segmented Layout (`kSegmented`) + +When `TRKBase.layoutMLOT=kSegmented` is used, each layer requires a minimum of 5 base parameters to define the geometry. The parser distinguishes between Middle Layers (ML) and Outer Layers (OT) based on the sequential layer index. + +* *(Note: The 5 base parameters map directly to: Inner Radius (`rInn`), Thickness (`thick`), Tilt Angle (`tiltAngle`), Number of Staves (`nStaves`), and Number of Modules per stave (`nMods`)).* + +**Middle Layers (ML) - Indices 0 to 4** +The first 5 valid lines are parsed as `TRKMLLayer` objects. These layers **require** a 6th parameter for the staggering offset (`stagOffset`). +* **Format:** `rInn` \t `thick` \t `tiltAngle` \t `nStaves` \t `nMods` \t `stagOffset` \t `[optional_mode]` + +**Outer Layers (OT) - Indices 5 and above** +From the 6th valid line onwards, lines are parsed as `TRKOTLayer` objects. These layers do **not** have a staggering offset. The optional mode parameter shifts to the 6th column. +* **Format:** `rInn` \t `thick` \t `tiltAngle` \t `nStaves` \t `nMods` \t `[optional_mode]` + +**Example for `kSegmented`:** + +```text +/ Configuration for kSegmented layout - ALICE3 TRK +/ --- ML LAYERS (Indices 0 to 4) --- +/ rInn thick tilt nStaves nMods stagOffset [optional_mode] +7.0 0.01 11.2 10 11 0.0 1 +9.0 0.01 11.9 14 11 0.0 1 +12.0 0.01 11.4 18 11 0.0 1 +20.0 0.01 0.0 26 11 1.17 1 +30.0 0.01 0.0 38 11 0.89 1 +/ +/ --- OT LAYERS (Indices 5 to 7) --- +/ Outer layers do NOT have stagOffset. +/ rInn thick tilt nStaves nMods [optional_mode] +45.0 0.01 0.0 32 22 1 +60.0 0.01 0.0 42 22 1 +80.0 0.01 0.0 56 22 1 +``` + diff --git a/Detectors/Upgrades/ALICE3/TRK/base/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/base/CMakeLists.txt index 96ebf4ead4b7b..89775e22ed8d0 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/base/CMakeLists.txt @@ -11,11 +11,13 @@ o2_add_library(TRKBase SOURCES src/GeometryTGeo.cxx + src/AlmiraParam.cxx src/TRKBaseParam.cxx src/SegmentationChip.cxx PUBLIC_LINK_LIBRARIES O2::DetectorsBase) o2_target_root_dictionary(TRKBase - HEADERS include/TRKBase/GeometryTGeo.h + HEADERS include/TRKBase/AlmiraParam.h + include/TRKBase/GeometryTGeo.h include/TRKBase/TRKBaseParam.h - include/TRKBase/SegmentationChip.h) \ No newline at end of file + include/TRKBase/SegmentationChip.h) diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h new file mode 100644 index 0000000000000..9929a14c4e39c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h @@ -0,0 +1,67 @@ +// 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. + +#ifndef O2_TRK_ALMIRAPARAM_H +#define O2_TRK_ALMIRAPARAM_H + +#include + +#include "CommonConstants/LHCConstants.h" +#include "CommonUtils/ConfigurableParam.h" +#include "CommonUtils/ConfigurableParamHelper.h" +#include "TRKBase/Specs.h" + +namespace o2 +{ +namespace trk +{ + +struct AlmiraParam : public o2::conf::ConfigurableParamHelper { + static constexpr size_t kNLayers = constants::VD::petal::nLayers + constants::ML::nLayers + constants::OT::nLayers; + static constexpr size_t getNLayers() { return kNLayers; } + + int roFrameLengthInBCPerLayer[kNLayers] = {0}; ///< ROF length in BC per layer + float strobeDelayPerLayer[kNLayers] = {0}; ///< strobe delay in ns per layer + float strobeLengthContPerLayer[kNLayers] = {0}; ///< strobe length in ns per layer + int roFrameBiasInBCPerLayer[kNLayers] = {0}; ///< ROF start bias in BC per layer + int roFrameDelayInBCPerLayer[kNLayers] = {0}; ///< extra ROF delay in BC per layer + + int getROFLengthInBC(int layer) const + { + if (roFrameLengthInBCPerLayer[layer] > 0) { + return roFrameLengthInBCPerLayer[layer]; + } else { + return o2::constants::lhc::LHCMaxBunches / 198; + } + } + float getStrobeDelay(int layer) const { return strobeDelayPerLayer[layer]; } + float getStrobeLengthCont(int layer) const { return strobeLengthContPerLayer[layer]; } + int getROFBiasInBC(int layer) const { return roFrameBiasInBCPerLayer[layer]; } + int getROFDelayInBC(int layer) const { return roFrameDelayInBCPerLayer[layer]; } + + O2ParamDef(AlmiraParam, "TRKAlmiraParam"); +}; + +} // namespace trk + +namespace framework +{ +template +struct is_messageable; + +template <> +struct is_messageable : std::true_type { +}; +} // namespace framework + +} // namespace o2 + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h index 21d86378f59ec..53ad7662cbfcd 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h @@ -43,6 +43,7 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache return sInstance.get(); }; static const char* getTRKVolPattern() { return sVolumeName.c_str(); } + static const char* getTRKServiceVolPattern() { return sServiceVolName.c_str(); } static const char* getTRKLayerPattern() { return sLayerName.c_str(); } static const char* getTRKPetalAssemblyPattern() { return sPetalAssemblyName.c_str(); } static const char* getTRKPetalPattern() { return sPetalName.c_str(); } @@ -88,7 +89,8 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache int getSubDetID(int index) const; int getPetalCase(int index) const; int getDisk(int index) const; - int getLayer(int index) const; + int getLayer(int index) const; ///< local layer index within the sub-detector (0-based per VD/MLOT) + int getLayerTRK(int index) const; ///< global layer index across the full TRK (VD layers 0..nVD-1, MLOT layers nVD..nTotal-1) int getStave(int index) const; int getHalfStave(int index) const; int getModule(int index) const; @@ -106,17 +108,23 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache float getSensorRefAlphaMLOT(int chipId) const { - assert(getSubDetID(chipId) != 0 && "Called MLOT getter with VD chipId"); + if (getSubDetID(chipId) == 0) { + LOG(error) << "getSensorRefAlphaMLOT(): VD layers are not supported yet! chipID = " << chipId + << "please provide chipId for ML/OT! "; + return std::numeric_limits::quiet_NaN(); + } const int local = chipId - getNumberOfActivePartsVD(); - assert(local >= 0 && local < (int)mCacheRefAlphaMLOT.size()); return mCacheRefAlphaMLOT[local]; } float getSensorXMLOT(int chipId) const { - assert(getSubDetID(chipId) != 0 && "Called MLOT getter with VD chipId"); + if (getSubDetID(chipId) == 0) { + LOG(error) << "getSensorXMLOT(): VD layers are not supported yet! chipID = " << chipId + << "please provide chipId for ML/OT! "; + return std::numeric_limits::quiet_NaN(); + } const int local = chipId - getNumberOfActivePartsVD(); - assert(local >= 0 && local < (int)mCacheRefXMLOT.size()); return mCacheRefXMLOT[local]; } @@ -192,6 +200,7 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache static constexpr int MAXLAYERS = 20; ///< max number of active layers static std::string sVolumeName; + static std::string sServiceVolName; static std::string sLayerName; static std::string sPetalAssemblyName; static std::string sPetalName; diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h index c3c7de9dbe910..b484e13f3546e 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h @@ -16,7 +16,9 @@ #define O2_ALICE_TRK_SPECS #include + #include + // This is a temporary version with the specs for the ALICE3 TRK // This files defines the design specifications of the chips for VD, ML, OT. // Each TGeoShape has the following properties @@ -78,9 +80,9 @@ constexpr double thickness{0 * mu}; // thickness of the copper metal stack - for namespace chip { constexpr double width{25 * mm}; // width of the chip -constexpr double length{32 * mm}; // length of the chip -constexpr double pitchX{50 * mu}; // pitch of the row -constexpr double pitchZ{50 * mu}; // pitch of the column +constexpr double length{29 * mm}; // length of the chip +constexpr double pitchX{20 * mu}; // pitch of the row +constexpr double pitchZ{20 * mu}; // pitch of the column constexpr double totalThickness{silicon::thickness + metalstack::thickness}; // total thickness of the chip static constexpr double passiveEdgeReadOut{1.5 * mm}; // width of the readout edge -> dead zone constexpr int nRows{static_cast((width - passiveEdgeReadOut) / pitchX)}; // number of rows in the chip @@ -100,6 +102,7 @@ constexpr int nCols{static_cast(length / chip::pitchZ)}; namespace ML { +constexpr int nLayers{5}; // number of layers in the ML constexpr double width{constants::moduleMLOT::width * 1}; // width of the stave // constexpr double length{constants::moduleMLOT::length * 10}; // length of the stave constexpr double length{124 * cm}; // length of the stave, hardcoded to fit the implemented geometry @@ -117,6 +120,7 @@ constexpr double length{258 * cm}; // len constexpr int nRows{static_cast(width / moduleMLOT::chip::pitchX)}; // number of rows in the halfstave constexpr int nCols{static_cast(length / moduleMLOT::chip::pitchZ)}; // number of columns in the halfstave } // namespace halfstave +constexpr int nLayers{3}; // number of layers in the OT constexpr double width{halfstave::width * 2}; // width of the stave constexpr double length{halfstave::length}; // length of the stave constexpr int nRows{static_cast(width / moduleMLOT::chip::pitchX)}; // number of rows in the stave @@ -138,7 +142,6 @@ constexpr double pitchZ{10.0 * mu}; constexpr double responseYShift{5 * mu}; /// center of the epitaxial layer constexpr double thickness{20 * mu}; } // namespace alice3resp - } // namespace o2::trk::constants #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h index 63e961db44505..65194ad6edfcb 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h @@ -40,7 +40,8 @@ enum eSrvLayout { struct TRKBaseParam : public o2::conf::ConfigurableParamHelper { std::string configFile = ""; float serviceTubeX0 = 0.02f; // X0 Al2O3 - Bool_t irisOpen = false; + bool irisOpen = false; + bool includeLowServices = false; eVDLayout layoutVD = kIRIS4; // VD detector layout design eMLOTLayout layoutMLOT = kSegmented; // ML and OT detector layout design diff --git a/GPU/TPCFastTransformation/MultivariatePolynomial.cxx b/Detectors/Upgrades/ALICE3/TRK/base/src/AlmiraParam.cxx similarity index 80% rename from GPU/TPCFastTransformation/MultivariatePolynomial.cxx rename to Detectors/Upgrades/ALICE3/TRK/base/src/AlmiraParam.cxx index b1ffe616fb65e..572c902fb23f1 100644 --- a/GPU/TPCFastTransformation/MultivariatePolynomial.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/base/src/AlmiraParam.cxx @@ -9,7 +9,6 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file MultivariatePolynomial.cxx -/// \author Matthias Kleiner +#include "TRKBase/AlmiraParam.h" -#include "MultivariatePolynomial.h" +O2ParamImpl(o2::trk::AlmiraParam); diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx index 36d26a6344e6c..ddfc844cc964d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx @@ -12,6 +12,7 @@ #include #include #include "TRKBase/SegmentationChip.h" +#include "TRKBase/Specs.h" #include #include @@ -26,6 +27,7 @@ std::unique_ptr GeometryTGeo::sInstance; // Names std::string GeometryTGeo::sVolumeName = "TRKV"; +std::string GeometryTGeo::sServiceVolName = "TRKService"; std::string GeometryTGeo::sLayerName = "TRKLayer"; std::string GeometryTGeo::sPetalAssemblyName = "PETAL"; std::string GeometryTGeo::sPetalName = "PETALCASE"; @@ -63,7 +65,7 @@ GeometryTGeo::GeometryTGeo(bool build, int loadTrans) : DetMatrixCache(detectors void GeometryTGeo::Build(int loadTrans) { ///// current geometry organization: - ///// total elements = x staves (*2 half staves if staggered geometry) * 8 layers ML+OT + 4 petal cases * (3 layers + 6 disks) + ///// total elements = x staves (*2 half staves if staggered geometry) * ML+OT layers + 4 petal cases * (3 layers + 6 disks) ///// indexing from 0 to 35: VD petals -> layers -> disks ///// indexing from 36 to y: MLOT staves @@ -202,6 +204,15 @@ int GeometryTGeo::getLayer(int index) const return -1; /// -1 if not found } //__________________________________________________________________________ +int GeometryTGeo::getLayerTRK(int index) const +{ + if (getDisk(index) != -1) { + return -1; /// disks do not have a global layer index + } + int subDetID = getSubDetID(index); + return subDetID * o2::trk::constants::VD::petal::nLayers + getLayer(index); // MLOT: offset by number of VD layers +} +//__________________________________________________________________________ int GeometryTGeo::getStave(int index) const { int subDetID = getSubDetID(index); @@ -1120,7 +1131,7 @@ void GeometryTGeo::Print(Option_t*) const std::cout << "Detector ID: " << sInstance.get()->getDetID() << std::endl; LOGF(info, "Summary of GeometryTGeo: %s", getName()); - LOGF(info, "Number of layers ML + OL: %d", mNumberOfLayersMLOT); + LOGF(info, "Number of layers ML + OT: %d", mNumberOfLayersMLOT); LOGF(info, "Number of active parts VD: %d", mNumberOfActivePartsVD); LOGF(info, "Number of layers VD: %d", mNumberOfLayersVD); LOGF(info, "Number of petals VD: %d", mNumberOfPetalsVD); @@ -1132,7 +1143,7 @@ void GeometryTGeo::Print(Option_t*) const LOGF(info, "Number of staves and half staves per layer MLOT: "); for (int i = 0; i < mNumberOfLayersMLOT; i++) { std::string mlot = ""; - mlot = (i < 4) ? "ML" : "OT"; + mlot = (i < constants::ML::nLayers) ? "ML" : "OT"; LOGF(info, "Layer: %d, %s, %d staves, %d half staves per stave", i, mlot.c_str(), mNumberOfStaves[i], mNumberOfHalfStaves[i]); } LOGF(info, "Number of modules per stave (half stave) in each ML(OT) layer: "); diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h b/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h index eee9a23eaf5e7..e36955cdd150d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h @@ -15,10 +15,12 @@ #pragma link off all classes; #pragma link off all functions; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::trk::AlmiraParam> + ; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::trk::TRKBaseParam> + ; +#pragma link C++ class o2::trk::AlmiraParam + ; #pragma link C++ class o2::trk::GeometryTGeo + #pragma link C++ class o2::trk::TRKBaseParam + ; #pragma link C++ class o2::trk::SegmentationChip + ; -#endif \ No newline at end of file +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt index edd9c785d89ce..cdae7c9c379fd 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt @@ -9,7 +9,19 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. -o2_add_test_root_macro(CheckDigits.C + +o2_add_test_root_macro(CheckBandwidth.C + PUBLIC_LINK_LIBRARIES O2::ITSMFTBase + O2::ITSMFTSimulation + O2::TRKBase + O2::TRKSimulation + O2::MathUtils + O2::SimulationDataFormat + O2::DetectorsBase + O2::Steer + LABELS trk COMPILE_ONLY) + +o2_add_test_root_macro(CheckDigitsTRK.C PUBLIC_LINK_LIBRARIES O2::ITSMFTBase O2::ITSMFTSimulation O2::TRKBase @@ -37,3 +49,11 @@ o2_add_test_root_macro(CheckClusters.C O2::TRKBase O2::TRKSimulation LABELS trk COMPILE_ONLY) + +o2_add_test_root_macro(postClusterSizeVsEta.C + PUBLIC_LINK_LIBRARIES O2::DataFormatsTRK + O2::SimulationDataFormat + O2::Framework + O2::TRKBase + O2::TRKSimulation + LABELS trk COMPILE_ONLY) diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C new file mode 100644 index 0000000000000..c071a06516d30 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C @@ -0,0 +1,597 @@ +// 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 CheckBandwidth.C +/// \brief Simple macro to check TRK bandwidth + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TRKBase/GeometryTGeo.h" +#include "DataFormatsITSMFT/Digit.h" +#include "MathUtils/Utils.h" +#include "DetectorsBase/GeometryManager.h" + +#include "DataFormatsITSMFT/ROFRecord.h" +#include "CommonDataFormat/InteractionRecord.h" +#include "SimulationDataFormat/DigitizationContext.h" + +#endif + +namespace +{ +constexpr double DigitBits = 24.; +} // namespace + +void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGeom = "sgn_geometry.root", std::string collContextFile = "collisioncontext.root") +{ + gStyle->SetPalette(55); + gStyle->SetOptStat(0); + + // --- Drawing helpers --- + + auto drawSummary = [](double averageValue, double peakValue, const char* unit) { + TLatex latex; + latex.SetNDC(); + latex.SetTextSize(0.03); + latex.SetTextAlign(13); + latex.DrawLatex(0.04, 0.06, Form("avg: %.3f %s", averageValue, unit)); + latex.DrawLatex(0.04, 0.03, Form("peak: %.3f %s", peakValue, unit)); + }; + + auto drawCollisionSummary = [](double averageValue, double nonEmptyAverageValue, double peakValue) { + TLatex latex; + latex.SetNDC(); + latex.SetTextSize(0.03); + latex.SetTextAlign(13); + latex.DrawLatex(0.04, 0.025, Form("avg: %.3f collisions/ROF", averageValue)); + latex.DrawLatex(0.42, 0.025, Form("peak: %.3f collisions/ROF", peakValue)); + latex.DrawLatex(0.04, 0.06, Form("avg non-empty: %.3f collisions/ROF", nonEmptyAverageValue)); + }; + + auto drawCollisionInfoBox = [](double effectiveIRRateHz, double rofLengthBC) { + TPaveText infoBox(0.55, 0.79, 0.88, 0.9, "NDC"); + infoBox.SetFillColor(0); + infoBox.SetBorderSize(1); + infoBox.SetTextAlign(12); + infoBox.SetTextSize(0.028); + infoBox.AddText(Form("effective IR: %.3f MHz", effectiveIRRateHz * 1.e-6)); + infoBox.AddText(Form("ROF length: %d BC", rofLengthBC)); + infoBox.DrawClone(); + }; + + const TString outputPdf = "trk_bandwidth_report.pdf"; + bool pdfOpened = false; + TCanvas* lastPdfCanvas = nullptr; + auto appendCanvasToPdf = [&](TCanvas* canvas) { + if (!pdfOpened) { + canvas->Print(Form("%s[", outputPdf.Data())); + pdfOpened = true; + } + canvas->Print(outputPdf.Data()); + lastPdfCanvas = canvas; + }; + + using namespace o2::base; + using namespace o2::trk; + + TFile* f = TFile::Open("CheckBandwidth.root", "recreate"); + + // --- Geometry --- + + o2::base::GeometryManager::loadGeometry(inputGeom); + auto* gman = o2::trk::GeometryTGeo::Instance(); + gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + + const int nVDPetals = gman->extractNumberOfPetalsVD(); + const int nVDLayers = gman->extractNumberOfLayersVD(); + const int nMLOTLayers = gman->getNumberOfLayersMLOT(); + const int nTotalLayers = nVDLayers + nMLOTLayers; + const int nChips = gman->getNumberOfChips(); + + // Precompute per-chip geometry — centralises all gman queries. + // globalLayer maps VD layers to [0, nVDLayers) and MLOT layers to [nVDLayers, nTotalLayers). + // disk == -1 identifies barrel chips (the only ones that produce digits in this detector). + struct ChipGeom { + int subDetID = -1, localLayer = -1, globalLayer = -1, disk = -1; + int stave = -1, halfStave = -1, petal = -1; + }; + std::vector chipGeom(nChips); + for (int chipID = 0; chipID < nChips; ++chipID) { + auto& g = chipGeom[chipID]; + g.subDetID = gman->getSubDetID(chipID); + g.localLayer = gman->getLayer(chipID); + g.disk = gman->getDisk(chipID); + g.globalLayer = g.localLayer + g.subDetID * nVDLayers; + g.stave = gman->getStave(chipID); + g.halfStave = std::max(0, gman->getHalfStave(chipID)); + g.petal = (g.subDetID == 0) ? gman->getPetalCase(chipID) : -1; + } + + // Number of barrel chips per global layer (used for per-layer bandwidth normalisation). + std::vector chipsPerLayer(nTotalLayers, 0u); + for (int chipID = 0; chipID < nChips; ++chipID) { + const auto& g = chipGeom[chipID]; + if (g.disk != -1 || g.globalLayer < 0 || g.globalLayer >= nTotalLayers) { + continue; + } + ++chipsPerLayer[g.globalLayer]; + } + + // MLOT sensor index within its half-stave, ordered by Z position. + // Precomputed here so the plotting loop only reads results. + std::vector chipSensorIndex(nChips, -1); + std::vector maxSensorsPerHalfStaveMLOT(nMLOTLayers, 0); + for (int layer = 0; layer < nMLOTLayers; ++layer) { + std::map, std::vector>> chipsPerHalfStave; + for (int chipID = 0; chipID < nChips; ++chipID) { + const auto& g = chipGeom[chipID]; + if (g.subDetID != 1 || g.localLayer != layer || g.disk != -1) { + continue; + } + const auto center = gman->getMatrixL2G(chipID)(o2::math_utils::Point3D(0.f, 0.f, 0.f)); + chipsPerHalfStave[{g.stave, g.halfStave}].push_back({center.Z(), chipID}); + } + for (auto& [key, chips] : chipsPerHalfStave) { + std::sort(chips.begin(), chips.end(), [](const auto& a, const auto& b) { + return std::abs(a.first - b.first) > 1.e-4 ? a.first < b.first : a.second < b.second; + }); + for (size_t i = 0; i < chips.size(); ++i) { + chipSensorIndex[chips[i].second] = (int)i; + } + maxSensorsPerHalfStaveMLOT[layer] = std::max(maxSensorsPerHalfStaveMLOT[layer], (int)chips.size()); + } + } + + // --- Digits --- + + TFile* digFile = TFile::Open(digifile.data()); + TTree* digTree = (TTree*)digFile->Get("o2sim"); + const int nDigitTreeEntries = digTree->GetEntries(); + + std::vector*> digArr(nTotalLayers, nullptr); + std::vector*> rofRecords(nTotalLayers, nullptr); + for (int nDigitsLayer{0}; nDigitsLayer < nTotalLayers; ++nDigitsLayer) { + if (!digTree->GetBranch(Form("TRKDigit_%i", nDigitsLayer))) { + break; + } + digTree->SetBranchAddress(Form("TRKDigit_%i", nDigitsLayer), &digArr[nDigitsLayer]); + digTree->SetBranchAddress(Form("TRKDigitROF_%i", nDigitsLayer), &rofRecords[nDigitsLayer]); + } + + digTree->GetEntry(0); + if (nDigitTreeEntries > 1) { + LOG(warning) << "Digit tree has " << nDigitTreeEntries << " entries, but this macro processes entry 0 only."; + } + + std::vector rofLengthBC(nTotalLayers, 0u); + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + if (rofRecords[iLayer]->size() < 2) { + LOG(fatal) << "ROF record tree for layer " << iLayer << " has " << rofRecords[iLayer]->size() + << " entries, but at least 2 are expected (one per ROF + one empty at the end). Check input files."; + } + rofLengthBC[iLayer] = (*rofRecords[iLayer])[1].getBCData().bc - (*rofRecords[iLayer])[0].getBCData().bc; + } + + // --- Collision context --- + + TFile* ccFile = TFile::Open(collContextFile.data()); + auto* digiContext = (o2::steer::DigitizationContext*)ccFile->Get("DigitizationContext"); + const o2::InteractionRecord firstSampledIR{0, digiContext->getFirstOrbitForSampling()}; + std::vector> collisionsPerROF(nTotalLayers); + for (const auto& record : digiContext->getEventRecords()) { + auto nbc = record.differenceInBC(firstSampledIR); + if (record.getTimeOffsetWrtBC() < 0. && nbc > 0) { + --nbc; + } + if (nbc < 0) { + continue; + } + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + if (rofLengthBC[iLayer] == 0) { + LOG(fatal) << "ROF length in BC for layer " << iLayer << " is zero. Check input files."; + } + const size_t rofID = nbc / rofLengthBC[iLayer]; + if (rofID >= collisionsPerROF[iLayer].size()) { + collisionsPerROF[iLayer].resize(rofID + 1, 0u); + } + ++collisionsPerROF[iLayer][rofID]; + } + } + + const int nROFRec = (int)rofRecords[0]->size(); + if (nROFRec != (int)collisionsPerROF[0].size()) { + LOG(fatal) << "Mismatch between number of ROF records in digit tree (" << nROFRec + << ") and number of ROFs computed from collisioncontext.root (" << collisionsPerROF[0].size() + << "). Check input files."; + } + + // --- Accumulate per-chip digit counts across all ROFs --- + + std::vector digitsPerChip(nChips, 0ull); + std::vector maxDigitsPerROFPerChip(nChips, 0u); + std::vector digitsInCurrentROFPerChip(nChips, 0u); + + for (unsigned int iROF = 0; iROF < (unsigned int)nROFRec; ++iROF) { + std::vector touchedChips; + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + const unsigned int rofStart = (*rofRecords[iLayer])[iROF].getFirstEntry(); + const unsigned int rofEnd = rofStart + (*rofRecords[iLayer])[iROF].getNEntries(); + for (unsigned int iDigit = rofStart; iDigit < rofEnd; ++iDigit) { + const int chipID = (*digArr[iLayer])[iDigit].getChipIndex(); + if (chipGeom[chipID].disk != -1) { + continue; + } + if (digitsInCurrentROFPerChip[chipID] == 0) { + touchedChips.push_back(chipID); + } + ++digitsPerChip[chipID]; + ++digitsInCurrentROFPerChip[chipID]; + } + } + + for (const int chipID : touchedChips) { + maxDigitsPerROFPerChip[chipID] = std::max(maxDigitsPerROFPerChip[chipID], digitsInCurrentROFPerChip[chipID]); + digitsInCurrentROFPerChip[chipID] = 0; + } + } + + // --- Per-layer bandwidth distribution histograms (second scan over digits) --- + + // Per-layer peak digit count (from per-chip maxima) — drives histogram binning. + std::vector maxDigitsPerLayer(nTotalLayers, 0u); + for (int chipID = 0; chipID < nChips; ++chipID) { + const auto& g = chipGeom[chipID]; + if (g.disk != -1 || g.globalLayer < 0 || g.globalLayer >= nTotalLayers) { + continue; + } + maxDigitsPerLayer[g.globalLayer] = std::max(maxDigitsPerLayer[g.globalLayer], maxDigitsPerROFPerChip[chipID]); + } + + std::vector hDigitsDistPerLayer(nTotalLayers, nullptr); + for (int l = 0; l < nTotalLayers; ++l) { + if (chipsPerLayer[l] == 0 || maxDigitsPerLayer[l] == 0) { + continue; + } + const int nBins = std::min((int)maxDigitsPerLayer[l] + 1, 200); + hDigitsDistPerLayer[l] = new TH1D(Form("h_digits_dist_layer%d", l), + Form("Layer %d;Fired pixels / ROF / chip;Probability", l), + nBins, -0.5, (double)maxDigitsPerLayer[l] + 0.5); + } + // digitsInCurrentROFPerChip is all zeros after the first scan — reuse it here. + { + std::vector touchedChips; + for (unsigned int iROF = 0; iROF < (unsigned int)nROFRec; ++iROF) { + touchedChips.clear(); + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + const unsigned int rofStart = (*rofRecords[iLayer])[iROF].getFirstEntry(); + const unsigned int rofEnd = rofStart + (*rofRecords[iLayer])[iROF].getNEntries(); + for (unsigned int iDigit = rofStart; iDigit < rofEnd; ++iDigit) { + const int chipID = (*digArr[iLayer])[iDigit].getChipIndex(); + if (chipGeom[chipID].disk != -1) { + continue; + } + if (digitsInCurrentROFPerChip[chipID] == 0) { + touchedChips.push_back(chipID); + } + ++digitsInCurrentROFPerChip[chipID]; + } + } + for (const int chipID : touchedChips) { + const int l = chipGeom[chipID].globalLayer; + if (hDigitsDistPerLayer[l]) { + hDigitsDistPerLayer[l]->Fill(digitsInCurrentROFPerChip[chipID]); + } + digitsInCurrentROFPerChip[chipID] = 0; + } + } + } + + // --- Per-layer bandwidth statistics, normalised by chips per layer --- + // + // avgDigitsPerROF : mean over chips of (total chip digits / nROFs) + // peakAvgDigitsPerROF : max over chips of (total chip digits / nROFs) + // avgMaxDigitsPerROF : mean over chips of (peak single-ROF digit count) + // peakMaxDigitsPerROF : max over chips of (peak single-ROF digit count) + // avg/peakBandwidthGbps derived from the avg/peak digit quantities above. + + struct LayerStats { + double avgDigitsPerROF = 0.; + double peakAvgDigitsPerROF = 0.; + double avgMaxDigitsPerROF = 0.; + double peakMaxDigitsPerROF = 0.; + double avgBandwidthGbps = 0.; + double peakBandwidthGbps = 0.; + }; + std::vector layerStats(nTotalLayers); + + for (int chipID = 0; chipID < nChips; ++chipID) { + const auto& g = chipGeom[chipID]; + if (g.disk != -1 || g.globalLayer < 0 || g.globalLayer >= nTotalLayers) { + continue; + } + const int l = g.globalLayer; + const double avgDigits = digitsPerChip[chipID] / collisionsPerROF[l].size(); + const double maxDigits = (double)maxDigitsPerROFPerChip[chipID]; + layerStats[l].avgDigitsPerROF += avgDigits; + layerStats[l].avgMaxDigitsPerROF += maxDigits; + layerStats[l].peakAvgDigitsPerROF = std::max(layerStats[l].peakAvgDigitsPerROF, avgDigits); + layerStats[l].peakMaxDigitsPerROF = std::max(layerStats[l].peakMaxDigitsPerROF, maxDigits); + } + for (int l = 0; l < nTotalLayers; ++l) { + if (chipsPerLayer[l] > 0) { + const double norm = 1. / chipsPerLayer[l]; + layerStats[l].avgDigitsPerROF *= norm; + layerStats[l].avgMaxDigitsPerROF *= norm; + } + layerStats[l].avgBandwidthGbps = layerStats[l].avgDigitsPerROF * DigitBits / rofLengthBC[l] / o2::constants::lhc::LHCBunchSpacingNS * 1.e9; + layerStats[l].peakBandwidthGbps = layerStats[l].peakAvgDigitsPerROF * DigitBits / rofLengthBC[l] / o2::constants::lhc::LHCBunchSpacingNS * 1.e9; + } + + // --- Collision plots --- + + if (nROFRec > 0) { + std::vector totalCollisionsPerROF(nTotalLayers, 0.); + std::vector peakCollisionsPerROF(nTotalLayers, 0.); + std::vector nNonEmptyROFs(nTotalLayers, 0); + std::vector hCollisionsPerROFPerLayer(nTotalLayers, nullptr); + + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + hCollisionsPerROFPerLayer[iLayer] = new TH1D(Form("h_collisions_per_rof_layer%d", iLayer), + Form("Layer %d;ROF id;N collisions", iLayer), + nROFRec, -0.5, nROFRec - 0.5); + for (int rofID = 0; rofID < nROFRec; ++rofID) { + const double nColl = collisionsPerROF[iLayer][rofID]; + hCollisionsPerROFPerLayer[iLayer]->SetBinContent(rofID + 1, nColl); + totalCollisionsPerROF[iLayer] += nColl; + peakCollisionsPerROF[iLayer] = std::max(peakCollisionsPerROF[iLayer], nColl); + if (nColl > 0.) { + ++nNonEmptyROFs[iLayer]; + } + } + } + + const int nCols = std::max(1, (int)std::ceil(std::sqrt((double)nTotalLayers))); + const int nRows = (nTotalLayers + nCols - 1) / nCols; + auto* canvCollisionsPerROF = new TCanvas("canvCollisionsPerROF", "Collisions per ROF", 350 * nCols, 300 * nRows); + canvCollisionsPerROF->Divide(nCols, nRows); + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + canvCollisionsPerROF->cd(iLayer + 1); + gPad->SetTopMargin(0.10); + gPad->SetBottomMargin(0.14); + gPad->SetLeftMargin(0.14); + hCollisionsPerROFPerLayer[iLayer]->Draw("hist"); + const double avgCollisionsPerROF = totalCollisionsPerROF[iLayer] / collisionsPerROF[iLayer].size(); + drawCollisionSummary(avgCollisionsPerROF, + nNonEmptyROFs[iLayer] > 0 ? totalCollisionsPerROF[iLayer] / nNonEmptyROFs[iLayer] : 0., + peakCollisionsPerROF[iLayer]); + const double effectiveIRRateHz = avgCollisionsPerROF / rofLengthBC[iLayer] / o2::constants::lhc::LHCBunchSpacingNS * 1.e9; + drawCollisionInfoBox(effectiveIRRateHz, rofLengthBC[iLayer]); + } + appendCanvasToPdf(canvCollisionsPerROF); + } + + // --- VD plots --- + + auto* hVDDigitsPerROF = new TH2F("h_digits_per_rof_vd", + "VD average digits per ROF;petal id;layer id;digits / ROF", + nVDPetals, -0.5, nVDPetals - 0.5, nVDLayers, -0.5, nVDLayers - 0.5); + auto* hVDMaxDigitsPerROF = new TH2F("h_max_digits_per_rof_vd", + "VD max digits in one ROF;petal id;layer id;max digits / ROF", + nVDPetals, -0.5, nVDPetals - 0.5, nVDLayers, -0.5, nVDLayers - 0.5); + auto* hVDBandwidth = new TH2F("h_bandwidth_vd", + "VD bandwidth map;petal id;layer id;bandwidth (Gbit/s)", + nVDPetals, -0.5, nVDPetals - 0.5, nVDLayers, -0.5, nVDLayers - 0.5); + + for (auto* hist : {hVDDigitsPerROF, hVDMaxDigitsPerROF, hVDBandwidth}) { + for (int petalID = 0; petalID < nVDPetals; ++petalID) { + hist->GetXaxis()->SetBinLabel(petalID + 1, Form("%d", petalID)); + } + for (int layerID = 0; layerID < nVDLayers; ++layerID) { + hist->GetYaxis()->SetBinLabel(layerID + 1, Form("%d", layerID)); + } + hist->GetXaxis()->SetNdivisions(0, kFALSE); + hist->GetYaxis()->SetNdivisions(0, kFALSE); + hist->LabelsOption("h", "X"); + hist->LabelsOption("h", "Y"); + } + + double totalVDAvgDigits = 0., peakVDAvgDigits = 0.; + double totalVDMaxDigits = 0., peakVDMaxDigits = 0.; + double totalVDBandwidth = 0., peakVDBandwidth = 0.; + + for (int chipID = 0; chipID < nChips; ++chipID) { + const auto& g = chipGeom[chipID]; + if (g.subDetID != 0 || g.disk != -1 || g.localLayer < 0 || g.localLayer >= nVDLayers) { + continue; + } + if (g.petal < 0 || g.petal >= nVDPetals) { + continue; + } + const double avgDigits = double(digitsPerChip[chipID]) / collisionsPerROF[g.globalLayer].size(); + const double maxDigits = (double)maxDigitsPerROFPerChip[chipID]; + const double bandwidth = avgDigits * DigitBits / rofLengthBC[g.globalLayer] / o2::constants::lhc::LHCBunchSpacingNS * 1.e9; + + hVDDigitsPerROF->SetBinContent(g.petal + 1, g.localLayer + 1, avgDigits); + hVDMaxDigitsPerROF->SetBinContent(g.petal + 1, g.localLayer + 1, maxDigits); + hVDBandwidth->SetBinContent(g.petal + 1, g.localLayer + 1, bandwidth); + + totalVDAvgDigits += avgDigits; + totalVDMaxDigits += maxDigits; + totalVDBandwidth += bandwidth; + peakVDAvgDigits = std::max(peakVDAvgDigits, avgDigits); + peakVDMaxDigits = std::max(peakVDMaxDigits, maxDigits); + peakVDBandwidth = std::max(peakVDBandwidth, bandwidth); + } + + const int nVDBarrelChips = std::accumulate(chipsPerLayer.begin(), chipsPerLayer.begin() + nVDLayers, 0); + const double normVD = nVDBarrelChips > 0 ? 1. / nVDBarrelChips : 0.; + const double avgVDAvgDigits = totalVDAvgDigits * normVD; + const double avgVDMaxDigits = totalVDMaxDigits * normVD; + const double avgVDBandwidth = totalVDBandwidth * normVD; + + auto* canvVDBandwidth = new TCanvas("canvBandwidthVD", "VD bandwidth", 1050, 1050); + canvVDBandwidth->SetTopMargin(0.08); + canvVDBandwidth->SetRightMargin(0.18); + hVDBandwidth->GetZaxis()->SetRangeUser(0., avgVDBandwidth > 0. ? 3. * avgVDBandwidth : 1.); + hVDBandwidth->SetMarkerSize(1.8); + hVDBandwidth->Draw("colz text"); + drawSummary(avgVDBandwidth, peakVDBandwidth, "Gbit/s"); + appendCanvasToPdf(canvVDBandwidth); + + auto* canvVDDigits = new TCanvas("canvDigitsVD", "VD digits per ROF", 1050, 1050); + canvVDDigits->SetTopMargin(0.08); + canvVDDigits->SetRightMargin(0.18); + hVDDigitsPerROF->SetMarkerSize(1.8); + hVDDigitsPerROF->Draw("colz text"); + drawSummary(avgVDAvgDigits, peakVDAvgDigits, "digits/ROF"); + appendCanvasToPdf(canvVDDigits); + + auto* canvVDMaxDigits = new TCanvas("canvMaxDigitsVD", "VD max digits per ROF", 1050, 1050); + canvVDMaxDigits->SetTopMargin(0.08); + canvVDMaxDigits->SetRightMargin(0.18); + hVDMaxDigitsPerROF->SetMarkerSize(1.8); + hVDMaxDigitsPerROF->Draw("colz text"); + drawSummary(avgVDMaxDigits, peakVDMaxDigits, "digits/ROF"); + appendCanvasToPdf(canvVDMaxDigits); + + // --- MLOT per-layer plots --- + + for (int layer = 0; layer < nMLOTLayers; ++layer) { + if (maxSensorsPerHalfStaveMLOT[layer] == 0) { + continue; + } + const int outputLayer = nVDLayers + layer; + const int nStaves = gman->extractNumberOfStavesMLOT(layer); + const int nHalfStaves = std::max(1, gman->getNumberOfHalfStaves(layer)); + const int maxSensors = maxSensorsPerHalfStaveMLOT[layer]; + + auto* hDigitsPerROF = new TH2F(Form("h_digits_per_rof_layer%d", outputLayer), + Form("Layer %d average digits per ROF;stave id / half-stave;sensor id in half-stave;digits / ROF", outputLayer), + nStaves * nHalfStaves, -0.5, nStaves - 0.5, maxSensors, -0.5, maxSensors - 0.5); + auto* hMaxDigitsPerROF = new TH2F(Form("h_max_digits_per_rof_layer%d", outputLayer), + Form("Layer %d max digits in one ROF;stave id / half-stave;sensor id in half-stave;max digits / ROF", outputLayer), + nStaves * nHalfStaves, -0.5, nStaves - 0.5, maxSensors, -0.5, maxSensors - 0.5); + auto* hBandwidth = new TH2F(Form("h_bandwidth_layer%d", outputLayer), + Form("Layer %d bandwidth map;stave id / half-stave;sensor id in half-stave;bandwidth (Gbit/s)", outputLayer), + nStaves * nHalfStaves, -0.5, nStaves - 0.5, maxSensors, -0.5, maxSensors - 0.5); + + for (int chipID = 0; chipID < nChips; ++chipID) { + const auto& g = chipGeom[chipID]; + if (g.subDetID != 1 || g.localLayer != layer || g.disk != -1) { + continue; + } + const int sensorID = chipSensorIndex[chipID]; + if (sensorID < 0) { + continue; + } + const double staveBinX = g.stave + (g.halfStave + 0.5) / nHalfStaves - 0.5; + const double avgDigits = double(digitsPerChip[chipID]) / collisionsPerROF[g.globalLayer].size(); + const double maxDigits = (double)maxDigitsPerROFPerChip[chipID]; + + hDigitsPerROF->Fill(staveBinX, sensorID, avgDigits); + hMaxDigitsPerROF->Fill(staveBinX, sensorID, maxDigits); + hBandwidth->Fill(staveBinX, sensorID, avgDigits * DigitBits / rofLengthBC[g.globalLayer] / o2::constants::lhc::LHCBunchSpacingNS * 1.e9); + } + + const auto& ls = layerStats[outputLayer]; + + auto* canvLayer = new TCanvas(Form("canvBandwidthLayer%d", outputLayer), Form("Layer %d bandwidth", outputLayer), 1050, 1050); + canvLayer->SetTopMargin(0.08); + canvLayer->SetRightMargin(0.18); + hBandwidth->GetZaxis()->SetRangeUser(0., ls.avgBandwidthGbps > 0. ? 3. * ls.avgBandwidthGbps : 1.); + hBandwidth->Draw("colz"); + drawSummary(ls.avgBandwidthGbps, ls.peakBandwidthGbps, "Gbit/s"); + appendCanvasToPdf(canvLayer); + + auto* canvLayerDigits = new TCanvas(Form("canvDigitsLayer%d", outputLayer), Form("Layer %d digits per ROF", outputLayer), 1050, 1050); + canvLayerDigits->SetTopMargin(0.08); + canvLayerDigits->SetRightMargin(0.18); + hDigitsPerROF->Draw("colz"); + drawSummary(ls.avgDigitsPerROF, ls.peakAvgDigitsPerROF, "digits/ROF"); + appendCanvasToPdf(canvLayerDigits); + + auto* canvLayerMaxDigits = new TCanvas(Form("canvMaxDigitsLayer%d", outputLayer), Form("Layer %d max digits per ROF", outputLayer), 1050, 1050); + canvLayerMaxDigits->SetTopMargin(0.08); + canvLayerMaxDigits->SetRightMargin(0.18); + hMaxDigitsPerROF->Draw("colz"); + drawSummary(ls.avgMaxDigitsPerROF, ls.peakMaxDigitsPerROF, "digits/ROF"); + appendCanvasToPdf(canvLayerMaxDigits); + } + + // --- Digits distribution per layer --- + // Each histogram shows the distribution of total-layer bandwidth across ROFs. + + { + const int nCols = std::max(1, (int)std::ceil(std::sqrt((double)nTotalLayers))); + const int nRows = (nTotalLayers + nCols - 1) / nCols; + auto* canvBwDist = new TCanvas("canvDigitsDistPerLayer", "Digits distribution per layer", 350 * nCols, 300 * nRows); + canvBwDist->Divide(nCols, nRows); + for (int layer = 0; layer < nTotalLayers; ++layer) { + if (!hDigitsDistPerLayer[layer]) { + continue; + } + canvBwDist->cd(layer + 1); + gPad->SetLogy(); + gPad->SetTopMargin(0.10); + gPad->SetBottomMargin(0.14); + gPad->SetLeftMargin(0.14); + hDigitsDistPerLayer[layer]->Scale(1. / hDigitsDistPerLayer[layer]->GetEntries()); + hDigitsDistPerLayer[layer]->Draw("hist"); + } + appendCanvasToPdf(canvBwDist); + } + + // --- Summary: bandwidth vs layer --- + + auto* hAvgBandwidthVsLayer = new TH1D("h_avg_bandwidth_vs_layer", + "Average bandwidth by layer;layer id;average bandwidth (Gbit/s)", + nTotalLayers, -0.5, nTotalLayers - 0.5); + auto* hPeakBandwidthVsLayer = new TH1D("h_peak_bandwidth_vs_layer", + "Peak bandwidth by layer;layer id;peak bandwidth (Gbit/s)", + nTotalLayers, -0.5, nTotalLayers - 0.5); + for (int layer = 0; layer < nTotalLayers; ++layer) { + hAvgBandwidthVsLayer->SetBinContent(layer + 1, layerStats[layer].avgBandwidthGbps); + hPeakBandwidthVsLayer->SetBinContent(layer + 1, layerStats[layer].peakBandwidthGbps); + } + + auto* canvBandwidthSummary = new TCanvas("canvBandwidthSummary", "Bandwidth summary by layer", 1050, 1050); + gStyle->SetOptTitle(0); + canvBandwidthSummary->cd(); + canvBandwidthSummary->SetTopMargin(0.08); + canvBandwidthSummary->SetBottomMargin(0.14); + canvBandwidthSummary->SetLogy(); + hAvgBandwidthVsLayer->SetTitle("Average bandwidth by layer;layer id;Bandwidth (Gbit/s)"); + hAvgBandwidthVsLayer->Draw("hist"); + hPeakBandwidthVsLayer->SetLineColor(kRed); + hPeakBandwidthVsLayer->Draw("hist same"); + canvBandwidthSummary->BuildLegend(0.6, 0.75, 0.9, 0.9); + appendCanvasToPdf(canvBandwidthSummary); + + if (lastPdfCanvas != nullptr) { + lastPdfCanvas->Print(Form("%s]", outputPdf.Data())); + } + + f->Write(); + f->Close(); +} diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C index 327577102d86e..7b9365dbe2011 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C @@ -22,10 +22,16 @@ #include #include #include +#include +#include +#include +#include #include +#include #include "DataFormatsTRK/Cluster.h" #include "DataFormatsTRK/ROFRecord.h" +#include "TRKBase/AlmiraParam.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" #include "TRKSimulation/Hit.h" @@ -47,11 +53,8 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", { gROOT->SetBatch(batch); - using o2::MCCompLabel; - using ROFRec = o2::trk::ROFRecord; - using MC2ROF = o2::trk::MC2ROFRecord; using HitVec = std::vector; - using MC2HITS_map = std::unordered_map; // maps (trackID << 32) + chipID -> hit index + using MC2HITS_map = std::unordered_map>; // maps (trackID << 32) + chipID -> hit indices // ── Chip response (for hit-segment propagation to charge-collection plane) ── // Fetches the same AlpideSimResponse from CCDB as the digitizer (IT3/Calib/APTSResponse) @@ -129,6 +132,10 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", LOGP(error, "Cannot find o2sim tree in {}", hitfile); return; } + if (hitTree->GetBranch("TRKHit") == nullptr) { + LOGP(error, "Cannot find TRKHit branch in {}", hitfile); + return; + } std::vector mc2hitVec; std::vector hitVecPool; mc2hitVec.resize(hitTree->GetEntries()); @@ -142,47 +149,132 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", return; } - std::vector* clusArr = nullptr; - std::vector* rofRecVecP = nullptr; - std::vector* patternsPtr = nullptr; - clusTree->SetBranchAddress("TRKClusterComp", &clusArr); - clusTree->SetBranchAddress("TRKClustersROF", &rofRecVecP); - if (clusTree->GetBranch("TRKClusterPatt") != nullptr) { - clusTree->SetBranchAddress("TRKClusterPatt", &patternsPtr); + // Read per-layer cluster branches and accumulate + static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; + std::vector*> clusArrPerLayer(nLayers, nullptr); + std::vector*> rofRecVecPerLayer(nLayers, nullptr); + std::vector*> patternsPerLayer(nLayers, nullptr); + std::vector*> clusLabArrPerLayer(nLayers, nullptr); + std::vector> patternOffsetsPerLayer(nLayers); + std::vector layerActive(nLayers, false); + + bool hasAnyMC = false; + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + std::string brClus = std::string("TRKClusterComp_") + std::to_string(iLayer); + std::string brROF = std::string("TRKClustersROF_") + std::to_string(iLayer); + std::string brPatt = std::string("TRKClusterPatt_") + std::to_string(iLayer); + std::string brMCTruth = std::string("TRKClusterMCTruth_") + std::to_string(iLayer); + + if (clusTree->GetBranch(brClus.c_str()) == nullptr) { + LOGP(warning, "Branch {} not found, skipping layer {}", brClus, iLayer); + continue; + } + if (clusTree->GetBranch(brROF.c_str()) == nullptr) { + LOGP(error, "Branch {} not found, skipping layer {}", brROF, iLayer); + continue; + } + clusTree->SetBranchAddress(brClus.c_str(), &clusArrPerLayer[iLayer]); + clusTree->SetBranchAddress(brROF.c_str(), &rofRecVecPerLayer[iLayer]); + if (clusTree->GetBranch(brPatt.c_str()) != nullptr) { + clusTree->SetBranchAddress(brPatt.c_str(), &patternsPerLayer[iLayer]); + } else { + LOGP(warning, "Branch {} not found, layer {} cluster positions use bbox origins", brPatt, iLayer); + } + if (clusTree->GetBranch(brMCTruth.c_str()) != nullptr) { + clusTree->SetBranchAddress(brMCTruth.c_str(), &clusLabArrPerLayer[iLayer]); + hasAnyMC = true; + } + layerActive[iLayer] = true; } - o2::dataformats::MCTruthContainer* clusLabArr = nullptr; - std::vector mc2rofVec, *mc2rofVecP = &mc2rofVec; - bool hasMC = (clusTree->GetBranch("TRKClusterMCTruth") != nullptr); - if (hasMC) { - clusTree->SetBranchAddress("TRKClusterMCTruth", &clusLabArr); - clusTree->SetBranchAddress("TRKClustersMC2ROF", &mc2rofVecP); + // Read entry and accumulate all layers + if (clusTree->GetEntry(0) <= 0) { + LOGP(error, "Cannot read entry 0 from {}", clusfile); + return; } - clusTree->GetEntry(0); - const unsigned int nROFRec = rofRecVecP ? (unsigned int)rofRecVecP->size() : 0u; - LOGP(info, "Number of ROF records: {}", nROFRec); - auto pattIt = patternsPtr ? patternsPtr->cbegin() : std::vector::const_iterator{}; - - // ── Build per-ROF MC event range ─────────────────────────────────────────── - std::vector mcEvMin(nROFRec, (int)hitTree->GetEntries()); - std::vector mcEvMax(nROFRec, -1); - if (hasMC) { - for (int imc = (int)mc2rofVec.size(); imc--;) { - const auto& mc2rof = mc2rofVec[imc]; - if (mc2rof.rofRecordID < 0) { - continue; + auto hasAnyActiveLayer = false; + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + hasAnyActiveLayer = hasAnyActiveLayer || layerActive[iLayer]; + } + if (!hasAnyActiveLayer) { + LOGP(error, "No usable TRK cluster layers found in {}", clusfile); + return; + } + + // Print total clusters per layer + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + if (!layerActive[iLayer]) { + continue; + } + if (clusArrPerLayer[iLayer] == nullptr || rofRecVecPerLayer[iLayer] == nullptr) { + LOGP(error, "Layer {} branches were declared but did not load usable data, skipping layer", iLayer); + layerActive[iLayer] = false; + continue; + } + LOGP(info, "Layer {}: {} clusters", iLayer, clusArrPerLayer[iLayer]->size()); + } + + // The pattern stream is variable-length, so index it by cluster entry once. + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + if (!layerActive[iLayer] || patternsPerLayer[iLayer] == nullptr) { + continue; + } + const auto nClusters = clusArrPerLayer[iLayer]->size(); + const auto& patterns = *patternsPerLayer[iLayer]; + auto& offsets = patternOffsetsPerLayer[iLayer]; + offsets.resize(nClusters, std::numeric_limits::max()); + size_t pattPos = 0; + bool validPatterns = true; + for (size_t icl = 0; icl < nClusters; icl++) { + if (pattPos + 2 > patterns.size()) { + validPatterns = false; + break; } - for (unsigned int irfd = mc2rof.maxROF - mc2rof.minROF + 1; irfd--;) { - unsigned int irof = mc2rof.rofRecordID + irfd; - if (irof >= nROFRec) { - continue; - } - if (mcEvMin[irof] > imc) { - mcEvMin[irof] = imc; + offsets[icl] = pattPos; + const uint8_t rowSpan = patterns[pattPos]; + const uint8_t colSpan = patterns[pattPos + 1]; + const size_t nBytes = (size_t(rowSpan) * colSpan + 7) / 8; + if (pattPos + 2 + nBytes > patterns.size()) { + validPatterns = false; + break; + } + pattPos += 2 + nBytes; + } + if (!validPatterns || pattPos != patterns.size()) { + LOGP(error, "Malformed pattern stream for layer {}: {} pattern bytes for {} clusters, disabling CoG corrections for this layer", + iLayer, patterns.size(), nClusters); + patternsPerLayer[iLayer] = nullptr; + offsets.clear(); + } + } + + // Accumulate max ROF count across all layers + unsigned int nROFRec = 0; + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + if (!layerActive[iLayer]) { + continue; + } + nROFRec = std::max(nROFRec, (unsigned int)rofRecVecPerLayer[iLayer]->size()); + } + LOGP(info, "Number of ROF records: {}", nROFRec); + + // ── Load all MC hit events upfront (TRK has no MC2ROF mapping) ────────────── + if (hasAnyMC) { + LOGP(info, "Pre-loading {} MC events", hitTree->GetEntries()); + for (int im = 0; im < (int)hitTree->GetEntries(); im++) { + if (hitVecPool[im] == nullptr) { + hitTree->SetBranchAddress("TRKHit", &hitVecPool[im]); + if (hitTree->GetEntry(im) <= 0 || hitVecPool[im] == nullptr) { + LOGP(error, "Cannot read TRKHit entry {} from {}", im, hitfile); + return; } - if (mcEvMax[irof] < imc) { - mcEvMax[irof] = imc; + auto& mc2hit = mc2hitVec[im]; + const auto* hv = hitVecPool[im]; + for (int ih = (int)hv->size(); ih--;) { + const auto& hit = (*hv)[ih]; + uint64_t key = (uint64_t(hit.GetTrackID()) << 32) + hit.GetDetectorID(); + mc2hit[key].push_back(ih); } } } @@ -193,180 +285,195 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", // columns: event, MC track label, // local hit x/z (flat frame), global hit x/y/z (midpoint), // global cluster x/y/z, local cluster x/z, - // residuals dx/dz (local, cluster - hit), // ROF frame, cluster size, chipID, layer, disk, subDetID, row, col, pt TNtuple nt("ntc", "TRK cluster ntuple", "event:mcTrackID:hitLocX:hitLocZ:hitGlobX:hitGlobY:hitGlobZ:clusGlobX:clusGlobY:clusGlobZ:clusLocX:clusLocZ:rofFrame:clusSize:chipID:layer:disk:subdet:row:col:pt"); // ── Counters ─────────────────────────────────────────────────────────────── - long nTot{0}, nInvalidLabel{0}, nNoMCHit{0}, nValid{0}; + long nTot{0}, nInvalidLabel{0}, nInvalidEvent{0}, nNoMCHit{0}, nValid{0}; // ── Main loop ────────────────────────────────────────────────────────────── for (unsigned int irof = 0; irof < nROFRec; irof++) { - const auto& rofRec = (*rofRecVecP)[irof]; - - // Cache MC hit events for this ROF - if (hasMC) { - for (int im = mcEvMin[irof]; im <= mcEvMax[irof]; im++) { - if (hitVecPool[im] == nullptr) { - hitTree->SetBranchAddress("TRKHit", &hitVecPool[im]); - hitTree->GetEntry(im); - auto& mc2hit = mc2hitVec[im]; - const auto* hv = hitVecPool[im]; - for (int ih = (int)hv->size(); ih--;) { - const auto& hit = (*hv)[ih]; - uint64_t key = (uint64_t(hit.GetTrackID()) << 32) + hit.GetDetectorID(); - mc2hit.emplace(key, ih); - } - } + // Process each layer + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + if (!layerActive[iLayer]) { + continue; } - } - - for (int icl = 0; icl < rofRec.getNEntries(); icl++) { - const int clEntry = rofRec.getFirstEntry() + icl; - const auto& cluster = (*clusArr)[clEntry]; - nTot++; - - // ── Parse pattern → center-of-gravity within bounding box ────────── - // The cluster stores the bounding-box top-left pixel (row, col). - // The pattern stream encodes [rowSpan, colSpan, bitmap...] for each cluster. - // We accumulate pixel row/col offsets to obtain a sub-pixel CoG correction. - float cogDr{0.f}, cogDc{0.f}; // mean offsets from bbox origin (pixels) - if (patternsPtr) { - const uint8_t rowSpan = *pattIt++; - const uint8_t colSpan = *pattIt++; - const int nBytes = (rowSpan * colSpan + 7) / 8; - int nPix{0}, pixIdx{0}; - for (int ib = 0; ib < nBytes; ib++) { - const uint8_t byte = *pattIt++; - for (int bit = 7; bit >= 0 && pixIdx < rowSpan * colSpan; bit--, pixIdx++) { - if (byte & (1 << bit)) { - cogDr += pixIdx / colSpan; - cogDc += pixIdx % colSpan; - nPix++; + if (rofRecVecPerLayer[iLayer]->empty() || irof >= rofRecVecPerLayer[iLayer]->size()) { + continue; + } + const auto& rofRec = (*rofRecVecPerLayer[iLayer])[irof]; + const auto& clusArr = *clusArrPerLayer[iLayer]; + const auto& clusLabArr = clusLabArrPerLayer[iLayer]; + const auto* patternsPtr = patternsPerLayer[iLayer]; + const auto& patternOffsets = patternOffsetsPerLayer[iLayer]; + + for (int icl = 0; icl < rofRec.getNEntries(); icl++) { + const int clEntry = rofRec.getFirstEntry() + icl; + if (clEntry < 0 || clEntry >= (int)clusArr.size()) { + LOGP(error, "Layer {} ROF {} points to cluster entry {} outside {} clusters", + iLayer, irof, clEntry, clusArr.size()); + continue; + } + const auto& cluster = clusArr[clEntry]; + nTot++; + + // ── Parse pattern → center-of-gravity within bounding box ────────── + // Keep this in sync with Clusterer::getClusterLocalCoordinates(). + float cogDr{0.f}, cogDc{0.f}; // mean offsets from bbox origin (pixels) + if (patternsPtr != nullptr) { + const auto pattOffset = patternOffsets[clEntry]; + const auto* pattIt = patternsPtr->data() + pattOffset; + const uint8_t rowSpan = *pattIt++; + const uint8_t colSpan = *pattIt++; + const int nBytes = (rowSpan * colSpan + 7) / 8; + int nPix{0}, pixIdx{0}; + for (int ib = 0; ib < nBytes; ib++) { + const uint8_t byte = *pattIt++; + for (int bit = 7; bit >= 0 && pixIdx < rowSpan * colSpan; bit--, pixIdx++) { + if (byte & (1 << bit)) { + cogDr += pixIdx / colSpan; + cogDc += pixIdx % colSpan; + nPix++; + } } } + if (nPix > 1) { + cogDr /= nPix; + cogDc /= nPix; + } + } + + // ── Cluster local → global (CoG position) ───────────────────────────── + // Get local coords of the bounding-box corner pixel, then apply the + // fractional CoG displacement using the pixel pitch. + float clLocX{0.f}, clLocZ{0.f}; + o2::trk::SegmentationChip::detectorToLocalUnchecked( + cluster.row, cluster.col, clLocX, clLocZ, + cluster.subDetID, cluster.layer, cluster.disk); + const float pitchRow = (cluster.subDetID == 0) + ? o2::trk::SegmentationChip::PitchRowVD + : o2::trk::SegmentationChip::PitchRowMLOT; + const float pitchCol = (cluster.subDetID == 0) + ? o2::trk::SegmentationChip::PitchColVD + : o2::trk::SegmentationChip::PitchColMLOT; + clLocX -= cogDr * pitchRow; // increasing row -> decreasing xRow + clLocZ += cogDc * pitchCol; // increasing col -> increasing zCol + + o2::math_utils::Point3D locC; + if (cluster.subDetID == 0) { + auto cv = o2::trk::SegmentationChip::flatToCurved(cluster.layer, clLocX, 0.f); + locC = {cv.X(), cv.Y(), clLocZ}; + } else { + locC = {clLocX, yPlaneMLOT, clLocZ}; } - if (nPix > 1) { - cogDr /= nPix; - cogDc /= nPix; + auto gloC = gman->getMatrixL2G(cluster.chipID)(locC); + + if (!hasAnyMC || clusLabArr == nullptr) { + // No MC info: just fill geometry columns, leave residuals as 0 + std::array data = { + -1.f, -1.f, + 0.f, 0.f, 0.f, 0.f, 0.f, + (float)gloC.X(), (float)gloC.Y(), (float)gloC.Z(), + clLocX, clLocZ, + (float)rofRec.getROFrame(), (float)cluster.size, (float)cluster.chipID, + (float)cluster.layer, (float)cluster.disk, (float)cluster.subDetID, + (float)cluster.row, (float)cluster.col, -1.f}; + nt.Fill(data.data()); + continue; } - } - // ── Cluster local → global (CoG position) ───────────────────────────── - // Get local coords of the bounding-box corner pixel, then apply the - // fractional CoG displacement using the pixel pitch. - // Formula from detectorToLocalUnchecked: - // VD : xRow = 0.5*(width[lay]-pitchRow) - row*pitchRow → row↑ xRow↓ - // zCol = col*pitchCol + 0.5*(pitchCol-length) → col↑ zCol↑ - // MLOT: same structure with MLOT pitches - float clLocX{0.f}, clLocZ{0.f}; - o2::trk::SegmentationChip::detectorToLocalUnchecked( - cluster.row, cluster.col, clLocX, clLocZ, - cluster.subDetID, cluster.layer, cluster.disk); - const float pitchRow = (cluster.subDetID == 0) - ? o2::trk::SegmentationChip::PitchRowVD - : o2::trk::SegmentationChip::PitchRowMLOT; - const float pitchCol = (cluster.subDetID == 0) - ? o2::trk::SegmentationChip::PitchColVD - : o2::trk::SegmentationChip::PitchColMLOT; - clLocX -= cogDr * pitchRow; // increasing row → decreasing xRow - clLocZ += cogDc * pitchCol; // increasing col → increasing zCol - const float yResponse = (cluster.subDetID == 0) ? yPlaneVD : yPlaneMLOT; - // For VD the L2G matrix is built in the *curved* local frame (quasi-Cartesian, - // origin at the beam axis). Convert flat (clLocX, 0) → curved (xC, yC) first. - // For MLOT (flat sensors) the local frame is already Cartesian: pass directly. - // clLocX is already in the flat frame from detectorToLocalUnchecked + CoG and - // does NOT need any further transformation for the residual comparison. - o2::math_utils::Point3D locC; - if (cluster.subDetID == 0) { - auto cv = o2::trk::SegmentationChip::flatToCurved(cluster.layer, clLocX, 0.f); - locC = {cv.X(), cv.Y(), clLocZ}; - } else { - locC = {clLocX, yResponse, clLocZ}; - } - auto gloC = gman->getMatrixL2G(cluster.chipID)(locC); + // ── MC label ─────────────────────────────────────────────────────── + const auto& labels = clusLabArr->getLabels(clEntry); + if (labels.empty() || !labels[0].isValid()) { + nInvalidLabel++; + continue; + } + const auto& lab = labels[0]; + const int trID = lab.getTrackID(); + const int evID = lab.getEventID(); + if (evID < 0 || evID >= (int)mc2hitVec.size()) { + nInvalidEvent++; + continue; + } - if (!hasMC || clusLabArr == nullptr) { - // No MC info: just fill geometry columns, leave residuals as 0 + // ── Find matching MC hit ──────────────────────────────────────────── + const auto& mc2hit = mc2hitVec[evID]; + uint64_t key = (uint64_t(trID) << 32) + cluster.chipID; + auto hitEntry = mc2hit.find(key); + if (hitEntry == mc2hit.end()) { + nNoMCHit++; + continue; + } + auto projectHitToResponsePlane = [&](const o2::trk::Hit& hit, float& hitLocX, float& hitLocZ) { + const auto& gloHend = hit.GetPos(); + const auto& gloHsta = hit.GetPosStart(); + o2::math_utils::Point3D locHsta = gman->getMatrixL2G(cluster.chipID) ^ (gloHsta); // inverse L2G + o2::math_utils::Point3D locHend = gman->getMatrixL2G(cluster.chipID) ^ (gloHend); // inverse L2G + + // Rather than the geometric midpoint, find where the track segment crosses + // the response plane. For VD convert the curved endpoints to the flat frame first. + if (cluster.subDetID == 0) { + auto flatSta = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHsta.X(), locHsta.Y()); + auto flatEnd = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHend.X(), locHend.Y()); + float x0 = flatSta.X(), y0 = flatSta.Y(), z0 = locHsta.Z(); + float dltx = flatEnd.X() - x0, dlty = flatEnd.Y() - y0, dltz = locHend.Z() - z0; + float r = (std::abs(dlty) > 1e-9f) ? (yPlaneVD - y0) / dlty : 0.5f; + hitLocX = x0 + r * dltx; + hitLocZ = z0 + r * dltz; + } else { + float x0 = locHsta.X(), y0 = locHsta.Y(), z0 = locHsta.Z(); + float dltx = locHend.X() - x0, dlty = locHend.Y() - y0, dltz = locHend.Z() - z0; + float r = (std::abs(dlty) > 1e-9f) ? (yPlaneMLOT - y0) / dlty : 0.5f; + hitLocX = x0 + r * dltx; + hitLocZ = z0 + r * dltz; + } + }; + + const o2::trk::Hit* bestHit = nullptr; + float hitLocX{0.f}, hitLocZ{0.f}; + float bestDist2 = std::numeric_limits::max(); + for (const auto ih : hitEntry->second) { + const auto& candHit = (*hitVecPool[evID])[ih]; + float candLocX{0.f}, candLocZ{0.f}; + projectHitToResponsePlane(candHit, candLocX, candLocZ); + const float dx = clLocX - candLocX; + const float dz = clLocZ - candLocZ; + const float dist2 = dx * dx + dz * dz; + if (dist2 < bestDist2) { + bestDist2 = dist2; + bestHit = &candHit; + hitLocX = candLocX; + hitLocZ = candLocZ; + } + } + if (bestHit == nullptr) { + nNoMCHit++; + continue; + } + const auto& hit = *bestHit; + const float pt = TMath::Hypot(hit.GetPx(), hit.GetPy()); + + // ── Hit global midpoint ──────────────────────────────────────────── + const auto& gloHend = hit.GetPos(); + const auto& gloHsta = hit.GetPosStart(); + o2::math_utils::Point3D gloHmid( + 0.5f * (gloHend.X() + gloHsta.X()), + 0.5f * (gloHend.Y() + gloHsta.Y()), + 0.5f * (gloHend.Z() + gloHsta.Z())); + + nValid++; std::array data = { - -1.f, -1.f, - 0.f, 0.f, 0.f, 0.f, 0.f, + (float)evID, (float)trID, + hitLocX, hitLocZ, + (float)gloHmid.X(), (float)gloHmid.Y(), (float)gloHmid.Z(), (float)gloC.X(), (float)gloC.Y(), (float)gloC.Z(), clLocX, clLocZ, (float)rofRec.getROFrame(), (float)cluster.size, (float)cluster.chipID, (float)cluster.layer, (float)cluster.disk, (float)cluster.subDetID, - (float)cluster.row, (float)cluster.col, -1.f}; + (float)cluster.row, (float)cluster.col, pt}; nt.Fill(data.data()); - continue; } - - // ── MC label ─────────────────────────────────────────────────────── - const auto& labels = clusLabArr->getLabels(clEntry); - if (labels.empty() || !labels[0].isValid()) { - nInvalidLabel++; - continue; - } - const auto& lab = labels[0]; - const int trID = lab.getTrackID(); - const int evID = lab.getEventID(); - - // ── Find matching MC hit ──────────────────────────────────────────── - const auto& mc2hit = mc2hitVec[evID]; - uint64_t key = (uint64_t(trID) << 32) + cluster.chipID; - auto hitEntry = mc2hit.find(key); - if (hitEntry == mc2hit.end()) { - nNoMCHit++; - continue; - } - const auto& hit = (*hitVecPool[evID])[hitEntry->second]; - const float pt = TMath::Hypot(hit.GetPx(), hit.GetPy()); - - // ── Hit global midpoint ──────────────────────────────────────────── - const auto& gloHend = hit.GetPos(); - const auto& gloHsta = hit.GetPosStart(); - o2::math_utils::Point3D gloHmid( - 0.5f * (gloHend.X() + gloHsta.X()), - 0.5f * (gloHend.Y() + gloHsta.Y()), - 0.5f * (gloHend.Z() + gloHsta.Z())); - - // ── Hit global → local ───────────────────────────── - o2::math_utils::Point3D locHsta = gman->getMatrixL2G(cluster.chipID) ^ (gloHsta); // inverse L2G - o2::math_utils::Point3D locHend = gman->getMatrixL2G(cluster.chipID) ^ (gloHend); // inverse L2G - - // ── Propagate hit segment to the sensor response surface ─────────────── - // Rather than the geometric midpoint, find where the track segment crosses - // the response plane (y = responseYShift in the flat local frame). - // For VD (curved): convert both endpoints to flat frame first. - // For ML/OT (flat): use local coordinates directly. - float hitLocX{0.f}, hitLocZ{0.f}; - if (cluster.subDetID == 0) { // VD – curved sensor - auto flatSta = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHsta.X(), locHsta.Y()); - auto flatEnd = o2::trk::SegmentationChip::curvedToFlat(cluster.layer, locHend.X(), locHend.Y()); - float x0 = flatSta.X(), y0 = flatSta.Y(), z0 = locHsta.Z(); - float dltx = flatEnd.X() - x0, dlty = flatEnd.Y() - y0, dltz = locHend.Z() - z0; - float r = (std::abs(dlty) > 1e-9f) ? (yPlaneVD - y0) / dlty : 0.5f; - hitLocX = x0 + r * dltx; - hitLocZ = z0 + r * dltz; - } else { // ML/OT – flat sensor - float x0 = locHsta.X(), y0 = locHsta.Y(), z0 = locHsta.Z(); - float dltx = locHend.X() - x0, dlty = locHend.Y() - y0, dltz = locHend.Z() - z0; - float r = (std::abs(dlty) > 1e-9f) ? (yPlaneMLOT - y0) / dlty : 0.5f; - hitLocX = x0 + r * dltx; - hitLocZ = z0 + r * dltz; - } - - nValid++; - std::array data = { - (float)evID, (float)trID, - hitLocX, hitLocZ, - (float)gloHmid.X(), (float)gloHmid.Y(), (float)gloHmid.Z(), - (float)gloC.X(), (float)gloC.Y(), (float)gloC.Z(), - clLocX, clLocZ, - (float)rofRec.getROFrame(), (float)cluster.size, (float)cluster.chipID, - (float)cluster.layer, (float)cluster.disk, (float)cluster.subDetID, - (float)cluster.row, (float)cluster.col, pt}; - nt.Fill(data.data()); } } @@ -375,6 +482,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", LOGP(info, "Total clusters: {}", nTot); LOGP(info, "Valid (hit matched): {}", nValid); LOGP(info, "Invalid/noise MC labels: {}", nInvalidLabel); + LOGP(info, "Invalid MC event IDs: {}", nInvalidEvent); LOGP(info, "MC hit not found: {}", nNoMCHit); // ── Visualisation ────────────────────────────────────────────────────────── auto canvGlobal = new TCanvas("canvGlobal", "Cluster global positions", 1600, 800); @@ -388,25 +496,25 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", auto canvRes = new TCanvas("canvRes", "Residuals (cluster - hit) [cm]", 1600, 1200); canvRes->Divide(2, 3); canvRes->cd(1)->SetLogy(); - nt.Draw("hitLocX-clusLocX>>h_dx_VD(200,-0.02,0.02)", "subdet==0&&event>=0"); + nt.Draw("clusLocX-hitLocX>>h_dx_VD(200,-0.02,0.02)", "subdet==0&&event>=0"); canvRes->cd(2)->SetLogy(); - nt.Draw("hitLocZ-clusLocZ>>h_dz_VD(200,-0.02,0.02)", "subdet==0&&event>=0"); + nt.Draw("clusLocZ-hitLocZ>>h_dz_VD(200,-0.02,0.02)", "subdet==0&&event>=0"); canvRes->cd(3)->SetLogy(); - nt.Draw("hitLocX-clusLocX>>h_dx_MLOT(200,-0.02,0.02)", "subdet==1&&event>=0"); + nt.Draw("clusLocX-hitLocX>>h_dx_MLOT(200,-0.02,0.02)", "subdet==1&&event>=0"); canvRes->cd(4)->SetLogy(); - nt.Draw("hitLocZ-clusLocZ>>h_dz_MLOT(200,-0.02,0.02)", "subdet==1&&event>=0"); + nt.Draw("clusLocZ-hitLocZ>>h_dz_MLOT(200,-0.02,0.02)", "subdet==1&&event>=0"); canvRes->cd(5)->SetLogz(); - nt.Draw("hitLocX-clusLocX:hitLocZ-clusLocZ>>h_dxdz_VD(200,-0.02,0.02,200,-0.02,0.02)", "subdet==0&&event>=0", "colz"); + nt.Draw("clusLocX-hitLocX:clusLocZ-hitLocZ>>h_dxdz_VD(200,-0.02,0.02,200,-0.02,0.02)", "subdet==0&&event>=0", "colz"); canvRes->cd(6); - nt.Draw("hitLocX-clusLocX:hitLocZ-clusLocZ>>h_dxdz_MLOT(200,-0.02,0.02,200,-0.02,0.02)", "subdet==1&&event>=0", "colz"); + nt.Draw("clusLocX-hitLocX:clusLocZ-hitLocZ>>h_dxdz_MLOT(200,-0.02,0.02,200,-0.02,0.02)", "subdet==1&&event>=0", "colz"); canvRes->SaveAs("trk_residuals.png"); auto canvResVsLayer = new TCanvas("canvResVsLayer", "Residuals vs layer", 1600, 600); canvResVsLayer->Divide(2, 1); canvResVsLayer->cd(1); - nt.Draw("hitLocX-clusLocX:layer>>h_dx_vs_lay(20,0,20,200,-0.02,0.02)", "event>=0", "prof"); + nt.Draw("clusLocX-hitLocX:layer>>h_dx_vs_lay(20,0,20,200,-0.02,0.02)", "event>=0", "prof"); canvResVsLayer->cd(2); - nt.Draw("hitLocZ-clusLocZ:layer>>h_dz_vs_lay(20,0,20,200,-0.02,0.02)", "event>=0", "prof"); + nt.Draw("clusLocZ-hitLocZ:layer>>h_dz_vs_lay(20,0,20,200,-0.02,0.02)", "event>=0", "prof"); canvResVsLayer->SaveAs("trk_residuals_vs_layer.png"); fout.cd(); diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigits.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C similarity index 50% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigits.C rename to Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C index 618dbe929a943..400457fc98585 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigits.C +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C @@ -31,6 +31,8 @@ #include "SimulationDataFormat/IOMCTruthContainerView.h" #include "SimulationDataFormat/MCCompLabel.h" #include "DetectorsBase/GeometryManager.h" +#include "ITSMFTSimulation/AlpideSimResponse.h" +#include "CCDB/BasicCCDBManager.h" #include "DataFormatsITSMFT/ROFRecord.h" @@ -73,7 +75,7 @@ void addTLines(float pitch) gPad->Update(); } -void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = "o2sim_HitsTRK.root", std::string inputGeom = "o2sim_geometry.root", std::string paramfile = "o2sim_par.root") +void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = "o2sim_HitsTRK.root", std::string inputGeom = "o2sim_geometry.root") { gStyle->SetPalette(55); @@ -95,9 +97,22 @@ void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = auto* gman = o2::trk::GeometryTGeo::Instance(); gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + const int nVDLayers = gman->extractNumberOfLayersVD(); + const int nMLOTLayers = gman->getNumberOfLayersMLOT(); + const int nTotalLayers = nVDLayers + nMLOTLayers; + SegmentationChip seg; // seg.Print(); + // MLOT response plane: y = halfThickness - depthMax. + float depthMax = (float)o2::trk::constants::apts::thickness; // fallback (no CCDB) + auto& ccdbMgr = o2::ccdb::BasicCCDBManager::instance(); + ccdbMgr.setURL("http://alice-ccdb.cern.ch"); + if (auto* alpResp = ccdbMgr.get("IT3/Calib/APTSResponse")) { + depthMax = alpResp->getDepthMax(); + } + const float yPlaneMLOT = o2::trk::SegmentationChip::SiliconThicknessMLOT / 2.f - depthMax; + const float yPlaneVD = -o2::trk::SegmentationChip::SiliconThicknessVD; // VD reference plane in local flat y // Hits TFile* hitFile = TFile::Open(hitfile.data()); TTree* hitTree = (TTree*)hitFile->Get("o2sim"); @@ -106,206 +121,181 @@ void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = std::vector> mc2hitVec(nevH); - // Digits + // Digits — per-layer branches TFile* digFile = TFile::Open(digifile.data()); TTree* digTree = (TTree*)digFile->Get("o2sim"); - std::vector* digArr = nullptr; - digTree->SetBranchAddress("TRKDigit", &digArr); - - o2::dataformats::IOMCTruthContainerView* plabels = nullptr; - digTree->SetBranchAddress("TRKDigitMCTruth", &plabels); - - // Get Read Out Frame arrays - std::vector* ROFRecordArrray = nullptr; - digTree->SetBranchAddress("TRKDigitROF", &ROFRecordArrray); - std::vector& ROFRecordArrrayRef = *ROFRecordArrray; + int nDigitLayers = 0; + std::vector*> digArr(nTotalLayers, nullptr); + std::vector*> rofRecordsArr(nTotalLayers, nullptr); + std::vector plabelsArr(nTotalLayers, nullptr); - std::vector* MC2ROFRecordArrray = nullptr; - digTree->SetBranchAddress("TRKDigitMC2ROF", &MC2ROFRecordArrray); - std::vector& MC2ROFRecordArrrayRef = *MC2ROFRecordArrray; + for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { + if (!digTree->GetBranch(Form("TRKDigit_%i", iLayer))) { + break; + } + digTree->SetBranchAddress(Form("TRKDigit_%i", iLayer), &digArr[iLayer]); + digTree->SetBranchAddress(Form("TRKDigitROF_%i", iLayer), &rofRecordsArr[iLayer]); + digTree->SetBranchAddress(Form("TRKDigitMCTruth_%i", iLayer), &plabelsArr[iLayer]); + ++nDigitLayers; + } digTree->GetEntry(0); - int nROFRec = (int)ROFRecordArrrayRef.size(); - std::vector mcEvMin(nROFRec, hitTree->GetEntries()); - std::vector mcEvMax(nROFRec, -1); - o2::dataformats::ConstMCTruthContainer labels; - plabels->copyandflatten(labels); - delete plabels; - - // >> build min and max MC events used by each ROF - for (int imc = MC2ROFRecordArrrayRef.size(); imc--;) { - const auto& mc2rof = MC2ROFRecordArrrayRef[imc]; - // printf("MCRecord: "); - // mc2rof.print(); - - if (mc2rof.rofRecordID < 0) { - continue; // this MC event did not contribute to any ROF + // Load all MC hit events upfront and build the hit lookup map. + for (int im = 0; im < nevH; ++im) { + hitTree->SetBranchAddress("TRKHit", &hitArray[im]); + hitTree->GetEntry(im); + auto& mc2hit = mc2hitVec[im]; + for (int ih = hitArray[im]->size(); ih--;) { + const auto& hit = (*hitArray[im])[ih]; + uint64_t key = (uint64_t(hit.GetTrackID()) << 32) + hit.GetDetectorID(); + mc2hit.emplace(key, ih); } + } - for (int irfd = mc2rof.maxROF - mc2rof.minROF + 1; irfd--;) { + // LOOP over layers, then ROFRecords within each layer + for (int iLayer = 0; iLayer < nDigitLayers; ++iLayer) { + auto& rofArr = *rofRecordsArr[iLayer]; + const int nROFRec = (int)rofArr.size(); - int irof = mc2rof.rofRecordID + irfd; + o2::dataformats::ConstMCTruthContainer labels; + plabelsArr[iLayer]->copyandflatten(labels); - if (irof >= nROFRec) { - LOG(error) << "ROF=" << irof << " from MC2ROF record is >= N ROFs=" << nROFRec; - } - if (mcEvMin[irof] > imc) { - mcEvMin[irof] = imc; - } - if (mcEvMax[irof] < imc) { - mcEvMax[irof] = imc; - } - } - } // << build min and max MC events used by each ROF + // LOOP on : ROFRecord array + for (unsigned int iROF = 0; iROF < rofArr.size(); ++iROF) { + + const unsigned int rofIndex = rofArr[iROF].getFirstEntry(); + const unsigned int rofNEntries = rofArr[iROF].getNEntries(); - unsigned int rofIndex = 0; - unsigned int rofNEntries = 0; + // LOOP on : digits array + for (unsigned int iDigit = rofIndex; iDigit < rofIndex + rofNEntries; iDigit++) { + if (iDigit % 1000 == 0) + std::cout << "Layer " << iLayer << ": reading digit " << iDigit << " / " << digArr[iLayer]->size() << std::endl; - // LOOP on : ROFRecord array - for (unsigned int iROF = 0; iROF < ROFRecordArrrayRef.size(); iROF++) { + Int_t ix = (*digArr[iLayer])[iDigit].getRow(), iz = (*digArr[iLayer])[iDigit].getColumn(); + Int_t iDetID = (*digArr[iLayer])[iDigit].getChipIndex(); + Int_t layer = gman->getLayer(iDetID); + Int_t disk = gman->getDisk(iDetID); + Int_t subDetID = gman->getSubDetID(iDetID); + Int_t petalCase = gman->getPetalCase(iDetID); + Int_t stave = gman->getStave(iDetID); + Int_t halfstave = gman->getHalfStave(iDetID); - rofIndex = ROFRecordArrrayRef[iROF].getFirstEntry(); - rofNEntries = ROFRecordArrrayRef[iROF].getNEntries(); + Float_t x = 0.f, y = 0.f, z = 0.f; + Float_t x_flat = 0.f, z_flat = 0.f; - // >> read and map MC events contributing to this ROF - for (int im = mcEvMin[iROF]; im <= mcEvMax[iROF]; im++) { + if (disk != -1) { + continue; // skip disks for the moment + } - if (!hitArray[im]) { + if (subDetID != 0) { + seg.detectorToLocal(ix, iz, x, z, subDetID, layer, disk); + } else if (subDetID == 0) { + seg.detectorToLocal(ix, iz, x_flat, z_flat, subDetID, layer, disk); + o2::math_utils::Vector2D xyCurved = seg.flatToCurved(layer, x_flat, 0.); + x = xyCurved.X(); + y = xyCurved.Y(); + z = z_flat; + } - hitTree->SetBranchAddress("TRKHit", &hitArray[im]); - hitTree->GetEntry(im); + o2::math_utils::Point3D locD(x, y, z); // local Digit curved + o2::math_utils::Point3D locDF(-1, -1, -1); // local Digit flat - auto& mc2hit = mc2hitVec[im]; + Int_t chipID = (*digArr[iLayer])[iDigit].getChipIndex(); + auto lab = (labels.getLabels(iDigit))[0]; - for (int ih = hitArray[im]->size(); ih--;) { + int trID = lab.getTrackID(); - const auto& hit = (*hitArray[im])[ih]; - uint64_t key = (uint64_t(hit.GetTrackID()) << 32) + hit.GetDetectorID(); - mc2hit.emplace(key, ih); + if (!lab.isValid()) { // not a noise + continue; } - } - } - // LOOP on : digits array - for (unsigned int iDigit = rofIndex; iDigit < rofIndex + rofNEntries; iDigit++) { - // if (iDigit % 10000 != 0) /// looking only at a small sample - // continue; - - if (iDigit % 1000 == 0) - std::cout << "Reading digit " << iDigit << " / " << digArr->size() << std::endl; - - Int_t ix = (*digArr)[iDigit].getRow(), iz = (*digArr)[iDigit].getColumn(); - Int_t iDetID = (*digArr)[iDigit].getChipIndex(); - Int_t layer = gman->getLayer(iDetID); - Int_t disk = gman->getDisk(iDetID); - Int_t subDetID = gman->getSubDetID(iDetID); - Int_t petalCase = gman->getPetalCase(iDetID); - Int_t stave = gman->getStave(iDetID); - Int_t halfstave = gman->getHalfStave(iDetID); - - Float_t x = 0.f, y = 0.f, z = 0.f; - Float_t x_flat = 0.f, z_flat = 0.f; - - if (disk != -1) { - continue; // skip disks for the moment - } - - if (subDetID != 0) { - seg.detectorToLocal(ix, iz, x, z, subDetID, layer, disk); - } else if (subDetID == 0) { - seg.detectorToLocal(ix, iz, x_flat, z_flat, subDetID, layer, disk); - o2::math_utils::Vector2D xyCurved = seg.flatToCurved(layer, x_flat, 0.); - x = xyCurved.X(); - y = xyCurved.Y(); - z = z_flat; - } - - o2::math_utils::Point3D locD(x, y, z); // local Digit curved - o2::math_utils::Point3D locDF(-1, -1, -1); // local Digit flat - - Int_t chipID = (*digArr)[iDigit].getChipIndex(); - auto lab = (labels.getLabels(iDigit))[0]; - - int trID = lab.getTrackID(); - - if (!lab.isValid()) { // not a noise - continue; - } - - const auto gloD = gman->getMatrixL2G(chipID)(locD); // convert to global - - std::unordered_map* mc2hit = &mc2hitVec[lab.getEventID()]; - - // get MC info - uint64_t key = (uint64_t(trID) << 32) + chipID; - auto hitEntry = mc2hit->find(key); - - if (hitEntry == mc2hit->end()) { - - LOG(error) << "Failed to find MC hit entry for Tr" << trID << " chipID" << chipID; - continue; - } - - ////// HITS - Hit& hit = (*hitArray[lab.getEventID()])[hitEntry->second]; - - auto xyzLocE = gman->getMatrixL2G(chipID) ^ (hit.GetPos()); // inverse conversion from global to local - auto xyzLocS = gman->getMatrixL2G(chipID) ^ (hit.GetPosStart()); - - o2::math_utils::Vector3D locH; /// Hit, average between start and end pos - locH.SetCoordinates(0.5f * (xyzLocE.X() + xyzLocS.X()), 0.5f * (xyzLocE.Y() + xyzLocS.Y()), 0.5f * (xyzLocE.Z() + xyzLocS.Z())); - o2::math_utils::Vector3D locHS; /// Hit, start pos - locHS.SetCoordinates(xyzLocS.X(), xyzLocS.Y(), xyzLocS.Z()); - o2::math_utils::Vector3D locHE; /// Hit, end pos - locHE.SetCoordinates(xyzLocE.X(), xyzLocE.Y(), xyzLocE.Z()); - o2::math_utils::Vector3D locHF; - - int row = 0, col = 0; - float xlc = 0., zlc = 0.; - - if (subDetID == 0) { - Float_t x_flat = 0.f, y_flat = 0.f; - o2::math_utils::Vector2D xyFlatH = seg.curvedToFlat(layer, locH.X(), locH.Y()); - o2::math_utils::Vector2D xyFlatD = seg.curvedToFlat(layer, locD.X(), locD.Y()); - locDF.SetCoordinates(xyFlatD.X(), xyFlatD.Y(), locD.Z()); - locHF.SetCoordinates(xyFlatH.X(), xyFlatH.Y(), locH.Z()); - seg.localToDetector(locHF.X(), locHF.Z(), row, col, subDetID, layer, disk); - } - - else { - seg.localToDetector(locH.X(), locH.Z(), row, col, subDetID, layer, disk); - } - - seg.detectorToLocal(row, col, xlc, zlc, subDetID, layer, disk); - - if (subDetID == 0) { - nt->Fill(chipID, /// detector ID - gloD.X(), gloD.Y(), gloD.Z(), /// global position retrieved from the digit: digit (row, col) ->local position -> global potision - ix, iz, /// row and column of the digit - row, col, /// row and col retrieved from the hit: hit global position -> hit local position -> detector position (row, col) - locH.X(), locH.Z(), /// x and z of the hit in the local reference frame: hit global position -> hit local position - xlc, zlc, /// x and z of the hit in the local frame: hit global position -> hit local position -> detector position (row, col) -> local position - locHF.X() - locDF.X(), locHF.Z() - locDF.Z()); /// difference in x and z between the hit and the digit in the local frame - - nt2->Fill(chipID, gloD.Z(), locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// differences between local hit start and hit end positions - } else { - - nt->Fill(chipID, /// detector ID - gloD.X(), gloD.Y(), gloD.Z(), /// global position retrieved from the digit: digit (row, col) ->local position -> global potision - ix, iz, /// row and column of the digit - row, col, /// row and col retrieved from the hit: hit global position -> hit local position -> detector position (row, col) - locH.X(), locH.Z(), /// x and z of the hit in the local reference frame: hit global position -> hit local position - xlc, zlc, /// x and z of the hit in the local frame: hit global position -> hit local position -> detector position (row, col) -> local position - locH.X() - locD.X(), locH.Z() - locD.Z()); /// difference in x and z between the hit and the digit in the local frame - // locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// difference in x and z between the hit and the digit in the local frame - nt2->Fill(chipID, gloD.Z(), locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// differences between local hit start and hit end positions - } - - } // end loop on digits array - - } // end loop on ROFRecords array + const auto gloD = gman->getMatrixL2G(chipID)(locD); // convert to global + + std::unordered_map* mc2hit = &mc2hitVec[lab.getEventID()]; + + // get MC info + uint64_t key = (uint64_t(trID) << 32) + chipID; + auto hitEntry = mc2hit->find(key); + + if (hitEntry == mc2hit->end()) { + LOG(error) << "Failed to find MC hit entry for Tr" << trID << " chipID" << chipID; + continue; + } + + ////// HITS + Hit& hit = (*hitArray[lab.getEventID()])[hitEntry->second]; + + auto xyzLocE = gman->getMatrixL2G(chipID) ^ (hit.GetPos()); // inverse conversion from global to local + auto xyzLocS = gman->getMatrixL2G(chipID) ^ (hit.GetPosStart()); + + // Hit local reference: Both VD and MLOT use response-plane interpolation (in flat local frame). + // For VD, transform curved → flat first, then interpolate. + o2::math_utils::Vector3D locH; /// Hit reference (at response plane) + o2::math_utils::Vector3D locHS; /// Hit, start pos + locHS.SetCoordinates(xyzLocS.X(), xyzLocS.Y(), xyzLocS.Z()); + o2::math_utils::Vector3D locHE; /// Hit, end pos + locHE.SetCoordinates(xyzLocE.X(), xyzLocE.Y(), xyzLocE.Z()); + o2::math_utils::Vector3D locHF; + + if (subDetID == 0) { + // VD: Interpolate to VD reference plane in flat frame; apply same r to X and Z + auto flatSta = seg.curvedToFlat(layer, locHS.X(), locHS.Y()); + auto flatEnd = seg.curvedToFlat(layer, locHE.X(), locHE.Y()); + float x0 = flatSta.X(), y0 = flatSta.Y(), z0 = locHS.Z(); + float dltx = flatEnd.X() - x0, dlty = flatEnd.Y() - y0, dltz = locHE.Z() - z0; + float r = (std::abs(dlty) > 1e-9f) ? (yPlaneVD - y0) / dlty : 0.5f; + locH.SetCoordinates(x0 + r * dltx, yPlaneVD, z0 + r * dltz); + } else { + // MLOT: Interpolate to response plane + float x0 = locHS.X(), y0 = locHS.Y(), z0 = locHS.Z(); + float dltx = locHE.X() - x0, dlty = locHE.Y() - y0, dltz = locHE.Z() - z0; + float r = (std::abs(dlty) > 1e-9f) ? (yPlaneMLOT - y0) / dlty : 0.5f; + locH.SetCoordinates(x0 + r * dltx, yPlaneMLOT, z0 + r * dltz); + } + + int row = 0, col = 0; + float xlc = 0., zlc = 0.; + + if (subDetID == 0) { + Float_t x_flat = 0.f, y_flat = 0.f; + // locH is already in flat frame from interpolation above; convert digit to flat for comparison + o2::math_utils::Vector2D xyFlatD = seg.curvedToFlat(layer, locD.X(), locD.Y()); + locDF.SetCoordinates(xyFlatD.X(), xyFlatD.Y(), locD.Z()); + locHF.SetCoordinates(locH.X(), locH.Y(), locH.Z()); // locH already in flat frame + seg.localToDetector(locHF.X(), locHF.Z(), row, col, subDetID, layer, disk); + } else { + seg.localToDetector(locH.X(), locH.Z(), row, col, subDetID, layer, disk); + } + + seg.detectorToLocal(row, col, xlc, zlc, subDetID, layer, disk); + + if (subDetID == 0) { + nt->Fill(chipID, /// detector ID + gloD.X(), gloD.Y(), gloD.Z(), /// global position retrieved from the digit: digit (row, col) ->local position -> global potision + ix, iz, /// row and column of the digit + row, col, /// row and col retrieved from the hit: hit global position -> hit local position -> detector position (row, col) + locH.X(), locH.Z(), /// x and z of the hit in the local reference frame: hit global position -> hit local position + xlc, zlc, /// x and z of the hit in the local frame: hit global position -> hit local position -> detector position (row, col) -> local position + locHF.X() - locDF.X(), locHF.Z() - locDF.Z()); /// difference in x and z between the hit and the digit in the local frame + nt2->Fill(chipID, gloD.Z(), locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// differences between local hit start and hit end positions + } else { + nt->Fill(chipID, /// detector ID + gloD.X(), gloD.Y(), gloD.Z(), /// global position retrieved from the digit: digit (row, col) ->local position -> global potision + ix, iz, /// row and column of the digit + row, col, /// row and col retrieved from the hit: hit global position -> hit local position -> detector position (row, col) + locH.X(), locH.Z(), /// x and z of the hit in the local reference frame: hit global position -> hit local position + xlc, zlc, /// x and z of the hit in the local frame: hit global position -> hit local position -> detector position (row, col) -> local position + locH.X() - locD.X(), locH.Z() - locD.Z()); /// difference in x and z between the hit and the digit in the local frame + nt2->Fill(chipID, gloD.Z(), locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// differences between local hit start and hit end positions + } + + } // end loop on digits array + + } // end loop on ROFRecords + + } // end loop on layers // digit maps in the xy and yz planes auto canvXY = new TCanvas("canvXY", "", 1600, 2400); diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C index ae75616b7719c..f7917ca4203f1 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C @@ -9,8 +9,8 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file CheckTracksCA.C -/// \brief Quality assurance macro for TRK tracking +/// \author A. Daribayeva +/// Quality assurance test on reconstructed tracks, producing efficiency plots and performance table #if !defined(__CLING__) || defined(__ROOTCLING__) #include @@ -18,15 +18,24 @@ #include #include #include +#include +#include +#include #include #include -#include +#include +#include #include #include #include #include #include +#include +#include +#include +#include +#include #include "DataFormatsITS/TrackITS.h" #include "SimulationDataFormat/MCCompLabel.h" @@ -41,6 +50,18 @@ using namespace std; using namespace o2; +enum class RangeMode { + ContentOnly, + ContentOrError, + ReferenceContent +}; + +void setAutoXRange(TH1* h, + RangeMode mode = RangeMode::ContentOnly, + const TH1* hRef = nullptr, + double threshold = 0.0, + int marginBins = 1); + /// Structure to track particle hit information struct ParticleHitInfo { std::bitset<11> layerHits; ///< Which layers have hits (11 layers for TRK) @@ -73,25 +94,37 @@ struct ParticleHitInfo { } }; -void CheckTracksCA(std::string tracfile = "o2trac_trk.root", +bool hasConsecutiveLayers(const o2::its::TrackITS& recoTrack, int nClusters) +{ + std::array layers{}; + + for (int i = 0; i < 11; i++) { + layers[i] = recoTrack.hasHitOnLayer(i); + } + + return std::search_n(layers.begin(), layers.end(), nClusters, true) != layers.end(); +} + +void CheckTracksCA(std::string trackfile = "o2trac_trk.root", std::string kinefile = "o2sim_Kine.root", std::string hitsfile = "o2sim_HitsTRK.root", - std::string outputfile = "trk_qa_output.root") + std::string outFile = "RecoPerformanceTable.dat", + std::string outFile1 = "RecoTracksQA.root") { - gStyle->SetOptStat(0); - std::cout << "=== Starting TRK Track Quality Assurance ===" << std::endl; + std::cout << "=== Starting TRK Track Reconstruction Quality Assurance ===" << std::endl; std::cout << "Input files:" << std::endl; - std::cout << " Tracks: " << tracfile << std::endl; + std::cout << " Tracks: " << trackfile << std::endl; std::cout << " Kinematics: " << kinefile << std::endl; std::cout << " Hits: " << hitsfile << std::endl; - std::cout << " Output: " << outputfile << std::endl; - std::cout << std::endl; + std::cout << " Output file with performance table: " << outFile << std::endl; + std::cout << " Output root file with histograms: " << outFile1 << std::endl; + + gROOT->SetBatch(true); // MC kinematics reader o2::steer::MCKinematicsReader kineReader("o2sim", o2::steer::MCKinematicsReader::Mode::kMCKine); const int nEvents = kineReader.getNEvents(0); - std::cout << "Number of MC events: " << nEvents << std::endl; // Open hits file to count hits per particle per layer TFile* hitsFile = TFile::Open(hitsfile.c_str(), "READ"); @@ -99,6 +132,7 @@ void CheckTracksCA(std::string tracfile = "o2trac_trk.root", std::cerr << "ERROR: Cannot open hits file: " << hitsfile << std::endl; return; } + TTree* hitsTree = hitsFile->Get("o2sim"); if (!hitsTree) { std::cerr << "ERROR: Cannot find o2sim tree in hits file" << std::endl; @@ -106,25 +140,19 @@ void CheckTracksCA(std::string tracfile = "o2trac_trk.root", } // Open reconstructed tracks file - TFile* tracFile = TFile::Open(tracfile.c_str(), "READ"); + TFile* tracFile = TFile::Open(trackfile.c_str(), "READ"); if (!tracFile || tracFile->IsZombie()) { - std::cerr << "ERROR: Cannot open tracks file: " << tracfile << std::endl; + std::cerr << "ERROR: Cannot open tracks file: " << trackfile << std::endl; return; } + TTree* recTree = tracFile->Get("o2sim"); if (!recTree) { std::cerr << "ERROR: Cannot find o2sim tree in tracks file" << std::endl; return; } - // Reconstructed tracks and labels - std::vector* recTracks = nullptr; - std::vector* trkLabels = nullptr; - recTree->SetBranchAddress("TRKTrack", &recTracks); - recTree->SetBranchAddress("TRKTrackMCTruth", &trkLabels); - - std::cout << "Reading tracks from tree..." << std::endl; - + // ============== MC part =============================== // Analyze hits tree to count hits per particle per layer std::cout << "Analyzing hits from tree..." << std::endl; std::unordered_map particleHitMap; @@ -133,9 +161,6 @@ void CheckTracksCA(std::string tracfile = "o2trac_trk.root", o2::base::GeometryManager::loadGeometry(); auto* gman = o2::trk::GeometryTGeo::Instance(); - // Array to map detector to starting layer - constexpr std::array startLayer{0, 3}; - std::vector* trkHit = nullptr; hitsTree->SetBranchAddress("TRKHit", &trkHit); @@ -152,8 +177,7 @@ void CheckTracksCA(std::string tracfile = "o2trac_trk.root", } // Determine layer - int subDetID = gman->getSubDetID(hit.GetDetectorID()); - const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); + const int layer = gman->getBarrelLayer(hit.GetDetectorID()); // Create label for this particle o2::MCCompLabel label(hit.GetTrackID(), static_cast(iEntry), 0); @@ -165,81 +189,132 @@ void CheckTracksCA(std::string tracfile = "o2trac_trk.root", std::cout << "Found " << particleHitMap.size() << " unique particles with hits" << std::endl; - // Store particle info and fill generated histograms - std::unordered_map particlePtMap; - - // Create histograms - constexpr int nLayers = 11; - constexpr int nb = 100; - double xbins[nb + 1], ptcutl = 0.05, ptcuth = 10.; - double a = std::log(ptcuth / ptcutl) / nb; - for (int i = 0; i <= nb; i++) - xbins[i] = ptcutl * std::exp(i * a); - - TH1D genParticlePtHist("genParticlePt", "Generated Particle p_{T} (All Layers); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); - TH1D genParticlePt7LayersHist("genParticlePt7Layers", "Generated Particle p_{T} with hits in at least 7 consecutive layers; #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins); - TH1D goodTracks("goodTracks", "Good Tracks; p_{T} (GeV/c); Counts", nb, xbins); - TH1D fakeTracks("fakeTracks", "Fake Tracks; p_{T} (GeV/c); Counts", nb, xbins); - - std::array goodTracksMatching, fakeTracksMatching; - for (int i = 0; i < 5; ++i) { - goodTracksMatching[i] = TH1D(Form("goodTracksMatching_%dLayers", i + 7), - Form("Good Tracks with %d layer hits; p_{T} (GeV/c); Counts", i + 7), - nb, xbins); - fakeTracksMatching[i] = TH1D(Form("fakeTracksMatching_%dLayers", i + 7), - Form("Fake Tracks with %d layer hits; p_{T} (GeV/c); Counts", i + 7), - nb, xbins); - } + //=========== need to set the min and max ranges for hists + std::vector pTDist; + std::unordered_map MCTrackMap; - TH1D numberOfClustersPerTrack("numberOfClustersPerTrack", - "Number of clusters per track; N_{clusters}; Counts", - 12, -0.5, 11.5); + // counters for general statisics + int counterPrimaries{0}, counterSecondaries{0}; - // First pass: identify particles with full hit coverage from kinematics - std::cout << "Analyzing MC particles..." << std::endl; for (int iEvent = 0; iEvent < nEvents; ++iEvent) { const auto& mcTracks = kineReader.getTracks(iEvent); + for (size_t iTrack = 0; iTrack < mcTracks.size(); ++iTrack) { const auto& mcTrack = mcTracks[iTrack]; - if (!mcTrack.isPrimary()) { - continue; - } // Create label for this particle o2::MCCompLabel label(iTrack, iEvent, 0); - float pt = mcTrack.GetPt(); - // Store particle info - particlePtMap[label] = pt; - - // Check if this particle has hits auto hitIt = particleHitMap.find(label); if (hitIt != particleHitMap.end()) { - // Store pT in hit info - hitIt->second.pt = pt; - // Fill histogram for particles with hits in all 11 layers - if (hitIt->second.nHits == 11) { - genParticlePtHist.Fill(pt); + if (mcTrack.isPrimary()) { + counterPrimaries++; } - - // Fill histogram for particles with at least 7 consecutive layer hits - if (hitIt->second.hasConsecutiveLayers(7)) { - genParticlePt7LayersHist.Fill(pt); + if (mcTrack.isSecondary()) { + counterSecondaries++; } + + MCTrackMap.emplace(label, mcTrack); + pTDist.push_back(mcTrack.GetPt()); + } + } + } + + int nBins = 100; + auto [minpT, maxpT] = std::minmax_element(pTDist.begin(), pTDist.end()); + + //=========== histograms ============= + // for exclusive studies + TH1F hPtDenExclusive("", "", nBins, *minpT, *maxpT); + + TH1F hPtNumExclusive("", "", nBins, *minpT, *maxpT); + + TH1F hPtEffExclusive("hPtEffExclusive", + "efficiency (exclusive, good, primaries) vs p_{T}; p_{T} [GeV/c]; Efficiency", + nBins, *minpT, *maxpT); + + // for inclusive studies + TH1F hPtDenInclusive("", "", nBins, *minpT, *maxpT); + + TH1F hPtNumInclusive("", "", nBins, *minpT, *maxpT); + + TH1F hPtEffInclusive("hPtEffInclusive", "", nBins, *minpT, *maxpT); + + // for inclusive studies, fake + TH1F hPtNumInclusiveFake("", "", nBins, *minpT, *maxpT); + TH1F hPtEffInclusiveFake("", "", nBins, *minpT, *maxpT); + + TH2D hPtResVsPt("", "", nBins, *minpT, *maxpT, 100, -0.5, 0.5); + + // for inclusive efficiencies + int counterAll{0}, prim_ge7{0}, sec_ge7{0}; + + // for exclusive studies when we have 7,8,9,10,11 hits + std::array mcExact{}; + + for (const auto& [label, mcTrack] : MCTrackMap) { + + const auto& hitInfo = particleHitMap.at(label); + int nHits = hitInfo.nHits; + + if (nHits < 7 || nHits > 11) { + continue; + } + + float pT = mcTrack.GetPt(); + + bool consecutive7 = hitInfo.hasConsecutiveLayers(7); + + if (mcTrack.isPrimary()) { + + // exclusive - all hits should be on subsequent layers + if (hitInfo.hasConsecutiveLayers(nHits)) { + hPtDenExclusive.Fill(pT); + ++mcExact[nHits]; + } + + // inclusive - it's enough to be on 7 consequtive layers + if (consecutive7) { + hPtDenInclusive.Fill(pT); + ++prim_ge7; + } + + } else if (mcTrack.isSecondary()) { + + if (consecutive7) { + ++sec_ge7; } } } - std::cout << "Generated particles with 11 hits: " << genParticlePtHist.GetEntries() << std::endl; - std::cout << "Generated particles with 7+ consecutive hits: " << genParticlePt7LayersHist.GetEntries() << std::endl; + counterAll = prim_ge7 + sec_ge7; + + //============ reco tracks =============== + + // Reconstructed tracks and labels + std::vector* recTracks = nullptr; + std::vector* trkLabels = nullptr; + std::vector pTResVector; // good, primaries, inclusive + + recTree->SetBranchAddress("TRKTrack", &recTracks); + recTree->SetBranchAddress("TRKTrackMCTruth", &trkLabels); // Second pass: analyze reconstructed tracks std::cout << "Analyzing reconstructed tracks..." << std::endl; int nROFs = recTree->GetEntries(); - int totalTracks = 0; - int goodTracksCount = 0; - int fakeTracksCount = 0; + int totalTracks{0}; + + // inclusive count + std::unordered_set foundAllGood, foundAllFake; + std::unordered_set foundPrimGood, foundPrimFake; + std::unordered_set foundSecGood, foundSecFake; + + // exclusive count + std::array, 12> foundExclusiveGood, foundExclusiveFake; + std::array, 12> foundWithLessClusters; + + int count7RecoGood{0}; for (int iROF = 0; iROF < nROFs; ++iROF) { recTree->GetEntry(iROF); @@ -258,88 +333,316 @@ void CheckTracksCA(std::string tracfile = "o2trac_trk.root", continue; } - int eventID = label.getEventID(); - int trackID = label.getTrackID(); int nClusters = track.getNumberOfClusters(); + if (nClusters < 7 || nClusters > 11) { + continue; + } + + auto key = o2::MCCompLabel(label.getTrackID(), label.getEventID(), 0); + + auto hitIt = particleHitMap.find(key); + auto mcIt = MCTrackMap.find(key); - // Get MC track info - if (eventID < 0 || eventID >= nEvents) { + if (hitIt == particleHitMap.end() || mcIt == MCTrackMap.end()) { continue; } - const auto& mcTracks = kineReader.getTracks(eventID); - if (trackID < 0 || trackID >= (int)mcTracks.size()) { + int nHits = hitIt->second.nHits; + if (nHits < 7 || nHits > 11) { continue; } - float pt = mcTracks[trackID].GetPt(); + bool mcHasN = hitIt->second.hasConsecutiveLayers(nHits); + bool recoHasN = hasConsecutiveLayers(track, nClusters); - // Fill histograms - numberOfClustersPerTrack.Fill(nClusters); + float mcPt = mcIt->second.GetPt(); + float recoPT = track.getPt(); - auto key = o2::MCCompLabel(trackID, eventID, 0); - if (particleHitMap.find(key) != particleHitMap.end() && particleHitMap[key].hasConsecutiveLayers(11)) { + // inclusive count + if (hitIt->second.hasConsecutiveLayers(7) && hasConsecutiveLayers(track, 7)) { + + // for good tracks + if (label.isCorrect()) { + foundAllGood.insert(key); + + if (mcIt->second.isPrimary()) { + foundPrimGood.insert(key); + hPtNumInclusive.Fill(mcPt); + + float ptRes = (recoPT - mcPt) / mcPt; + pTResVector.push_back(ptRes); + hPtResVsPt.Fill(mcPt, ptRes); + + } else if (mcIt->second.isSecondary()) { + foundSecGood.insert(key); + } + } + + // for fake tracks if (label.isFake()) { - fakeTracks.Fill(pt); - fakeTracksCount++; - if (nClusters >= 7 && nClusters <= 11) { - fakeTracksMatching[nClusters - 7].Fill(pt); + foundAllFake.insert(key); + + if (mcIt->second.isPrimary()) { + foundPrimFake.insert(key); + hPtNumInclusiveFake.Fill(mcPt); + } else if (mcIt->second.isSecondary()) { + foundSecFake.insert(key); } - } else { - goodTracks.Fill(pt); - goodTracksCount++; - if (nClusters >= 7 && nClusters <= 11) { - goodTracksMatching[nClusters - 7].Fill(pt); + } + } + + // exclusive count + if (nHits == nClusters && mcHasN && recoHasN) { + + if (mcIt->second.isPrimary()) { + + if (label.isCorrect()) { + + hPtNumExclusive.Fill(mcPt); + foundExclusiveGood[nHits].insert(key); + } + + if (label.isFake()) { + foundExclusiveFake[nHits].insert(key); } } } + + // counting cluster loss + if (mcIt->second.isPrimary() && mcHasN && recoHasN && + label.isCorrect() && + nClusters < nHits) { + + foundWithLessClusters[nHits].insert(key); + } + + } // end loop over reco tracks + } // end loop over RoFs + + // inclusive efficiencies for Good tracks + float effForAllGood = counterAll > 0 ? 100.f * foundAllGood.size() / counterAll : 0.f; + float effForPrimGood = prim_ge7 > 0 ? 100.f * foundPrimGood.size() / prim_ge7 : 0.f; + float effForSecGood = sec_ge7 > 0 ? 100.f * foundSecGood.size() / sec_ge7 : 0.f; + + // inclusive efficiencies for Fake tracks + float effForAllFake = counterAll > 0 ? 100.f * foundAllFake.size() / counterAll : 0.f; + float effForPrimFake = prim_ge7 > 0 ? 100.f * foundPrimFake.size() / prim_ge7 : 0.f; + float effForSecFake = sec_ge7 > 0 ? 100.f * foundSecFake.size() / sec_ge7 : 0.f; + + // exclusive efficiencies for Good and Fake tracks + std::array effExactAllGood{}, effExactAllFake{}; + + for (int n = 7; n <= 11; ++n) { + effExactAllGood[n] = mcExact[n] > 0 ? 100.f * foundExclusiveGood[n].size() / mcExact[n] : 0.f; + effExactAllFake[n] = mcExact[n] > 0 ? 100.f * foundExclusiveFake[n].size() / mcExact[n] : 0.f; + } + + // cluster loss + std::array fracWithLessClusters{}; + for (int n = 7; n <= 11; ++n) { + fracWithLessClusters[n] = mcExact[n] > 0 ? 100.f * foundWithLessClusters[n].size() / mcExact[n] : 0.f; + } + + // pT vs inclusive & exclusive track efficiencies + hPtEffExclusive.Divide(&hPtNumExclusive, &hPtDenExclusive, 1.0, 1.0, "B"); + hPtEffInclusive.Divide(&hPtNumInclusive, &hPtDenInclusive, 1.0, 1.0, "B"); + hPtEffInclusiveFake.Divide(&hPtNumInclusiveFake, &hPtDenInclusive, 1.0, 1.0, "B"); + + // pT resolution for good inclusive tracks, primaries + auto [minPtRes, maxPtRes] = std::minmax_element(pTResVector.begin(), pTResVector.end()); + TH1F pTResolution("pTResolutionForInclusive", "p_{T} resolution; (p_{T}^{rec}-p_{T}^{MC})/p_{T}^{MC}; Counts", nBins, *minPtRes, *maxPtRes); + for (const auto& pTVal : pTResVector) { + pTResolution.Fill(pTVal); + } + pTResolution.Fit("gaus"); + + TObjArray fitSlices; + hPtResVsPt.FitSlicesY(nullptr, 0, -1, 0, "QNR", &fitSlices); + + TH1D* hSigmaVsPt = nullptr; + + if (fitSlices.GetEntries() > 2 && fitSlices.At(2)) { + hSigmaVsPt = dynamic_cast(fitSlices.At(2)->Clone("hSigmaVsPt")); + if (hSigmaVsPt) { + hSigmaVsPt->SetTitle("#sigma(p_{T} resolution) vs p_{T}; p_{T}^{MC} [GeV/c]; #sigma"); + hSigmaVsPt->GetXaxis()->SetRangeUser(0.5, *maxpT); } } - // Create efficiency histograms - std::cout << "Computing efficiencies..." << std::endl; + // Style + hPtEffInclusive.SetLineColor(kBlue + 1); + hPtEffInclusive.SetMarkerColor(kBlue + 1); + hPtEffInclusive.SetMarkerStyle(20); + hPtEffInclusive.SetMarkerSize(1.0); + hPtEffInclusive.SetLineWidth(2); + + hPtEffInclusiveFake.SetLineColor(kRed + 1); + hPtEffInclusiveFake.SetMarkerColor(kRed + 1); + hPtEffInclusiveFake.SetMarkerStyle(24); + hPtEffInclusiveFake.SetMarkerSize(1.0); + hPtEffInclusiveFake.SetLineWidth(2); + + // Titles and axis labels + hPtEffInclusive.SetTitle("Inclusive tracking performance vs p_{T}"); + hPtEffInclusive.GetXaxis()->SetTitle("p_{T} [GeV/c]"); + hPtEffInclusive.GetYaxis()->SetTitle("Rate"); + + // Canvas + TCanvas* cPtEff = new TCanvas("", "", 900, 700); + + setAutoXRange(&hPtEffInclusive, RangeMode::ReferenceContent, &hPtDenInclusive); + setAutoXRange(&hPtEffInclusiveFake, RangeMode::ReferenceContent, &hPtDenInclusive); + + hPtEffInclusive.Draw("E1"); + hPtEffInclusiveFake.Draw("E1 SAME"); + + // Legend + TLegend* leg = new TLegend(0.60, 0.15, 0.88, 0.35); + leg->SetBorderSize(0); + leg->SetFillStyle(0); + leg->AddEntry(&hPtEffInclusive, "Inclusive good efficiency", "lp"); + leg->AddEntry(&hPtEffInclusiveFake, "Inclusive fake rate", "lp"); + leg->Draw("E1 SAME"); + + setAutoXRange(&hPtEffExclusive, RangeMode::ContentOnly); + + // Writing to output Root file + std::cout << "Writing histograms to " << outFile1 << std::endl; + TFile outFileRoot(outFile1.c_str(), "RECREATE"); + if (hSigmaVsPt) { + hSigmaVsPt->Write(); + } + hPtEffExclusive.Write(); + hPtEffInclusive.Write(); + cPtEff->Write(); + pTResolution.Write(); + outFileRoot.Close(); + + // Building performance table + std::cout << "Building performance table ... " << std::endl; + std::ofstream outFileTxt(outFile.c_str()); + outFileTxt << std::fixed << std::setprecision(2); + + outFileTxt << "This is preliminary reconstruction performance table !!" << std::endl; + outFileTxt << "\nGenerated " << particleHitMap.size() << " unique particles with hits" << std::endl; + outFileTxt << "Among them, N primaries: " << counterPrimaries << " and secondaries: " << counterSecondaries << std::endl; + outFileTxt << "Number of total reconstructed tracks: " << totalTracks << std::endl; + + outFileTxt << "\nReconstruction performance table\n\n"; + + outFileTxt << "| " + << std::left << std::setw(20) << "Track category" + << "| " << std::setw(14) << "Efficiency (%)" + << "| " << std::setw(14) << "Fake rate (%)" + << "| " << std::setw(12) << "MC counts" + << " |\n"; + + outFileTxt << std::string(70, '-') << "\n"; + + outFileTxt << "| " + << std::left << std::setw(20) << "All (prim+sec)" + << "| " << std::setw(14) << effForAllGood + << "| " << std::setw(14) << effForAllFake + << "| " << std::setw(12) << counterAll + << " |\n"; + + outFileTxt << "| " + << std::left << std::setw(20) << "Primaries" + << "| " << std::setw(14) << effForPrimGood + << "| " << std::setw(14) << effForPrimFake + << "| " << std::setw(12) << prim_ge7 + << " |\n"; + + outFileTxt << "| " + << std::left << std::setw(20) << "Secondaries" + << "| " << std::setw(14) << effForSecGood + << "| " << std::setw(14) << effForSecFake + << "| " << std::setw(12) << sec_ge7 + << " |\n"; + + outFileTxt << "\n\nExclusive efficiencies for primaries:\n\n"; + + outFileTxt << "| " + << std::left << std::setw(15) << "Track length" + << "| " << std::setw(14) << "Efficiency (%)" + << "| " << std::setw(14) << "Fake rate (%)" + << "| " << std::setw(14) << "Cluster loss (%)" + << "| " << std::setw(14) << "MC counts" + << " |\n"; + + outFileTxt << std::string(85, '-') << "\n"; + + for (int n = 11; n >= 7; --n) { + outFileTxt << "| " + << std::left << std::setw(15) << (std::to_string(n) + "-hit") + << "| " << std::setw(14) << effExactAllGood[n] + << "| " << std::setw(14) << effExactAllFake[n] + << "| " << std::setw(16) << fracWithLessClusters[n] + << "| " << std::setw(14) << mcExact[n] + << " |\n"; + } + + std::cout << "Analysis complete!" << std::endl; - std::array efficiencyHistograms; - THStack* efficiencyStack = new THStack("efficiencyStack", - "Tracking Efficiency; #it{p}_{T} (GeV/#it{c}); Efficiency"); +} // end of macro - int colors[5] = {kRed, kBlue, kGreen + 2, kMagenta, kOrange}; - for (int i = 0; i < 5; ++i) { - int nClusters = i + 7; - efficiencyHistograms[i] = TH1D(Form("efficiency_%dClusters", nClusters), - Form("Efficiency for %d cluster tracks; #it{p}_{T} (GeV/#it{c}); Efficiency", nClusters), - nb, xbins); +void setAutoXRange(TH1* h, RangeMode mode, + const TH1* hRef, + double threshold, + int marginBins) +{ + if (!h) + return; - efficiencyHistograms[i].Divide(&goodTracksMatching[i], &genParticlePtHist, 1, 1, "B"); + const TH1* hScan = h; - efficiencyHistograms[i].SetLineColor(colors[i]); - efficiencyHistograms[i].SetFillColor(colors[i]); - efficiencyHistograms[i].SetLineWidth(2); - efficiencyHistograms[i].SetMarkerColor(colors[i]); - efficiencyHistograms[i].SetMarkerStyle(20 + i); - efficiencyStack->Add(&efficiencyHistograms[i]); + if (mode == RangeMode::ReferenceContent) { + if (!hRef) + return; + hScan = hRef; } - // Write output - std::cout << "Writing output to " << outputfile << std::endl; - TFile outFile(outputfile.c_str(), "RECREATE"); - genParticlePtHist.Write(); - goodTracks.Write(); - fakeTracks.Write(); - for (int i = 0; i < 5; ++i) { - goodTracksMatching[i].Write(); - fakeTracksMatching[i].Write(); - efficiencyHistograms[i].Write(); + const int nBins = hScan->GetNbinsX(); + int first = -1; + int last = -1; + + auto isUsefulBin = [&](int i) -> bool { + const double content = hScan->GetBinContent(i); + const double error = hScan->GetBinError(i); + + switch (mode) { + case RangeMode::ContentOnly: + return content > threshold; + + case RangeMode::ContentOrError: + return (content > threshold) || (error > 0.0); + + case RangeMode::ReferenceContent: + return content > threshold; + } + return false; + }; + + for (int i = 1; i <= nBins; ++i) { + if (isUsefulBin(i)) { + first = i; + break; + } } - efficiencyStack->Write(); - genParticlePt7LayersHist.Write(); - numberOfClustersPerTrack.Write(); - outFile.Close(); - - // Clean up - hitsFile->Close(); - tracFile->Close(); - delete efficiencyStack; - delete hitsFile; - delete tracFile; + + for (int i = nBins; i >= 1; --i) { + if (isUsefulBin(i)) { + last = i; + break; + } + } + + if (first == -1 || last == -1 || first > last) { + return; + } + + first = std::max(1, first - marginBins); + last = std::min(nBins, last + marginBins); + + h->GetXaxis()->SetRange(first, last); } diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/postClusterSizeVsEta.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/postClusterSizeVsEta.C new file mode 100644 index 0000000000000..47beaf36f2957 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/postClusterSizeVsEta.C @@ -0,0 +1,199 @@ +// Copyright 2019-2026 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 postClusterSizeVsEta.C +/// \brief A post-processing macro to draw average cluster size vs eta + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +using namespace std; + +// ### required input file: CheckClusters.root, which is the output of CheckClusters.C macro +void postClusterSizeVsEta(const std::string& strFileInput = "CheckClusters.root") +{ + gStyle->SetOptStat(0); + + TFile* fileInput = new TFile(strFileInput.c_str()); + TTree* tree = (TTree*)fileInput->Get("ntc"); + std::cout << "Opened tree: " << tree->GetName() << ", entries = " << tree->GetEntries() << std::endl; + + // set branch addresses + Float_t event; + Float_t mcTrackID; + Float_t hitLocX, hitLocZ; + Float_t hitGlobX, hitGlobY, hitGlobZ; + Float_t clusGlobX, clusGlobY, clusGlobZ; + Float_t clusLocX, clusLocZ; + Float_t rofFrame; + Float_t clusSize; + Float_t chipID; + Float_t layer; + Float_t disk; + Float_t subdet; + Float_t row, col; + Float_t pt; + + // set branch addresses + tree->SetBranchAddress("event", &event); + tree->SetBranchAddress("mcTrackID", &mcTrackID); + tree->SetBranchAddress("hitLocX", &hitLocX); + tree->SetBranchAddress("hitLocZ", &hitLocZ); + tree->SetBranchAddress("hitGlobX", &hitGlobX); + tree->SetBranchAddress("hitGlobY", &hitGlobY); + tree->SetBranchAddress("hitGlobZ", &hitGlobZ); + tree->SetBranchAddress("clusGlobX", &clusGlobX); + tree->SetBranchAddress("clusGlobY", &clusGlobY); + tree->SetBranchAddress("clusGlobZ", &clusGlobZ); + tree->SetBranchAddress("clusLocX", &clusLocX); + tree->SetBranchAddress("clusLocZ", &clusLocZ); + tree->SetBranchAddress("rofFrame", &rofFrame); + tree->SetBranchAddress("clusSize", &clusSize); + tree->SetBranchAddress("chipID", &chipID); + tree->SetBranchAddress("layer", &layer); + tree->SetBranchAddress("disk", &disk); + tree->SetBranchAddress("subdet", &subdet); + tree->SetBranchAddress("row", &row); + tree->SetBranchAddress("col", &col); + tree->SetBranchAddress("pt", &pt); + + // Some QA histograms + TH1F* hPt = new TH1F("hPt", "p_{T};p_{T};Entries", 100, 0., 10.); + TH1F* hClusSize = new TH1F("hClusSize", "Cluster size;clusSize;Entries", 20, 0., 20.); + TH1F* hLayer = new TH1F("hLayer", "Layer;layer;Entries", 20, -0.5, 19.5); + TH1F* hDxGlob = new TH1F("hDxGlob", "clusGlobX - hitGlobX;#DeltaX [global];Entries", 200, -1., 1.); + TH1F* hDzGlob = new TH1F("hDzGlob", "clusGlobZ - hitGlobZ;#DeltaZ [global];Entries", 200, -1., 1.); + TH2F* hHitXY = new TH2F("hHitXY", "Hit global XY;hitGlobX;hitGlobY", 200, -20., 20., 200, -20., 20.); + TH2F* hClusVsHitX = new TH2F("hClusVsHitX", "clusGlobX vs hitGlobX;hitGlobX;clusGlobX", 200, -20., 20., 200, -20., 20.); + + // histograms for cluster size vs eta for each barrel layer: + const int nLayers = 11; + TH2F* hClustSizePerLayerVsEta[nLayers]; + for (int i = 0; i < nLayers; i++) { + hClustSizePerLayerVsEta[i] = new TH2F(Form("hClustSizePerLayerVsEta_Lay%d", i), Form("Cluster size vs eta for layer %d;#eta;Cluster size", i), 200, -5, 5, 101, -0.5, 100.5); + } + + // Loop over entries + const Long64_t nEntries = tree->GetEntries(); + for (Long64_t i = 0; i < nEntries; ++i) { + tree->GetEntry(i); + + // Fill QA histograms + float dXGlob = clusGlobX - hitGlobX; + float dZGlob = clusGlobZ - hitGlobZ; + hPt->Fill(pt); + hClusSize->Fill(clusSize); + hLayer->Fill(layer); + hDxGlob->Fill(dXGlob); + hDzGlob->Fill(dZGlob); + hHitXY->Fill(hitGlobX, hitGlobY); + hClusVsHitX->Fill(hitGlobX, clusGlobX); + + // cls size vs eta: + float clustR = sqrt(clusGlobX * clusGlobX + clusGlobY * clusGlobY); + float clustPhi = atan2(clusGlobY, clusGlobX); + float clustTheta = atan2(clustR, clusGlobZ); + float clustEta = -log(tan(clustTheta / 2)); + + // !!! important: to avoid VD layers (numeration for ML starts from 0, while VD layers are also numbered as 0,1,2) + if (clustR > 5) // cm + hClustSizePerLayerVsEta[(int)layer + 3]->Fill(clustEta, clusSize); + else if (layer < 3) // VD layers + hClustSizePerLayerVsEta[(int)layer]->Fill(clustEta, clusSize); + + // progress print + if ((i + 1) % 200000 == 0) { + std::cout << "Processed " << (i + 1) << " / " << nEntries << " entries" << std::endl; + } + } + + // Save histograms to file + TFile* fout = TFile::Open("clusterSizes_vs_eta.root", "RECREATE"); + hPt->Write(); + hClusSize->Write(); + hLayer->Write(); + hDxGlob->Write(); + hDzGlob->Write(); + hHitXY->Write(); + hClusVsHitX->Write(); + + // draw some QA histograms + TCanvas* c1 = new TCanvas("canv_clusters_QA", "Clusters QA", 1200, 800); + c1->Divide(2, 2); + c1->cd(1); + hPt->Draw(); + c1->cd(2); + hClusSize->Draw(); + c1->cd(3); + hDxGlob->Draw(); + c1->cd(4); + hHitXY->Draw("COLZ"); + + int colors[] = {kRed, kBlue + 1, kMagenta + 1, + kRed, kBlue + 1, kMagenta + 1, + kCyan + 1, kGray + 2, kRed, kBlue, kMagenta + 1, kCyan, kAzure + 1, kOrange - 9, kRed + 2, kBlue + 2, kMagenta + 2}; + + TCanvas* canv_clsSize_vs_eta[nLayers]; + TProfile* profPerLayerVsEta[nLayers]; + for (int i = 0; i < nLayers; i++) { + canv_clsSize_vs_eta[i] = new TCanvas(Form("canv_clsSize_vs_eta_Lay%d", i), Form("Cluster size vs eta for layer %d", i), 800, 600); + hClustSizePerLayerVsEta[i]->Draw("COLZ"); + gPad->SetLogz(); + profPerLayerVsEta[i] = hClustSizePerLayerVsEta[i]->ProfileX(); + profPerLayerVsEta[i]->SetLineColor(colors[i]); + profPerLayerVsEta[i]->SetMarkerColor(colors[i]); + profPerLayerVsEta[i]->SetMarkerStyle(i < 8 ? 20 : 24); + profPerLayerVsEta[i]->SetTitle(";#eta;#LTcluster size#GT"); + profPerLayerVsEta[i]->DrawCopy("same"); + + hClustSizePerLayerVsEta[i]->Write(); + profPerLayerVsEta[i]->Write(); + } + + // ### canvas with profiles for 3 VD layers + TCanvas* canv_av_clsSize_vs_eta_VD_layers = new TCanvas("canv_clsSize_vs_eta_VD_layers", "Cluster size vs eta for VD layers", 800, 600); + TLegend* legLayersVD = new TLegend(0.3, 0.72, 0.65, 0.89); + for (int i = 0; i < 3; i++) { + profPerLayerVsEta[i]->GetYaxis()->SetRangeUser(0., 60.); + profPerLayerVsEta[i]->DrawCopy(i == 0 ? "P" : "P same"); + legLayersVD->AddEntry(profPerLayerVsEta[i], Form("VD layer %d", i), "P"); + } + legLayersVD->Draw(); + gPad->SetGrid(); + canv_av_clsSize_vs_eta_VD_layers->SaveAs("clsSize_vs_eta_VD_layers.png"); + canv_av_clsSize_vs_eta_VD_layers->Write(); + + // ### canvas with profiles for MLOT layers + TCanvas* canv_av_clsSize_vs_eta_MLOT_layers = new TCanvas("canv_clsSize_vs_eta_MLOT_layers", "Cluster size vs eta for MLOT layers", 800, 600); + TLegend* legLayersMLOT = new TLegend(0.3, 0.52, 0.65, 0.89); + for (int i = 3; i < nLayers; i++) { + profPerLayerVsEta[i]->GetYaxis()->SetRangeUser(0., 12.5); + profPerLayerVsEta[i]->GetXaxis()->SetRangeUser(-3.5, 3.5); + profPerLayerVsEta[i]->DrawCopy(i == 3 ? "P" : "P same"); + legLayersMLOT->AddEntry(profPerLayerVsEta[i], Form("MLOT layer %d", i), "P"); + } + legLayersMLOT->Draw(); + gPad->SetGrid(); + canv_av_clsSize_vs_eta_MLOT_layers->SaveAs("clsSize_vs_eta_MLOT_layers.png"); + canv_av_clsSize_vs_eta_MLOT_layers->Write(); +} \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt index 59a7f47955938..45ce53ba7c3a3 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt @@ -15,41 +15,17 @@ endif() o2_add_library(TRKReconstruction TARGETVARNAME targetName - SOURCES src/TimeFrame.cxx - src/Clusterer.cxx + SOURCES src/Clusterer.cxx $<$:src/ClustererACTS.cxx> - $<$:src/TrackerACTS.cxx> PUBLIC_LINK_LIBRARIES - O2::ITStracking - O2::GPUCommon Microsoft.GSL::GSL - O2::CommonConstants O2::DataFormatsITSMFT O2::DataFormatsTRK O2::SimulationDataFormat - O2::ITSBase - O2::ITSReconstruction - O2::ITSMFTReconstruction - O2::DataFormatsITS - O2::TRKSimulation + O2::TRKBase nlohmann_json::nlohmann_json - ${actsTarget} - PRIVATE_LINK_LIBRARIES - O2::Steer - TBB::tbb) + ${actsTarget}) if(Acts_FOUND) target_compile_definitions(${targetName} PUBLIC O2_WITH_ACTS) endif() - -set(dictHeaders include/TRKReconstruction/TimeFrame.h - include/TRKReconstruction/Clusterer.h) - -if(Acts_FOUND) - list(APPEND dictHeaders include/TRKReconstruction/ClustererACTS.h - include/TRKReconstruction/TrackerACTS.h) -endif() - -o2_target_root_dictionary(TRKReconstruction - HEADERS ${dictHeaders} - LINKDEF src/TRKReconstructionLinkDef.h) diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h index 70518b2ace593..3d30eb5068efe 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h @@ -28,6 +28,7 @@ #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" #include "TRKBase/Specs.h" +#include "MathUtils/Cartesian.h" #include #include #include @@ -171,6 +172,9 @@ class Clusterer gsl::span digMC2ROFs = {}, std::vector* clusterMC2ROFs = nullptr); + static o2::math_utils::Point3D getClusterLocalCoordinates(const Cluster& cluster, const uint8_t* patt, + float yPlaneMLOT = 0.f) noexcept; + protected: int mNHugeClus = 0; std::unique_ptr mThread; diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h index 4111737d17a9f..37a148aa78afb 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h @@ -9,8 +9,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +/// /// \file ClustererACTS.h /// \brief Definition of the TRK cluster finder +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-03-01 +/// #ifndef ALICEO2_TRK_CLUSTERERACTS_H #define ALICEO2_TRK_CLUSTERERACTS_H diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TimeFrame.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TimeFrame.h deleted file mode 100644 index c07767d50b113..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/TimeFrame.h +++ /dev/null @@ -1,69 +0,0 @@ -// 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 TimeFrame.h -/// \brief TRK TimeFrame class derived from ITS TimeFrame -/// - -#ifndef ALICEO2_TRK_TIMEFRAME_H -#define ALICEO2_TRK_TIMEFRAME_H - -#include "ITStracking/TimeFrame.h" -#include "ITStracking/Constants.h" -#include "ITStracking/Configuration.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include -#include -#include -#include - -#include - -class TTree; - -namespace o2 -{ -namespace trk -{ -class GeometryTGeo; - -/// TRK TimeFrame class that extends ITS TimeFrame functionality -/// This allows for customization of tracking algorithms specific to the TRK detector -template -class TimeFrame : public o2::its::TimeFrame -{ - public: - TimeFrame() = default; - ~TimeFrame() override = default; - - /// Override methods if needed for TRK-specific behavior - /// For now, we inherit all functionality from ITS TimeFrame - - /// Process hits from TTree to initialize ROFs - /// \param hitsTree Tree containing TRK hits - /// \param mcHeaderTree Tree containing MC event headers - /// \param nEvents Number of events to process - /// \param gman TRK geometry manager instance - /// \param config Configuration parameters for hit reconstruction - int loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config); - - /// Add primary vertices from MC headers for each ROF - /// \param mcHeaderTree Tree containing MC event headers - /// \param nRofs Number of ROFs (Read-Out Frames) - /// \param nEvents Number of events to process - /// \param inROFpileup Number of events per ROF - void getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup); -}; - -} // namespace trk -} // namespace o2 - -#endif // ALICEO2_TRK_TIMEFRAME_H diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx index bdaa76319c1f2..d60d6900657ba 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx @@ -14,6 +14,7 @@ #include "TRKReconstruction/Clusterer.h" #include "TRKBase/GeometryTGeo.h" +#include "TRKBase/SegmentationChip.h" #include #include @@ -21,6 +22,51 @@ namespace o2::trk { +//__________________________________________________ +o2::math_utils::Point3D Clusterer::getClusterLocalCoordinates(const Cluster& cluster, const uint8_t* patt, + float yPlaneMLOT) noexcept +{ + const uint8_t rowSpan = *patt++; + const uint8_t colSpan = *patt++; + const int nBytes = (rowSpan * colSpan + 7) / 8; + + float cogDr{0.f}, cogDc{0.f}; + int nPix{0}, pixIdx{0}; + for (int ib = 0; ib < nBytes; ib++) { + const uint8_t byte = *patt++; + for (int bit = 7; bit >= 0 && pixIdx < rowSpan * colSpan; bit--, pixIdx++) { + if (byte & (1 << bit)) { + cogDr += pixIdx / colSpan; + cogDc += pixIdx % colSpan; + nPix++; + } + } + } + if (nPix > 1) { + cogDr /= nPix; + cogDc /= nPix; + } + + float x{0.f}, y{0.f}, z{0.f}; + SegmentationChip::detectorToLocalUnchecked(cluster.row, cluster.col, x, z, + cluster.subDetID, cluster.layer, cluster.disk); + + const float pitchRow = (cluster.subDetID == 0) ? SegmentationChip::PitchRowVD : SegmentationChip::PitchRowMLOT; + const float pitchCol = (cluster.subDetID == 0) ? SegmentationChip::PitchColVD : SegmentationChip::PitchColMLOT; + x -= cogDr * pitchRow; + z += cogDc * pitchCol; + + if (cluster.subDetID == 0) { + auto cv = SegmentationChip::flatToCurved(cluster.layer, x, 0.f); + x = cv.X(); + y = cv.Y(); + } else { + y = yPlaneMLOT; + } + + return {x, y, z}; +} + //__________________________________________________ void Clusterer::process(gsl::span digits, gsl::span digitROFs, diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx index 0cf7c26e0ea41..30ab503b7e250 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx @@ -9,8 +9,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +/// /// \file ClustererACTS.cxx /// \brief Implementation of the TRK cluster finder with the ACTS +/// \author Nicolò Jacazio, Università del Piemonte Orientale (IT) +/// \since 2026-03-01 +/// #include "TRKReconstruction/ClustererACTS.h" #include "TRKBase/GeometryTGeo.h" @@ -383,10 +387,10 @@ void ClustererACTS::process(gsl::span digits, outFirst, static_cast(clusters.size()) - outFirst); } - if (clusterMC2ROFs && !digMC2ROFs.empty()) { - clusterMC2ROFs->reserve(clusterMC2ROFs->size() + digMC2ROFs.size()); - for (const auto& in : digMC2ROFs) { - clusterMC2ROFs->emplace_back(in.eventRecordID, in.rofRecordID, in.minROF, in.maxROF); - } - } + // if (clusterMC2ROFs && !digMC2ROFs.empty()) { + // clusterMC2ROFs->reserve(clusterMC2ROFs->size() + digMC2ROFs.size()); + // for (const auto& in : digMC2ROFs) { + // clusterMC2ROFs->emplace_back(in.eventRecordID, in.rofRecordID, in.minROF, in.maxROF); + // } + // } } diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TimeFrame.cxx b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TimeFrame.cxx deleted file mode 100644 index 610a08450d5ee..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/TimeFrame.cxx +++ /dev/null @@ -1,194 +0,0 @@ -// 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 TimeFrame.cxx -/// \brief TRK TimeFrame implementation -/// - -#include "TRKReconstruction/TimeFrame.h" -#include "TRKSimulation/Hit.h" -#include "TRKBase/GeometryTGeo.h" -#include "Framework/Logger.h" -#include "SimulationDataFormat/MCEventHeader.h" -#include -#include -#include -#include - -namespace o2::trk -{ - -template -int TimeFrame::loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config) -{ - constexpr std::array startLayer{0, 3}; - const Long64_t nEvents = hitsTree->GetEntries(); - - gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); - - std::vector* trkHit = nullptr; - hitsTree->SetBranchAddress("TRKHit", &trkHit); - - const int inROFpileup{config.contains("inROFpileup") ? config["inROFpileup"].get() : 1}; - - // Calculate number of ROFs and initialize data structures - this->mNrof = (nEvents + inROFpileup - 1) / inROFpileup; - - // Reset and prepare ROF data structures - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - this->mMinR[iLayer] = std::numeric_limits::max(); - this->mMaxR[iLayer] = std::numeric_limits::lowest(); - this->mROFramesClusters[iLayer].clear(); - this->mROFramesClusters[iLayer].resize(this->mNrof + 1, 0); - this->mUnsortedClusters[iLayer].clear(); - this->mTrackingFrameInfo[iLayer].clear(); - this->mClusterExternalIndices[iLayer].clear(); - } - - // Pre-count hits to reserve memory efficiently - int totalNHits{0}; - std::array clusterCountPerLayer{}; - for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { - hitsTree->GetEntry(iEvent); - for (const auto& hit : *trkHit) { - if (gman->getDisk(hit.GetDetectorID()) != -1) { - continue; // skip non-barrel hits - } - int subDetID = gman->getSubDetID(hit.GetDetectorID()); - const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); - if (layer >= nLayers) { - continue; - } - ++clusterCountPerLayer[layer]; - totalNHits++; - } - } - - // Reserve memory for all layers - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - this->mUnsortedClusters[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mTrackingFrameInfo[iLayer].reserve(clusterCountPerLayer[iLayer]); - this->mClusterExternalIndices[iLayer].reserve(clusterCountPerLayer[iLayer]); - } - clearResizeBoundedVector(this->mClusterSize, totalNHits, this->mMemoryPool.get()); - - std::array resolution{0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004}; - if (config["geometry"]["pitch"].size() == nLayers) { - for (int iLayer{0}; iLayer < config["geometry"]["pitch"].size(); ++iLayer) { - LOGP(info, "Setting resolution for layer {} from config", iLayer); - LOGP(info, "Layer {} pitch {} cm", iLayer, config["geometry"]["pitch"][iLayer].get()); - resolution[iLayer] = config["geometry"]["pitch"][iLayer].get() / std::sqrt(12.f); - } - } - LOGP(info, "Number of active parts in VD: {}", gman->getNumberOfActivePartsVD()); - - int hitCounter{0}; - auto labels = new dataformats::MCTruthContainer(); - - int iRof{0}; // Current ROF index - for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { - hitsTree->GetEntry(iEvent); - - for (auto& hit : *trkHit) { - if (gman->getDisk(hit.GetDetectorID()) != -1) { - continue; // skip non-barrel hits for this test - } - int subDetID = gman->getSubDetID(hit.GetDetectorID()); - const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID()); - - float alpha{0.f}; - o2::math_utils::Point3D gloXYZ; - o2::math_utils::Point3D trkXYZ; - float r{0.f}; - if (layer >= nLayers) { - continue; - } - if (layer >= 3) { - int chipID = hit.GetDetectorID(); - alpha = gman->getSensorRefAlphaMLOT(chipID); - const o2::math_utils::Transform3D& l2g = gman->getMatrixL2G(chipID); - auto locXYZ = l2g ^ (hit.GetPos()); - locXYZ.SetX(locXYZ.X() + gRandom->Gaus(0.0, resolution[layer])); - locXYZ.SetZ(locXYZ.Z() + gRandom->Gaus(0.0, resolution[layer])); - gloXYZ = gman->getMatrixL2G(chipID) * locXYZ; - trkXYZ = gman->getMatrixT2L(chipID - gman->getNumberOfActivePartsVD()) ^ locXYZ; - r = std::hypot(gloXYZ.X(), gloXYZ.Y()); - } else { - const auto& hitPos = hit.GetPos(); - r = std::hypot(hitPos.X(), hitPos.Y()); - alpha = std::atan2(hitPos.Y(), hitPos.X()) + gRandom->Gaus(0.0, resolution[layer] / r); - o2::math_utils::bringTo02Pi(alpha); - gloXYZ.SetX(r * std::cos(alpha)); - gloXYZ.SetY(r * std::sin(alpha)); - gloXYZ.SetZ(hitPos.Z() + gRandom->Gaus(0.0, resolution[layer])); - trkXYZ.SetX(r); - trkXYZ.SetY(0.f); - trkXYZ.SetZ(gloXYZ.Z()); - } - this->mMinR[layer] = std::min(this->mMinR[layer], r); - this->mMaxR[layer] = std::max(this->mMaxR[layer], r); - this->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, - std::array{trkXYZ.y(), trkXYZ.z()}, - std::array{resolution[layer] * resolution[layer], 0., resolution[layer] * resolution[layer]}); - /// Rotate to the global frame - this->addClusterToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), this->mUnsortedClusters[layer].size()); - this->addClusterExternalIndexToLayer(layer, hitCounter); - MCCompLabel label{hit.GetTrackID(), static_cast(iEvent), 0}; - labels->addElement(hitCounter, label); - this->mClusterSize[hitCounter] = 1; // For compatibility with cluster-based tracking, set cluster size to 1 for hits - hitCounter++; - } - trkHit->clear(); - - // Update ROF structure when we complete an ROF or reach the last event - if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { - iRof++; - for (unsigned int iLayer{0}; iLayer < this->mUnsortedClusters.size(); ++iLayer) { - this->mROFramesClusters[iLayer][iRof] = this->mUnsortedClusters[iLayer].size(); // effectively calculating an exclusive sum - } - // Update primary vertices ROF structure - } - this->mClusterLabels = labels; - } - return this->mNrof; -} - -template -void TimeFrame::getPrimaryVerticesFromMC(TTree* mcHeaderTree, int nRofs, Long64_t nEvents, int inROFpileup) -{ - auto mcheader = new o2::dataformats::MCEventHeader; - mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); - - this->mROFramesPV.clear(); - this->mROFramesPV.resize(nRofs + 1, 0); - this->mPrimaryVertices.clear(); - - int iRof{0}; - for (Long64_t iEvent = 0; iEvent < nEvents; ++iEvent) { - mcHeaderTree->GetEntry(iEvent); - o2::its::Vertex vertex; - vertex.setXYZ(mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); - vertex.setNContributors(30); - vertex.setChi2(0.f); - LOGP(debug, "ROF {}: Added primary vertex at ({}, {}, {})", iRof, mcheader->GetX(), mcheader->GetY(), mcheader->GetZ()); - this->mPrimaryVertices.push_back(vertex); - if ((iEvent + 1) % inROFpileup == 0 || iEvent == nEvents - 1) { - iRof++; - this->mROFramesPV[iRof] = this->mPrimaryVertices.size(); // effectively calculating an exclusive sum - } - } - this->mMultiplicityCutMask.resize(nRofs, true); /// all ROFs are valid with MC primary vertices. -} - -// Explicit template instantiation for TRK with 11 layers -template class TimeFrame<11>; - -} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt index 10f117750d793..6d30d8d01bb12 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt @@ -39,4 +39,4 @@ o2_target_root_dictionary(TRKSimulation include/TRKSimulation/VDLayer.h include/TRKSimulation/VDGeometryBuilder.h include/TRKSimulation/VDSensorRegistry.h - include/TRKSimulation/DPLDigitizerParam.h) \ No newline at end of file + include/TRKSimulation/DPLDigitizerParam.h) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h index 168ae172f4b86..de839b27aefee 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h @@ -32,9 +32,9 @@ struct DPLDigitizerParam : public o2::conf::ConfigurableParamHelper + #include #include "ITSMFTSimulation/AlpideSignalTrapezoid.h" #include "ITSMFTSimulation/AlpideSimResponse.h" +#include "TRKBase/AlmiraParam.h" #include "TRKBase/TRKBaseParam.h" #include "TRKBase/GeometryTGeo.h" @@ -50,27 +53,24 @@ class DigiParams void setNoisePerPixel(float v) { mNoisePerPixel = v; } float getNoisePerPixel() const { return mNoisePerPixel; } - void setContinuous(bool v) { mIsContinuous = v; } - bool isContinuous() const { return mIsContinuous; } - - int getROFrameLengthInBC() const { return mROFrameLengthInBC; } - void setROFrameLengthInBC(int n) { mROFrameLengthInBC = n; } + int getROFrameLengthInBC(int layer) const { return mROFrameLayerLengthInBC[layer]; } + void setROFrameLengthInBC(int n, int layer) { mROFrameLayerLengthInBC[layer] = n; } - void setROFrameLength(float ns); - float getROFrameLength() const { return mROFrameLength; } - float getROFrameLengthInv() const { return mROFrameLengthInv; } + void setROFrameLength(float ns, int layer); + float getROFrameLength(int layer) const { return mROFrameLayerLength[layer]; } + float getROFrameLengthInv(int layer) const { return mROFrameLayerLengthInv[layer]; } - void setStrobeDelay(float ns) { mStrobeDelay = ns; } - float getStrobeDelay() const { return mStrobeDelay; } + void setStrobeDelay(float ns, int layer) { mStrobeLayerDelay[layer] = ns; } + float getStrobeDelay(int layer) const { return mStrobeLayerDelay[layer]; } - void setStrobeLength(float ns) { mStrobeLength = ns; } - float getStrobeLength() const { return mStrobeLength; } + void setStrobeLength(float ns, int layer) { mStrobeLayerLength[layer] = ns; } + float getStrobeLength(int layer) const { return mStrobeLayerLength[layer]; } void setTimeOffset(double sec) { mTimeOffset = sec; } double getTimeOffset() const { return mTimeOffset; } - void setROFrameBiasInBC(int n) { mROFrameBiasInBC = n; } - int getROFrameBiasInBC() const { return mROFrameBiasInBC; } + void setROFrameBiasInBC(int n, int layer) { mROFrameLayerBiasInBC[layer] = n; } + int getROFrameBiasInBC(int layer) const { return mROFrameLayerBiasInBC[layer]; } void setChargeThreshold(int v, float frac2Account = 0.1); void setNSimSteps(int v); @@ -92,8 +92,8 @@ class DigiParams bool isTimeOffsetSet() const { return mTimeOffset > -infTime; } - const o2::trk::ChipSimResponse* getAlpSimResponse() const { return mAlpSimResponse.get(); } - void setAlpSimResponse(const o2::itsmft::AlpideSimResponse*); + const o2::trk::ChipSimResponse* getResponse() const { return mResponse.get(); } + void setResponse(const o2::itsmft::AlpideSimResponse*); const SignalShape& getSignalShape() const { return mSignalShape; } SignalShape& getSignalShape() { return (SignalShape&)mSignalShape; } @@ -102,14 +102,8 @@ class DigiParams private: static constexpr double infTime = 1e99; - bool mIsContinuous = false; ///< flag for continuous simulation float mNoisePerPixel = 1.e-7; ///< Noise per chip - int mROFrameLengthInBC = 0; ///< ROF length in BC for continuos mode - float mROFrameLength = 0; ///< length of RO frame in ns - float mStrobeDelay = 0.; ///< strobe start (in ns) wrt ROF start - float mStrobeLength = 0; ///< length of the strobe in ns (sig. over threshold checked in this window only) double mTimeOffset = -2 * infTime; ///< time offset (in seconds!) to calculate ROFrame from hit time - int mROFrameBiasInBC = 0; ///< misalignment of the ROF start in BC int mChargeThreshold = 75; ///< charge threshold in Nelectrons int mMinChargeToAccount = 7; ///< minimum charge contribution to account int mNSimSteps = 475; ///< number of steps in response simulation @@ -121,12 +115,18 @@ class DigiParams float mIBVbb = 0.0; ///< back bias absolute value for ITS Inner Barrel (in Volt) float mOBVbb = 0.0; ///< back bias absolute value for ITS Outter Barrel (in Volt) + std::array mROFrameLayerLengthInBC; ///< staggering ROF length in BC for continuous mode per layer + std::array mROFrameLayerBiasInBC; ///< staggering ROF bias in BC for continuous mode per layer + std::array mROFrameLayerLength; ///< staggering ROF length in ns for continuous mode per layer + std::array mStrobeLayerLength; ///< staggering strobe length in ns per layer + std::array mStrobeLayerDelay; ///< staggering strobe delay in ns per layer + o2::itsmft::AlpideSignalTrapezoid mSignalShape; ///< signal timeshape parameterization - std::unique_ptr mAlpSimResponse; //!< pointer on external response + std::unique_ptr mResponse; //!< pointer on external response // auxiliary precalculated parameters - float mROFrameLengthInv = 0; ///< inverse length of RO frame in ns + std::array mROFrameLayerLengthInv; ///< inverse length of RO frame in ns per layer // ClassDef(DigiParams, 2); }; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h index 362de63fb8cb6..5910fc98134aa 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h @@ -55,18 +55,20 @@ class Digitizer const o2::trk::ChipSimResponse* getChipResponse(int chipID); /// Steer conversion of hits to digits - void process(const std::vector* hits, int evID, int srcID); - void setEventTime(const o2::InteractionTimeRecord& irt); - double getEndTimeOfROFMax() const + void process(const std::vector* hits, int evID, int srcID, int layer); + void setEventTime(const o2::InteractionTimeRecord& irt, int layer); + + void fillOutputContainer(uint32_t maxFrame, int layer); + + void resetROFrameBounds() { - ///< return the time corresponding to end of the last reserved ROFrame : mROFrameMax - return mParams.getROFrameLength() * (mROFrameMax + 1) + mParams.getTimeOffset(); + mROFrameMin = 0; + mROFrameMax = 0; + mNewROFrame = 0; + mIsBeforeFirstRO = false; + mExtraBuff.clear(); } - void setContinuous(bool v) { mParams.setContinuous(v); } - bool isContinuous() const { return mParams.isContinuous(); } - void fillOutputContainer(uint32_t maxFrame = 0xffffffff); - const o2::trk::DigiParams& getDigitParams() const { return mParams; } // provide the common trk::GeometryTGeo to access matrices and segmentation @@ -83,9 +85,9 @@ class Digitizer void setDeadChannelsMap(const o2::itsmft::NoiseMap* mp) { mDeadChanMap = mp; } private: - void processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID); + void processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer); void registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, - uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl); + uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int layer); ExtraDig* getExtraDigBuffer(uint32_t roFrame) { diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h index 88afac8682cf4..402a343ead472 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h @@ -19,7 +19,11 @@ namespace o2::trk { -using Hit = o2::itsmft::Hit; // For now we rely on the same Hit class as ITSMFT, but we can extend it with TRK-specific information if needed in the future +class Hit : public o2::itsmft::Hit +{ + public: + using o2::itsmft::Hit::Hit; // Inherit constructors +}; } // namespace o2::trk #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h index 6077d9e5f9839..e900cfa679ffe 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h @@ -12,11 +12,14 @@ #ifndef ALICEO2_TRK_LAYER_H #define ALICEO2_TRK_LAYER_H +#include "TRKBase/Specs.h" +#include "TRKBase/TRKBaseParam.h" #include + #include -#include "TRKBase/TRKBaseParam.h" -#include "TRKBase/Specs.h" +#include +#include namespace o2 { @@ -68,7 +71,7 @@ class TRKSegmentedLayer : public TRKCylindricalLayer { public: TRKSegmentedLayer() = default; - TRKSegmentedLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); + TRKSegmentedLayer(int layerNumber, std::string layerName, float rInn, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); ~TRKSegmentedLayer() override = default; TGeoVolume* createSensor() override; @@ -80,7 +83,10 @@ class TRKSegmentedLayer : public TRKCylindricalLayer void createLayer(TGeoVolume* motherVolume) override = 0; protected: + float mTiltAngle; int mNumberOfModules; + int mNumberOfStaves; + bool mIsFlipped = false; // Fixed parameters for the layer, to be set based on the specifications of the chip and module static constexpr double sChipWidth = constants::moduleMLOT::chip::width; @@ -93,6 +99,12 @@ class TRKSegmentedLayer : public TRKCylindricalLayer // TGeo objects outside logical volumes can cause errors static constexpr float sLogicalVolumeThickness = 1.3; + // For the segmented layers, because of tilting and staggering the bounding radii can be different + // from the inner radius and inner radius + thickness. + // This function calculates the bounding radii based on the geometry of the stave and the tilt angle, + // to ensure that the layer volume is large enough to contain all the staves without overlaps. + virtual std::pair getBoundingRadii(double staveWidth) const; + ClassDefOverride(TRKSegmentedLayer, 0); }; @@ -100,14 +112,20 @@ class TRKMLLayer : public TRKSegmentedLayer { public: TRKMLLayer() = default; - TRKMLLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); + TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); ~TRKMLLayer() override = default; TGeoVolume* createStave() override; void createLayer(TGeoVolume* motherVolume) override; private: + float mStaggerOffset; + static constexpr double sStaveWidth = constants::ML::width; + static constexpr int sFlippedLayerNumber = 3; + + // Override to account for the staggering offset present in specific ML layers + std::pair getBoundingRadii(double staveWidth) const override; ClassDefOverride(TRKMLLayer, 0); }; @@ -116,21 +134,27 @@ class TRKOTLayer : public TRKSegmentedLayer { public: TRKOTLayer() = default; - TRKOTLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); + TRKOTLayer(int layerNumber, std::string layerName, float rInn, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); ~TRKOTLayer() override = default; TGeoVolume* createStave() override; TGeoVolume* createHalfStave(); void createLayer(TGeoVolume* motherVolume) override; + protected: + static constexpr float sGapBetweenOuterTrackerBarrelHalves = 0.8; // cm, gap between the two halves of the OT barrel + private: static constexpr double sHalfStaveWidth = constants::OT::halfstave::width; static constexpr double sInStaveOverlap = constants::moduleMLOT::gaps::outerEdgeLongSide + constants::moduleMLOT::chip::passiveEdgeReadOut + 0.1; // 1.5mm outer-edge + 1mm deadzone + 1mm (true) overlap static constexpr double sStaveWidth = constants::OT::width - sInStaveOverlap; - ClassDefOverride(TRKOTLayer, 0) + // Override to account for the staggering offset present in OT layers + std::pair getBoundingRadii(double staveWidth) const override; + + ClassDefOverride(TRKOTLayer, 0); }; } // namespace trk } // namespace o2 -#endif // ALICEO2_TRK_LAYER_H \ No newline at end of file +#endif // ALICEO2_TRK_LAYER_H diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h index 79033f48cb0b9..dedbbb096b8e8 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h @@ -21,6 +21,7 @@ // Water bundle disk PU 0,44 19 H2O 0,56 36,08 #include + #include namespace o2 @@ -51,6 +52,7 @@ class TRKServices : public FairModule void createMiddleServices(TGeoVolume* motherVolume); void createOuterDisksServices(TGeoVolume* motherVolume); void createOuterBarrelServices(TGeoVolume* motherVolume); + void createServicesAroundBeamPipe(TGeoVolume* motherVolume); void createMLServicesPeacock(TGeoVolume* motherVolume); void createOTServicesPeacock(TGeoVolume* motherVolume); void createVacuumCompositeShape(); @@ -81,4 +83,4 @@ class TRKServices : public FairModule }; } // namespace trk } // namespace o2 -#endif // O2_TRK_SERVICES_H \ No newline at end of file +#endif // O2_TRK_SERVICES_H diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx index 66c02a080e0b6..196727b2c140f 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx @@ -9,18 +9,20 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include - -#include -#include -#include +#include "TRKSimulation/Detector.h" #include "DetectorsBase/Stack.h" -#include "TRKSimulation/Hit.h" -#include "TRKSimulation/Detector.h" + +#include "TRKBase/Specs.h" #include "TRKBase/TRKBaseParam.h" +#include "TRKSimulation/Hit.h" #include "TRKSimulation/VDGeometryBuilder.h" #include "TRKSimulation/VDSensorRegistry.h" +#include +#include +#include + +#include #include #include @@ -96,23 +98,30 @@ void Detector::configMLOT() switch (trkPars.layoutMLOT) { case kCylindrical: { - const std::vector length{128.35f, 128.35f, 128.35f, 128.35f, 128.35f, 256.7f, 256.7f, 256.7f}; + const std::vector length{127.985f, 127.985f, 127.985f, 127.985f, 127.985f, 255.9f, 255.9f, 255.9f}; LOGP(warning, "Loading cylindrical configuration for ALICE3 TRK"); - for (int i{0}; i < 8; ++i) { + for (int i{0}; i < constants::ML::nLayers + constants::OT::nLayers; ++i) { std::string name = GeometryTGeo::getTRKLayerPattern() + std::to_string(i); mLayers.push_back(std::make_unique(i, name, rInn[i], length[i], thick, MatBudgetParamMode::Thickness)); } break; } case kSegmented: { - const std::vector nMods{10, 10, 10, 10, 10, 20, 20, 20}; + const std::vector tiltAngles{11.2f, 11.9f, 11.4f, 0.f, 0.f, 0.f, 0.f, 0.f}; + // const std::vector tiltAngles{10.f, 16.1f, 19.2f, 0.f, 0.f, 0.f, 0.f, 0.f}; + const std::vector nStaves{10, 14, 18, 26, 38, 32, 42, 56}; + // const std::vector nStaves{10, 16, 22, 26, 38, 32, 42, 56}; + const std::vector nMods{11, 11, 11, 11, 11, 22, 22, 22}; + + const std::vector stagOffsets{0.f, 0.f, 0.f, 1.17f, 0.89f}; + LOGP(warning, "Loading segmented configuration for ALICE3 TRK"); - for (int i{0}; i < 8; ++i) { + for (int i{0}; i < constants::ML::nLayers + constants::OT::nLayers; ++i) { std::string name = GeometryTGeo::getTRKLayerPattern() + std::to_string(i); - if (i < 4) { - mLayers.push_back(std::make_unique(i, name, rInn[i], nMods[i], thick, MatBudgetParamMode::Thickness)); + if (i < constants::ML::nLayers) { + mLayers.push_back(std::make_unique(i, name, rInn[i], stagOffsets[i], tiltAngles[i], nStaves[i], nMods[i], thick, MatBudgetParamMode::Thickness)); } else { - mLayers.push_back(std::make_unique(i, name, rInn[i], nMods[i], thick, MatBudgetParamMode::Thickness)); + mLayers.push_back(std::make_unique(i, name, rInn[i], tiltAngles[i], nStaves[i], nMods[i], thick, MatBudgetParamMode::Thickness)); } } break; @@ -153,16 +162,76 @@ void Detector::configFromFile(std::string fileName) } std::string name = GeometryTGeo::getTRKLayerPattern() + std::to_string(layerCount); + switch (trkPars.layoutMLOT) { - case kCylindrical: - mLayers.push_back(std::make_unique(layerCount, name, tmpBuff[0], tmpBuff[1], tmpBuff[2], MatBudgetParamMode::Thickness)); + case kCylindrical: { + // Expected column mapping in the text file (separated by \t): + // tmpBuff[0] = rInn + // tmpBuff[1] = length + // tmpBuff[2] = thick + // tmpBuff[3] = matBudgetMode (optional, default = Thickness) + + // Cylindrical requires at least 3 parameters + if (tmpBuff.size() < 3) { + LOGP(fatal, "Invalid configuration for cylindrical layer {}: insufficient parameters.", layerCount); + } + + float rInn = tmpBuff[0]; + float length = tmpBuff[1]; + float thick = tmpBuff[2]; + + // Default mode is Thickness + MatBudgetParamMode matBudgetMode = MatBudgetParamMode::Thickness; + if (tmpBuff.size() >= 4) { + matBudgetMode = static_cast(static_cast(tmpBuff[3])); + } + + mLayers.push_back(std::make_unique(layerCount, name, rInn, length, thick, matBudgetMode)); break; + } case kSegmented: { - int nMods = static_cast(tmpBuff[1]); - if (layerCount < 4) { - mLayers.push_back(std::make_unique(layerCount, name, tmpBuff[0], nMods, tmpBuff[2], MatBudgetParamMode::Thickness)); + // Expected column mapping in the text file (separated by \t): + // tmpBuff[0] = rInn + // tmpBuff[1] = thick + // tmpBuff[2] = tiltAngle + // tmpBuff[3] = nStaves + // tmpBuff[4] = nMods + // tmpBuff[5] = stagOffset (required ONLY for ML) + // tmpBuff[6] = matBudgetMode (optional, default = Thickness) + + // Base parameters for all segmented layers (at least 5 needed) + if (tmpBuff.size() < 5) { + LOGP(fatal, "Invalid configuration for segmented layer {}: missing base parameters.", layerCount); + } + + float rInn = tmpBuff[0]; + float thick = tmpBuff[1]; + float tiltAngle = tmpBuff[2]; + int nStaves = static_cast(tmpBuff[3]); + int nMods = static_cast(tmpBuff[4]); + + // Default mode is Thickness + MatBudgetParamMode matBudgetMode = MatBudgetParamMode::Thickness; + + if (layerCount < constants::ML::nLayers) { + // ML layers require stagOffset (index 5) + if (tmpBuff.size() < 6) { + LOGP(fatal, "Invalid configuration for ML layer {}: stagOffset is missing.", layerCount); + } + float stagOffset = tmpBuff[5]; + + if (tmpBuff.size() >= 7) { + matBudgetMode = static_cast(static_cast(tmpBuff[6])); + } + + mLayers.push_back(std::make_unique(layerCount, name, rInn, stagOffset, tiltAngle, nStaves, nMods, thick, matBudgetMode)); } else { - mLayers.push_back(std::make_unique(layerCount, name, tmpBuff[0], nMods, tmpBuff[2], MatBudgetParamMode::Thickness)); + // OT layers do NOT have stagOffset. The optional mode is at index 5. + if (tmpBuff.size() >= 6) { + matBudgetMode = static_cast(static_cast(tmpBuff[5])); + } + + mLayers.push_back(std::make_unique(layerCount, name, rInn, tiltAngle, nStaves, nMods, thick, matBudgetMode)); } break; } diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx index e2a78702204e5..3558a6a87ce71 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx @@ -25,12 +25,12 @@ DigiParams::DigiParams() setNSimSteps(mNSimSteps); } -void DigiParams::setROFrameLength(float lNS) +void DigiParams::setROFrameLength(float lNS, int layer) { // set ROFrame length in nanosecongs - mROFrameLength = lNS; - assert(mROFrameLength > 1.); - mROFrameLengthInv = 1. / mROFrameLength; + mROFrameLayerLength[layer] = lNS; + assert(mROFrameLayerLength[layer] > 1.); + mROFrameLayerLengthInv[layer] = 1. / mROFrameLayerLength[layer]; } void DigiParams::setNSimSteps(int v) @@ -59,10 +59,6 @@ void DigiParams::print() const { // print settings printf("TRK digitization params:\n"); - printf("Continuous readout : %s\n", mIsContinuous ? "ON" : "OFF"); - printf("Readout Frame Length(ns) : %f\n", mROFrameLength); - printf("Strobe delay (ns) : %f\n", mStrobeDelay); - printf("Strobe length (ns) : %f\n", mStrobeLength); printf("Threshold (N electrons) : %d\n", mChargeThreshold); printf("Min N electrons to account : %d\n", mMinChargeToAccount); printf("Number of charge sharing steps : %d\n", mNSimSteps); @@ -72,7 +68,7 @@ void DigiParams::print() const mSignalShape.print(); } -void DigiParams::setAlpSimResponse(const o2::itsmft::AlpideSimResponse* resp) +void DigiParams::setResponse(const o2::itsmft::AlpideSimResponse* resp) { LOG(debug) << "Response function data path: " << resp->getDataPath(); LOG(debug) << "Response function info: "; @@ -80,5 +76,5 @@ void DigiParams::setAlpSimResponse(const o2::itsmft::AlpideSimResponse* resp) if (!resp) { LOGP(fatal, "cannot set response function from null"); } - mAlpSimResponse = std::make_unique(resp); + mResponse = std::make_unique(resp); } diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx index 31ef19a21cce9..890c272fefbc2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx @@ -23,6 +23,7 @@ #include #include #include +#include #include // for LOG using o2::itsmft::Digit; @@ -50,7 +51,7 @@ void Digitizer::init() } // setting the correct response function (for the moment, for both VD and MLOT the same response function is used) - mChipSimResp = mParams.getAlpSimResponse(); + mChipSimResp = mParams.getResponse(); mChipSimRespVD = mChipSimResp; /// for the moment considering the same response mChipSimRespMLOT = mChipSimResp; /// for the moment considering the same response @@ -113,14 +114,13 @@ const o2::trk::ChipSimResponse* Digitizer::getChipResponse(int chipID) }; //_______________________________________________________________________ -void Digitizer::process(const std::vector* hits, int evID, int srcID) +void Digitizer::process(const std::vector* hits, int evID, int srcID, int layer) { // digitize single event, the time must have been set beforehand LOG(info) << " Digitizing " << mGeometry->getName() << " (ID: " << mGeometry->getDetID() << ") hits of event " << evID << " from source " << srcID << " at time " << mEventTime.getTimeNS() << " ROFrame = " << mNewROFrame - << " cont.mode: " << isContinuous() << " Min/Max ROFrames " << mROFrameMin << "/" << mROFrameMax; std::cout << "Printing segmentation info: " << std::endl; @@ -128,7 +128,7 @@ void Digitizer::process(const std::vector* hits, int evID, int srcID) // // is there something to flush ? if (mNewROFrame > mROFrameMin) { - fillOutputContainer(mNewROFrame - 1); // flush out all frames preceding the new one + fillOutputContainer(mNewROFrame - 1, layer); // flush out all frames preceding the new one } int nHits = hits->size(); @@ -140,59 +140,55 @@ void Digitizer::process(const std::vector* hits, int evID, int srcID) return (*hits)[lhs].GetDetectorID() < (*hits)[rhs].GetDetectorID(); }); LOG(info) << "Processing " << nHits << " hits"; - for (int i : hitIdx) { - processHit((*hits)[i], mROFrameMax, evID, srcID); - } - - // in the triggered mode store digits after every MC event - // TODO: in the real triggered mode this will not be needed, this is actually for the - // single event processing only - if (!mParams.isContinuous()) { - fillOutputContainer(mROFrameMax); + for (int i : hitIdx | std::views::filter([&](int idx) { + if (layer < 0) { + return true; + } + return mGeometry->getLayerTRK((*hits)[idx].GetDetectorID()) == layer; + })) { + processHit((*hits)[i], mROFrameMax, evID, srcID, layer); } } //_______________________________________________________________________ -void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt) +void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) { LOG(info) << "Setting event time to " << irt.getTimeNS() << " ns after orbit 0 bc 0"; // assign event time in ns mEventTime = irt; - if (!mParams.isContinuous()) { - mROFrameMin = 0; // in triggered mode reset the frame counters - mROFrameMax = 0; - } // RO frame corresponding to provided time mCollisionTimeWrtROF = mEventTime.timeInBCNS; // in triggered mode the ROF starts at BC (is there a delay?) - if (mParams.isContinuous()) { - auto nbc = mEventTime.differenceInBC(mIRFirstSampledTF); + auto nbc = mEventTime.differenceInBC(mIRFirstSampledTF); - if (mCollisionTimeWrtROF < 0 && nbc > 0) { - nbc--; - } - - mNewROFrame = nbc / mParams.getROFrameLengthInBC(); - - LOG(debug) << " NewROFrame " << mNewROFrame << " = " << nbc << "/" << mParams.getROFrameLengthInBC() << " (nbc/mParams.getROFrameLengthInBC()"; + if (mCollisionTimeWrtROF < 0 && nbc > 0) { + nbc--; + } - // in continuous mode depends on starts of periodic readout frame - mCollisionTimeWrtROF += (nbc % mParams.getROFrameLengthInBC()) * o2::constants::lhc::LHCBunchSpacingNS; - } else { + if (nbc < 0) { mNewROFrame = 0; + mIsBeforeFirstRO = true; + } else { + mNewROFrame = nbc / mParams.getROFrameLengthInBC(layer); + mIsBeforeFirstRO = false; } + LOG(debug) << " NewROFrame " << mNewROFrame << " = " << nbc << "/" << mParams.getROFrameLengthInBC(layer) << " (nbc/mParams.getROFrameLengthInBC()"; + + // in continuous mode depends on starts of periodic readout frame + mCollisionTimeWrtROF += (nbc % mParams.getROFrameLengthInBC(layer)) * o2::constants::lhc::LHCBunchSpacingNS; + if (mNewROFrame < mROFrameMin) { LOG(error) << "New ROFrame " << mNewROFrame << " (" << irt << ") precedes currently cashed " << mROFrameMin; throw std::runtime_error("deduced ROFrame precedes already processed one"); } - if (mParams.isContinuous() && mROFrameMax < mNewROFrame) { + if (mROFrameMax < mNewROFrame) { mROFrameMax = mNewROFrame - 1; // all frames up to this are finished } } //_______________________________________________________________________ -void Digitizer::fillOutputContainer(uint32_t frameLast) +void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) { // // fill output with digits from min.cached up to requested frame, generating the noise beforehand if (frameLast > mROFrameMax) { @@ -212,7 +208,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast) auto& extra = *(mExtraBuff.front().get()); for (auto& chip : mChips) { - if (chip.isDisabled()) { + if (chip.isDisabled() || (layer >= 0 && mGeometry->getLayerTRK(chip.getChipIndex()) != layer)) { continue; } chip.addNoise(mROFrameMin, mROFrameMin, &mParams, mGeometry->getSubDetID(chip.getChipIndex()), mGeometry->getLayer(chip.getChipIndex())); /// TODO: add noise @@ -244,11 +240,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast) } // finalize ROF record rcROF.setNEntries(mDigits->size() - rcROF.getFirstEntry()); // number of digits - if (isContinuous()) { - rcROF.getBCData().setFromLong(mIRFirstSampledTF.toLong() + mROFrameMin * mParams.getROFrameLengthInBC()); - } else { - rcROF.getBCData() = mEventTime; // RSTODO do we need to add trigger delay? - } + rcROF.getBCData().setFromLong(mIRFirstSampledTF.toLong() + mROFrameMin * mParams.getROFrameLengthInBC(layer)); if (mROFRecords) { mROFRecords->push_back(rcROF); } @@ -260,7 +252,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast) } //_______________________________________________________________________ -void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID) +void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer) { int chipID = hit.GetDetectorID(); //// the chip ID at the moment is not referred to the chip but to a wider detector element (e.g. quarter of layer or disk in VD, stave in ML, half stave in OT) int subDetID = mGeometry->getSubDetID(chipID); @@ -290,9 +282,7 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i } return; } - if (isContinuous()) { - timeInROF += mCollisionTimeWrtROF; - } + timeInROF += mCollisionTimeWrtROF; if (mIsBeforeFirstRO && timeInROF < 0) { // disregard this hit because it comes from an event byefore readout starts and it does not effect this RO LOG(debug) << "Ignoring hit with timeInROF = " << timeInROF; @@ -305,9 +295,9 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i } float tTot = mParams.getSignalShape().getMaxDuration(); // frame of the hit signal start wrt event ROFrame - int roFrameRel = int(timeInROF * mParams.getROFrameLengthInv()); + int roFrameRel = int(timeInROF * mParams.getROFrameLengthInv(rofLayer)); // frame of the hit signal end wrt event ROFrame: in the triggered mode we read just 1 frame - uint32_t roFrameRelMax = mParams.isContinuous() ? (timeInROF + tTot) * mParams.getROFrameLengthInv() : roFrameRel; + uint32_t roFrameRelMax = (timeInROF + tTot) * mParams.getROFrameLengthInv(rofLayer); int nFrames = roFrameRelMax + 1 - roFrameRel; uint32_t roFrameMax = mNewROFrame + roFrameRelMax; if (roFrameMax > maxFr) { @@ -502,25 +492,25 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i if (mDeadChanMap && mDeadChanMap->isNoisy(chipID, rowIS, colIS)) { continue; } - registerDigits(chip, roFrameAbs, timeInROF, nFrames, rowIS, colIS, nEle, lbl); + registerDigits(chip, roFrameAbs, timeInROF, nFrames, rowIS, colIS, nEle, lbl, rofLayer); } } } //________________________________________________________________________________ void Digitizer::registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, - uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl) + uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int layer) { // Register digits for given pixel, accounting for the possible signal contribution to // multiple ROFrame. The signal starts at time tInROF wrt the start of provided roFrame // In every ROFrame we check the collected signal during strobe LOG(debug) << "Registering digits for chip " << chip.getChipIndex() << " at ROFrame " << roFrame << " row " << row << " col " << col << " nEle " << nEle << " label " << lbl; - float tStrobe = mParams.getStrobeDelay() - tInROF; // strobe start wrt signal start - for (int i = 0; i < nROF; i++) { // loop on all the ROFs occupied by the same signal to calculate the charge accumulated in that ROF + float tStrobe = mParams.getStrobeDelay(layer) - tInROF; // strobe start wrt signal start + for (int i = 0; i < nROF; i++) { // loop on all the ROFs occupied by the same signal to calculate the charge accumulated in that ROF uint32_t roFr = roFrame + i; - int nEleROF = mParams.getSignalShape().getCollectedCharge(nEle, tStrobe, tStrobe + mParams.getStrobeLength()); - tStrobe += mParams.getROFrameLength(); // for the next ROF + int nEleROF = mParams.getSignalShape().getCollectedCharge(nEle, tStrobe, tStrobe + mParams.getStrobeLength(layer)); + tStrobe += mParams.getROFrameLength(layer); // for the next ROF // discard too small contributions, they have no chance to produce a digit if (nEleROF < mParams.getMinChargeToAccount()) { /// use threshold instead? diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx index 39c7b3598d19b..5206985992ecf 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx @@ -10,17 +10,21 @@ // or submit itself to any jurisdiction. #include "TRKSimulation/TRKLayer.h" -#include "TRKBase/GeometryTGeo.h" -#include "TRKBase/Specs.h" #include "Framework/Logger.h" -#include +#include "TRKBase/GeometryTGeo.h" +#include "TRKBase/Specs.h" #include +#include #include - #include +#include +#include +#include +#include + namespace o2 { namespace trk @@ -84,9 +88,10 @@ void TRKCylindricalLayer::createLayer(TGeoVolume* motherVolume) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -TRKSegmentedLayer::TRKSegmentedLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) - : TRKCylindricalLayer(layerNumber, layerName, rInn, numberOfModules * sModuleLength, thickOrX2X0, mode), mNumberOfModules(numberOfModules) +TRKSegmentedLayer::TRKSegmentedLayer(int layerNumber, std::string layerName, float rInn, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) + : TRKCylindricalLayer(layerNumber, layerName, rInn, numberOfModules * sModuleLength, thickOrX2X0, mode), mTiltAngle(tiltAngle), mNumberOfStaves(numberOfStaves), mNumberOfModules(numberOfModules) { + assert(numberOfStaves % 2 == 0 && "Error: numberOfStaves must be even!"); } TGeoVolume* TRKSegmentedLayer::createSensor() @@ -132,22 +137,29 @@ TGeoVolume* TRKSegmentedLayer::createChip() TGeoVolume* sensVol = createSensor(); TGeoCombiTrans* transSens = new TGeoCombiTrans(); - // transSens->SetTranslation(-sDeadzoneWidth / 2, -(mChipThickness - sSensorThickness) / 2, 0); - transSens->SetTranslation(-sDeadzoneWidth / 2, (mChipThickness - sSensorThickness) / 2, 0); - LOGP(debug, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); - chipVol->AddNode(sensVol, 1, transSens); TGeoVolume* deadVol = createDeadzone(); TGeoCombiTrans* transDead = new TGeoCombiTrans(); - // transDead->SetTranslation((sChipWidth - sDeadzoneWidth) / 2, -(mChipThickness - sSensorThickness) / 2, 0); - transDead->SetTranslation((sChipWidth - sDeadzoneWidth) / 2, (mChipThickness - sSensorThickness) / 2, 0); - LOGP(debug, "Inserting {} in {} ", deadVol->GetName(), chipVol->GetName()); - chipVol->AddNode(deadVol, 1, transDead); TGeoVolume* metalVol = createMetalStack(); TGeoCombiTrans* transMetal = new TGeoCombiTrans(); - // transMetal->SetTranslation(0, sSensorThickness / 2, 0); - transMetal->SetTranslation(0, -sSensorThickness / 2, 0); + + if (!mIsFlipped) { + transSens->SetTranslation(-sDeadzoneWidth / 2, (mChipThickness - sSensorThickness) / 2, 0); + transDead->SetTranslation((sChipWidth - sDeadzoneWidth) / 2, (mChipThickness - sSensorThickness) / 2, 0); + transMetal->SetTranslation(0, -sSensorThickness / 2, 0); + } else { + transSens->SetTranslation(-sDeadzoneWidth / 2, -(mChipThickness - sSensorThickness) / 2, 0); + transDead->SetTranslation((sChipWidth - sDeadzoneWidth) / 2, -(mChipThickness - sSensorThickness) / 2, 0); + transMetal->SetTranslation(0, sSensorThickness / 2, 0); + } + + LOGP(debug, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); + chipVol->AddNode(sensVol, 1, transSens); + + LOGP(debug, "Inserting {} in {} ", deadVol->GetName(), chipVol->GetName()); + chipVol->AddNode(deadVol, 1, transDead); + LOGP(debug, "Inserting {} in {} ", metalVol->GetName(), chipVol->GetName()); chipVol->AddNode(metalVol, 1, transMetal); @@ -186,11 +198,59 @@ TGeoVolume* TRKSegmentedLayer::createModule() return moduleVol; } +std::pair TRKSegmentedLayer::getBoundingRadii(double staveWidth) const +{ + const float avgRadius = 0.5 * (mInnerRadius + mOuterRadius); + const float staveSizeX = staveWidth; + const float staveSizeY = mOuterRadius - mInnerRadius; + + /*const float deltaForTilt = 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); + + float radiusMin = std::sqrt(avgRadius * avgRadius + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY - avgRadius * 2. * deltaForTilt); + float radiusMax = std::sqrt(avgRadius * avgRadius + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY + avgRadius * 2. * deltaForTilt);*/ + + const double alpha = TMath::DegToRad() * std::abs(mTiltAngle); + + // The maximum distance from the center is always the outer top corner + double u_max = avgRadius * std::sin(alpha) + staveSizeX / 2.0; + double v_max = avgRadius * std::cos(alpha) + staveSizeY / 2.0; + double radiusMax = std::sqrt(u_max * u_max + v_max * v_max); + + // The perpendicular distance from the center to the line where the inner face lies + double perpDistance = avgRadius * std::cos(alpha) - staveSizeY / 2.0; + + // The projection of the center along the width of the stave + double projDistance = avgRadius * std::sin(alpha); + + double radiusMin; + if (projDistance <= staveSizeX / 2.0) { + // The center projects directly inside the flat face. + // The closest point is on the face itself, not on the corner + radiusMin = perpDistance; + } else { + // The center projects outside the face. The closest point is the inner corner + double u_min = projDistance - staveSizeX / 2.0; + radiusMin = std::sqrt(u_min * u_min + perpDistance * perpDistance); + } + + // Add a 0.5 mm safety margin to prevent false-positive overlaps in ROOT's geometry checker caused by floating-point inaccuracies + const float precisionMargin = 0.05f; + + return {radiusMin - precisionMargin, radiusMax + precisionMargin}; +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) - : TRKSegmentedLayer(layerNumber, layerName, rInn, numberOfModules, thickOrX2X0, mode) +TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) + : TRKSegmentedLayer(layerNumber, layerName, rInn, tiltAngle, numberOfStaves, numberOfModules, thickOrX2X0, mode), mStaggerOffset(staggerOffset) { + if (mLayerNumber == sFlippedLayerNumber) { + mOuterRadius = rInn; + mInnerRadius = rInn - mChipThickness; + mIsFlipped = true; + mStaggerOffset = -staggerOffset; + LOGP(info, "Layer {} is flipped: sensor and metal stack positions are switched", mLayerNumber); + } } TGeoVolume* TRKMLLayer::createStave() @@ -215,32 +275,43 @@ TGeoVolume* TRKMLLayer::createStave() void TRKMLLayer::createLayer(TGeoVolume* motherVolume) { + // Retrieve exact bounding boundaries and create the logical container volume + auto [rMin, rMax] = getBoundingRadii(sStaveWidth); + TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.333 * sLogicalVolumeThickness, mInnerRadius + 0.667 * sLogicalVolumeThickness, mLength / 2); + // TGeoTube* layer = new TGeoTube(mInnerRadius - 0.333 * sLogicalVolumeThickness, mInnerRadius + 0.667 * sLogicalVolumeThickness, mLength / 2); + TGeoTube* layer = new TGeoTube(rMin, rMax, mLength / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); layerVol->SetLineColor(kYellow); // Compute the number of staves - int nStaves = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / sStaveWidth); - nStaves += nStaves % 2; // Require an even number of staves + // int nStaves = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / sStaveWidth); + // nStaves += nStaves % 2; // Require an even number of staves + + // Nominal average radii used as placement barycenters for the staves + const double avgRadiusInner = 0.5 * (mInnerRadius + mOuterRadius); + const double avgRadiusOuter = avgRadiusInner + mStaggerOffset; // Compute the size of the overlap region - double theta = 2 * TMath::Pi() / nStaves; + double theta = 2. * TMath::Pi() / mNumberOfStaves; double theta1 = std::atan(sStaveWidth / 2 / mInnerRadius); double st = std::sin(theta); double ct = std::cos(theta); double theta2 = std::atan((mInnerRadius * st - sStaveWidth / 2 * ct) / (mInnerRadius * ct + sStaveWidth / 2 * st)); double overlap = (theta1 - theta2) * mInnerRadius; - LOGP(info, "Creating a layer with {} staves and {} mm overlap", nStaves, overlap * 10); + LOGP(info, "Creating a layer with {} staves and {} mm overlap", mNumberOfStaves, overlap * 10); - for (int iStave = 0; iStave < nStaves; iStave++) { + for (int iStave = 0; iStave < mNumberOfStaves; iStave++) { TGeoVolume* staveVol = createStave(); TGeoCombiTrans* trans = new TGeoCombiTrans(); - double theta = 360. * iStave / nStaves; - // TGeoRotation* rot = new TGeoRotation("rot", theta - 90 + 4, 0, 0); - TGeoRotation* rot = new TGeoRotation("rot", theta + 90 + 4, 0, 0); + // If the number of staves is a multiple of 4, rotate by half a stave to avoid having the first one exactly on the x + double phi = (mNumberOfStaves % 4 == 0) ? theta * (iStave + 0.5) : theta * iStave; + double phiDeg = phi * TMath::RadToDeg(); + TGeoRotation* rot = new TGeoRotation("rot", phiDeg + 90 + mTiltAngle, 0, 0); trans->SetRotation(rot); - trans->SetTranslation(mInnerRadius * std::cos(2. * TMath::Pi() * iStave / nStaves), mInnerRadius * std::sin(2 * TMath::Pi() * iStave / nStaves), 0); + // float trueRadius = (mLayerNumber == 3 || mLayerNumber == 4) ? (iStave % 2 == 0 ? mInnerRadius : mInnerRadius + mStaggerOffset) : mInnerRadius; + float trueRadius = (mLayerNumber == 3 || mLayerNumber == 4) ? (iStave % 2 == 0 ? avgRadiusInner : avgRadiusOuter) : avgRadiusInner; + trans->SetTranslation(trueRadius * std::cos(phi), trueRadius * std::sin(phi), 0); LOGP(debug, "Inserting {} in {} ", staveVol->GetName(), layerVol->GetName()); layerVol->AddNode(staveVol, iStave, trans); } @@ -249,10 +320,67 @@ void TRKMLLayer::createLayer(TGeoVolume* motherVolume) motherVolume->AddNode(layerVol, 1, nullptr); } +std::pair TRKMLLayer::getBoundingRadii(double staveWidth) const +{ + // Get the baseline RMin from the base class + auto [defaultRadiusMin, defaultRadiusMax] = TRKSegmentedLayer::getBoundingRadii(staveWidth); + + // If we are not in the staggered layers, return the baseline values + if (mLayerNumber != 3 && mLayerNumber != 4) { + return {defaultRadiusMin, defaultRadiusMax}; + } + + /*// For staggered layers, we must recalculate RMax based on the outer shifted row + const float avgRadiusInner = 0.5 * (mInnerRadius + mOuterRadius); + const float avgRadiusOuter = avgRadiusInner + mStaggerOffset; + + const float staveSizeX = staveWidth; + const float staveSizeY = mOuterRadius - mInnerRadius; + + const float deltaForTiltOuter = 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); + + const float radiusMax = std::sqrt(avgRadiusOuter * avgRadiusOuter + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY + avgRadiusOuter * 2. * deltaForTiltOuter);*/ + + const float avgRadiusInner = 0.5 * (mInnerRadius + mOuterRadius); + const float avgRadiusStaggered = avgRadiusInner + mStaggerOffset; + + const float staveSizeX = staveWidth; + const float staveSizeY = mOuterRadius - mInnerRadius; + const float alpha = TMath::DegToRad() * std::abs(mTiltAngle); + + const float precisionMargin = 0.05f; + + // If the layer is NOT flipped (e.g., Layer 4), the stagger goes outwards + // Therefore, we must recalculate only the maximum radius based on the outer shifted row + if (!mIsFlipped) { + float u_max = avgRadiusStaggered * std::sin(alpha) + staveSizeX / 2.0; + float v_max = avgRadiusStaggered * std::cos(alpha) + staveSizeY / 2.0; + float radiusMax = std::sqrt(u_max * u_max + v_max * v_max); + + return {defaultRadiusMin, radiusMax + precisionMargin}; + } + // If the layer IS flipped (e.g., Layer 3), the stagger goes inwards + // Therefore, we must recalculate only the minimum radius based on the inner shifted row + else { + double perpDistance = avgRadiusStaggered * std::cos(alpha) - staveSizeY / 2.0; + double projDistance = avgRadiusStaggered * std::sin(alpha); + double newRadiusMin; + + if (projDistance <= staveSizeX / 2.0) { + newRadiusMin = perpDistance; + } else { + double u_min = projDistance - staveSizeX / 2.0; + newRadiusMin = std::sqrt(u_min * u_min + perpDistance * perpDistance); + } + + return {newRadiusMin - precisionMargin, defaultRadiusMax}; + } +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -TRKOTLayer::TRKOTLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) - : TRKSegmentedLayer(layerNumber, layerName, rInn, numberOfModules, thickOrX2X0, mode) +TRKOTLayer::TRKOTLayer(int layerNumber, std::string layerName, float rInn, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) + : TRKSegmentedLayer(layerNumber, layerName, rInn, tiltAngle, numberOfStaves, numberOfModules, thickOrX2X0, mode) { } @@ -260,13 +388,15 @@ TGeoVolume* TRKOTLayer::createHalfStave() { TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); std::string halfStaveName = GeometryTGeo::getTRKHalfStavePattern() + std::to_string(mLayerNumber); - TGeoShape* halfStave = new TGeoBBox(sHalfStaveWidth / 2, mChipThickness / 2, mLength / 2); + float lengthHalfBarrel = mLength / 2; + TGeoShape* halfStave = new TGeoBBox(sHalfStaveWidth / 2, mChipThickness / 2, lengthHalfBarrel / 2); TGeoVolume* halfStaveVol = new TGeoVolume(halfStaveName.c_str(), halfStave, medSi); halfStaveVol->SetLineColor(kYellow); - for (int iModule = 0; iModule < mNumberOfModules; iModule++) { + int nModulesPerHalfBarrel = mNumberOfModules / 2; // assuming mNumberOfModules is always even, which should be the case given the current specifications + for (int iModule = 0; iModule < nModulesPerHalfBarrel; iModule++) { TGeoVolume* moduleVol = createModule(); - double zPos = -0.5 * mNumberOfModules * sModuleLength + (iModule + 0.5) * sModuleLength; + double zPos = -0.5 * nModulesPerHalfBarrel * sModuleLength + (iModule + 0.5) * sModuleLength; TGeoCombiTrans* trans = new TGeoCombiTrans(); trans->SetTranslation(0, 0, zPos); LOGP(debug, "Inserting {} in {} ", moduleVol->GetName(), halfStaveVol->GetName()); @@ -298,32 +428,51 @@ TGeoVolume* TRKOTLayer::createStave() void TRKOTLayer::createLayer(TGeoVolume* motherVolume) { + // Retrieve exact bounding boundaries automatically inherited from TRKSegmentedLayer + auto [rMin, rMax] = getBoundingRadii(sStaveWidth); + TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.333 * sLogicalVolumeThickness, mInnerRadius + 0.667 * sLogicalVolumeThickness, mLength / 2); + // TGeoTube* layer = new TGeoTube(mInnerRadius - 0.333 * sLogicalVolumeThickness, mInnerRadius + 0.667 * sLogicalVolumeThickness, mLength / 2); + TGeoTube* layer = new TGeoTube(rMin, rMax, (mLength + sGapBetweenOuterTrackerBarrelHalves) / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); layerVol->SetLineColor(kYellow); // Compute the number of staves - int nStaves = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / sStaveWidth); - nStaves += nStaves % 2; // Require an even number of staves + int nStavesHalfBarrel = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / sStaveWidth); + nStavesHalfBarrel += nStavesHalfBarrel % 2; // Require an even number of staves + + // Nominal average radius used as the placement barycenter for all staves + const double avgRadius = 0.5 * (mInnerRadius + mOuterRadius); // Compute the size of the overlap region - double theta = 2 * TMath::Pi() / nStaves; + double theta = 2. * TMath::Pi() / nStavesHalfBarrel; double theta1 = std::atan(sStaveWidth / 2 / mInnerRadius); double st = std::sin(theta); double ct = std::cos(theta); double theta2 = std::atan((mInnerRadius * st - sStaveWidth / 2 * ct) / (mInnerRadius * ct + sStaveWidth / 2 * st)); double overlap = (theta1 - theta2) * mInnerRadius; - LOGP(info, "Creating a layer with {} staves and {} mm overlap", nStaves, overlap * 10); + LOGP(info, "Creating a layer with two half barrels, each with {} staves and {} mm overlap", nStavesHalfBarrel, overlap * 10); + + float lengthHalfBarrel = mLength / 2; + int nStaves = nStavesHalfBarrel * 2; // since we now have two half-barrels (separated by a small gap), we double the number of staves for (int iStave = 0; iStave < nStaves; iStave++) { TGeoVolume* staveVol = createStave(); + int whichHalfBarrel = iStave / nStavesHalfBarrel; // 0 for the first half (negative z), 1 for the second half (positive z) TGeoCombiTrans* trans = new TGeoCombiTrans(); - double theta = 360. * iStave / nStaves; - // TGeoRotation* rot = new TGeoRotation("rot", theta - 90, 0, 0); - TGeoRotation* rot = new TGeoRotation("rot", theta + 90, 0, 0); + double phi = theta * iStave; + double phiDeg = phi * TMath::RadToDeg(); + // TGeoRotation* rot = new TGeoRotation("rot", phiDeg + 90 + mTiltAngle, 0, 0); + TGeoRotation* rot = new TGeoRotation("rot"); + if (whichHalfBarrel == 1) { + rot->RotateY(180.); // degrees, rotate the second half barrel by 180 degrees around Y to achieve the correct staggering orientation + } + rot->RotateZ(phiDeg + 90 + (whichHalfBarrel == 0 ? +1 : -1) * mTiltAngle); // phi in degrees, tilting depends on the half-barrel side trans->SetRotation(rot); - trans->SetTranslation(mInnerRadius * std::cos(2. * TMath::Pi() * iStave / nStaves), mInnerRadius * std::sin(2 * TMath::Pi() * iStave / nStaves), 0); + // trans->SetTranslation(mInnerRadius * std::cos(phi), mInnerRadius * std::sin(phi), 0); + // trans->SetTranslation(avgRadius * std::cos(phi), avgRadius * std::sin(phi), 0); + double zPos = (whichHalfBarrel == 0 ? -1 : 1) * (0.5 * lengthHalfBarrel + sGapBetweenOuterTrackerBarrelHalves / 2); + trans->SetTranslation(avgRadius * std::cos(phi), avgRadius * std::sin(phi), zPos); LOGP(debug, "Inserting {} in {} ", staveVol->GetName(), layerVol->GetName()); layerVol->AddNode(staveVol, iStave, trans); } @@ -331,7 +480,14 @@ void TRKOTLayer::createLayer(TGeoVolume* motherVolume) LOGP(debug, "Inserting {} in {} ", layerVol->GetName(), motherVolume->GetName()); motherVolume->AddNode(layerVol, 1, nullptr); } + +std::pair TRKOTLayer::getBoundingRadii(double staveWidth) const +{ + auto [radiusMin, radiusMax] = TRKSegmentedLayer::getBoundingRadii(staveWidth); + + return {radiusMin - 0.201f, radiusMax}; +} // ClassImp(TRKLayer); } // namespace trk -} // namespace o2 \ No newline at end of file +} // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx index 0394c59780141..53ac0a4b12865 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx @@ -9,22 +9,23 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include #include +#include + +#include +#include +#include +#include +#include #include #include +#include + #include -#include -#include -#include -#include -#include -#include -#include -#include +#include -using std::string; +#include namespace o2 { @@ -108,7 +109,7 @@ void TRKServices::createMaterials() matmgr.Material("ALICE3_TRKSERVICES", 73, "BERYLLIUM", 9.01, 4., 1.848, 35.3, 36.7); // Beryllium - Candidate for IRIS vacuum vessel matmgr.Mixture("ALICE3_TRKSERVICES", 74, "ALUMINIUM5083", aAl5083, zAl5083, dAl5083, 9, wAl5083); // AL5083 - Candidate for IRIS vacuum vessel matmgr.Mixture("ALICE3_TRKSERVICES", 75, "ALUMINIUMBERYLLIUMMETAL", aAlBeMet, zAlBeMet, dAlBeMet, 2, wAlBeMet); // Aluminium-Beryllium metal - Candidate for IRIS vacuum vessel - matmgr.Material("ALICE3_TRKSERVICES", 76, "CARBONFIBERM55J6K", 12.0107, 6, 1.92, 999, 999); // Carbon Fiber M55J + matmgr.Material("ALICE3_TRKSERVICES", 76, "CARBONFIBERM55J6K", 12.0107, 6, 1.92, 22.4, 45.4); // Carbon Fiber M55J matmgr.Mixture("ALICE3_PIPE", 77, "VACUUM", aAir, zAir, dAir1, 4, wAir); matmgr.Medium("ALICE3_TRKSERVICES", 1, "CERAMIC", 66, 0, ifield, fieldm, tmaxfd, stemax, deemax, epsil, stmin); // Ceramic for cold plate @@ -127,18 +128,24 @@ void TRKServices::createMaterials() void TRKServices::createServices(TGeoVolume* motherVolume) { + + TGeoVolumeAssembly* vol = new TGeoVolumeAssembly(GeometryTGeo::getTRKServiceVolPattern()); + motherVolume->AddNode(vol, 2, new TGeoTranslation(0, 0., 0)); createMaterials(); createVacuumCompositeShape(); auto& trkPars = TRKBaseParam::Instance(); if (trkPars.getLayoutSRV() == kLOISymm) { LOGP(info, "TRK services: LoI version"); - createMiddleServices(motherVolume); - createOuterDisksServices(motherVolume); - createOuterBarrelServices(motherVolume); + createMiddleServices(vol); + createOuterDisksServices(vol); + createOuterBarrelServices(vol); } else { LOGP(info, "TRK services: Peacock layout"); - createMLServicesPeacock(motherVolume); - createOTServicesPeacock(motherVolume); + if (trkPars.includeLowServices) { + createServicesAroundBeamPipe(vol); + } + createMLServicesPeacock(vol); + createOTServicesPeacock(vol); } } @@ -184,7 +191,7 @@ void TRKServices::registerVacuum(TGeoVolume* motherVolume) TGeoVolume* vacuumVolume = new TGeoVolume("A3IP_VACUUM", vacuumComposite, kMedVac); // Add the vacuum to the barrel - vacuumVolume->SetLineColor(kAzure + 7); + vacuumVolume->SetLineColor(kAzure + 6); vacuumVolume->SetTransparency(80); motherVolume->AddNode(vacuumVolume, 1, new TGeoTranslation(0, 0, 0)); @@ -518,9 +525,63 @@ void TRKServices::createOuterBarrelServices(TGeoVolume* motherVolume) motherVolume->AddNode(outerBarrelCoolingH2OVolume, 1, nullptr); } +void TRKServices::createServicesAroundBeamPipe(TGeoVolume* motherVolume) +{ + // This method hardcodes the shape for the low services around the beam pipe + auto& matmgr = o2::base::MaterialManager::Instance(); + + TGeoMedium* medCu = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_COPPER"); + + const float tolleranceLowServices = 0.3f; + + // Low services start longitudinally from middle barrel on the C side, while from the middle barrel connection disks on the A side + const float zStartASideFirstBlock = 65.265f + tolleranceLowServices; + const float zStartCSideFirstBlock = 64.5f + tolleranceLowServices; + const float zStartSecondBlock = 150.f; + const float zStartThirdBlock = 365.f; + const float zEndThirdBlock = 400.f; + + // Low services start radially from IRIS out-vacuum services on the A side, while from beam pipe on the C side + const float rInASide = 3.333f + tolleranceLowServices; + const float rInCSide = 5.6f + tolleranceLowServices; + + // Low services end radially at the disks inners radius + const float rOutFirstBlock = 10.f - tolleranceLowServices; + const float rOutSecondBlock = 20.f - tolleranceLowServices; + const float rOutThirdBlock = 15.f - tolleranceLowServices; + + for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { + std::string orLabel = orientation == Orientation::kASide ? "A" : "C"; + + float zStartLowServices = orientation == Orientation::kASide ? zStartASideFirstBlock : zStartCSideFirstBlock; + float rInLowServices = orientation == Orientation::kASide ? rInASide : rInCSide; + + TGeoTube* lowServicesFirstBlock = new TGeoTube(Form("TRK_LOWSERVICES_FIRSTBLOCKsh_%s", orLabel.c_str()), rInLowServices, rOutFirstBlock, (zStartSecondBlock - zStartLowServices) / 2.); + TGeoVolume* lowServicesFirstBlockVolume = new TGeoVolume(Form("TRK_LOWSERVICES_FIRSTBLOCK_%s", orLabel.c_str()), lowServicesFirstBlock, medCu); + lowServicesFirstBlockVolume->SetLineColor(kGray); + + TGeoTube* lowServicesSecondBlock = new TGeoTube(Form("TRK_LOWSERVICES_SECONDBLOCKsh_%s", orLabel.c_str()), rInLowServices, rOutSecondBlock, (zStartThirdBlock - zStartSecondBlock) / 2.); + TGeoVolume* lowServicesSecondBlockVolume = new TGeoVolume(Form("TRK_LOWSERVICES_SECONDBLOCK_%s", orLabel.c_str()), lowServicesSecondBlock, medCu); + lowServicesSecondBlockVolume->SetLineColor(kGray); + + TGeoTube* lowServicesThirdBlock = new TGeoTube(Form("TRK_LOWSERVICES_THIRDBLOCKsh_%s", orLabel.c_str()), rInLowServices, rOutThirdBlock, (zEndThirdBlock - zStartThirdBlock) / 2.); + TGeoVolume* lowServicesThirdBlockVolume = new TGeoVolume(Form("TRK_LOWSERVICES_THIRDBLOCK_%s", orLabel.c_str()), lowServicesThirdBlock, medCu); + lowServicesThirdBlockVolume->SetLineColor(kGray); + + auto* rot = new TGeoRotation("", 0, 0, 180); + auto* combiTransFirstBlock = new TGeoCombiTrans(0, 0, (int)orientation * (zStartLowServices + (zStartSecondBlock - zStartLowServices) / 2.), rot); + auto* combiTransSecondBlock = new TGeoCombiTrans(0, 0, (int)orientation * (zStartSecondBlock + (zStartThirdBlock - zStartSecondBlock) / 2.), rot); + auto* combiTransThirdBlock = new TGeoCombiTrans(0, 0, (int)orientation * (zStartThirdBlock + (zEndThirdBlock - zStartThirdBlock) / 2.), rot); + + motherVolume->AddNode(lowServicesFirstBlockVolume, 1, combiTransFirstBlock); + motherVolume->AddNode(lowServicesSecondBlockVolume, 1, combiTransSecondBlock); + motherVolume->AddNode(lowServicesThirdBlockVolume, 1, combiTransThirdBlock); + } +} + void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) { - // This method hardcoes the yellow shape for the middle services + // This method hardcodes the yellow shape for the middle services auto& matmgr = o2::base::MaterialManager::Instance(); TGeoMedium* medSiO2 = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_SILICONDIOXIDE"); @@ -553,9 +614,10 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) float pePowerAreaD = ITDisknPower * mPowerBundleArea * mPowerBundleComposition[1]; // Carbon Fiber Cylinder support for the middle tracker - float rMinMiddleCarbonSupport = 34.8f; // Arbitrary value - float rMaxMiddleCarbonSupport = 35.f; // 2 mm of carbon fiber - const float zLengthMiddleCarbon = 129.f; + // (from ICD_ALICE3_V3.b.3 drawing: 38.5 cm are allocated for staves and services, + 1 cm for the support; we assume less for the support - to be reconsidered if necessary) + float rMinMiddleCarbonSupport = 39.3f; // cm + float rMaxMiddleCarbonSupport = 39.5f; // cm, assume 2 mm of carbon fiber, ~0.88% X/X0 + const float zLengthMiddleCarbon = 282.f; // cm, to cover the full length of ML barrel and disks, from Corrado's drawing TGeoTube* middleBarrelCarbonSupport = new TGeoTube("TRK_MID_CARBONSUPPORTsh", rMinMiddleCarbonSupport, rMaxMiddleCarbonSupport, zLengthMiddleCarbon / 2.); TGeoVolume* middleBarrelCarbonSupportVolume = new TGeoVolume("TRK_MID_CARBONSUPPORT", middleBarrelCarbonSupport, medCFiber); middleBarrelCarbonSupportVolume->SetLineColor(kGray); @@ -563,27 +625,28 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) motherVolume->AddNode(middleBarrelCarbonSupportVolume, 1, nullptr); // Get geometry information from TRK which is already present - float rMinMiddleServices = 35.f; - float rMinMiddleBarrel = rMinMiddleServices; - const float zLengthCylinderMiddleServices = 40.5f; - const float zLengthMiddleServices = 143.f; + float rMinMiddleServices = 38.0f; // cm, start radius of the ML services = maximum radius allowed for sensors (35 cm), plus some margin for disk paving with modules + const float zMiddleServicesBarrel = 64.5f; // cm, z position of the first barrel ML service disk + const float zMiddleServicesBarrelFwdConnection = 143.f; // cm, z position of barrel to forward connection services + const float zLengthCylinderMiddleServicesBarrel = zMiddleServicesBarrelFwdConnection - zMiddleServicesBarrel; + + const float zStartServicesForMiddleDisks = 77.0f; // cm, starting z position of ML disk services, assumed to be the same as of the first ML disk + const float zLengthCylinderMiddleServicesDisk = zMiddleServicesBarrelFwdConnection - zStartServicesForMiddleDisks; // Middle layer barrel services are only on A side - rMinMiddleServices = 35.f; - LOGP(info, "Building services for Middle Tracker rminMiddleServices"); + LOGP(info, "Building services for barrel Middle Layers"); // Middle barrel connection disks const float rMinMiddleBarrelDisk = 5.68f; - const float rMaxMiddleBarrelDisk = 35.f; - const float zLengthMiddleBarrel = 64.5f; + const float rMaxMiddleBarrelDisk = rMinMiddleServices; auto orientation = Orientation::kASide; float diskCircumference = rMaxMiddleBarrelDisk * 3.14; // Use only half circumference - double zCur = zLengthMiddleBarrel; + double zCur = zMiddleServicesBarrel; double dZ = siO2FiberAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskSIO2 = new TGeoTube("TRK_MIDBARCONN_DISK_FIBER_SIO2sh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskSIO2Volume = new TGeoVolume("TRK_MIDBARCONN_DISK_FIBER_SIO2", middleBarrelConnDiskSIO2, medSiO2); - middleBarrelConnDiskSIO2Volume->SetLineColor(kGray); + middleBarrelConnDiskSIO2Volume->SetLineColor(kOrange - 9); auto* rot = new TGeoRotation("", 0, 0, 180); // Why this? auto* combiTransSIO2 = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); @@ -591,7 +654,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) dZ = peFiberAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskPE = new TGeoTube("TRK_MIDBARCONN_DISK_FIBER_PEsh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskPEVolume = new TGeoVolume("TRK_MIDBARCONN_DISK_FIBER_PE", middleBarrelConnDiskPE, medPE); - middleBarrelConnDiskPEVolume->SetLineColor(kGray); + middleBarrelConnDiskPEVolume->SetLineColor(kOrange - 9); auto* combiTransPE = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); motherVolume->AddNode(middleBarrelConnDiskSIO2Volume, 1, combiTransSIO2); @@ -601,14 +664,14 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) dZ = cuPowerAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskCu = new TGeoTube("TRK_MIDBARCONN_DISK_POWER_CUsh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskCuVolume = new TGeoVolume("TRK_MIDBARCONN_DISK_POWER_CU", middleBarrelConnDiskCu, medCu); - middleBarrelConnDiskCuVolume->SetLineColor(kGray); + middleBarrelConnDiskCuVolume->SetLineColor(kOrange - 9); auto* combiTransCu = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); zCur += 2. * dZ; dZ = pePowerAreaB / diskCircumference / 2.; TGeoTube* middleBarrelConnDiskPEPower = new TGeoTube("TRK_MIDBARCONN_DISK_POWER_PEsh", rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, dZ); TGeoVolume* middleBarrelConnDiskPEPowerVolume = new TGeoVolume("TRK_MIDBARCONN_DISK_POWER_PE", middleBarrelConnDiskPEPower, medPE); - middleBarrelConnDiskPEPowerVolume->SetLineColor(kGray); + middleBarrelConnDiskPEPowerVolume->SetLineColor(kOrange - 9); auto* combiTransPEPower = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); motherVolume->AddNode(middleBarrelConnDiskCuVolume, 1, combiTransCu); motherVolume->AddNode(middleBarrelConnDiskPEPowerVolume, 1, combiTransPEPower); @@ -616,7 +679,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { for (int iSide = 0; iSide < 2; iSide++) { // left/right or top/bottom float refAngle = 0; - string orLabel("A"); + std::string orLabel("A"); if (orientation == Orientation::kCSide) { orLabel = "C"; refAngle = 90; @@ -625,31 +688,31 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) // create data fiber volumes double rCur = rMinMiddleServices; double dR = siO2FiberAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLD_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleDiskFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLD_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesDisk / 2, -45, 45); TGeoVolume* middleDiskFiberSIO2Volume = new TGeoVolume(Form("TRK_MLD_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), middleDiskFiberSIO2, medSiO2); - middleDiskFiberSIO2Volume->SetLineColor(kGray); + middleDiskFiberSIO2Volume->SetLineColor(kOrange + 1); rCur += dR; dR = peFiberAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskFiberPE = new TGeoTubeSeg(Form("TRK_MLD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleDiskFiberPE = new TGeoTubeSeg(Form("TRK_MLD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesDisk / 2, -45, 45); TGeoVolume* middleDiskFiberPEVolume = new TGeoVolume(Form("TRK_MLD_FIBER_PE_%s%d", orLabel.c_str(), iSide), middleDiskFiberPE, medPE); - middleDiskFiberPEVolume->SetLineColor(kGray); - auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zLengthMiddleServices - zLengthCylinderMiddleServices), new TGeoRotation("", refAngle + iSide * 180., 0, 0)); + middleDiskFiberPEVolume->SetLineColor(kOrange + 1); + auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zMiddleServicesBarrelFwdConnection - zLengthCylinderMiddleServicesDisk / 2), new TGeoRotation("", refAngle + iSide * 180., 0, 0)); motherVolume->AddNode(middleDiskFiberSIO2Volume, 1, combiTrans); motherVolume->AddNode(middleDiskFiberPEVolume, 1, combiTrans); // Create powerlines rCur += dR; dR = cuPowerAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskPowerCu = new TGeoTubeSeg(Form("TRK_MLD_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleDiskPowerCu = new TGeoTubeSeg(Form("TRK_MLD_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesDisk / 2, -45, 45); TGeoVolume* middleDiskPowerCuVolume = new TGeoVolume(Form("TRK_MLD_POWER_CU_%s%d", orLabel.c_str(), iSide), middleDiskPowerCu, medCu); - middleDiskPowerCuVolume->SetLineColor(kGray); + middleDiskPowerCuVolume->SetLineColor(kOrange + 1); rCur += dR; dR = pePowerAreaD / (3.14 * rCur); - TGeoTubeSeg* middleDiskPowerPE = new TGeoTubeSeg(Form("TRK_MLD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleDiskPowerPE = new TGeoTubeSeg(Form("TRK_MLD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesDisk / 2, -45, 45); TGeoVolume* middleDiskPowerPEVolume = new TGeoVolume(Form("TRK_MLD_POWER_PE_%s%d", orLabel.c_str(), iSide), middleDiskPowerPE, medPE); - middleDiskPowerPEVolume->SetLineColor(kGray); + middleDiskPowerPEVolume->SetLineColor(kOrange + 1); motherVolume->AddNode(middleDiskPowerCuVolume, 1, combiTrans); motherVolume->AddNode(middleDiskPowerPEVolume, 1, combiTrans); @@ -659,31 +722,31 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) // create data fiber volumes rCur += dR; dR = siO2FiberAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLB_FIBER_SIO2sh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleBarrelFiberSIO2 = new TGeoTubeSeg(Form("TRK_MLB_FIBER_SIO2sh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); TGeoVolume* middleBarrelFiberSIO2Volume = new TGeoVolume(Form("TRK_MLB_FIBER_SIO2_A%d", iSide), middleBarrelFiberSIO2, medSiO2); - middleBarrelFiberSIO2Volume->SetLineColor(kGray); + middleBarrelFiberSIO2Volume->SetLineColor(kOrange - 9); rCur += dR; dR = peFiberAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelFiberPE = new TGeoTubeSeg(Form("TRK_MLB_FIBER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleBarrelFiberPE = new TGeoTubeSeg(Form("TRK_MLB_FIBER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); TGeoVolume* middleBarrelFiberPEVolume = new TGeoVolume(Form("TRK_MLB_FIBER_PE_A%d", iSide), middleBarrelFiberPE, medPE); - middleBarrelFiberPEVolume->SetLineColor(kGray); - auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zLengthMiddleServices - zLengthCylinderMiddleServices), new TGeoRotation(nullptr, refAngle + iSide * 180., 0, 0)); + middleBarrelFiberPEVolume->SetLineColor(kOrange - 9); + auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zMiddleServicesBarrelFwdConnection - zLengthCylinderMiddleServicesBarrel / 2), new TGeoRotation(nullptr, refAngle + iSide * 180., 0, 0)); motherVolume->AddNode(middleBarrelFiberSIO2Volume, 1, combiTrans); motherVolume->AddNode(middleBarrelFiberPEVolume, 1, combiTrans); // Create powerlines rCur += dR; dR = cuPowerAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelPowerCu = new TGeoTubeSeg(Form("TRK_MLB_POWER_CUsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleBarrelPowerCu = new TGeoTubeSeg(Form("TRK_MLB_POWER_CUsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); TGeoVolume* middleBarrelPowerCuVolume = new TGeoVolume(Form("TRK_MLB_POWER_CU_A%d", iSide), middleBarrelPowerCu, medCu); - middleBarrelPowerCuVolume->SetLineColor(kGray); + middleBarrelPowerCuVolume->SetLineColor(kOrange - 9); rCur += dR; dR = pePowerAreaB / (3.14 * rCur); - TGeoTubeSeg* middleBarrelPowerPE = new TGeoTubeSeg(Form("TRK_MLB_POWER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServices, -45, 45); + TGeoTubeSeg* middleBarrelPowerPE = new TGeoTubeSeg(Form("TRK_MLB_POWER_PEsh_A%d", iSide), rCur, rCur + dR, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); TGeoVolume* middleBarrelPowerPEVolume = new TGeoVolume(Form("TRK_MLB_POWER_PE_A%d", iSide), middleBarrelPowerPE, medPE); - middleBarrelPowerPEVolume->SetLineColor(kGray); + middleBarrelPowerPEVolume->SetLineColor(kOrange - 9); motherVolume->AddNode(middleBarrelPowerCuVolume, 1, combiTrans); motherVolume->AddNode(middleBarrelPowerPEVolume, 1, combiTrans); @@ -697,10 +760,11 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) // A side: barrel + disk services // C side: only disk services float rMaxMiddleServicesBarFwd = 74.5f; // TODO: add thickness of service barrels + float rMinMiddleBarrel = rMinMiddleServices; // min radius of the service disk diskCircumference = rMaxMiddleServicesBarFwd * 3.14; // Only half of the area is used for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { float refAngle = 0; - string orLabel("A"); + std::string orLabel("A"); if (orientation == Orientation::kCSide) { refAngle = 90; orLabel = "C"; @@ -708,7 +772,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) double totalThickness = 0; for (int iSide = 0; iSide < 2; iSide++) { // Create fibers - double zCur = zLengthMiddleServices; // Change to f + double zCur = zMiddleServicesBarrelFwdConnection; // Change to f double dZ = siO2FiberAreaD / diskCircumference / 2.; totalThickness += 2 * dZ; if (orientation == Orientation::kASide) { @@ -727,8 +791,8 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleBarFwdFiberPE = new TGeoTubeSeg(Form("TRK_MIDBARFWD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rMinMiddleBarrel, rMaxMiddleServicesBarFwd, dZ, -45, 45); TGeoVolume* middleBarFwdFiberPEVolume = new TGeoVolume(Form("TRK_MIDBARFWD_FIBER_PE_%s%d", orLabel.c_str(), iSide), middleBarFwdFiberPE, medPE); - middleBarFwdFiberSIO2Volume->SetLineColor(kGray); - middleBarFwdFiberPEVolume->SetLineColor(kGray); + middleBarFwdFiberSIO2Volume->SetLineColor(kOrange + 1); + middleBarFwdFiberPEVolume->SetLineColor(kOrange + 1); auto* combiTransPE = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); motherVolume->AddNode(middleBarFwdFiberSIO2Volume, 1, combiTransSIO2); motherVolume->AddNode(middleBarFwdFiberPEVolume, 1, combiTransPE); @@ -752,8 +816,8 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleBarFwdPowerPE = new TGeoTubeSeg(Form("TRK_MIDBARFWD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rMinMiddleBarrel, rMaxMiddleServicesBarFwd, dZ, -45, 45); TGeoVolume* middleBarFwdPowerPEVolume = new TGeoVolume(Form("TRK_MIDBARFWD_POWER_PE_%s%d", orLabel.c_str(), iSide), middleBarFwdPowerPE, medPE); - middleBarFwdPowerCuVolume->SetLineColor(kGray); - middleBarFwdPowerPEVolume->SetLineColor(kGray); + middleBarFwdPowerCuVolume->SetLineColor(kOrange + 1); + middleBarFwdPowerPEVolume->SetLineColor(kOrange + 1); auto* combiTransPEPower = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); motherVolume->AddNode(middleBarFwdPowerCuVolume, 1, combiTransCu); motherVolume->AddNode(middleBarFwdPowerPEVolume, 1, combiTransPEPower); @@ -762,13 +826,13 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } // Forward part - float zLengthMiddleServicesFwd = 350.f - (143.f + totalThickness); + float zLengthMiddleServicesFwd = 350.f - (zMiddleServicesBarrelFwdConnection + totalThickness); + float rMinMiddleServicesFwd = 74.5f; // 74.5cm for (int iSide = 0; iSide < 2; iSide++) { // Create fibers - float rMinMiddleServicesFwd = 74.5f; // 74.5cm - float translation = (int)orientation * (143.f + totalThickness + zLengthMiddleServicesFwd / 2); + float translation = (int)orientation * (zMiddleServicesBarrelFwdConnection + totalThickness + zLengthMiddleServicesFwd / 2); double rCur = rMinMiddleServicesFwd; double dR = siO2FiberAreaD / (3.14 * rCur); @@ -777,7 +841,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleFwdFiberSIO2 = new TGeoTubeSeg(Form("TRK_MIDFWD_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthMiddleServicesFwd / 2, -45, 45); TGeoVolume* middleFwdFiberSIO2Volume = new TGeoVolume(Form("TRK_MIDFWD_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), middleFwdFiberSIO2, medSiO2); - middleFwdFiberSIO2Volume->SetLineColor(kGray); + middleFwdFiberSIO2Volume->SetLineColor(kOrange + 1); rCur += dR; dR = peFiberAreaD / (3.14 * rCur); @@ -786,7 +850,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleFwdFiberPE = new TGeoTubeSeg(Form("TRK_MIDFWD_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthMiddleServicesFwd / 2, -45, 45); TGeoVolume* middleFwdFiberPEVolume = new TGeoVolume(Form("TRK_MIDFWD_FIBER_PE_%s%d", orLabel.c_str(), iSide), middleFwdFiberPE, medPE); - middleFwdFiberPEVolume->SetLineColor(kGray); + middleFwdFiberPEVolume->SetLineColor(kOrange + 1); auto* rot = new TGeoRotation("", refAngle + iSide * 180., 0, 0.); auto* combiTrans = new TGeoCombiTrans(0, 0, translation, rot); @@ -801,7 +865,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleFwdPowerCu = new TGeoTubeSeg(Form("TRK_MIDFWD_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthMiddleServicesFwd / 2, -45, 45); TGeoVolume* middleFwdPowerCuVolume = new TGeoVolume(Form("TRK_MIDFWD_POWER_CU_%s%d", orLabel.c_str(), iSide), middleFwdPowerCu, medCu); - middleFwdPowerCuVolume->SetLineColor(kGray); + middleFwdPowerCuVolume->SetLineColor(kOrange + 1); rCur += dR; dR = pePowerAreaD / (3.14 * rCur); @@ -810,7 +874,7 @@ void TRKServices::createMLServicesPeacock(TGeoVolume* motherVolume) } TGeoTubeSeg* middleFwdPowerPE = new TGeoTubeSeg(Form("TRK_MIDFWD_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthMiddleServicesFwd / 2, -45, 45); TGeoVolume* middleFwdPowerPEVolume = new TGeoVolume(Form("TRK_MIDFWD_POWER_PE_%s%d", orLabel.c_str(), iSide), middleFwdPowerPE, medPE); - middleFwdPowerPEVolume->SetLineColor(kGray); + middleFwdPowerPEVolume->SetLineColor(kOrange + 1); motherVolume->AddNode(middleFwdPowerCuVolume, 1, combiTrans); motherVolume->AddNode(middleFwdPowerPEVolume, 1, combiTrans); @@ -855,8 +919,20 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) float cuPowerAreaB = OTBarrelnPower * mPowerBundleArea * mPowerBundleComposition[0]; float pePowerAreaB = OTBarrelnPower * mPowerBundleArea * mPowerBundleComposition[1]; - float rMinOuterServices = 68.5f; // 68.5cm - float zLengthOuterServices = 201.f; // 201cm + // geometry of service "disk" for OT barrel + double rMinOTbarrelServices = 45.0; // cm, radius of first OT barrel layer + double rMaxOTbarrelServices = 78.0; // cm, radius of last OT barrel layer + double zOTbarrelServices = 132.0; // cm, approximate position of OT services in z + + // geometry of service "tubes" for OT barrel + float rMinOuterBarrelTubeServices = rMaxOTbarrelServices; // cm, IA, May 11, 2026: temporary radius (?) + float zStartOuterBarrelTubeServices = zOTbarrelServices + 0.8f; // cm, IA, May 11, 2026: start "OT service tubes" close in z to the "OT service disks" + float zLengthOuterBarrelTubeServices = 215.f; // cm, IA, May 11, 2026: temporary length (?) + + // geometry of service "tubes" for OT disks + float rMinOuterDiskServices = 70.5f; // cm + float zStartOuterDiskServices = 149.f; // cm + float zLengthOuterDiskServices = 201.f; // cm // Carbon Fiber Cylinder support for the middle tracker float rMinOuterCarbonSupport = 82.0f; // TODO: get more precise location @@ -869,80 +945,112 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) motherVolume->AddNode(outerBarrelCarbonSupportVolume, 1, nullptr); for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { - string orLabel = "A"; + std::string orLabel = "A"; float refAngle = 0; if (orientation == Orientation::kCSide) { orLabel = "C"; refAngle = 90; } // TODO: add cables/connections at ends of OT barrels - // Set rMin, rMax and dZ + double zCur = zOTbarrelServices; - double rMin = 45.0; - double rMax = rMinOuterServices; - double zCur = 145.0; - double dZ = siO2FiberAreaB / (4 * 3.14 * rMax); - TGeoTube* outerBarrelFiberSIO2 = new TGeoTube(Form("TRK_OUTERBARREL_FIBER_SIO2sh_%s", orLabel.c_str()), rMin, rMax, dZ); + double dZ = siO2FiberAreaB / (4 * 3.14 * rMaxOTbarrelServices); + TGeoTube* outerBarrelFiberSIO2 = new TGeoTube(Form("TRK_OUTERBARREL_FIBER_SIO2sh_%s", orLabel.c_str()), rMinOTbarrelServices, rMaxOTbarrelServices, dZ); TGeoVolume* outerBarrelFiberSIO2Volume = new TGeoVolume(Form("TRK_OUTERBARREL_FIBER_SIO2_%s", orLabel.c_str()), outerBarrelFiberSIO2, medSiO2); - outerBarrelFiberSIO2Volume->SetLineColor(kGray); + outerBarrelFiberSIO2Volume->SetLineColor(kAzure + 6); auto* combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), nullptr); motherVolume->AddNode(outerBarrelFiberSIO2Volume, 1, combiTrans); zCur += 2 * dZ; - dZ = peFiberAreaB / (4 * 3.14 * rMax); - TGeoTube* outerBarrelFiberPE = new TGeoTube(Form("TRK_OUTERBARREL_FIBER_PEsh_%s", orLabel.c_str()), rMin, rMax, dZ); + dZ = peFiberAreaB / (4 * 3.14 * rMaxOTbarrelServices); + TGeoTube* outerBarrelFiberPE = new TGeoTube(Form("TRK_OUTERBARREL_FIBER_PEsh_%s", orLabel.c_str()), rMinOTbarrelServices, rMaxOTbarrelServices, dZ); TGeoVolume* outerBarrelFiberPEVolume = new TGeoVolume(Form("TRK_OUTERBARREL_FIBER_PE_%s", orLabel.c_str()), outerBarrelFiberPE, medPE); - outerBarrelFiberPEVolume->SetLineColor(kGray); + outerBarrelFiberPEVolume->SetLineColor(kAzure + 6); combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), nullptr); motherVolume->AddNode(outerBarrelFiberPEVolume, 1, combiTrans); zCur += 2 * dZ; - dZ = cuPowerAreaB / (4 * 3.14 * rMax); - TGeoTube* outerBarrelPowerCu = new TGeoTube(Form("TRK_OUTERBARREL_POWER_CUsh_%s", orLabel.c_str()), rMin, rMax, dZ); + dZ = cuPowerAreaB / (4 * 3.14 * rMaxOTbarrelServices); + TGeoTube* outerBarrelPowerCu = new TGeoTube(Form("TRK_OUTERBARREL_POWER_CUsh_%s", orLabel.c_str()), rMinOTbarrelServices, rMaxOTbarrelServices, dZ); TGeoVolume* outerBarrelPowerCuVolume = new TGeoVolume(Form("TRK_OUTERBARREL_POWER_CU_%s", orLabel.c_str()), outerBarrelPowerCu, medCu); - outerBarrelFiberSIO2Volume->SetLineColor(kGray); + outerBarrelPowerCuVolume->SetLineColor(kAzure + 6); combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), nullptr); motherVolume->AddNode(outerBarrelPowerCuVolume, 1, combiTrans); zCur += 2 * dZ; - dZ = pePowerAreaB / (4 * 3.14 * rMax); - TGeoTube* outerBarrelPowerPE = new TGeoTube(Form("TRK_OUTERBARREL_POWER_PEsh_%s", orLabel.c_str()), rMin, rMax, dZ); + dZ = pePowerAreaB / (4 * 3.14 * rMaxOTbarrelServices); + TGeoTube* outerBarrelPowerPE = new TGeoTube(Form("TRK_OUTERBARREL_POWER_PEsh_%s", orLabel.c_str()), rMinOTbarrelServices, rMaxOTbarrelServices, dZ); TGeoVolume* outerBarrelPowerPEVolume = new TGeoVolume(Form("TRK_OUTERBARREL_POWER_PE_%s", orLabel.c_str()), outerBarrelPowerPE, medPE); - outerBarrelPowerPEVolume->SetLineColor(kGray); + outerBarrelPowerPEVolume->SetLineColor(kAzure + 6); combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), nullptr); motherVolume->AddNode(outerBarrelPowerPEVolume, 1, combiTrans); for (int iSide = 0; iSide < 2; iSide++) { - // Create fibers - double rCur = rMinOuterServices; - double dR = (siO2FiberAreaD + siO2FiberAreaB) / (3.14 * rCur); - TGeoTubeSeg* outerDisksFiberSIO2 = new TGeoTubeSeg(Form("TRK_OUTERDISKS_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterServices / 2, -45, 45); + // #### OT barrel services, implemented as tubes + // Create fibers for service barrel tubes + double rCur = rMinOuterBarrelTubeServices; // set starting radius for barrel service tube + double dR = siO2FiberAreaB / (3.14 * rCur); + TGeoTubeSeg* outerBarrelTubeFiberSIO2 = new TGeoTubeSeg(Form("TRK_OUTERBARREL_TUBE_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterBarrelTubeServices / 2, -45, 45); + TGeoVolume* outerBarrelTubeFiberSIO2Volume = new TGeoVolume(Form("TRK_OUTERBARREL_TUBE_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), outerBarrelTubeFiberSIO2, medSiO2); + outerBarrelTubeFiberSIO2Volume->SetLineColor(kAzure + 6); + + rCur += dR; + dR = peFiberAreaB / (3.14 * rCur); + TGeoTubeSeg* outerBarrelTubeFiberPE = new TGeoTubeSeg(Form("TRK_OUTERBARREL_TUBE_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterBarrelTubeServices / 2, -45, 45); + TGeoVolume* outerBarrelTubeFiberPEVolume = new TGeoVolume(Form("TRK_OUTERBARREL_TUBE_FIBER_PE_%s%d", orLabel.c_str(), iSide), outerBarrelTubeFiberPE, medPE); + outerBarrelTubeFiberPEVolume->SetLineColor(kAzure + 6); + + float translation = (int)orientation * (zStartOuterBarrelTubeServices + zLengthOuterBarrelTubeServices / 2); + auto* combiTrans = new TGeoCombiTrans(0, 0, translation, new TGeoRotation("", refAngle + iSide * 180., 0, 0)); + motherVolume->AddNode(outerBarrelTubeFiberSIO2Volume, 1, combiTrans); + motherVolume->AddNode(outerBarrelTubeFiberPEVolume, 1, combiTrans); + + // Create power lines for service barrel tubes + rCur += dR; + dR = cuPowerAreaB / (3.14 * rCur); + TGeoTubeSeg* outerBarrelTubePowerCu = new TGeoTubeSeg(Form("TRK_OUTERBARREL_TUBE_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterBarrelTubeServices / 2, -45, 45); + TGeoVolume* outerBarrelTubePowerCuVolume = new TGeoVolume(Form("TRK_OUTERBARREL_TUBE_POWER_CU_%s%d", orLabel.c_str(), iSide), outerBarrelTubePowerCu, medCu); + outerBarrelTubePowerCuVolume->SetLineColor(kAzure + 6); + + rCur += dR; + dR = pePowerAreaB / (3.14 * rCur); + TGeoTubeSeg* outerBarrelTubePowerPE = new TGeoTubeSeg(Form("TRK_OUTERBARREL_TUBE_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterBarrelTubeServices / 2, -45, 45); + TGeoVolume* outerBarrelTubePowerPEVolume = new TGeoVolume(Form("TRK_OUTERBARREL_TUBE_POWER_PE_%s%d", orLabel.c_str(), iSide), outerBarrelTubePowerPE, medPE); + outerBarrelTubePowerPEVolume->SetLineColor(kAzure + 6); + motherVolume->AddNode(outerBarrelTubePowerCuVolume, 1, combiTrans); + motherVolume->AddNode(outerBarrelTubePowerPEVolume, 1, combiTrans); + + // #### OT disk services, implemented as tubes + // Create fibers for disks + rCur = rMinOuterDiskServices; // set starting radius for disk service tube + dR = siO2FiberAreaD / (3.14 * rCur); + TGeoTubeSeg* outerDisksFiberSIO2 = new TGeoTubeSeg(Form("TRK_OUTERDISKS_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterDiskServices / 2, -45, 45); TGeoVolume* outerDisksFiberSIO2Volume = new TGeoVolume(Form("TRK_OUTERDISKS_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), outerDisksFiberSIO2, medSiO2); - outerDisksFiberSIO2Volume->SetLineColor(kGray); + outerDisksFiberSIO2Volume->SetLineColor(kMagenta); rCur += dR; - dR = (peFiberAreaD + peFiberAreaB) / (3.14 * rCur); - TGeoTubeSeg* outerDisksFiberPE = new TGeoTubeSeg(Form("TRK_OUTERDISKS_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterServices / 2, -45, 45); + dR = peFiberAreaD / (3.14 * rCur); + TGeoTubeSeg* outerDisksFiberPE = new TGeoTubeSeg(Form("TRK_OUTERDISKS_FIBER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterDiskServices / 2, -45, 45); TGeoVolume* outerDisksFiberPEVolume = new TGeoVolume(Form("TRK_OUTERDISKS_FIBER_PE_%s%d", orLabel.c_str(), iSide), outerDisksFiberPE, medPE); - outerDisksFiberPEVolume->SetLineColor(kGray); + outerDisksFiberPEVolume->SetLineColor(kMagenta); - float translation = (int)orientation * (149.f + zLengthOuterServices / 2); // ±149cm - auto* combiTrans = new TGeoCombiTrans(0, 0, translation, new TGeoRotation("", refAngle + iSide * 180., 0, 0)); + translation = (int)orientation * (zStartOuterDiskServices + zLengthOuterDiskServices / 2); + combiTrans = new TGeoCombiTrans(0, 0, translation, new TGeoRotation("", refAngle + iSide * 180., 0, 0)); motherVolume->AddNode(outerDisksFiberSIO2Volume, 1, combiTrans); motherVolume->AddNode(outerDisksFiberPEVolume, 1, combiTrans); - // Create power lines + // Create power lines for disks rCur += dR; - dR = (cuPowerAreaD + cuPowerAreaB) / (3.14 * rCur); - TGeoTubeSeg* outerDisksPowerCu = new TGeoTubeSeg(Form("TRK_OUTERDISKS_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterServices / 2, -45, 45); + dR = cuPowerAreaD / (3.14 * rCur); + TGeoTubeSeg* outerDisksPowerCu = new TGeoTubeSeg(Form("TRK_OUTERDISKS_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterDiskServices / 2, -45, 45); TGeoVolume* outerDisksPowerCuVolume = new TGeoVolume(Form("TRK_OUTERDISKS_POWER_CU_%s%d", orLabel.c_str(), iSide), outerDisksPowerCu, medCu); - outerDisksPowerCuVolume->SetLineColor(kGray); + outerDisksPowerCuVolume->SetLineColor(kMagenta + 1); rCur += dR; - dR = (pePowerAreaD + pePowerAreaB) / (3.14 * rCur); - TGeoTubeSeg* outerDisksPowerPE = new TGeoTubeSeg(Form("TRK_OUTERDISKS_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterServices / 2, -45, 45); + dR = pePowerAreaD / (3.14 * rCur); + TGeoTubeSeg* outerDisksPowerPE = new TGeoTubeSeg(Form("TRK_OUTERDISKS_POWER_PEsh_%s%d", orLabel.c_str(), iSide), rCur, rCur + dR, zLengthOuterDiskServices / 2, -45, 45); TGeoVolume* outerDisksPowerPEVolume = new TGeoVolume(Form("TRK_OUTERDISKS_POWER_PE_%s%d", orLabel.c_str(), iSide), outerDisksPowerPE, medPE); - outerDisksPowerPEVolume->SetLineColor(kGray); + outerDisksPowerPEVolume->SetLineColor(kMagenta + 1); motherVolume->AddNode(outerDisksPowerCuVolume, 1, combiTrans); motherVolume->AddNode(outerDisksPowerPEVolume, 1, combiTrans); diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx index 2f1a83f73bca3..809923b048234 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx @@ -272,8 +272,8 @@ static constexpr double kInclinedWallPhi0_deg = 27.799f; static constexpr double kInclinedWallRmax_cm = 4.75f; // 47.5 mm outer extension // Coldplate specs (cm) -static constexpr double kColdplateRadius_cm = 2.6f; // 26 mm (outer radius) -static constexpr double kColdplateThickness_cm = 0.15f; // 1.5 mm +static constexpr double kColdplateRadius_cm = 2.6f; // 26 mm (inner radius) +static constexpr double kColdplateThickness_cm = 0.02f; // 1.5 mm static constexpr double kColdplateZ_cm = 50.0f; // full length // ========== φ-span helpers (gap/arc → degrees) ========== diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt index 42402fe6b62dc..e3309d78f47ea 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt @@ -15,8 +15,6 @@ o2_add_library(TRKWorkflow src/DigitWriterSpec.cxx src/ClustererSpec.cxx src/ClusterWriterSpec.cxx - src/TrackerSpec.cxx - src/TrackWriterSpec.cxx src/RecoWorkflow.cxx PUBLIC_LINK_LIBRARIES O2::Framework O2::GPUWorkflow @@ -35,5 +33,4 @@ o2_add_executable(reco-workflow COMPONENT_NAME alice3-trk PUBLIC_LINK_LIBRARIES O2::TRKWorkflow O2::TRKSimulation - O2::TRKReconstruction - O2::ITStracking) + O2::TRKReconstruction) diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/README.md b/Detectors/Upgrades/ALICE3/TRK/workflow/README.md index 1cdce15b72726..2afb599319217 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/README.md +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/README.md @@ -1,130 +1,11 @@ # TRK Reconstruction Workflow -This document describes how to run the TRK (ALICE 3 Tracker) reconstruction workflow and provides examples of configuration files. +This workflow handles TRK-local reconstruction devices such as digit reading and clusterization. -## Overview - -The TRK reconstruction workflow performs track reconstruction from simulated hits, producing reconstructed tracks with MC truth labels. The workflow currently supports the track reconstruction from hits using the Cellular Automaton (CA) algorithm. The ouput is stored to a ROOT file for offline analysis (example of QA macro provided in `macros/test/CheckTracksCA.C`). - -## Quick Start - -### Basic Command +## Basic Command ```bash -o2-alice3-trk-reco-workflow --tracking-from-hits-config config_tracker.json -b -``` - -### Command Line Options - -- `--tracking-from-hits-config `: Path to tracking configuration JSON file (required) -- `-b`: Batch mode (no GUI) -- `--disable-root-output`: Skip writing tracks to ROOT file -- `--help`: Show all available options - -## Configuration File - -The tracking configuration is provided via a JSON file that specifies: -1. Input file paths -2. Geometry parameters (magnetic field, detector pitch) -3. Tracking algorithm parameters (can specify multiple iterations) - -### Example Configuration (`config_tracker.json`) - -```json -{ - "inputfiles": { - "hits": "o2sim_HitsTRK.root", - "geometry": "o2sim_geometry.root", - "mcHeader": "o2sim_MCHeader.root", - "kinematics": "o2sim_Kine.root" - }, - "geometry": { - "bz": 5.0, - "pitch": [0.001, 0.001, 0.001, 0.001, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004] - }, - "trackingparams": [{ - "NLayers": 11, - "DeltaROF": 0, - "LayerZ": [25.1, 25.1, 25.1, 64.2, 64.2, 64.2, 64.2, 64.2, 128.5, 128.5, 128.5], - "LayerRadii": [0.5, 1.2, 2.5, 7.05, 9.05, 12.05, 20.05, 30.05, 45.05, 60.5, 80.05], - "LayerxX0": [0.001, 0.001, 0.001, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], - "LayerResolution": [0.0003, 0.0003, 0.0003, 0.0003, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012, 0.0012], - "SystErrorY2": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - "SystErrorZ2": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - "ZBins": 256, - "PhiBins": 128, - "nROFsPerIterations": -1, - "UseDiamond": false, - "Diamond": [0.0, 0.0, 0.0], - "AllowSharingFirstCluster": false, - "ClusterSharing": 0, - "MinTrackLength": 7, - "NSigmaCut": 10, - "PVres": 0.01, - "TrackletMinPt": 0.1, - "TrackletsPerClusterLimit": 2.0, - "CellDeltaTanLambdaSigma": 0.007, - "CellsPerClusterLimit": 2.0, - "MaxChi2ClusterAttachment": 60.0, - "MaxChi2NDF": 30.0, - "ReseedIfShorter": 6, - "MinPt": [0.0, 0.0, 0.0, 0.0], - "StartLayerMask": 4095, - "RepeatRefitOut": false, - "ShiftRefToCluster": true, - "FindShortTracks": false, - "PerPrimaryVertexProcessing": false, - "SaveTimeBenchmarks": false, - "DoUPCIteration": false, - "FataliseUponFailure": true, - "UseTrackFollower": true, - "UseTrackFollowerTop": false, - "UseTrackFollowerBot": false, - "UseTrackFollowerMix": true, - "TrackFollowerNSigmaCutZ": 1.0, - "TrackFollowerNSigmaCutPhi": 1.0, - "createArtefactLabels": false, - "PrintMemory": false, - "DropTFUponFailure": false - }] -} +o2-alice3-trk-reco-workflow -b ``` -Note that the `trackingparams` field can contain multiple sets of parameters for different iterations of the tracking algorithm. The example above shows a single iteration with 11 layers and it is **not** optimized. - -## Complete Workflow Example -### 1. Run Simulation - -First, generate simulation data: - -```bash -o2-sim-serial-run5 -n 200 -g pythia8hi -m TRK --configKeyValues "Diamond.width[0]=0.01;Diamond.width[1]=0.01;Diamond.width[2]=5;TRKBase.layoutML=kTurboStaves;TRKBase.layoutOT=kStaggered;" -``` - -This produces, among other files: -- `o2sim_HitsTRK.root` -- `o2sim_geometry.root` -- `o2sim_MCHeader.root` -- `o2sim_Kine.root` -That will be used by the reconstruction as currently we do not have clusters. - -### 2. Run Reconstruction - -Execute the tracking workflow: - -```bash -o2-alice3-trk-reco-workflow --tracking-from-hits-config config_tracker.json -b -``` - -This produces: -- `o2trac_trk.root`: Reconstructed tracks with MC labels - -### 3. Run Quality Assurance - -Analyze the tracking performance: - -```bash -root -l -.L CheckTracksCA.C+ -CheckTracksCA("o2trac_trk.root", "o2sim_Kine.root", "o2sim_HitsTRK.root", "trk_qa_output.root") -``` +Use `o2-alice3-global-reconstruction-reco-workflow` for ALICE 3 tracking from hits. diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h index 9cfab104ecdf9..9d072e85d574a 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h @@ -14,6 +14,7 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" +#include "TRKBase/AlmiraParam.h" #include "TRKReconstruction/Clusterer.h" #ifdef O2_WITH_ACTS #include "TRKReconstruction/ClustererACTS.h" @@ -30,13 +31,12 @@ class ClustererDPL : public o2::framework::Task void run(o2::framework::ProcessingContext& pc) final; private: + static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; bool mUseMC = true; int mNThreads = 1; -#ifdef O2_WITH_ACTS - bool mUseACTS = false; -#endif o2::trk::Clusterer mClusterer; #ifdef O2_WITH_ACTS + bool mUseACTS = false; o2::trk::ClustererACTS mClustererACTS; #endif }; diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h index 2a0acd792f4a9..92b64e0815cfb 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h @@ -12,16 +12,20 @@ #ifndef O2_TRK_DIGITREADER #define O2_TRK_DIGITREADER +#include + #include "TFile.h" #include "TTree.h" #include "DataFormatsITSMFT/Digit.h" #include "DataFormatsITSMFT/GBTCalibData.h" #include "DataFormatsITSMFT/ROFRecord.h" +#include "SimulationDataFormat/IOMCTruthContainerView.h" #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" #include "Headers/DataHeader.h" #include "DataFormatsITSMFT/ROFRecord.h" #include "DetectorsCommonDataFormats/DetID.h" +#include "TRKBase/AlmiraParam.h" using namespace o2::framework; @@ -41,11 +45,16 @@ class DigitReader : public Task protected: void connectTree(const std::string& filename); + template + void setBranchAddress(const std::string& base, Ptr& addr, int layer = -1); + std::string getBranchName(const std::string& base, int index) const; + + static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; - std::vector mDigits, *mDigitsPtr = &mDigits; + std::vector*> mDigits{nullptr}; std::vector mCalib, *mCalibPtr = &mCalib; - std::vector mDigROFRec, *mDigROFRecPtr = &mDigROFRec; - std::vector mDigMC2ROFs, *mDigMC2ROFsPtr = &mDigMC2ROFs; + std::vector*> mDigROFRec{nullptr}; + std::vector mPLabels{nullptr}; o2::header::DataOrigin mOrigin = o2::header::gDataOriginInvalid; @@ -64,7 +73,6 @@ class DigitReader : public Task std::string mCalibBranchName = "Calib"; std::string mDigtMCTruthBranchName = "DigitMCTruth"; - std::string mDigtMC2ROFBranchName = "DigitMC2ROF"; }; class TRKDigitReader : public DigitReader diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h index 7046955a20c2e..863c5deae7241 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h @@ -13,8 +13,6 @@ #define O2_TRK_RECOWORKFLOW_H #include "Framework/WorkflowSpec.h" -#include "GPUDataTypesConfig.h" -#include namespace o2::trk { @@ -22,12 +20,9 @@ namespace reco_workflow { o2::framework::WorkflowSpec getWorkflow(bool useMC, - const std::string& hitRecoConfig, bool upstreamDigits = false, bool upstreamClusters = false, - bool disableRootOutput = false, - bool useGPUWF = false, - o2::gpu::gpudatatypes::DeviceType dType = o2::gpu::gpudatatypes::DeviceType::CPU); + bool disableRootOutput = false); } } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx index bc3a75c646198..863915bac0572 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx @@ -11,9 +11,16 @@ /// @file ClusterWriterSpec.cxx +#include +#include +#include #include +#include #include "TRKWorkflow/ClusterWriterSpec.h" +#include "Framework/ConcreteDataMatcher.h" +#include "Framework/DataRef.h" +#include "TRKBase/AlmiraParam.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" #include "DataFormatsTRK/Cluster.h" #include "DataFormatsTRK/ROFRecord.h" @@ -35,31 +42,68 @@ using ROFRecLblType = std::vector; DataProcessorSpec getClusterWriterSpec(bool useMC) { - auto clustersSize = std::make_shared(0); - auto clustersSizeGetter = [clustersSize](ClustersType const& clusters) { - *clustersSize = clusters.size(); + static constexpr o2::header::DataOrigin Origin{o2::header::gDataOriginTRK}; + static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; + const auto detName = Origin.as(); + + auto compClusterSizes = std::make_shared>(nLayers, 0); + auto compClustersSizeGetter = [compClusterSizes](ClustersType const& compClusters, DataRef const& ref) { + auto const* dh = DataRefUtils::getHeader(ref); + (*compClusterSizes)[dh->subSpecification] = compClusters.size(); + }; + auto logger = [detName, compClusterSizes](ROFrameType const& rofs, DataRef const& ref) { + auto const* dh = DataRefUtils::getHeader(ref); + const auto i = dh->subSpecification; + LOG(info) << detName << "ClusterWriter on layer " << i + << " pulled " << (*compClusterSizes)[i] << " clusters, in " << rofs.size() << " RO frames"; }; - auto logger = [clustersSize](ROFrameType const& rofs) { - LOG(info) << "TRKClusterWriter pulled " << *clustersSize << " clusters, in " << rofs.size() << " RO frames"; + auto getIndex = [](DataRef const& ref) -> size_t { + auto const* dh = DataRefUtils::getHeader(ref); + return static_cast(dh->subSpecification); }; + auto getName = [](std::string base, size_t index) -> std::string { + return base + "_" + std::to_string(index); + }; + auto detNameLC = detName; + std::transform(detNameLC.begin(), detNameLC.end(), detNameLC.begin(), [](unsigned char c) { return std::tolower(c); }); + + std::vector vecInpSpecClus, vecInpSpecPatt, vecInpSpecROF, vecInpSpecLbl; + vecInpSpecClus.reserve(nLayers); + vecInpSpecPatt.reserve(nLayers); + vecInpSpecROF.reserve(nLayers); + vecInpSpecLbl.reserve(nLayers); + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + vecInpSpecClus.emplace_back(getName("compclus", iLayer), Origin, "COMPCLUSTERS", iLayer); + vecInpSpecPatt.emplace_back(getName("patterns", iLayer), Origin, "PATTERNS", iLayer); + vecInpSpecROF.emplace_back(getName("ROframes", iLayer), Origin, "CLUSTERSROF", iLayer); + vecInpSpecLbl.emplace_back(getName("labels", iLayer), Origin, "CLUSTERSMCTR", iLayer); + } - return MakeRootTreeWriterSpec("trk-cluster-writer", + return MakeRootTreeWriterSpec(std::format("{}-cluster-writer", detNameLC).c_str(), "o2clus_trk.root", - MakeRootTreeWriterSpec::TreeAttributes{"o2sim", "Tree with TRK clusters"}, - BranchDefinition{InputSpec{"compclus", "TRK", "COMPCLUSTERS", 0}, - "TRKClusterComp", - clustersSizeGetter}, - BranchDefinition{InputSpec{"patterns", "TRK", "PATTERNS", 0}, - "TRKClusterPatt"}, - BranchDefinition{InputSpec{"ROframes", "TRK", "CLUSTERSROF", 0}, - "TRKClustersROF", - logger}, - BranchDefinition{InputSpec{"labels", "TRK", "CLUSTERSMCTR", 0}, - "TRKClusterMCTruth", - (useMC ? 1 : 0)}, - BranchDefinition{InputSpec{"MC2ROframes", "TRK", "CLUSTERSMC2ROF", 0}, - "TRKClustersMC2ROF", - (useMC ? 1 : 0)})(); + MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = "Tree with TRK clusters"}, + BranchDefinition{vecInpSpecClus, + "TRKClusterComp", "compact-cluster-branch", + nLayers, + compClustersSizeGetter, + getIndex, + getName}, + BranchDefinition{vecInpSpecPatt, + "TRKClusterPatt", "cluster-pattern-branch", + nLayers, + getIndex, + getName}, + BranchDefinition{vecInpSpecROF, + "TRKClustersROF", "cluster-rof-branch", + nLayers, + logger, + getIndex, + getName}, + BranchDefinition{vecInpSpecLbl, + "TRKClusterMCTruth", "cluster-label-branch", + (useMC ? nLayers : 0), + getIndex, + getName})(); } } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx index 5d9ac463b3f54..f91262e021a55 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx @@ -17,6 +17,8 @@ #include "Framework/Logger.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" +#include + namespace o2::trk { @@ -30,82 +32,84 @@ void ClustererDPL::init(o2::framework::InitContext& ic) void ClustererDPL::run(o2::framework::ProcessingContext& pc) { - auto digits = pc.inputs().get>("digits"); - auto rofs = pc.inputs().get>("ROframes"); + o2::base::GeometryManager::loadGeometry("sgn_geometry.root", false, true); - gsl::span mc2rofs; - gsl::span labelbuffer; - if (mUseMC) { - labelbuffer = pc.inputs().get>("labels"); - mc2rofs = pc.inputs().get>("MC2ROframes"); - } - o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); + uint64_t totalClusters = 0; + for (int iLayer = 0; iLayer < mLayers; ++iLayer) { + auto digits = pc.inputs().get>(std::format("digits_{}", iLayer)); + auto rofs = pc.inputs().get>(std::format("ROframes_{}", iLayer)); - std::vector clusters; - std::vector patterns; - std::vector clusterROFs; - std::unique_ptr> clusterLabels; - std::vector clusterMC2ROFs; - if (mUseMC) { - clusterLabels = std::make_unique>(); - } - o2::base::GeometryManager::loadGeometry("o2sim_geometry.root", false, true); + gsl::span labelbuffer; + if (mUseMC) { + labelbuffer = pc.inputs().get>(std::format("labels_{}", iLayer)); + } + o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); + + std::vector clusters; + std::vector patterns; + std::vector clusterROFs; + std::unique_ptr> clusterLabels; + if (mUseMC) { + clusterLabels = std::make_unique>(); + } #ifdef O2_WITH_ACTS - if (mUseACTS) { - LOG(info) << "Running TRKClusterer with ACTS"; - mClustererACTS.process(digits, - rofs, - clusters, - patterns, - clusterROFs, - mUseMC ? &labels : nullptr, - clusterLabels.get(), - mc2rofs, - mUseMC ? &clusterMC2ROFs : nullptr); - } else + if (mUseACTS) { + LOG(info) << "Running TRKClusterer with ACTS on layer " << iLayer; + mClustererACTS.process(digits, + rofs, + clusters, + patterns, + clusterROFs, + mUseMC ? &labels : nullptr, + clusterLabels.get()); + } else #endif - { - LOG(info) << "Running TRKClusterer"; - mClusterer.process(digits, - rofs, - clusters, - patterns, - clusterROFs, - mUseMC ? &labels : nullptr, - clusterLabels.get(), - mc2rofs, - mUseMC ? &clusterMC2ROFs : nullptr); - } - - pc.outputs().snapshot(o2::framework::Output{"TRK", "COMPCLUSTERS", 0}, clusters); - pc.outputs().snapshot(o2::framework::Output{"TRK", "PATTERNS", 0}, patterns); - pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSROF", 0}, clusterROFs); + { + LOG(info) << "Running TRKClusterer on layer " << iLayer; + mClusterer.process(digits, + rofs, + clusters, + patterns, + clusterROFs, + mUseMC ? &labels : nullptr, + clusterLabels.get()); + } - if (mUseMC) { - pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSMCTR", 0}, *clusterLabels); - pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSMC2ROF", 0}, clusterMC2ROFs); + const auto subspec = static_cast(iLayer); + pc.outputs().snapshot(o2::framework::Output{"TRK", "COMPCLUSTERS", subspec}, clusters); + pc.outputs().snapshot(o2::framework::Output{"TRK", "PATTERNS", subspec}, patterns); + pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSROF", subspec}, clusterROFs); + if (mUseMC) { + pc.outputs().snapshot(o2::framework::Output{"TRK", "CLUSTERSMCTR", subspec}, *clusterLabels); + } + totalClusters += clusters.size(); + LOGP(info, "TRKClusterer layer {} pushed {} clusters in {} ROFs", iLayer, clusters.size(), clusterROFs.size()); } - LOGP(info, "TRKClusterer pushed {} clusters in {} ROFs", clusters.size(), clusterROFs.size()); + LOGP(info, "TRKClusterer produced {} clusters", totalClusters); } o2::framework::DataProcessorSpec getClustererSpec(bool useMC) { + static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; std::vector inputs; - inputs.emplace_back("digits", "TRK", "DIGITS", 0, o2::framework::Lifetime::Timeframe); - inputs.emplace_back("ROframes", "TRK", "DIGITSROF", 0, o2::framework::Lifetime::Timeframe); + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + inputs.emplace_back(std::format("digits_{}", iLayer), "TRK", "DIGITS", iLayer, o2::framework::Lifetime::Timeframe); + inputs.emplace_back(std::format("ROframes_{}", iLayer), "TRK", "DIGITSROF", iLayer, o2::framework::Lifetime::Timeframe); + if (useMC) { + inputs.emplace_back(std::format("labels_{}", iLayer), "TRK", "DIGITSMCTR", iLayer, o2::framework::Lifetime::Timeframe); + } + } std::vector outputs; - outputs.emplace_back("TRK", "COMPCLUSTERS", 0, o2::framework::Lifetime::Timeframe); - outputs.emplace_back("TRK", "PATTERNS", 0, o2::framework::Lifetime::Timeframe); - outputs.emplace_back("TRK", "CLUSTERSROF", 0, o2::framework::Lifetime::Timeframe); - - if (useMC) { - inputs.emplace_back("labels", "TRK", "DIGITSMCTR", 0, o2::framework::Lifetime::Timeframe); - inputs.emplace_back("MC2ROframes", "TRK", "DIGITSMC2ROF", 0, o2::framework::Lifetime::Timeframe); - outputs.emplace_back("TRK", "CLUSTERSMCTR", 0, o2::framework::Lifetime::Timeframe); - outputs.emplace_back("TRK", "CLUSTERSMC2ROF", 0, o2::framework::Lifetime::Timeframe); + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + outputs.emplace_back("TRK", "COMPCLUSTERS", iLayer, o2::framework::Lifetime::Timeframe); + outputs.emplace_back("TRK", "PATTERNS", iLayer, o2::framework::Lifetime::Timeframe); + outputs.emplace_back("TRK", "CLUSTERSROF", iLayer, o2::framework::Lifetime::Timeframe); + if (useMC) { + outputs.emplace_back("TRK", "CLUSTERSMCTR", iLayer, o2::framework::Lifetime::Timeframe); + } } return o2::framework::DataProcessorSpec{ diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx index 09bb1f12a48e4..ec2b6d4d66192 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx @@ -36,12 +36,15 @@ DigitReader::DigitReader(o2::detectors::DetID id, bool useMC, bool useCalib) mDetNameLC = mDetName = id.getName(); mDigTreeName = "o2sim"; + mDigits.resize(mLayers, nullptr); + mDigROFRec.resize(mLayers, nullptr); + mPLabels.resize(mLayers, nullptr); + mDigitBranchName = mDetName + mDigitBranchName; mDigROFBranchName = mDetName + mDigROFBranchName; mCalibBranchName = mDetName + mCalibBranchName; mDigtMCTruthBranchName = mDetName + mDigtMCTruthBranchName; - mDigtMC2ROFBranchName = mDetName + mDigtMC2ROFBranchName; mUseMC = useMC; mUseCalib = useCalib; @@ -58,30 +61,27 @@ void DigitReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; assert(ent < mTree->GetEntries()); // this should not happen + mTree->GetEntry(ent); + + for (int iLayer = 0; iLayer < mLayers; ++iLayer) { + LOG(info) << mDetName << "DigitReader on layer " << iLayer << " pushes " << mDigROFRec[iLayer]->size() << " ROFRecords, " + << mDigits[iLayer]->size() << " digits at entry " << ent; - o2::dataformats::IOMCTruthContainerView* plabels = nullptr; - if (mUseMC) { - mTree->SetBranchAddress(mDigtMCTruthBranchName.c_str(), &plabels); + pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", static_cast(iLayer)}, *mDigROFRec[iLayer]); + pc.outputs().snapshot(Output{mOrigin, "DIGITS", static_cast(iLayer)}, *mDigits[iLayer]); + + if (mUseMC) { + auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", static_cast(iLayer)}); + mPLabels[iLayer]->copyandflatten(sharedlabels); + delete mPLabels[iLayer]; + mPLabels[iLayer] = nullptr; + } } - mTree->GetEntry(ent); - LOG(info) << mDetName << "DigitReader pushes " << mDigROFRec.size() << " ROFRecords, " - << mDigits.size() << " digits at entry " << ent; - // This is a very ugly way of providing DataDescription, which anyway does not need to contain detector name. - // To be fixed once the names-definition class is ready - pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", 0}, mDigROFRec); - pc.outputs().snapshot(Output{mOrigin, "DIGITS", 0}, mDigits); if (mUseCalib) { pc.outputs().snapshot(Output{mOrigin, "GBTCALIB", 0}, mCalib); } - if (mUseMC) { - auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", 0}); - plabels->copyandflatten(sharedlabels); - delete plabels; - pc.outputs().snapshot(Output{mOrigin, "DIGITSMC2ROF", 0}, mDigMC2ROFs); - } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); @@ -96,35 +96,59 @@ void DigitReader::connectTree(const std::string& filename) mTree.reset((TTree*)mFile->Get(mDigTreeName.c_str())); assert(mTree); - mTree->SetBranchAddress(mDigROFBranchName.c_str(), &mDigROFRecPtr); - mTree->SetBranchAddress(mDigitBranchName.c_str(), &mDigitsPtr); + for (int iLayer = 0; iLayer < mLayers; ++iLayer) { + setBranchAddress(mDigROFBranchName, mDigROFRec[iLayer], iLayer); + setBranchAddress(mDigitBranchName, mDigits[iLayer], iLayer); + if (mUseMC) { + const auto mctruthBranch = getBranchName(mDigtMCTruthBranchName, iLayer); + if (!mTree->GetBranch(mctruthBranch.c_str())) { + throw std::runtime_error("MC data requested but missing branch(es) at layer " + std::to_string(iLayer) + + ": " + mctruthBranch); + } + setBranchAddress(mDigtMCTruthBranchName, mPLabels[iLayer], iLayer); + } + } + if (mUseCalib) { if (!mTree->GetBranch(mCalibBranchName.c_str())) { throw std::runtime_error("GBT calibration data requested but not found in the tree"); } - mTree->SetBranchAddress(mCalibBranchName.c_str(), &mCalibPtr); - } - if (mUseMC) { - if (!mTree->GetBranch(mDigtMC2ROFBranchName.c_str()) || !mTree->GetBranch(mDigtMCTruthBranchName.c_str())) { - throw std::runtime_error("MC data requested but not found in the tree"); - } - mTree->SetBranchAddress(mDigtMC2ROFBranchName.c_str(), &mDigMC2ROFsPtr); + setBranchAddress(mCalibBranchName, mCalibPtr); } LOG(info) << "Loaded tree from " << filename << " with " << mTree->GetEntries() << " entries"; } +std::string DigitReader::getBranchName(const std::string& base, int index) const +{ + if (index >= 0) { + return base + "_" + std::to_string(index); + } + return base; +} + +template +void DigitReader::setBranchAddress(const std::string& base, Ptr& addr, int layer) +{ + const auto name = getBranchName(base, layer); + if (Int_t ret = mTree->SetBranchAddress(name.c_str(), &addr); ret != 0) { + LOGP(fatal, "failed to set branch address for {} ret={}", name, ret); + } +} + DataProcessorSpec getTRKDigitReaderSpec(bool useMC, bool useCalib, std::string defname) { + static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; std::vector outputSpec; - outputSpec.emplace_back("TRK", "DIGITS", 0, Lifetime::Timeframe); - outputSpec.emplace_back("TRK", "DIGITSROF", 0, Lifetime::Timeframe); + for (int iLayer = 0; iLayer < nLayers; ++iLayer) { + outputSpec.emplace_back("TRK", "DIGITS", iLayer, Lifetime::Timeframe); + outputSpec.emplace_back("TRK", "DIGITSROF", iLayer, Lifetime::Timeframe); + if (useMC) { + outputSpec.emplace_back("TRK", "DIGITSMCTR", iLayer, Lifetime::Timeframe); + } + } if (useCalib) { outputSpec.emplace_back("TRK", "GBTCALIB", 0, Lifetime::Timeframe); } - if (useMC) { - outputSpec.emplace_back("TRK", "DIGITSMCTR", 0, Lifetime::Timeframe); - outputSpec.emplace_back("TRK", "DIGITSMC2ROF", 0, Lifetime::Timeframe); - } return DataProcessorSpec{ "trk-digit-reader", diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx index 2a743551adddb..591b084aee3ba 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx @@ -9,9 +9,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// @brief Processor spec for a ROOT file writer for ITSMFT digits +/// @brief Processor spec for a ROOT file writer for TRK digits (per-layer) #include "TRKWorkflow/DigitWriterSpec.h" +#include "Framework/ConcreteDataMatcher.h" +#include "Framework/DataRef.h" +#include "TRKBase/AlmiraParam.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" #include "DataFormatsITSMFT/Digit.h" #include "DataFormatsITSMFT/GBTCalibData.h" @@ -24,6 +27,7 @@ #include #include #include +#include using namespace o2::framework; using SubSpecificationType = o2::framework::DataAllocator::SubSpecificationType; @@ -37,16 +41,22 @@ template using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; using MCCont = o2::dataformats::ConstMCTruthContainer; -/// create the processor spec -/// describing a processor receiving digits for ITS/MFT and writing them to file -DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib, o2::header::DataOrigin detOrig, o2::detectors::DetID detId) +DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) { - std::string detStr = o2::detectors::DetID::getName(detId); - std::string detStrL = dec ? "o2_" : ""; // for decoded digits prepend by o2 - detStrL += detStr; - std::transform(detStrL.begin(), detStrL.end(), detStrL.begin(), ::tolower); - auto logger = [](std::vector const& inDigits) { - LOG(info) << "RECEIVED DIGITS SIZE " << inDigits.size(); + static constexpr o2::header::DataOrigin Origin = o2::header::gDataOriginTRK; + const int mLayers = o2::trk::AlmiraParam::kNLayers; + std::string detStr = "TRK"; + std::string detStrL = dec ? "o2_trk" : "trk"; + + auto digitSizes = std::make_shared>(mLayers, 0); + auto digitSizeGetter = [digitSizes](std::vector const& inDigits, DataRef const& ref) { + auto const* dh = DataRefUtils::getHeader(ref); + (*digitSizes)[dh->subSpecification] = inDigits.size(); + }; + auto rofSizes = std::make_shared>(mLayers, 0); + auto rofSizeGetter = [rofSizes](std::vector const& inROFs, DataRef const& ref) { + auto const* dh = DataRefUtils::getHeader(ref); + (*rofSizes)[dh->subSpecification] = inROFs.size(); }; // the callback to be set as hook for custom action when the writer is closed @@ -61,16 +71,18 @@ DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib, o2::hea nent = n; } outputtree->SetEntries(nent); - outputtree->Write("", TObject::kOverwrite); + outputfile->Write("", TObject::kOverwrite); outputfile->Close(); }; // handler for labels - // This is necessary since we can't store the original label buffer in a ROOT entry -- as is -- if it exceeds a certain size. - // We therefore convert it to a special split class. - auto fillLabels = [](TBranch& branch, std::vector const& labelbuffer, DataRef const& /*ref*/) { + auto fillLabels = [detStr, digitSizes, rofSizes](TBranch& branch, std::vector const& labelbuffer, DataRef const& ref) { o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); - LOG(info) << "WRITING " << labels.getNElements() << " LABELS "; + auto const* dh = DataRefUtils::getHeader(ref); + auto layer = static_cast(dh->subSpecification); + LOG(info) << detStr << ": WRITING " << labels.getNElements() << " LABELS" + << std::format(" FOR LAYER {}", layer) << " WITH " << (*digitSizes)[layer] + << " DIGITS IN " << (*rofSizes)[layer] << " ROFS"; o2::dataformats::IOMCTruthContainerView outputcontainer; auto ptr = &outputcontainer; @@ -80,30 +92,49 @@ DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib, o2::hea br->ResetAddress(); }; - return MakeRootTreeWriterSpec((detStr + "DigitWriter" + (dec ? "_dec" : "")).c_str(), + auto getIndex = [](DataRef const& ref) -> size_t { + auto const* dh = DataRefUtils::getHeader(ref); + return static_cast(dh->subSpecification); + }; + auto getName = [](std::string base, size_t index) -> std::string { + return base + "_" + std::to_string(index); + }; + + std::vector vecInpSpecDig, vecInpSpecROF, vecInpSpecLbl; + vecInpSpecDig.reserve(mLayers); + vecInpSpecROF.reserve(mLayers); + vecInpSpecLbl.reserve(mLayers); + for (int iLayer = 0; iLayer < mLayers; iLayer++) { + vecInpSpecDig.emplace_back(getName(detStr + "digits", iLayer), Origin, "DIGITS", iLayer); + vecInpSpecROF.emplace_back(getName(detStr + "digitsROF", iLayer), Origin, "DIGITSROF", iLayer); + vecInpSpecLbl.emplace_back(getName(detStr + "_digitsMCTR", iLayer), Origin, "DIGITSMCTR", iLayer); + } + + return MakeRootTreeWriterSpec(("TRKDigitWriter" + std::string(dec ? "_dec" : "")).c_str(), (detStrL + "digits.root").c_str(), - MakeRootTreeWriterSpec::TreeAttributes{"o2sim", "Digits tree"}, + MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = detStr + " Digits tree"}, MakeRootTreeWriterSpec::CustomClose(finishWriting), - // in case of labels we first read them as std::vector and process them correctly in the fillLabels hook - BranchDefinition>{InputSpec{"digitsMCTR", detOrig, "DIGITSMCTR", 0}, - (detStr + "DigitMCTruth").c_str(), - (mctruth ? 1 : 0), fillLabels}, - BranchDefinition>{InputSpec{"digitsMC2ROF", detOrig, "DIGITSMC2ROF", 0}, - (detStr + "DigitMC2ROF").c_str(), - (mctruth ? 1 : 0)}, - BranchDefinition>{InputSpec{"digits", detOrig, "DIGITS", 0}, - (detStr + "Digit").c_str(), - logger}, - BranchDefinition>{InputSpec{"calib", detOrig, "GBTCALIB", 0}, - (detStr + "Calib").c_str(), - (calib ? 1 : 0)}, - BranchDefinition>{InputSpec{"digitsROF", detOrig, "DIGITSROF", 0}, - (detStr + "DigitROF").c_str()})(); -} - -DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) -{ - return getDigitWriterSpec(mctruth, dec, calib, o2::header::gDataOriginTRK, o2::detectors::DetID::TRK); + BranchDefinition>{vecInpSpecDig, + detStr + "Digit", "digit-branch", + mLayers, + digitSizeGetter, + getIndex, + getName}, + BranchDefinition>{vecInpSpecROF, + detStr + "DigitROF", "digit-rof-branch", + mLayers, + rofSizeGetter, + getIndex, + getName}, + BranchDefinition>{vecInpSpecLbl, + detStr + "DigitMCTruth", "digit-mctruth-branch", + (mctruth ? mLayers : 0), + fillLabels, + getIndex, + getName}, + BranchDefinition>{InputSpec{detStr + "calib", ConcreteDataTypeMatcher{Origin, "GBTCALIB"}}, + detStr + "Calib", "digit-calib-branch", + (calib ? 1 : 0)})(); } } // end namespace trk diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx index d10feb4214f38..02895f42ac094 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx @@ -13,8 +13,6 @@ #include "TRKWorkflow/ClustererSpec.h" #include "TRKWorkflow/ClusterWriterSpec.h" #include "TRKWorkflow/DigitReaderSpec.h" -#include "TRKWorkflow/TrackerSpec.h" -#include "TRKWorkflow/TrackWriterSpec.h" #include "Framework/CCDBParamSpec.h" #include @@ -23,12 +21,9 @@ namespace o2::trk::reco_workflow { framework::WorkflowSpec getWorkflow(bool useMC, - const std::string& hitRecoConfig, bool upstreamDigits, bool upstreamClusters, - bool disableRootOutput, - bool useGPUWF, - o2::gpu::gpudatatypes::DeviceType dtype) + bool disableRootOutput) { framework::WorkflowSpec specs; @@ -43,14 +38,6 @@ framework::WorkflowSpec getWorkflow(bool useMC, specs.emplace_back(o2::trk::getClusterWriterSpec(useMC)); } - if (!hitRecoConfig.empty()) { - LOGP(info, "Using hit reco config from file {}", hitRecoConfig); - specs.emplace_back(o2::trk::getTrackerSpec(useMC, hitRecoConfig, dtype)); - if (!disableRootOutput) { - specs.emplace_back(o2::trk::getTrackWriterSpec(useMC)); - } - } - return specs; } diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx deleted file mode 100644 index 20bd45557dac5..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx +++ /dev/null @@ -1,437 +0,0 @@ -// 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. - -#include -#include - -#include "DetectorsBase/GeometryManager.h" -#include "ITStracking/TimeFrame.h" -#include "ITStracking/Configuration.h" -#include "Field/MagneticField.h" -#include "Field/MagFieldParam.h" -#include "Framework/ControlService.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/CCDBParamSpec.h" -#include "SimulationDataFormat/MCEventHeader.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "TRKBase/GeometryTGeo.h" -#include "TRKBase/SegmentationChip.h" -#include "TRKSimulation/Hit.h" -#include "TRKReconstruction/TimeFrame.h" -#include "TRKWorkflow/TrackerSpec.h" -#include - -#include -#include - -namespace o2 -{ -using namespace framework; -namespace trk -{ -using Vertex = o2::dataformats::Vertex>; - -TrackerDPL::TrackerDPL(std::shared_ptr gr, - bool isMC, - const std::string& hitRecoConfigFileName, - o2::gpu::gpudatatypes::DeviceType dType) -{ - if (!hitRecoConfigFileName.empty()) { - std::ifstream configFile(hitRecoConfigFileName); - mHitRecoConfig = nlohmann::json::parse(configFile); - } - - // mITSTrackingInterface.setTrackingMode(trMode); -} - -void TrackerDPL::init(InitContext& ic) -{ - // mTimer.Stop(); - // mTimer.Reset(); - // o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); - // mChainITS.reset(mRecChain->AddChain()); - // mITSTrackingInterface.setTraitsFromProvider(mChainITS->GetITSVertexerTraits(), - // mChainITS->GetITSTrackerTraits(), - // mChainITS->GetITSTimeframe()); -} - -void TrackerDPL::stop() -{ - LOGF(info, "CPU Reconstruction total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); -} - -std::vector TrackerDPL::createTrackingParamsFromConfig() -{ - std::vector trackingParams; - - if (!mHitRecoConfig.contains("trackingparams") || !mHitRecoConfig["trackingparams"].is_array()) { - LOGP(fatal, "No trackingparams field found in configuration or it is not an array. Returning empty vector."); - return trackingParams; - } - - for (const auto& paramConfig : mHitRecoConfig["trackingparams"]) { - o2::its::TrackingParameters params; - - // Parse integer parameters - if (paramConfig.contains("NLayers")) { - params.NLayers = paramConfig["NLayers"].get(); - } - if (paramConfig.contains("DeltaROF")) { - params.DeltaROF = paramConfig["DeltaROF"].get(); - } - if (paramConfig.contains("ZBins")) { - params.ZBins = paramConfig["ZBins"].get(); - } - if (paramConfig.contains("PhiBins")) { - params.PhiBins = paramConfig["PhiBins"].get(); - } - if (paramConfig.contains("nROFsPerIterations")) { - params.nROFsPerIterations = paramConfig["nROFsPerIterations"].get(); - } - if (paramConfig.contains("ClusterSharing")) { - params.ClusterSharing = paramConfig["ClusterSharing"].get(); - } - if (paramConfig.contains("MinTrackLength")) { - params.MinTrackLength = paramConfig["MinTrackLength"].get(); - } - if (paramConfig.contains("ReseedIfShorter")) { - params.ReseedIfShorter = paramConfig["ReseedIfShorter"].get(); - } - if (paramConfig.contains("StartLayerMask")) { - params.StartLayerMask = paramConfig["StartLayerMask"].get(); - } - - // Parse float parameters - if (paramConfig.contains("NSigmaCut")) { - params.NSigmaCut = paramConfig["NSigmaCut"].get(); - } - if (paramConfig.contains("PVres")) { - params.PVres = paramConfig["PVres"].get(); - } - if (paramConfig.contains("TrackletMinPt")) { - params.TrackletMinPt = paramConfig["TrackletMinPt"].get(); - } - if (paramConfig.contains("TrackletsPerClusterLimit")) { - params.TrackletsPerClusterLimit = paramConfig["TrackletsPerClusterLimit"].get(); - } - if (paramConfig.contains("CellDeltaTanLambdaSigma")) { - params.CellDeltaTanLambdaSigma = paramConfig["CellDeltaTanLambdaSigma"].get(); - } - if (paramConfig.contains("CellsPerClusterLimit")) { - params.CellsPerClusterLimit = paramConfig["CellsPerClusterLimit"].get(); - } - if (paramConfig.contains("MaxChi2ClusterAttachment")) { - params.MaxChi2ClusterAttachment = paramConfig["MaxChi2ClusterAttachment"].get(); - } - if (paramConfig.contains("MaxChi2NDF")) { - params.MaxChi2NDF = paramConfig["MaxChi2NDF"].get(); - } - if (paramConfig.contains("TrackFollowerNSigmaCutZ")) { - params.TrackFollowerNSigmaCutZ = paramConfig["TrackFollowerNSigmaCutZ"].get(); - } - if (paramConfig.contains("TrackFollowerNSigmaCutPhi")) { - params.TrackFollowerNSigmaCutPhi = paramConfig["TrackFollowerNSigmaCutPhi"].get(); - } - - // Parse boolean parameters - if (paramConfig.contains("UseDiamond")) { - params.UseDiamond = paramConfig["UseDiamond"].get(); - } - if (paramConfig.contains("AllowSharingFirstCluster")) { - params.AllowSharingFirstCluster = paramConfig["AllowSharingFirstCluster"].get(); - } - if (paramConfig.contains("RepeatRefitOut")) { - params.RepeatRefitOut = paramConfig["RepeatRefitOut"].get(); - } - if (paramConfig.contains("ShiftRefToCluster")) { - params.ShiftRefToCluster = paramConfig["ShiftRefToCluster"].get(); - } - if (paramConfig.contains("FindShortTracks")) { - params.FindShortTracks = paramConfig["FindShortTracks"].get(); - } - if (paramConfig.contains("PerPrimaryVertexProcessing")) { - params.PerPrimaryVertexProcessing = paramConfig["PerPrimaryVertexProcessing"].get(); - } - if (paramConfig.contains("SaveTimeBenchmarks")) { - params.SaveTimeBenchmarks = paramConfig["SaveTimeBenchmarks"].get(); - } - if (paramConfig.contains("DoUPCIteration")) { - params.DoUPCIteration = paramConfig["DoUPCIteration"].get(); - } - if (paramConfig.contains("FataliseUponFailure")) { - params.FataliseUponFailure = paramConfig["FataliseUponFailure"].get(); - } - if (paramConfig.contains("UseTrackFollower")) { - params.UseTrackFollower = paramConfig["UseTrackFollower"].get(); - } - if (paramConfig.contains("UseTrackFollowerTop")) { - params.UseTrackFollowerTop = paramConfig["UseTrackFollowerTop"].get(); - } - if (paramConfig.contains("UseTrackFollowerBot")) { - params.UseTrackFollowerBot = paramConfig["UseTrackFollowerBot"].get(); - } - if (paramConfig.contains("UseTrackFollowerMix")) { - params.UseTrackFollowerMix = paramConfig["UseTrackFollowerMix"].get(); - } - if (paramConfig.contains("createArtefactLabels")) { - params.createArtefactLabels = paramConfig["createArtefactLabels"].get(); - } - if (paramConfig.contains("PrintMemory")) { - params.PrintMemory = paramConfig["PrintMemory"].get(); - } - if (paramConfig.contains("DropTFUponFailure")) { - params.DropTFUponFailure = paramConfig["DropTFUponFailure"].get(); - } - - // Parse vector parameters - if (paramConfig.contains("LayerZ")) { - params.LayerZ = paramConfig["LayerZ"].get>(); - } - if (paramConfig.contains("LayerRadii")) { - params.LayerRadii = paramConfig["LayerRadii"].get>(); - } - if (paramConfig.contains("LayerxX0")) { - params.LayerxX0 = paramConfig["LayerxX0"].get>(); - } - if (paramConfig.contains("LayerResolution")) { - params.LayerResolution = paramConfig["LayerResolution"].get>(); - } - if (paramConfig.contains("SystErrorY2")) { - params.SystErrorY2 = paramConfig["SystErrorY2"].get>(); - } - if (paramConfig.contains("SystErrorZ2")) { - params.SystErrorZ2 = paramConfig["SystErrorZ2"].get>(); - } - if (paramConfig.contains("MinPt")) { - params.MinPt = paramConfig["MinPt"].get>(); - } - - // Parse Diamond array - if (paramConfig.contains("Diamond") && paramConfig["Diamond"].is_array() && paramConfig["Diamond"].size() == 3) { - params.Diamond[0] = paramConfig["Diamond"][0].get(); - params.Diamond[1] = paramConfig["Diamond"][1].get(); - params.Diamond[2] = paramConfig["Diamond"][2].get(); - } - - // Parse size_t parameter - if (paramConfig.contains("MaxMemory")) { - params.MaxMemory = paramConfig["MaxMemory"].get(); - } - - // Parse CorrType enum - if (paramConfig.contains("CorrType")) { - int corrTypeInt = paramConfig["CorrType"].get(); - params.CorrType = static_cast::MatCorrType>(corrTypeInt); - } - - trackingParams.push_back(params); - } - - LOGP(info, "Loaded {} tracking parameter sets from configuration", trackingParams.size()); - return trackingParams; -} - -void TrackerDPL::run(ProcessingContext& pc) -{ - auto cput = mTimer.CpuTime(); - auto realt = mTimer.RealTime(); - mTimer.Start(false); - - if (!mHitRecoConfig.empty()) { - TFile hitsFile(mHitRecoConfig["inputfiles"]["hits"].get().c_str(), "READ"); - TFile mcHeaderFile(mHitRecoConfig["inputfiles"]["mcHeader"].get().c_str(), "READ"); - TTree* hitsTree = hitsFile.Get("o2sim"); - std::vector* trkHit = nullptr; - hitsTree->SetBranchAddress("TRKHit", &trkHit); - - TTree* mcHeaderTree = mcHeaderFile.Get("o2sim"); - auto mcheader = new o2::dataformats::MCEventHeader; - mcHeaderTree->SetBranchAddress("MCEventHeader.", &mcheader); - - o2::base::GeometryManager::loadGeometry(mHitRecoConfig["inputfiles"]["geometry"].get().c_str(), false, true); - auto* gman = o2::trk::GeometryTGeo::Instance(); - - const Long64_t nEvents{hitsTree->GetEntries()}; - LOGP(info, "Starting reconstruction from hits for {} events", nEvents); - - if (mMemoryPool.get() == nullptr) { - mMemoryPool = std::make_shared(); - } - if (mTaskArena.get() == nullptr) { - mTaskArena = std::make_shared(1); /// TODO: make it configurable - } - - o2::trk::TimeFrame<11> timeFrame; - o2::its::TrackerTraits<11> itsTrackerTraits; - o2::its::Tracker<11> itsTracker(&itsTrackerTraits); - timeFrame.setMemoryPool(mMemoryPool); - itsTrackerTraits.setMemoryPool(mMemoryPool); - itsTrackerTraits.setNThreads(mTaskArena->max_concurrency(), mTaskArena); - itsTrackerTraits.adoptTimeFrame(static_cast*>(&timeFrame)); - itsTracker.adoptTimeFrame(timeFrame); - itsTrackerTraits.setBz(mHitRecoConfig["geometry"]["bz"].get()); - auto field = new field::MagneticField("ALICE3Mag", "ALICE 3 Magnetic Field", mHitRecoConfig["geometry"]["bz"].get() / 5.f, 0.0, o2::field::MagFieldParam::k5kGUniform); - TGeoGlobalMagField::Instance()->SetField(field); - TGeoGlobalMagField::Instance()->Lock(); - - int nRofs = timeFrame.loadROFsFromHitTree(hitsTree, gman, mHitRecoConfig); - - const int inROFpileup{mHitRecoConfig.contains("inROFpileup") ? mHitRecoConfig["inROFpileup"].get() : 1}; - - // Add primary vertices from MC headers for each ROF - timeFrame.getPrimaryVerticesFromMC(mcHeaderTree, nRofs, nEvents, inROFpileup); - // Create tracking parameters from config and set them in the time frame - auto trackingParams = createTrackingParamsFromConfig(); - - itsTrackerTraits.updateTrackingParameters(trackingParams); - - const auto trackingLoopStart = std::chrono::steady_clock::now(); - for (size_t iter{0}; iter < trackingParams.size(); ++iter) { - LOGP(info, "{}", trackingParams[iter].asString()); - timeFrame.initialise(iter, trackingParams[iter], 11, false); - itsTrackerTraits.computeLayerTracklets(iter, -1, -1); - LOGP(info, "Number of tracklets in iteration {}: {}", iter, timeFrame.getNumberOfTracklets()); - itsTrackerTraits.computeLayerCells(iter); - LOGP(info, "Number of cells in iteration {}: {}", iter, timeFrame.getNumberOfCells()); - itsTrackerTraits.findCellsNeighbours(iter); - LOGP(info, "Number of cell neighbours in iteration {}: {}", iter, timeFrame.getNumberOfNeighbours()); - itsTrackerTraits.findRoads(iter); - LOGP(info, "Number of roads in iteration {}: {}", iter, timeFrame.getNumberOfTracks()); - itsTrackerTraits.extendTracks(iter); - } - const auto trackingLoopElapsedMs = std::chrono::duration_cast(std::chrono::steady_clock::now() - trackingLoopStart).count(); - LOGP(info, "Tracking iterations block took {} ms", trackingLoopElapsedMs); - - itsTracker.computeTracksMClabels(); - - // Stream tracks and their MC labels to the output - // Collect all tracks and labels from all ROFs - std::vector allTracks; - std::vector allLabels; - - int totalTracks = 0; - int goodTracks = 0; - int fakeTracks = 0; - - for (int iRof = 0; iRof < nRofs; ++iRof) { - const auto& rofTracks = timeFrame.getTracks(iRof); - const auto& rofLabels = timeFrame.getTracksLabel(iRof); - - allTracks.insert(allTracks.end(), rofTracks.begin(), rofTracks.end()); - allLabels.insert(allLabels.end(), rofLabels.begin(), rofLabels.end()); - - totalTracks += rofTracks.size(); - for (const auto& label : rofLabels) { - if (label.isFake()) { - fakeTracks++; - } else { - goodTracks++; - } - } - } - - LOGP(info, "=== Tracking Summary ==="); - LOGP(info, "Total tracks reconstructed: {}", totalTracks); - LOGP(info, "Good tracks: {} ({:.1f}%)", goodTracks, totalTracks > 0 ? 100.0 * goodTracks / totalTracks : 0); - LOGP(info, "Fake tracks: {} ({:.1f}%)", fakeTracks, totalTracks > 0 ? 100.0 * fakeTracks / totalTracks : 0); - - // Stream tracks and labels to DPL output - pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKS", 0}, allTracks); - pc.outputs().snapshot(o2::framework::Output{"TRK", "TRACKSMCTR", 0}, allLabels); - - LOGP(info, "Tracks and MC labels streamed to output"); - - pc.services().get().endOfStream(); - pc.services().get().readyToQuit(framework::QuitRequest::Me); - } - - mTimer.Stop(); - LOGP(info, "CPU Reconstruction time for this TF {} s (cpu), {} s (wall)", mTimer.CpuTime() - cput, mTimer.RealTime() - realt); -} - -// void TrackerDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) -// { -// // mITSTrackingInterface.finaliseCCDB(matcher, obj); -// } - -void TrackerDPL::endOfStream(EndOfStreamContext& ec) -{ - LOGF(info, "TRK CA-Tracker total timing: Cpu: %.3e Real: %.3e s in %d slots", mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); -} - -DataProcessorSpec getTrackerSpec(bool useMC, const std::string& hitRecoConfig, o2::gpu::gpudatatypes::DeviceType dType) -{ - std::vector inputs; - std::vector outputs; - outputs.emplace_back("TRK", "TRACKS", 0, Lifetime::Timeframe); - auto ggRequest = std::make_shared(false, // orbitResetTime - false, // GRPECS=true - false, // GRPLHCIF - false, // GRPMagField - false, // askMatLUT - o2::base::GRPGeomRequest::None, // geometry, but ignored until it will be put in the CCDB - inputs, - true); - - if (!hitRecoConfig.empty()) { - outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); - return DataProcessorSpec{ - "trk-hits-tracker", - {}, - outputs, - AlgorithmSpec{adaptFromTask(ggRequest, - useMC, - hitRecoConfig, - dType)}, - Options{ConfigParamSpec{"max-loops", VariantType::Int, 1, {"max number of loops"}}}}; - } - - inputs.emplace_back("dummy", "TRK", "DUMMY", 0, Lifetime::Timeframe); - - constexpr bool expectClusterInputs = false; - if (expectClusterInputs) { - inputs.pop_back(); - inputs.emplace_back("compClusters", "TRK", "COMPCLUSTERS", 0, Lifetime::Timeframe); - inputs.emplace_back("patterns", "TRK", "PATTERNS", 0, Lifetime::Timeframe); - inputs.emplace_back("ROframes", "TRK", "CLUSTERSROF", 0, Lifetime::Timeframe); - } - - // inputs.emplace_back("itscldict", "TRK", "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec("ITS/Calib/ClusterDictionary")); - // inputs.emplace_back("itsalppar", "TRK", "ALPIDEPARAM", 0, Lifetime::Condition, ccdbParamSpec("ITS/Config/AlpideParam")); - - // outputs.emplace_back("TRK", "TRACKCLSID", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "TRKTrackROF", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "VERTICES", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "VERTICESROF", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "IRFRAMES", 0, Lifetime::Timeframe); - - if (useMC) { - // inputs.emplace_back("trkmclabels", "TRK", "CLUSTERSMCTR", 0, Lifetime::Timeframe); - // inputs.emplace_back("TRKMC2ROframes", "TRK", "CLUSTERSMC2ROF", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "VERTICESMCTR", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "VERTICESMCPUR", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "TRACKSMCTR", 0, Lifetime::Timeframe); - // outputs.emplace_back("TRK", "TRKTrackMC2ROF", 0, Lifetime::Timeframe); - } - - return DataProcessorSpec{ - "trk-tracker", - inputs, - outputs, - AlgorithmSpec{adaptFromTask(ggRequest, - useMC, - hitRecoConfig, - dType)}, - Options{}}; -} - -} // namespace trk -} // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx b/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx index 166e6f65b4b2b..bd1d5acc9b9a7 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx @@ -9,21 +9,8 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -// 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. - #include "TRKWorkflow/RecoWorkflow.h" #include "CommonUtils/ConfigurableParam.h" -#include "ITStracking/TrackingConfigParam.h" -#include "ITStracking/Configuration.h" #include "Framework/CallbacksPolicy.h" #include "Framework/ConfigContext.h" @@ -52,11 +39,7 @@ void customize(std::vector& workflowOptions) {"clusters-from-upstream", VariantType::Bool, false, {"clusters will be provided from upstream, skip clusterizer"}}, {"disable-root-output", VariantType::Bool, false, {"do not write output root files"}}, {"disable-mc", VariantType::Bool, false, {"disable MC propagation even if available"}}, - {"tracking-from-hits-config", VariantType::String, "", {"JSON file with tracking from hits configuration"}}, - {"disable-tracking", VariantType::Bool, false, {"disable tracking step"}}, - {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, - {"use-gpu-workflow", VariantType::Bool, false, {"use GPU workflow (default: false)"}}, - {"gpu-device", VariantType::Int, 1, {"use gpu device: CPU=1,CUDA=2,HIP=3 (default: CPU)"}}}; + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; std::swap(workflowOptions, options); } @@ -67,9 +50,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) { // Update the (declared) parameters if changed from the command line auto useMC = !configcontext.options().get("disable-mc"); - auto hitRecoConfig = configcontext.options().get("tracking-from-hits-config"); - auto useGpuWF = configcontext.options().get("use-gpu-workflow"); - auto gpuDevice = static_cast(configcontext.options().get("gpu-device")); auto extDigits = configcontext.options().get("digits-from-upstream"); auto extClusters = configcontext.options().get("clusters-from-upstream"); auto disableRootOutput = configcontext.options().get("disable-root-output"); @@ -78,5 +58,5 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) // write the configuration used for the reco workflow o2::conf::ConfigurableParam::writeINI("o2itsrecoflow_configuration.ini"); - return o2::trk::reco_workflow::getWorkflow(useMC, hitRecoConfig, extDigits, extClusters, disableRootOutput, useGpuWF, gpuDevice); + return o2::trk::reco_workflow::getWorkflow(useMC, extDigits, extClusters, disableRootOutput); } diff --git a/Detectors/Upgrades/ITS3/alignment/CMakeLists.txt b/Detectors/Upgrades/ITS3/alignment/CMakeLists.txt index f89ad821c65e7..e04dfcbb43963 100644 --- a/Detectors/Upgrades/ITS3/alignment/CMakeLists.txt +++ b/Detectors/Upgrades/ITS3/alignment/CMakeLists.txt @@ -9,18 +9,40 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. +# add_compile_options(-O0 -g -fPIC -fno-omit-frame-pointer) o2_add_library(ITS3Align - SOURCES src/MisalignmentParameters.cxx - src/MisalignmentHits.cxx - src/MisalignmentManager.cxx - src/Deformations.cxx + TARGETVARNAME targetName + SOURCES src/AlignmentHierarchy.cxx + src/AlignmentDOF.cxx + src/AlignmentMath.cxx + src/MisalignmentUtils.cxx + src/AlignmentSensors.cxx + src/AlignmentParams.cxx + src/AlignmentTypes.cxx + src/AlignmentSpec.cxx PUBLIC_LINK_LIBRARIES O2::MathUtils O2::Steer O2::ITSBase - O2::ITSMFTSimulation) + O2::ITSMFTSimulation + O2::ITS3Reconstruction + O2::Framework + O2::GlobalTrackingWorkflowReaders + O2::GlobalTrackingWorkflowHelpers + O2::DataFormatsGlobalTracking + O2::DetectorsVertexing + nlohmann_json::nlohmann_json + GBL::GBL) +if (OpenMP_CXX_FOUND) + target_compile_definitions(${targetName} PRIVATE WITH_OPENMP) + target_link_libraries(${targetName} PRIVATE OpenMP::OpenMP_CXX) +endif() o2_target_root_dictionary(ITS3Align - HEADERS include/ITS3Align/MisalignmentParameters.h - include/ITS3Align/MisalignmentHits.h - include/ITS3Align/MisalignmentHits.h - include/ITS3Align/Deformations.h) + HEADERS include/ITS3Align/AlignmentParams.h + include/ITS3Align/AlignmentTypes.h) + + +o2_add_executable(alignment-workflow + SOURCES src/alignment-workflow.cxx + COMPONENT_NAME its3 + PUBLIC_LINK_LIBRARIES O2::ITS3Align) diff --git a/Detectors/Upgrades/ITS3/alignment/README.md b/Detectors/Upgrades/ITS3/alignment/README.md new file mode 100644 index 0000000000000..80213eb4e03b1 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/README.md @@ -0,0 +1,64 @@ +# Simulate ITS3 misalignment and re-alignment + + +```bash +o2-its3-alignment-workflow --track-sources ITS --output MilleData,MilleSteer --configKeyValues "ITS3AlignmentParams.minPt=0.1;ITS3AlignmentParams.doMisalignmentLeg=true;ITS3AlignmentParams.doMisalignmentRB=true;ITS3AlignmentParams.misAlgJson=test_closure.json;ITS3AlignmentParams.extraClsErrZ[0]=10e-4;ITS3AlignmentParams.extraClsErrY[0]=10e-4;ITS3AlignmentParams.extraClsErrZ[3]=10e-4;ITS3AlignmentParams.extraClsErrY[3]=10e-4;ITS3AlignmentParams.dofConfigJson=dofSet.json" -b --run +``` + +test_closure.json: +```json +[ + { + "id": 0, + "rigidBody": [0.001, 0.0005, 0.0, 0.0, 0.0001, 0.0], + "matrix": [[0.0], [0.0008, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]] + } +] +``` + +dofSet.json: +```json +{ + "defaults": { "rigidBody": "fixed" }, + "rules": [ + { + "match": "ITS3Layer0/ITS3CarbonForm0", + "rigidBody": ["TX", "TY", "RY"], + "calib": { "type": "legendre", "order": 1, "fix": [0, 2] } + } + ] +} +``` + + +## In-existensional modes +```json +{ + "defaults": { "rigidBody": "fixed" }, + "rules": [ + { + "match": "ITS3Layer1/ITS3CarbonForm0", + "calib": { + "type": "inextensional", + "order": 2, + "free": ["a_2", "b_2", "c_2", "d_2", "alpha", "beta"] + } + } + ] +} +``` + +```json +[ + { + "id": 2, + "inextensional": { + "modes": { + "2": [0.0008, -0.0005, 0.0006, -0.0007] + }, + "alpha": 0.0004, + "beta": -0.0003 + } + } +] +``` diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentDOF.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentDOF.h new file mode 100644 index 0000000000000..3fed9decbd6e7 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentDOF.h @@ -0,0 +1,176 @@ +// Copyright 2019-2026 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. + +#ifndef O2_ITS3_ALIGNMENT_DOF_H +#define O2_ITS3_ALIGNMENT_DOF_H + +#include +#include +#include +#include +#include +#include + +#include + +struct DerivativeContext { + int sensorID{-1}; + int layerID{-1}; + double measX{0.}; + double measAlpha{0.}; + double measZ{0.}; + double trkY{0.}; + double trkZ{0.}; + double snp{0.}; + double tgl{0.}; + double dydx{0.}; + double dzdx{0.}; +}; + +// Generic set of DOF +class DOFSet +{ + public: + enum class Type : uint8_t { + RigidBody, + Legendre, + Inextensional + }; + virtual ~DOFSet() = default; + virtual Type type() const = 0; + int nDOFs() const { return static_cast(mFree.size()); } + virtual std::string dofName(int idx) const = 0; + virtual void fillDerivatives(const DerivativeContext& ctx, Eigen::Ref out) const = 0; + bool isFree(int idx) const { return mFree[idx]; } + void setFree(int idx, bool f) { mFree[idx] = f; } + void setAllFree(bool f) { std::fill(mFree.begin(), mFree.end(), f); } + int nFreeDOFs() const + { + int n = 0; + for (bool f : mFree) { + n += f; + } + return n; + } + + protected: + DOFSet(int n) : mFree(n, true) {} + std::vector mFree; +}; + +// Rigid body set +class RigidBodyDOFSet final : public DOFSet +{ + public: + // indices for rigid body parameters in LOC frame + enum RigidBodyDOF : uint8_t { + TX = 0, + TY, + TZ, + RX, + RY, + RZ, + NDOF, + }; + static constexpr const char* RigidBodyDOFNames[RigidBodyDOF::NDOF] = {"TX", "TY", "TZ", "RX", "RY", "RZ"}; + + RigidBodyDOFSet() : DOFSet(NDOF) {} + // mask: bitmask of free DOFs (bit i = DOF i is free) + explicit RigidBodyDOFSet(uint8_t mask) : DOFSet(NDOF) + { + for (int i = 0; i < NDOF; ++i) { + mFree[i] = (mask >> i) & 1; + } + } + Type type() const override { return Type::RigidBody; } + std::string dofName(int idx) const override { return RigidBodyDOFNames[idx]; } + void fillDerivatives(const DerivativeContext& ctx, Eigen::Ref out) const override; + uint8_t mask() const + { + uint8_t m = 0; + for (int i = 0; i < NDOF; ++i) { + m |= (uint8_t(mFree[i]) << i); + } + return m; + } +}; + +// Legendre DOFs +// Describing radial misplacement +class LegendreDOFSet final : public DOFSet +{ + public: + explicit LegendreDOFSet(int order) : DOFSet((order + 1) * (order + 2) / 2), mOrder(order) {} + Type type() const override { return Type::Legendre; } + int order() const { return mOrder; } + std::string dofName(int idx) const override + { + int i = 0; + while ((i + 1) * (i + 2) / 2 <= idx) { + ++i; + } + int j = idx - (i * (i + 1) / 2); + return std::format("L({},{})", i, j); + } + void fillDerivatives(const DerivativeContext& ctx, Eigen::Ref out) const override; + + private: + int mOrder; +}; + +// In-extensional deformation DOFs for cylindrical half-shells +// Fourier modes n=2..N: 4 params each (a_n, b_n, c_n, d_n) +// Plus 2 non-periodic modes (alpha, beta) for the half-cylinder open edges +// Total: 4*(N-1) + 2 +class InextensionalDOFSet final : public DOFSet +{ + public: + explicit InextensionalDOFSet(int maxOrder) : DOFSet((4 * (maxOrder - 1)) + 2), mMaxOrder(maxOrder) + { + if (maxOrder < 2) { + // the rest is eq. to rigid body + throw std::invalid_argument("InextensionalDOFSet requires maxOrder >= 2"); + } + } + Type type() const override { return Type::Inextensional; } + int maxOrder() const { return mMaxOrder; } + + // number of periodic DOFs (before alpha, beta) + int nPeriodic() const { return 4 * (mMaxOrder - 1); } + + // flat index layout: [a_2, b_2, c_2, d_2, a_3, b_3, c_3, d_3, ..., alpha, beta] + // index of first DOF for mode n + static int modeOffset(int n) { return 4 * (n - 2); } + + // indices of the non-periodic modes + int alphaIdx() const { return nPeriodic(); } + int betaIdx() const { return nPeriodic() + 1; } + + std::string dofName(int idx) const override + { + if (idx == alphaIdx()) { + return "alpha"; + } + if (idx == betaIdx()) { + return "beta"; + } + int n = (idx / 4) + 2; + int sub = idx % 4; + static constexpr const char* subNames[] = {"a", "b", "c", "d"}; + return std::format("{}_{}", subNames[sub], n); + } + void fillDerivatives(const DerivativeContext& ctx, Eigen::Ref out) const override; + + private: + int mMaxOrder; +}; + +#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentHierarchy.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentHierarchy.h new file mode 100644 index 0000000000000..ae8989deec21b --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentHierarchy.h @@ -0,0 +1,169 @@ +// Copyright 2019-2026 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. + +#ifndef O2_ITS3_ALIGNMENT_HIERARCHY_H +#define O2_ITS3_ALIGNMENT_HIERARCHY_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "ITS3Align/AlignmentLabel.h" +#include "ITS3Align/AlignmentDOF.h" + +namespace o2::its3::align +{ +using Matrix36 = Eigen::Matrix; +using Matrix66 = Eigen::Matrix; + +class HierarchyConstraint +{ + public: + HierarchyConstraint(std::string name, double value) : mName(std::move(name)), mValue(value) {} + void add(uint32_t lab, double coeff) + { + mLabels.push_back(lab); + mCoeff.push_back(coeff); + } + void write(std::ostream& os) const; + auto getSize() const noexcept { return mLabels.size(); } + + private: + std::string mName; // name of the constraint + double mValue{0.0}; // constraint value + std::vector mLabels; // parameter labels + std::vector mCoeff; // their coefficients +}; + +class AlignableVolume +{ + public: + using Ptr = std::unique_ptr; + using SensorMapping = std::map; + + AlignableVolume(const AlignableVolume&) = delete; + AlignableVolume(AlignableVolume&&) = delete; + AlignableVolume& operator=(const AlignableVolume&) = delete; + AlignableVolume& operator=(AlignableVolume&&) = delete; + AlignableVolume(const char* symName, uint32_t label, uint32_t det, bool sens); + AlignableVolume(const char* symName, GlobalLabel label); + virtual ~AlignableVolume() = default; + + void finalise(uint8_t level = 0); + + // steering file output + void writeRigidBodyConstraints(std::ostream& os) const; + void writeParameters(std::ostream& os) const; + void writeTree(std::ostream& os, int indent = 0) const; + + // tree-like + auto getLevel() const noexcept { return mLevel; } + bool isRoot() const noexcept { return mParent == nullptr; } + bool isLeaf() const noexcept { return mChildren.empty(); } + template + requires std::derived_from + AlignableVolume* addChild(const char* symName, uint32_t label, uint32_t det, bool sens) + { + auto c = std::make_unique(symName, label, det, sens); + return setParent(std::move(c)); + } + template + requires std::derived_from + AlignableVolume* addChild(const char* symName, GlobalLabel lbl) + { + auto c = std::make_unique(symName, lbl); + return setParent(std::move(c)); + } + + // bfs traversal + void traverse(const std::function& visitor) + { + visitor(this); + for (auto& c : mChildren) { + c->traverse(visitor); + } + } + + std::string getSymName() const noexcept { return mSymName; } + GlobalLabel getLabel() const noexcept { return mLabel; } + AlignableVolume* getParent() const { return mParent; } + size_t getNChildren() const noexcept { return mChildren.size(); } + + // DOF management + void setRigidBody(std::unique_ptr rb) { mRigidBody = std::move(rb); } + void setCalib(std::unique_ptr cal) { mCalib = std::move(cal); } + DOFSet* getRigidBody() const { return mRigidBody.get(); } + DOFSet* getCalib() const { return mCalib.get(); } + void setPseudo(bool p) noexcept { mIsPseudo = p; } + bool isPseudo() const noexcept { return mIsPseudo; } + void setSensorId(int id) noexcept { mSensorId = id; } + int getSensorId() const noexcept { return mSensorId; } + // true if this volume participates in the hierarchy (has DOFs or is pseudo) + bool isActive() const noexcept { return mRigidBody != nullptr || mIsPseudo; } + + // transformation matrices + virtual void defineMatrixL2G() {} + virtual void defineMatrixT2L() {} + virtual void computeJacobianL2T(const double* pos, Matrix66& jac) const {}; + const TGeoHMatrix& getL2P() const { return mL2P; } + const TGeoHMatrix& getT2L() const { return mT2L; } + const Matrix66& getJL2P() const { return mJL2P; } + const Matrix66& getJP2L() const { return mJP2L; } + + protected: + /// matrices + AlignableVolume* mParent{nullptr}; // parent + TGeoPNEntry* mPNE{nullptr}; // physical entry + TGeoPhysicalNode* mPN{nullptr}; // physical node + TGeoHMatrix mL2G; // (LOC) -> (GLO) + TGeoHMatrix mL2P; // (LOC) -> (PAR) + Matrix66 mJL2P; // jac (LOC) -> (PAR) + Matrix66 mJP2L; // jac (PAR) -> (LOC) + TGeoHMatrix mT2L; // (TRK) -> (LOC) + + private: + std::string mSymName; + GlobalLabel mLabel; + uint8_t mLevel{0}; + bool mIsPseudo{false}; + int mSensorId{-1}; + std::unique_ptr mRigidBody; + std::unique_ptr mCalib; + + AlignableVolume* setParent(Ptr c) + { + c->mParent = this; + mChildren.push_back(std::move(c)); + return mChildren.back().get(); + } + std::vector mChildren; // children + + void init(); +}; + +// apply DOF configuration from a JSON file to the hierarchy +void applyDOFConfig(AlignableVolume* root, const std::string& jsonPath); + +// parse millepede.res and write result.json with fitted parameters for ITS3 half barrels +void writeMillepedeResults(AlignableVolume* root, const std::string& milleResPath, const std::string& outJsonPath, const std::string& injectedJsonPath = ""); + +} // namespace o2::its3::align + +#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentLabel.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentLabel.h new file mode 100644 index 0000000000000..83495491b87e0 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentLabel.h @@ -0,0 +1,87 @@ +// Copyright 2019-2026 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. + +#ifndef O2_ITS3_ALIGNMENT_LABEL_H +#define O2_ITS3_ALIGNMENT_LABEL_H + +#include +#include +#include + +class GlobalLabel +{ + // Millepede label is any positive integer [1....) + // Layout: DOF(5) | CALIB(1) | ID(22) | SENS(1) | DET(2) = 31 usable bits (MSB reserved, GBL uses signed int) + public: + using T = uint32_t; + static constexpr int DOF_BITS = 5; // bits 0-4 + static constexpr int CALIB_BITS = 1; // bit 5: 0 = rigid body, 1 = calibration (only allow for one calibration, could be extended if needed) + static constexpr int ID_BITS = 22; // bits 6-27 + static constexpr int SENS_BITS = 1; // bit 28 + static constexpr int TOTAL_BITS = sizeof(T) * 8; + static constexpr int DET_BITS = TOTAL_BITS - (DOF_BITS + CALIB_BITS + ID_BITS + SENS_BITS) - 1; // one less bit since GBL uses int! + static constexpr T bitMask(int b) noexcept + { + return (T(1) << b) - T(1); + } + static constexpr int DOF_SHIFT = 0; + static constexpr T DOF_MAX = (T(1) << DOF_BITS) - T(1); + static constexpr T DOF_MASK = DOF_MAX << DOF_SHIFT; + static constexpr int CALIB_SHIFT = DOF_BITS; + static constexpr T CALIB_MAX = (T(1) << CALIB_BITS) - T(1); + static constexpr T CALIB_MASK = CALIB_MAX << CALIB_SHIFT; + static constexpr int ID_SHIFT = DOF_BITS + CALIB_BITS; + static constexpr T ID_MAX = (T(1) << ID_BITS) - T(1); + static constexpr T ID_MASK = ID_MAX << ID_SHIFT; + static constexpr int SENS_SHIFT = DOF_BITS + CALIB_BITS + ID_BITS; + static constexpr T SENS_MAX = (T(1) << SENS_BITS) - T(1); + static constexpr T SENS_MASK = SENS_MAX << SENS_SHIFT; + static constexpr int DET_SHIFT = DOF_BITS + CALIB_BITS + ID_BITS + SENS_BITS; + static constexpr T DET_MAX = (T(1) << DET_BITS) - T(1); + static constexpr T DET_MASK = DET_MAX << DET_SHIFT; + + GlobalLabel(T det, T id, bool sens, bool calib = false) + : mID((((id + 1) & ID_MAX) << ID_SHIFT) | + ((det & DET_MAX) << DET_SHIFT) | + ((T(sens) & SENS_MAX) << SENS_SHIFT) | + ((T(calib) & CALIB_MAX) << CALIB_SHIFT)) + { + } + + /// produce the raw Millepede label for a given DOF index (rigid body: calib=0 in label) + constexpr T raw(T dof) const noexcept { return (mID & ~DOF_MASK) | ((dof & DOF_MAX) << DOF_SHIFT); } + constexpr int rawGBL(T dof) const noexcept { return static_cast(raw(dof)); } + + /// return a copy of this label with the CALIB bit set (for calibration DOFs on same volume) + GlobalLabel asCalib() const noexcept + { + GlobalLabel c{*this}; + c.mID |= (T(1) << CALIB_SHIFT); + return c; + } + + constexpr T id() const noexcept { return ((mID >> ID_SHIFT) & ID_MAX) - 1; } + constexpr T det() const noexcept { return (mID & DET_MASK) >> DET_SHIFT; } + constexpr bool sens() const noexcept { return (mID & SENS_MASK) >> SENS_SHIFT; } + constexpr bool calib() const noexcept { return (mID & CALIB_MASK) >> CALIB_SHIFT; } + + std::string asString() const + { + return std::format("Det:{} Id:{} Sens:{} Calib:{}", det(), id(), sens(), calib()); + } + + constexpr auto operator<=>(const GlobalLabel&) const noexcept = default; + + private: + T mID{0}; +}; + +#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentMath.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentMath.h new file mode 100644 index 0000000000000..9409648dca3e4 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentMath.h @@ -0,0 +1,32 @@ +// Copyright 2019-2026 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. + +#ifndef O2_ITS3_ALIGNMENT_MATH_H +#define O2_ITS3_ALIGNMENT_MATH_H + +#include +#include + +namespace o2::its3::align +{ + +struct TrackSlopes { + double dydx{0.}; + double dzdx{0.}; +}; + +std::pair computeUV(double gloX, double gloY, double gloZ, int sensorID, double radius); +TrackSlopes computeTrackSlopes(double snp, double tgl); +std::vector legendrePols(int order, double x); + +} // namespace o2::its3::align + +#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentParams.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentParams.h new file mode 100644 index 0000000000000..5a11066fd3c3b --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentParams.h @@ -0,0 +1,68 @@ +// 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. +#ifndef ALICEO2_ITS3_ALIGNMENTPARAMS_H_ +#define ALICEO2_ITS3_ALIGNMENTPARAMS_H_ + +#include "CommonUtils/ConfigurableParam.h" +#include "CommonUtils/ConfigurableParamHelper.h" +#include "DetectorsBase/Propagator.h" + +namespace o2::its3::align +{ +struct AlignmentParams : public o2::conf::ConfigurableParamHelper { + // Track selection + float minPt = 1.f; // minimum pt required + int minITSCls = 7; // minimum number of ITS clusters + float maxITSChi2Ndf = 1.2; // maximum ITS track chi2 + + // propagation opt + double maxSnp = 0.85; + double maxStep = 2.0; + // o2::base::PropagatorD::MatCorrType matCorrType = o2::base::PropagatorD::MatCorrType::USEMatCorrTGeo; + o2::base::PropagatorD::MatCorrType corrType = o2::base::PropagatorD::MatCorrType::USEMatCorrLUT; + + bool useStableRef = true; // use input tracks as linearization point + float minMS = 1e-6f; // minimum scattering to account for + float maxChi2Ndf = 10; // maximum Chi2/Ndf allowed for GBL fit + + // per chip extra error + float extraClsErrY[6] = {0}; + float extraClsErrZ[6] = {0}; + + // misalignment simulation + bool doMisalignmentLeg = false; // simulate Legendre deformation on ITS3 layers + bool doMisalignmentRB = false; // simulate rigid body misalignment on ITS3 layers + bool doMisalignmentInex = false; // simulate in-extensional deformation on ITS3 layers + std::string misAlgJson; // JSON file with deformation and/or rigid body params + + // DOF configuration (JSON file defining which volumes have which DOFs) + std::string dofConfigJson; // if empty, no DOFs are configured + + // Ridder options + int ridderMaxExtrap = 10; + double ridderRelIniStep[5] = {0.01, 0.01, 0.02, 0.02, 0.02}; + double ridderMaxIniStep[5] = {0.1, 0.1, 0.05, 0.05, 0.05}; + double ridderShrinkFac = 2.0; + double ridderEps = 1e-16; + + // MillePede output + std::string milleBinFile = "mp2data.bin"; + std::string milleConFile = "mp2con.txt"; + std::string milleParamFile = "mp2param.txt"; + std::string milleTreeFile = "mp2tree.txt"; + std::string milleResFile = "millepede.res"; + std::string milleResOutJson = "result.json"; + + O2ParamDef(AlignmentParams, "ITS3AlignmentParams"); +}; +} // namespace o2::its3::align + +#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentSensors.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentSensors.h new file mode 100644 index 0000000000000..535f67156a16c --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentSensors.h @@ -0,0 +1,41 @@ +// Copyright 2019-2026 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. + +#ifndef O2_ITS3_ALIGNMENT_SENSORS_H +#define O2_ITS3_ALIGNMENT_SENSORS_H + +#include "ITS3Align/AlignmentHierarchy.h" + +namespace o2::its3::align +{ + +AlignableVolume::Ptr buildHierarchyITS(AlignableVolume::SensorMapping& sensorMap); +AlignableVolume::Ptr buildHierarchyIT3(AlignableVolume::SensorMapping& sensorMap); + +class AlignableSensorITS final : public AlignableVolume +{ + using AlignableVolume::AlignableVolume; + void defineMatrixL2G() final; + void defineMatrixT2L() final; + void computeJacobianL2T(const double* pos, Matrix66& jac) const final; +}; + +class AlignableSensorIT3 final : public AlignableVolume +{ + using AlignableVolume::AlignableVolume; + void defineMatrixL2G() final; + void defineMatrixT2L() final; + void computeJacobianL2T(const double* pos, Matrix66& jac) const final; +}; + +} // namespace o2::its3::align + +#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentSpec.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentSpec.h new file mode 100644 index 0000000000000..d171454bc4794 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentSpec.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef O2_ITS3_ALIGNMENT_H +#define O2_ITS3_ALIGNMENT_H + +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include "Framework/DataProcessorSpec.h" +#include "CommonUtils/EnumFlags.h" + +namespace o2::its3::align +{ + +enum class OutputOpt : uint8_t { + VerboseGBL = 0, + MilleData, + MilleSteer, + MilleRes, + MisRes, + Debug, +}; +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); +} // namespace o2::its3::align + +#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentTypes.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentTypes.h new file mode 100644 index 0000000000000..6dc84b2323d35 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentTypes.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef O2_ITS3_ALIGNMENT_TYPES_H +#define O2_ITS3_ALIGNMENT_TYPES_H + +#include +#include "ReconstructionDataFormats/Track.h" +#include "DataFormatsITS/TrackITS.h" + +namespace o2::its3::align +{ + +struct Measurement final { + double dy = 0.f; + double dz = 0.f; + double sig2y = 0.f; + double sig2z = 0.f; + double phi = 0.f; + double z = 0.f; + ClassDefNV(Measurement, 1) +}; + +struct FrameInfoExt final { + int16_t sens = -1; + int8_t lr = -1; // -1 = vtx + double x{-999.f}; + double alpha{-999.f}; + std::array positionTrackingFrame = {999., 999.}; + std::array covarianceTrackingFrame = {999., 999., 999.}; + + std::string asString() const; + + ClassDefNV(FrameInfoExt, 1) +}; + +struct FitInfo final { + float chi2Ndf{-1}; // Chi2/Ndf of track refit + float chi2{-1}; // Chi2 + int ndf{-1}; // ndf + ClassDefNV(FitInfo, 1) +}; + +struct Track { + o2::its::TrackITS its; // original ITS track + o2::track::TrackParCovD track; // track at innermost update point, refitted from outwards seed + FitInfo kfFit; // kf fit information + FitInfo gblFit; // gbl fit information + std::vector points; // measurment point + std::vector info; // frame info + ClassDefNV(Track, 1) +}; + +} // namespace o2::its3::align + +#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/Deformations.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/Deformations.h deleted file mode 100644 index dfaade51e82ff..0000000000000 --- a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/Deformations.h +++ /dev/null @@ -1,84 +0,0 @@ -// 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. - -#ifndef ITS3_DEFORMATIONS_H_ -#define ITS3_DEFORMATIONS_H_ - -#include "ITS3Align/MisalignmentParameters.h" -#include "MathUtils/LegendrePols.h" - -#include - -namespace o2::its3::align -{ - -class Deformations -{ - public: - // init deformations from the parameter file - void init(const std::filesystem::path&); - - double getDeformationX(unsigned int id, double u, double v) const { return getDeformation<0>(id, u, v); } - double getDeformationY(unsigned int id, double u, double v) const { return getDeformation<1>(id, u, v); } - double getDeformationZ(unsigned int id, double u, double v) const { return getDeformation<2>(id, u, v); } - double getDeformation(unsigned int id, unsigned int axis, double u, double v) const - { - if (axis == 0) { - return mLegX[id](u, v); - } else if (axis == 1) { - return mLegY[id](u, v); - } else { - return mLegZ[id](u, v); - } - } - std::array getDeformation(unsigned int id, double u, double v) const - { - return {getDeformation<0>(id, u, v), - getDeformation<1>(id, u, v), - getDeformation<2>(id, u, v)}; - } - std::array getOrders(unsigned int id) const - { - return {mLegX[id].NOrder(), mLegY[id].NOrder(), mLegZ[id].NOrder()}; - } - const o2::math_utils::Legendre2DPolynominal& getLegendre(unsigned int id, unsigned int axis) const - { - if (axis == 0) { - return mLegX[id]; - } else if (axis == 1) { - return mLegY[id]; - } else { - return mLegZ[id]; - } - } - - private: - template - double getDeformation(unsigned int id, double u, double v) const - { - if constexpr (axis == 0) { - return mLegX[id](u, v); - } else if constexpr (axis == 1) { - return mLegY[id](u, v); - } else { - return mLegZ[id](u, v); - } - } - - // 3 Legendre polynominals to model deformations in x,y,z; parameterized by normalized phi (u) and z (v) coordinates - std::array mLegX; - std::array mLegY; - std::array mLegZ; -}; - -} // namespace o2::its3::align - -#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentHits.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentHits.h deleted file mode 100644 index 37f5c9fdf701d..0000000000000 --- a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentHits.h +++ /dev/null @@ -1,216 +0,0 @@ -// 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. - -#ifndef ITS3_MISALIGNMENTHITS_H_ -#define ITS3_MISALIGNMENTHITS_H_ - -#include "Math/IFunction.h" -#include "Math/Minimizer.h" - -#include "ReconstructionDataFormats/Track.h" -#include "ITS3Align/Deformations.h" -#include "ITSBase/GeometryTGeo.h" -#include "ITSMFTSimulation/Hit.h" -#include "MathUtils/Cartesian.h" -#include "MathUtils/Utils.h" -#include "Steer/MCKinematicsReader.h" - -#include -#include -#include -#include -#include - -namespace o2::its3::align -{ - -class MisAlignmentHits -{ - public: - enum class PropMethod { - Propagator, - Line, - }; - - void init(); - - std::optional processHit(int iEvent, const o2::itsmft::Hit& hit); - - void resetStats() { mStats.fill(0ull); } - void printStats() const; - - private: - Deformations mDeformations; - std::unique_ptr mMinimizer; - PropMethod mMethod{PropMethod::Line}; - o2::its::GeometryTGeo* mGeo{nullptr}; - std::unique_ptr mMCReader; - - short getDetID(const o2::math_utils::Point3D& point); - short getDetIDFromCords(const o2::math_utils::Point3D& point); - short getDetIDFromPath(const std::string& path) const; - - // We treat each hit as two separate hits', one for the entering and one for the exiting hit - struct WorkingHit { - enum HitType : uint8_t { - kEntering = 0, - kExiting, - kTypes, - }; - - WorkingHit() = default; - - WorkingHit(int eventID, HitType t, const o2::itsmft::Hit& hit) : mEvent(eventID), - mTrackID(hit.GetTrackID()), - mType(t), - mDetID(hit.GetDetectorID()), - mLayerID(constants::detID::getDetID2Layer(mDetID)), - mSensorID(constants::detID::getSensorID(mDetID)) - { - if (mType == kEntering) { - mRadius = constants::radiiInner[mLayerID]; - mPoint = hit.GetPosStart(); - } else { - mRadius = constants::radiiOuter[mLayerID]; - mPoint = hit.GetPos(); - } - - // Pre-calculate the normalized u,v coordinates as starting parameters - const bool isTop = mSensorID % 2 == 0; - mPhi = o2::math_utils::to02Pi(std::atan2(mPoint.Y(), mPoint.X())); - mPhiBorder1 = o2::math_utils::to02Pi(((isTop) ? 0.f : 1.f) * TMath::Pi() + std::asin(constants::equatorialGap / 2.f / mRadius)); - mPhiBorder2 = o2::math_utils::to02Pi(((isTop) ? 1.f : 2.f) * TMath::Pi() - std::asin(constants::equatorialGap / 2.f / mRadius)); - mU = ((mPhi - mPhiBorder1) * 2.f) / (mPhiBorder2 - mPhiBorder1) - 1.f; - mV = (2.f * mPoint.Z() + constants::segment::lengthSensitive) / constants::segment::lengthSensitive - 1.f; - } - - void recalculateIdeal(float phi, float z) - { - mPointDef.SetX(mRadius * std::cos(phi)); - mPointDef.SetY(mRadius * std::sin(phi)); - mPointDef.SetZ(z); - } - - int mEvent; - int mTrackID; - HitType mType; - short mDetID; - int mLayerID; - int mSensorID; - float mRadius; - float mPhi; - o2::math_utils::Point3D mPoint; - o2::math_utils::Point3D mPointDef; - float mU; // u is normalized phi - float mV; // u is normalized z - - float mPhiBorder1; - float mPhiBorder2; - }; - std::array mCurWorkingHits; - o2::itsmft::Hit mCurHit; - - bool deformHit(WorkingHit::HitType t); - - auto getDeformation(unsigned int id, double u, double v) const - { - return mDeformations.getDeformation(id, u, v); - } - - // Mimize function assuming a straight line - // given in the parametric representation by y_v = t * d_x + x_s - // assuming no offset is needed - class StraightLine : public ROOT::Math::IBaseFunctionMultiDim - { - public: - StraightLine(const MisAlignmentHits* m) : mMis(m) {} - - std::array mD; - o2::math_utils::Point3D mStart; - unsigned int mSensorID; - double mRadius; - const MisAlignmentHits* mMis; - - double mPhiTot; - double mPhi1; - - unsigned int NDim() const override { return 3; } - ROOT::Math::IBaseFunctionMultiDim* Clone() const override { return nullptr; } - - private: - double DoEval(const double* x) const override; - }; - StraightLine mLine{this}; - void prepareLineMethod(WorkingHit::HitType from); - - // Mimize function using the MCTrack - class Propagator : public ROOT::Math::IBaseFunctionMultiDim - { - public: - Propagator(const MisAlignmentHits* m) : mMis(m) {} - - o2::track::TrackPar mTrack; - float mBz; - unsigned int mSensorID; - double mRadius; - const MisAlignmentHits* mMis; - - double mPhiTot; - double mPhi1; - - unsigned int NDim() const override { return 3; } - ROOT::Math::IBaseFunctionMultiDim* Clone() const override { return nullptr; } - - private: - double DoEval(const double* x) const override; - }; - Propagator mPropagator{this}; - bool preparePropagatorMethod(WorkingHit::HitType from); - - enum Stats : uint8_t { - kHitTotal = 0, - kHitIsOB, - kHitIsIB, - kHitDead, - kHitAlive, - kHitSuccess, - kHitMigrated, - kHitNotMigrated, - kHitEntBoundary, - kHitExtBoundary, - kHitNoBoundary, - kHitSameBoundary, - kFindNodeFailed, - kFindNodeSuccess, - kProjSensitive, - kProjNonSensitive, - kDetIDOk, - kDetIDBad, - kMinimizerStatusOk, - kMinimizerStatusBad, - kMinimizerValueOk, - kMinimizerValueBad, - kMinimizerConverged, - kMinimizerCovPos, - kMinimizerHesse, - kMinimizerEDM, - kMinimizerLimit, - kMinimizerOther, - kPropTrackNull, - kPropPDGNull, - kALL, - }; - std::array mStats; -}; - -} // namespace o2::its3::align - -#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentManager.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentManager.h deleted file mode 100644 index 0fe972442809d..0000000000000 --- a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentManager.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020-2022 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. - -#ifndef ITS3_MISALIGNMENTMANAGER_H_ -#define ITS3_MISALIGNMENTMANAGER_H_ - -#include "Math/Transform3D.h" -#include "Math/Translation3D.h" -#include "Math/Rotation3D.h" -#include "Math/EulerAngles.h" -#include "Math/PositionVector3D.h" -#include "TGeoMatrix.h" - -#include - -namespace o2::its3::align -{ - -/// Collection of static functions and types to perform misalignment studies -struct MisalignmentManager { - using Vector3D = ROOT::Math::DisplacementVector3D, ROOT::Math::DefaultCoordinateSystemTag>; - using Point3D = ROOT::Math::PositionVector3D, ROOT::Math::DefaultCoordinateSystemTag>; - using Trans3D = ROOT::Math::Translation3DF; - using Rot3D = ROOT::Math::Rotation3D; - using Euler3D = ROOT::Math::EulerAngles; - using Trafo3D = ROOT::Math::Transform3DF; - - static void misalignHits(); - - static void createBackup(const std::filesystem::path& src, const std::filesystem::path& dest); - - static std::string appendStem(const std::string& filename, const std::string& add); - - static std::vector split(const std::string& s, char delimiter = '/'); - - static void navigate(const std::string& path); - - static std::string composePathSensor(int sensor); - - static void applyGlobalMatrixVolume(const std::string& path, const TGeoHMatrix& globalMatrix); -}; - -} // namespace o2::its3::align - -#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentParameters.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentParameters.h deleted file mode 100644 index 243623cc650e1..0000000000000 --- a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentParameters.h +++ /dev/null @@ -1,93 +0,0 @@ -// 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 MisalignmentParameters.h -/// \brief Definition of the MisalignmentParameters class - -#ifndef ITS3_MISALIGNMENTPARAMETERS_H_ -#define ITS3_MISALIGNMENTPARAMETERS_H_ - -#include "ITS3Base/SpecsV2.h" - -#include "TNamed.h" -#include "TFile.h" -#include "TMatrixD.h" - -#include -#include - -namespace o2::its3::align -{ - -class MisalignmentParameters : public TNamed -{ - public: - MisalignmentParameters(); - - // IO - bool store(const std::string& file) const; - static MisalignmentParameters* load(const std::string& file); - - /// Global getters - double getGloTransX(unsigned int detID) const { return mGloTransX[detID]; } - double getGloTransY(unsigned int detID) const { return mGloTransY[detID]; } - double getGloTransZ(unsigned int detID) const { return mGloTransZ[detID]; } - double getGloRotX(unsigned int detID) const { return mGloRotX[detID]; } - double getGloRotY(unsigned int detID) const { return mGloRotY[detID]; } - double getGloRotZ(unsigned int detID) const { return mGloRotZ[detID]; } - /// Global setters - void setGloTransX(unsigned int detID, double v) { mGloTransX[detID] = v; } - void setGloTransY(unsigned int detID, double v) { mGloTransY[detID] = v; } - void setGloTransZ(unsigned int detID, double v) { mGloTransZ[detID] = v; } - void setGloRotX(unsigned int detID, double v) { mGloRotX[detID] = v; } - void setGloRotY(unsigned int detID, double v) { mGloRotY[detID] = v; } - void setGloRotZ(unsigned int detID, double v) { mGloRotZ[detID] = v; } - - /// Legendre Coeff. getters - const TMatrixD& getLegendreCoeffX(unsigned int sensorID) const { return mLegCoeffX[sensorID]; } - const TMatrixD& getLegendreCoeffY(unsigned int sensorID) const { return mLegCoeffY[sensorID]; } - const TMatrixD& getLegendreCoeffZ(unsigned int sensorID) const { return mLegCoeffZ[sensorID]; } - /// Legendre Coeff. setters - void setLegendreCoeffX(unsigned int sensorID, const TMatrixD& m) { setMatrix(mLegCoeffX[sensorID], m); } - void setLegendreCoeffY(unsigned int sensorID, const TMatrixD& m) { setMatrix(mLegCoeffY[sensorID], m); } - void setLegendreCoeffZ(unsigned int sensorID, const TMatrixD& m) { setMatrix(mLegCoeffZ[sensorID], m); } - - void printParams(unsigned int detID) const; - void printLegendreParams(unsigned int sensorID) const; - - private: - inline void setMatrix(TMatrixD& o, const TMatrixD& n) - { - o.ResizeTo(n.GetNrows(), n.GetNcols()); - o = n; - } - - static constexpr unsigned int nDetectors{constants::detID::nChips}; ///! for now just the IB - - // Global parameters - std::array mGloTransX; ///< Array to hold the global misalignment in x-direction - std::array mGloTransY; ///< Array to hold the global misalignment in y-direction - std::array mGloTransZ; ///< Array to hold the global misalignment in z-direction - std::array mGloRotX; ///< Array to hold the global misalignment in x-direction - std::array mGloRotY; ///< Array to hold the global misalignment in y-direction - std::array mGloRotZ; ///< Array to hold the global misalignment in z-direction - - // Legendre Polynominals coefficients - std::array mLegCoeffX; - std::array mLegCoeffY; - std::array mLegCoeffZ; - - ClassDefOverride(MisalignmentParameters, 1); -}; - -} // namespace o2::its3::align - -#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentUtils.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentUtils.h new file mode 100644 index 0000000000000..457eccaeff4e6 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentUtils.h @@ -0,0 +1,79 @@ +// Copyright 2019-2026 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. + +#ifndef O2_ITS3_ALIGNMENT_MISALIGNMENTUTILS_H +#define O2_ITS3_ALIGNMENT_MISALIGNMENTUTILS_H + +#include +#include +#include +#include +#include + +#include "ITS3Align/AlignmentMath.h" +#include "MathUtils/LegendrePols.h" + +namespace o2::its3::align +{ + +struct InextensionalMisalignment { + std::map> modes; // n -> (a_n, b_n, c_n, d_n) + double alpha{0.}; + double beta{0.}; +}; + +struct SensorMisalignment { + o2::math_utils::Legendre2DPolynominal legendre; + bool hasLegendre{false}; + InextensionalMisalignment inextensional; + bool hasInextensional{false}; + + bool empty() const noexcept { return !hasLegendre && !hasInextensional; } +}; + +struct MisalignmentModel { + static constexpr std::size_t NSensors = 6; + std::array sensors{}; + + bool empty() const noexcept; + const SensorMisalignment& operator[](std::size_t idx) const { return sensors[idx]; } + SensorMisalignment& operator[](std::size_t idx) { return sensors[idx]; } +}; + +struct MisalignmentFrame { + int sensorID{-1}; + int layerID{-1}; + double x{0.}; // tracking-frame X / nominal radius at the measurement + double alpha{0.}; // tracking-frame alpha + double z{0.}; // tracking-frame measurement z +}; + +struct MisalignmentShift { + double dy{0.}; + double dz{0.}; + bool accepted{true}; + + MisalignmentShift& operator+=(const MisalignmentShift& other) + { + dy += other.dy; + dz += other.dz; + accepted = accepted && other.accepted; + return *this; + } +}; + +MisalignmentModel loadMisalignmentModel(const std::string& jsonPath); +MisalignmentShift evaluateLegendreShift(const SensorMisalignment& sensor, const MisalignmentFrame& frame, const TrackSlopes& slopes); +MisalignmentShift evaluateInextensionalShift(const SensorMisalignment& sensor, const MisalignmentFrame& frame, const TrackSlopes& slopes); + +} // namespace o2::its3::align + +#endif diff --git a/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/TrackFit.h b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/TrackFit.h new file mode 100644 index 0000000000000..4625776398c89 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/include/ITS3Align/TrackFit.h @@ -0,0 +1,177 @@ +// Copyright 2019-2026 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. + +#ifndef O2_ITS3_ALIGN_TRACKFIT +#define O2_ITS3_ALIGN_TRACKFIT + +#include + +#include "ITSBase/GeometryTGeo.h" +#include "DetectorsBase/Propagator.h" +#include "ReconstructionDataFormats/Track.h" +#include "DataFormatsITS/TrackITS.h" +#include "DataFormatsGlobalTracking/RecoContainer.h" + +namespace o2::its3::align +{ +using Mat51 = Eigen::Matrix; +using Mat55 = Eigen::Matrix; +using TrackD = o2::track::TrackParCovD; + +template +struct TrackingCluster : public o2::BaseCluster { + using o2::BaseCluster::BaseCluster; + T alpha{}; +}; + +template +track::TrackParametrizationWithError convertTrack(const track::TrackParametrizationWithError& trk) +{ + if constexpr (std::is_same_v) { + return trk; + } + track::TrackParametrizationWithError dst; + dst.setX(trk.getX()); + dst.setAlpha(trk.getAlpha()); + for (int iPar{0}; iPar < track::kNParams; ++iPar) { + dst.setParam(trk.getParam(iPar), iPar); + } + dst.setAbsCharge(trk.getAbsCharge()); + dst.setPID(trk.getPID()); + dst.setUserField(trk.getUserField()); + for (int iCov{0}; iCov < track::kCovMatSize; ++iCov) { + dst.setCov(trk.getCov()[iCov], iCov); + } + return dst; +} + +// Both tracks must be at the same (alpha, x). +// Returns the interpolated track. +template +o2::track::TrackParametrizationWithError interpolateTrackParCov( + const o2::track::TrackParametrizationWithError& tA, + const o2::track::TrackParametrizationWithError& tB) +{ + auto res = tA; + if (!tA.isValid() || !tB.isValid() || tA.getAlpha() != tB.getAlpha() || tA.getX() != tB.getX()) { + res.invalidate(); + return res; + } + auto unpack = [](const std::array& c) { + Mat55 m; + for (int i = 0, k = 0; i < 5; ++i) { + for (int j = 0; j <= i; ++j, ++k) { + m(i, j) = m(j, i) = (double)c[k]; + } + } + return m; + }; + Mat55 cA = unpack(tA.getCov()); + Mat55 cB = unpack(tB.getCov()); + Eigen::LLT lltA(cA), lltB(cB); + Mat55 wA = lltA.solve(Mat55::Identity()); + Mat55 wB = lltB.solve(Mat55::Identity()); + Mat55 wTot = wA + wB; + Eigen::LLT lltTot(wTot); + Mat55 cTot = lltTot.solve(Mat55::Identity()); + Mat51 pA, pB; + for (int i = 0; i < 5; ++i) { + pA(i) = tA.getParam(i); + pB(i) = tB.getParam(i); + } + Mat51 pTot = cTot * (wA * pA + wB * pB); + // build result - same alpha/x as inputs + for (int i = 0; i < 5; ++i) { + res.setParam(pTot(i), i); + } + for (int i = 0, k = 0; i < 5; ++i) { + for (int j = 0; j <= i; ++j, ++k) { + res.setCov(static_cast(cTot(i, j)), k); + } + } + return res; +} + +// Performs an outward (0->7) and inward (7->0) Kalman refit storing the +// extrapolation *before* the cluster update at each layer. +// cluster array clArr[0] = PV (optional), clArr[1..7] = layers 0-6. +// chi2 is accumulated only for the outward direction +template +bool doBidirRefit( + const o2::its::TrackITS& iTrack, + std::array*, 8>& clArr, + std::array, 8>& extrapOut, + std::array, 8>& extrapInw, + T& chi2, + bool useStableRef, + typename o2::base::PropagatorImpl::MatCorrType corrType) +{ + const auto prop = o2::base::PropagatorImpl::Instance(); + const auto geom = o2::its::GeometryTGeo::Instance(); + const auto bz = prop->getNominalBz(); + + auto rotateTrack = [bz](o2::track::TrackParametrizationWithError& tr, T alpha, o2::track::TrackParametrization* refLin) { + return refLin ? tr.rotate(alpha, *refLin, bz) : tr.rotate(alpha); + }; + auto accountCluster = [&](int i, std::array, 8>& extrapDest, o2::track::TrackParametrizationWithError& tr, o2::track::TrackParametrization* refLin) -> int { + if (clArr[i]) { + bool outward = tr.getX() < clArr[i]->getX(); + if (!rotateTrack(tr, clArr[i]->alpha, refLin) || !prop->propagateTo(tr, refLin, clArr[i]->getX(), false, base::PropagatorImpl::MAX_SIN_PHI, base::PropagatorImpl::MAX_STEP, corrType)) { + return 0; + } + if (outward) { + chi2 += tr.getPredictedChi2Quiet(*clArr[i]); + } + extrapDest[i] = tr; // before update + if (!tr.update(*clArr[i])) { + return 0; + } + } else { + extrapDest[i].invalidate(); + return -1; + } + return 1; + }; + auto trFitInw = convertTrack(iTrack.getParamOut()); + auto trFitOut = convertTrack(iTrack.getParamIn()); + if (clArr[0]) { // propagate outward seed to PV cluster's tracking frame + if (!trFitOut.rotate(clArr[0]->alpha) || !prop->propagateToX(trFitOut, clArr[0]->getX(), bz, base::PropagatorImpl::MAX_SIN_PHI, base::PropagatorImpl::MAX_STEP, corrType)) { + return false; + } + } + // linearization references + o2::track::TrackParametrization refLinInw0, refLinOut0, *refLinOut = nullptr, *refLinInw = nullptr; + if (useStableRef) { + refLinOut = &(refLinOut0 = trFitOut); + refLinInw = &(refLinInw0 = trFitInw); + } + + auto resetTrackCov = [bz](auto& trk) { + trk.resetCovariance(); + float qptB5Scale = std::abs(bz) > 0.1f ? std::abs(bz) / 5.006680f : 1.f; + float q2pt2 = trk.getQ2Pt() * trk.getQ2Pt(), q2pt2Wgh = q2pt2 * qptB5Scale * qptB5Scale; + float err2 = (100.f + q2pt2Wgh) / (1.f + q2pt2Wgh) * q2pt2; // -> 100 for high pTs, -> 1 for low pTs. + trk.setCov(err2, 14); // 100% error + }; + resetTrackCov(trFitOut); + resetTrackCov(trFitInw); + + for (int i = 0; i <= 7; i++) { + if (!accountCluster(i, extrapOut, trFitOut, refLinOut) || !accountCluster(7 - i, extrapInw, trFitInw, refLinInw)) { + return false; + } + } + return true; +} + +} // namespace o2::its3::align + +#endif diff --git a/Detectors/Upgrades/ITS3/alignment/src/AlignmentDOF.cxx b/Detectors/Upgrades/ITS3/alignment/src/AlignmentDOF.cxx new file mode 100644 index 0000000000000..d2a78dba791e6 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/src/AlignmentDOF.cxx @@ -0,0 +1,114 @@ +// Copyright 2019-2026 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. + +#include "ITS3Align/AlignmentDOF.h" + +#include +#include + +#include "ITS3Align/AlignmentMath.h" +#include "ITS3Base/SpecsV2.h" + +namespace +{ + +void validateDerivativeOutput(const DOFSet& dofSet, Eigen::Ref out) +{ + if (out.rows() != 3 || out.cols() != dofSet.nDOFs()) { + throw std::invalid_argument(std::format("Derivative buffer shape {}x{} does not match expected 3x{}", + out.rows(), out.cols(), dofSet.nDOFs())); + } + out.setZero(); +} + +} // namespace + +void RigidBodyDOFSet::fillDerivatives(const DerivativeContext& ctx, Eigen::Ref out) const +{ + validateDerivativeOutput(*this, out); + + const double csp = 1. / std::sqrt(1. + (ctx.tgl * ctx.tgl)); + const double uP = ctx.snp * csp; + const double vP = ctx.tgl * csp; + + out(0, TX) = uP; + out(0, TY) = -1.; + out(0, RX) = ctx.trkZ; + out(0, RY) = ctx.trkZ * uP; + out(0, RZ) = -ctx.trkY * uP; + + out(1, TX) = vP; + out(1, TZ) = -1.; + out(1, RX) = -ctx.trkY; + out(1, RY) = ctx.trkZ * vP; + out(1, RZ) = -ctx.trkY * vP; +} + +void LegendreDOFSet::fillDerivatives(const DerivativeContext& ctx, Eigen::Ref out) const +{ + validateDerivativeOutput(*this, out); + if (ctx.sensorID < 0 || ctx.layerID < 0) { + throw std::invalid_argument("LegendreDOFSet requires an ITS3 measurement context"); + } + + const double gloX = ctx.measX * std::cos(ctx.measAlpha); + const double gloY = ctx.measX * std::sin(ctx.measAlpha); + const auto [u, v] = o2::its3::align::computeUV(gloX, gloY, ctx.measZ, ctx.sensorID, o2::its3::constants::radii[ctx.layerID]); + const auto pu = o2::its3::align::legendrePols(mOrder, u); + const auto pv = o2::its3::align::legendrePols(mOrder, v); + + int idx = 0; + for (int i = 0; i <= mOrder; ++i) { + for (int j = 0; j <= i; ++j) { + const double basis = pu[j] * pv[i - j]; + out(0, idx) = ctx.dydx * basis; + out(1, idx) = ctx.dzdx * basis; + ++idx; + } + } +} + +void InextensionalDOFSet::fillDerivatives(const DerivativeContext& ctx, Eigen::Ref out) const +{ + validateDerivativeOutput(*this, out); + if (ctx.layerID < 0) { + throw std::invalid_argument("InextensionalDOFSet requires an ITS3 measurement context"); + } + + const double r = o2::its3::constants::radii[ctx.layerID]; + const double phi = std::atan2(r * std::sin(ctx.measAlpha), r * std::cos(ctx.measAlpha)); + const double z = ctx.measZ; + + for (int n = 2; n <= mMaxOrder; ++n) { + const double sn = std::sin(n * phi); + const double cn = std::cos(n * phi); + const double n2 = static_cast(n * n); + const int off = modeOffset(n); + + out(0, off + 0) = -(z / r) * (n * sn + ctx.dydx * n2 * cn); + out(1, off + 0) = -cn - ctx.dzdx * (z / r) * n2 * cn; + + out(0, off + 1) = (z / r) * (n * cn - ctx.dydx * n2 * sn); + out(1, off + 1) = -sn * (1. + ctx.dzdx * (z / r) * n2); + + out(0, off + 2) = -cn + ctx.dydx * n * sn; + out(1, off + 2) = ctx.dzdx * n * sn; + + out(0, off + 3) = -sn - ctx.dydx * n * cn; + out(1, off + 3) = -ctx.dzdx * n * cn; + } + + out(0, alphaIdx()) = z / r; + out(1, alphaIdx()) = -phi; + + out(0, betaIdx()) = -phi - ctx.dydx; + out(1, betaIdx()) = -ctx.dzdx; +} diff --git a/Detectors/Upgrades/ITS3/alignment/src/AlignmentHierarchy.cxx b/Detectors/Upgrades/ITS3/alignment/src/AlignmentHierarchy.cxx new file mode 100644 index 0000000000000..938c14c2c4759 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/src/AlignmentHierarchy.cxx @@ -0,0 +1,585 @@ +// Copyright 2019-2026 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. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ITS3Align/AlignmentHierarchy.h" +#include "ITSBase/GeometryTGeo.h" +#include "Framework/Logger.h" +#include "MathUtils/Utils.h" + +namespace o2::its3::align +{ + +void HierarchyConstraint::write(std::ostream& os) const +{ + os << "!!! " << mName << '\n'; + os << "Constraint " << mValue << '\n'; + for (size_t i{0}; i < mLabels.size(); ++i) { + os << mLabels[i] << " " << mCoeff[i] << '\n'; + } + os << '\n'; +} + +AlignableVolume::AlignableVolume(const char* symName, uint32_t label, uint32_t det, bool sens) : mSymName(symName), mLabel(det, label, sens) +{ + init(); +} + +AlignableVolume::AlignableVolume(const char* symName, GlobalLabel label) : mSymName(symName), mLabel(label) +{ + init(); +} + +void AlignableVolume::init() +{ + // check if this sym volume actually exists + mPNE = gGeoManager->GetAlignableEntry(mSymName.c_str()); + if (mPNE == nullptr) { + LOGP(fatal, "Symbolic volume '{}' has no corresponding alignable entry!", mSymName); + } + mPN = mPNE->GetPhysicalNode(); + if (mPN == nullptr) { + LOGP(debug, "Adding physical node to {}", mSymName); + mPN = gGeoManager->MakePhysicalNode(mPNE->GetTitle()); + if (mPN == nullptr) { + LOGP(fatal, "Failed to make physical node for {}", mSymName); + } + } +} + +void AlignableVolume::finalise(uint8_t level) +{ + if (level == 0 && !isRoot()) { + LOGP(fatal, "Finalise should be called only from the root node!"); + } + mLevel = level; + if (!isLeaf()) { + // depth first + for (const auto& c : mChildren) { + c->finalise(level + 1); + } + // auto-disable parent RB DOFs if no children are active + if (mRigidBody) { + int nActiveChildren = 0; + for (const auto& c : mChildren) { + if (c->isActive()) { + ++nActiveChildren; + } + } + if (!nActiveChildren) { + for (int iDOF = 0; iDOF < mRigidBody->nDOFs(); ++iDOF) { + if (mRigidBody->isFree(iDOF)) { + LOGP(warn, "Auto-disabling DOF {} for {} since no active children", + mRigidBody->dofName(iDOF), mSymName); + mRigidBody->setFree(iDOF, false); + } + } + } + } + } else { + // for sensors we need also to define the transformation from the measurment (TRK) to the local frame (LOC) + // need to it with including possible pre-alignment to allow for iterative convergence + // (TRK) is defined wrt global z-axis + defineMatrixL2G(); + defineMatrixT2L(); + } + if (!isRoot()) { + // prepare the transformation matrices, e.g. from child frame to parent frame + // this is not necessarily just one level transformation + TGeoHMatrix mat = *mPN->GetMatrix(); // global matrix (including possible pre-alignment) from this volume to the global frame + if (isLeaf()) { + mat = mL2G; // for sensor volumes they might have redefined the L2G definition + } + auto inv = mParent->mPN->GetMatrix()->Inverse(); // global (including possible pre-alignment) from this volume to the global frame + mat.MultiplyLeft(inv); // left mult. effectively subtracts the parent transformation which is included in the the childs + mL2P = mat; // now this is directly the child to the parent transformation (LOC) (including possible pre-alignment) + + // prepare jacobian from child to parent frame + Eigen::Map> rotL2P(mL2P.GetRotationMatrix()); + Eigen::Matrix3d rotInv = rotL2P.transpose(); // parent-to-child rotation + const double* t = mL2P.GetTranslation(); // child origin in parent frame + Eigen::Matrix3d skewT; + skewT << 0, -t[2], t[1], t[2], 0, -t[0], -t[1], t[0], 0; + mJL2P.setZero(); + mJL2P.topLeftCorner<3, 3>() = rotInv; + mJL2P.topRightCorner<3, 3>() = -rotInv * skewT; + mJL2P.bottomRightCorner<3, 3>() = rotInv; + mJP2L = mJL2P.inverse(); + } +} + +void AlignableVolume::writeRigidBodyConstraints(std::ostream& os) const +{ + if (isLeaf() || !mRigidBody) { + // recurse even if this node has no RB DOFs + for (const auto& c : mChildren) { + c->writeRigidBodyConstraints(os); + } + return; + } + + for (int iDOF = 0; iDOF < mRigidBody->nDOFs(); ++iDOF) { + if (!mRigidBody->isFree(iDOF)) { + continue; + } + double nActiveChildren = 0.; + for (const auto& c : mChildren) { + if (c->isActive()) { + ++nActiveChildren; + } + } + if (nActiveChildren == 0.) { + LOGP(fatal, "{} has dof {} active but no active children!", mSymName, mRigidBody->dofName(iDOF)); + } + const double invN = 1.0 / nActiveChildren; + HierarchyConstraint con(std::format("DOF {} for {}", mRigidBody->dofName(iDOF), mSymName), 0.0); + for (const auto& c : mChildren) { + if (!c->mRigidBody) { + continue; + } + for (int jDOF = 0; jDOF < c->mRigidBody->nDOFs(); ++jDOF) { + if (!c->mRigidBody->isFree(jDOF)) { + continue; + } + double coeff = invN * c->getJP2L()(iDOF, jDOF); + if (std::abs(coeff) > 1e-16f) { + con.add(c->getLabel().raw(jDOF), coeff); + } + } + } + + if (con.getSize() > 1) { + con.write(os); + } + } + for (const auto& c : mChildren) { + c->writeRigidBodyConstraints(os); + } +} + +void AlignableVolume::writeParameters(std::ostream& os) const +{ + if (isRoot()) { + os << "Parameter\n"; + } + if (!mIsPseudo) { + if (mRigidBody) { + for (int iDOF = 0; iDOF < mRigidBody->nDOFs(); ++iDOF) { + os << std::format("{:<10} {:>+15g} {:>+15g} ! {} {} ", + mLabel.raw(iDOF), 0.0, (mRigidBody->isFree(iDOF) ? 0.0 : -1.0), + (mRigidBody->isFree(iDOF) ? 'V' : 'F'), mRigidBody->dofName(iDOF)) + << mSymName << '\n'; + } + } + if (mCalib) { + auto calibLbl = mLabel.asCalib(); + for (int iDOF = 0; iDOF < mCalib->nDOFs(); ++iDOF) { + os << std::format("{:<10} {:>+15g} {:>+15g} ! {} {:<5} ", + calibLbl.raw(iDOF), 0.0, (mCalib->isFree(iDOF) ? 0.0 : -1.0), + (mCalib->isFree(iDOF) ? 'V' : 'F'), mCalib->dofName(iDOF)) + << mSymName << '\n'; + } + } + } + for (const auto& c : mChildren) { + c->writeParameters(os); + } +} + +void AlignableVolume::writeTree(std::ostream& os, int indent) const +{ + os << std::string(static_cast(indent * 2), ' ') << mSymName << (mLabel.sens() ? " (sens)" : " (pasv)"); + if (mIsPseudo) { + os << " pseudo"; + } else { + int nFreeDofs{0}; + if (mRigidBody && mRigidBody->nFreeDOFs()) { + nFreeDofs += mRigidBody->nFreeDOFs(); + os << " RB["; + for (int i = 0; i < mRigidBody->nDOFs(); ++i) { + if (mRigidBody->isFree(i)) { + os << " " << mRigidBody->dofName(i) << "(" << mLabel.raw(i) << ")"; + } + } + os << " ]"; + } + if (mCalib && mCalib->nFreeDOFs()) { + nFreeDofs += mCalib->nFreeDOFs(); + os << " CAL["; + auto calibLbl = mLabel.asCalib(); + for (int i = 0; i < mCalib->nDOFs(); ++i) { + if (mCalib->isFree(i)) { + os << " " << mCalib->dofName(i) << "(" << calibLbl.raw(i) << ")"; + } + } + os << " ]"; + } + if (!nFreeDofs) { + os << " no DOFs"; + } + } + os << '\n'; + for (const auto& c : mChildren) { + c->writeTree(os, indent + 2); + } +} + +void applyDOFConfig(AlignableVolume* root, const std::string& jsonPath) +{ + using json = nlohmann::json; + std::ifstream f(jsonPath); + if (!f.is_open()) { + LOGP(fatal, "Cannot open DOF config file: {}", jsonPath); + } + auto data = json::parse(f); + json rules = data.is_array() ? data : data.value("rules", json::array()); + + static const std::map rbNameToIdx = { + {"TX", 0}, {"TY", 1}, {"TZ", 2}, {"RX", 3}, {"RY", 4}, {"RZ", 5}}; + + auto matchPattern = [](const std::string& pattern, const std::string& sym) -> bool { + if (fnmatch(pattern.c_str(), sym.c_str(), 0) == 0) { + return true; + } + std::string prefixed = "*" + pattern; + return fnmatch(prefixed.c_str(), sym.c_str(), 0) == 0; + }; + + if (data.is_object() && data.contains("defaults")) { + json defRule = data["defaults"]; + defRule["match"] = "*"; + rules.insert(rules.begin(), defRule); + } + + root->traverse([&](AlignableVolume* vol) { + if (vol->isPseudo()) { + return; + } + const std::string& sym = vol->getSymName(); + for (const auto& rule : rules) { + const auto pattern = rule["match"].get(); + if (!matchPattern(pattern, sym)) { + continue; + } + // rigid body DOFs + if (rule.contains("rigidBody")) { + const auto& rb = rule["rigidBody"]; + if (rb.is_string()) { + auto s = rb.get(); + if (s == "all" || s == "free") { + vol->setRigidBody(std::make_unique()); + } else if (s == "fixed") { + auto dofSet = std::make_unique(); + dofSet->setAllFree(false); + vol->setRigidBody(std::move(dofSet)); + } + } else if (rb.is_array()) { + auto dofSet = std::make_unique(); + dofSet->setAllFree(false); + for (const auto& name : rb) { + auto it = rbNameToIdx.find(name.get()); + if (it != rbNameToIdx.end()) { + dofSet->setFree(it->second, true); + } + } + vol->setRigidBody(std::move(dofSet)); + } else if (rb.is_object()) { + auto dofs = rb.value("dofs", std::string("all")); + bool fixed = rb.value("fixed", false); + if (dofs == "all") { + auto dofSet = std::make_unique(); + if (fixed) { + dofSet->setAllFree(false); + } + vol->setRigidBody(std::move(dofSet)); + } else if (rb["dofs"].is_array()) { + auto dofSet = std::make_unique(); + dofSet->setAllFree(false); + for (const auto& name : rb["dofs"]) { + auto it = rbNameToIdx.find(name.get()); + if (it != rbNameToIdx.end()) { + dofSet->setFree(it->second, !fixed); + } + } + vol->setRigidBody(std::move(dofSet)); + } + } + } + // calibration DOFs + if (rule.contains("calib")) { + const auto& cal = rule["calib"]; + auto calType = cal.value("type", std::string("")); + if (calType == "legendre") { + int order = cal.value("order", 3); + auto dofSet = std::make_unique(order); + bool fixed = cal.value("fixed", false); + if (fixed) { + dofSet->setAllFree(false); + } + // fix/free individual coefficients by name or index + if (cal.contains("free")) { + dofSet->setAllFree(false); + for (const auto& item : cal["free"]) { + if (item.is_number_integer()) { + dofSet->setFree(item.get(), true); + } else if (item.is_string()) { + // match by name e.g. "L(1,0)" + for (int k = 0; k < dofSet->nDOFs(); ++k) { + if (dofSet->dofName(k) == item.get()) { + dofSet->setFree(k, true); + } + } + } + } + } + if (cal.contains("fix")) { + for (const auto& item : cal["fix"]) { + if (item.is_number_integer()) { + dofSet->setFree(item.get(), false); + } else if (item.is_string()) { + for (int k = 0; k < dofSet->nDOFs(); ++k) { + if (dofSet->dofName(k) == item.get()) { + dofSet->setFree(k, false); + } + } + } + } + } + vol->setCalib(std::move(dofSet)); + } else if (calType == "inextensional") { + int maxOrder = cal.value("order", 2); + auto dofSet = std::make_unique(maxOrder); + bool fixed = cal.value("fixed", false); + if (fixed) { + dofSet->setAllFree(false); + } + if (cal.contains("free")) { + dofSet->setAllFree(false); + for (const auto& item : cal["free"]) { + if (item.is_number_integer()) { + dofSet->setFree(item.get(), true); + } else if (item.is_string()) { + for (int k = 0; k < dofSet->nDOFs(); ++k) { + if (dofSet->dofName(k) == item.get()) { + dofSet->setFree(k, true); + } + } + } + } + } + if (cal.contains("fix")) { + for (const auto& item : cal["fix"]) { + if (item.is_number_integer()) { + dofSet->setFree(item.get(), false); + } else if (item.is_string()) { + for (int k = 0; k < dofSet->nDOFs(); ++k) { + if (dofSet->dofName(k) == item.get()) { + dofSet->setFree(k, false); + } + } + } + } + } + vol->setCalib(std::move(dofSet)); + } + } + } + }); +} + +void writeMillepedeResults(AlignableVolume* root, const std::string& milleResPath, const std::string& outJsonPath, const std::string& injectedJsonPath) +{ + using json = nlohmann::json; + + // parse millepede.res: label fittedValue presigma [...] + std::ifstream fin(milleResPath); + if (!fin.is_open()) { + LOGP(fatal, "Cannot open millepede result file: {}", milleResPath); + } + std::map labelToValue; + std::string line; + while (std::getline(fin, line)) { + if (line.empty() || line[0] == '!' || line[0] == '*') { + continue; + } + if (line.find("Parameter") != std::string::npos) { + continue; + } + std::istringstream iss(line); + uint32_t label = 0; + double value = NAN, presigma = NAN; + if (!(iss >> label >> value >> presigma)) { + continue; + } + if (presigma >= 0.0) { // skip fixed parameters + labelToValue[label] = value; + } + } + fin.close(); + LOGP(info, "Parsed {} not fixed parameters from {}", labelToValue.size(), milleResPath); + + // load injected misalignment if provided (same format as closure test input) + // indexed by sensorID + std::map> injRB; + std::map>> injMatrix; + struct InjInex { + std::map> modes; + double alpha{0.}; + double beta{0.}; + }; + std::map injInex; + if (!injectedJsonPath.empty()) { + std::ifstream injFile(injectedJsonPath); + if (injFile.is_open()) { + json injData = json::parse(injFile); + for (const auto& item : injData) { + int id = item["id"].get(); + if (item.contains("rigidBody")) { + injRB[id] = item["rigidBody"].get>(); + } + if (item.contains("matrix")) { + injMatrix[id] = item["matrix"].get>>(); + } + if (item.contains("inextensional")) { + InjInex ii; + const auto& inex = item["inextensional"]; + if (inex.contains("modes")) { + for (auto& [key, val] : inex["modes"].items()) { + ii.modes[std::stoi(key)] = val.get>(); + } + } + if (inex.contains("alpha")) { + ii.alpha = inex["alpha"].get(); + } + if (inex.contains("beta")) { + ii.beta = inex["beta"].get(); + } + injInex[id] = ii; + } + } + LOGP(info, "Loaded injected misalignment for {} sensors", injData.size()); + } else { + LOGP(warn, "Cannot open injected misalignment file: {}, writing absolute values", injectedJsonPath); + } + } + + // collect results per volume that has RB or calib DOFs + json output = json::array(); + root->traverse([&](AlignableVolume* vol) { + auto* rb = vol->getRigidBody(); + auto* cal = vol->getCalib(); + if ((!rb && !cal) || vol->isPseudo()) { + return; + } + int id = vol->getSensorId(); + json entry; + entry["symName"] = vol->getSymName(); + entry["id"] = id; + bool write = false; + + // rigid body parameters + if (rb && rb->nFreeDOFs()) { + write = true; + json rbArr = json::array(); + const auto& inj = injRB.contains(id) ? injRB[id] : std::vector{}; + for (int i = 0; i < rb->nDOFs(); ++i) { + uint32_t raw = vol->getLabel().raw(i); + auto it = labelToValue.find(raw); + double fitted = it != labelToValue.end() ? it->second : 0.0; + double ref = i < static_cast(inj.size()) ? inj[i] : 0.0; + rbArr.push_back(fitted - ref); + } + entry["rigidBody"] = rbArr; + } + + // calibration (Legendre) parameters + if (cal && cal->nFreeDOFs() && cal->type() == DOFSet::Type::Legendre) { + write = true; + auto* leg = dynamic_cast(cal); + int order = leg->order(); + auto calibLbl = vol->getLabel().asCalib(); + const auto& inj = injMatrix.contains(id) ? injMatrix[id] : std::vector>{}; + json matrix = json::array(); + int idx = 0; + for (int i = 0; i <= order; ++i) { + json row = json::array(); + for (int j = 0; j <= i; ++j) { + uint32_t raw = calibLbl.raw(idx); + auto it = labelToValue.find(raw); + double fitted = it != labelToValue.end() ? it->second : 0.0; + double ref = (i < static_cast(inj.size()) && j < static_cast(inj[i].size())) ? inj[i][j] : 0.0; + row.push_back(fitted - ref); + ++idx; + } + matrix.push_back(row); + } + entry["matrix"] = matrix; + } else if (cal && cal->nFreeDOFs() && cal->type() == DOFSet::Type::Inextensional) { + write = true; + auto* inexSet = static_cast(cal); + int maxN = inexSet->maxOrder(); + auto calibLbl = vol->getLabel().asCalib(); + const auto& inj = injInex.contains(id) ? injInex[id] : InjInex{}; + + json inexEntry; + json modesObj = json::object(); + for (int n = 2; n <= maxN; ++n) { + int off = InextensionalDOFSet::modeOffset(n); + std::array injCoeffs = {0., 0., 0., 0.}; + if (inj.modes.contains(n)) { + injCoeffs = inj.modes.at(n); + } + json modeArr = json::array(); + for (int k = 0; k < 4; ++k) { + uint32_t raw = calibLbl.raw(off + k); + auto it = labelToValue.find(raw); + double fitted = it != labelToValue.end() ? it->second : 0.0; + modeArr.push_back(fitted - injCoeffs[k]); + } + modesObj[std::to_string(n)] = modeArr; + } + inexEntry["modes"] = modesObj; + + // alpha + uint32_t rawAlpha = calibLbl.raw(inexSet->alphaIdx()); + auto itA = labelToValue.find(rawAlpha); + inexEntry["alpha"] = (itA != labelToValue.end() ? itA->second : 0.0) - inj.alpha; + + // beta + uint32_t rawBeta = calibLbl.raw(inexSet->betaIdx()); + auto itB = labelToValue.find(rawBeta); + inexEntry["beta"] = (itB != labelToValue.end() ? itB->second : 0.0) - inj.beta; + + entry["inextensional"] = inexEntry; + } + if (write) { + output.push_back(entry); + } + }); + + std::ofstream fout(outJsonPath); + if (!fout.is_open()) { + LOGP(fatal, "Cannot open output file: {}", outJsonPath); + } + fout << output.dump(2) << '\n'; + fout.close(); + LOGP(info, "Wrote millepede results to {}", outJsonPath); +} + +} // namespace o2::its3::align diff --git a/Detectors/Upgrades/ITS3/alignment/src/AlignmentMath.cxx b/Detectors/Upgrades/ITS3/alignment/src/AlignmentMath.cxx new file mode 100644 index 0000000000000..52e9c03540d4c --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/src/AlignmentMath.cxx @@ -0,0 +1,54 @@ +// Copyright 2019-2026 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. + +#include "ITS3Align/AlignmentMath.h" + +#include + +#include + +#include "ITS3Base/SpecsV2.h" +#include "MathUtils/Utils.h" + +namespace o2::its3::align +{ + +std::pair computeUV(double gloX, double gloY, double gloZ, int sensorID, double radius) +{ + const bool isTop = sensorID % 2 == 0; + const double phi = o2::math_utils::to02Pid(std::atan2(gloY, gloX)); + const double phiBorder1 = o2::math_utils::to02Pid(((isTop ? 0. : 1.) * TMath::Pi()) + std::asin(constants::equatorialGap / 2. / radius)); + const double phiBorder2 = o2::math_utils::to02Pid(((isTop ? 1. : 2.) * TMath::Pi()) - std::asin(constants::equatorialGap / 2. / radius)); + const double u = (((phi - phiBorder1) * 2.) / (phiBorder2 - phiBorder1)) - 1.; + const double v = ((2. * gloZ + constants::segment::lengthSensitive) / constants::segment::lengthSensitive) - 1.; + return {u, v}; +} + +TrackSlopes computeTrackSlopes(double snp, double tgl) +{ + const double csci = 1. / std::sqrt(1. - (snp * snp)); + return {.dydx = snp * csci, .dzdx = tgl * csci}; +} + +std::vector legendrePols(int order, double x) +{ + std::vector p(order + 1); + p[0] = 1.; + if (order > 0) { + p[1] = x; + } + for (int n = 1; n < order; ++n) { + p[n + 1] = ((2 * n + 1) * x * p[n] - n * p[n - 1]) / (n + 1); + } + return p; +} + +} // namespace o2::its3::align diff --git a/GPU/TPCFastTransformation/NDPiecewisePolynomials.cxx b/Detectors/Upgrades/ITS3/alignment/src/AlignmentParams.cxx similarity index 80% rename from GPU/TPCFastTransformation/NDPiecewisePolynomials.cxx rename to Detectors/Upgrades/ITS3/alignment/src/AlignmentParams.cxx index 1e2e540bdfdc8..0d89cb4d4cffd 100644 --- a/GPU/TPCFastTransformation/NDPiecewisePolynomials.cxx +++ b/Detectors/Upgrades/ITS3/alignment/src/AlignmentParams.cxx @@ -9,7 +9,5 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file NDPiecewisePolynomials.cxx -/// \author Matthias Kleiner - -#include "NDPiecewisePolynomials.h" +#include "ITS3Align/AlignmentParams.h" +O2ParamImpl(o2::its3::align::AlignmentParams); diff --git a/Detectors/Upgrades/ITS3/alignment/src/AlignmentSensors.cxx b/Detectors/Upgrades/ITS3/alignment/src/AlignmentSensors.cxx new file mode 100644 index 0000000000000..7644c37107104 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/src/AlignmentSensors.cxx @@ -0,0 +1,201 @@ +// Copyright 2019-2026 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. + +#include +#include + +#include "Framework/Logger.h" +#include "ITSMFTBase/SegmentationAlpide.h" +#include "ITS3Align/AlignmentSensors.h" +#include "ITSBase/GeometryTGeo.h" + +namespace o2::its3::align +{ + +AlignableVolume::Ptr buildHierarchyITS(AlignableVolume::SensorMapping& sensorMap) +{ + uint32_t gLbl{0}, det{0}; + auto geom = o2::its::GeometryTGeo::Instance(); + AlignableVolume *volHB{nullptr}, *volSt{nullptr}, *volHSt{nullptr}, *volMod{nullptr}; + std::unordered_map sym2vol; + + auto root = std::make_unique(geom->composeSymNameITS(), gLbl++, det, false); + sym2vol[root->getSymName()] = root.get(); + for (int ilr = 0; ilr < geom->getNumberOfLayers(); ilr++) { + for (int ihb = 0; ihb < geom->getNumberOfHalfBarrels(); ihb++) { + volHB = root->addChild(geom->composeSymNameHalfBarrel(ilr, ihb), gLbl++, det, false); + sym2vol[volHB->getSymName()] = volHB; + int nstavesHB = geom->getNumberOfStaves(ilr) / 2; + for (int ist = 0; ist < nstavesHB; ist++) { + volSt = volHB->addChild(geom->composeSymNameStave(ilr, ihb, ist), gLbl++, det, false); + sym2vol[volSt->getSymName()] = volSt; + for (int ihst = 0; ihst < geom->getNumberOfHalfStaves(ilr); ihst++) { + volHSt = volSt->addChild(geom->composeSymNameHalfStave(ilr, ihb, ist, ihst), gLbl++, det, false); + sym2vol[volHSt->getSymName()] = volHSt; + for (int imd = 0; imd < geom->getNumberOfModules(ilr); imd++) { + volMod = volHSt->addChild(geom->composeSymNameModule(ilr, ihb, ist, ihst, imd), gLbl++, det, false); + sym2vol[volMod->getSymName()] = volMod; + } + } + } + } + } + + // NOTE: for ITS sensors the local x and y are swapped + int lay = 0, hba = 0, sta = 0, ssta = 0, modd = 0, chip = 0; + for (int ich = 0; ich < geom->getNumberOfChips(); ich++) { + geom->getChipId(ich, lay, hba, sta, ssta, modd, chip); + GlobalLabel lbl(det, ich, true); + AlignableVolume* parVol = sym2vol[modd < 0 ? geom->composeSymNameStave(lay, hba, sta) : geom->composeSymNameModule(lay, hba, sta, ssta, modd)]; + if (!parVol) { + LOGP(fatal, "did not find parent for chip {}", ich); + } + int nch = modd < 0 ? geom->getNumberOfChipsPerStave(lay) : geom->getNumberOfChipsPerModule(lay); + int jch = ich % nch; + auto* chip = parVol->addChild(geom->composeSymNameChip(lay, hba, sta, ssta, modd, jch), lbl); + chip->setSensorId(ich); + sensorMap[lbl] = chip; + } + return root; +} + +AlignableVolume::Ptr buildHierarchyIT3(AlignableVolume::SensorMapping& sensorMap) +{ + uint32_t gLbl{0}, det{0}; + auto geom = o2::its::GeometryTGeo::Instance(); + AlignableVolume *volHB{nullptr}, *volSt{nullptr}, *volHSt{nullptr}, *volMod{nullptr}; + std::unordered_map sym2vol; + + auto root = std::make_unique(geom->composeSymNameITS(), gLbl++, det, false); + sym2vol[root->getSymName()] = root.get(); + for (int ilr = 0; ilr < geom->getNumberOfLayers(); ilr++) { + const bool isLayITS3 = (ilr < 3); + for (int ihb = 0; ihb < geom->getNumberOfHalfBarrels(); ihb++) { + volHB = root->addChild(geom->composeSymNameHalfBarrel(ilr, ihb, isLayITS3), gLbl++, det, false); + sym2vol[volHB->getSymName()] = volHB; + if (isLayITS3) { + volHB->setSensorId((2 * ilr) + ihb); + continue; // no deeper hierarchy for ITS3 layers + } + int nstavesHB = geom->getNumberOfStaves(ilr) / 2; + for (int ist = 0; ist < nstavesHB; ist++) { + volSt = volHB->addChild(geom->composeSymNameStave(ilr, ihb, ist), gLbl++, det, false); + sym2vol[volSt->getSymName()] = volSt; + for (int ihst = 0; ihst < geom->getNumberOfHalfStaves(ilr); ihst++) { + volHSt = volSt->addChild(geom->composeSymNameHalfStave(ilr, ihb, ist, ihst), gLbl++, det, false); + sym2vol[volHSt->getSymName()] = volHSt; + for (int imd = 0; imd < geom->getNumberOfModules(ilr); imd++) { + volMod = volHSt->addChild(geom->composeSymNameModule(ilr, ihb, ist, ihst, imd), gLbl++, det, false); + sym2vol[volMod->getSymName()] = volMod; + } + } + } + } + } + + int lay = 0, hba = 0, sta = 0, ssta = 0, modd = 0, chip = 0; + for (int ich = 0; ich < geom->getNumberOfChips(); ich++) { + geom->getChipId(ich, lay, hba, sta, ssta, modd, chip); + const bool isLayITS3 = (lay < 3); + GlobalLabel lbl(det, ich, true); + if (isLayITS3) { + // ITS3 chips by construction do not have any DOFs still add them to have the measurment to alignable layer relation + AlignableVolume* parVol = sym2vol[geom->composeSymNameHalfBarrel(lay, hba, true)]; + if (!parVol) { + LOGP(fatal, "did not find parent for chip {}", ich); + } + auto* tile = parVol->addChild(geom->composeSymNameChip(lay, hba, sta, ssta, modd, chip, true), lbl); + tile->setPseudo(true); + tile->setSensorId(ich); + sensorMap[lbl] = tile; + } else { + AlignableVolume* parVol = sym2vol[modd < 0 ? geom->composeSymNameStave(lay, hba, sta) : geom->composeSymNameModule(lay, hba, sta, ssta, modd)]; + if (!parVol) { + LOGP(fatal, "did not find parent for chip {}", ich); + } + int nch = modd < 0 ? geom->getNumberOfChipsPerStave(lay) : geom->getNumberOfChipsPerModule(lay); + int jch = ich % nch; + auto* chip = parVol->addChild(geom->composeSymNameChip(lay, hba, sta, ssta, modd, jch), lbl); + chip->setSensorId(ich); + sensorMap[lbl] = chip; + } + } + return root; +} + +void AlignableSensorITS::defineMatrixL2G() +{ + // the chip volume is not the measurment plane, need to correct for the epitaxial layer + const auto* chipL2G = mPN->GetMatrix(); + mL2G = *chipL2G; + double delta = itsmft::SegmentationAlpide::SensorLayerThickness - itsmft::SegmentationAlpide::SensorLayerThicknessEff; + TGeoTranslation tra(0., 0.5 * delta, 0.); + mL2G *= tra; +} + +void AlignableSensorITS::defineMatrixT2L() +{ + double locA[3] = {-100., 0., 0.}, locB[3] = {100., 0., 0.}, gloA[3], gloB[3]; + mL2G.LocalToMaster(locA, gloA); + mL2G.LocalToMaster(locB, gloB); + double dx = gloB[0] - gloA[0], dy = gloB[1] - gloA[1]; + double t = (gloB[0] * dx + gloB[1] * dy) / (dx * dx + dy * dy); + double xp = gloB[0] - (dx * t), yp = gloB[1] - (dy * t); + double alp = std::atan2(yp, xp); + o2::math_utils::bringTo02Pid(alp); + mT2L.RotateZ(alp * TMath::RadToDeg()); // mT2L before is identity and afterwards rotated + const TGeoHMatrix l2gI = mL2G.Inverse(); + mT2L.MultiplyLeft(l2gI); +} + +void AlignableSensorITS::computeJacobianL2T(const double* posLoc, Matrix66& jac) const +{ + jac.setZero(); + Eigen::Map> rotT2L(mT2L.GetRotationMatrix()); + Eigen::Matrix3d skew, rotL2T = rotT2L.transpose(); + skew << 0, -posLoc[2], posLoc[1], posLoc[2], 0, -posLoc[0], -posLoc[1], posLoc[0], 0; + jac.topLeftCorner<3, 3>() = rotL2T; + jac.topRightCorner<3, 3>() = -rotL2T * skew; + jac.bottomRightCorner<3, 3>() = rotL2T; +} + +void AlignableSensorIT3::defineMatrixL2G() +{ + mL2G = *mPN->GetMatrix(); +} + +void AlignableSensorIT3::defineMatrixT2L() +{ + double locA[3] = {-100., 0., 0.}, locB[3] = {100., 0., 0.}, gloA[3], gloB[3]; + mL2G.LocalToMaster(locA, gloA); + mL2G.LocalToMaster(locB, gloB); + double dx = gloB[0] - gloA[0], dy = gloB[1] - gloA[1]; + double t = (gloB[0] * dx + gloB[1] * dy) / (dx * dx + dy * dy); + double xp = gloB[0] - (dx * t), yp = gloB[1] - (dy * t); + double alp = std::atan2(yp, xp); + o2::math_utils::bringTo02Pid(alp); + mT2L.RotateZ(alp * TMath::RadToDeg()); + const TGeoHMatrix l2gI = mL2G.Inverse(); + mT2L.MultiplyLeft(l2gI); +} + +void AlignableSensorIT3::computeJacobianL2T(const double* posLoc, Matrix66& jac) const +{ + jac.setZero(); + Eigen::Map> rotT2L(mT2L.GetRotationMatrix()); + Eigen::Matrix3d skew, rotL2T = rotT2L.transpose(); + skew << 0, -posLoc[2], posLoc[1], posLoc[2], 0, -posLoc[0], -posLoc[1], posLoc[0], 0; + jac.topLeftCorner<3, 3>() = rotL2T; + jac.topRightCorner<3, 3>() = -rotL2T * skew; + jac.bottomRightCorner<3, 3>() = rotL2T; +} + +} // namespace o2::its3::align diff --git a/Detectors/Upgrades/ITS3/alignment/src/AlignmentSpec.cxx b/Detectors/Upgrades/ITS3/alignment/src/AlignmentSpec.cxx new file mode 100644 index 0000000000000..72f968bdbf338 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/src/AlignmentSpec.cxx @@ -0,0 +1,982 @@ +// Copyright 2019-2026 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. + +#include +#include +#include +#include + +#ifdef WITH_OPENMP +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "Framework/ConfigParamRegistry.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/Task.h" +#include "ITSBase/GeometryTGeo.h" +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "DetectorsBase/Propagator.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include "ReconstructionDataFormats/PrimaryVertex.h" +#include "Steer/MCKinematicsReader.h" +#include "CommonUtils/TreeStreamRedirector.h" +#include "ReconstructionDataFormats/VtxTrackRef.h" +#include "ITS3Reconstruction/TopologyDictionary.h" +#include "DataFormatsITSMFT/TopologyDictionary.h" +#include "ITStracking/MathUtils.h" +#include "ITStracking/IOUtils.h" +#include "ITS3Reconstruction/IOUtils.h" +#include "ITS3Align/TrackFit.h" +#include "ITS3Align/AlignmentMath.h" +#include "ITS3Align/AlignmentSpec.h" +#include "ITS3Align/AlignmentParams.h" +#include "ITS3Align/AlignmentTypes.h" +#include "ITS3Align/AlignmentHierarchy.h" +#include "ITS3Align/MisalignmentUtils.h" +#include "ITS3Align/AlignmentSensors.h" + +namespace o2::its3::align +{ +using namespace o2::framework; +using DetID = o2::detectors::DetID; +using DataRequest = o2::globaltracking::DataRequest; +using PVertex = o2::dataformats::PrimaryVertex; +using V2TRef = o2::dataformats::VtxTrackRef; +using VTIndex = o2::dataformats::VtxTrackIndex; +using GTrackID = o2::dataformats::GlobalTrackID; +using TrackD = o2::track::TrackParCovD; + +namespace +{ +DerivativeContext makeDerivativeContext(const FrameInfoExt& frame, const TrackD& trk) +{ + const auto slopes = computeTrackSlopes(trk.getSnp(), trk.getTgl()); + const bool isITS3 = constants::detID::isDetITS3(frame.sens); + return {.sensorID = isITS3 ? constants::detID::getSensorID(frame.sens) : -1, + .layerID = isITS3 ? constants::detID::getDetID2Layer(frame.sens) : -1, + .measX = frame.x, + .measAlpha = frame.alpha, + .measZ = frame.positionTrackingFrame[1], + .trkY = trk.getY(), + .trkZ = trk.getZ(), + .snp = trk.getSnp(), + .tgl = trk.getTgl(), + .dydx = slopes.dydx, + .dzdx = slopes.dzdx}; +} + +Matrix36 getRigidBodyBaseDerivatives(const DerivativeContext& ctx) +{ + static const RigidBodyDOFSet sRigidBodyBasis; + Eigen::MatrixXd dyn(3, sRigidBodyBasis.nDOFs()); + sRigidBodyBasis.fillDerivatives(ctx, dyn); + return dyn; +} +} // namespace + +class AlignmentSpec final : public Task +{ + public: + ~AlignmentSpec() final = default; + AlignmentSpec(const AlignmentSpec&) = delete; + AlignmentSpec(AlignmentSpec&&) = delete; + AlignmentSpec& operator=(const AlignmentSpec&) = delete; + AlignmentSpec& operator=(AlignmentSpec&&) = delete; + AlignmentSpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool useMC, bool withPV, bool withITS, OutputEnum out) + : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC), mWithPV(withPV), mIsITS3(!withITS), mOutOpt(out) + { + } + + void init(InitContext& ic) final; + void run(ProcessingContext& pc) final; + void endOfStream(EndOfStreamContext& ec) final; + void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final; + void process(); + + private: + void updateTimeDependentParams(ProcessingContext& pc); + void buildHierarchy(); + + // calculate the transport jacobian for points FROM and TO numerically via ridder's method + // this assumes the track is already at point FROM and will be extrapolated to TO's x (xTo) + // method does not modify the original track + bool getTransportJacobian(const TrackD& track, double xTo, double alphaTo, gbl::Matrix5d& jac, gbl::Matrix5d& err); + + // refit ITS track with inward/outward fit (opt. impose pv as additional constraint) + // after this we have the refitted track at the innermost update point + bool prepareITSTrack(int iTrk, const o2::its::TrackITS& itsTrack, Track& resTrack); + + // prepare ITS measuremnt points + void prepareMeasurments(std::span clusters, std::span pattIt); + + // build track to vertex association + void buildT2V(); + + // apply some misalignment on inner ITS3 layers + // it can happen that a measurement is pushed outside of + // ITS3 acceptance so false is to discard track + bool applyMisalignment(Eigen::Vector2d& res, const FrameInfoExt& frame, const TrackD& wTrk, size_t iTrk); + + OutputEnum mOutOpt; + std::unique_ptr mDBGOut; + std::vector mPVs; + std::vector mT2PV; + bool mIsITS3{true}; + const o2::itsmft::TopologyDictionary* mITSDict{nullptr}; + const o2::its3::TopologyDictionary* mIT3Dict{nullptr}; + o2::globaltracking::RecoContainer* mRecoData = nullptr; + std::unique_ptr mcReader; + std::vector mITSTrackingInfo; + std::shared_ptr mDataRequest; + std::shared_ptr mGGCCDBRequest; + std::unique_ptr mHierarchy; // tree-hiearchy + AlignableVolume::SensorMapping mChip2Hiearchy; // global label mapping to leaves in the tree + bool mUseMC{false}; + bool mWithPV{false}; + GTrackID::mask_t mTracksSrc; + int mNThreads{1}; + const AlignmentParams* mParams{nullptr}; + MisalignmentModel mMisalignment; + std::array, 6> mRigidBodyParams; // (dx,dy,dz,rx,ry,rz) in LOC per sensorID +}; + +void AlignmentSpec::init(InitContext& ic) +{ + o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); + mNThreads = ic.options().get("nthreads"); + if (mOutOpt) { + LOG(info) << mOutOpt.pstring(); + mDBGOut = std::make_unique("its3_debug_alg.root", "recreate"); + } + if (mUseMC) { + mcReader = std::make_unique("collisioncontext.root"); + } +} + +void AlignmentSpec::run(ProcessingContext& pc) +{ + if (mOutOpt[OutputOpt::MilleRes]) { + updateTimeDependentParams(pc); + writeMillepedeResults(mHierarchy.get(), mParams->milleResFile, mParams->milleResOutJson, mParams->misAlgJson); + } else { + o2::globaltracking::RecoContainer recoData; + mRecoData = &recoData; + mRecoData->collectData(pc, *mDataRequest); + updateTimeDependentParams(pc); + process(); + } + mRecoData = nullptr; +} + +void AlignmentSpec::process() +{ + if (!mITSDict && !mIT3Dict) { + LOGP(fatal, "ITS data is not loaded"); + } + auto prop = o2::base::PropagatorD::Instance(); + 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); + + if (mWithPV) { + buildT2V(); + } + + LOGP(info, "Starting fits with {} threads", mNThreads); + + // Data + std::vector> gblTrajSlots(mNThreads); + std::vector> resTrackSlots(mNThreads); + + auto timeStart = std::chrono::high_resolution_clock::now(); + int cFailedRefit{0}, cFailedProp{0}, cSelected{0}, cGBLFit{0}, cGBLFitFail{0}, cGBLChi2Rej{0}, cGBLConstruct{0}; + double chi2Sum{0}, lostWeightSum{0}; + int ndfSum{0}; +#ifdef WITH_OPENMP +#pragma omp parallel num_threads(mNThreads) \ + reduction(+ : cFailedRefit) \ + reduction(+ : cFailedProp) \ + reduction(+ : cSelected) \ + reduction(+ : cGBLFit) \ + reduction(+ : cGBLFitFail) \ + reduction(+ : cGBLChi2Rej) \ + reduction(+ : cGBLConstruct) \ + reduction(+ : chi2Sum) \ + reduction(+ : lostWeightSum) \ + reduction(+ : ndfSum) +#endif + { +#ifdef WITH_OPENMP + const int tid = omp_get_thread_num(); +#else + const int tid = 0; +#endif + auto& gblTrajSlot = gblTrajSlots[tid]; + auto& resTrackSlot = resTrackSlots[tid]; + +#ifdef WITH_OPENMP +#pragma omp for schedule(dynamic) +#endif + for (size_t iTrk = 0; iTrk < (int)itsTracks.size(); ++iTrk) { + const auto& trk = itsTracks[iTrk]; + if (trk.getNClusters() < mParams->minITSCls || + (trk.getChi2() / ((float)trk.getNClusters() * 2 - 5)) >= mParams->maxITSChi2Ndf || + trk.getPt() < mParams->minPt || + (mUseMC && (!mcLbls[iTrk].isValid() || !mcLbls[iTrk].isCorrect()))) { + continue; + } + ++cSelected; + Track& resTrack = resTrackSlot.emplace_back(); + if (!prepareITSTrack((int)iTrk, trk, resTrack)) { + ++cFailedRefit; + resTrackSlot.pop_back(); + continue; + } + + o2::track::TrackParD* refLin = nullptr; + if (mParams->useStableRef) { + refLin = &resTrack.track; + } + + // outward stepping from track IU + auto wTrk = resTrack.track; + const bool hasPV = resTrack.info[0].lr == -1; + std::vector points; + bool failed = false; + const int np = (int)resTrack.points.size(); + track::TrackLTIntegral lt; + lt.setTimeNotNeeded(); + constexpr int perm[5] = {4, 2, 3, 0, 1}; // ALICE->GBL: Q/Pt,Snp,Tgl,Y,Z + for (int ip{0}; ip < np; ++ip) { + const auto& frame = resTrack.info[ip]; + gbl::Matrix5d err = gbl::Matrix5d::Identity(), jacALICE = gbl::Matrix5d::Identity(), jacGBL; + float msErr = 0.f; + if (ip) { + // numerically calculates the transport jacobian from prev. point to this point + // then we actually do the step to the point and accumulate the material + if (!getTransportJacobian(wTrk, frame.x, frame.alpha, jacALICE, err) || + !prop->propagateToAlphaX(wTrk, refLin, frame.alpha, frame.x, false, mParams->maxSnp, mParams->maxStep, 1, mParams->corrType, <)) { + ++cFailedProp; + failed = true; + break; + } + msErr = its::math_utils::MSangle(trk.getPID().getMass(), trk.getP(), lt.getX2X0()); + // after computing jac, reorder to GBL convention + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + jacGBL(i, j) = jacALICE(perm[i], perm[j]); + } + } + } + + // wTrk is now in the measurment frame + gbl::GblPoint point(jacGBL); + // measurement + Eigen::Vector2d res, prec; + res << frame.positionTrackingFrame[0] - wTrk.getY(), frame.positionTrackingFrame[1] - wTrk.getZ(); + + // here we can apply some misalignment on the measurment + if (!applyMisalignment(res, frame, wTrk, iTrk)) { + failed = true; + break; + } + + prec << 1. / resTrack.points[ip].sig2y, 1. / resTrack.points[ip].sig2z; + // the projection matrix is in the tracking frame the idendity so no need to diagonalize it + point.addMeasurement(res, prec); + if (msErr > mParams->minMS && ip < np - 1) { + Eigen::Vector2d scat(0., 0.), scatPrec = Eigen::Vector2d::Constant(1. / (msErr * msErr)); + point.addScatterer(scat, scatPrec); + lt.clearFast(); // clear if accounted + } + + if (frame.lr >= 0) { + GlobalLabel lbl(0, frame.sens, true); + if (mChip2Hiearchy.find(lbl) == mChip2Hiearchy.end()) { + LOGP(fatal, "Cannot find global label: {}", lbl.asString()); + } + + // derivatives for all sensitive volumes and their parents + // this is the derivative in TRK but we want to align in LOC + // so dr/da_(LOC) = dr/da_(TRK) * da_(TRK)/da_(LOC) + const auto* tileVol = mChip2Hiearchy.at(lbl); + const auto derCtx = makeDerivativeContext(frame, wTrk); + Matrix36 der = getRigidBodyBaseDerivatives(derCtx); + + // count rigid body columns: only volumes with real DOFs (not DOFPseudo) + int nColRB{0}; + for (const auto* v = tileVol; v && !v->isRoot(); v = v->getParent()) { + if (v->getRigidBody()) { + nColRB += v->getRigidBody()->nDOFs(); + } + } + + // count calibration columns + const auto* sensorVol = tileVol->getParent(); + const auto* calibSet = sensorVol ? sensorVol->getCalib() : nullptr; + const int nCalib = calibSet ? calibSet->nDOFs() : 0; + const int nCol = nColRB + nCalib; + + std::vector gLabels; + gLabels.reserve(nCol); + Eigen::MatrixXd gDer(3, nCol); + gDer.setZero(); + Eigen::Index curCol{0}; + + // 1) tile: TRK -> LOC via precomputed T2L and J_L2T + const double posTrk[3] = {frame.x, 0., 0.}; + double posLoc[3]; + tileVol->getT2L().LocalToMaster(posTrk, posLoc); + Matrix66 jacL2T; + tileVol->computeJacobianL2T(posLoc, jacL2T); + der *= jacL2T; + if (tileVol->getRigidBody()) { + const int nd = tileVol->getRigidBody()->nDOFs(); + for (int iDOF = 0; iDOF < nd; ++iDOF) { + gLabels.push_back(tileVol->getLabel().rawGBL(iDOF)); + } + gDer.middleCols(curCol, nd) = der; + curCol += nd; + } + + // 2) chain through parents: child's J_L2P + for (const auto* child = tileVol; child->getParent() && !child->getParent()->isRoot(); child = child->getParent()) { + der *= child->getJL2P(); + const auto* parent = child->getParent(); + if (parent->getRigidBody()) { + const int nd = parent->getRigidBody()->nDOFs(); + for (int iDOF = 0; iDOF < nd; ++iDOF) { + gLabels.push_back(parent->getLabel().rawGBL(iDOF)); + } + gDer.middleCols(curCol, nd) = der; + curCol += nd; + } + } + + // 3) calibration derivatives (apply directly on the whole sensor, not on individual tiles) + if (calibSet) { + const int nd = calibSet->nDOFs(); + Eigen::MatrixXd calDer(3, nd); + calibSet->fillDerivatives(derCtx, calDer); + for (int iDOF = 0; iDOF < nd; ++iDOF) { + gLabels.push_back(sensorVol->getLabel().asCalib().rawGBL(iDOF)); + } + gDer.middleCols(curCol, nd) = calDer; + curCol += nd; + } + point.addGlobals(gLabels, gDer); + } + + if (mOutOpt[OutputOpt::VerboseGBL]) { + static Eigen::IOFormat fmt(4, 0, ", ", "\n", "[", "]"); + LOGP(info, "WORKING-POINT {}", ip); + LOGP(info, "Track: {}", wTrk.asString()); + LOGP(info, "FrameInfo: {}", frame.asString()); + std::cout << "jacALICE:\n" + << jacALICE.format(fmt) << '\n'; + std::cout << "jacGBL:\n" + << jacGBL.format(fmt) << '\n'; + LOGP(info, "Point {}: GBL res=({}, {}), KF stored res=({}, {})", + ip, res[0], res[1], resTrack.points[ip].dy, resTrack.points[ip].dz); + LOGP(info, "residual: dy={} dz={}", res[0], res[1]); + LOGP(info, "precision: precY={} precZ={}", prec[0], prec[1]); + point.printPoint(5); + } + points.push_back(point); + } + if (!failed) { + gbl::GblTrajectory traj(points, std::abs(bz) > 0.01); + if (traj.isValid()) { + double chi2 = NAN, lostWeight = NAN; + int ndf = 0; + if (auto ierr = traj.fit(chi2, ndf, lostWeight); !ierr) { + if (mOutOpt[OutputOpt::VerboseGBL]) { + LOGP(info, "GBL FIT chi2 {} ndf {}", chi2, ndf); + traj.printTrajectory(5); + } + if (chi2 / ndf > mParams->maxChi2Ndf && cGBLChi2Rej++ < 10) { + LOGP(error, "GBL fit exceeded red chi2 {}", chi2 / ndf); + if (std::abs(resTrack.kfFit.chi2Ndf - 1) < 0.02) { + LOGP(error, "\tGBL is far away from good KF fit!!!!"); + continue; + } + } else { + ++cGBLFit; + chi2Sum += chi2; + lostWeightSum += lostWeight; + ndfSum += ndf; + if (mOutOpt[OutputOpt::MilleData]) { + gblTrajSlot.push_back(traj); + } + FitInfo fit; + fit.ndf = ndf; + fit.chi2 = (float)chi2; + fit.chi2Ndf = (float)chi2 / (float)ndf; + resTrack.gblFit = fit; + } + } else { + ++cGBLFitFail; + } + } else { + ++cGBLConstruct; + } + } + } + } + auto timeEnd = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(timeEnd - timeStart); + LOGP(info, "Fitted {} tracks out of {} (selected {}) in {} sec", cGBLFit, itsTracks.size(), cSelected, duration.count() / 1e3); + LOGP(info, "\tRefit failed for {} tracks; Failed prop for {} tracks", cFailedRefit, cFailedProp); + LOGP(info, "\tGBL SUMMARY:"); + LOGP(info, "\t\tGBL construction failed {}", cGBLConstruct); + LOGP(info, "\t\tGBL fit failed {}", cGBLFitFail); + LOGP(info, "\t\tGBL chi2Ndf rejected {}", cGBLChi2Rej); + if (!ndfSum) { + LOGP(info, "\t\tGBL Chi2/Ndf = NDF IS 0"); + } else { + LOGP(info, "\t\tGBL Chi2/Ndf = {}", chi2Sum / ndfSum); + } + LOGP(info, "\t\tGBL LostWeight = {}", lostWeightSum); + LOGP(info, "Streaming results to output"); + if (mOutOpt[OutputOpt::MilleData]) { + gbl::MilleBinary mille(mParams->milleBinFile, true); + for (auto& slot : gblTrajSlots) { + for (auto& traj : slot) { + traj.milleOut(mille); + } + } + } + if (mOutOpt[OutputOpt::Debug]) { + for (auto& slot : resTrackSlots) { + for (auto& res : slot) { + (*mDBGOut) << "res" + << "trk=" << res + << "\n"; + } + } + } +} + +void AlignmentSpec::updateTimeDependentParams(ProcessingContext& pc) +{ + o2::base::GRPGeomHelper::instance().checkUpdates(pc); + if (static bool initOnce{false}; !initOnce) { + initOnce = true; + auto geom = o2::its::GeometryTGeo::Instance(); + o2::its::GeometryTGeo::Instance()->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G, o2::math_utils::TransformType::T2G)); + mParams = &AlignmentParams::Instance(); + mParams->printKeyValues(true, true); + + buildHierarchy(); + + if (mParams->doMisalignmentLeg || mParams->doMisalignmentRB || mParams->doMisalignmentInex) { + mMisalignment = {}; + for (auto& rb : mRigidBodyParams) { + rb.setZero(); + } + if (!mParams->misAlgJson.empty()) { + mMisalignment = loadMisalignmentModel(mParams->misAlgJson); + if (mParams->doMisalignmentRB) { + using json = nlohmann::json; + std::ifstream f(mParams->misAlgJson); + auto data = json::parse(f); + for (const auto& item : data) { + int id = item["id"].get(); + if (!item.contains("rigidBody")) { + continue; + } + auto rb = item["rigidBody"].get>(); + for (int k = 0; k < 6 && k < static_cast(rb.size()); ++k) { + mRigidBodyParams[id](k) = rb[k]; + } + } + } + } + } + } +} + +void AlignmentSpec::buildHierarchy() +{ + if (mIsITS3) { + mHierarchy = buildHierarchyIT3(mChip2Hiearchy); + } else { + mHierarchy = buildHierarchyITS(mChip2Hiearchy); + } + + if (!mParams->dofConfigJson.empty()) { + applyDOFConfig(mHierarchy.get(), mParams->dofConfigJson); + } + + mHierarchy->finalise(); + if (mOutOpt[OutputOpt::MilleSteer]) { + std::ofstream tree(mParams->milleTreeFile); + mHierarchy->writeTree(tree); + std::ofstream cons(mParams->milleConFile); + mHierarchy->writeRigidBodyConstraints(cons); + std::ofstream par(mParams->milleParamFile); + mHierarchy->writeParameters(par); + } +} + +bool AlignmentSpec::getTransportJacobian(const TrackD& track, double xTo, double alphaTo, gbl::Matrix5d& jac, gbl::Matrix5d& err) +{ + auto prop = o2::base::PropagatorD::Instance(); + const auto bz = prop->getNominalBz(); + const auto minStep = std::sqrt(std::numeric_limits::epsilon()); + const gbl::Vector5d x0(track.getParams()); + auto trackC = track; + o2::track::TrackParD* refLin{nullptr}; + if (mParams->useStableRef) { + refLin = &trackC; + } + + auto propagate = [&](gbl::Vector5d& p) -> bool { + TrackD tmp(track); + for (int i{0}; i < track::kNParams; ++i) { + tmp.setParam(p[i], i); + } + if (!prop->propagateToAlphaX(tmp, refLin, alphaTo, xTo, false, mParams->maxSnp, mParams->maxStep, 1, mParams->corrType)) { + return false; + } + p = gbl::Vector5d(tmp.getParams()); + return true; + }; + + for (int iPar{0}; iPar < track::kNParams; ++iPar) { + // step size + double h = std::min(mParams->ridderMaxIniStep[iPar], std::max(minStep, std::abs(track.getParam(iPar)) * mParams->ridderRelIniStep[iPar]) * std::pow(mParams->ridderShrinkFac, mParams->ridderMaxExtrap / 2)); + ; + // romberg tableu + Eigen::MatrixXd cur(track::kNParams, mParams->ridderMaxExtrap); + Eigen::MatrixXd pre(track::kNParams, mParams->ridderMaxExtrap); + double normErr = std::numeric_limits::max(); + gbl::Vector5d bestDeriv = gbl::Vector5d::Constant(std::numeric_limits::max()); + for (int iExt{0}; iExt < mParams->ridderMaxExtrap; ++iExt) { + gbl::Vector5d xPlus = x0, xMinus = x0; + xPlus(iPar) += h; + xMinus(iPar) -= h; + if (!propagate(xPlus) || !propagate(xMinus)) { + return false; + } + cur.col(0) = (xPlus - xMinus) / (2.0 * h); + if (!iExt) { + bestDeriv = cur.col(0); + } + // shrink step in next iteration + h /= mParams->ridderShrinkFac; + // richardson extrapolation + double fac = mParams->ridderShrinkFac * mParams->ridderShrinkFac; + for (int k{1}; k <= iExt; ++k) { + cur.col(k) = (fac * cur.col(k - 1) - pre.col(k - 1)) / (fac - 1.0); + fac *= mParams->ridderShrinkFac * mParams->ridderShrinkFac; + double e = std::max((cur.col(k) - cur.col(k - 1)).norm(), (cur.col(k) - pre.col(k - 1)).norm()); + if (e <= normErr) { + normErr = e; + bestDeriv = cur.col(k); + if (normErr < mParams->ridderEps) { + break; + } + } + } + if (normErr < mParams->ridderEps) { + break; + } + // check stability + if (iExt > 0) { + double tableauErr = (cur.col(iExt) - pre.col(iExt - 1)).norm(); + if (tableauErr >= 2.0 * normErr) { + break; + } + } + std::swap(cur, pre); + } + if (bestDeriv.isApproxToConstant(std::numeric_limits::max())) { + return false; + } + jac.col(iPar) = bestDeriv; + err.col(iPar) = gbl::Vector5d::Constant(normErr); + } + + if (jac.isIdentity(1e-8)) { + LOGP(error, "Near jacobian idendity for taking track from {} to {}", track.getX(), xTo); + return false; + } + + return true; +} + +bool AlignmentSpec::prepareITSTrack(int iTrk, const o2::its::TrackITS& itsTrack, align::Track& resTrack) +{ + const auto itsClRefs = mRecoData->getITSTracksClusterRefs(); + auto trFit = convertTrack(itsTrack.getParamOut()); // take outer track fit as start of refit + auto prop = o2::base::PropagatorD::Instance(); + auto geom = o2::its::GeometryTGeo::Instance(); + const auto bz = prop->getNominalBz(); + std::array frameArr{}; + o2::track::TrackParD trkOut, *refLin = nullptr; + if (mParams->useStableRef) { + refLin = &(trkOut = trFit); + } + + auto accountCluster = [&](int i, TrackD& tr, float& chi2, Measurement& meas, o2::track::TrackParD* refLin) { + if (frameArr[i]) { // update with cluster + if (!prop->propagateToAlphaX(tr, refLin, frameArr[i]->alpha, frameArr[i]->x, false, mParams->maxSnp, mParams->maxStep, 1, mParams->corrType)) { + return 2; + } + meas.dy = frameArr[i]->positionTrackingFrame[0] - tr.getY(); + meas.dz = frameArr[i]->positionTrackingFrame[1] - tr.getZ(); + meas.sig2y = frameArr[i]->covarianceTrackingFrame[0]; + meas.sig2z = frameArr[i]->covarianceTrackingFrame[2]; + meas.z = tr.getZ(); + meas.phi = tr.getPhi(); + o2::math_utils::bringTo02Pid(meas.phi); + chi2 += (float)tr.getPredictedChi2Quiet(frameArr[i]->positionTrackingFrame, frameArr[i]->covarianceTrackingFrame); + if (!tr.update(frameArr[i]->positionTrackingFrame, frameArr[i]->covarianceTrackingFrame)) { + return 2; + } + if (refLin) { // displace the reference to the last updated cluster + refLin->setY(frameArr[i]->positionTrackingFrame[0]); + refLin->setZ(frameArr[i]->positionTrackingFrame[1]); + } + return 0; + } + return 1; + }; + + FrameInfoExt pvInfo; + if (mWithPV) { // add PV as constraint + const int iPV = mT2PV[iTrk]; + if (iPV < 0) { + return false; + } + const auto& pv = mPVs[iPV]; + auto tmp = convertTrack(itsTrack.getParamIn()); + if (!prop->propagateToDCA(pv, tmp, bz)) { + return false; + } + pvInfo.alpha = (float)tmp.getAlpha(); + double ca{0}, sa{0}; + o2::math_utils::bringToPMPid(pvInfo.alpha); + o2::math_utils::sincosd(pvInfo.alpha, sa, ca); + pvInfo.x = tmp.getX(); + pvInfo.positionTrackingFrame[0] = -pv.getX() * sa + pv.getY() * ca; + pvInfo.positionTrackingFrame[1] = pv.getZ(); + pvInfo.covarianceTrackingFrame[0] = 0.5 * (pv.getSigmaX2() + pv.getSigmaY2()); + pvInfo.covarianceTrackingFrame[2] = pv.getSigmaY2(); + pvInfo.sens = -1; + pvInfo.lr = -1; + frameArr[0] = &pvInfo; + } + + // collect all track clusters to array, placing them to layer+1 slot + int nCl = itsTrack.getNClusters(); + for (int i = 0; i < nCl; i++) { // clusters are ordered from the outermost to the innermost + const auto& curInfo = mITSTrackingInfo[itsClRefs[itsTrack.getClusterEntry(i)]]; + frameArr[1 + curInfo.lr] = &curInfo; + } + + // start refit + resTrack.points.clear(); + resTrack.info.clear(); + trFit.resetCovariance(); + trFit.setCov(trFit.getQ2Pt() * trFit.getQ2Pt() * trFit.getCov()[14], 14); + float chi2{0}; + for (int i{7}; i >= 0; --i) { + Measurement point; + int res = accountCluster(i, trFit, chi2, point, refLin); + if (res == 2) { + return false; + } else if (res == 0) { + resTrack.points.push_back(point); + resTrack.info.push_back(*frameArr[i]); + resTrack.track = trFit; // put track to whatever the IU is + } + } + // reverse inserted points so they are in the same order as the track + std::reverse(resTrack.info.begin(), resTrack.info.end()); + std::reverse(resTrack.points.begin(), resTrack.points.end()); + resTrack.kfFit.chi2 = chi2; + resTrack.kfFit.ndf = (int)resTrack.info.size() * 2 - 5; + resTrack.kfFit.chi2Ndf = chi2 / (float)resTrack.kfFit.ndf; + + return true; +} + +void AlignmentSpec::prepareMeasurments(std::span clusters, std::span patterns) +{ + 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]; + } + math_utils::bringToPMPid(alpha); + mITSTrackingInfo.emplace_back(sens, lay, x, alpha, + std::array{trkXYZ.y(), trkXYZ.z()}, + std::array{sigmaY2, 0., sigmaZ2}); + } +} + +void AlignmentSpec::buildT2V() +{ + const auto& itsTracks = mRecoData->getITSTracks(); + mT2PV.clear(); + mT2PV.resize(itsTracks.size(), -1); + if (mUseMC) { + mPVs.reserve(mcReader->getNEvents(0)); + for (int iEve{0}; iEve < mcReader->getNEvents(0); ++iEve) { + const auto& eve = mcReader->getMCEventHeader(0, iEve); + dataformats::VertexBase vtx; + constexpr float err{22e-4f}; + vtx.setX((float)eve.GetX()); + vtx.setY((float)eve.GetY()); + vtx.setZ((float)eve.GetZ()); + vtx.setSigmaX(err); + vtx.setSigmaY(err); + vtx.setSigmaZ(err); + mPVs.push_back(vtx); + } + const auto& mcLbls = mRecoData->getITSTracksMCLabels(); + for (size_t iTrk{0}; iTrk < mcLbls.size(); ++iTrk) { + const auto& lbl = mcLbls[iTrk]; + if (!lbl.isValid() || !lbl.isCorrect()) { + continue; + } + const auto& mcTrk = mcReader->getTrack(lbl); + if (mcTrk->isPrimary()) { + mT2PV[iTrk] = lbl.getEventID(); + } + } + } else { + LOGP(fatal, "Data PV to track TODO"); + } +} + +bool AlignmentSpec::applyMisalignment(Eigen::Vector2d& res, const FrameInfoExt& frame, const TrackD& wTrk, size_t iTrk) +{ + if (!constants::detID::isDetITS3(frame.sens)) { + return true; + } + + const int sensorID = constants::detID::getSensorID(frame.sens); + const int layerID = constants::detID::getDetID2Layer(frame.sens); + const MisalignmentFrame misFrame{ + .sensorID = sensorID, + .layerID = layerID, + .x = frame.x, + .alpha = frame.alpha, + .z = frame.positionTrackingFrame[1]}; + + // --- Legendre deformation (non-rigid-body) --- + if (mParams->doMisalignmentLeg && mIsITS3 && mUseMC) { + const auto prop = o2::base::PropagatorD::Instance(); + + const auto lbl = mRecoData->getITSTracksMCLabels()[iTrk]; + const auto mcTrk = mcReader->getTrack(lbl); + if (!mcTrk) { + return false; + } + std::array xyz{mcTrk->GetStartVertexCoordinatesX(), mcTrk->GetStartVertexCoordinatesY(), mcTrk->GetStartVertexCoordinatesZ()}; + std::array pxyz{mcTrk->GetStartVertexMomentumX(), mcTrk->GetStartVertexMomentumY(), mcTrk->GetStartVertexMomentumZ()}; + TParticlePDG* pPDG = TDatabasePDG::Instance()->GetParticle(mcTrk->GetPdgCode()); + if (!pPDG) { + return false; + } + o2::track::TrackParD mcPar(xyz, pxyz, TMath::Nint(pPDG->Charge() / 3), false); + + auto mcAtCl = mcPar; + if (!mcAtCl.rotate(frame.alpha) || !prop->PropagateToXBxByBz(mcAtCl, frame.x)) { + return false; + } + + const auto shift = evaluateLegendreShift(mMisalignment[sensorID], misFrame, computeTrackSlopes(mcAtCl.getSnp(), mcAtCl.getTgl())); + if (!shift.accepted) { + return false; + } + + res[0] += shift.dy; + res[1] += shift.dz; + } + + // --- Rigid body misalignment --- + // Must use the same derivative chain as GBL: + // dres/da_parent = dres/da_TRK * J_L2T_tile * J_L2P_tile + // The tile is a pseudo-volume; Millepede fits at the halfBarrel (parent) level. + if (mParams->doMisalignmentRB) { + GlobalLabel lbl(0, frame.sens, true); + if (mChip2Hiearchy.find(lbl) == mChip2Hiearchy.end()) { + return true; // sensor not in hierarchy, skip + } + const auto* tileVol = mChip2Hiearchy.at(lbl); + + // derivative in TRK frame (3x6: rows = dy, dz, dsnp) + Matrix36 der = getRigidBodyBaseDerivatives(makeDerivativeContext(frame, wTrk)); + + // TRK -> tile LOC + const double posTrk[3] = {frame.x, 0., 0.}; + double posLoc[3]; + tileVol->getT2L().LocalToMaster(posTrk, posLoc); + Matrix66 jacL2T; + tileVol->computeJacobianL2T(posLoc, jacL2T); + der *= jacL2T; + + // tile LOC -> halfBarrel LOC (same chain as GBL hierarchy walk) + der *= tileVol->getJL2P(); + + // apply: delta_res = der * delta_a_halfBarrel + Eigen::Vector3d shift = der * mRigidBodyParams[sensorID]; + res[0] += shift[0]; // dy + res[1] += shift[1]; // dz + } + + // --- In-extensional deformation --- + // displacement field u(phi,z) = (u_phi, u_z, u_r) + // dy = -u_phi + y' * u_r, dz = -u_z + z' * u_r + if (mParams->doMisalignmentInex) { + const auto shift = evaluateInextensionalShift(mMisalignment[sensorID], misFrame, computeTrackSlopes(wTrk.getSnp(), wTrk.getTgl())); + res[0] += shift.dy; + res[1] += shift.dz; + } + + if (mOutOpt[OutputOpt::MisRes]) { + (*mDBGOut) << "mis" + << "dy=" << res[0] + << "dz=" << res[1] + << "sens=" << sensorID + << "lay=" << layerID + << "z=" << frame.positionTrackingFrame[1] + << "phi=" << frame.alpha + << "\n"; + } + + return true; +} + +void AlignmentSpec::endOfStream(EndOfStreamContext& /*ec*/) +{ + mDBGOut->Close(); + mDBGOut.reset(); +} + +void AlignmentSpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) +{ + if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { + return; + } + if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) { + LOG(info) << "its cluster dictionary updated"; + mITSDict = (const o2::itsmft::TopologyDictionary*)obj; + return; + } + if (matcher == ConcreteDataMatcher("IT3", "CLUSDICT", 0)) { + LOG(info) << "it3 cluster dictionary updated"; + mIT3Dict = (const o2::its3::TopologyDictionary*)obj; + return; + } +} + +DataProcessorSpec getAlignmentSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, bool withPV, bool withITS, OutputEnum out) +{ + auto dataRequest = std::make_shared(); + std::shared_ptr ggRequest{nullptr}; + if (!out[OutputOpt::MilleRes]) { + dataRequest->requestTracks(srcTracks, useMC); + if (!withITS) { + dataRequest->requestIT3Clusters(useMC); + } else { + dataRequest->requestClusters(srcClusters, useMC); + } + if (withPV && !useMC) { + dataRequest->requestPrimaryVertices(useMC); + } + ggRequest = std::make_shared(false, // orbitResetTime + false, // GRPECS=true + true, // GRPLHCIF + true, // GRPMagField + true, // askMatLUT + o2::base::GRPGeomRequest::Aligned, // geometry + dataRequest->inputs, // inputs + true, // askOnce + true); // propagatorD + } else { + dataRequest->inputs.emplace_back("dummy", "GLO", "DUMMY_OUT", 0); + ggRequest = std::make_shared(false, // orbitResetTime + false, // GRPECS=true + false, // GRPLHCIF + false, // GRPMagField + false, // askMatLUT + o2::base::GRPGeomRequest::Aligned, // geometry + dataRequest->inputs); + } + + Options opts{ + {"nthreads", VariantType::Int, 1, {"number of threads"}}, + }; + + return DataProcessorSpec{ + .name = "its3-alignment", + .inputs = dataRequest->inputs, + .outputs = {}, + .algorithm = AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, useMC, withPV, withITS, out)}, + .options = opts}; +} +} // namespace o2::its3::align diff --git a/Detectors/Upgrades/ITS3/alignment/src/AlignmentTypes.cxx b/Detectors/Upgrades/ITS3/alignment/src/AlignmentTypes.cxx new file mode 100644 index 0000000000000..5ad06a6c78381 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/src/AlignmentTypes.cxx @@ -0,0 +1,24 @@ +// 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. + +#include +#include + +#include "ITS3Align/AlignmentTypes.h" +ClassImp(o2::its3::align::Point); +ClassImp(o2::its3::align::FrameInfoExt); +ClassImp(o2::its3::align::FitInfo); +ClassImp(o2::its3::align::Track); + +std::string o2::its3::align::FrameInfoExt::asString() const +{ + return std::format("Sensor={} Layer={} X={} Alpha={}\n\tMEAS: y={} z={}", sens, lr, x, alpha, positionTrackingFrame[0], positionTrackingFrame[1]); +} diff --git a/Detectors/Upgrades/ITS3/alignment/src/Deformations.cxx b/Detectors/Upgrades/ITS3/alignment/src/Deformations.cxx deleted file mode 100644 index 38a959cf7030f..0000000000000 --- a/Detectors/Upgrades/ITS3/alignment/src/Deformations.cxx +++ /dev/null @@ -1,41 +0,0 @@ -// 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. - -#include "ITS3Align/Deformations.h" -#include "ITS3Align/MisalignmentParameters.h" - -#include "Framework/Logger.h" - -#include - -namespace fs = std::filesystem; - -namespace o2::its3::align -{ - -void Deformations::init(const fs::path& path) -{ - if (!fs::exists(path)) { - LOGP(fatal, "File {} does not exists!", path.c_str()); - } - - auto params = MisalignmentParameters::load(path.string()); - LOGP(info, "Loaded Parameters"); - - // Set the legendre pols - for (int iSensor{0}; iSensor < 6; ++iSensor) { - mLegX[iSensor] = o2::math_utils::Legendre2DPolynominal(params->getLegendreCoeffX(iSensor)); - mLegY[iSensor] = o2::math_utils::Legendre2DPolynominal(params->getLegendreCoeffY(iSensor)); - mLegZ[iSensor] = o2::math_utils::Legendre2DPolynominal(params->getLegendreCoeffZ(iSensor)); - } -} - -} // namespace o2::its3::align diff --git a/Detectors/Upgrades/ITS3/alignment/src/ITS3AlignLinkDef.h b/Detectors/Upgrades/ITS3/alignment/src/ITS3AlignLinkDef.h index ef526284f3a58..e6e6a8c2cc73c 100644 --- a/Detectors/Upgrades/ITS3/alignment/src/ITS3AlignLinkDef.h +++ b/Detectors/Upgrades/ITS3/alignment/src/ITS3AlignLinkDef.h @@ -15,6 +15,12 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::its3::align::MisalignmentParameters + ; +#pragma link C++ struct o2::its3::align::AlignmentParams + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::its3::align::AlignmentParams> + ; + +#pragma link C++ struct o2::its3::align::Measurement + ; +#pragma link C++ struct o2::its3::align::FrameInfoExt + ; +#pragma link C++ struct o2::its3::align::FitInfo + ; +#pragma link C++ struct o2::its3::align::Track + ; #endif diff --git a/Detectors/Upgrades/ITS3/alignment/src/MisalignmentHits.cxx b/Detectors/Upgrades/ITS3/alignment/src/MisalignmentHits.cxx deleted file mode 100644 index 66ab4c8090b54..0000000000000 --- a/Detectors/Upgrades/ITS3/alignment/src/MisalignmentHits.cxx +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright 2020-2022 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. - -#include "ITS3Align/MisalignmentHits.h" -#include "ITS3Base/ITS3Params.h" -#include "SimConfig/DigiParams.h" -#include "DetectorsBase/Propagator.h" -#include "Framework/Logger.h" - -#include "Math/Factory.h" -#include "Math/UnaryOperators.h" -#include "TGeoNode.h" -#include "TGeoBBox.h" -#include "TString.h" - -#include -#include -#include -#include - -namespace o2::its3::align -{ - -void MisAlignmentHits::init() -{ - if (o2::its3::ITS3Params::Instance().misalignmentHitsUseProp) { - mMethod = PropMethod::Propagator; - } else { - mMethod = PropMethod::Line; - } - - mGeo = o2::its::GeometryTGeo::Instance(); - - mMinimizer.reset(ROOT::Math::Factory::CreateMinimizer("Minuit2", "Migrad")); - if (mMinimizer == nullptr) { - LOGP(fatal, "Cannot create minimizer"); - } - mMinimizer->SetMaxFunctionCalls(1'000'000'000); - mMinimizer->SetStrategy(0); - mMinimizer->SetPrintLevel(0); - - if (mMethod == PropMethod::Propagator) { - LOGP(info, "Using propagator to find intersection"); - const auto& prefix = o2::conf::DigiParams::Instance().digitizationgeometry_prefix; - mMCReader = std::make_unique(prefix, o2::steer::MCKinematicsReader::Mode::kMCKine); - mMinimizer->SetFunction(mPropagator); - } else { - LOGP(info, "Using local straight-line to find intersection"); - mMinimizer->SetFunction(mLine); - } - - resetStats(); - - if (auto file = o2::its3::ITS3Params::Instance().misalignmentHitsParams; file.empty()) { - LOGP(fatal, "No parameter file specified"); - } else { - mDeformations.init(file); - } -} - -std::optional MisAlignmentHits::processHit(int iEvent, const o2::itsmft::Hit& hit) -{ - ++mStats[Stats::kHitTotal]; - - if (!constants::detID::isDetITS3(hit.GetDetectorID())) { - ++mStats[Stats::kHitIsOB]; - return hit; - } - ++mStats[Stats::kHitIsIB]; - - // Set the working hits - mCurHit = hit; - mCurWorkingHits[WorkingHit::kEntering] = WorkingHit(iEvent, WorkingHit::kEntering, hit); - mCurWorkingHits[WorkingHit::kExiting] = WorkingHit(iEvent, WorkingHit::kExiting, hit); - - // Do work - if (!deformHit(WorkingHit::kEntering) || !deformHit(WorkingHit::kExiting)) { - ++mStats[Stats::kHitDead]; - return std::nullopt; - } - ++mStats[Stats::kHitAlive]; - - // Set the possibly new detectorIDs with mid point approximation - auto midPointOrig = mCurWorkingHits[WorkingHit::kEntering].mPoint + (mCurWorkingHits[WorkingHit::kExiting].mPoint - mCurWorkingHits[WorkingHit::kEntering].mPoint) * 0.5; - auto midPointDef = mCurWorkingHits[WorkingHit::kEntering].mPointDef + (mCurWorkingHits[WorkingHit::kExiting].mPointDef - mCurWorkingHits[WorkingHit::kEntering].mPointDef) * 0.5; - const short idDef = getDetID(midPointDef), idOrig = getDetID(midPointOrig); - if (idDef == -1) { - return std::nullopt; - } - - if (idDef != idOrig) { - ++mStats[Stats::kHitMigrated]; - } else { - ++mStats[Stats::kHitNotMigrated]; - } - - if constexpr (false) { - /// TODO Does not yet work correctly - /// Check if we crossed a boundary within the entering and exiting hit from the midpoint - bool crossesBoundary{false}; - TGeoNode *nEnt{nullptr}, *nExt{nullptr}; - { - auto dirEnt = mCurWorkingHits[WorkingHit::kEntering].mPointDef - midPointDef; - auto stepEnt = std::min(static_cast(dirEnt.R()), std::abs(dirEnt.R() - 5.e-4)); - auto dirEntU = dirEnt.Unit(); - gGeoManager->SetCurrentPoint(midPointDef.X(), midPointDef.Y(), midPointDef.Z()); - gGeoManager->SetCurrentDirection(dirEntU.X(), dirEntU.Y(), dirEntU.Z()); - nEnt = gGeoManager->FindNextBoundaryAndStep(stepEnt, false); - if (gGeoManager->IsOnBoundary()) { - ++mStats[Stats::kHitEntBoundary]; - crossesBoundary = true; - } - } - { - auto dirExt = midPointDef - mCurWorkingHits[WorkingHit::kEntering].mPointDef; - auto stepExt = std::min(static_cast(dirExt.R()), std::abs(dirExt.R() - 5.e-4)); - auto dirExtU = dirExt.Unit(); - gGeoManager->SetCurrentPoint(midPointDef.X(), midPointDef.Y(), midPointDef.Z()); - gGeoManager->SetCurrentDirection(dirExtU.X(), dirExtU.Y(), dirExtU.Z()); - nExt = gGeoManager->FindNextBoundaryAndStep(stepExt, false); - if (gGeoManager->IsOnBoundary()) { - ++mStats[Stats::kHitExtBoundary]; - crossesBoundary = true; - } - } - - if (crossesBoundary && nEnt != nullptr && nExt != nullptr) { - if (nEnt != nExt) { - return std::nullopt; - } else { - ++mStats[Stats::kHitSameBoundary]; // indicates that the step size is too large and we end up in the mother volume; just pretend that his fine for now - } - } - ++mStats[Stats::kHitNoBoundary]; - } - - // Get new postion - mCurHit.SetPosStart(mCurWorkingHits[WorkingHit::kEntering].mPointDef); - mCurHit.SetPos(mCurWorkingHits[WorkingHit::kExiting].mPointDef); - mCurHit.SetDetectorID(idDef); - - ++mStats[Stats::kHitSuccess]; - return mCurHit; -} - -bool MisAlignmentHits::deformHit(WorkingHit::HitType t) -{ - auto& wHit = mCurWorkingHits[t]; - - mMinimizer->Clear(); // clear for next iteration - constexpr double minStep{1e-5}; - constexpr double zMargin{4.0}; - constexpr double phiMargin{0.4}; - if (mMethod == PropMethod::Line) { - prepareLineMethod(t); - mMinimizer->SetVariable(0, "t", 0.0, minStep); // this is left as a free parameter on since t is very small since start and end of hit are close - } else { - if (!preparePropagatorMethod(t)) { - return false; - } - mMinimizer->SetVariable(0, "r", mPropagator.mTrack.getX(), minStep); // this is left as a free parameter on since t is very small since start and end of hit are close - } - mMinimizer->SetLimitedVariable(1, "phiStar", wHit.mPhi, minStep, - std::max(static_cast(wHit.mPhiBorder1), static_cast(wHit.mPhi) - phiMargin), - std::min(static_cast(wHit.mPhiBorder2), static_cast(wHit.mPhi) + phiMargin)); - mMinimizer->SetLimitedVariable(2, "zStar", wHit.mPoint.Z(), minStep, - std::max(static_cast(-constants::segment::lengthSensitive / 2.f), static_cast(wHit.mPoint.Z()) - zMargin), - std::min(static_cast(constants::segment::lengthSensitive / 2.f), static_cast(wHit.mPoint.Z()) + zMargin)); - - mMinimizer->Minimize(); // perform the actual minimization - - auto ss = mMinimizer->Status(); - if (ss == 1) { - ++mStats[Stats::kMinimizerCovPos]; - } else if (ss == 2) { - ++mStats[Stats::kMinimizerHesse]; - } else if (ss == 3) { - ++mStats[Stats::kMinimizerEDM]; - } else if (ss == 4) { - ++mStats[Stats::kMinimizerLimit]; - } else if (ss == 5) { - ++mStats[Stats::kMinimizerOther]; - } else { - ++mStats[Stats::kMinimizerConverged]; - } - - if (ss == 0 || ss == 1) { // for Minuit2 0=ok, 1=ok with pos. forced hesse - ++mStats[Stats::kMinimizerStatusOk]; - if (mMinimizer->MinValue() < 2e-4) { // within 2 um considering the pixel pitch this good enough - ++mStats[Stats::kMinimizerValueOk]; - } else { - ++mStats[Stats::kMinimizerValueBad]; - return false; - } - } else { - ++mStats[Stats::kMinimizerStatusBad]; - return false; - } - - // Valid solution found; calculate new position on ideal geo - wHit.recalculateIdeal(static_cast(mMinimizer->X()[1]), static_cast(mMinimizer->X()[2])); - - return true; -} - -short MisAlignmentHits::getDetID(const o2::math_utils::Point3D& point) -{ - // Do not modify the path, I do not know if this is needed but lets be safe - gGeoManager->PushPath(); - auto id = getDetIDFromCords(point); - gGeoManager->PopPath(); - return id; -} - -short MisAlignmentHits::getDetIDFromCords(const o2::math_utils::Point3D& point) -{ - // retrive if any the node which constains the point - const auto node = gGeoManager->FindNode(point.X(), point.Y(), point.Z()); - if (node == nullptr) { - ++mStats[Stats::kFindNodeFailed]; - return -1; - } - ++mStats[Stats::kFindNodeSuccess]; - - // check if this node is a sensitive volume - const std::string path = gGeoManager->GetPath(); - if (path.find(o2::its::GeometryTGeo::getITS3SensorPattern()) == std::string::npos) { - ++mStats[Stats::kProjNonSensitive]; - return -1; - } - ++mStats[Stats::kProjSensitive]; - - return getDetIDFromPath(path); -} - -short MisAlignmentHits::getDetIDFromPath(const std::string& path) const -{ - static const std::regex pattern{R"(/cave_1/barrel_1/ITSV_2/ITSUWrapVol0_1/ITS3Layer(\d+)_(\d+)/ITS3CarbonForm(\d+)_(\d+)/ITS3Chip(\d+)_(\d+)/ITS3Segment(\d+)_(\d+)/ITS3RSU(\d+)_(\d+)/ITS3Tile(\d+)_(\d+)/ITS3PixelArray(\d+)_(\d+))"}; - if (std::smatch matches; std::regex_search(path, matches, pattern)) { - if (matches.size() == 15) { - int iLayer = std::stoi(matches[1]); - int iCarbonForm = std::stoi(matches[4]); - int iSegment = std::stoi(matches[8]); - int iRSU = std::stoi(matches[10]); - int iTile = std::stoi(matches[12]); - return mGeo->getChipIndex(iLayer, iCarbonForm, 0, iSegment, iRSU, iTile); - } else { - LOGP(fatal, "Path did not contain expected number of matches ({})!", matches.size()); - } - } else { - LOGP(fatal, "Path was not matched ({})!", path); - } - __builtin_unreachable(); -} - -void MisAlignmentHits::printStats() const -{ - auto makeFraction = [&](Stats n, Stats d) -> float { return static_cast(mStats[n]) / static_cast(mStats[d] + mStats[n]); }; - LOGP(info, "Processed {} Hits (IB:{}; OB:{}) ({:.2f}%):", mStats[Stats::kHitTotal], mStats[Stats::kHitIsIB], mStats[Stats::kHitIsOB], makeFraction(Stats::kHitIsIB, Stats::kHitIsOB)); - LOGP(info, " - Minimizer Status: {} ok {} bad ({:.2f}%)", mStats[Stats::kMinimizerStatusOk], mStats[Stats::kMinimizerStatusBad], makeFraction(Stats::kMinimizerStatusOk, Stats::kMinimizerStatusBad)); - LOGP(info, " - Minimizer Value: {} ok {} bad ({:.2f}%)", mStats[Stats::kMinimizerValueOk], mStats[Stats::kMinimizerValueBad], makeFraction(Stats::kMinimizerValueOk, Stats::kMinimizerValueBad)); - LOGP(info, " - Minimizer Detailed: {} Converged {} pos. forced Hesse ({:.2f}%)", mStats[Stats::kMinimizerConverged], mStats[Stats::kMinimizerHesse], makeFraction(Stats::kMinimizerConverged, Stats::kMinimizerHesse)); - LOGP(info, " - Minimizer Detailed: {} EDM {} call limit {} other ({:.2f}%)", mStats[Stats::kMinimizerEDM], mStats[Stats::kMinimizerLimit], mStats[Stats::kMinimizerOther], makeFraction(Stats::kMinimizerEDM, Stats::kMinimizerLimit)); - LOGP(info, " - FindNode: {} ok {} failed", mStats[Stats::kFindNodeSuccess], mStats[Stats::kFindNodeFailed]); - LOGP(info, " - IsSensitve: {} yes {} no ({:.2f}%)", mStats[Stats::kProjSensitive], mStats[Stats::kProjNonSensitive], makeFraction(Stats::kProjSensitive, Stats::kProjNonSensitive)); - LOGP(info, " - IsAlive: {} yes {} no ({:.2f}%)", mStats[Stats::kHitAlive], mStats[Stats::kHitDead], makeFraction(Stats::kHitAlive, Stats::kHitDead)); - LOGP(info, " - HasMigrated: {} yes {} no ({:.2f}%)", mStats[Stats::kHitMigrated], mStats[Stats::kHitNotMigrated], makeFraction(Stats::kHitMigrated, Stats::kHitNotMigrated)); - // LOGP(info, " - Crosses Boundary: {} entering {} exiting {} same {} no", mStats[Stats::kHitEntBoundary], mStats[Stats::kHitExtBoundary], mStats[Stats::kHitSameBoundary], mStats[Stats::kHitNoBoundary]); - if (mMethod == PropMethod::Propagator) { - LOGP(info, " - Propagator: {} null track {} null pdg", mStats[Stats::kPropTrackNull], mStats[Stats::kPropPDGNull]); - } - LOGP(info, " --> Good Hits {} ({:.2f}%)", mStats[Stats::kHitSuccess], makeFraction(Stats::kHitSuccess, Stats::kHitIsIB)); -} - -void MisAlignmentHits::prepareLineMethod(WorkingHit::HitType from) -{ - // Set the starint point and radius - // always start from the entering hit that way t is always pos. defined - mLine.mStart = mCurWorkingHits[WorkingHit::kEntering].mPoint; - mLine.mRadius = mCurWorkingHits[from].mRadius; - mLine.mSensorID = mCurWorkingHits[from].mSensorID; - mLine.mPhiTot = mCurWorkingHits[from].mPhiBorder2 - mCurWorkingHits[from].mPhiBorder1; - mLine.mPhi1 = mCurWorkingHits[from].mPhiBorder1; - // Calculate the direction vector - mLine.mD[0] = mCurWorkingHits[WorkingHit::kExiting].mPoint.X() - mCurWorkingHits[WorkingHit::kEntering].mPoint.X(); - mLine.mD[1] = mCurWorkingHits[WorkingHit::kExiting].mPoint.Y() - mCurWorkingHits[WorkingHit::kEntering].mPoint.Y(); - mLine.mD[2] = mCurWorkingHits[WorkingHit::kExiting].mPoint.Z() - mCurWorkingHits[WorkingHit::kEntering].mPoint.Z(); -} - -double MisAlignmentHits::StraightLine::DoEval(const double* x) const -{ - const double t = x[0]; - const double phi = x[1]; - const double z = x[2]; - const double nphi = std::clamp((phi - mPhi1) * 2.0 / mPhiTot - 1.0, -1.0, 1.0); - const double nz = std::clamp((z - (-constants::segment::lengthSensitive / 2.0)) * 2.0 / constants::segment::lengthSensitive - 1.0, -1.0, 1.0); - - /// Find the point along the line given current t - double xline = mStart.X() + t * mD[0], - yline = mStart.Y() + t * mD[1], - zline = mStart.Z() + t * mD[2]; - - // Find the point of the deformed geometry given a certain phi' and z' - double xideal = mRadius * std::cos(phi), yideal = mRadius * std::sin(phi), - zideal = z; - const auto [dx, dy, dz] = mMis->getDeformation(mSensorID, nphi, nz); - double xdef = xideal + dx, ydef = yideal + dy, zdef = zideal + dz; - - // Minimize the euclidean distance of the line point and the deformed point - return std::hypot(xline - xdef, yline - ydef, zline - zdef); -} - -bool MisAlignmentHits::preparePropagatorMethod(WorkingHit::HitType from) -{ - mPropagator.mRadius = mCurWorkingHits[from].mRadius; - mPropagator.mSensorID = mCurWorkingHits[from].mSensorID; - mPropagator.mPhiTot = mCurWorkingHits[from].mPhiBorder2 - mCurWorkingHits[from].mPhiBorder1; - mPropagator.mPhi1 = mCurWorkingHits[from].mPhiBorder1; - const auto mcTrack = mMCReader->getTrack(mCurWorkingHits[from].mEvent, mCurWorkingHits[from].mTrackID); - if (mcTrack == nullptr) { - ++mStats[Stats::kPropTrackNull]; - return false; - } - const std::array xyz{(float)mcTrack->GetStartVertexCoordinatesX(), (float)mcTrack->GetStartVertexCoordinatesY(), (float)mcTrack->GetStartVertexCoordinatesZ()}, - pxyz{(float)mcTrack->GetStartVertexMomentumX(), (float)mcTrack->GetStartVertexMomentumY(), (float)mcTrack->GetStartVertexMomentumZ()}; - const TParticlePDG* pPDG = TDatabasePDG::Instance()->GetParticle(mcTrack->GetPdgCode()); - if (pPDG == nullptr) { - ++mStats[Stats::kPropPDGNull]; - return false; - } - mPropagator.mTrack = o2::track::TrackPar(xyz, pxyz, TMath::Nint(pPDG->Charge() / 3), false); - mPropagator.mBz = o2::base::Propagator::Instance()->getNominalBz(); - return true; -} - -double MisAlignmentHits::Propagator::DoEval(const double* x) const -{ - const double r = x[0]; - const double phi = x[1]; - const double z = x[2]; - const double nphi = (phi - mPhi1) * 2.0 / mPhiTot - 1.0; - const double nz = (z - (-constants::segment::lengthSensitive / 2.0)) * 2.0 / constants::segment::lengthSensitive - 1.0; - - auto trc = mTrack; - if (!trc.propagateTo(r, mBz)) { - return 999; - } - const auto glo = trc.getXYZGlo(); - - // Find the point of the deformed geometry given a certain phi' and z' - double xideal = mRadius * std::cos(phi), yideal = mRadius * std::sin(phi), - zideal = z; - const auto [dx, dy, dz] = mMis->getDeformation(mSensorID, nphi, nz); - double xdef = xideal + dx, ydef = yideal + dy, zdef = zideal + dz; - - // Minimize the euclidean distance of the propagator point and the deformed point - return std::hypot(glo.X() - xdef, glo.Y() - ydef, glo.Z() - zdef); -} - -} // namespace o2::its3::align diff --git a/Detectors/Upgrades/ITS3/alignment/src/MisalignmentManager.cxx b/Detectors/Upgrades/ITS3/alignment/src/MisalignmentManager.cxx deleted file mode 100644 index c9d71541bcd0e..0000000000000 --- a/Detectors/Upgrades/ITS3/alignment/src/MisalignmentManager.cxx +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2020-2022 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. - -#include "Framework/Logger.h" -#include "ITS3Align/MisalignmentManager.h" -#include "ITS3Align/MisalignmentHits.h" -#include "SimConfig/DigiParams.h" - -#include "TFile.h" -#include "TStopwatch.h" -#include "TGeoManager.h" - -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace o2::its3::align -{ - -void MisalignmentManager::createBackup(const fs::path& src, const fs::path& dest) -{ - if (fs::exists(dest)) { - LOGP(info, "Previous orignal file found, using this as src"); - } else { - if (!fs::exists(src)) { - LOGP(fatal, "File {} does not exist", src.c_str()); - } - LOGP(info, "Trying to backup file to {}", dest.c_str()); - try { - fs::rename(src, dest); - } catch (const fs::filesystem_error& err) { - LOGP(fatal, "Cannot create backup file for Hit-File: {}", err.what()); - } - } -} - -void MisalignmentManager::misalignHits() -{ - LOGP(info, "{:*^90}", " ITS3 LOCAL MISALIGNMENT START "); - - TStopwatch timer; - timer.Start(); - - MisAlignmentHits MisAligner; - MisAligner.init(); - - const fs::path oldHitFileSrc{fs::current_path().string() + "/" + o2::conf::DigiParams::Instance().digitizationgeometry_prefix + "_HitsIT3.root"}; - const fs::path oldHitFileDest{fs::current_path().string() + "/" + o2::conf::DigiParams::Instance().digitizationgeometry_prefix + "_HitsIT3_Orig.root"}; - createBackup(oldHitFileSrc, oldHitFileDest); - - std::unique_ptr origFile{TFile::Open(oldHitFileDest.c_str(), "READ")}; - if (origFile == nullptr || origFile->IsZombie()) { - LOGP(fatal, "Original file {} cannot be opened", oldHitFileDest.c_str()); - } - - std::unique_ptr origTree{origFile->Get("o2sim")}; - if (origTree == nullptr) { - LOGP(fatal, "Cannot get hit-tree from orignal file"); - } - std::vector origHits, *origHitsPtr{&origHits}; - origTree->SetBranchAddress("IT3Hit", &origHitsPtr); - - std::unique_ptr newFile{TFile::Open(oldHitFileSrc.c_str(), "RECREATE")}; - if (newFile == nullptr || newFile->IsZombie()) { - LOGP(fatal, "New file {} cannot be opened", oldHitFileSrc.c_str()); - } - - auto newTree = std::make_unique("o2sim", "o2sim"); - std::vector newHits, *newHitsPtr{nullptr}; - newTree->Branch("IT3Hit", &newHitsPtr); - - LOGP(info, "Preparations done; starting hit loop"); - auto nEntries = origTree->GetEntries(); - ULong64_t totalOrigHits{0}, totalNewHits{0}; - for (Long64_t iEntry{0}; origTree->LoadTree(iEntry) >= 0; ++iEntry) { - if (origTree->GetEntry(iEntry) <= 0) { - continue; - } - - const auto progress = (iEntry * 100) / nEntries; - LOG_IF(info, progress % 10 == 0) << "Processing event " << iEntry << " / " << nEntries; - - newHits.clear(); - newHits.reserve(origHits.size()); - for (const auto& origHit : origHits) { - if (auto newHit = MisAligner.processHit(iEntry, origHit)) { - newHits.emplace_back(*newHit); - } - } - - newHitsPtr = &newHits; - newTree->Fill(); - - totalNewHits += newHits.size(); - totalOrigHits += origHits.size(); - } - - newFile->WriteTObject(newTree.get()); - - timer.Stop(); - - MisAligner.printStats(); - - auto totalDiscardedHits = totalOrigHits - totalNewHits; - LOGP(info, "Summary: Total orignal Hits {}", totalOrigHits); - LOGP(info, "Summary: Total misaligned Hits {} ({:.2f}%)", totalNewHits, static_cast(totalNewHits) / static_cast(totalOrigHits) * 100); - LOGP(info, "Summary: Total discarded Hits {} ({:.2f}%)", totalDiscardedHits, static_cast(totalDiscardedHits) / static_cast(totalOrigHits) * 100); - LOGP(info, "Summary: Misalignment took {:.2f}s", timer.CpuTime()); - LOGP(info, "{:*^90}", " ITS3 LOCAL MISALIGNMENT END "); -} - -std::string MisalignmentManager::appendStem(const std::string& filename, const std::string& add) -{ - fs::path filepath{filename}; - auto stem = filepath.stem().string(); - auto extension = filepath.extension().string(); - return stem + add + extension; -} - -std::vector MisalignmentManager::split(const std::string& s, char delimiter) -{ - std::vector tokens; - std::string token; - std::istringstream tokenStream(s); - while (std::getline(tokenStream, token, delimiter)) { - if (!token.empty()) { - tokens.push_back(token); - } - } - return tokens; -} - -void MisalignmentManager::navigate(const std::string& path) -{ - if (!gGeoManager->cd(path.c_str())) { - LOGP(fatal, "Cannot navigate to {}", path); - } -} - -std::string MisalignmentManager::composePathSensor(int sensor) -{ - const int layerID{sensor / 2}; - const int sensorID{sensor % 2}; - return fmt::format("/cave/barrel_1/ITSV_2/ITSUWrapVol0_1/ITS3Layer{}_0/ITS3CarbonForm{}_{}", - layerID, layerID, sensorID); -} - -void MisalignmentManager::applyGlobalMatrixVolume(const std::string& path, const TGeoHMatrix& globalMatrix) -{ - gGeoManager->CdTop(); - TGeoHMatrix* pgMatrix{nullptr}; - TGeoHMatrix gAccMatrix; - std::string curPath{}; - for (const auto& comp : split(path)) { - curPath += "/" + comp; - navigate(curPath); - pgMatrix = gGeoManager->GetCurrentMatrix(); - gAccMatrix.Multiply(pgMatrix); - } - navigate(path); - auto node = gGeoManager->GetCurrentNode(); - if (node == nullptr) { - LOGP(fatal, "Nullptr for node at {}", path); - } - auto motherVol = node->GetMotherVolume(); - if (motherVol == nullptr) { - LOGP(fatal, "Nullptr for motherVol at {}", path); - } - // Compute the inverse of the accumulated global transformation matrix - auto gAccMatrix1 = gAccMatrix.Inverse(); - // Compute the relative transformation matrix for the volume - auto relativeMatrix = globalMatrix; - relativeMatrix.MultiplyLeft(gAccMatrix1); - - auto nodemat = dynamic_cast(node); - nodemat->SetMatrix(new TGeoHMatrix(globalMatrix)); - - // Force the container volume of the object to update itself - motherVol->Voxelize(""); -} - -} // namespace o2::its3::align diff --git a/Detectors/Upgrades/ITS3/alignment/src/MisalignmentParameters.cxx b/Detectors/Upgrades/ITS3/alignment/src/MisalignmentParameters.cxx deleted file mode 100644 index 0842b7252486a..0000000000000 --- a/Detectors/Upgrades/ITS3/alignment/src/MisalignmentParameters.cxx +++ /dev/null @@ -1,80 +0,0 @@ -// 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 MisalignmentParameter.cxx -/// \brief Implementation of the MisalignmentParameter class - -#include "ITS3Align/MisalignmentParameters.h" -#include "Framework/Logger.h" - -#include "TFile.h" - -#include - -ClassImp(o2::its3::align::MisalignmentParameters); - -namespace o2::its3::align -{ - -MisalignmentParameters::MisalignmentParameters() -{ - SetName("MisalignmentParameters"); - SetTitle("ITS3 MisalignmentParameters"); -} - -bool MisalignmentParameters::store(const std::string& file) const -{ - std::unique_ptr fOut(TFile::Open(file.c_str(), "RECREATE")); - if (fOut == nullptr || fOut->IsZombie()) { - LOGP(info, "Unable to save misalignment parameters"); - return false; - } - fOut->WriteObjectAny(this, "o2::its3::align::MisalignmentParameters", "ccdb_object"); - return true; -} - -MisalignmentParameters* MisalignmentParameters::load(const std::string& file) -{ - std::unique_ptr fIn(TFile::Open(file.c_str(), "READ")); - auto p = fIn->Get("ccdb_object"); - if (p == nullptr) { - LOGP(fatal, "Unable to load parameters from file!"); - } - return p; -} - -void MisalignmentParameters::printParams(unsigned int detID) const -{ - LOGP(info, "Parameters for ID={}:", detID); - LOGP(info, " - Global Trans: X={} Y={} Z={}", getGloTransX(detID), getGloTransY(detID), getGloTransZ(detID)); - LOGP(info, " - Global Rots: X={} Y={} Z={}", getGloRotX(detID), getGloRotY(detID), getGloRotZ(detID)); - if (constants::detID::isDetITS3(detID)) { - auto sensorID = constants::detID::getSensorID(detID); - LOGP(info, " - Legendre Pol X:"); - getLegendreCoeffX(sensorID).Print(); - LOGP(info, " - Legendre Pol Y:"); - getLegendreCoeffY(sensorID).Print(); - LOGP(info, " - Legendre Pol Z:"); - getLegendreCoeffZ(sensorID).Print(); - } -} - -void MisalignmentParameters::printLegendreParams(unsigned int sensorID) const -{ - LOGP(info, " - Legendre Pol X:"); - getLegendreCoeffX(sensorID).Print(); - LOGP(info, " - Legendre Pol Y:"); - getLegendreCoeffY(sensorID).Print(); - LOGP(info, " - Legendre Pol Z:"); - getLegendreCoeffZ(sensorID).Print(); -} - -} // namespace o2::its3::align diff --git a/Detectors/Upgrades/ITS3/alignment/src/MisalignmentUtils.cxx b/Detectors/Upgrades/ITS3/alignment/src/MisalignmentUtils.cxx new file mode 100644 index 0000000000000..ee5198ee98e0c --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/src/MisalignmentUtils.cxx @@ -0,0 +1,151 @@ +// Copyright 2019-2026 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. + +#include "ITS3Align/MisalignmentUtils.h" + +#include +#include +#include +#include + +#include +#include + +#include "Framework/Logger.h" +#include "ITS3Base/SpecsV2.h" + +namespace o2::its3::align +{ + +bool MisalignmentModel::empty() const noexcept +{ + return std::all_of(sensors.begin(), sensors.end(), [](const auto& sensor) { return sensor.empty(); }); +} + +MisalignmentModel loadMisalignmentModel(const std::string& jsonPath) +{ + MisalignmentModel model; + if (jsonPath.empty()) { + return model; + } + + std::ifstream f(jsonPath); + if (!f.is_open()) { + LOGP(fatal, "Cannot open misalignment JSON file: {}", jsonPath); + } + + using json = nlohmann::json; + const auto data = json::parse(f); + for (const auto& item : data) { + const int id = item["id"].get(); + if (id < 0 || id >= static_cast(MisalignmentModel::NSensors)) { + LOGP(fatal, "Misalignment sensor id {} out of range [0, {}) in {}", id, MisalignmentModel::NSensors, jsonPath); + } + + auto& sensor = model[id]; + if (item.contains("matrix")) { + auto v = item["matrix"].get>>(); + if (v.empty()) { + LOGP(fatal, "Legendre matrix for sensor {} is empty in {}", id, jsonPath); + } + TMatrixD m(v.size(), v.back().size()); + for (std::size_t r{0}; r < v.size(); ++r) { + for (std::size_t c{0}; c < v[r].size(); ++c) { + m(r, c) = v[r][c]; + } + } + sensor.legendre = o2::math_utils::Legendre2DPolynominal(m); + sensor.hasLegendre = true; + } + if (item.contains("inextensional")) { + const auto& inex = item["inextensional"]; + sensor.hasInextensional = true; + if (inex.contains("modes")) { + for (const auto& [key, val] : inex["modes"].items()) { + sensor.inextensional.modes[std::stoi(key)] = val.get>(); + } + } + if (inex.contains("alpha")) { + sensor.inextensional.alpha = inex["alpha"].get(); + } + if (inex.contains("beta")) { + sensor.inextensional.beta = inex["beta"].get(); + } + } + } + + return model; +} + +MisalignmentShift evaluateLegendreShift(const SensorMisalignment& sensor, const MisalignmentFrame& frame, const TrackSlopes& slopes) +{ + MisalignmentShift shift; + if (!sensor.hasLegendre) { + return shift; + } + + const double gloX = frame.x * std::cos(frame.alpha); + const double gloY = frame.x * std::sin(frame.alpha); + const double gloZ = frame.z; + auto [u, v] = computeUV(gloX, gloY, gloZ, frame.sensorID, constants::radii[frame.layerID]); + const double h = sensor.legendre(u, v); + + shift.dy = slopes.dydx * h; + shift.dz = slopes.dzdx * h; + + const double newGloY = gloY + (shift.dy * std::cos(frame.alpha)); + const double newGloX = gloX - (shift.dy * std::sin(frame.alpha)); + const double newGloZ = gloZ + shift.dz; + auto [uNew, vNew] = computeUV(newGloX, newGloY, newGloZ, frame.sensorID, constants::radii[frame.layerID]); + shift.accepted = std::abs(uNew) <= 1. && std::abs(vNew) <= 1.; + return shift; +} + +MisalignmentShift evaluateInextensionalShift(const SensorMisalignment& sensor, const MisalignmentFrame& frame, const TrackSlopes& slopes) +{ + MisalignmentShift shift; + if (!sensor.hasInextensional) { + return shift; + } + + const double r = constants::radii[frame.layerID]; + const double phi = std::atan2(r * std::sin(frame.alpha), r * std::cos(frame.alpha)); + const double z = frame.z; + const auto& inex = sensor.inextensional; + + double uz = 0., uphi = 0., ur = 0.; + for (const auto& [n, coeffs] : inex.modes) { + const double a_n = coeffs[0], b_n = coeffs[1], c_n = coeffs[2], d_n = coeffs[3]; + const double sn = std::sin(n * phi); + const double cn = std::cos(n * phi); + const int n2 = n * n; + + const double fn = (a_n * cn) + (b_n * sn); + const double fpn = (-n * a_n * sn) + (n * b_n * cn); + const double fppn = (-n2 * a_n * cn) - (n2 * b_n * sn); + const double gn = (c_n * cn) + (d_n * sn); + const double gpn = (-n * c_n * sn) + (n * d_n * cn); + + uz += fn; + uphi += -(z / r) * fpn + gn; + ur += (z / r) * fppn - gpn; + } + + uz += inex.alpha * phi; + uphi += -(z / r) * inex.alpha + inex.beta * phi; + ur += -inex.beta; + + shift.dy = -uphi + (slopes.dydx * ur); + shift.dz = -uz + (slopes.dzdx * ur); + return shift; +} + +} // namespace o2::its3::align diff --git a/Detectors/Upgrades/ITS3/alignment/src/alignment-workflow.cxx b/Detectors/Upgrades/ITS3/alignment/src/alignment-workflow.cxx new file mode 100644 index 0000000000000..6ec7615885556 --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/src/alignment-workflow.cxx @@ -0,0 +1,71 @@ +// 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. + +#include "CommonUtils/ConfigurableParam.h" +#include "Framework/ConfigParamSpec.h" +#include "Framework/CallbacksPolicy.h" +#include "GlobalTrackingWorkflowHelpers/InputHelper.h" +#include "GlobalTrackingWorkflowHelpers/NoInpDummyOutSpec.h" +#include "DetectorsRaw/HBFUtilsInitializer.h" +#include "ITS3Align/AlignmentSpec.h" + +using namespace o2::framework; +using namespace o2::its3::align; +using GID = o2::dataformats::GlobalTrackID; +using DetID = o2::detectors::DetID; + +void customize(std::vector& policies) +{ + o2::raw::HBFUtilsInitializer::addNewTimeSliceCallback(policies); +} +void customize(std::vector& workflowOptions) +{ + std::vector options{ + {"disable-mc", o2::framework::VariantType::Bool, false, {"enable MC propagation"}}, + {"track-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of track sources to use"}}, + {"cluster-sources", VariantType::String, "ITS", {"comma-separated list of cluster sources to use"}}, + {"with-its", VariantType::Bool, false, {"ITS alignment mode"}}, + {"without-pv", VariantType::Bool, false, {"Do not use in track refit the PV as an additional constraint"}}, + {"output", VariantType::String, "", {"output steering"}}, + {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; + o2::raw::HBFUtilsInitializer::addConfigOption(options); + std::swap(workflowOptions, options); +} +#include "Framework/runDataProcessing.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& cfg) +{ + o2::conf::ConfigurableParam::updateFromString(cfg.options().get("configKeyValues")); + const GID::mask_t allowedSourcesTrc = GID::getSourcesMask("ITS,TPC,ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); + const GID::mask_t allowedSourcesClus = GID::getSourcesMask("ITS"); + const GID::mask_t srcTrc = allowedSourcesTrc & GID::getSourcesMask(cfg.options().get("track-sources")); + const GID::mask_t srcCls = allowedSourcesClus & GID::getSourcesMask(cfg.options().get("cluster-sources")); + 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 OutputEnum output(cfg.options().get("output")); + + WorkflowSpec specs; + if (!output[OutputOpt::MilleRes]) { + o2::globaltracking::InputHelper::addInputSpecs(cfg, specs, srcCls, srcTrc, srcTrc, useMC); + if (withPV && !useMC) { + o2::globaltracking::InputHelper::addInputSpecsPVertex(cfg, specs, useMC); + } + } else { + specs.emplace_back(o2::globaltracking::getNoInpDummyOutSpec(0)); + } + + specs.emplace_back(o2::its3::align::getAlignmentSpec(srcTrc, srcCls, useMC, withPV, withITS, output)); + + o2::raw::HBFUtilsInitializer hbfIni(cfg, specs); + return std::move(specs); +} diff --git a/Detectors/Upgrades/ITS3/base/include/ITS3Base/ITS3Params.h b/Detectors/Upgrades/ITS3/base/include/ITS3Base/ITS3Params.h index 0bd548cef953d..1e3f4f47b8a29 100644 --- a/Detectors/Upgrades/ITS3/base/include/ITS3Base/ITS3Params.h +++ b/Detectors/Upgrades/ITS3/base/include/ITS3Base/ITS3Params.h @@ -19,11 +19,6 @@ namespace o2::its3 { struct ITS3Params : public o2::conf::ConfigurableParamHelper { - // Alignment studies - bool applyMisalignmentHits{false}; // Apply detector misalignment on hit level - std::string misalignmentHitsParams{}; // Path to parameter file for mis-alignment - bool misalignmentHitsUseProp{false}; // Use propagtor for mis-alignment - std::string globalGeoMisAlignerMacro{"${O2_ROOT}/share/macro/MisAlignGeoITS3.C"}; // Path to macro for global geometry mis-alignment // Chip studies bool useDeadChannelMap{false}; // Query for a dead channel map to study disabling individual tiles std::string chipResponseFunction{"APTS"}; // Chip response function one of "Alpide", "APTS" or "Mosaix" (not yet available) diff --git a/Detectors/Upgrades/ITS3/base/include/ITS3Base/SegmentationMosaix.h b/Detectors/Upgrades/ITS3/base/include/ITS3Base/SegmentationMosaix.h index fbf9a59e6da4b..088dd858fff73 100644 --- a/Detectors/Upgrades/ITS3/base/include/ITS3Base/SegmentationMosaix.h +++ b/Detectors/Upgrades/ITS3/base/include/ITS3Base/SegmentationMosaix.h @@ -151,7 +151,8 @@ class SegmentationMosaix } // Same as localToDetector w.o. checks. - constexpr void localToDetectorUnchecked(float const xRow, float const zCol, int& iRow, int& iCol) const noexcept + template + constexpr void localToDetectorUnchecked(T const xRow, T const zCol, int& iRow, int& iCol) const noexcept { iRow = static_cast(std::floor((WidthH - xRow) / PitchRow)); iCol = static_cast(std::floor((zCol + LengthH) / PitchCol)); @@ -167,7 +168,8 @@ class SegmentationMosaix /// center of the sensitive volume. /// If iRow and or iCol is outside of the segmentation range a value of -0.5*Dx() /// or -0.5*Dz() is returned. - bool detectorToLocal(float const row, float const col, float& xRow, float& zCol) const noexcept + template + bool detectorToLocal(T const row, T const col, L& xRow, L& zCol) const noexcept { if (!isValidDet(row, col)) { return false; @@ -178,15 +180,17 @@ class SegmentationMosaix // Same as detectorToLocal w.o. checks. // We position ourself in the middle of the pixel. - void detectorToLocalUnchecked(float const row, float const col, float& xRow, float& zCol) const noexcept + template + void detectorToLocalUnchecked(T const row, T const col, L& xRow, L& zCol) const noexcept { xRow = -(row + 0.5f) * PitchRow + WidthH; zCol = (col + 0.5f) * PitchCol - LengthH; } - bool detectorToLocal(float const row, float const col, math_utils::Point3D& loc) const noexcept + template + bool detectorToLocal(T const row, T const col, math_utils::Point3D& loc) const noexcept { - float xRow{0.}, zCol{0.}; + L xRow{0.}, zCol{0.}; if (!detectorToLocal(row, col, xRow, zCol)) { return false; } @@ -194,9 +198,10 @@ class SegmentationMosaix return true; } - void detectorToLocalUnchecked(float const row, float const col, math_utils::Point3D& loc) const noexcept + template + void detectorToLocalUnchecked(T const row, T const col, math_utils::Point3D& loc) const noexcept { - float xRow{0.}, zCol{0.}; + L xRow{0.}, zCol{0.}; detectorToLocalUnchecked(row, col, xRow, zCol); loc.SetCoordinates(xRow, 0.0f, zCol); } diff --git a/Detectors/Upgrades/ITS3/base/include/ITS3Base/SpecsV2.h b/Detectors/Upgrades/ITS3/base/include/ITS3Base/SpecsV2.h index 937fa8d2e982c..270b1a7148f61 100644 --- a/Detectors/Upgrades/ITS3/base/include/ITS3Base/SpecsV2.h +++ b/Detectors/Upgrades/ITS3/base/include/ITS3Base/SpecsV2.h @@ -209,7 +209,7 @@ inline T getSensorID(T detID) template inline bool isDetITS3(T detID) { - return detID < static_cast(nChips); + return detID < static_cast(nChips) && detID >= 0; } } // namespace detID diff --git a/Detectors/Upgrades/ITS3/macros/align/CMakeLists.txt b/Detectors/Upgrades/ITS3/macros/align/CMakeLists.txt index 86ebd989133e0..1d5c18cc0f4f3 100644 --- a/Detectors/Upgrades/ITS3/macros/align/CMakeLists.txt +++ b/Detectors/Upgrades/ITS3/macros/align/CMakeLists.txt @@ -9,9 +9,5 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. -# install(FILES MisAlignGeoITS3.C DESTINATION share/macro/) -# its3_add_macro(MisAlignGeoITS3.C) -its3_add_macro(TestLegendrePol.C) -its3_add_macro(CreateMisalignmentITS3.C) -its3_add_macro(ShowCoefficients.C) its3_add_macro(CheckResidualsITS3.C) +its3_add_macro(CheckHitResiduals.C) diff --git a/Detectors/Upgrades/ITS3/macros/align/CheckHitResiduals.C b/Detectors/Upgrades/ITS3/macros/align/CheckHitResiduals.C new file mode 100644 index 0000000000000..555ba177a60ce --- /dev/null +++ b/Detectors/Upgrades/ITS3/macros/align/CheckHitResiduals.C @@ -0,0 +1,131 @@ +// 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. +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include + +#include "MathUtils/Utils.h" +#include "ITSMFTSimulation/Hit.h" +#include "ITS3Base/SpecsV2.h" +#endif + +void CheckHitResiduals(const std::string& hitOFile = "o2sim_HitsIT3_Orig.root", + const std::string& hitRFile = "o2sim_HitsIT3.root") +{ + gStyle->SetOptStat(0); + + TFile fileO(hitOFile.data()); + auto* hitOTree = dynamic_cast(fileO.Get("o2sim")); + std::vector* hitOArray = nullptr; + hitOTree->SetBranchAddress("IT3Hit", &hitOArray); + + TFile fileR(hitRFile.data()); + auto* hitRTree = dynamic_cast(fileR.Get("o2sim")); + std::vector* hitRArray = nullptr; + hitRTree->SetBranchAddress("IT3Hit", &hitRArray); + + struct Hits { + o2::itsmft::Hit oHit, rHit; + int oEve{-1}, rEve{-1}; + bool hasBoth() const noexcept { return oEve >= 0 && rEve >= 0 && oEve == rEve && oHit.GetDetectorID() == rHit.GetDetectorID() && rHit.GetTrackID() == oHit.GetTrackID(); } + }; + std::unordered_map hits; + + size_t total{0}; + for (int iEntry{0}; iEntry < hitOTree->GetEntries(); ++iEntry) { + hitOTree->GetEntry(iEntry); + total += hitOArray->size(); + for (const auto& h : *hitOArray) { + if (!o2::its3::constants::detID::isDetITS3(h.GetDetectorID())) { + continue; + } + uint64_t key = (uint64_t(iEntry) << 48) | (uint64_t(h.GetTrackID()) << 24) | uint64_t(h.GetDetectorID()); + auto& hh = hits[key]; + hh.oHit = h; + hh.oEve = iEntry; + } + } + printf("placed %zu hits saw %zu\n", hits.size(), total); + for (int iEntry{0}; iEntry < hitRTree->GetEntries(); ++iEntry) { + hitRTree->GetEntry(iEntry); + for (const auto& h : *hitRArray) { + if (!o2::its3::constants::detID::isDetITS3(h.GetDetectorID())) { + continue; + } + uint64_t key = (uint64_t(iEntry) << 48) | (uint64_t(h.GetTrackID()) << 24) | uint64_t(h.GetDetectorID()); + auto& hh = hits[key]; + hh.rHit = h; + hh.rEve = iEntry; + } + } + + // plot the residuals in dRPhi, dZ against ideal phi,z for each layer + std::array mDRPhi{}, mDZ{}; + for (int i{0}; i < 3; ++i) { + mDRPhi[i] = new TProfile2D(Form("hDRPhi_%d", i), Form("#Delta_{r#varphi};z (cm); r#varphi (cm)"), 100, -15, 15, 100, 0, 2 * TMath::Pi()); + mDRPhi[i]->SetDirectory(nullptr); + mDZ[i] = new TProfile2D(Form("hDZ_%d", i), Form("#Delta_{z};z (cm); r#varphi (cm)"), 100, -15, 15, 100, 0, 2 * TMath::Pi()); + mDZ[i]->SetDirectory(nullptr); + } + std::array cIB{}; + for (const auto& [_, h] : hits) { + if (h.hasBoth()) { + int chip = o2::its3::constants::detID::getSensorID(h.oHit.GetDetectorID()) / 2; + ++cIB[chip]; + auto gloO = h.oHit.GetPosStart(); + auto gloR = h.rHit.GetPosStart(); + // ideal (original) cluster: phi, z, rphi + float phiO = std::atan2(gloO.Y(), gloO.X()); + o2::math_utils::bringTo02Pi(phiO); + const float rO = std::hypot(gloO.X(), gloO.Y()); + + // deformed (reconstructed) cluster + float phiR = std::atan2(gloR.Y(), gloR.X()); + o2::math_utils::bringTo02Pi(phiR); + const float rR = std::hypot(gloR.X(), gloR.Y()); + + // residuals + const float dRPhi = rO * (phiR - phiO); // or use average r, doesn't matter at this precision + const float dZ = gloR.Z() - gloO.Z(); + + // fill vs (z, phi) of ideal position + mDRPhi[chip]->Fill(gloO.Z(), phiO, dRPhi * 1e4); + mDZ[chip]->Fill(gloO.Z(), phiO, dZ * 1e4); + } + } + for (int lay{0}; lay < 3; ++lay) { + printf("\t%d has %d\n", lay, cIB[lay]); + } + auto c1 = new TCanvas; + c1->Divide(3, 1); + for (int i{0}; i < 3; ++i) { + c1->cd(1 + i); + gPad->SetRightMargin(3); + mDRPhi[i]->GetZaxis()->SetRangeUser(-200, 200); + mDRPhi[i]->Draw("colz"); + } + c1->Draw(); + c1->SaveAs("its3_clus_res_rphi.pdf"); + auto c2 = new TCanvas; + c2->Divide(3, 1); + for (int i{0}; i < 3; ++i) { + c2->cd(1 + i); + gPad->SetRightMargin(3); + mDZ[i]->GetZaxis()->SetRangeUser(-200, 200); + mDZ[i]->Draw("colz"); + } + c2->Draw(); + c2->SaveAs("its3_clus_res_z.pdf"); +} diff --git a/Detectors/Upgrades/ITS3/macros/align/CreateMisalignmentITS3.C b/Detectors/Upgrades/ITS3/macros/align/CreateMisalignmentITS3.C deleted file mode 100644 index 8df00ee25de00..0000000000000 --- a/Detectors/Upgrades/ITS3/macros/align/CreateMisalignmentITS3.C +++ /dev/null @@ -1,94 +0,0 @@ -// 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. - -#if !defined(__CLING__) || defined(__ROOTCLING__) -#include "TRandom.h" -#include "TMatrixD.h" - -#include "ITS3Align/MisalignmentParameters.h" -#endif - -void CreateMisalignmentITS3(bool dummy = false, bool manual = false) -{ - gRandom->SetSeed(42); - - // Legendre coeff. - constexpr int nOrder{2}; - auto getRandom = []() { - constexpr double scale{50.e-4}; - return scale * gRandom->Uniform(-1.0, 1.0); - }; - - auto getSign = []() { return gRandom->Uniform() ? -1.0 : 1.0; }; - - o2::its3::align::MisalignmentParameters params; - - if (dummy) { - TMatrixD coeffNull(0 + 1, 0 + 1); - for (int sensorID{0}; sensorID < 6; ++sensorID) { - params.setLegendreCoeffX(sensorID, coeffNull); - params.setLegendreCoeffY(sensorID, coeffNull); - params.setLegendreCoeffZ(sensorID, coeffNull); - } - } else if (manual) { - // (0,0) -> shift - // (1,0) -> - for (int sensorID{0}; sensorID < 6; ++sensorID) { - constexpr double scale{20e-4}; - TMatrixD coeffNull(1, 1); - - TMatrixD coeffMinusX(1 + 1, 1 + 1); - TMatrixD coeffPlusX(1 + 1, 1 + 1); - coeffMinusX(1, 1) = -scale; - coeffPlusX(1, 1) = scale; - - TMatrixD coeffMinusY(4 + 1, 4 + 1); - TMatrixD coeffPlusY(4 + 1, 4 + 1); - coeffMinusY(0, 0) = scale; - coeffPlusY(0, 0) = -scale; - coeffMinusY(4, 4) = -scale; - coeffPlusY(4, 4) = scale; - if (sensorID % 2 == 0) { - params.setLegendreCoeffX(sensorID, coeffPlusX); - params.setLegendreCoeffY(sensorID, coeffPlusY); - params.setLegendreCoeffZ(sensorID, coeffNull); - } else { - params.setLegendreCoeffX(sensorID, coeffMinusX); - params.setLegendreCoeffY(sensorID, coeffMinusY); - params.setLegendreCoeffZ(sensorID, coeffNull); - } - } - } else { - for (int sensorID{0}; sensorID < 6; ++sensorID) { - TMatrixD coeffX(nOrder + 1, nOrder + 1); - TMatrixD coeffY(nOrder + 1, nOrder + 1); - TMatrixD coeffZ(nOrder + 1, nOrder + 1); - for (int i{0}; i <= nOrder; ++i) { - for (int j{0}; j <= i; ++j) { - // some random scaling as higher order parameters have higher influence - coeffX(i, j) = getRandom() / (1.0 + i * j * 2.0); - coeffZ(i, j) = getRandom() / (1.0 + i * j * 2.0); - coeffY(i, j) = getRandom() / (1.0 + i * j * 2.0); - } - } - - params.setLegendreCoeffX(sensorID, coeffX); - params.setLegendreCoeffY(sensorID, coeffY); - params.setLegendreCoeffZ(sensorID, coeffZ); - } - } - - for (int sensorID{0}; sensorID < 6; ++sensorID) { - params.printLegendreParams(sensorID); - } - - params.store("misparams.root"); -} diff --git a/Detectors/Upgrades/ITS3/macros/align/MisAlignGeoITS3.notest b/Detectors/Upgrades/ITS3/macros/align/MisAlignGeoITS3.notest deleted file mode 100644 index 2a4bef978d0da..0000000000000 --- a/Detectors/Upgrades/ITS3/macros/align/MisAlignGeoITS3.notest +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2020-2022 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 MisAlignGeoITS3.C -/// \brief Misalign the global geometry of ITS3 -/// \author felix.schlepper@cern.ch - -#include "ITS3Align/MisalignmentManager.h" - -#include "TGeoManager.h" -#include "TGeoMatrix.h" -#include "TMath.h" - -#include "CommonUtils/ConfigurableParam.h" -#include "CommonUtils/ConfigurableParamHelper.h" -#include "Framework/Logger.h" -#include "ITS3Base/SpecsV2.h" - -#include - -#include "boost/property_tree/ptree.hpp" - -using MM = o2::its3::align::MisalignmentManager; -namespace fs = std::filesystem; -namespace pt = boost::property_tree; -namespace its3c = o2::its3::constants; - -#define DECLARE_SENSOR(id) \ - float Sensor##id##Dx = 0.f; \ - float Sensor##id##Dy = 0.f; \ - float Sensor##id##Dz = 0.f; \ - float Sensor##id##Phi = 0.f; \ - float Sensor##id##Theta = 0.f; \ - float Sensor##id##Psi = 0.f; - -struct MisAlignGlobalParams : public o2::conf::ConfigurableParamHelper { - DECLARE_SENSOR(0) - DECLARE_SENSOR(1) - DECLARE_SENSOR(2) - DECLARE_SENSOR(3) - DECLARE_SENSOR(4) - DECLARE_SENSOR(5) - - O2ParamDef(MisAlignGlobalParams, "MisAlignGlobalParams"); -}; -O2ParamImpl(MisAlignGlobalParams); - -void MisAlignGeoITS3(const std::string& configFilePath = "", bool _export = false, bool draw = false, bool check = false, const std::string& geomFile = "o2sim_geometry-aligned.root") -{ - LOGP(info, "{:*^90}", " ITS3 GLOBAL MISALIGNMENT START "); - auto& params = MisAlignGlobalParams::Instance(); - params.writeINI("default_parameters_global.ini", "MisAlignGlobalParams"); - if (configFilePath.empty()) { - LOGP(info, "No user config provided using defaults"); - } else { - LOGP(info, "User config at {}", configFilePath); - params.updateFromFile(configFilePath); - } - params.writeINI("used_parameters_global.ini", "MisAlignGlobalParams"); - params.printKeyValues(true, true); - - const fs::path srcFile{geomFile}; - const fs::path destFile{MM::appendStem(geomFile, "_Orig")}; - if (gGeoManager == nullptr) { - MM::createBackup(srcFile, destFile); - TGeoManager::Import(destFile.c_str()); - } - - LOGP(info, "Building matrices"); - std::array gRotoTranslations{}; - for (int iSensor{0}; iSensor < (int)its3c::nSensorsIB; ++iSensor) { - auto& mat = gRotoTranslations[iSensor]; - // Phi Z rotation angle (first) defined in [-PI,PI] - // Theta X rotation angle (second) defined only [0,PI] - // Psi Z rotation angle (third) defined in [-PI,PI] - MM::Euler3D euler{ - ((iSensor % 2 == 0) ? 0. : -TMath::Pi()) + - TMath::DegToRad() * params.getValueAs(fmt::format("MisAlignGlobalParams.Sensor{}Phi", iSensor)), - TMath::DegToRad() * params.getValueAs(fmt::format("MisAlignGlobalParams.Sensor{}Theta", iSensor)), - TMath::DegToRad() * params.getValueAs(fmt::format("MisAlignGlobalParams.Sensor{}Psi", iSensor)), - }; - MM::Rot3D rot(euler); - std::array rota; - rot.GetComponents(std::begin(rota)); - mat.SetRotation(rota.data()); - std::array trans{ - params.getValueAs(fmt::format("MisAlignGlobalParams.Sensor{}Dx", iSensor)), - params.getValueAs(fmt::format("MisAlignGlobalParams.Sensor{}Dy", iSensor)), - params.getValueAs(fmt::format("MisAlignGlobalParams.Sensor{}Dz", iSensor)), - }; - mat.SetTranslation(trans.data()); - } - - LOGP(info, "Appying Global RotoTranslations"); - for (int iSensor{0}; iSensor < (int)its3c::nSensorsIB; ++iSensor) { - auto path = MM::composePathSensor(iSensor); - auto& mat = gRotoTranslations[iSensor]; - MM::applyGlobalMatrixVolume(path, mat); - } - - if (_export) { - gGeoManager->Export(srcFile.c_str()); - } - if (draw) { - MM::navigate("cave/barrel_1/ITSV_2/ITSUWrapVol0_1"); - gGeoManager->GetCurrentVolume()->Draw(); - gGeoManager->SetTopVisible(); - gGeoManager->RestoreMasterVolume(); - } - if (check) { - gGeoManager->CdTop(); - gGeoManager->CloseGeometry(); - gGeoManager->CheckGeometryFull(); - gGeoManager->CheckOverlaps(0.1, "s"); - gGeoManager->PrintOverlaps(); - auto overlaps = gGeoManager->GetListOfOverlaps(); - overlaps->At(0)->Print(); - overlaps->At(0)->Draw("ogl"); - } - LOGP(info, "{:*^90}", " ITS3 GLOBAL MISALIGNMENT END "); -} diff --git a/Detectors/Upgrades/ITS3/macros/align/ShowCoefficients.C b/Detectors/Upgrades/ITS3/macros/align/ShowCoefficients.C deleted file mode 100644 index 42749b707e81d..0000000000000 --- a/Detectors/Upgrades/ITS3/macros/align/ShowCoefficients.C +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2020-2022 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. - -#if !defined(__CLING__) || defined(__ROOTCLING__) -#include "Math/Factory.h" -#include "Math/Minimizer.h" -#include "TAxis.h" -#include "TCanvas.h" -#include "TGraph.h" -#include "TGraph2D.h" -#include "TLegend.h" -#include "TMarker.h" -#include "TMultiGraph.h" -#include "TRandom.h" -#include "TMath.h" - -#include "MathUtils/LegendrePols.h" -#include "ITS3Align/Deformations.h" -#include "MathUtils/Utils.h" -#endif - -static ROOT::Math::Minimizer* gMin; - -void ShowCoefficients(const std::string& fileName = "misparams.root", bool findMin = false) -{ - constexpr double factor{10}; - o2::its3::align::Deformations def; - def.init(fileName); - - if (findMin) { - gMin = ROOT::Math::Factory::CreateMinimizer("Minuit2", "Migrad"); - if (gMin == nullptr) { - Error("", "Cannot create minimizer !"); - return; - } - gMin->SetMaxFunctionCalls(1000000); // for Minuit/Minuit2 - gMin->SetTolerance(0.00001); - gMin->SetPrintLevel(1); - } - - if constexpr (1) { - constexpr int nPoints{1000}; - const std::array zFix{-12., -8.67, -4.33, 0., 4.33, 8.67, 12.}; - const std::array phiFix{1. / 4. * TMath::Pi(), 0.5 * TMath::Pi(), 3. / 4. * TMath::Pi(), TMath::Pi(), 5. / 4. * TMath::Pi(), 6. / 4. * TMath::Pi(), 7. / 4. * TMath::Pi()}; - const std::array phiFixName{"#frac{1}{4}", "#frac{1}{2}", "#frac{3}{4}", "1", "#frac{5}{4}", "#frac{6}{4}", "#frac{7}{4}"}; - const std::array phiFixTop{true, true, true, false, false, false, false}; - const std::array, 2> sensorN{{{0, 2, 4}, {1, 3, 5}}}; - constexpr double z1{-o2::its3::constants::segment::lengthSensitive / 2.0}, z2{o2::its3::constants::segment::lengthSensitive / 2.0}, zTot{z2 - z1}, zStep{zTot / (nPoints - 1)}; - auto canv = new TCanvas(); - canv->Divide(7, 2); - for (int i{0}; i < 7; ++i) { - std::array xi, yi, xd, yd; - for (int s{0}; s < 6; ++s) { - const bool isTop = s % 2 == 0; - const double radius = o2::its3::constants::radii[s / 2]; - const double nzFix = (zFix[i] - z1) * 2.0 / zTot - 1.0; - const double phi1 = o2::math_utils::to02Pi(((isTop) ? 0.f : 1.f) * TMath::Pi() + std::asin(o2::its3::constants::equatorialGap / 2.f / radius)); - const double phi2 = o2::math_utils::to02Pi(((isTop) ? 1.f : 2.f) * TMath::Pi() - std::asin(o2::its3::constants::equatorialGap / 2.f / radius)); - const double phiTot{phi2 - phi1}, phiStep{phiTot / (nPoints - 1)}; - const double nphiFix = ((phiFix[i]) - phi1) * 2.0 / phiTot - 1.0; - - for (int j{0}; j < nPoints; ++j) { - const int idx = s * nPoints + j; - const double phi = phi1 + j * phiStep; - const double nphi = (phi - phi1) * 2.0 / phiTot - 1.0; - - xi[idx] = radius * std::cos(phi); - yi[idx] = radius * std::sin(phi); - const auto [dxXY, dyXY, _] = def.getDeformation(s, nphi, nzFix); - xd[idx] = xi[idx] + dxXY * factor; - yd[idx] = yi[idx] + dyXY * factor; - } - } - canv->cd(i + 1); - auto gixy = new TGraph(nPoints * 6, xi.data(), yi.data()); - gixy->SetNameTitle(Form("g_i_xy_%d", i), Form("Ideal xy at z=%.2f; x (cm); y (cm)", zFix[i])); - gixy->SetMarkerColor(kBlue); - gixy->Draw("AP"); - auto gdxy = new TGraph(nPoints * 6, xd.data(), yd.data()); - gdxy->SetNameTitle(Form("g_d_xy_%d", i), Form("Deformed (x%.0f) xy at z=%.2f; x (cm); y (cm)", factor, zFix[i])); - gdxy->SetMarkerColor(kRed); - gdxy->Draw("P;same"); - - if (i == 3) { - continue; - } - - std::array zi, ri, zd, rd; - const bool isTop = phiFixTop[i]; - for (const int s : ((isTop) ? sensorN[0] : sensorN[1])) { - const double radius = o2::its3::constants::radii[s / 2]; - const double phi1 = o2::math_utils::to02Pi(((isTop) ? 0.f : 1.f) * TMath::Pi() + std::asin(o2::its3::constants::equatorialGap / 2.f / radius)); - const double phi2 = o2::math_utils::to02Pi(((isTop) ? 1.f : 2.f) * TMath::Pi() - std::asin(o2::its3::constants::equatorialGap / 2.f / radius)); - const double phiTot{phi2 - phi1}, phiStep{phiTot / (nPoints - 1)}; - const double nphiFix = ((phiFix[i]) - phi1) * 2.0 / phiTot - 1.0; - for (int j{0}; j < nPoints; ++j) { - const int idx = (s / 2) * nPoints + j; - const double z = z1 + j * zStep; - const double nz = (z - z1) * 2.0 / zTot - 1.0; - const double xi = radius * std::cos(phiFix[i]), yi = radius * std::sin(phiFix[i]); - ri[idx] = radius; - zi[idx] = z; - const auto [dxZR, dyZR, dzZR] = def.getDeformation(s, nphiFix, nz); - zd[idx] = z + dzZR * factor; - const double xxd = xi + dxZR * factor, yyd = yi + dyZR * factor; - rd[idx] = std::sqrt(xxd * xxd + yyd * yyd); - } - } - canv->cd(i + 8); - auto gizr = new TGraph(nPoints * 3, zi.data(), ri.data()); - gizr->SetNameTitle(Form("g_i_zr_%d", i), Form("Ideal zr at #varphi=%s #Pi; z (cm); r (cm)", phiFixName[i])); - gizr->SetMarkerColor(kBlue); - gizr->Draw("AP"); - auto gdzr = new TGraph(nPoints * 3, zd.data(), rd.data()); - gdzr->SetNameTitle(Form("g_d_zr_%d", i), Form("Deformed (x%0.f) zr at #varphi=%s #Pi; z (cm); r (cm)", factor, phiFixName[i])); - gdzr->SetMarkerColor(kRed); - gdzr->Draw("P;same"); - } - canv->Draw(); - canv->SaveAs("its3_deformation.pdf"); - } - - if constexpr (1) { - const std::array axisName{"x", "y", "z"}; - constexpr int nPoints{100}; - constexpr int nPoints2{nPoints * nPoints}; - constexpr double minX{-1.0}, maxX{1.0}, - stepX{(maxX - minX) / static_cast(nPoints)}; - - for (unsigned int iSensor{0}; iSensor < 6; ++iSensor) { - const auto nOrders = def.getOrders(iSensor); - for (unsigned int iAxis{0}; iAxis < 3; ++iAxis) { - std::array x, y, z; - auto canv = new TCanvas(Form("c_sensor%d_d%s", iSensor, axisName[iAxis]), Form("Legendre Coefficients Sensor %d - Axis %s", iSensor, axisName[iAxis])); - canv->Divide(nOrders[iAxis] + 1, nOrders[iAxis] + 1); - - { // Draw total polynominal - for (int iPoint{0}; iPoint < nPoints; ++iPoint) { - double xcur = minX + iPoint * stepX; - for (int jPoint{0}; jPoint < nPoints; ++jPoint) { - double ycur = minX + jPoint * stepX; - int i = iPoint * nPoints + jPoint; - x[i] = xcur; - y[i] = ycur; - z[i] = def.getDeformation(iSensor, iAxis, xcur, ycur); - } - } - canv->cd(nOrders[iAxis] + 1); - auto g = new TGraph2D(nPoints2, x.data(), y.data(), z.data()); - g->SetTitle(Form("Legendre Polynominal %d-deg Sensor %d #Delta%s;u;v;w", nOrders[iAxis], iSensor, axisName[iAxis])); - g->SetName(Form("g_%d_%s", iSensor, axisName[iAxis])); - g->Draw("surf1"); - g->GetXaxis()->SetRangeUser(minX, maxX); - g->GetYaxis()->SetRangeUser(minX, maxX); - gPad->Update(); - } - - { // Draw decomposition of contributions to polynominal - const auto& leg = def.getLegendre(iSensor, iAxis); - const auto coeff = leg.getCoefficients(); - for (unsigned int iOrder{0}; iOrder <= nOrders[iAxis]; ++iOrder) { - for (unsigned int jOrder{0}; jOrder <= iOrder; ++jOrder) { - for (int iPoint{0}; iPoint < nPoints; ++iPoint) { - double xcur = minX + iPoint * stepX; - for (int jPoint{0}; jPoint < nPoints; ++jPoint) { - double ycur = minX + jPoint * stepX; - int i = iPoint * nPoints + jPoint; - x[i] = xcur; - y[i] = ycur; - z[i] = leg(iOrder, jOrder, xcur, ycur); - } - } - canv->cd(1 + iOrder * (nOrders[iAxis] + 1) + jOrder); - auto g = new TGraph2D(nPoints2, x.data(), y.data(), z.data()); - g->SetTitle(Form("c_{%d,%d}=%.4f;u;v;w", iOrder, jOrder, coeff(iOrder, jOrder))); - g->SetName(Form("g_%d_%d_%d_%d", iSensor, iAxis, iOrder, jOrder)); - if (iOrder == 0 && jOrder == 0) { // Fix display of constant value - g->Draw("P0"); - } else { - g->Draw("surf1"); - } - g->GetXaxis()->SetRangeUser(minX, maxX); - g->GetYaxis()->SetRangeUser(minX, maxX); - gPad->Update(); - } - } - } - - canv->Draw(); - canv->SaveAs(Form("its3_sensor%d_%s.pdf", iSensor, axisName[iAxis])); - } - } - } - - if constexpr (1) { - constexpr int nPoints{50}; - constexpr int nPoints2{nPoints * nPoints}; - constexpr double radL = o2::its3::constants::radii[2] + 0.3, zL = o2::its3::constants::segment::lengthSensitive / 2.0 + 2.0; - - // Plot the whole geometry - std::array gIdeal; - std::array gDeformed; - constexpr double z1{-o2::its3::constants::segment::lengthSensitive / 2.0}, z2{o2::its3::constants::segment::lengthSensitive / 2.0}, zTot{z2 - z1}, zStep{zTot / (nPoints - 1)}; - auto canv = new TCanvas(); - canv->Divide(2, 1); - for (unsigned int iSensor{0}; iSensor < 6; ++iSensor) { - std::array xi, yi, zi, xd, yd, zd; - const double radius = o2::its3::constants::radii[iSensor / 2]; - const bool isTop = iSensor % 2 == 0; - const double phi1 = o2::math_utils::to02Pi(((isTop) ? 0.f : 1.f) * TMath::Pi() + std::asin(o2::its3::constants::equatorialGap / 2.f / radius)); - const double phi2 = o2::math_utils::to02Pi(((isTop) ? 1.f : 2.f) * TMath::Pi() - std::asin(o2::its3::constants::equatorialGap / 2.f / radius)); - const double phiTot{phi2 - phi1}, phiStep{phiTot / (nPoints - 1)}; - for (int iZ{0}; iZ < nPoints; ++iZ) { - double z = z1 + iZ * zStep; - for (int iPhi{0}; iPhi < nPoints; ++iPhi) { - int i = iZ * nPoints + iPhi; - double phi = phi1 + iPhi * phiStep; - - xi[i] = radius * std::cos(phi); - yi[i] = radius * std::sin(phi); - zi[i] = z; - - const double u = ((phi - phi1) * 2.f) / phiTot - 1.f; - const double v = ((z - z1)) / zTot - 1.f; - const auto [dx, dy, dz] = def.getDeformation(iSensor, u, v); - xd[i] = xi[i] + dx * factor; - yd[i] = yi[i] + dy * factor; - zd[i] = zi[i] + dz * factor; - } - } - - canv->cd(1); - auto ideal = new TGraph2D(Form("g_ideal_%d", iSensor), "Ideal Geometry", nPoints2, xi.data(), zi.data(), yi.data()); - ideal->SetMarkerStyle(kFullCircle); - ideal->SetMarkerSize(1); - ideal->SetMarkerColor(kBlue); - ideal->SetLineColor(kBlue); - if (iSensor == 0) { - ideal->Draw("p0"); - } else { - ideal->Draw("p0;same"); - if (iSensor == 5) { - gPad->Update(); - ideal->GetXaxis()->SetTitle("X"); - ideal->GetYaxis()->SetTitle("Z"); - ideal->GetZaxis()->SetTitle("Y"); - ideal->GetXaxis()->SetRangeUser(-radL, radL); - ideal->GetZaxis()->SetRangeUser(-radL, radL); - ideal->GetYaxis()->SetRangeUser(-zL, zL); - } - } - - canv->cd(2); - auto deformed = new TGraph2D(Form("g_def_%d", iSensor), "Deformed Geometry", nPoints2, xd.data(), zd.data(), yd.data()); - deformed->SetMarkerStyle(kFullCircle); - deformed->SetMarkerSize(1); - deformed->SetMarkerColor(kRed); - deformed->SetLineColor(kRed); - if (iSensor == 0) { - deformed->Draw("p0"); - } else { - deformed->Draw("p0;same"); - if (iSensor == 5) { - gPad->Update(); - deformed->GetXaxis()->SetTitle("X"); - deformed->GetYaxis()->SetTitle("Z"); - deformed->GetZaxis()->SetTitle("Y"); - deformed->GetXaxis()->SetRangeUser(-radL, radL); - deformed->GetZaxis()->SetRangeUser(-radL, radL); - deformed->GetYaxis()->SetRangeUser(-zL, zL); - } - } - } - - // Optionally find a deformation - /* if (findMin2D) { */ - /* std::vector cccc(nOrder + 2, 0.0); */ - /* cccc[0] = nOrder; */ - /* for (int i{0}; i <= nOrder; ++i) { */ - /* for (int j{0}; j <= i; ++j) { */ - /* int k = i * (i + 1) / 2 + j; */ - /* cccc[1 + k] = coeff(i, j); */ - /* } */ - /* } */ - /* auto ig = legendre_poly2D_integral(cccc.data()); */ - - /* gMin->Clear(); */ - /* ROOT::Math::Functor fmin(&legendre_poly2D_integral, */ - /* 2 + nOrder * (nOrder + 1) / 2 + nOrder); */ - /* Info("", "ig=%f parameters=%d", ig, */ - /* 2 + nOrder * (nOrder + 1) / 2 + nOrder); */ - /* gMin->SetFunction(fmin); */ - /* constexpr double minStep{0.001}; */ - /* gMin->SetFixedVariable(0, "nOrder", nOrder); */ - /* gMin->SetFixedVariable(1, "c_00", coeff(0, 0)); */ - /* for (int iOrder{1}; iOrder <= nOrder; ++iOrder) { */ - /* for (int jOrder{0}; jOrder <= iOrder; ++jOrder) { */ - /* int k = iOrder * (iOrder + 1) / 2 + jOrder + 1; */ - /* Info("", "Setting parameter %d", k); */ - /* if (getRandom() < 0.0) { */ - /* gMin->SetFixedVariable(k, Form("c_%d_%d", iOrder, jOrder), */ - /* coeff(iOrder, jOrder)); */ - /* } else { */ - /* gMin->SetVariable(k, Form("c_%d_%d", iOrder, jOrder), */ - /* coeff(iOrder, jOrder), minStep); */ - /* } */ - /* } */ - /* } */ - /* gMin->Minimize(); */ - /* return; */ - /* auto stat = gMin->Status(); */ - /* auto min = gMin->MinValue(); */ - /* if ((stat == 0 || stat == 1) && min < 0.01) { */ - /* Info("", "Minimizer converged with %f; using new values!", min); */ - /* const double *cmins = gMin->X(); */ - /* for (int iOrder{1}; iOrder <= nOrder; ++iOrder) { */ - /* for (int jOrder{0}; jOrder <= iOrder; ++jOrder) { */ - /* int k = iOrder * (iOrder + 1) / 2 + jOrder; */ - /* coeff(iOrder, jOrder) = cmins[k + 1]; */ - /* } */ - /* } */ - /* } */ - /* } */ - } -} diff --git a/Detectors/Upgrades/ITS3/macros/align/TestLegendrePol.C b/Detectors/Upgrades/ITS3/macros/align/TestLegendrePol.C deleted file mode 100644 index 720ab80264838..0000000000000 --- a/Detectors/Upgrades/ITS3/macros/align/TestLegendrePol.C +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright 2020-2022 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. - -#if !defined(__CLING__) || defined(__ROOTCLING__) -#include "Math/Factory.h" -#include "Math/Minimizer.h" -#include "TAxis.h" -#include "TCanvas.h" -#include "TGraph.h" -#include "TGraph2D.h" -#include "TLegend.h" -#include "TMarker.h" -#include "TMultiGraph.h" -#include "TRandom.h" - -#include "MathUtils/LegendrePols.h" -#endif - -static ROOT::Math::Minimizer* gMin; - -void TestLegendrePol(bool findMin1D = false, bool findMin2D = false) -{ - constexpr int nMaxOrder{2}; - constexpr int nPoints{100}; - constexpr int nPoints2{nPoints * nPoints}; - constexpr double minX{-1.0}, maxX{1.0}, - stepX{(maxX - minX) / static_cast(nPoints)}; - - gRandom->SetSeed(0); - auto getRandom = []() { - constexpr double scale{80.e-4}; - return scale * gRandom->Uniform(-1.0, 1.0); - }; - - if (findMin1D || findMin2D) { - gMin = ROOT::Math::Factory::CreateMinimizer("Minuit2", "Migrad"); - if (gMin == nullptr) { - Error("", "Cannot create minimizer !"); - return; - } - gMin->SetMaxFunctionCalls(1000000); // for Minuit/Minuit2 - gMin->SetTolerance(0.00001); - gMin->SetPrintLevel(1); - } - - { // 1D - Info("", "---------------- 1D -------------"); - std::array x, y; - for (int nOrder{0}; nOrder <= nMaxOrder; ++nOrder) { - std::vector coeff(nOrder + 1, 0.0); - std::generate(std::begin(coeff), std::end(coeff), getRandom); - - o2::math_utils::Legendre1DPolynominal leg1D(coeff); - - // Optionally find a deformation - if (findMin1D) { - std::vector cccc(nOrder + 2, 0.0); - cccc[0] = nOrder; - for (int iOrder{0}; iOrder <= nOrder; ++iOrder) { - cccc[1 + iOrder] = coeff[iOrder]; - } - - gMin->Clear(); - /* gMin->SetFunction(&leg1D.DoIntegralPar); */ - constexpr double minStep{0.001}; - gMin->SetFixedVariable(0, "c_0", coeff[0]); - for (int iOrder{1}; iOrder <= nOrder; ++iOrder) { - gMin->SetVariable(iOrder, Form("c_%d", iOrder), coeff[iOrder], - minStep); - } - gMin->Minimize(); - auto stat = gMin->Status(); - auto min = gMin->MinValue(); - if ((stat == 0 || stat == 1) && min < 0.01) { - Info("", "Minimizer converged with %f; using new values!", min); - const double* cmins = gMin->X(); - for (int i{0}; i <= nOrder; ++i) { - Info("", "New values are c_%d=%.4f -> %.4f", i, coeff[i], - cmins[1 + i]); - coeff[i] = cmins[1 + i]; - } - } - } - - auto c1d = new TCanvas(Form("c1D_%d", nOrder), - Form("Legendre 1D Order %d", nOrder)); - c1d->Divide(2, 1); - - { // Draw total polynominal - for (int iPoint{0}; iPoint < nPoints; ++iPoint) { - double xcur = minX + iPoint * stepX; - x[iPoint] = xcur; - y[iPoint] = leg1D(xcur); - } - c1d->cd(2); - auto g = new TGraph(nPoints, x.data(), y.data()); - g->SetName(Form("g1d_%d", nOrder)); - g->SetTitle(Form("Legendre Polynominal %d-deg;u;w", nOrder)); - g->Draw(); - } - - { // Draw single contributions - auto mg = new TMultiGraph(); - auto leg = new TLegend(); - for (int iOrder{0}; iOrder <= nOrder; ++iOrder) { - for (int iPoint{0}; iPoint < nPoints; ++iPoint) { - double xcur = minX + iPoint * stepX; - x[iPoint] = xcur; - y[iPoint] = leg1D(iOrder, xcur); - } - auto g = new TGraph(nPoints, x.data(), y.data()); - g->SetName(Form("g1d_%d_%d", nOrder, iOrder)); - g->SetTitle(Form("c_{%d}=%.4f;u;w", iOrder, coeff[iOrder])); - mg->Add(g, "PL"); - leg->AddEntry(g, Form("c_{%d}=%.4f", iOrder, coeff[iOrder]), "lp"); - } - c1d->cd(1); - mg->SetTitle("Contribution decomposition;u;w"); - mg->Draw("A pmc plc"); - leg->Draw(); - gPad->Update(); - } - } - } - - { // 2D - Info("", "---------------- 2D -------------"); - std::array x, y, z; - for (int nOrder{0}; nOrder <= nMaxOrder; ++nOrder) { - auto c2d = new TCanvas(Form("c2D_%d", nOrder), - Form("Legendre 2D Order %d", nOrder)); - c2d->Divide(nOrder + 1, nOrder + 1); - - TMatrixD coeff(nOrder + 1, nOrder + 1); - // Random initialization - for (int i{0}; i <= nOrder; ++i) { - for (int j{0}; j <= i; ++j) { - coeff(i, j) = getRandom(); - } - } - - o2::math_utils::Legendre2DPolynominal leg2D(coeff); - - // Optionally find a deformation - /* if (findMin2D) { */ - /* std::vector cccc(nOrder + 2, 0.0); */ - /* cccc[0] = nOrder; */ - /* for (int i{0}; i <= nOrder; ++i) { */ - /* for (int j{0}; j <= i; ++j) { */ - /* int k = i * (i + 1) / 2 + j; */ - /* cccc[1 + k] = coeff(i, j); */ - /* } */ - /* } */ - /* auto ig = legendre_poly2D_integral(cccc.data()); */ - - /* gMin->Clear(); */ - /* ROOT::Math::Functor fmin(&legendre_poly2D_integral, */ - /* 2 + nOrder * (nOrder + 1) / 2 + nOrder); */ - /* Info("", "ig=%f parameters=%d", ig, */ - /* 2 + nOrder * (nOrder + 1) / 2 + nOrder); */ - /* gMin->SetFunction(fmin); */ - /* constexpr double minStep{0.001}; */ - /* gMin->SetFixedVariable(0, "nOrder", nOrder); */ - /* gMin->SetFixedVariable(1, "c_00", coeff(0, 0)); */ - /* for (int iOrder{1}; iOrder <= nOrder; ++iOrder) { */ - /* for (int jOrder{0}; jOrder <= iOrder; ++jOrder) { */ - /* int k = iOrder * (iOrder + 1) / 2 + jOrder + 1; */ - /* Info("", "Setting parameter %d", k); */ - /* if (getRandom() < 0.0) { */ - /* gMin->SetFixedVariable(k, Form("c_%d_%d", iOrder, jOrder), */ - /* coeff(iOrder, jOrder)); */ - /* } else { */ - /* gMin->SetVariable(k, Form("c_%d_%d", iOrder, jOrder), */ - /* coeff(iOrder, jOrder), minStep); */ - /* } */ - /* } */ - /* } */ - /* gMin->Minimize(); */ - /* return; */ - /* auto stat = gMin->Status(); */ - /* auto min = gMin->MinValue(); */ - /* if ((stat == 0 || stat == 1) && min < 0.01) { */ - /* Info("", "Minimizer converged with %f; using new values!", min); */ - /* const double *cmins = gMin->X(); */ - /* for (int iOrder{1}; iOrder <= nOrder; ++iOrder) { */ - /* for (int jOrder{0}; jOrder <= iOrder; ++jOrder) { */ - /* int k = iOrder * (iOrder + 1) / 2 + jOrder; */ - /* coeff(iOrder, jOrder) = cmins[k + 1]; */ - /* } */ - /* } */ - /* } */ - /* } */ - - leg2D.printCoefficients(); - - { // Draw total polynominal - for (int iPoint{0}; iPoint < nPoints; ++iPoint) { - double xcur = minX + iPoint * stepX; - for (int jPoint{0}; jPoint < nPoints; ++jPoint) { - double ycur = minX + jPoint * stepX; - int i = iPoint * nPoints + jPoint; - x[i] = xcur; - y[i] = ycur; - z[i] = leg2D(xcur, ycur); - } - } - c2d->cd(nOrder + 1); - auto g = new TGraph2D(nPoints2, x.data(), y.data(), z.data()); - g->SetTitle(Form("Legendre Polynominal %d-deg;u;v;w", nOrder)); - g->SetName(Form("g2d_%d", nOrder)); - g->Draw("surf1"); - g->GetXaxis()->SetRangeUser(minX, maxX); - g->GetYaxis()->SetRangeUser(minX, maxX); - gPad->Update(); - } - - { // Draw decomposition of contributions to polynominal - for (int iOrder{0}; iOrder <= nOrder; ++iOrder) { - for (int jOrder{0}; jOrder <= iOrder; ++jOrder) { - for (int iPoint{0}; iPoint < nPoints; ++iPoint) { - double xcur = minX + iPoint * stepX; - for (int jPoint{0}; jPoint < nPoints; ++jPoint) { - double ycur = minX + jPoint * stepX; - int i = iPoint * nPoints + jPoint; - x[i] = xcur; - y[i] = ycur; - z[i] = leg2D(iOrder, jOrder, xcur, ycur); - } - } - c2d->cd(1 + iOrder * (nOrder + 1) + jOrder); - auto g = new TGraph2D(nPoints2, x.data(), y.data(), z.data()); - g->SetTitle(Form("c_{%d,%d}=%.4f;u;v;w", iOrder, jOrder, - coeff(iOrder, jOrder))); - g->SetName(Form("g2d_%d_%d_%d", nOrder, iOrder, jOrder)); - if (iOrder == 0 && jOrder == 0) { // Fix display of constant value - g->Draw("P0"); - } else { - g->Draw("surf1"); - } - g->GetXaxis()->SetRangeUser(minX, maxX); - g->GetYaxis()->SetRangeUser(minX, maxX); - gPad->Update(); - } - } - } - c2d->Draw(); - } - } -} diff --git a/Detectors/Upgrades/ITS3/macros/test/buildMatBudLUT.C b/Detectors/Upgrades/ITS3/macros/test/buildMatBudLUT.C index fa4f7e3910b99..4af1aa64a8ab2 100644 --- a/Detectors/Upgrades/ITS3/macros/test/buildMatBudLUT.C +++ b/Detectors/Upgrades/ITS3/macros/test/buildMatBudLUT.C @@ -25,8 +25,6 @@ #include #endif -#ifndef GPUCA_ALIGPUCODE // this part is invisible on GPU version - o2::base::MatLayerCylSet mbLUT; bool testMBLUT(const std::string& lutFile = "matbud.root"); @@ -405,5 +403,3 @@ void configLayers() lrData.emplace_back(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin); } while (lrData.back().rMax < 500); } - -#endif //!_COMPILED_ON_GPU_ diff --git a/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/IOUtils.h b/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/IOUtils.h index fa15e73118524..d82cd26e63c56 100644 --- a/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/IOUtils.h +++ b/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/IOUtils.h @@ -14,12 +14,19 @@ #include "DataFormatsITSMFT/CompCluster.h" #include "DataFormatsITSMFT/ROFRecord.h" #include "ITS3Reconstruction/TopologyDictionary.h" -#include "ITStracking/TimeFrame.h" #include "ITStracking/IOUtils.h" #include "ITS3Base/SegmentationMosaix.h" #include "ITS3Base/SpecsV2.h" -namespace o2::its3::ioutils +namespace o2 +{ +namespace its +{ +template +class TimeFrame; +} + +namespace its3::ioutils { constexpr float DefClusErrorRow = o2::its3::SegmentationMosaix::PitchRow * 0.5; constexpr float DefClusErrorCol = o2::its3::SegmentationMosaix::PitchCol * 0.5; @@ -27,7 +34,7 @@ constexpr float DefClusError2Row = DefClusErrorRow * DefClusErrorRow; constexpr float DefClusError2Col = DefClusErrorCol * DefClusErrorCol; template -o2::math_utils::Point3D extractClusterData(const itsmft::CompClusterExt& c, iterator& iter, const its3::TopologyDictionary* dict, T& sig2y, T& sig2z) +o2::math_utils::Point3D extractClusterData(const itsmft::CompClusterExt& c, iterator& iter, const o2::its3::TopologyDictionary* dict, T& sig2y, T& sig2z) { auto pattID = c.getPatternID(); auto ib = constants::detID::isDetITS3(c.getSensorID()); @@ -50,7 +57,7 @@ o2::math_utils::Point3D extractClusterData(const itsmft::CompClusterExt& c, i } template -o2::math_utils::Point3D extractClusterData(const itsmft::CompClusterExt& c, iterator& iter, const its3::TopologyDictionary* dict, T& sig2y, T& sig2z, uint8_t& cls) +o2::math_utils::Point3D extractClusterData(const itsmft::CompClusterExt& c, iterator& iter, const o2::its3::TopologyDictionary* dict, T& sig2y, T& sig2z, uint8_t& cls) { auto pattID = c.getPatternID(); auto ib = constants::detID::isDetITS3(c.getSensorID()); @@ -69,13 +76,13 @@ o2::math_utils::Point3D extractClusterData(const itsmft::CompClusterExt& c, i void convertCompactClusters(gsl::span clusters, gsl::span::iterator& pattIt, std::vector>& output, - const its3::TopologyDictionary* dict); + const o2::its3::TopologyDictionary* dict); int loadROFrameDataITS3(its::TimeFrame<7>* tf, gsl::span rofs, gsl::span clusters, gsl::span::iterator& pattIt, - const its3::TopologyDictionary* dict, + const o2::its3::TopologyDictionary* dict, const dataformats::MCTruthContainer* mcLabels = nullptr); - -} // namespace o2::its3::ioutils +} // namespace its3::ioutils +} // namespace o2 diff --git a/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/TrackingInterface.h b/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/TrackingInterface.h index 931628f2cf876..3b743c59524d2 100644 --- a/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/TrackingInterface.h +++ b/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/TrackingInterface.h @@ -31,6 +31,7 @@ class ITS3TrackingInterface final : public its::ITSTrackingInterface void loadROF(gsl::span& trackROFspan, gsl::span clusters, gsl::span::iterator& pattIt, + int layer, const dataformats::MCTruthContainer* mcLabels) final; private: diff --git a/Detectors/Upgrades/ITS3/reconstruction/src/IOUtils.cxx b/Detectors/Upgrades/ITS3/reconstruction/src/IOUtils.cxx index d7ba4d48dbce4..92e36cd2a4b84 100644 --- a/Detectors/Upgrades/ITS3/reconstruction/src/IOUtils.cxx +++ b/Detectors/Upgrades/ITS3/reconstruction/src/IOUtils.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -10,18 +10,13 @@ // or submit itself to any jurisdiction. #include "ITS3Reconstruction/IOUtils.h" -#include "ITStracking/IOUtils.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/BoundedAllocator.h" #include "DataFormatsITSMFT/CompCluster.h" #include "DataFormatsITSMFT/ROFRecord.h" #include "ITS3Reconstruction/TopologyDictionary.h" #include "ITSBase/GeometryTGeo.h" -#include "ITS3Base/SpecsV2.h" #include "ITStracking/TrackingConfigParam.h" -#include "Framework/Logger.h" - -#include namespace o2::its3::ioutils { @@ -45,16 +40,19 @@ void convertCompactClusters(gsl::span clusters, } for (auto& c : clusters) { - float sigmaY2, sigmaZ2, sigmaYZ = 0; + float sigmaY2 = NAN, sigmaZ2 = NAN; auto locXYZ = extractClusterData(c, pattIt, dict, sigmaY2, sigmaZ2); const auto detID = c.getSensorID(); + // NOTE: this is not consistent with the TRK definition below! + // There we put the alpha for everything cluster to its phi + // here we extract it from the middle of the tile auto& cl3d = output.emplace_back(detID, geom->getMatrixT2L(detID) ^ locXYZ); // local --> tracking if (applyMisalignment) { - auto lrID = geom->getLayer(detID); + const auto lrID = geom->getLayer(detID); sigmaY2 += conf.sysErrY2[lrID]; sigmaZ2 += conf.sysErrZ2[lrID]; } - cl3d.setErrors(sigmaY2, sigmaZ2, sigmaYZ); + cl3d.setErrors(sigmaY2, sigmaZ2, 0.f); } } @@ -68,34 +66,44 @@ int loadROFrameDataITS3(its::TimeFrame<7>* tf, auto geom = its::GeometryTGeo::Instance(); geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G)); - tf->resetROFrameData(rofs.size()); - tf->prepareROFrameData(rofs, clusters); + // tf->resetROFrameData(rofs.size()); // FIXME + // tf->prepareROFrameData(rofs, clusters); FIXME its::bounded_vector clusterSizeVec(clusters.size(), tf->getMemoryPool().get()); for (size_t iRof{0}; iRof < rofs.size(); ++iRof) { const auto& rof = rofs[iRof]; for (int clusterId{rof.getFirstEntry()}; clusterId < rof.getFirstEntry() + rof.getNEntries(); ++clusterId) { - auto& c = clusters[clusterId]; - auto sensorID = c.getSensorID(); - auto layer = geom->getLayer(sensorID); + const auto& c = clusters[clusterId]; + const auto sensorID = c.getSensorID(); + const auto layer = geom->getLayer(sensorID); float sigmaY2{0}, sigmaZ2{0}, sigmaYZ{0}; uint8_t clusterSize{0}; - auto locXYZ = extractClusterData(c, pattIt, dict, sigmaY2, sigmaZ2, clusterSize); + const auto locXYZ = extractClusterData(c, pattIt, dict, sigmaY2, sigmaZ2, clusterSize); clusterSizeVec.push_back(clusterSize); // Transformation to the local --> global - auto gloXYZ = geom->getMatrixL2G(sensorID) * locXYZ; + const auto gloXYZ = geom->getMatrixL2G(sensorID) * locXYZ; // Inverse transformation to the local --> tracking - o2::math_utils::Point3D trkXYZ = geom->getMatrixT2L(sensorID) ^ locXYZ; + const o2::math_utils::Point3D trkXYZ = geom->getMatrixT2L(sensorID) ^ 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(sensorID); - - tf->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), trkXYZ.x(), alpha, - std::array{trkXYZ.y(), trkXYZ.z()}, + float x = trkXYZ.x(), y = trkXYZ.y(); + if (constants::detID::isDetITS3(sensorID)) { + y = 0.f; + x = std::hypot(gloXYZ.x(), gloXYZ.y()); + alpha = std::atan2(gloXYZ.y(), gloXYZ.x()); + } + math_utils::detail::bringToPMPi(alpha); // alpha is defined on -Pi,Pi + + tf->addTrackingFrameInfoToLayer(layer, gloXYZ.x(), gloXYZ.y(), gloXYZ.z(), x, alpha, + std::array{y, trkXYZ.z()}, std::array{sigmaY2, sigmaYZ, sigmaZ2}); /// Rotate to the global frame @@ -103,11 +111,11 @@ int loadROFrameDataITS3(its::TimeFrame<7>* tf, tf->addClusterExternalIndexToLayer(layer, clusterId); } for (unsigned int iL{0}; iL < tf->getUnsortedClusters().size(); ++iL) { - tf->mROFramesClusters[iL][iRof + 1] = tf->getUnsortedClusters()[iL].size(); + tf->mROFramesClusters[iL][iRof + 1] = (int)tf->getUnsortedClusters()[iL].size(); } } - tf->setClusterSize(clusterSizeVec); + // tf->setClusterSize(clusterSizeVec); FIXME for (auto& v : tf->mNTrackletsPerCluster) { v.resize(tf->getUnsortedClusters()[1].size()); @@ -117,8 +125,8 @@ int loadROFrameDataITS3(its::TimeFrame<7>* tf, } if (mcLabels != nullptr) { - tf->mClusterLabels = mcLabels; + // tf->mClusterLabels = mcLabels; // FIXME } - return tf->mNrof; + return 0; } } // namespace o2::its3::ioutils diff --git a/Detectors/Upgrades/ITS3/reconstruction/src/TopologyDictionary.cxx b/Detectors/Upgrades/ITS3/reconstruction/src/TopologyDictionary.cxx index 0d1deb77b7c2e..c7bb4dbe9b6ee 100644 --- a/Detectors/Upgrades/ITS3/reconstruction/src/TopologyDictionary.cxx +++ b/Detectors/Upgrades/ITS3/reconstruction/src/TopologyDictionary.cxx @@ -255,5 +255,7 @@ TopologyDictionary* TopologyDictionary::loadFrom(const std::string& fname, const // Explicitly instaniate templates template math_utils::Point3D TopologyDictionary::getClusterCoordinates(const itsmft::CompClusterExt& cl) const; template math_utils::Point3D TopologyDictionary::getClusterCoordinates(const itsmft::CompClusterExt& cl, const itsmft::ClusterPattern& patt, bool isGroup); +template math_utils::Point3D TopologyDictionary::getClusterCoordinates(const itsmft::CompClusterExt& cl) const; +template math_utils::Point3D TopologyDictionary::getClusterCoordinates(const itsmft::CompClusterExt& cl, const itsmft::ClusterPattern& patt, bool isGroup); } // namespace o2::its3 diff --git a/Detectors/Upgrades/ITS3/reconstruction/src/TrackingInterface.cxx b/Detectors/Upgrades/ITS3/reconstruction/src/TrackingInterface.cxx index 0f5c66a7f9663..9fe6f3735a845 100644 --- a/Detectors/Upgrades/ITS3/reconstruction/src/TrackingInterface.cxx +++ b/Detectors/Upgrades/ITS3/reconstruction/src/TrackingInterface.cxx @@ -13,7 +13,7 @@ #include "ITS3Reconstruction/IOUtils.h" #include "ITSBase/GeometryTGeo.h" #include "ITStracking/TrackingConfigParam.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DetectorsBase/GRPGeomHelper.h" #include "Framework/DeviceSpec.h" @@ -77,9 +77,10 @@ void ITS3TrackingInterface::finaliseCCDB(framework::ConcreteDataMatcher& matcher void ITS3TrackingInterface::loadROF(gsl::span& trackROFspan, gsl::span clusters, gsl::span::iterator& pattIt, + int layer, const dataformats::MCTruthContainer* mcLabels) { - ioutils::loadROFrameDataITS3(mTimeFrame, trackROFspan, clusters, pattIt, mDict, mcLabels); + // ioutils::loadROFrameDataITS3(mTimeFrame, trackROFspan, clusters, pattIt, mDict, mcLabels); } } // namespace o2::its3 diff --git a/Detectors/Upgrades/ITS3/study/CMakeLists.txt b/Detectors/Upgrades/ITS3/study/CMakeLists.txt index 4bb1cbca7dcb0..314b21c529ebf 100644 --- a/Detectors/Upgrades/ITS3/study/CMakeLists.txt +++ b/Detectors/Upgrades/ITS3/study/CMakeLists.txt @@ -17,12 +17,16 @@ o2_add_library(ITS3TrackingStudy src/TrackingStudy.cxx src/ParticleInfoExt.cxx PUBLIC_LINK_LIBRARIES O2::ITS3Workflow + O2::ITS3Align O2::GlobalTracking O2::GlobalTrackingWorkflowReaders O2::GlobalTrackingWorkflowHelpers O2::DataFormatsGlobalTracking O2::DetectorsVertexing - O2::SimulationDataFormat) + O2::SimulationDataFormat + Eigen3::Eigen + nlohmann_json::nlohmann_json +) o2_target_root_dictionary(ITS3TrackingStudy HEADERS include/ITS3TrackingStudy/ITS3TrackingStudyParam.h diff --git a/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/ITS3TrackingStudyParam.h b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/ITS3TrackingStudyParam.h index 2e718622daa90..1150dc19a1162 100644 --- a/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/ITS3TrackingStudyParam.h +++ b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/ITS3TrackingStudyParam.h @@ -22,7 +22,7 @@ namespace o2::its3::study struct ITS3TrackingStudyParam : o2::conf::ConfigurableParamHelper { /// general track selection float maxChi2{36}; - float maxEta{1.0}; + float maxEta{1.5}; float minPt{0.1}; float maxPt{1e2}; /// PV selection @@ -30,6 +30,8 @@ struct ITS3TrackingStudyParam : o2::conf::ConfigurableParamHelper::MatCorrType CorrType = o2::base::PropagatorImpl::MatCorrType::USEMatCorrLUT; /// studies - bool doDCA = true; - bool doDCARefit = true; - bool doPull = true; + bool doDCA = false; + bool doDCARefit = false; + bool doPull = false; + bool doResid = false; bool doMC = false; + bool doMisalignment = false; + + // misalignment + std::string misAlgJson; // json file containing to be applied misalignment + float misAlgExtCY[6] = {0.f}; // extra uncertainty on y + float misAlgExtCZ[6] = {0.f}; // extra uncertainty on z + O2ParamDef(ITS3TrackingStudyParam, "ITS3TrackingStudyParam"); }; diff --git a/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/TrackingStudy.h b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/TrackingStudy.h index 065629058fd32..6d55dfc80aa8a 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); +o2::framework::DataProcessorSpec getTrackingStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, bool withPV); } // namespace o2::its3::study diff --git a/Detectors/Upgrades/ITS3/study/macros/CMakeLists.txt b/Detectors/Upgrades/ITS3/study/macros/CMakeLists.txt index aaf763888c5e0..a772913c51f15 100644 --- a/Detectors/Upgrades/ITS3/study/macros/CMakeLists.txt +++ b/Detectors/Upgrades/ITS3/study/macros/CMakeLists.txt @@ -16,3 +16,11 @@ o2_add_test_root_macro(PlotDCA.C o2_add_test_root_macro(PlotPulls.C PUBLIC_LINK_LIBRARIES O2::ITS3TrackingStudy LABELS its COMPILE_ONLY) + +o2_add_test_root_macro(PlotResiduals.C + PUBLIC_LINK_LIBRARIES O2::ITS3TrackingStudy + LABELS its COMPILE_ONLY) + +o2_add_test_root_macro(PlotMisalignment.C + PUBLIC_LINK_LIBRARIES O2::ITS3TrackingStudy + LABELS its COMPILE_ONLY) diff --git a/Detectors/Upgrades/ITS3/study/macros/PlotMisalignment.C b/Detectors/Upgrades/ITS3/study/macros/PlotMisalignment.C new file mode 100644 index 0000000000000..5b9372bc3402e --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/macros/PlotMisalignment.C @@ -0,0 +1,228 @@ +// 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. + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ReconstructionDataFormats/Track.h" +#include +#endif + +constexpr int kNLay = 8; // slots: PV(-1) + layers 0-6 +constexpr int kNVar = 2; // dY, dZ +constexpr int kNPar = 5; // Y, Z, Snp, Tgl, Q2Pt +// constexpr int kNPtBins = 6; // integrated + 5 differential +// const float kPtEdges[kNPtBins] = {0., 0.3, 0.8, 2., 5., 10.}; +// const char* kPtLabels[kNPtBins] = {"", "0.0= kPtEdges[i] && pt < kPtEdges[i + 1]) { + return i + 1; // 1-indexed, 0 = integrated + } + } + return -1; +} + +void processTree(TFile* f, const char* treeName) +{ + auto* tree = f->Get(treeName); + if (!tree) { + return; + } + + // branch variables + float dY, dZ, phi, eta; + int lay; + auto* trk = new o2::track::TrackParCov; + auto* mcTrk = new o2::track::TrackPar; + tree->SetBranchAddress("dY", &dY); + tree->SetBranchAddress("dZ", &dZ); + tree->SetBranchAddress("phi", &phi); + tree->SetBranchAddress("eta", &eta); + tree->SetBranchAddress("lay", &lay); + tree->SetBranchAddress("trk", &trk); + tree->SetBranchAddress("mcTrk", &mcTrk); + + // --- book histograms --- + // [ptBin][lay] for each plot type + // dY/dZ vs phi + TH2F* hVsPhi[kNVar][kNPtBins][kNLay]; + // dY/dZ vs eta + TH2F* hVsEta[kNVar][kNPtBins][kNLay]; + // profile2D phi vs eta + TProfile2D* hProf[kNVar][kNPtBins][kNLay]; + // pulls + TH1F* hPull[kNPar][kNPtBins][kNLay]; + + for (int ipt = 0; ipt < kNPtBins; ipt++) { + for (int ilay = 0; ilay < kNLay; ilay++) { + for (int iv = 0; iv < kNVar; iv++) { + hVsPhi[iv][ipt][ilay] = new TH2F( + Form("%s_%s_phi%s_l%d", treeName, kVarNames[iv], kPtTags[ipt], ilay), + Form("Layer %d %s;#phi (rad);%s", ilay - 1, kPtLabels[ipt], kVarTitles[iv]), + 100, 0, 2 * TMath::Pi(), 100, -100, 100); + hVsEta[iv][ipt][ilay] = new TH2F( + Form("%s_%s_eta%s_l%d", treeName, kVarNames[iv], kPtTags[ipt], ilay), + Form("Layer %d %s;#eta;%s", ilay - 1, kPtLabels[ipt], kVarTitles[iv]), + 100, -1.5, 1.5, 100, -100, 100); + hProf[iv][ipt][ilay] = new TProfile2D( + Form("%s_%s_prof%s_l%d", treeName, kVarNames[iv], kPtTags[ipt], ilay), + Form("Layer %d %s;#phi (rad);#eta;#LT%s#GT", ilay - 1, kPtLabels[ipt], kVarTitles[iv]), + 50, 0, 2 * TMath::Pi(), 50, -1.5, 1.5); + } + for (int ip = 0; ip < kNPar; ip++) { + hPull[ip][ipt][ilay] = new TH1F( + Form("%s_pull_%s%s_l%d", treeName, kParNames[ip], kPtTags[ipt], ilay), + Form("Layer %d %s;pull_{%s};counts", ilay - 1, kPtLabels[ipt], kParNames[ip]), + 100, -5, 5); + } + } + } + + // --- fill loop --- + const Long64_t nEntries = tree->GetEntries(); + for (Long64_t i = 0; i < nEntries; i++) { + tree->GetEntry(i); + if (i % 100000 == 0) { + std::cout << "Progress: " << i << "/" << nEntries << " (" << (100.0 * i / nEntries) << "%)" << std::endl; + } + + int ilay = lay + 1; + float pt = trk->getPt(); + float dYum = dY * 10000.f; + float dZum = dZ * 10000.f; + + // integrated (ipt=0) + differential + int iptDiff = getPtBin(pt); + for (int ipt : {0, iptDiff}) { + if (ipt < 0) { + continue; + } + for (int iv = 0; iv < kNVar; iv++) { + float val = (iv == 0) ? dYum : dZum; + hVsPhi[iv][ipt][ilay]->Fill(phi, val); + hVsEta[iv][ipt][ilay]->Fill(eta, val); + hProf[iv][ipt][ilay]->Fill(phi, eta, val); + } + for (int ip = 0; ip < kNPar; ip++) { + float sigma2 = trk->getDiagError2(ip); + if (sigma2 > 0) { + hPull[ip][ipt][ilay]->Fill((trk->getParam(ip) - mcTrk->getParam(ip)) / std::sqrt(sigma2)); + } + } + } + } + + // --- draw & save --- + auto drawSliceFits = [](TH2F* h) { + h->FitSlicesY(nullptr, 0, -1, 5); + auto* hMean = (TH1D*)gDirectory->Get(Form("%s_1", h->GetName())); + auto* hSigma = (TH1D*)gDirectory->Get(Form("%s_2", h->GetName())); + if (hMean && hSigma) { + for (auto* hh : {hMean, hSigma}) { + hh->SetMarkerStyle(20); + hh->SetMarkerSize(0.6); + } + hMean->SetMarkerColor(kRed); + hMean->SetLineColor(kRed); + hSigma->SetMarkerColor(kOrange + 1); + hSigma->SetLineColor(kOrange + 1); + hMean->Draw("same"); + hSigma->Draw("same"); + } + }; + + for (int ipt = 0; ipt < kNPtBins; ipt++) { + // dY/dZ vs phi + for (int iv = 0; iv < kNVar; iv++) { + auto* c = new TCanvas(Form("%s_%s_vs_phi%s", treeName, kVarNames[iv], kPtTags[ipt]), + Form("%s vs #phi %s", kVarNames[iv], kPtLabels[ipt]), 800, 1600); + c->Divide(2, 4); + for (int ilay = 0; ilay < kNLay; ilay++) { + c->cd(ilay + 1); + gPad->SetRightMargin(0.13); + hVsPhi[iv][ipt][ilay]->Draw("col"); + drawSliceFits(hVsPhi[iv][ipt][ilay]); + } + c->SaveAs(Form("%s.png", c->GetName())); + } + + // dY/dZ vs eta + for (int iv = 0; iv < kNVar; iv++) { + auto* c = new TCanvas(Form("%s_%s_vs_eta%s", treeName, kVarNames[iv], kPtTags[ipt]), + Form("%s vs #eta %s", kVarNames[iv], kPtLabels[ipt]), 800, 1600); + c->Divide(2, 4); + for (int ilay = 0; ilay < kNLay; ilay++) { + c->cd(ilay + 1); + gPad->SetRightMargin(0.13); + hVsEta[iv][ipt][ilay]->Draw("col"); + drawSliceFits(hVsEta[iv][ipt][ilay]); + } + c->SaveAs(Form("%s.png", c->GetName())); + } + + // profile2D + for (int iv = 0; iv < kNVar; iv++) { + auto* c = new TCanvas(Form("%s_%s_prof2d%s", treeName, kVarNames[iv], kPtTags[ipt]), + Form("%s #phi vs #eta %s", kVarNames[iv], kPtLabels[ipt]), 800, 1600); + c->Divide(2, 4); + for (int ilay = 0; ilay < kNLay; ilay++) { + c->cd(ilay + 1); + gPad->SetRightMargin(0.15); + hProf[iv][ipt][ilay]->Draw("colz"); + hProf[iv][ipt][ilay]->GetZaxis()->SetRangeUser(-100, 100); + } + c->SaveAs(Form("%s.png", c->GetName())); + } + + // pulls + for (int ilay = 0; ilay < kNLay; ilay++) { + auto* c = new TCanvas(Form("%s_pulls_l%d%s", treeName, ilay, kPtTags[ipt]), + Form("Pulls layer %d %s", ilay - 1, kPtLabels[ipt]), 1200, 800); + c->Divide(3, 2); + for (int ip = 0; ip < kNPar; ip++) { + c->cd(ip + 1); + hPull[ip][ipt][ilay]->Draw(); + if (hPull[ip][ipt][ilay]->GetEntries() > 20) { + hPull[ip][ipt][ilay]->Fit("gaus", "Q"); + } + } + c->SaveAs(Form("%s.png", c->GetName())); + } + } +} + +void PlotMisalignment(const char* fname = "its3TrackStudy.root") +{ + gStyle->SetOptStat(0); + gStyle->SetOptFit(1); + auto f = TFile::Open(fname); + processTree(f, "idealRes"); + processTree(f, "misRes"); +} diff --git a/Detectors/Upgrades/ITS3/study/macros/PlotResiduals.C b/Detectors/Upgrades/ITS3/study/macros/PlotResiduals.C new file mode 100644 index 0000000000000..e35fd4df125e5 --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/macros/PlotResiduals.C @@ -0,0 +1,70 @@ +// 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. + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include +#endif + +void PlotResiduals(const char* fname = "its3TrackStudy.root") +{ + auto f = TFile::Open(fname); + auto res = f->Get("res"); + + const int nLay = 8; + const int nVar = 6; + const char* vars[nVar] = {"dYInt", "dZInt", "dYIn", "dZIn", "dYOut", "dZOut"}; + const char* titles[nVar] = {"d_{Y} (#mum) (weighted)", "d_{Z} (#mum) (weighted)", "d_{Y} (#mu) (inward)", "d_{Y} (#mu) (inward)", "d_{Y} (#mu) (outward)", "d_{Z} (#mu) (outward)"}; + + TCanvas* canvs[nVar]; + for (int iv = 0; iv < nVar; iv++) { + canvs[iv] = new TCanvas(vars[iv], Form("%s residuals", vars[iv]), 800, 1600); + canvs[iv]->Divide(2, 4); + } + + for (int iv = 0; iv < nVar; iv++) { + canvs[iv]->cd(0); + for (int lay = -1; lay <= 6; lay++) { + canvs[iv]->cd(lay + 2); + gPad->SetRightMargin(0.13); + + TString hname = Form("h_%s_lay%d", vars[iv], lay + 1); + TString expr = Form("%s*10000:phi>>%s(100,0,6.283,100,-100,100)", vars[iv], hname.Data()); + TString sel = Form("lay==%d", lay); + res->Draw(expr, sel, "col"); + + auto* h = (TH2F*)gDirectory->Get(hname); + h->SetTitle(Form("Layer %d ;#phi (rad);%s", lay, titles[iv])); + h->GetZaxis()->SetLabelSize(0.035); + + // fit y-slices with gaussian + h->FitSlicesY(nullptr, 0, -1, 5); + auto* hMean = (TH1D*)gDirectory->Get(Form("%s_1", hname.Data())); + auto* hSigma = (TH1D*)gDirectory->Get(Form("%s_2", hname.Data())); + + for (auto* hh : {hMean, hSigma}) { + hh->SetMarkerStyle(20); + hh->SetMarkerSize(0.6); + } + hMean->SetMarkerColor(kRed); + hMean->SetLineColor(kRed); + hSigma->SetMarkerColor(kOrange + 1); + hSigma->SetLineColor(kOrange + 1); + hMean->Draw("same"); + hSigma->Draw("same"); + } + canvs[iv]->SaveAs(Form("%s.png", canvs[iv]->GetName())); + } +} diff --git a/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx b/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx index cb1d7f381983d..63a08d1c9c6ab 100644 --- a/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx +++ b/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2026 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. // @@ -14,6 +14,7 @@ #include #include +#include #include "CommonUtils/TreeStreamRedirector.h" #include "DataFormatsGlobalTracking/RecoContainer.h" @@ -35,6 +36,8 @@ #include "ITS3Reconstruction/IOUtils.h" #include "ITS3TrackingStudy/ITS3TrackingStudyParam.h" #include "ITS3TrackingStudy/ParticleInfoExt.h" +#include "ITS3Align/MisalignmentUtils.h" +#include "ITS3Align/TrackFit.h" #include "ReconstructionDataFormats/DCA.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "ReconstructionDataFormats/PrimaryVertex.h" @@ -43,6 +46,7 @@ #include "SimulationDataFormat/MCEventLabel.h" #include "SimulationDataFormat/MCUtils.h" #include "Steer/MCKinematicsReader.h" +#include "Framework/Logger.h" namespace o2::its3::study { @@ -58,6 +62,10 @@ using T2VMap = std::unordered_map; class TrackingStudySpec : public Task { public: + TrackingStudySpec(const TrackingStudySpec&) = delete; + TrackingStudySpec(TrackingStudySpec&&) = delete; + TrackingStudySpec& operator=(const TrackingStudySpec&) = delete; + TrackingStudySpec& operator=(TrackingStudySpec&&) = delete; TrackingStudySpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool useMC) : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC) {} ~TrackingStudySpec() final = default; @@ -67,16 +75,19 @@ class TrackingStudySpec : public Task void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final; private: - void process(o2::globaltracking::RecoContainer& recoData); + void process(); void updateTimeDependentParams(ProcessingContext& pc); - std::vector> prepareITSClusters(const o2::globaltracking::RecoContainer& data) const; - bool selectTrack(GTrackID trkID, o2::globaltracking::RecoContainer& recoData, bool checkMCTruth = true) const; - T2VMap buildT2V(o2::globaltracking::RecoContainer& recoData, bool includeCont = false, bool requireMCMatch = true) const; - bool refitITSPVTrack(o2::globaltracking::RecoContainer& recoData, o2::track::TrackParCov& trFit, GTrackID gidx); - void doDCAStudy(o2::globaltracking::RecoContainer& recoData); - void doDCARefitStudy(o2::globaltracking::RecoContainer& recoData); - void doPullStudy(o2::globaltracking::RecoContainer& recoData); - void doMCStudy(o2::globaltracking::RecoContainer& recoData); + void prepareITSClusters(); + bool selectTrack(GTrackID trkID, bool checkMCTruth = true) const; + T2VMap buildT2V(bool includeCont = false, bool requireMCMatch = true) const; + bool refitITSPVTrack(o2::track::TrackParCov& trFit, GTrackID gidx); + + void doDCAStudy(); + void doDCARefitStudy(); + void doPullStudy(); + void doMCStudy(); + void doResidStudy(); + void doMisalignmentStudy(); struct TrackCounter { TrackCounter() = default; @@ -126,14 +137,21 @@ class TrackingStudySpec : public Task }; TrackCounter mTrackCounter; + using TrackingCluster = align::TrackingCluster; + std::vector mITScl; + std::span mITSclRef; + + const ITS3TrackingStudyParam* mParams{nullptr}; std::unique_ptr mDBGOut; std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; bool mUseMC{false}; GTrackID::mask_t mTracksSrc; o2::vertexing::PVertexer mVertexer; - o2::steer::MCKinematicsReader mcReader; // reader of MC information + o2::steer::MCKinematicsReader mMCReader; // reader of MC information const o2::its3::TopologyDictionary* mITSDict = nullptr; // cluster patterns dictionary + o2::globaltracking::RecoContainer mRecoData; + align::MisalignmentModel mMisalignment; }; void TrackingStudySpec::init(InitContext& ic) @@ -144,17 +162,16 @@ void TrackingStudySpec::init(InitContext& ic) std::string dbgnm = maxLanes == 1 ? "its3TrackStudy.root" : fmt::format("its3TrackStudy_{}.root", lane); mDBGOut = std::make_unique(dbgnm.c_str(), "recreate"); - if (mUseMC && !mcReader.initFromDigitContext(o2::base::NameConf::getCollisionContextFileName())) { + if (mUseMC && !mMCReader.initFromDigitContext(o2::base::NameConf::getCollisionContextFileName())) { LOGP(fatal, "initialization of MCKinematicsReader failed"); } } void TrackingStudySpec::run(ProcessingContext& pc) { - o2::globaltracking::RecoContainer recoData; - recoData.collectData(pc, *mDataRequest); + mRecoData.collectData(pc, *mDataRequest); updateTimeDependentParams(pc); - process(recoData); + process(); } void TrackingStudySpec::updateTimeDependentParams(ProcessingContext& pc) @@ -165,6 +182,13 @@ void TrackingStudySpec::updateTimeDependentParams(ProcessingContext& pc) auto grp = o2::base::GRPGeomHelper::instance().getGRPECS(); mVertexer.init(); o2::its::GeometryTGeo::Instance()->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G, o2::math_utils::TransformType::T2G)); + mParams = &ITS3TrackingStudyParam::Instance(); + if (mParams->doMisalignment) { + mMisalignment = {}; + if (!mParams->misAlgJson.empty()) { + mMisalignment = align::loadMisalignmentModel(mParams->misAlgJson); + } + } } } @@ -185,76 +209,105 @@ void TrackingStudySpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) } } -void TrackingStudySpec::process(o2::globaltracking::RecoContainer& recoData) +void TrackingStudySpec::process() { - const auto& conf = ITS3TrackingStudyParam::Instance(); - if (conf.doDCA) { - doDCAStudy(recoData); + prepareITSClusters(); + if (mParams->doDCA) { + doDCAStudy(); + } + if (mParams->doDCARefit) { + doDCARefitStudy(); + } + if (mUseMC && mParams->doPull) { + doPullStudy(); } - if (conf.doDCARefit) { - doDCARefitStudy(recoData); + if (mUseMC && mParams->doMC) { + doMCStudy(); } - if (mUseMC && conf.doPull) { - doPullStudy(recoData); + if (mParams->doResid) { + doResidStudy(); } - if (mUseMC && conf.doMC) { - doMCStudy(recoData); + if (mUseMC && mParams->doMisalignment) { + doMisalignmentStudy(); } } -std::vector> TrackingStudySpec::prepareITSClusters(const o2::globaltracking::RecoContainer& data) const +void TrackingStudySpec::prepareITSClusters() { - 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::its3::ioutils::convertCompactClusters(clusITS, pattIt, itscl, mITSDict); - } - return std::move(itscl); + 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()); + } + 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 + } } -bool TrackingStudySpec::selectTrack(GTrackID trkID, o2::globaltracking::RecoContainer& recoData, bool checkMCTruth) const +bool TrackingStudySpec::selectTrack(GTrackID trkID, bool checkMCTruth) const { - const auto& conf = ITS3TrackingStudyParam::Instance(); if (!trkID.includesDet(GTrackID::ITS)) { return false; } - if (!recoData.isTrackSourceLoaded(trkID.getSource())) { + if (!mRecoData.isTrackSourceLoaded(trkID.getSource())) { return false; } - auto contributorsGID = recoData.getSingleDetectorRefs(trkID); + auto contributorsGID = mRecoData.getSingleDetectorRefs(trkID); if (!contributorsGID[GTrackID::ITS].isIndexSet()) { // we need of course ITS return false; } // ITS specific - const auto& itsTrk = recoData.getITSTrack(contributorsGID[GTrackID::ITS]); - if (itsTrk.getChi2() > conf.maxChi2 || itsTrk.getNClusters() < conf.minITSCls) { + const auto& itsTrk = mRecoData.getITSTrack(contributorsGID[GTrackID::ITS]); + if (itsTrk.getChi2() > mParams->maxChi2 || itsTrk.getNClusters() < mParams->minITSCls) { return false; } // TPC specific if (contributorsGID[GTrackID::TPC].isIndexSet()) { - const auto& tpcTrk = recoData.getTPCTrack(contributorsGID[GTrackID::TPC]); - if (tpcTrk.getNClusters() < conf.minTPCCls) { + const auto& tpcTrk = mRecoData.getTPCTrack(contributorsGID[GTrackID::TPC]); + if (tpcTrk.getNClusters() < mParams->minTPCCls) { return false; } } // general - const auto& gTrk = recoData.getTrackParam(trkID); - if (gTrk.getPt() < conf.minPt || gTrk.getPt() > conf.maxPt) { + const auto& gTrk = mRecoData.getTrackParam(trkID); + if (gTrk.getPt() < mParams->minPt || gTrk.getPt() > mParams->maxPt) { return false; } - if (std::abs(gTrk.getEta()) > conf.maxEta) { + if (std::abs(gTrk.getEta()) > mParams->maxEta) { return false; } if (mUseMC && checkMCTruth) { - const auto& itsLbl = recoData.getTrackMCLabel(contributorsGID[GTrackID::ITS]); + const auto& itsLbl = mRecoData.getTrackMCLabel(contributorsGID[GTrackID::ITS]); if (!itsLbl.isValid()) { return false; } if (contributorsGID[GTrackID::TPC].isIndexSet()) { - const auto& tpcLbl = recoData.getTrackMCLabel(contributorsGID[GTrackID::TPC]); + const auto& tpcLbl = mRecoData.getTrackMCLabel(contributorsGID[GTrackID::TPC]); if (itsLbl != tpcLbl) { return false; } @@ -263,7 +316,7 @@ bool TrackingStudySpec::selectTrack(GTrackID trkID, o2::globaltracking::RecoCont // TODO } if (contributorsGID[GTrackID::TOF].isIndexSet()) { - const auto& tofLbls = recoData.getTOFClustersMCLabels()->getLabels(contributorsGID[GTrackID::TOF]); + const auto& tofLbls = mRecoData.getTOFClustersMCLabels()->getLabels(contributorsGID[GTrackID::TOF]); for (const auto& lbl : tofLbls) { if (lbl.isValid()) { return true; @@ -274,22 +327,21 @@ bool TrackingStudySpec::selectTrack(GTrackID trkID, o2::globaltracking::RecoCont return true; } -T2VMap TrackingStudySpec::buildT2V(o2::globaltracking::RecoContainer& recoData, bool includeCont, bool requireMCMatch) const +T2VMap TrackingStudySpec::buildT2V(bool includeCont, bool requireMCMatch) const { // build track->vertex assoc., maybe including contributor tracks - const auto& conf = ITS3TrackingStudyParam::Instance(); - auto pvvec = recoData.getPrimaryVertices(); - auto trackIndex = recoData.getPrimaryVertexMatchedTracks(); // Global ID's for associated tracks - auto vtxRefs = recoData.getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs - auto nv = vtxRefs.size() - 1; // last entry is for unassigned tracks, ignore them + auto pvvec = mRecoData.getPrimaryVertices(); + auto trackIndex = mRecoData.getPrimaryVertexMatchedTracks(); // Global ID's for associated tracks + auto vtxRefs = mRecoData.getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs + auto nv = vtxRefs.size() - 1; // last entry is for unassigned tracks, ignore them T2VMap t2v; for (size_t iv = 0; iv < nv; ++iv) { const auto& pv = pvvec[iv]; - if (pv.getNContributors() - 1 < conf.minPVCont) { + if (pv.getNContributors() - 1 < mParams->minPVCont) { continue; } if (requireMCMatch) { - auto pvl = recoData.getPrimaryVertexMCLabel(iv); + auto pvl = mRecoData.getPrimaryVertexMCLabel(iv); } const auto& vtxRef = vtxRefs[iv]; int it = vtxRef.getFirstEntry(), itLim = it + vtxRef.getEntries(); @@ -298,26 +350,26 @@ T2VMap TrackingStudySpec::buildT2V(o2::globaltracking::RecoContainer& recoData, if (tvid.isAmbiguous()) { continue; } - if (!recoData.isTrackSourceLoaded(tvid.getSource())) { + if (!mRecoData.isTrackSourceLoaded(tvid.getSource())) { continue; } if (mUseMC && requireMCMatch) { - const auto& pvlbl = recoData.getPrimaryVertexMCLabel(iv); - if (pvlbl.getEventID() != recoData.getTrackMCLabel(tvid).getEventID()) { + const auto& pvlbl = mRecoData.getPrimaryVertexMCLabel(iv); + if (pvlbl.getEventID() != mRecoData.getTrackMCLabel(tvid).getEventID()) { continue; } } t2v[tvid] = iv; if (includeCont) { - auto contributorsGID = recoData.getSingleDetectorRefs(tvid); + auto contributorsGID = mRecoData.getSingleDetectorRefs(tvid); for (int cis = 0; cis < GTrackID::NSources; cis++) { const auto cdm = GTrackID::getSourceDetectorsMask(cis); - if (!recoData.isTrackSourceLoaded(cis) || !cdm[DetID::ITS] || !contributorsGID[cis].isIndexSet()) { + if (!mRecoData.isTrackSourceLoaded(cis) || !cdm[DetID::ITS] || !contributorsGID[cis].isIndexSet()) { continue; } if (mUseMC && requireMCMatch) { - const auto& pvlbl = recoData.getPrimaryVertexMCLabel(iv); - if (pvlbl.getEventID() != recoData.getTrackMCLabel(contributorsGID[cis]).getEventID()) { + const auto& pvlbl = mRecoData.getPrimaryVertexMCLabel(iv); + if (pvlbl.getEventID() != mRecoData.getTrackMCLabel(contributorsGID[cis]).getEventID()) { continue; } } @@ -329,19 +381,18 @@ T2VMap TrackingStudySpec::buildT2V(o2::globaltracking::RecoContainer& recoData, return std::move(t2v); } -bool TrackingStudySpec::refitITSPVTrack(o2::globaltracking::RecoContainer& recoData, o2::track::TrackParCov& trFit, GTrackID gidx) +bool TrackingStudySpec::refitITSPVTrack(o2::track::TrackParCov& trFit, GTrackID gidx) { if (gidx.getSource() != GTrackID::ITS) { return false; } - static auto pvvec = recoData.getPrimaryVertices(); - static auto t2v = buildT2V(recoData, true, true); - static const auto itsClusters = prepareITSClusters(recoData); + static auto pvvec = mRecoData.getPrimaryVertices(); + static auto t2v = buildT2V(true, true); static std::vector itsTracksROF; if (static bool done{false}; !done) { done = true; - const auto& itsTracksROFRec = recoData.getITSTracksROFRecords(); - itsTracksROF.resize(recoData.getITSTracks().size()); + const auto& itsTracksROFRec = mRecoData.getITSTracksROFRecords(); + itsTracksROF.resize(mRecoData.getITSTracks().size()); for (unsigned irf = 0, cnt = 0; irf < itsTracksROFRec.size(); irf++) { int ntr = itsTracksROFRec[irf].getNEntries(); for (int itr = 0; itr < ntr; itr++) { @@ -350,20 +401,18 @@ bool TrackingStudySpec::refitITSPVTrack(o2::globaltracking::RecoContainer& recoD } } auto prop = o2::base::Propagator::Instance(); - const auto& conf = ITS3TrackingStudyParam::Instance(); - std::array, 8> clArr{}; - std::array clAlpha{}; - const auto trkIn = recoData.getTrackParam(gidx); - const auto trkOut = recoData.getTrackParamOut(gidx); - const auto& itsTrOrig = recoData.getITSTrack(gidx); + std::array clArr{nullptr}; + const auto trkIn = mRecoData.getTrackParam(gidx); + const auto trkOut = mRecoData.getTrackParamOut(gidx); + const auto& itsTrOrig = mRecoData.getITSTrack(gidx); int ncl = itsTrOrig.getNumberOfClusters(), rof = itsTracksROF[gidx.getIndex()]; - const auto& itsTrackClusRefs = recoData.getITSTracksClusterRefs(); + const auto& itsTrackClusRefs = mRecoData.getITSTracksClusterRefs(); int clEntry = itsTrOrig.getFirstClusterEntry(); const auto propagator = o2::base::Propagator::Instance(); // convert PV to a fake cluster in the track DCA frame const auto& pv = pvvec[t2v[gidx]]; auto trkPV = trkIn; - if (!prop->propagateToDCA(pv, trkPV, prop->getNominalBz(), 2.0, conf.CorrType)) { + if (!prop->propagateToDCA(pv, trkPV, prop->getNominalBz(), 2.0, mParams->CorrType)) { mTrackCounter -= gidx.getSource(); return false; } @@ -371,25 +420,25 @@ bool TrackingStudySpec::refitITSPVTrack(o2::globaltracking::RecoContainer& recoD float cosAlp = NAN, sinAlp = NAN; o2::math_utils::sincos(trkPV.getAlpha(), sinAlp, cosAlp); // vertex position rotated to track frame - clArr[0].setXYZ(pv.getX() * cosAlp + pv.getY() * sinAlp, -pv.getX() * sinAlp + pv.getY() * cosAlp, pv.getZ()); - clArr[0].setSigmaY2(0.5 * (pv.getSigmaX2() + pv.getSigmaY2())); - clArr[0].setSigmaZ2(pv.getSigmaZ2()); - clAlpha[0] = trkPV.getAlpha(); + TrackingCluster pvCls; + pvCls.setXYZ((pv.getX() * cosAlp) + (pv.getY() * sinAlp), (-pv.getX() * sinAlp) + (pv.getY() * cosAlp), pv.getZ()); + pvCls.setSigmaY2(0.5f * (pv.getSigmaX2() + pv.getSigmaY2())); + pvCls.setSigmaZ2(pv.getSigmaZ2()); + clArr[0] = &pvCls; for (int icl = 0; icl < ncl; ++icl) { // ITS clusters are referred in layer decreasing order - clArr[ncl - icl] = itsClusters[itsTrackClusRefs[clEntry + icl]]; - clAlpha[ncl - icl] = o2::its::GeometryTGeo::Instance()->getSensorRefAlpha(clArr[ncl - icl].getSensorID()); + clArr[ncl - icl] = &mITScl[itsTrackClusRefs[clEntry + icl]]; } // start refit trFit = trkOut; trFit.resetCovariance(1'000); float chi2{0}; for (int icl = ncl; icl >= 0; --icl) { // go backwards - if (!trFit.rotate(clAlpha[icl]) || !prop->propagateToX(trFit, clArr[icl].getX(), prop->getNominalBz(), 0.85, 2.0, conf.CorrType)) { + if (!trFit.rotate(clArr[icl]->alpha) || !prop->propagateToX(trFit, clArr[icl]->getX(), prop->getNominalBz(), 0.85, 2.0, mParams->CorrType)) { mTrackCounter -= gidx.getSource(); return false; } - chi2 += trFit.getPredictedChi2(clArr[icl]); - if (!trFit.update(clArr[icl])) { + chi2 += trFit.getPredictedChi2(*clArr[icl]); + if (!trFit.update(*clArr[icl])) { mTrackCounter -= gidx.getSource(); return false; } @@ -398,27 +447,26 @@ bool TrackingStudySpec::refitITSPVTrack(o2::globaltracking::RecoContainer& recoD return true; }; -void TrackingStudySpec::doDCAStudy(o2::globaltracking::RecoContainer& recoData) +void TrackingStudySpec::doDCAStudy() { /// analyse DCA of impact parameter for different track types LOGP(info, "Doing DCA study"); mTrackCounter.reset(); - const auto& conf = ITS3TrackingStudyParam::Instance(); auto prop = o2::base::Propagator::Instance(); TStopwatch sw; sw.Start(); int nDCAFits{0}, nDCAFitsFail{0}; - auto pvvec = recoData.getPrimaryVertices(); - auto trackIndex = recoData.getPrimaryVertexMatchedTracks(); // Global ID's for associated tracks - auto vtxRefs = recoData.getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs - auto nv = vtxRefs.size() - 1; // last entry is for unassigned tracks, ignore them + auto pvvec = mRecoData.getPrimaryVertices(); + auto trackIndex = mRecoData.getPrimaryVertexMatchedTracks(); // Global ID's for associated tracks + auto vtxRefs = mRecoData.getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs + auto nv = vtxRefs.size() - 1; // last entry is for unassigned tracks, ignore them auto& stream = (*mDBGOut) << "dca"; for (int iv = 0; iv < nv; iv++) { const auto& pv = pvvec[iv]; const auto& vtref = vtxRefs[iv]; for (int is = 0; is < GTrackID::NSources; is++) { const auto dm = GTrackID::getSourceDetectorsMask(is); - if (!recoData.isTrackSourceLoaded(is) || !dm[DetID::ITS]) { + if (!mRecoData.isTrackSourceLoaded(is) || !dm[DetID::ITS]) { mTrackCounter &= is; continue; } @@ -432,23 +480,23 @@ void TrackingStudySpec::doDCAStudy(o2::globaltracking::RecoContainer& recoData) // we fit each different sub-track type, that include ITS, e.g. // ITS,ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF - auto contributorsGID = recoData.getSingleDetectorRefs(vid); + auto contributorsGID = mRecoData.getSingleDetectorRefs(vid); for (int cis = 0; cis < GTrackID::NSources && cis <= is; cis++) { const auto cdm = GTrackID::getSourceDetectorsMask(cis); - if (!recoData.isTrackSourceLoaded(cis) || !cdm[DetID::ITS] || !contributorsGID[cis].isIndexSet()) { + if (!mRecoData.isTrackSourceLoaded(cis) || !cdm[DetID::ITS] || !contributorsGID[cis].isIndexSet()) { mTrackCounter &= cis; continue; } - if (!selectTrack(contributorsGID[cis], recoData)) { + if (!selectTrack(contributorsGID[cis])) { mTrackCounter &= vid.getSource(); continue; } o2::dataformats::DCA dcaInfo; - const auto& trk = recoData.getTrackParam(contributorsGID[cis]); + const auto& trk = mRecoData.getTrackParam(contributorsGID[cis]); auto trkRefit = trk; // for ITS standalone tracks instead of having the trk at the pv we refit with the pv - if (conf.refitITS && cis == GTrackID::ITS && !refitITSPVTrack(recoData, trkRefit, contributorsGID[cis])) { + if (mParams->refitITS && cis == GTrackID::ITS && !refitITSPVTrack(trkRefit, contributorsGID[cis])) { mTrackCounter -= cis; continue; } else { @@ -456,7 +504,7 @@ void TrackingStudySpec::doDCAStudy(o2::globaltracking::RecoContainer& recoData) }; auto trkDCA = trk; - if (!prop->propagateToDCABxByBz(pv, trkDCA, 2.f, conf.CorrType, &dcaInfo)) { + if (!prop->propagateToDCABxByBz(pv, trkDCA, 2.f, mParams->CorrType, &dcaInfo)) { mTrackCounter -= cis; ++nDCAFitsFail; continue; @@ -470,19 +518,19 @@ void TrackingStudySpec::doDCAStudy(o2::globaltracking::RecoContainer& recoData) << "dca=" << dcaInfo; if (mUseMC) { - const auto& lbl = recoData.getTrackMCLabel(contributorsGID[cis]); + const auto& lbl = mRecoData.getTrackMCLabel(contributorsGID[cis]); lbl.print(); o2::dataformats::DCA dcaInfoMC; - const auto& eve = mcReader.getMCEventHeader(lbl.getSourceID(), lbl.getEventID()); + const auto& eve = mMCReader.getMCEventHeader(lbl.getSourceID(), lbl.getEventID()); o2::dataformats::VertexBase mcEve; mcEve.setPos({(float)eve.GetX(), (float)eve.GetY(), (float)eve.GetZ()}); auto trkC = trk; - if (!prop->propagateToDCABxByBz(mcEve, trkC, 2.f, conf.CorrType, &dcaInfoMC)) { + if (!prop->propagateToDCABxByBz(mcEve, trkC, 2.f, mParams->CorrType, &dcaInfoMC)) { mTrackCounter -= cis; ++nDCAFitsFail; continue; } - const auto& mcTrk = mcReader.getTrack(lbl); + const auto& mcTrk = mMCReader.getTrack(lbl); if (mcTrk == nullptr) { LOGP(fatal, "mcTrk is null did selection fail?"); } @@ -503,21 +551,20 @@ void TrackingStudySpec::doDCAStudy(o2::globaltracking::RecoContainer& recoData) mTrackCounter.print(); } -void TrackingStudySpec::doDCARefitStudy(o2::globaltracking::RecoContainer& recoData) +void TrackingStudySpec::doDCARefitStudy() { /// analyse DCA of impact parameter for different track types while refitting the PV without the cand track LOGP(info, "Doing DCARefit study"); mTrackCounter.reset(); - const auto& conf = ITS3TrackingStudyParam::Instance(); auto prop = o2::base::Propagator::Instance(); TStopwatch sw; sw.Start(); // build track->vertex assoc. - auto pvvec = recoData.getPrimaryVertices(); - auto vtxRefs = recoData.getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs - auto nv = vtxRefs.size() - 1; // last entry is for unassigned tracks, ignore them - auto t2v = buildT2V(recoData); + auto pvvec = mRecoData.getPrimaryVertices(); + auto vtxRefs = mRecoData.getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs + auto nv = vtxRefs.size() - 1; // last entry is for unassigned tracks, ignore them + auto t2v = buildT2V(); std::vector> v2t; v2t.resize(nv); auto creator = [&](const auto& trk, GTrackID trkID, float _t0, float terr) -> bool { @@ -531,11 +578,11 @@ void TrackingStudySpec::doDCARefitStudy(o2::globaltracking::RecoContainer& recoD } // general if constexpr (isBarrelTrack()) { - if (trk.getPt() < conf.minPt || trk.getPt() > conf.maxPt) { + if (trk.getPt() < mParams->minPt || trk.getPt() > mParams->maxPt) { mTrackCounter &= trkID.getSource(); return false; } - if (std::abs(trk.getEta()) > conf.maxEta) { + if (std::abs(trk.getEta()) > mParams->maxEta) { mTrackCounter &= trkID.getSource(); return false; } @@ -543,7 +590,7 @@ void TrackingStudySpec::doDCARefitStudy(o2::globaltracking::RecoContainer& recoD mTrackCounter &= trkID.getSource(); return false; } - if (!selectTrack(trkID, recoData, mUseMC)) { + if (!selectTrack(trkID, mUseMC)) { mTrackCounter &= trkID.getSource(); return false; } @@ -551,20 +598,20 @@ void TrackingStudySpec::doDCARefitStudy(o2::globaltracking::RecoContainer& recoD v2t[t2v[trkID]].push_back(trkID); return true; }; - recoData.createTracksVariadic(creator); + mRecoData.createTracksVariadic(creator); int nDCAFits{0}, nDCAFitsFail{0}; auto& stream = (*mDBGOut) << "dcaRefit"; for (size_t iv = 0; iv < nv; ++iv) { const auto& pv = pvvec[iv]; const auto& trkIDs = v2t[iv]; - if (trkIDs.size() - 1 < conf.minPVCont) { + if (trkIDs.size() - 1 < mParams->minPVCont) { continue; } std::vector trks; trks.reserve(trkIDs.size()); for (const auto& trkID : trkIDs) { - trks.push_back(recoData.getTrackParam(trkID)); + trks.push_back(mRecoData.getTrackParam(trkID)); } if (!mVertexer.prepareVertexRefit(trks, pv)) { @@ -585,14 +632,14 @@ void TrackingStudySpec::doDCARefitStudy(o2::globaltracking::RecoContainer& recoD // check DCA both for refitted and original PV o2::dataformats::DCA dcaInfo; auto trkC = trks[it]; - if (!prop->propagateToDCABxByBz(pv, trkC, 2.f, conf.CorrType, &dcaInfo)) { + if (!prop->propagateToDCABxByBz(pv, trkC, 2.f, mParams->CorrType, &dcaInfo)) { mTrackCounter -= trkIDs[it].getSource(); ++nDCAFitsFail; continue; } o2::dataformats::DCA dcaInfoRefit; auto trkCRefit = trks[it]; - if (!prop->propagateToDCABxByBz(pv, trkCRefit, 2.f, conf.CorrType, &dcaInfoRefit)) { + if (!prop->propagateToDCABxByBz(pv, trkCRefit, 2.f, mParams->CorrType, &dcaInfoRefit)) { mTrackCounter -= trkIDs[it].getSource(); ++nDCAFitsFail; continue; @@ -606,7 +653,7 @@ void TrackingStudySpec::doDCARefitStudy(o2::globaltracking::RecoContainer& recoD << "trkAtPVRefit=" << trkC << "dcaRefit=" << dcaInfoRefit; if (mUseMC) { - const auto& mcTrk = mcReader.getTrack(recoData.getTrackMCLabel(trkIDs[it])); + const auto& mcTrk = mMCReader.getTrack(mRecoData.getTrackMCLabel(trkIDs[it])); if (mcTrk == nullptr) { LOGP(fatal, "mcTrk is null did selection fail?"); } @@ -622,7 +669,7 @@ void TrackingStudySpec::doDCARefitStudy(o2::globaltracking::RecoContainer& recoD mTrackCounter.print(); } -void TrackingStudySpec::doPullStudy(o2::globaltracking::RecoContainer& recoData) +void TrackingStudySpec::doPullStudy() { // check track pulls compared to mc generation LOGP(info, "Doing Pull study"); @@ -631,21 +678,20 @@ void TrackingStudySpec::doPullStudy(o2::globaltracking::RecoContainer& recoData) sw.Start(); int nPulls{0}, nPullsFail{0}; auto prop = o2::base::Propagator::Instance(); - const auto& conf = ITS3TrackingStudyParam::Instance(); auto checkInTrack = [&](GTrackID trkID) { - if (!selectTrack(trkID, recoData)) { + if (!selectTrack(trkID)) { mTrackCounter &= trkID.getSource(); return; } - const auto mcTrk = mcReader.getTrack(recoData.getTrackMCLabel(trkID)); + const auto mcTrk = mMCReader.getTrack(mRecoData.getTrackMCLabel(trkID)); if (!mcTrk) { return; } - auto trk = recoData.getTrackParam(trkID); + auto trk = mRecoData.getTrackParam(trkID); // for ITS standalone tracks we add the PV as an additional measurement point - if (conf.refitITS && trkID.getSource() == GTrackID::ITS && !refitITSPVTrack(recoData, trk, trkID)) { + if (mParams->refitITS && trkID.getSource() == GTrackID::ITS && !refitITSPVTrack(trk, trkID)) { mTrackCounter -= trkID.getSource(); ++nPullsFail; return; @@ -666,8 +712,8 @@ void TrackingStudySpec::doPullStudy(o2::globaltracking::RecoContainer& recoData) ++nPullsFail; return; } - const auto contTrk = recoData.getSingleDetectorRefs(trkID); - const auto& itsTrk = recoData.getITSTrack(contTrk[GTrackID::ITS]); + const auto contTrk = mRecoData.getSingleDetectorRefs(trkID); + const auto& itsTrk = mRecoData.getITSTrack(contTrk[GTrackID::ITS]); (*mDBGOut) << "pull" @@ -681,19 +727,19 @@ void TrackingStudySpec::doPullStudy(o2::globaltracking::RecoContainer& recoData) mTrackCounter += trkID.getSource(); }; - for (size_t iTrk{0}; iTrk < recoData.getITSTracks().size(); ++iTrk) { + for (size_t iTrk{0}; iTrk < mRecoData.getITSTracks().size(); ++iTrk) { checkInTrack(GTrackID(iTrk, GTrackID::ITS)); } - for (size_t iTrk{0}; iTrk < recoData.getTPCITSTracks().size(); ++iTrk) { + for (size_t iTrk{0}; iTrk < mRecoData.getTPCITSTracks().size(); ++iTrk) { checkInTrack(GTrackID(iTrk, GTrackID::ITSTPC)); } - for (size_t iTrk{0}; iTrk < recoData.getITSTPCTRDTracksMCLabels().size(); ++iTrk) { + for (size_t iTrk{0}; iTrk < mRecoData.getITSTPCTRDTracksMCLabels().size(); ++iTrk) { checkInTrack(GTrackID(iTrk, GTrackID::ITSTPCTRD)); } - for (size_t iTrk{0}; iTrk < recoData.getITSTPCTOFMatches().size(); ++iTrk) { + for (size_t iTrk{0}; iTrk < mRecoData.getITSTPCTOFMatches().size(); ++iTrk) { checkInTrack(GTrackID(iTrk, GTrackID::ITSTPCTOF)); } - for (size_t iTrk{0}; iTrk < recoData.getITSTPCTRDTOFMatches().size(); ++iTrk) { + for (size_t iTrk{0}; iTrk < mRecoData.getITSTPCTRDTOFMatches().size(); ++iTrk) { checkInTrack(GTrackID(iTrk, GTrackID::ITSTPCTRDTOF)); } sw.Stop(); @@ -701,7 +747,7 @@ void TrackingStudySpec::doPullStudy(o2::globaltracking::RecoContainer& recoData) mTrackCounter.print(); } -void TrackingStudySpec::doMCStudy(o2::globaltracking::RecoContainer& recoData) +void TrackingStudySpec::doMCStudy() { LOGP(info, "Doing MC study"); mTrackCounter.reset(); @@ -710,12 +756,12 @@ void TrackingStudySpec::doMCStudy(o2::globaltracking::RecoContainer& recoData) int nTracks{0}; const int iSrc{0}; - const int nev = mcReader.getNEvents(iSrc); + const int nev = mMCReader.getNEvents(iSrc); std::unordered_map info; LOGP(info, "** Filling particle table ... "); for (int iEve{0}; iEve < nev; ++iEve) { - const auto& mcTrks = mcReader.getTracks(iSrc, iEve); + const auto& mcTrks = mMCReader.getTracks(iSrc, iEve); for (int iTrk{0}; iTrk < mcTrks.size(); ++iTrk) { const auto& mcTrk = mcTrks[iTrk]; const auto pdg = mcTrk.GetPdgCode(); @@ -732,8 +778,8 @@ void TrackingStudySpec::doMCStudy(o2::globaltracking::RecoContainer& recoData) } } LOGP(info, "** Creating particle/clusters correspondence ... "); - const auto& clusters = recoData.getITSClusters(); - const auto& clustersMCLCont = recoData.getITSClustersMCLabels(); + 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) { @@ -755,7 +801,7 @@ void TrackingStudySpec::doMCStudy(o2::globaltracking::RecoContainer& recoData) LOGP(info, "** Analysing tracks ... "); auto accountLbl = [&](const globaltracking::RecoContainer::GlobalIDSet& contributorsGID, DetID::ID det) { if (contributorsGID[det].isIndexSet()) { - const auto& lbl = recoData.getTrackMCLabel(contributorsGID[det]); + const auto& lbl = mRecoData.getTrackMCLabel(contributorsGID[det]); if (lbl.isValid()) { o2::MCCompLabel iLbl(lbl.getTrackID(), lbl.getEventID(), lbl.getSourceID()); if (info.contains(iLbl)) { @@ -776,11 +822,11 @@ void TrackingStudySpec::doMCStudy(o2::globaltracking::RecoContainer& recoData) return false; } // general - auto contributorsGID = recoData.getSingleDetectorRefs(trkID); + auto contributorsGID = mRecoData.getSingleDetectorRefs(trkID); if (!contributorsGID[GTrackID::ITS].isIndexSet()) { // we need of course ITS return false; } - const auto& gLbl = recoData.getTrackMCLabel(trkID); + const auto& gLbl = mRecoData.getTrackMCLabel(trkID); if (!gLbl.isValid()) { return false; } @@ -789,7 +835,7 @@ void TrackingStudySpec::doMCStudy(o2::globaltracking::RecoContainer& recoData) return false; } auto& part = info[iLbl]; - part.recoTrack = recoData.getTrackParam(trkID); + part.recoTrack = mRecoData.getTrackParam(trkID); accountLbl(contributorsGID, DetID::ITS); accountLbl(contributorsGID, DetID::TPC); @@ -799,7 +845,7 @@ void TrackingStudySpec::doMCStudy(o2::globaltracking::RecoContainer& recoData) ++nTracks; return true; }; - recoData.createTracksVariadic(creator); + mRecoData.createTracksVariadic(creator); LOGP(info, "Streaming output to tree"); for (const auto& [_, part] : info) { @@ -812,7 +858,304 @@ void TrackingStudySpec::doMCStudy(o2::globaltracking::RecoContainer& recoData) LOGP(info, "doMCStudy: accounted {} MCParticles and {} tracks (in {:.2f} seconds)", info.size(), nTracks, sw.RealTime()); } -DataProcessorSpec getTrackingStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC) +void TrackingStudySpec::doResidStudy() +{ + LOGP(info, "Doing residual study"); + const auto geom = o2::its::GeometryTGeo::Instance(); + const auto prop = o2::base::Propagator::Instance(); + const float bz = prop->getNominalBz(); + + int goodRefit{0}, notPassedSel{0}, fitFail{0}; + + auto doRefits = [&](const o2::its::TrackITS& iTrack, const o2::MCCompLabel& lbl) { + std::array cl; + std::array clArr{nullptr}; + if (mParams->addPVAsCluster) { + const auto& eve = mMCReader.getMCEventHeader(lbl.getSourceID(), lbl.getEventID()); + dataformats::VertexBase pv; + auto trFitOut = iTrack.getParamIn(); + pv.setXYZ(eve.GetX(), eve.GetY(), eve.GetZ()); + if (!prop->propagateToDCA(pv, trFitOut, bz, base::Propagator::MAX_STEP, mParams->CorrType)) { + return; + } + pv.setSigmaX(20e-4f); + pv.setSigmaY(20e-4f); + pv.setSigmaZ(20e-4f); + float cosAlp = NAN, sinAlp = NAN; + o2::math_utils::sincos(trFitOut.getAlpha(), sinAlp, cosAlp); + cl[0].alpha = trFitOut.getAlpha(); + cl[0].setXYZ((pv.getX() * cosAlp) + (pv.getY() * sinAlp), (-pv.getX() * sinAlp) + (pv.getY() * cosAlp), pv.getZ()); + cl[0].setSigmaY2(0.5f * (pv.getSigmaX2() + pv.getSigmaY2())); + cl[0].setSigmaZ2(pv.getSigmaZ2()); + cl[0].setSensorID(-1); + clArr[0] = &cl[0]; + } + + // collect track clusters into layer slots + int nCl = iTrack.getNClusters(); + for (int i = 0; i < nCl; i++) { + const auto& curClu = mITScl[mITSclRef[iTrack.getClusterEntry(i)]]; + int sens = curClu.getSensorID(); + int llr = geom->getLayer(sens); + if (clArr[1 + llr]) { + LOGP(fatal, "Cluster at lr {} was already assigned, old sens {}, new sens {}", llr, clArr[1 + llr]->getSensorID(), sens); + } + clArr[1 + llr] = &curClu; + } + + std::array extrapOut, extrapInw; + float chi2{0}; + if (!align::doBidirRefit(iTrack, clArr, extrapOut, extrapInw, chi2, mParams->useStableRef, mParams->CorrType)) { + ++fitFail; + return; + } + + for (int i = 0; i <= 7; i++) { + if (clArr[i]) { + const auto tInt = align::interpolateTrackParCov(extrapInw[i], extrapOut[i]); + if (!tInt.isValid()) { + continue; + } + auto phi = i == 0 ? tInt.getPhi() : tInt.getPhiPos(); + o2::math_utils::bringTo02Pi(phi); + (*mDBGOut) << "res" + << "dYInt=" << clArr[i]->getY() - tInt.getY() + << "dZInt=" << clArr[i]->getZ() - tInt.getZ() + << "dYIn=" << clArr[i]->getY() - extrapInw[i].getY() + << "dZIn=" << clArr[i]->getZ() - extrapInw[i].getZ() + << "dYOut=" << clArr[i]->getY() - extrapOut[i].getY() + << "dZOut=" << clArr[i]->getZ() - extrapOut[i].getZ() + << "chi2=" << chi2 + << "clY=" << clArr[i]->getY() + << "clZ=" << clArr[i]->getZ() + << "clX=" << clArr[i]->getX() + << "alpha=" << clArr[i]->alpha + << "sens=" << clArr[i]->getSensorID() + << "phi=" << phi + << "pt=" << tInt.getPt() + << "chip=" << constants::detID::getSensorID(clArr[i]->getSensorID()) + << "lay=" << i - 1 + << "\n"; + } + } + ++goodRefit; + }; + + const auto itsTracks = mRecoData.getITSTracks(); + const auto itsMC = mRecoData.getITSTracksMCLabels(); + for (size_t iTrk{0}; iTrk < itsTracks.size(); ++iTrk) { + const auto& iTrack = itsTracks[iTrk]; + const auto& lbl = itsMC[iTrk]; + const auto& mc = mMCReader.getTrack(lbl); + if (std::abs(iTrack.getEta()) > mParams->maxEta || iTrack.getChi2() > mParams->maxChi2 || iTrack.getNClusters() < mParams->minITSCls || iTrack.getPt() < mParams->minPt || !lbl.isCorrect() || !mc->isPrimary()) { + ++notPassedSel; + continue; + } + doRefits(iTrack, lbl); + } + + LOGP(info, "\trefitted {} out of {} tracks ({} !sel, {} !fit)", goodRefit, itsTracks.size(), notPassedSel, fitFail); +} + +void TrackingStudySpec::doMisalignmentStudy() +{ + LOGP(info, "Doing misalignment study"); + const auto prop = o2::base::Propagator::Instance(); + const auto geom = o2::its::GeometryTGeo::Instance(); + + int goodRefit{0}, notPassedSel{0}, fitFail{0}, fitFailMis{0}; + + float chi2{0}; + auto writeTree = [&](const char* treeName, + const std::array& clArr, + const std::array& extrapOut, + const std::array& extrapInw, + const o2::MCCompLabel& lbl) { + for (int i = 0; i <= 7; i++) { + if (!clArr[i]) { + continue; + } + // interpolated result + auto tInt = align::interpolateTrackParCov(extrapInw[i], extrapOut[i]); + if (!tInt.isValid()) { + continue; + } + float dY = clArr[i]->getY() - tInt.getY(); + float dZ = clArr[i]->getZ() - tInt.getZ(); + // MC truth at same (alpha, x) + o2::track::TrackPar mcTrkAtX; + const auto mcTrk = mMCReader.getTrack(lbl); + if (mcTrk) { + std::array xyz{(float)mcTrk->GetStartVertexCoordinatesX(), (float)mcTrk->GetStartVertexCoordinatesY(), (float)mcTrk->GetStartVertexCoordinatesZ()}; + std::array pxyz{(float)mcTrk->GetStartVertexMomentumX(), (float)mcTrk->GetStartVertexMomentumY(), (float)mcTrk->GetStartVertexMomentumZ()}; + TParticlePDG* pPDG = TDatabasePDG::Instance()->GetParticle(mcTrk->GetPdgCode()); + if (pPDG) { + mcTrkAtX = o2::track::TrackPar(xyz, pxyz, TMath::Nint(pPDG->Charge() / 3), false); + if (mcTrkAtX.rotate(tInt.getAlpha()) && prop->PropagateToXBxByBz(mcTrkAtX, tInt.getX())) { + auto phi = i == 0 ? tInt.getPhi() : tInt.getPhiPos(); + o2::math_utils::bringTo02Pi(phi); + (*mDBGOut) << treeName + << "trk=" << tInt + << "mcTrk=" << mcTrkAtX + << "chi2=" << chi2 + << "dY=" << dY + << "dZ=" << dZ + << "phi=" << phi + << "eta=" << tInt.getEta() + << "lay=" << i - 1 + << "\n"; + } + } + } + } + }; + + const auto itsTracks = mRecoData.getITSTracks(); + const auto itsMC = mRecoData.getITSTracksMCLabels(); + for (size_t iTrk{0}; iTrk < itsTracks.size(); ++iTrk) { + const auto& iTrack = itsTracks[iTrk]; + if (std::abs(iTrack.getEta()) > mParams->maxEta || iTrack.getChi2() > mParams->maxChi2 || iTrack.getNClusters() < mParams->minITSCls || iTrack.getPt() < mParams->minPt) { + ++notPassedSel; + continue; + } + const auto& lbl = itsMC[iTrk]; + if (!lbl.isCorrect() || !lbl.isValid()) { + ++notPassedSel; + continue; + } + const auto& mc = mMCReader.getTrack(lbl); + if (!mc->isPrimary()) { + ++notPassedSel; + continue; + } + + // ideal clusters + std::array cl; + std::array clArr{nullptr}; + if (mParams->addPVAsCluster) { + const auto& eve = mMCReader.getMCEventHeader(lbl.getSourceID(), lbl.getEventID()); + dataformats::VertexBase pv; + auto trFitOut = iTrack.getParamIn(); + pv.setXYZ(eve.GetX(), eve.GetY(), eve.GetZ()); + if (!prop->propagateToDCA(pv, trFitOut, prop->getNominalBz(), base::Propagator::MAX_STEP, mParams->CorrType)) { + return; + } + pv.setSigmaX(20e-4f); + pv.setSigmaY(20e-4f); + pv.setSigmaZ(20e-4f); + float cosAlp = NAN, sinAlp = NAN; + o2::math_utils::sincos(trFitOut.getAlpha(), sinAlp, cosAlp); + cl[0].alpha = trFitOut.getAlpha(); + cl[0].setXYZ((pv.getX() * cosAlp) + (pv.getY() * sinAlp), (-pv.getX() * sinAlp) + (pv.getY() * cosAlp), pv.getZ()); + cl[0].setSigmaY2(0.5f * (pv.getSigmaX2() + pv.getSigmaY2())); + cl[0].setSigmaZ2(pv.getSigmaZ2()); + cl[0].setSensorID(-1); + clArr[0] = &cl[0]; + } + + // collect track clusters into layer slots + int nCl = iTrack.getNClusters(); + for (int i = 0; i < nCl; i++) { + const auto& curClu = mITScl[mITSclRef[iTrack.getClusterEntry(i)]]; + int sens = curClu.getSensorID(); + int llr = geom->getLayer(sens); + if (clArr[1 + llr]) { + LOGP(fatal, "Cluster at lr {} was already assigned, old sens {}, new sens {}", llr, clArr[1 + llr]->getSensorID(), sens); + } + clArr[1 + llr] = &curClu; + } + std::array extrapOut, extrapInw; + chi2 = 0; + if (!align::doBidirRefit(iTrack, clArr, extrapOut, extrapInw, chi2, mParams->useStableRef, mParams->CorrType)) { + ++fitFail; + continue; + } + writeTree("idealRes", clArr, extrapOut, extrapInw, lbl); + + // Propagate MC truth to each cluster's (alpha, x) to get true track direction. + // The shared misalignment evaluators then provide the tracking-frame dy/dz shift. + const auto mcTrk = mMCReader.getTrack(lbl); + if (!mcTrk) { + continue; + } + std::array xyz{(float)mcTrk->GetStartVertexCoordinatesX(), (float)mcTrk->GetStartVertexCoordinatesY(), (float)mcTrk->GetStartVertexCoordinatesZ()}; + std::array pxyz{(float)mcTrk->GetStartVertexMomentumX(), (float)mcTrk->GetStartVertexMomentumY(), (float)mcTrk->GetStartVertexMomentumZ()}; + TParticlePDG* pPDG = TDatabasePDG::Instance()->GetParticle(mcTrk->GetPdgCode()); + if (!pPDG) { + continue; + } + o2::track::TrackPar mcPar(xyz, pxyz, TMath::Nint(pPDG->Charge() / 3), false); + + std::array misClArr; // shifted copies for up to 3 IT3 layers + std::array clArrMis{}; + for (int i = 0; i <= 7; i++) { + clArrMis[i] = clArr[i]; // PV and OB clusters stay the same + } + for (int iLay = 0; iLay < 3; ++iLay) { + if (!clArr[1 + iLay]) { + continue; + } + const auto& orig = *clArr[1 + iLay]; + const int sens = orig.getSensorID(); + if (!constants::detID::isDetITS3(sens)) { + continue; + } + const int sensorID = constants::detID::getSensorID(sens); + const int layerID = constants::detID::getDetID2Layer(sens); + const auto& sensorMis = mMisalignment[sensorID]; + + // propagate MC track to cluster's tracking frame to get true slopes + auto mcAtCl = mcPar; + if (!mcAtCl.rotate(orig.alpha) || !prop->PropagateToXBxByBz(mcAtCl, orig.getX())) { + clArrMis[1 + iLay] = nullptr; // can't compute slopes -> drop cluster + continue; + } + const align::MisalignmentFrame misFrame{ + .sensorID = sensorID, + .layerID = layerID, + .x = orig.getX(), + .alpha = orig.alpha, + .z = orig.getZ()}; + const auto slopes = align::computeTrackSlopes(mcAtCl.getSnp(), mcAtCl.getTgl()); + + align::MisalignmentShift totalShift; + if (sensorMis.hasLegendre) { + const auto shift = align::evaluateLegendreShift(sensorMis, misFrame, slopes); + if (!shift.accepted) { + clArrMis[1 + iLay] = nullptr; // shifted outside acceptance + continue; + } + totalShift += shift; + } + if (sensorMis.hasInextensional) { + totalShift += align::evaluateInextensionalShift(sensorMis, misFrame, slopes); + } + + // create shifted copy: keep x=r (nominal), shift y and z + misClArr[iLay] = orig; + misClArr[iLay].setY(orig.getY() + totalShift.dy); + misClArr[iLay].setZ(orig.getZ() + totalShift.dz); + misClArr[iLay].setSigmaY2(orig.getSigmaY2() + (mParams->misAlgExtCY[sensorID] * mParams->misAlgExtCY[sensorID])); + misClArr[iLay].setSigmaZ2(orig.getSigmaZ2() + (mParams->misAlgExtCZ[sensorID] * mParams->misAlgExtCZ[sensorID])); + clArrMis[1 + iLay] = &misClArr[iLay]; + } + + // refit with shifted clusters + chi2 = 0; + if (!align::doBidirRefit(iTrack, clArrMis, extrapOut, extrapInw, chi2, mParams->useStableRef, mParams->CorrType)) { + ++fitFailMis; + ++goodRefit; // ideal still succeeded + continue; + } + writeTree("misRes", clArrMis, extrapOut, extrapInw, lbl); + + ++goodRefit; + } + + LOGP(info, "\tdoMisalignmentStudy: refitted {} out of {} tracks ({} !sel, {} !fit, {} !fitMis)", goodRefit, itsTracks.size(), notPassedSel, fitFail, fitFailMis); +} + +DataProcessorSpec getTrackingStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, bool withPV) { std::vector outputs; auto dataRequest = std::make_shared(); @@ -820,7 +1163,9 @@ DataProcessorSpec getTrackingStudySpec(GTrackID::mask_t srcTracks, GTrackID::mas dataRequest->requestTracks(srcTracks, useMC); dataRequest->requestIT3Clusters(useMC); dataRequest->requestClusters(srcClusters, useMC); - dataRequest->requestPrimaryVertices(useMC); + if (withPV) { + dataRequest->requestPrimaryVertices(useMC); + } auto ggRequest = std::make_shared(false, // orbitResetTime true, // GRPECS=true true, // GRPLHCIF 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 e0a0aea1c368a..482ef2bb71e1d 100644 --- a/Detectors/Upgrades/ITS3/study/src/its3-tracking-study-workflow.cxx +++ b/Detectors/Upgrades/ITS3/study/src/its3-tracking-study-workflow.cxx @@ -39,6 +39,7 @@ void customize(std::vector& workflowOptions) {"disable-mc", o2::framework::VariantType::Bool, false, {"disable MC propagation"}}, {"track-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of track sources to use"}}, {"cluster-sources", VariantType::String, "ITS,TRD,TOF", {"comma-separated list of cluster sources to use"}}, + {"without-pv", VariantType::Bool, false, {"do not use the PV as an additional fit point"}}, {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings ..."}}}; o2::raw::HBFUtilsInitializer::addConfigOption(options); @@ -58,14 +59,17 @@ 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"); GID::mask_t srcTrc = allowedSourcesTrc & GID::getSourcesMask(configcontext.options().get("track-sources")); GID::mask_t srcCls = allowedSourcesClus & GID::getSourcesMask(configcontext.options().get("cluster-sources")); o2::globaltracking::InputHelper::addInputSpecs(configcontext, specs, srcCls, srcTrc, srcTrc, useMC); - o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, useMC); + if (usePV) { + o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, useMC); + } - specs.emplace_back(o2::its3::study::getTrackingStudySpec(srcTrc, srcCls, useMC)); + specs.emplace_back(o2::its3::study::getTrackingStudySpec(srcTrc, srcCls, useMC, usePV)); o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); diff --git a/Detectors/Upgrades/ITS3/workflow/src/ClustererSpec.cxx b/Detectors/Upgrades/ITS3/workflow/src/ClustererSpec.cxx index f0238b74a3502..73b5f4650d02d 100644 --- a/Detectors/Upgrades/ITS3/workflow/src/ClustererSpec.cxx +++ b/Detectors/Upgrades/ITS3/workflow/src/ClustererSpec.cxx @@ -27,7 +27,7 @@ #include "DataFormatsITSMFT/ROFRecord.h" #include "DataFormatsParameters/GRPObject.h" #include "ITSMFTReconstruction/DigitPixelReader.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "CommonConstants/LHCConstants.h" using namespace o2::framework; diff --git a/Detectors/Upgrades/ITS3/workflow/src/RecoWorkflow.cxx b/Detectors/Upgrades/ITS3/workflow/src/RecoWorkflow.cxx index 60fe4fabfe481..f27fda19fe00c 100644 --- a/Detectors/Upgrades/ITS3/workflow/src/RecoWorkflow.cxx +++ b/Detectors/Upgrades/ITS3/workflow/src/RecoWorkflow.cxx @@ -40,7 +40,7 @@ framework::WorkflowSpec getWorkflow(bool useMC, its::TrackingMode::Type trmode, } if (!disableRootOutput) { - specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC)); + specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC, false)); } if (trmode != its::TrackingMode::Off) { diff --git a/Detectors/Upgrades/ITS3/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ITS3/workflow/src/TrackerSpec.cxx index 0326c12f804e0..8db02d7227e7f 100644 --- a/Detectors/Upgrades/ITS3/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ITS3/workflow/src/TrackerSpec.cxx @@ -23,7 +23,7 @@ #include "DataFormatsITSMFT/PhysTrigger.h" #include "ITStracking/TrackingConfigParam.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "ITSBase/GeometryTGeo.h" #include "CommonDataFormat/IRFrame.h" @@ -46,7 +46,7 @@ TrackerDPL::TrackerDPL(std::shared_ptr gr, const bool overrBeamEst, o2::gpu::gpudatatypes::DeviceType dType) : mGGCCDBRequest(gr), mRecChain{o2::gpu::GPUReconstruction::CreateInstance(dType, true)}, - mITS3TrackingInterface{isMC, trgType, overrBeamEst} + mITS3TrackingInterface{isMC, false, trgType, overrBeamEst} { mITS3TrackingInterface.setTrackingMode(trMode); } diff --git a/Detectors/Vertexing/include/DetectorsVertexing/PVertexer.h b/Detectors/Vertexing/include/DetectorsVertexing/PVertexer.h index c06c2119b0cd1..db8bff7d52ebe 100644 --- a/Detectors/Vertexing/include/DetectorsVertexing/PVertexer.h +++ b/Detectors/Vertexing/include/DetectorsVertexing/PVertexer.h @@ -29,9 +29,10 @@ #include "ReconstructionDataFormats/PrimaryVertex.h" #include "DetectorsVertexing/PVertexerHelpers.h" #include "DetectorsVertexing/PVertexerParams.h" +#include "DetectorsBase/Propagator.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DataFormatsCalibration/MeanVertexObject.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "gsl/span" #include #include @@ -85,6 +86,7 @@ class PVertexer void setBunchFilling(const o2::BunchFilling& bf); void setBz(float bz) { mBz = bz; } + void setMatCorrType(o2::base::Propagator::MatCorrType type) { mMatCorr = type; } void setValidateWithIR(bool v) { mValidateWithIR = v; } bool getValidateWithIR() const { return mValidateWithIR; } void setTrackSources(GTrackID::mask_t s); @@ -187,6 +189,7 @@ class PVertexer std::vector mTimeZClusters; ///< set of time clusters float mITSROFrameLengthMUS = 0; ///< ITS readout time span in \mus float mBz = 0.; ///< mag.field at beam line + o2::base::Propagator::MatCorrType mMatCorr = o2::base::Propagator::MatCorrType::USEMatCorrLUT; ///< material correction for propagation float mDBScanDeltaT = 0.; ///< deltaT cut for DBScan check float mDBSMaxZ2InvCorePoint = 0; ///< inverse of max sigZ^2 of the track which can be core point in the DBScan bool mValidateWithIR = false; ///< require vertex validation with InteractionCandidates (if available) diff --git a/Detectors/Vertexing/include/DetectorsVertexing/SVertexer.h b/Detectors/Vertexing/include/DetectorsVertexing/SVertexer.h index b933363bb352d..4d783b0018fc3 100644 --- a/Detectors/Vertexing/include/DetectorsVertexing/SVertexer.h +++ b/Detectors/Vertexing/include/DetectorsVertexing/SVertexer.h @@ -35,7 +35,6 @@ #include #include #include "GPUO2InterfaceRefit.h" -#include "TPCFastTransform.h" #include "DataFormatsTPC/PIDResponse.h" namespace o2 @@ -46,7 +45,7 @@ class VDriftCorrFact; } namespace gpu { -class CorrectionMapsHelper; +class TPCFastTransformPOD; } namespace vertexing @@ -143,7 +142,7 @@ class SVertexer mMUS2TPCBin = 1.f / (nbc * o2::constants::lhc::LHCBunchSpacingMUS); } void setTPCVDrift(const o2::tpc::VDriftCorrFact& v); - void setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph); + void setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph); void setStrangenessTracker(o2::strangeness_tracking::StrangenessTracker* tracker) { mStrTracker = tracker; } o2::strangeness_tracking::StrangenessTracker* getStrangenessTracker() { return mStrTracker; } @@ -175,7 +174,7 @@ class SVertexer gsl::span mTPCTrackClusIdx; ///< input TPC track cluster indices span gsl::span mTPCRefitterShMap; ///< externally set TPC clusters sharing map gsl::span mTPCRefitterOccMap; ///< externally set TPC clusters occupancy map - o2::gpu::CorrectionMapsHelper* mTPCCorrMapsHelper = nullptr; + const o2::gpu::TPCFastTransformPOD* mTPCCorrMaps = nullptr; std::unique_ptr mTPCRefitter; ///< TPC refitter used for TPC tracks refit during the reconstruction o2::strangeness_tracking::StrangenessTracker* mStrTracker = nullptr; gsl::span mPVertices; diff --git a/Detectors/Vertexing/src/PVertexer.cxx b/Detectors/Vertexing/src/PVertexer.cxx index 10e504bba0772..c4b5dc5cfc14c 100644 --- a/Detectors/Vertexing/src/PVertexer.cxx +++ b/Detectors/Vertexing/src/PVertexer.cxx @@ -1218,7 +1218,7 @@ bool PVertexer::relateTrackToMeanVertex(o2::track::TrackParCov& trc, float vtxEr z = mMeanVertex.getZ(); } mMeanVertex.setMeanXYVertexAtZ(mMeanVertexSeed, z); - if (!o2::base::Propagator::Instance()->propagateToDCA(mMeanVertex, trc, mBz, 2.0f, o2::base::Propagator::MatCorrType::USEMatCorrLUT, &dca, nullptr, 0, mPVParams->dcaTolerance)) { + if (!o2::base::Propagator::Instance()->propagateToDCA(mMeanVertex, trc, mBz, 2.0f, mMatCorr, &dca, nullptr, 0, mPVParams->dcaTolerance)) { return false; } return dca.getY() * dca.getY() / (dca.getSigmaY2() + vtxErr2) < mPVParams->pullIniCut; @@ -1227,7 +1227,7 @@ bool PVertexer::relateTrackToMeanVertex(o2::track::TrackParCov& trc, float vtxEr //______________________________________________ bool PVertexer::relateTrackToVertex(o2::track::TrackParCov& trc, const o2d::VertexBase& vtxSeed) const { - return o2::base::Propagator::Instance()->propagateToDCA(vtxSeed, trc, mBz, 2.0f, o2::base::Propagator::MatCorrType::USEMatCorrLUT); + return o2::base::Propagator::Instance()->propagateToDCA(vtxSeed, trc, mBz, 2.0f, mMatCorr); } //______________________________________________ diff --git a/Detectors/Vertexing/src/SVertexer.cxx b/Detectors/Vertexing/src/SVertexer.cxx index 2c625c9cfaf0a..bf7d436ca150c 100644 --- a/Detectors/Vertexing/src/SVertexer.cxx +++ b/Detectors/Vertexing/src/SVertexer.cxx @@ -18,7 +18,7 @@ #include "TPCReconstruction/TPCFastTransformHelperO2.h" #include "DataFormatsTPC/WorkflowHelper.h" #include "DataFormatsTPC/VDriftCorrFact.h" -#include "CorrectionMapsHelper.h" +#include "TPCFastTransformPOD.h" #include "Framework/ProcessingContext.h" #include "Framework/DataProcessorSpec.h" #include "ReconstructionDataFormats/StrangeTrack.h" @@ -331,9 +331,9 @@ void SVertexer::setTPCVDrift(const o2::tpc::VDriftCorrFact& v) mTPCBin2Z = mTPCVDrift / mMUS2TPCBin; } //______________________________________________ -void SVertexer::setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph) +void SVertexer::setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD* maph) { - mTPCCorrMapsHelper = maph; + mTPCCorrMaps = maph; } //__________________________________________________________________ @@ -458,7 +458,7 @@ void SVertexer::buildT2V(const o2::globaltracking::RecoContainer& recoData) // a mTPCClusterIdxStruct = &recoData.inputsTPCclusters->clusterIndex; mTPCRefitterShMap = recoData.clusterShMapTPC; mTPCRefitterOccMap = mRecoCont->occupancyMapTPC; - mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, mTPCCorrMapsHelper, o2::base::Propagator::Instance()->getNominalBz(), mTPCTrackClusIdx.data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size(), nullptr, o2::base::Propagator::Instance()); + mTPCRefitter = std::make_unique(mTPCClusterIdxStruct, mTPCCorrMaps, o2::base::Propagator::Instance()->getNominalBz(), mTPCTrackClusIdx.data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size(), nullptr, o2::base::Propagator::Instance()); } std::unordered_map> tmap; @@ -1360,7 +1360,7 @@ float SVertexer::correctTPCTrack(SVertexer::TrackCand& trc, const o2::tpc::Track uint8_t sector, row; auto cl = &tTPC.getCluster(mTPCTrackClusIdx, tTPC.getNClusters() - 1, *mTPCClusterIdxStruct, sector, row); float x = 0, y = 0, z = 0; - mTPCCorrMapsHelper->Transform(sector, row, cl->getPad(), cl->getTime(), x, y, z, tTB); + mTPCCorrMaps->Transform(sector, row, cl->getPad(), cl->getTime(), x, y, z, tTB); if (x < o2::constants::geom::XTPCInnerRef) { x = o2::constants::geom::XTPCInnerRef; } diff --git a/Detectors/Vertexing/src/VertexTrackMatcher.cxx b/Detectors/Vertexing/src/VertexTrackMatcher.cxx index 8612187c0bffc..f66d2b8c4d347 100644 --- a/Detectors/Vertexing/src/VertexTrackMatcher.cxx +++ b/Detectors/Vertexing/src/VertexTrackMatcher.cxx @@ -15,7 +15,7 @@ #include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" #include "DetectorsVertexing/VertexTrackMatcher.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include #include diff --git a/Detectors/Vertexing/test/PVFromPool.C b/Detectors/Vertexing/test/PVFromPool.C index 7bca9c03bf42f..248cbda401eca 100644 --- a/Detectors/Vertexing/test/PVFromPool.C +++ b/Detectors/Vertexing/test/PVFromPool.C @@ -1,3 +1,14 @@ +// Copyright 2019-2026 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. + #if !defined(__CLING__) || defined(__ROOTCLING__) #include "DetectorsVertexing/PVertexer.h" @@ -11,7 +22,7 @@ #include "DataFormatsParameters/GRPECSObject.h" #include "DataFormatsParameters/GRPMagField.h" #include "DetectorsBase/Propagator.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "CCDB/BasicCCDBManager.h" #include diff --git a/Detectors/gconfig/src/FlukaConfig.cxx b/Detectors/gconfig/src/FlukaConfig.cxx index 4723d98244ca9..fe5f73bc7026c 100644 --- a/Detectors/gconfig/src/FlukaConfig.cxx +++ b/Detectors/gconfig/src/FlukaConfig.cxx @@ -42,6 +42,7 @@ void linkFlukaFiles() gSystem->Exec("ln -s $FLUKADATA/random.dat ."); gSystem->Exec("ln -s $FLUKADATA/dnr.dat ."); gSystem->Exec("ln -s $FLUKADATA/nunstab.data ."); + gSystem->Exec("ln -s $FLUKADATA/neutron ."); // Give some meaningfull name to the output gSystem->Exec("ln -s fluka.out fort.11"); gSystem->Exec("ln -s fluka.err fort.15"); diff --git a/EventVisualisation/Workflow/src/EveWorkflowHelper.cxx b/EventVisualisation/Workflow/src/EveWorkflowHelper.cxx index 2bb3c220d67a0..b4f7655648001 100644 --- a/EventVisualisation/Workflow/src/EveWorkflowHelper.cxx +++ b/EventVisualisation/Workflow/src/EveWorkflowHelper.cxx @@ -32,7 +32,7 @@ #include "MCHTracking/TrackParam.h" #include "MCHTracking/TrackExtrap.h" #include "DataFormatsITSMFT/TrkClusRef.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "CommonDataFormat/IRFrame.h" #include "MFTBase/GeometryTGeo.h" #include "ITSBase/GeometryTGeo.h" diff --git a/EventVisualisation/Workflow/src/O2DPLDisplay.cxx b/EventVisualisation/Workflow/src/O2DPLDisplay.cxx index bd8ab5a664d99..946602d8c4802 100644 --- a/EventVisualisation/Workflow/src/O2DPLDisplay.cxx +++ b/EventVisualisation/Workflow/src/O2DPLDisplay.cxx @@ -25,7 +25,6 @@ #include "CommonUtils/NameConf.h" #include "TRDBase/GeometryFlat.h" #include "TOFBase/Geo.h" -#include "TPCFastTransform.h" #include "TRDBase/Geometry.h" #include "EMCALCalib/CellRecalibrator.h" #include "EMCALWorkflow/CalibLoader.h" @@ -37,6 +36,7 @@ #include "DataFormatsMCH/ROFRecord.h" #include #include "DataFormatsMCH/Cluster.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include using std::chrono::duration_cast; @@ -78,7 +78,7 @@ void customize(std::vector& workflowOptions) {"configKeyValues", VariantType::String, "", {"semicolon separated key=value strings, e.g. EveConfParam content..."}}, {"skipOnEmptyInput", VariantType::Bool, false, {"don't run the ED when no input is provided"}}, }; - + o2::itsmft::DPLAlpideParamInitializer::addConfigOption(options); o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } diff --git a/Framework/AODMerger/src/aodMerger.cxx b/Framework/AODMerger/src/aodMerger.cxx index 3ea45e84a39e0..11dae2dc5eac4 100644 --- a/Framework/AODMerger/src/aodMerger.cxx +++ b/Framework/AODMerger/src/aodMerger.cxx @@ -38,6 +38,7 @@ int main(int argc, char* argv[]) long maxDirSize = 100000000; bool skipNonExistingFiles = false; bool skipParentFilesList = false; + bool mergeByName = false; int verbosity = 2; int exitCode = 0; // 0: success, >0: failure int compression = 505; @@ -50,6 +51,7 @@ int main(int argc, char* argv[]) {"skip-non-existing-files", no_argument, nullptr, 3}, {"skip-parent-files-list", no_argument, nullptr, 4}, {"compression", required_argument, nullptr, 5}, + {"merge-by-name", no_argument, nullptr, 6}, {"verbosity", required_argument, nullptr, 'v'}, {"help", no_argument, nullptr, 'h'}, {nullptr, 0, nullptr, 0}}; @@ -70,6 +72,8 @@ int main(int argc, char* argv[]) skipParentFilesList = true; } else if (c == 5) { compression = atoi(optarg); + } else if (c == 6) { + mergeByName = true; } else if (c == 'v') { verbosity = atoi(optarg); } else if (c == 'h') { @@ -80,6 +84,7 @@ int main(int argc, char* argv[]) printf(" --skip-non-existing-files Flag to allow skipping of non-existing files in the input list.\n"); printf(" --skip-parent-files-list Flag to allow skipping the merging of the parent files list.\n"); printf(" --compression Compression algorithm / level to use (default: %d)\n", compression); + printf(" --merge-by-name Only merge TTrees from folders with the same name.\n"); printf(" --verbosity Verbosity of output (default: %d).\n", verbosity); return -1; } else { @@ -94,6 +99,9 @@ int main(int argc, char* argv[]) if (skipNonExistingFiles) { printf(" WARNING: Skipping non-existing files.\n"); } + if (mergeByName) { + printf(" Merging only folders with the same name.\n"); + } std::map trees; std::map sizeCompressed; @@ -112,6 +120,28 @@ int main(int argc, char* argv[]) TMap* parentFiles = nullptr; int totalMergedDFs = 0; int mergedDFs = 0; + + // Write all accumulated trees to outputDir, update stats, and clean up state. + auto flushTrees = [&](bool resetState) { + if (!outputDir) { + return; + } + for (auto const& tree : trees) { + outputDir->cd(); + tree.second->Write(); + sizeCompressed[tree.first] += tree.second->GetZipBytes(); + sizeUncompressed[tree.first] += tree.second->GetTotBytes(); + delete tree.second; + } + if (resetState) { + outputDir = nullptr; + trees.clear(); + offsets.clear(); + mergedDFs = 0; + currentDirSize = 0; + } + }; + while (in.good() && exitCode == 0) { in >> line; @@ -182,6 +212,14 @@ int main(int argc, char* argv[]) auto dfName = ((TObjString*)key1)->GetString().Data(); + // If merge-by-name is active, flush accumulated trees when the folder name changes + if (mergeByName && outputDir && std::string(outputDir->GetName()) != std::string(dfName)) { + if (verbosity > 0) { + printf("Folder name changed: closing folder %s.\n", outputDir->GetName()); + } + flushTrees(true); + } + if (verbosity > 0) { printf(" Processing folder %s\n", dfName); } @@ -396,21 +434,7 @@ int main(int argc, char* argv[]) if (verbosity > 0) { printf("Maximum size reached: %ld. Closing folder %s.\n", currentDirSize, dfName); } - for (auto const& tree : trees) { - // printf("Writing %s\n", tree.first.c_str()); - outputDir->cd(); - tree.second->Write(); - - // stats - sizeCompressed[tree.first] += tree.second->GetZipBytes(); - sizeUncompressed[tree.first] += tree.second->GetTotBytes(); - - delete tree.second; - } - outputDir = nullptr; - trees.clear(); - offsets.clear(); - mergedDFs = 0; + flushTrees(true); } } inputFile->Close(); @@ -421,16 +445,7 @@ int main(int argc, char* argv[]) parentFiles->Write("parentFiles", TObject::kSingleKey); } - for (auto const& tree : trees) { - outputDir->cd(); - tree.second->Write(); - - // stats - sizeCompressed[tree.first] += tree.second->GetZipBytes(); - sizeUncompressed[tree.first] += tree.second->GetTotBytes(); - - delete tree.second; - } + flushTrees(false); outputFile->Write(); outputFile->Close(); @@ -462,4 +477,4 @@ int main(int argc, char* argv[]) printf("\n"); return exitCode; -} +} \ No newline at end of file diff --git a/Framework/AnalysisSupport/CMakeLists.txt b/Framework/AnalysisSupport/CMakeLists.txt index 6024134a5495d..956c4a44c5684 100644 --- a/Framework/AnalysisSupport/CMakeLists.txt +++ b/Framework/AnalysisSupport/CMakeLists.txt @@ -47,6 +47,15 @@ o2_add_test(DataInputDirector NAME test_Framework_test_DataInputDirector LABELS framework PUBLIC_LINK_LIBRARIES O2::FrameworkAnalysisSupport) +add_executable(o2-test-framework-analysis-support + test/test_NavigateToLevel.cxx) +target_link_libraries(o2-test-framework-analysis-support PRIVATE O2::FrameworkAnalysisSupport O2::Catch2) + +get_filename_component(outdir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../tests ABSOLUTE) +set_property(TARGET o2-test-framework-analysis-support PROPERTY RUNTIME_OUTPUT_DIRECTORY ${outdir}) + +add_test(NAME framework:analysis-support COMMAND o2-test-framework-analysis-support) + o2_add_test(TableToTree NAME benchmark_TableToTree SOURCES test/benchmark_TableToTree.cxx COMPONENT_NAME Framework diff --git a/Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx b/Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx index 57a397822d167..8fde9f52a0e09 100644 --- a/Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx +++ b/Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx @@ -10,7 +10,10 @@ // or submit itself to any jurisdiction. #include "AODJAlienReaderHelpers.h" +#include #include +#include +#include #include "Framework/TableTreeHelpers.h" #include "Framework/AnalysisHelpers.h" #include "Framework/DataProcessingStats.h" @@ -111,10 +114,31 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const if (ctx.options().isSet("aod-parent-access-level")) { parentAccessLevel = ctx.options().get("aod-parent-access-level"); } - auto callback = AlgorithmSpec{adaptStateful([parentFileReplacement, parentAccessLevel](ConfigParamRegistry const& options, - DeviceSpec const& spec, - Monitoring& monitoring, - DataProcessingStats& stats) { + std::vector> originLevelMapping; + if (ctx.options().isSet("aod-origin-level-mapping")) { + auto originLevelMappingStr = ctx.options().get("aod-origin-level-mapping"); + for (auto pairRange : originLevelMappingStr | std::views::split(',')) { + std::string_view pair{pairRange.begin(), pairRange.end()}; + auto colonPos = pair.find(':'); + if (colonPos == std::string_view::npos) { + LOGP(fatal, "Badly formatted aod-origin-level-mapping entry: \"{}\"", pair); + continue; + } + std::string key(pair.substr(0, colonPos)); + std::string_view valueStr = pair.substr(colonPos + 1); + int value{}; + auto [ptr, ec] = std::from_chars(valueStr.data(), valueStr.data() + valueStr.size(), value); + if (ec == std::errc{}) { + originLevelMapping.emplace_back(std::move(key), value); + } else { + LOGP(fatal, "Unable to parse level in aod-origin-level-mapping entry: \"{}\"", pair); + } + } + } + auto callback = AlgorithmSpec{adaptStateful([parentFileReplacement, parentAccessLevel, originLevelMapping](ConfigParamRegistry const& options, + DeviceSpec const& spec, + Monitoring& monitoring, + DataProcessingStats& stats) { // FIXME: not actually needed, since data processing stats can specify that we should // send the initial value. stats.updateStats({static_cast(ProcessingStatsId::ARROW_BYTES_CREATED), DataProcessingStats::Op::Set, 0}); @@ -134,7 +158,7 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const auto maxRate = options.get("aod-max-io-rate"); // create a DataInputDirector - auto didir = std::make_shared(std::vector{filename}, DataInputDirectorContext{&monitoring, parentAccessLevel, parentFileReplacement}); + auto didir = std::make_shared(std::vector{filename}, DataInputDirectorContext{&monitoring, parentAccessLevel, parentFileReplacement, originLevelMapping}); if (options.isSet("aod-reader-json")) { auto jsonFile = options.get("aod-reader-json"); if (!didir->readJson(jsonFile)) { diff --git a/Framework/AnalysisSupport/src/AODWriterHelpers.cxx b/Framework/AnalysisSupport/src/AODWriterHelpers.cxx index b76ffca13977e..5b5829d96a1de 100644 --- a/Framework/AnalysisSupport/src/AODWriterHelpers.cxx +++ b/Framework/AnalysisSupport/src/AODWriterHelpers.cxx @@ -21,7 +21,6 @@ #include "Framework/TableConsumer.h" #include "Framework/DataOutputDirector.h" #include "Framework/TableTreeHelpers.h" -#include "Framework/Monitoring.h" #include "Framework/Signpost.h" #include @@ -32,8 +31,6 @@ #include #include #include -#include -#include O2_DECLARE_DYNAMIC_LOG(histogram_registry); diff --git a/Framework/AnalysisSupport/src/DataInputDirector.cxx b/Framework/AnalysisSupport/src/DataInputDirector.cxx index 7027655b7abe7..46674f19400a6 100644 --- a/Framework/AnalysisSupport/src/DataInputDirector.cxx +++ b/Framework/AnalysisSupport/src/DataInputDirector.cxx @@ -122,7 +122,7 @@ void DataInputDescriptor::addFileNameHolder(FileNameHolder* fn) mfilenames.emplace_back(fn); } -bool DataInputDescriptor::setFile(int counter, std::string_view origin) +bool DataInputDescriptor::setFile(int counter, int wantedParentLevel, std::string_view origin) { // no files left if (counter >= getNumberInputfiles()) { @@ -133,7 +133,9 @@ bool DataInputDescriptor::setFile(int counter, std::string_view origin) // of the filename. In the future we might expand this for proper rewriting of the // filename based on the origin and the original file information. std::string filename = mfilenames[counter]->fileName; - if (!origin.starts_with("AOD")) { + // In case we do not need to remap parent levels, the requested origin is what + // drives the filename. + if (wantedParentLevel == -1 && !origin.starts_with("AOD")) { filename = std::regex_replace(filename, std::regex("[.]root$"), fmt::format("_{}.root", origin)); } @@ -146,7 +148,19 @@ bool DataInputDescriptor::setFile(int counter, std::string_view origin) closeInputFile(); } - mCurrentFilesystem = std::make_shared(TFile::Open(filename.c_str()), 50 * 1024 * 1024, mFactory); + TFile* tfile = nullptr; + bool externalFile = false; + for (auto& [name, f] : mContext.openFiles) { + if (name == filename) { + tfile = f; + externalFile = true; + break; + } + } + if (tfile == nullptr) { + tfile = TFile::Open(filename.c_str()); + } + mCurrentFilesystem = std::make_shared(tfile, 50 * 1024 * 1024, mFactory, !externalFile); if (!mCurrentFilesystem.get()) { throw std::runtime_error(fmt::format("Couldn't open file \"{}\"!", filename)); } @@ -218,11 +232,11 @@ bool DataInputDescriptor::setFile(int counter, std::string_view origin) return true; } -uint64_t DataInputDescriptor::getTimeFrameNumber(int counter, int numTF, std::string_view origin) +uint64_t DataInputDescriptor::getTimeFrameNumber(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin) { // open file - if (!setFile(counter, origin)) { + if (!setFile(counter, wantedParentLevel, wantedOrigin)) { return 0ul; } @@ -234,10 +248,32 @@ uint64_t DataInputDescriptor::getTimeFrameNumber(int counter, int numTF, std::st return (mfilenames[counter]->listOfTimeFrameNumbers)[numTF]; } -arrow::dataset::FileSource DataInputDescriptor::getFileFolder(int counter, int numTF, std::string_view origin) +std::pair DataInputDescriptor::navigateToLevel(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin) +{ + if (!setFile(counter, wantedParentLevel, wantedOrigin)) { + return {nullptr, -1}; + } + auto folderName = fmt::format("DF_{}", mfilenames[counter]->listOfTimeFrameNumbers[numTF]); + auto parentFile = getParentFile(counter, numTF, "", wantedParentLevel, wantedOrigin); + if (parentFile == nullptr) { + return {nullptr, -1}; + } + return {parentFile, parentFile->findDFNumber(0, folderName)}; +} + +arrow::dataset::FileSource DataInputDescriptor::getFileFolder(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin) { + // If mapped to a parent level deeper than current, skip directly to the right level. + if (wantedParentLevel != -1 && mLevel < wantedParentLevel) { + auto [parentFile, parentNumTF] = navigateToLevel(counter, numTF, wantedParentLevel, wantedOrigin); + if (parentFile == nullptr || parentNumTF == -1) { + return {}; + } + return parentFile->getFileFolder(0, parentNumTF, wantedParentLevel, wantedOrigin); + } + // open file - if (!setFile(counter, origin)) { + if (!setFile(counter, wantedParentLevel, wantedOrigin)) { return {}; } @@ -251,7 +287,7 @@ arrow::dataset::FileSource DataInputDescriptor::getFileFolder(int counter, int n return {fmt::format("DF_{}", mfilenames[counter]->listOfTimeFrameNumbers[numTF]), mCurrentFilesystem}; } -DataInputDescriptor* DataInputDescriptor::getParentFile(int counter, int numTF, std::string treename, std::string_view origin) +DataInputDescriptor* DataInputDescriptor::getParentFile(int counter, int numTF, std::string treename, int wantedParentLevel, std::string_view wantedOrigin) { if (!mParentFileMap) { // This file has no parent map @@ -288,7 +324,7 @@ DataInputDescriptor* DataInputDescriptor::getParentFile(int counter, int numTF, mParentFile->mdefaultFilenamesPtr = new std::vector; mParentFile->mdefaultFilenamesPtr->emplace_back(makeFileNameHolder(parentFileName->GetString().Data())); mParentFile->fillInputfiles(); - mParentFile->setFile(0, origin); + mParentFile->setFile(0, wantedParentLevel, wantedOrigin); return mParentFile; } @@ -450,8 +486,26 @@ struct CalculateDelta { bool DataInputDescriptor::readTree(DataAllocator& outputs, header::DataHeader dh, int counter, int numTF, std::string treename, size_t& totalSizeCompressed, size_t& totalSizeUncompressed) { CalculateDelta t(mIOTime); - std::string origin = dh.dataOrigin.as(); - auto folder = getFileFolder(counter, numTF, origin); + std::string wantedOrigin = dh.dataOrigin.as(); + int wantedLevel = mContext.levelForOrigin(wantedOrigin); + + // If this origin is mapped to a parent level deeper than current, skip directly without + // attempting to read from this level. + if (wantedLevel != -1 && mLevel < wantedLevel) { + auto [parentFile, parentNumTF] = navigateToLevel(counter, numTF, wantedLevel, wantedOrigin); + if (parentFile == nullptr) { + auto rootFS = std::dynamic_pointer_cast(mCurrentFilesystem); + throw std::runtime_error(fmt::format(R"(No parent file found for "{}" while looking for level {} in "{}")", treename, wantedLevel, rootFS->GetFile()->GetName())); + } + if (parentNumTF == -1) { + auto parentRootFS = std::dynamic_pointer_cast(parentFile->mCurrentFilesystem); + throw std::runtime_error(fmt::format(R"(DF not found in parent file "{}")", parentRootFS->GetFile()->GetName())); + } + t.deactivate(); + return parentFile->readTree(outputs, dh, 0, parentNumTF, treename, totalSizeCompressed, totalSizeUncompressed); + } + + auto folder = getFileFolder(counter, numTF, wantedLevel, wantedOrigin); if (!folder.filesystem()) { t.deactivate(); return false; @@ -484,7 +538,7 @@ bool DataInputDescriptor::readTree(DataAllocator& outputs, header::DataHeader dh if (!format) { t.deactivate(); LOGP(debug, "Could not find tree {}. Trying in parent file.", fullpath.path()); - auto parentFile = getParentFile(counter, numTF, treename, origin); + auto parentFile = getParentFile(counter, numTF, treename, wantedLevel, wantedOrigin); if (parentFile != nullptr) { int parentNumTF = parentFile->findDFNumber(0, folder.path()); if (parentNumTF == -1) { @@ -817,8 +871,9 @@ arrow::dataset::FileSource DataInputDirector::getFileFolder(header::DataHeader d didesc = mdefaultDataInputDescriptor; } std::string origin = dh.dataOrigin.as(); + int wantedLevel = mContext.levelForOrigin(origin); - return didesc->getFileFolder(counter, numTF, origin); + return didesc->getFileFolder(counter, numTF, wantedLevel, origin); } int DataInputDirector::getTimeFramesInFile(header::DataHeader dh, int counter) @@ -840,8 +895,9 @@ uint64_t DataInputDirector::getTimeFrameNumber(header::DataHeader dh, int counte didesc = mdefaultDataInputDescriptor; } std::string origin = dh.dataOrigin.as(); + int wantedLevel = mContext.levelForOrigin(origin); - return didesc->getTimeFrameNumber(counter, numTF, origin); + return didesc->getTimeFrameNumber(counter, numTF, wantedLevel, origin); } bool DataInputDirector::readTree(DataAllocator& outputs, header::DataHeader dh, int counter, int numTF, size_t& totalSizeCompressed, size_t& totalSizeUncompressed) diff --git a/Framework/AnalysisSupport/src/DataInputDirector.h b/Framework/AnalysisSupport/src/DataInputDirector.h index 2d63a1c71ea77..18ab5c0c1382e 100644 --- a/Framework/AnalysisSupport/src/DataInputDirector.h +++ b/Framework/AnalysisSupport/src/DataInputDirector.h @@ -21,6 +21,7 @@ #include #include +#include #include "rapidjson/fwd.h" namespace o2::monitoring @@ -44,6 +45,20 @@ struct DataInputDirectorContext { o2::monitoring::Monitoring* monitoring = nullptr; int allowedParentLevel = 0; std::string parentFileReplacement = ""; + std::vector> parentLevelToOrigin = {}; + // Optional registry of pre-opened TFiles (keyed by name) used to bypass + // TFile::Open for testing with in-memory TMemFile instances. + std::vector> openFiles = {}; + + int levelForOrigin(std::string_view origin) const + { + for (auto& [o, level] : parentLevelToOrigin) { + if (o == origin) { + return level; + } + } + return -1; + } }; class DataInputDescriptor @@ -71,7 +86,7 @@ class DataInputDescriptor void addFileNameHolder(FileNameHolder* fn); int fillInputfiles(); - bool setFile(int counter, std::string_view origin); + bool setFile(int counter, int wantedParentLevel, std::string_view wantedOrigin); // getters std::string getInputfilesFilename(); @@ -81,9 +96,12 @@ class DataInputDescriptor int getNumberTimeFrames() { return mtotalNumberTimeFrames; } int findDFNumber(int file, std::string dfName); - uint64_t getTimeFrameNumber(int counter, int numTF, std::string_view origin); - arrow::dataset::FileSource getFileFolder(int counter, int numTF, std::string_view origin); - DataInputDescriptor* getParentFile(int counter, int numTF, std::string treename, std::string_view origin); + uint64_t getTimeFrameNumber(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin); + arrow::dataset::FileSource getFileFolder(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin); + // Open the current file to populate the parent map, then return the parent descriptor and + // the TF index within it that corresponds to numTF at this level. Returns {nullptr, -1} on failure. + std::pair navigateToLevel(int counter, int numTF, int wantedParentLevel, std::string_view wantedOrigin); + DataInputDescriptor* getParentFile(int counter, int numTF, std::string treename, int wantedParentLevel, std::string_view wantedOrigin); int getTimeFramesInFile(int counter); int getReadTimeFramesInFile(int counter); diff --git a/Framework/AnalysisSupport/src/TTreePlugin.cxx b/Framework/AnalysisSupport/src/TTreePlugin.cxx index 4a51f4eac68f6..1a6f48ebef5b4 100644 --- a/Framework/AnalysisSupport/src/TTreePlugin.cxx +++ b/Framework/AnalysisSupport/src/TTreePlugin.cxx @@ -12,7 +12,7 @@ #include "Framework/RootArrowFilesystem.h" #include "Framework/Plugins.h" #include "Framework/Signpost.h" -#include "Framework/Endian.h" +#include "Framework/BigEndian.h" #include #include #include @@ -197,7 +197,7 @@ auto readValues = [](uint8_t* target, ReadOps& op, TBufferFile& rootBuffer) { } int size = readLast * op.listSize; readEntries += readLast; - swapCopy(target, rootBuffer.GetCurrent(), size, op.typeSize); + bigEndianCopy(target, rootBuffer.GetCurrent(), size, op.typeSize); target += (ptrdiff_t)(size * op.typeSize); } }; @@ -230,7 +230,7 @@ auto readVLAValues = [](uint8_t* target, ReadOps& op, ReadOps const& offsetOp, T auto readLast = op.branch->GetBulkRead().GetEntriesSerialized(readEntries, rootBuffer); int size = offsets[readEntries + readLast] - offsets[readEntries]; readEntries += readLast; - swapCopy(target, rootBuffer.GetCurrent(), size, op.typeSize); + bigEndianCopy(target, rootBuffer.GetCurrent(), size, op.typeSize); target += (ptrdiff_t)(size * op.typeSize); } }; @@ -581,7 +581,8 @@ auto readOffsets = [](ReadOps& op, TBufferFile& rootBuffer) { readEntries += readLast; for (auto i = 0; i < readLast; ++i) { offsets[count++] = (int)offset; - offset += swap32_(reinterpret_cast(rootBuffer.GetCurrent())[i]); + uint32_t raw = reinterpret_cast(rootBuffer.GetCurrent())[i]; + offset += (std::endian::native == std::endian::little) ? __builtin_bswap32(raw) : raw; } } offsets[count] = (int)offset; diff --git a/Framework/AnalysisSupport/src/TableTreeHelpers.cxx b/Framework/AnalysisSupport/src/TableTreeHelpers.cxx index 800a31e8ecac3..4cf3d3fce2266 100644 --- a/Framework/AnalysisSupport/src/TableTreeHelpers.cxx +++ b/Framework/AnalysisSupport/src/TableTreeHelpers.cxx @@ -10,7 +10,6 @@ // or submit itself to any jurisdiction. #include "Framework/TableTreeHelpers.h" #include "Framework/Logger.h" -#include "Framework/Endian.h" #include "Framework/Signpost.h" #include diff --git a/Framework/AnalysisSupport/test/test_NavigateToLevel.cxx b/Framework/AnalysisSupport/test/test_NavigateToLevel.cxx new file mode 100644 index 0000000000000..0072ee3b67d37 --- /dev/null +++ b/Framework/AnalysisSupport/test/test_NavigateToLevel.cxx @@ -0,0 +1,135 @@ +// 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. + +#include + +#include "../src/DataInputDirector.h" + +#include +#include +#include +#include + +using namespace o2::framework; + +// Tests for DataInputDirectorContext::levelForOrigin + +TEST_CASE("levelForOrigin empty mapping") +{ + DataInputDirectorContext ctx; + CHECK(ctx.levelForOrigin("AOD") == -1); + CHECK(ctx.levelForOrigin("DYN") == -1); +} + +TEST_CASE("levelForOrigin single entry") +{ + DataInputDirectorContext ctx; + ctx.parentLevelToOrigin = {{"DYN", 1}}; + CHECK(ctx.levelForOrigin("DYN") == 1); + CHECK(ctx.levelForOrigin("AOD") == -1); +} + +TEST_CASE("levelForOrigin multiple entries") +{ + DataInputDirectorContext ctx; + ctx.parentLevelToOrigin = {{"DYN", 1}, {"EMB", 2}, {"EXT", 1}}; + CHECK(ctx.levelForOrigin("DYN") == 1); + CHECK(ctx.levelForOrigin("EMB") == 2); + CHECK(ctx.levelForOrigin("EXT") == 1); + CHECK(ctx.levelForOrigin("AOD") == -1); + CHECK(ctx.levelForOrigin("") == -1); +} + +// Tests for DataInputDescriptor::navigateToLevel + +TEST_CASE("navigateToLevel returns null with no input files") +{ + // With no input files setFile fails immediately → {nullptr, -1} + DataInputDirectorContext ctx; + ctx.allowedParentLevel = 2; + DataInputDescriptor desc(false, 0, ctx); + + auto [parentFile, parentNumTF] = desc.navigateToLevel(0, 0, 1, "DYN"); + CHECK(parentFile == nullptr); + CHECK(parentNumTF == -1); +} + +// --------------------------------------------------------------------------- +// Helpers: build an AO2D-shaped TMemFile with one DF directory. +// The AO2D format uses top-level TDirectory entries named DF_. +// An optional "parentFiles" TMap maps each DF name to its parent file path. +// --------------------------------------------------------------------------- + +static TMemFile* makeAODFile(const char* name) +{ + auto* f = new TMemFile(name, "RECREATE"); + f->mkdir("DF_1"); + f->Write(); + return f; +} + +static TMemFile* makeAODFileWithParent(const char* name, const char* parentName) +{ + auto* f = new TMemFile(name, "RECREATE"); + f->mkdir("DF_1"); + auto* parentMap = new TMap(); + parentMap->Add(new TObjString("DF_1"), new TObjString(parentName)); + parentMap->Write("parentFiles", TObject::kSingleKey); + f->Write(); + return f; +} + +TEST_CASE("navigateToLevel finds parent TMemFile") +{ + // child.root DF_1 parentFiles: {DF_1 -> parent.root} + // parent.root DF_1 + auto* parentMF = makeAODFile("parent.root"); + auto* childMF = makeAODFileWithParent("child.root", "parent.root"); + + DataInputDirectorContext ctx; + ctx.allowedParentLevel = 2; + ctx.openFiles = {{"child.root", childMF}, {"parent.root", parentMF}}; + + DataInputDescriptor desc(false, 0, ctx); + desc.addFileNameHolder(makeFileNameHolder("child.root")); + + auto [parentDesc, parentNumTF] = desc.navigateToLevel(0, 0, 1, "AOD"); + + REQUIRE(parentDesc != nullptr); + // DF_1 is the only timeframe in the parent, so its index is 0 + CHECK(parentNumTF == 0); +} + +TEST_CASE("navigateToLevel returns -1 for missing DF in parent") +{ + // child has DF_2 but parent only has DF_1 — findDFNumber returns -1 + auto* parentMF = makeAODFile("parent2.root"); + + auto* childMF = new TMemFile("child2.root", "RECREATE"); + childMF->mkdir("DF_2"); + auto* parentMap = new TMap(); + parentMap->Add(new TObjString("DF_2"), new TObjString("parent2.root")); + parentMap->Write("parentFiles", TObject::kSingleKey); + childMF->Write(); + + DataInputDirectorContext ctx; + ctx.allowedParentLevel = 2; + ctx.openFiles = {{"child2.root", childMF}, {"parent2.root", parentMF}}; + + DataInputDescriptor desc(false, 0, ctx); + desc.addFileNameHolder(makeFileNameHolder("child2.root")); + + auto [parentDesc, parentNumTF] = desc.navigateToLevel(0, 0, 1, "AOD"); + + // Parent has DF_1 but child references DF_2 — not found in parent + REQUIRE(parentDesc != nullptr); + CHECK(parentNumTF == -1); +} diff --git a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx index 413adfddecf04..21fdae4a57760 100644 --- a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx +++ b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx @@ -11,6 +11,7 @@ #include "AnalysisCCDBHelpers.h" #include "CCDBFetcherHelper.h" +#include "Framework/DataProcessingStats.h" #include "Framework/DeviceSpec.h" #include "Framework/TimingInfo.h" #include "Framework/ConfigParamRegistry.h" @@ -20,7 +21,7 @@ #include "Framework/Signpost.h" #include "Framework/DanglingEdgesContext.h" #include "Framework/ConfigContext.h" -#include "Framework/ConfigContext.h" +#include "Framework/ConfigParamsHelper.h" #include #include #include @@ -47,8 +48,7 @@ namespace void fillValidRoutes(CCDBFetcherHelper& helper, std::vector const& outputRoutes, std::unordered_map& bindings) { for (auto& route : outputRoutes) { - auto originMatcher = DataSpecUtils::asConcreteDataMatcher(route.matcher); - if (originMatcher.origin != header::DataOrigin{"ATIM"}) { + if (std::ranges::none_of(route.matcher.metadata, [](auto const& m) { return m.name.starts_with("ccdb:"); })) { continue; } auto specStr = DataSpecUtils::describe(route.matcher); @@ -71,31 +71,45 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) { return adaptStateful([](ConfigParamRegistry const& options, DeviceSpec const& spec, InitContext& ic) { auto& dec = ic.services().get(); + // The effective default for each ccdb: option was already resolved at topology + // time by ArrowSupport (consulting task Configurables) and registered on this + // device's options. Here we just read the final value — honouring any further + // runtime override supplied via CLI or JSON config. + std::unordered_map ccdbUrls; + for (auto& input : dec.analysisCCDBInputs) { + for (auto& m : input.metadata) { + if (!m.name.starts_with("ccdb:") || ccdbUrls.count(m.name)) { + continue; + } + std::string url = m.defaultValue.asString(); + if (ConfigParamsHelper::hasOption(spec.options, m.name)) { + url = options.get(m.name.c_str()); + } + LOGP(info, "CCDB path resolved for {}: {}", m.name, url); + ccdbUrls.emplace(m.name, std::move(url)); + } + } std::vector> schemas; - auto schemaMetadata = std::make_shared(); - for (auto& input : dec.analysisCCDBInputs) { + auto schemaMetadata = std::make_shared(); std::vector> fields; schemaMetadata->Append("outputRoute", DataSpecUtils::describe(input)); schemaMetadata->Append("outputBinding", input.binding); - for (auto& m : input.metadata) { - // Save the list of input tables if (m.name.starts_with("input:")) { auto name = m.name.substr(6); schemaMetadata->Append("sourceTable", name); schemaMetadata->Append("sourceMatcher", DataSpecUtils::describe(std::get(DataSpecUtils::fromMetadataString(m.defaultValue.get()).matcher))); continue; } - // Ignore the non ccdb: entries if (!m.name.starts_with("ccdb:")) { continue; } - // Create the schema of the output - auto metadata = std::make_shared(); - metadata->Append("url", m.defaultValue.asString()); + auto fieldMetadata = std::make_shared(); + auto it = ccdbUrls.find(m.name); + fieldMetadata->Append("url", it != ccdbUrls.end() ? it->second : m.defaultValue.asString()); auto columnName = m.name.substr(strlen("ccdb:")); - fields.emplace_back(std::make_shared(columnName, arrow::binary_view(), false, metadata)); + fields.emplace_back(std::make_shared(columnName, arrow::binary_view(), false, fieldMetadata)); } schemas.emplace_back(std::make_shared(fields, schemaMetadata)); } @@ -105,7 +119,7 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) std::unordered_map bindings; fillValidRoutes(*helper, spec.outputs, bindings); - return adaptStateless([schemas, bindings, helper](InputRecord& inputs, DataTakingContext& dtc, DataAllocator& allocator, TimingInfo& timingInfo) { + return adaptStateless([schemas, bindings, helper](InputRecord& inputs, DataTakingContext& dtc, DataAllocator& allocator, TimingInfo& timingInfo, DataProcessingStats& stats) { O2_SIGNPOST_ID_GENERATE(sid, ccdb); O2_SIGNPOST_START(ccdb, sid, "fetchFromAnalysisCCDB", "Fetching CCDB objects for analysis%" PRIu64, (uint64_t)timingInfo.timeslice); for (auto& schema : schemas) { @@ -182,6 +196,8 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) allocator.adopt(Output{concrete.origin, concrete.description, concrete.subSpec}, outTable); } + stats.updateStats({(int)ProcessingStatsId::CCDB_CACHE_FETCHED_BYTES, DataProcessingStats::Op::Set, (int64_t)helper->totalFetchedBytes}); + stats.updateStats({(int)ProcessingStatsId::CCDB_CACHE_REQUESTED_BYTES, DataProcessingStats::Op::Set, (int64_t)helper->totalRequestedBytes}); O2_SIGNPOST_END(ccdb, sid, "fetchFromAnalysisCCDB", "Fetching CCDB objects"); }); }); diff --git a/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx b/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx index 151703105a1dd..8d50dac63a67b 100644 --- a/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx +++ b/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx @@ -254,6 +254,8 @@ auto CCDBFetcherHelper::populateCacheWith(std::shared_ptr con helper->mapURL2UUID[path].minSize = std::min(v.size(), helper->mapURL2UUID[path].minSize); helper->mapURL2UUID[path].maxSize = std::max(v.size(), helper->mapURL2UUID[path].maxSize); auto size = v.size(); + helper->totalFetchedBytes += size; + helper->totalRequestedBytes += size; api.appendFlatHeader(v, headers); auto cacheId = allocator.adoptContainer(output, std::move(v), DataAllocator::CacheStrategy::Always, header::gSerializationMethodCCDB); helper->mapURL2DPLCache[path] = cacheId; @@ -271,6 +273,8 @@ auto CCDBFetcherHelper::populateCacheWith(std::shared_ptr con helper->mapURL2UUID[path].minSize = std::min(v.size(), helper->mapURL2UUID[path].minSize); helper->mapURL2UUID[path].maxSize = std::max(v.size(), helper->mapURL2UUID[path].maxSize); auto size = v.size(); + helper->totalFetchedBytes += size; + helper->totalRequestedBytes += size; api.appendFlatHeader(v, headers); auto cacheId = allocator.adoptContainer(output, std::move(v), DataAllocator::CacheStrategy::Always, header::gSerializationMethodCCDB); helper->mapURL2DPLCache[path] = cacheId; diff --git a/Framework/CCDBSupport/src/CCDBFetcherHelper.h b/Framework/CCDBSupport/src/CCDBFetcherHelper.h index 1778712f45002..a6cb3f70215af 100644 --- a/Framework/CCDBSupport/src/CCDBFetcherHelper.h +++ b/Framework/CCDBSupport/src/CCDBFetcherHelper.h @@ -84,6 +84,8 @@ struct CCDBFetcherHelper { static ParserResult parseRemappings(char const*); + size_t totalFetchedBytes = 0; + size_t totalRequestedBytes = 0; std::unordered_map mapURL2UUID; std::unordered_map mapURL2DPLCache; std::string createdNotBefore = "0"; diff --git a/Framework/CCDBSupport/src/CCDBHelpers.cxx b/Framework/CCDBSupport/src/CCDBHelpers.cxx index 80a79796f8c1b..fd78594e365bf 100644 --- a/Framework/CCDBSupport/src/CCDBHelpers.cxx +++ b/Framework/CCDBSupport/src/CCDBHelpers.cxx @@ -11,6 +11,7 @@ #include "CCDBHelpers.h" #include "Framework/DeviceSpec.h" +#include "Framework/DataProcessingStats.h" #include "Framework/Logger.h" #include "Framework/TimingInfo.h" #include "Framework/ConfigParamRegistry.h" @@ -28,7 +29,8 @@ O2_DECLARE_DYNAMIC_LOG(ccdb); namespace o2::framework { -namespace { +namespace +{ struct CCDBFetcherHelper { struct CCDBCacheInfo { std::string etag; @@ -36,6 +38,7 @@ struct CCDBFetcherHelper { size_t cachePopulatedAt = 0; size_t cacheMiss = 0; size_t cacheHit = 0; + size_t size = 0; size_t minSize = -1ULL; size_t maxSize = 0; int lastCheckedTF = 0; @@ -50,6 +53,8 @@ struct CCDBFetcherHelper { std::string url; }; + size_t totalFetchedBytes = 0; + size_t totalRequestedBytes = 0; std::unordered_map mapURL2UUID; std::unordered_map mapURL2DPLCache; std::string createdNotBefore = "0"; @@ -80,7 +85,7 @@ struct CCDBFetcherHelper { return apis[entry == remappings.end() ? "" : entry->second]; } }; -} +} // namespace bool isPrefix(std::string_view prefix, std::string_view full) { @@ -336,8 +341,11 @@ auto populateCacheWith(std::shared_ptr const& helper, helper->mapURL2UUID[path].etag = headers["ETag"]; // update uuid helper->mapURL2UUID[path].cachePopulatedAt = timestampToUse; helper->mapURL2UUID[path].cacheMiss++; + helper->mapURL2UUID[path].size = v.size(); helper->mapURL2UUID[path].minSize = std::min(v.size(), helper->mapURL2UUID[path].minSize); helper->mapURL2UUID[path].maxSize = std::max(v.size(), helper->mapURL2UUID[path].maxSize); + helper->totalFetchedBytes += v.size(); + helper->totalRequestedBytes += v.size(); api.appendFlatHeader(v, headers); auto cacheId = allocator.adoptContainer(output, std::move(v), DataAllocator::CacheStrategy::Always, header::gSerializationMethodCCDB); helper->mapURL2DPLCache[path] = cacheId; @@ -350,8 +358,11 @@ auto populateCacheWith(std::shared_ptr const& helper, helper->mapURL2UUID[path].cachePopulatedAt = timestampToUse; helper->mapURL2UUID[path].cacheValidUntil = headers["Cache-Valid-Until"].empty() ? 0 : std::stoul(headers["Cache-Valid-Until"]); helper->mapURL2UUID[path].cacheMiss++; + helper->mapURL2UUID[path].size = v.size(); helper->mapURL2UUID[path].minSize = std::min(v.size(), helper->mapURL2UUID[path].minSize); helper->mapURL2UUID[path].maxSize = std::max(v.size(), helper->mapURL2UUID[path].maxSize); + helper->totalFetchedBytes += v.size(); + helper->totalRequestedBytes += v.size(); api.appendFlatHeader(v, headers); auto cacheId = allocator.adoptContainer(output, std::move(v), DataAllocator::CacheStrategy::Always, header::gSerializationMethodCCDB); helper->mapURL2DPLCache[path] = cacheId; @@ -368,6 +379,7 @@ auto populateCacheWith(std::shared_ptr const& helper, auto cacheId = helper->mapURL2DPLCache[path]; O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "Reusing %{public}s for %{public}s (DPL id %" PRIu64 ")", path.data(), headers["ETag"].data(), cacheId.value); helper->mapURL2UUID[path].cacheHit++; + helper->totalRequestedBytes += helper->mapURL2UUID[path].size; allocator.adoptFromCache(output, cacheId, header::gSerializationMethodCCDB); // the outputBuffer was not used, can we destroy it? } @@ -382,13 +394,13 @@ AlgorithmSpec CCDBHelpers::fetchFromCCDB() /// Add a callback on stop which dumps the statistics for the caching per /// path callbacks.set([helper]() { - LOGP(info, "CCDB cache miss/hit ratio:"); + LOGP(info, "CCDB cache miss/hit ratio ({} fetched / {} requested bytes):", helper->totalFetchedBytes, helper->totalRequestedBytes); for (auto& entry : helper->mapURL2UUID) { LOGP(info, " {}: {}/{} ({}-{} bytes)", entry.first, entry.second.cacheMiss, entry.second.cacheHit, entry.second.minSize, entry.second.maxSize); } }); - return adaptStateless([helper](DataTakingContext& dtc, DataAllocator& allocator, TimingInfo& timingInfo) { + return adaptStateless([helper](DataTakingContext& dtc, DataAllocator& allocator, TimingInfo& timingInfo, DataProcessingStats& stats) { auto sid = _o2_signpost_id_t{(int64_t)timingInfo.timeslice}; O2_SIGNPOST_START(ccdb, sid, "fetchFromCCDB", "Fetching CCDB objects for timeslice %" PRIu64, (uint64_t)timingInfo.timeslice); static Long64_t orbitResetTime = -1; @@ -429,8 +441,11 @@ AlgorithmSpec CCDBHelpers::fetchFromCCDB() if (etag.empty()) { helper->mapURL2UUID[path].etag = headers["ETag"]; // update uuid helper->mapURL2UUID[path].cacheMiss++; + helper->mapURL2UUID[path].size = v.size(); helper->mapURL2UUID[path].minSize = std::min(v.size(), helper->mapURL2UUID[path].minSize); helper->mapURL2UUID[path].maxSize = std::max(v.size(), helper->mapURL2UUID[path].maxSize); + helper->totalFetchedBytes += v.size(); + helper->totalRequestedBytes += v.size(); newOrbitResetTime = getOrbitResetTime(v); api.appendFlatHeader(v, headers); auto cacheId = allocator.adoptContainer(output, std::move(v), DataAllocator::CacheStrategy::Always, header::gSerializationMethodNone); @@ -440,8 +455,11 @@ AlgorithmSpec CCDBHelpers::fetchFromCCDB() // somewhere here pruneFromCache should be called helper->mapURL2UUID[path].etag = headers["ETag"]; // update uuid helper->mapURL2UUID[path].cacheMiss++; + helper->mapURL2UUID[path].size = v.size(); helper->mapURL2UUID[path].minSize = std::min(v.size(), helper->mapURL2UUID[path].minSize); helper->mapURL2UUID[path].maxSize = std::max(v.size(), helper->mapURL2UUID[path].maxSize); + helper->totalFetchedBytes += v.size(); + helper->totalRequestedBytes += v.size(); newOrbitResetTime = getOrbitResetTime(v); api.appendFlatHeader(v, headers); auto cacheId = allocator.adoptContainer(output, std::move(v), DataAllocator::CacheStrategy::Always, header::gSerializationMethodNone); @@ -455,6 +473,7 @@ AlgorithmSpec CCDBHelpers::fetchFromCCDB() auto cacheId = helper->mapURL2DPLCache[path]; O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "fetchFromCCDB", "Reusing %{public}s for %{public}s (DPL id %" PRIu64 ")", path.data(), headers["ETag"].data(), cacheId.value); helper->mapURL2UUID[path].cacheHit++; + helper->totalRequestedBytes += helper->mapURL2UUID[path].size; allocator.adoptFromCache(output, cacheId, header::gSerializationMethodNone); if (newOrbitResetTime != orbitResetTime) { @@ -480,6 +499,8 @@ AlgorithmSpec CCDBHelpers::fetchFromCCDB() dtc.runNumber.data(), orbitResetTime, timingInfo.creation, timestamp, timingInfo.firstTForbit); populateCacheWith(helper, timestamp, timingInfo, dtc, allocator); + stats.updateStats({(int)ProcessingStatsId::CCDB_CACHE_FETCHED_BYTES, DataProcessingStats::Op::Set, (int64_t)helper->totalFetchedBytes}); + stats.updateStats({(int)ProcessingStatsId::CCDB_CACHE_REQUESTED_BYTES, DataProcessingStats::Op::Set, (int64_t)helper->totalRequestedBytes}); O2_SIGNPOST_END(ccdb, _o2_signpost_id_t{(int64_t)timingInfo.timeslice}, "fetchFromCCDB", "Fetching CCDB objects"); }); }); } diff --git a/Framework/Core/CMakeLists.txt b/Framework/Core/CMakeLists.txt index c311ba980a20b..45af3ad6c59cc 100644 --- a/Framework/Core/CMakeLists.txt +++ b/Framework/Core/CMakeLists.txt @@ -158,6 +158,7 @@ o2_add_library(Framework src/StepTHn.cxx src/Base64.cxx src/DPLWebSocket.cxx + src/StatusWebSocketHandler.cxx src/TimerParamSpec.cxx test/TestClasses.cxx TARGETVARNAME targetName @@ -237,6 +238,7 @@ add_executable(o2-test-framework-core test/test_IndexBuilder.cxx test/test_InputRecord.cxx test/test_InputRecordWalker.cxx + test/test_DataModelViews.cxx test/test_InputSpan.cxx test/test_InputSpec.cxx test/test_LogParsingHelpers.cxx diff --git a/Framework/Core/COOKBOOK.md b/Framework/Core/COOKBOOK.md index c327651ae53ca..a99890bdcfa85 100644 --- a/Framework/Core/COOKBOOK.md +++ b/Framework/Core/COOKBOOK.md @@ -549,3 +549,43 @@ Streams can be explicitly enabled or disabled in code using the `O2_SIGNPOST_ENA If a process is already running and you wish to enable one or more of its signposts logs, you can do so using the `o2-log` utility, passing the address of the log to enable and the PID of the running process. E.g. `o2-log -p -a `. Finally, on macOS, you can also use Instruments to visualise your Signpost, just like any other macOS application. In order to do so you need to enable the "Signpost" instrument, making sure you add `ch.cern.aliceo2.completion` to the list of loggers to watch. + +## Improving lldb experience + +You can make lldb understand some of the O2 types by having the following +in your `~/.lldbinit` (or `$PWD/.lldbinit`): + +```lldb +command script import Framework/Core/scripts/lldb_o2_formatters.py +``` + + +Before: + +```gdb +(o2::framework::ConfigParamSpec &) 0x0000000774871e20: { + name = "timeframes-rate-limit-ipcid" + type = String + defaultValue = { + mStore = (__data = "\xa0\xae\x80t\a") + mType = String + mSize = 1 + } + help = (str = "Suffix for IPC channel for metric-feedback, -1 = disable") + kind = kGeneric +} +``` + +After: + +```gdb +(o2::framework::ConfigParamSpec &) 0x00000007cac75e20: { + name = "timeframes-rate-limit-ipcid" + type = String + defaultValue = { + value = 0x00000007cac0eea0 "-1" + } + help = (str = "Suffix for IPC channel for metric-feedback, -1 = disable") + kind = kGeneric +} +``` diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 475823b150d90..fc17fa139875c 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -395,6 +395,20 @@ static constexpr auto sourceSpec() { return fmt::format("{}/{}/{}/{}", label(), origin_str(), description_str(signature()), R.version); } + +/// Replace origins in the TableRef array +template ar, o2::aod::is_origin_hash O> +consteval auto replaceOrigin() +{ + std::array res; + for (auto i = 0U; i < N; ++i) { + res[i].label_hash = ar[i].label_hash; + res[i].desc_hash = ar[i].desc_hash; + res[i].origin_hash = O::hash; + res[i].version = ar[i].version; + } + return res; +} } // namespace o2::aod namespace o2::soa @@ -1176,12 +1190,6 @@ struct TableIterator : IP, C... { return *this; } - template - void doSetCurrentIndex(framework::pack, TA* current) - { - (CL::setCurrent(current), ...); - } - template auto getCurrent() const { @@ -1202,7 +1210,18 @@ struct TableIterator : IP, C... { template void bindExternalIndices(TA*... current) { - (doSetCurrentIndex(external_index_columns_t{}, current), ...); + ([this](TA* cur, framework::pack) { + (CCs::setCurrent(cur), ...); + }(current, external_index_columns_t{}), + ...); + } + + template + void bindExternalIndex(TA* current) + { + [this](TA* cur, framework::pack) { + (CCs::setCurrent(cur), ...); + }(current, external_index_columns_t{}); } template @@ -1306,6 +1325,11 @@ concept with_sources = requires { T::sources.size(); }; +template +concept with_sources_generator = requires(T t) { + t.template generateSources>(); +}; + template concept with_ccdb_urls = requires { T::ccdb_urls.size(); @@ -1313,7 +1337,7 @@ concept with_ccdb_urls = requires { template concept with_base_table = requires { - typename aod::MetadataTrait>::metadata::base_table_t; + typename aod::MetadataTrait>::metadata::base_table_t; }; template @@ -1379,9 +1403,10 @@ static constexpr std::string getLabelFromType() return getLabelForTable::first_t>(); } template + requires(!soa::is_iterator) static constexpr std::string getLabelFromType() { - return getLabelForTable>::metadata::base_table_t>(); + return getLabelForTable>::metadata::base_table_t>(); } template @@ -1806,12 +1831,18 @@ consteval auto computeOriginals() } template - requires((sizeof...(Ts) > 0) && (!o2::soa::is_column || ...)) + requires((sizeof...(Ts) > 0) && (!(o2::soa::is_column && ...))) consteval auto computeOriginals() { return o2::soa::mergeOriginals(); } +// template refs> +// consteval auto commonOrigin() +// { +// return (refs | std::ranges::views::filter([](TableRef const& r) { return (!(r.origin_hash == "DYN"_h || r.origin_hash == "IDX"_h)); })).front().origin_hash; +// } + /// A Table class which observes an arrow::Table and provides /// It is templated on a set of Column / DynamicColumn types. template @@ -1823,7 +1854,11 @@ class Table using table_t = self_t; static constexpr const auto originals = computeOriginals(); - static constexpr const auto originalLabels = [] refs, size_t... Is>(std::index_sequence) { return std::array{o2::aod::label()...}; }.template operator()(std::make_index_sequence()); + static constexpr const auto originalLabels = [] refs, size_t... Is>(std::index_sequence) { + return std::array{o2::aod::label()...}; + }.template operator()(std::make_index_sequence()); + static constexpr const uint32_t binding_origin = originals[0].origin_hash; // commonOrigin(); + static constexpr header::DataOrigin binding_origin_ = o2::aod::Hash::origin; template bindings> requires(ref.origin_hash == "CONC"_h) @@ -1836,10 +1871,10 @@ class Table requires(ref.origin_hash == "JOIN"_h) static consteval auto isIndexTargetOf() { - return std::find_if(self_t::originals.begin(), self_t::originals.end(), - [](TableRef const& r) { - return std::find(bindings.begin(), bindings.end(), r) != bindings.end(); - }) != self_t::originals.end(); + return std::ranges::any_of(self_t::originals, + [](TableRef const& r) { + return std::ranges::any_of(bindings, [&r](TableRef const& b) { return b == r; }); + }); } template bindings> @@ -1852,7 +1887,7 @@ class Table template static consteval bool hasOriginal() { - return std::find_if(originals.begin(), originals.end(), [](TableRef const& o) { return o.desc_hash == r.desc_hash; }) != originals.end(); + return std::ranges::any_of(originals, [](TableRef const& o) { return o.desc_hash == r.desc_hash; }); } using columns_t = decltype(getColumns()); @@ -2179,7 +2214,18 @@ class Table template void bindExternalIndices(TA*... current) { - mBegin.bindExternalIndices(current...); + ([this](TA* cur) { + if constexpr (binding_origin == TA::binding_origin) { + mBegin.bindExternalIndex(cur); + } + }(current), + ...); + } + + template + void bindExternalIndex(TA* current) + { + mBegin.bindExternalIndex(current); // unchecked binding for the derived tables } template @@ -2364,9 +2410,9 @@ namespace o2::aod O2ORIGIN("AOD"); O2ORIGIN("AOD1"); O2ORIGIN("AOD2"); -O2ORIGIN("DYN"); -O2ORIGIN("IDX"); -O2ORIGIN("ATIM"); +// O2ORIGIN("DYN"); +// O2ORIGIN("IDX"); +// O2ORIGIN("ATIM"); O2ORIGIN("JOIN"); O2HASH("JOIN/0"); O2ORIGIN("CONC"); @@ -2427,43 +2473,47 @@ consteval static std::string_view namespace_prefix() }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_ { _Label_, _Name_::hash, o2::framework::expressions::selectArrowType<_Type_>() } -#define DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, _Label_, _Getter_, _ConcreteType_, _CCDBQuery_) \ - struct _Name_ : o2::soa::Column, _Name_> { \ - static constexpr const char* mLabel = _Label_; \ - static constexpr const char* query = _CCDBQuery_; \ - static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ - using base = o2::soa::Column, _Name_>; \ - using type = std::span; \ - using column_t = _Name_; \ - _Name_(arrow::ChunkedArray const* column) \ - : o2::soa::Column, _Name_>(o2::soa::ColumnIterator>(column)) \ - { \ - } \ - \ - _Name_() = default; \ - _Name_(_Name_ const& other) = default; \ - _Name_& operator=(_Name_ const& other) = default; \ - \ - decltype(auto) _Getter_() const \ - { \ - static std::byte* payload = nullptr; \ - static _ConcreteType_* deserialised = nullptr; \ - static TClass* c = TClass::GetClass(#_ConcreteType_); \ - auto span = *mColumnIterator; \ - if (payload != (std::byte*)span.data()) { \ - payload = (std::byte*)span.data(); \ - delete deserialised; \ - TBufferFile f(TBufferFile::EMode::kRead, span.size(), (char*)span.data(), kFALSE); \ - deserialised = (_ConcreteType_*)soa::extractCCDBPayload((char*)payload, span.size(), c, "ccdb_object"); \ - } \ - return *deserialised; \ - } \ - \ - decltype(auto) \ - get() const \ - { \ - return _Getter_(); \ - } \ +#define DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, _Label_, _Getter_, _ConcreteType_, _CCDBQuery_) \ + struct _Name_ : o2::soa::Column, _Name_> { \ + static constexpr const char* mLabel = _Label_; \ + static constexpr const char* query = _CCDBQuery_; \ + static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + using base = o2::soa::Column, _Name_>; \ + using type = std::span; \ + using column_t = _Name_; \ + _Name_(arrow::ChunkedArray const* column) \ + : o2::soa::Column, _Name_>(o2::soa::ColumnIterator>(column)) \ + { \ + } \ + \ + _Name_() = default; \ + _Name_(_Name_ const& other) = default; \ + _Name_& operator=(_Name_ const& other) = default; \ + \ + decltype(auto) _Getter_() const \ + { \ + if constexpr (std::same_as<_ConcreteType_, std::span>) { \ + return *mColumnIterator; \ + } else { \ + static std::byte* payload = nullptr; \ + static _ConcreteType_* deserialised = nullptr; \ + static TClass* c = TClass::GetClass(#_ConcreteType_); \ + auto span = *mColumnIterator; \ + if (payload != (std::byte*)span.data()) { \ + payload = (std::byte*)span.data(); \ + delete deserialised; \ + TBufferFile f(TBufferFile::EMode::kRead, span.size(), (char*)span.data(), kFALSE); \ + deserialised = (_ConcreteType_*)soa::extractCCDBPayload((char*)payload, span.size(), c, "ccdb_object"); \ + } \ + return *deserialised; \ + } \ + } \ + \ + decltype(auto) \ + get() const \ + { \ + return _Getter_(); \ + } \ }; #define DECLARE_SOA_CCDB_COLUMN(_Name_, _Getter_, _ConcreteType_, _CCDBQuery_) \ @@ -3244,149 +3294,176 @@ consteval auto getIndexTargets() #define DECLARE_SOA_TABLE_STAGED(_BaseName_, _Desc_, ...) \ DECLARE_SOA_TABLE_STAGED_VERSIONED(_BaseName_, _Desc_, 0, __VA_ARGS__); -#define DECLARE_SOA_EXTENDED_TABLE_FULL(_Name_, _Label_, _OriginalTable_, _Origin_, _Desc_, _Version_, ...) \ +#define DECLARE_SOA_EXTENDED_TABLE_NG(_Name_, _OriginalTable_, _Desc_, _Version_, ...) \ O2HASH(_Desc_ "/" #_Version_); \ + O2HASH(#_Name_ "Extension"); \ template \ - using _Name_##ExtensionFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ - using _Name_##Extension = _Name_##ExtensionFrom>; \ - template > \ - struct _Name_##ExtensionMetadataFrom : TableMetadata, __VA_ARGS__> { \ + using _Name_##ExtensionFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ + using _Name_##Extension = _Name_##ExtensionFrom>; \ + struct _Name_##ExtensionMetadata : TableMetadata, __VA_ARGS__> { \ using base_table_t = _OriginalTable_; \ - using extension_table_t = _Name_##ExtensionFrom; \ + template \ + using extension_table_t_from = _Name_##ExtensionFrom; \ + using extension_table_t = _Name_##Extension; \ using expression_pack_t = framework::pack<__VA_ARGS__>; \ - static constexpr auto sources = _OriginalTable_::originals; \ + static constexpr auto N = _OriginalTable_::originals.size(); \ + template > \ + static consteval auto generateSources() \ + { \ + return _OriginalTable_##From::originals; \ + } \ }; \ - using _Name_##ExtensionMetadata = _Name_##ExtensionMetadataFrom>; \ template <> \ struct MetadataTrait> { \ using metadata = _Name_##ExtensionMetadata; \ }; \ template \ - using _Name_##From = o2::soa::JoinFull, _OriginalTable_, _Name_##ExtensionFrom>; \ - using _Name_ = _Name_##From>; + using _Name_##From = o2::soa::Join<_OriginalTable_##From, _Name_##ExtensionFrom>; \ + using _Name_ = _Name_##From>; #define DECLARE_SOA_EXTENDED_TABLE(_Name_, _Table_, _Description_, _Version_, ...) \ - O2HASH(#_Name_ "Extension"); \ - DECLARE_SOA_EXTENDED_TABLE_FULL(_Name_, #_Name_ "Extension", _Table_, "DYN", _Description_, _Version_, __VA_ARGS__) + DECLARE_SOA_EXTENDED_TABLE_NG(_Name_, _Table_, _Description_, _Version_, __VA_ARGS__) #define DECLARE_SOA_EXTENDED_TABLE_USER(_Name_, _Table_, _Description_, ...) \ - O2HASH(#_Name_ "Extension"); \ - DECLARE_SOA_EXTENDED_TABLE_FULL(_Name_, #_Name_ "Extension", _Table_, "AOD", "EX" _Description_, 0, __VA_ARGS__) - -#define DECLARE_SOA_CONFIGURABLE_EXTENDED_TABLE_FULL(_Name_, _Label_, _OriginalTable_, _Origin_, _Desc_, _Version_, ...) \ - O2HASH(_Desc_ "/" #_Version_); \ - template \ - using _Name_##CfgExtensionFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ - using _Name_##CfgExtension = _Name_##CfgExtensionFrom>; \ - template > \ - struct _Name_##CfgExtensionMetadataFrom : TableMetadata, __VA_ARGS__> { \ - using base_table_t = _OriginalTable_; \ - using extension_table_t = _Name_##CfgExtensionFrom; \ - using placeholders_pack_t = framework::pack<__VA_ARGS__>; \ - using configurable_t = std::true_type; \ - static constexpr auto sources = _OriginalTable_::originals; \ - }; \ - using _Name_##CfgExtensionMetadata = _Name_##CfgExtensionMetadataFrom>; \ - template <> \ - struct MetadataTrait> { \ - using metadata = _Name_##CfgExtensionMetadata; \ - }; \ - template \ - using _Name_##From = o2::soa::JoinFull, _OriginalTable_, _Name_##CfgExtensionFrom>; \ - using _Name_ = _Name_##From>; - -#define DECLARE_SOA_CONFIGURABLE_EXTENDED_TABLE(_Name_, _Table_, _Description_, ...) \ - O2HASH(#_Name_ "CfgExtension"); \ - DECLARE_SOA_CONFIGURABLE_EXTENDED_TABLE_FULL(_Name_, #_Name_ "CfgExtension", _Table_, "AOD", "EX" _Description_, 0, __VA_ARGS__) - -#define DECLARE_SOA_INDEX_TABLE_FULL(_Name_, _Key_, _Origin_, _Version_, _Desc_, _Exclusive_, ...) \ - O2HASH(#_Name_); \ - O2HASH(_Desc_ "/" #_Version_); \ - template > \ - struct _Name_##MetadataFrom : o2::aod::TableMetadata, soa::Index<>, __VA_ARGS__> { \ - static constexpr bool exclusive = _Exclusive_; \ - using Key = _Key_; \ - using index_pack_t = framework::pack<__VA_ARGS__>; \ - static constexpr const auto sources = [](framework::pack) { \ - constexpr auto a = o2::soa::mergeOriginals(); \ - return o2::aod::filterForKey(); \ - }(framework::pack<__VA_ARGS__>{}); \ - static_assert(sources.size() - Key::originals.size() + 1 == framework::pack_size(index_pack_t{}), "One of the referred tables does not have index to Key"); \ - }; \ - using _Name_##Metadata = _Name_##MetadataFrom>; \ - \ - template > \ - using _Name_##From = o2::soa::IndexTable, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O, _Key_, __VA_ARGS__>; \ - using _Name_ = _Name_##From>; \ - \ - template <> \ - struct MetadataTrait> { \ - using metadata = _Name_##Metadata; \ - }; + DECLARE_SOA_EXTENDED_TABLE_NG(_Name_, _Table_, "EX" _Description_, 0, __VA_ARGS__) + +#define DECLARE_SOA_CONFIGURABLE_EXTENDED_TABLE_NG(_Name_, _OriginalTable_, _Desc_, _Version_, ...) \ + O2HASH(_Desc_ "/" #_Version_); \ + O2HASH(#_Name_ "CfgExtension"); \ + template \ + using _Name_##CfgExtensionFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ + using _Name_##CfgExtension = _Name_##CfgExtensionFrom>; \ + struct _Name_##CfgExtensionMetadata : TableMetadata, __VA_ARGS__> { \ + using base_table_t = _OriginalTable_; \ + template \ + using extension_table_t_from = _Name_##CfgExtensionFrom; \ + using extension_table_t = _Name_##CfgExtension; \ + using placeholders_pack_t = framework::pack<__VA_ARGS__>; \ + using configurable_t = std::true_type; \ + static constexpr auto N = _OriginalTable_::originals.size(); \ + template > \ + static consteval auto generateSources() \ + { \ + return _OriginalTable_##From::originals; \ + } \ + }; \ + template <> \ + struct MetadataTrait> { \ + using metadata = _Name_##CfgExtensionMetadata; \ + }; \ + template \ + using _Name_##From = o2::soa::Join<_OriginalTable_##From, _Name_##CfgExtensionFrom>; \ + using _Name_ = _Name_##From>; + +#define DECLARE_SOA_CONFIGURABLE_EXTENDED_TABLE(_Name_, _OriginalTable_, _Description_, ...) \ + DECLARE_SOA_CONFIGURABLE_EXTENDED_TABLE_NG(_Name_, _OriginalTable_, "EX" _Description_, 0, __VA_ARGS__) + +#define DECLARE_SOA_INDEX_TABLE_NG(_Name_, _Key_, _Version_, _Desc_, _Exclusive_, ...) \ + O2HASH(#_Name_); \ + O2HASH(_Desc_ "/" #_Version_); \ + struct _Name_##Metadata : o2::aod::TableMetadata, soa::Index<>, __VA_ARGS__> { \ + static constexpr bool exclusive = _Exclusive_; \ + template \ + using KeyFrom = _Key_##From; \ + using Key = _Key_; \ + using index_pack_t = framework::pack<__VA_ARGS__>; \ + template > \ + static consteval auto generateSources() \ + { \ + return [](framework::pack) { \ + constexpr auto first = o2::soa::mergeOriginals(); \ + constexpr auto second = o2::aod::filterForKey(); \ + return o2::aod::replaceOrigin(); \ + }(framework::pack<__VA_ARGS__>{}); \ + } \ + static constexpr auto N = [](framework::pack) { \ + constexpr auto a = o2::soa::mergeOriginals(); \ + return o2::aod::filterForKey(); \ + }(framework::pack<__VA_ARGS__>{}) \ + .size(); \ + }; \ + template <> \ + struct MetadataTrait> { \ + using metadata = _Name_##Metadata; \ + }; \ + template \ + using _Name_##From = o2::soa::IndexTable, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O, _Key_##From, __VA_ARGS__>; \ + using _Name_ = _Name_##From>; + +#define DECLARE_SOA_INDEX_TABLE(_Name_, _Key_, _Description_, ...) \ + DECLARE_SOA_INDEX_TABLE_NG(_Name_, _Key_, 0, _Description_, false, __VA_ARGS__) + +#define DECLARE_SOA_INDEX_TABLE_EXCLUSIVE(_Name_, _Key_, _Description_, ...) \ + DECLARE_SOA_INDEX_TABLE_NG(_Name_, _Key_, 0, _Description_, true, __VA_ARGS__) + +#define DECLARE_SOA_INDEX_TABLE_USER(_Name_, _Key_, _Description_, ...) \ + DECLARE_SOA_INDEX_TABLE_NG(_Name_, _Key_, 0, _Description_, false, __VA_ARGS__) + +#define DECLARE_SOA_INDEX_TABLE_EXCLUSIVE_USER(_Name_, _Key_, _Description_, ...) \ + DECLARE_SOA_INDEX_TABLE_NG(_Name_, _Key_, 0, _Description_, true, __VA_ARGS__) // Declare were each row is associated to a timestamp column of an _TimestampSource_ // table. // // The columns of this table have to be CCDB_COLUMNS so that for each timestamp, we get a row // which points to the specified CCDB objectes described by those columns. -#define DECLARE_SOA_TIMESTAMPED_TABLE_FULL(_Name_, _Label_, _TimestampSource_, _TimestampColumn_, _Origin_, _Version_, _Desc_, ...) \ - O2HASH(_Desc_ "/" #_Version_); \ - template \ - using _Name_##TimestampFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ - using _Name_##Timestamp = _Name_##TimestampFrom>; \ - template > \ - struct _Name_##TimestampMetadataFrom : TableMetadata, __VA_ARGS__> { \ - using base_table_t = _TimestampSource_; \ - using extension_table_t = _Name_##TimestampFrom; \ - static constexpr const auto ccdb_urls = [](framework::pack) { \ - return std::array{Cs::query...}; \ - }(framework::pack<__VA_ARGS__>{}); \ - static constexpr const auto ccdb_bindings = [](framework::pack) { \ - return std::array{Cs::mLabel...}; \ - }(framework::pack<__VA_ARGS__>{}); \ - static constexpr auto sources = _TimestampSource_::originals; \ - static constexpr auto timestamp_column_label = _TimestampColumn_::mLabel; \ - /*static constexpr auto timestampColumn = _TimestampColumn_;*/ \ - }; \ - using _Name_##TimestampMetadata = _Name_##TimestampMetadataFrom>; \ - template <> \ - struct MetadataTrait> { \ - using metadata = _Name_##TimestampMetadata; \ - }; \ - template \ - using _Name_##From = o2::soa::JoinFull, _TimestampSource_, _Name_##TimestampFrom>; \ - using _Name_ = _Name_##From>; +#define DECLARE_SOA_TIMESTAMPED_TABLE_FULL(_Name_, _Label_, _TimestampSource_, _TimestampColumn_, _Version_, _Desc_, ...) \ + O2HASH(_Desc_ "/" #_Version_); \ + template \ + using _Name_##TimestampFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ + using _Name_##Timestamp = _Name_##TimestampFrom>; \ + struct _Name_##TimestampMetadata : TableMetadata, __VA_ARGS__> { \ + template > \ + using base_table_t = _TimestampSource_##From; \ + template > \ + using extension_table_t = _Name_##TimestampFrom; \ + static constexpr const auto ccdb_urls = [](framework::pack) { \ + return std::array{Cs::query...}; \ + }(framework::pack<__VA_ARGS__>{}); \ + static constexpr const auto ccdb_bindings = [](framework::pack) { \ + return std::array{Cs::mLabel...}; \ + }(framework::pack<__VA_ARGS__>{}); \ + static constexpr auto N = _TimestampSource_::originals.size(); \ + template > \ + static consteval auto generateSources() \ + { \ + return _TimestampSource_##From::originals; \ + } \ + static constexpr auto timestamp_column_label = _TimestampColumn_::mLabel; \ + /*static constexpr auto timestampColumn = _TimestampColumn_;*/ \ + }; \ + template <> \ + struct MetadataTrait> { \ + using metadata = _Name_##TimestampMetadata; \ + }; \ + template \ + using _Name_##From = o2::soa::Join<_TimestampSource_, _Name_##TimestampFrom>; \ + using _Name_ = _Name_##From>; #define DECLARE_SOA_TIMESTAMPED_TABLE(_Name_, _TimestampSource_, _TimestampColumn_, _Version_, _Desc_, ...) \ O2HASH(#_Name_ "Timestamped"); \ - DECLARE_SOA_TIMESTAMPED_TABLE_FULL(_Name_, #_Name_ "Timestamped", _TimestampSource_, _TimestampColumn_, "ATIM", _Version_, _Desc_, __VA_ARGS__) - -#define DECLARE_SOA_INDEX_TABLE(_Name_, _Key_, _Description_, ...) \ - DECLARE_SOA_INDEX_TABLE_FULL(_Name_, _Key_, "IDX", 0, _Description_, false, __VA_ARGS__) - -#define DECLARE_SOA_INDEX_TABLE_EXCLUSIVE(_Name_, _Key_, _Description_, ...) \ - DECLARE_SOA_INDEX_TABLE_FULL(_Name_, _Key_, "IDX", 0, _Description_, true, __VA_ARGS__) - -#define DECLARE_SOA_INDEX_TABLE_USER(_Name_, _Key_, _Description_, ...) \ - DECLARE_SOA_INDEX_TABLE_FULL(_Name_, _Key_, "AOD", 0, _Description_, false, __VA_ARGS__) - -#define DECLARE_SOA_INDEX_TABLE_EXCLUSIVE_USER(_Name_, _Key_, _Description_, ...) \ - DECLARE_SOA_INDEX_TABLE_FULL(_Name_, _Key_, "AOD", 0, _Description_, true, __VA_ARGS__) + DECLARE_SOA_TIMESTAMPED_TABLE_FULL(_Name_, #_Name_ "Timestamped", _TimestampSource_, _TimestampColumn_, _Version_, _Desc_, __VA_ARGS__) namespace o2::soa { -template -struct JoinFull : Table, D, o2::aod::Hash<"JOIN"_h>, Ts...> { - using base = Table, D, o2::aod::Hash<"JOIN"_h>, Ts...>; +template +struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Ts...> { + using base = Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Ts...>; - JoinFull(std::shared_ptr&& table, uint64_t offset = 0) + Join(std::shared_ptr&& table, uint64_t offset = 0) : base{std::move(table), offset} { if (this->tableSize() != 0) { bindInternalIndicesTo(this); } } - JoinFull(std::vector>&& tables, uint64_t offset = 0) + Join(std::vector>&& tables, uint64_t offset = 0) : base{ArrowHelpers::joinTables(std::move(tables), std::span{base::originalLabels}), offset} { if (this->tableSize() != 0) { @@ -3395,8 +3472,21 @@ struct JoinFull : Table, D, o2::aod::Hash<"JOIN"_h>, Ts. } using base::bindExternalIndices; using base::bindInternalIndicesTo; + static constexpr const uint32_t binding_origin = base::binding_origin; + static constexpr const header::DataOrigin binding_origin_ = base::binding_origin_; + + template + void bindExternalIndices(TA*... current) + { + ([this](TA* cur) { + if constexpr (binding_origin == TA::binding_origin) { + this->bindExternalIndex(cur); + } + }(current), + ...); + } - using self_t = JoinFull; + using self_t = Join; using table_t = base; static constexpr const auto originals = base::originals; static constexpr const auto originalLabels = base::originalLabels; @@ -3460,13 +3550,12 @@ struct JoinFull : Table, D, o2::aod::Hash<"JOIN"_h>, Ts. template static consteval bool contains() { - return std::find_if(originals.begin(), originals.end(), [](TableRef const& ref) { return ref.desc_hash == T::ref.desc_hash; }) != originals.end(); + return [](std::index_sequence) { + return (std::ranges::any_of(originals, [](TableRef const& ref) { return ref.desc_hash == T::originals[Is].desc_hash; }) && ...); + }(std::make_index_sequence()); } }; -template -using Join = JoinFull, Ts...>; - template constexpr auto join(Ts const&... t) { @@ -3474,7 +3563,7 @@ constexpr auto join(Ts const&... t) } template -concept is_join = framework::specialization_of_template; +concept is_join = framework::specialization_of_template; template constexpr bool is_soa_join_v = is_join; @@ -3524,6 +3613,19 @@ class FilteredBase : public T using self_t = FilteredBase; using table_t = typename T::table_t; using T::originals; + static constexpr const uint32_t binding_origin = T::binding_origin; + static constexpr const header::DataOrigin binding_origin_ = T::binding_origin_; + template + void bindExternalIndices(TA*... current) + { + ([this](TA* cur) { + if constexpr (binding_origin == TA::binding_origin) { + this->bindExternalIndex(cur); + mFilteredBegin.bindExternalIndex(cur); + } + }(current), + ...); + } using columns_t = typename T::columns_t; using persistent_columns_t = typename T::persistent_columns_t; using external_index_columns_t = typename T::external_index_columns_t; @@ -3645,13 +3747,6 @@ class FilteredBase : public T /// Bind the columns which refer to other tables /// to the associated tables. - template - void bindExternalIndices(TA*... current) - { - table_t::bindExternalIndices(current...); - mFilteredBegin.bindExternalIndices(current...); - } - void bindExternalIndicesRaw(std::vector&& ptrs) { mFilteredBegin.bindExternalIndicesRaw(std::forward>(ptrs)); @@ -4134,6 +4229,20 @@ struct IndexTable : Table { using first_t = typename H::binding_t; using rest_t = framework::pack; + static constexpr const uint32_t binding_origin = Key::binding_origin; + static constexpr const header::DataOrigin binding_origin_ = Key::binding_origin_; + + template + void bindExternalIndices(TA*... current) + { + ([this](TA* cur) { + if constexpr (binding_origin == TA::binding_origin) { + this->bindExternalIndex(cur); + } + }(current), + ...); + } + IndexTable(std::shared_ptr table, uint64_t offset = 0) : base_t{table, offset} { diff --git a/Framework/Core/include/Framework/AnalysisDataModel.h b/Framework/Core/include/Framework/AnalysisDataModel.h index 9f48685820634..c8dd33fba62ee 100644 --- a/Framework/Core/include/Framework/AnalysisDataModel.h +++ b/Framework/Core/include/Framework/AnalysisDataModel.h @@ -51,6 +51,8 @@ DECLARE_SOA_TABLE(BCFlags, "AOD", "BCFLAG", //! flag for tagging UPCs, joinable bc::Flags); using BCs = BCs_001; // current version +template +using BCsFrom = BCs_001From; using BC = BCs::iterator; namespace timestamp @@ -66,7 +68,7 @@ using BCsWithTimestamps = soa::Join; namespace soa { -extern template struct JoinFull, aod::BCs, aod::Timestamps>; +extern template struct Join; } namespace aod { @@ -413,7 +415,7 @@ DECLARE_SOA_DYNAMIC_COLUMN(HasTPCSideC, hasTPCSideC, //! Run 3: Has this track T DECLARE_SOA_DYNAMIC_COLUMN(HasTPCSideCOnly, hasTPCSideCOnly, //! Run 3: Has this track TPC clusters from side C only? [](uint8_t flags) -> bool { return (flags & (o2::aod::track::TPCSideA | o2::aod::track::TPCSideC)) == o2::aod::track::TPCSideC; }); DECLARE_SOA_DYNAMIC_COLUMN(HasTPCBothSides, hasTPCBothSides, //! Run 3: Has this track TPC clusters from both side A and C? - [](uint8_t flags) -> bool { return (flags & (o2::aod::track::TPCSideA | o2::aod::track::TPCSideC)) == (o2::aod::track::TPCSideA || o2::aod::track::TPCSideC); }); + [](uint8_t flags) -> bool { return (flags & (o2::aod::track::TPCSideA | o2::aod::track::TPCSideC)) == (o2::aod::track::TPCSideA | o2::aod::track::TPCSideC); }); DECLARE_SOA_DYNAMIC_COLUMN(PIDForTracking, pidForTracking, //! PID hypothesis used during tracking. See the constants in the class PID in PID.h [](uint32_t flags) -> uint32_t { return flags >> 28; }); DECLARE_SOA_DYNAMIC_COLUMN(TPCNClsFound, tpcNClsFound, //! Number of found TPC clusters @@ -514,11 +516,11 @@ DECLARE_SOA_TABLE_FULL(StoredTracksIU, "Tracks_IU", "AOD", "TRACK_IU", //! On di track::Sign, o2::soa::Marker<2>); -DECLARE_SOA_EXTENDED_TABLE(TracksIU, StoredTracksIU, "EXTRACK_IU", 0, //! Track parameters at inner most update (e.g. ITS) as it comes from the tracking - aod::track::Pt, - aod::track::P, - aod::track::Eta, - aod::track::Phi); +DECLARE_SOA_EXTENDED_TABLE_NG(TracksIU, StoredTracksIU, "EXTRACK_IU", 0, //! Track parameters at inner most update (e.g. ITS) as it comes from the tracking + aod::track::Pt, + aod::track::P, + aod::track::Eta, + aod::track::Phi); DECLARE_SOA_TABLE_FULL(StoredTracksCov, "TracksCov", "AOD", "TRACKCOV", //! On disk version of the TracksCov table at collision vertex track::SigmaY, track::SigmaZ, track::SigmaSnp, track::SigmaTgl, track::Sigma1Pt, @@ -680,9 +682,9 @@ using Run2TrackExtra = Run2TrackExtras::iterator; } // namespace aod namespace soa { -extern template struct soa::JoinFull, aod::Tracks, aod::TracksExtra>; -extern template struct soa::JoinFull, aod::Tracks, aod::TracksCov, aod::TracksExtra>; -extern template struct soa::JoinFull, aod::TracksExtension, aod::StoredTracks>; +extern template struct soa::Join; +extern template struct soa::Join; +extern template struct soa::Join; } // namespace soa namespace aod { @@ -926,6 +928,8 @@ using MFTTracks = MFTTracks_001; using StoredMFTTracks = StoredMFTTracks_001; using MFTTrack = MFTTracks::iterator; +template +using MFTTracksFrom = MFTTracks_001From; namespace fwdtrack // Index to MFTtrack column must be defined after table definition. { @@ -1005,7 +1009,7 @@ using MFTTrackCovFwd = MFTTracksCov::iterator; } // namespace aod namespace soa { -extern template struct JoinFull, aod::FwdTracks, aod::FwdTracksCov>; +extern template struct Join; } namespace aod { @@ -2026,6 +2030,8 @@ DECLARE_SOA_EXTENDED_TABLE(McParticles_001, StoredMcParticles_001, "EXMCPARTICLE using StoredMcParticles = StoredMcParticles_001; using McParticles = McParticles_001; using McParticle = McParticles::iterator; +template +using McParticlesFrom = McParticles_001From; } // namespace aod namespace soa { @@ -2191,11 +2197,11 @@ DECLARE_SOA_INDEX_COLUMN(FDD, fdd); //! // First entry: Collision #define INDEX_LIST_RUN2 indices::CollisionId, indices::ZdcId, indices::BCId, indices::FT0Id, indices::FV0AId, indices::FV0CId, indices::FDDId DECLARE_SOA_INDEX_TABLE_EXCLUSIVE(Run2MatchedExclusive, BCs, "MA_RN2_EX", INDEX_LIST_RUN2); //! -DECLARE_SOA_INDEX_TABLE(Run2MatchedSparse, BCs, "MA_RN2_SP", INDEX_LIST_RUN2); //! +DECLARE_SOA_INDEX_TABLE(Run2MatchedSparse, BCs_001, "MA_RN2_SP", INDEX_LIST_RUN2); //! #define INDEX_LIST_RUN3 indices::CollisionId, indices::ZdcId, indices::BCId, indices::FT0Id, indices::FV0AId, indices::FDDId DECLARE_SOA_INDEX_TABLE_EXCLUSIVE(Run3MatchedExclusive, BCs, "MA_RN3_EX", INDEX_LIST_RUN3); //! -DECLARE_SOA_INDEX_TABLE(Run3MatchedSparse, BCs, "MA_RN3_SP", INDEX_LIST_RUN3); //! +DECLARE_SOA_INDEX_TABLE(Run3MatchedSparse, BCs_001, "MA_RN3_SP", INDEX_LIST_RUN3); //! // First entry: BC DECLARE_SOA_INDEX_TABLE_EXCLUSIVE(MatchedBCCollisionsExclusive, BCs, "MA_BCCOL_EX", //! @@ -2225,8 +2231,8 @@ DECLARE_EQUIVALENT_FOR_INDEX(aod::StoredTracksIU, aod::McTrackLabels); DECLARE_EQUIVALENT_FOR_INDEX(aod::Collisions, aod::McCollisionLabels); // Joins with collisions (only for sparse ones) // NOTE: index table needs to be always last argument -extern template struct JoinFull, aod::Collisions, aod::Run2MatchedSparse>; -extern template struct JoinFull, aod::Collisions, aod::Run3MatchedSparse>; +extern template struct Join; +extern template struct Join; } // namespace soa namespace aod { diff --git a/Framework/Core/include/Framework/AnalysisHelpers.h b/Framework/Core/include/Framework/AnalysisHelpers.h index a01d14b6632a9..cfd2f357ba06f 100644 --- a/Framework/Core/include/Framework/AnalysisHelpers.h +++ b/Framework/Core/include/Framework/AnalysisHelpers.h @@ -79,6 +79,7 @@ auto makeEmptyTable(const char* name) } template + requires(soa::not_void>::metadata>) auto makeEmptyTable() { auto schema = std::make_shared(soa::createFieldsFromColumns(typename aod::MetadataTrait>::metadata::persistent_columns_t{})); @@ -93,6 +94,7 @@ auto makeEmptyTable(const char* name, framework::pack p) } template + requires(soa::not_void::metadata>) auto makeEmptyTable(const char* name) { auto schema = std::make_shared(soa::createFieldsFromColumns(typename aod::MetadataTrait::metadata::persistent_columns_t{})); @@ -170,7 +172,7 @@ struct Builder { std::shared_ptr materialize(ProcessingContext& pc); }; -} // namespace o2::framework +} // namespace o2::framework namespace o2::soa { @@ -216,6 +218,26 @@ inline constexpr auto getSourceSchemas() }.template operator()(); } +template > +inline constexpr auto getSources() +{ + return [] refs>() { + return [](std::index_sequence) { + return std::vector{soa::tableRef2ConfigParamSpec()...}; + }(std::make_index_sequence()); + }.template operator()()>(); +} + +template > +inline constexpr auto getSourceSchemas() +{ + return [] refs>() { + return [](std::index_sequence) { + return std::vector{soa::tableRef2Schema()...}; + }(std::make_index_sequence()); + }.template operator()()>(); +} + template inline constexpr auto getCCDBUrls() { @@ -257,7 +279,7 @@ inline constexpr auto getIndexMapping() using indices = T::index_pack_t; using Key = T::Key; [&idx](std::index_sequence) mutable { - constexpr auto refs = T::sources; + constexpr auto refs = T::generateSources(); ([&idx]() mutable { constexpr auto pos = o2::aod::MetadataTrait>::metadata::template getIndexPosToKey(); if constexpr (pos == -1) { @@ -272,6 +294,26 @@ inline constexpr auto getIndexMapping() return idx; } +template > +constexpr auto getInputMetadata() -> std::vector +{ + std::vector inputMetadata; + + auto inputSources = getSources(); + std::sort(inputSources.begin(), inputSources.end(), [](framework::ConfigParamSpec const& a, framework::ConfigParamSpec const& b) { return a.name < b.name; }); + auto last = std::unique(inputSources.begin(), inputSources.end(), [](framework::ConfigParamSpec const& a, framework::ConfigParamSpec const& b) { return a.name == b.name; }); + inputSources.erase(last, inputSources.end()); + inputMetadata.insert(inputMetadata.end(), inputSources.begin(), inputSources.end()); + + auto inputSchemas = getSourceSchemas(); + std::sort(inputSchemas.begin(), inputSchemas.end(), [](framework::ConfigParamSpec const& a, framework::ConfigParamSpec const& b) { return a.name < b.name; }); + last = std::unique(inputSchemas.begin(), inputSchemas.end(), [](framework::ConfigParamSpec const& a, framework::ConfigParamSpec const& b) { return a.name == b.name; }); + inputSchemas.erase(last, inputSchemas.end()); + inputMetadata.insert(inputMetadata.end(), inputSchemas.begin(), inputSchemas.end()); + + return inputMetadata; +} + template constexpr auto getInputMetadata() -> std::vector { @@ -293,7 +335,7 @@ constexpr auto getInputMetadata() -> std::vector } template - requires(!soa::with_sources) + requires(!(soa::with_sources || soa::with_sources_generator)) constexpr auto getInputMetadata() -> std::vector { return {}; @@ -352,20 +394,25 @@ constexpr auto getIndexMetadata() -> std::vector return {}; } -} // namespace +} // namespace template constexpr auto tableRef2InputSpec() { std::vector metadata; - auto m = getInputMetadata>::metadata>(); - metadata.insert(metadata.end(), m.begin(), m.end()); - auto ccdbMetadata = getCCDBMetadata>::metadata>(); - metadata.insert(metadata.end(), ccdbMetadata.begin(), ccdbMetadata.end()); - auto p = getExpressionMetadata>::metadata>(); - metadata.insert(metadata.end(), p.begin(), p.end()); - auto idx = getIndexMetadata>::metadata>(); - metadata.insert(metadata.end(), idx.begin(), idx.end()); + std::vector sources; + if constexpr (soa::with_sources>::metadata>) { + sources = getInputMetadata>::metadata>(); + } else if constexpr (soa::with_sources_generator>::metadata>) { + sources = getInputMetadata>::metadata, o2::aod::Hash>(); + } + metadata.insert(metadata.end(), sources.begin(), sources.end()); + auto ccdbURLs = getCCDBMetadata>::metadata>(); + metadata.insert(metadata.end(), ccdbURLs.begin(), ccdbURLs.end()); + auto expressions = getExpressionMetadata>::metadata>(); + metadata.insert(metadata.end(), expressions.begin(), expressions.end()); + auto indices = getIndexMetadata>::metadata>(); + metadata.insert(metadata.end(), indices.begin(), indices.end()); if constexpr (!soa::with_ccdb_urls>::metadata>) { metadata.emplace_back(framework::ConfigParamSpec{"schema", framework::VariantType::String, framework::serializeSchema(o2::aod::MetadataTrait>::metadata::getSchema()), {"\"\""}}); } @@ -382,11 +429,22 @@ constexpr auto tableRef2InputSpec() template constexpr auto tableRef2OutputSpec() { + std::vector metadata; + using md = typename o2::aod::MetadataTrait>::metadata; + if constexpr (soa::with_ccdb_urls) { + metadata.emplace_back("ccdb:", framework::VariantType::Bool, true, framework::ConfigParamSpec::HelpString{"\"\""}); + } else if constexpr (soa::with_expression_pack) { + metadata.emplace_back("projectors", framework::VariantType::Bool, true, framework::ConfigParamSpec::HelpString{"\"\""}); + } else if constexpr (soa::with_index_pack) { + metadata.emplace_back("index-records", framework::VariantType::Bool, true, framework::ConfigParamSpec::HelpString{"\"\""}); + } return framework::OutputSpec{ framework::OutputLabel{o2::aod::label()}, o2::aod::origin(), o2::aod::description(o2::aod::signature()), - R.version}; + R.version, + framework::Lifetime::Timeframe, + metadata}; } template @@ -405,7 +463,7 @@ constexpr auto tableRef2OutputRef() o2::aod::label(), R.version}; } -} // namespace o2::soa +} // namespace o2::soa namespace o2::framework { @@ -504,14 +562,14 @@ struct OutputForTable { using table_t = decltype(typeWithRef()); using metadata = aod::MetadataTrait>::metadata; - static OutputSpec const spec() + static constexpr auto spec() { - return OutputSpec{OutputLabel{aod::label()}, o2::aod::origin(), o2::aod::description(o2::aod::signature()), table_t::ref.version}; + return soa::tableRef2OutputSpec(); } - static OutputRef ref() + static constexpr auto ref() { - return OutputRef{aod::label(), table_t::ref.version}; + return soa::tableRef2OutputRef(); } }; @@ -544,7 +602,7 @@ concept is_produces_group = std::derived_from; template struct TableTransform { using metadata = M; - constexpr static auto sources = M::sources; + constexpr static auto sources = M::template generateSources>(); template static auto base_spec() @@ -578,23 +636,23 @@ struct TableTransform { /// This helper struct allows you to declare extended tables which should be /// created by the task (as opposed to those pre-defined by data model) template -concept is_spawnable = soa::has_metadata>> && soa::has_extension>::metadata>; +concept is_spawnable = soa::has_metadata>> && soa::has_extension>::metadata>; template -concept is_dynamically_spawnable = soa::has_metadata>> && soa::has_configurable_extension>::metadata>; +concept is_dynamically_spawnable = soa::has_metadata>> && soa::has_configurable_extension>::metadata>; template constexpr auto transformBase() { - using metadata = typename aod::MetadataTrait>::metadata; - return TableTransform{}; + using metadata = typename aod::MetadataTrait>::metadata; + return TableTransform>::ref>{}; } template struct Spawns : decltype(transformBase()) { using spawnable_t = T; using metadata = decltype(transformBase())::metadata; - using extension_t = typename metadata::extension_table_t; + using extension_t = typename metadata::template extension_table_t_from>; using expression_pack_t = typename metadata::expression_pack_t; static constexpr size_t N = framework::pack_size(expression_pack_t{}); @@ -614,7 +672,7 @@ struct Spawns : decltype(transformBase()) { std::shared_ptr table = nullptr; std::shared_ptr extension = nullptr; - std::array projectors = [](framework::pack) -> std::array + std::array projectors = [](framework::pack)->std::array { return {{std::move(C::Projector())...}}; } @@ -644,7 +702,7 @@ struct Defines : decltype(transformBase()) { static constexpr bool delayed = DELAYED; using spawnable_t = T; using metadata = decltype(transformBase())::metadata; - using extension_t = typename metadata::extension_table_t; + using extension_t = typename metadata::template extension_table_t_from>; using placeholders_pack_t = typename metadata::placeholders_pack_t; static constexpr size_t N = framework::pack_size(placeholders_pack_t{}); @@ -1019,7 +1077,7 @@ concept is_partition = requires(T t) { requires std::same_as; requires std::same_as>>; }; -} // namespace o2::framework +} // namespace o2::framework namespace o2::soa { @@ -1042,6 +1100,6 @@ auto Attach(T const& table) using output_t = Join, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Cs...>>; return output_t{{table.asArrowTable()}, table.offset()}; } -} // namespace o2::soa +} // namespace o2::soa -#endif // o2_framework_AnalysisHelpers_H_DEFINED +#endif // o2_framework_AnalysisHelpers_H_DEFINED diff --git a/Framework/Core/include/Framework/AnalysisManagers.h b/Framework/Core/include/Framework/AnalysisManagers.h index 121ce7f4b4a77..1873f33937742 100644 --- a/Framework/Core/include/Framework/AnalysisManagers.h +++ b/Framework/Core/include/Framework/AnalysisManagers.h @@ -183,20 +183,20 @@ bool newDataframeCondition(InputRecord& record, C& conditionGroup) /// Outputs handling template -bool appendOutput(std::vector&, T&, uint32_t) +constexpr bool appendOutput(std::vector&, T&, uint32_t) { return false; } template -bool appendOutput(std::vector& outputs, T&, uint32_t) +constexpr bool appendOutput(std::vector& outputs, T&, uint32_t) { - outputs.emplace_back(OutputForTable::spec()); + outputs.emplace_back(soa::tableRef2OutputSpec()); return true; } template -bool appendOutput(std::vector& outputs, T& producesGroup, uint32_t hash) +constexpr bool appendOutput(std::vector& outputs, T& producesGroup, uint32_t hash) { homogeneous_apply_refs([&outputs, hash](auto& produces) { return appendOutput(outputs, produces, hash); }, producesGroup); return true; @@ -261,7 +261,7 @@ bool prepareOutput(ProcessingContext&, T&) template bool prepareOutput(ProcessingContext& context, T& produces) { - produces.resetCursor(std::move(context.outputs().make(OutputForTable::ref()))); + produces.resetCursor(std::move(context.outputs().make(soa::tableRef2OutputRef()))); return true; } @@ -275,10 +275,10 @@ bool prepareOutput(ProcessingContext& context, T& producesGroup) template bool prepareOutput(ProcessingContext& context, T& spawns) { - using metadata = o2::aod::MetadataTrait>::metadata; - auto originalTable = soa::ArrowHelpers::joinTables(extractOriginals(context), std::span{metadata::base_table_t::originalLabels}); + using metadata = o2::aod::MetadataTrait>::metadata; + auto originalTable = soa::ArrowHelpers::joinTables(extractOriginals>()>(context), std::span{metadata::base_table_t::originalLabels}); if (originalTable->num_rows() == 0) { - originalTable = makeEmptyTable(); + originalTable = makeEmptyTable("EMPTY", typename metadata::base_table_t::persistent_columns_t{}); } using D = o2::aod::Hash; @@ -295,17 +295,17 @@ template bool prepareOutput(ProcessingContext& context, T& builds) { using metadata = o2::aod::MetadataTrait>::metadata; - return builds.build(extractOriginals(context)); + return builds.build(extractOriginals>()>(context)); } template bool prepareOutput(ProcessingContext& context, T& defines) requires(T::delayed == false) { - using metadata = o2::aod::MetadataTrait>::metadata; - auto originalTable = soa::ArrowHelpers::joinTables(extractOriginals(context), std::span{metadata::base_table_t::originalLabels}); + using metadata = o2::aod::MetadataTrait>::metadata; + auto originalTable = soa::ArrowHelpers::joinTables(extractOriginals>()>(context), std::span{metadata::base_table_t::originalLabels}); if (originalTable->num_rows() == 0) { - originalTable = makeEmptyTable(); + originalTable = makeEmptyTable("EMPTY", typename metadata::base_table_t::persistent_columns_t{}); } if (defines.inputSchema == nullptr) { defines.inputSchema = originalTable->schema(); diff --git a/Framework/Core/include/Framework/AnalysisSupportHelpers.h b/Framework/Core/include/Framework/AnalysisSupportHelpers.h index ef1d056b62f2b..c1968123e765d 100644 --- a/Framework/Core/include/Framework/AnalysisSupportHelpers.h +++ b/Framework/Core/include/Framework/AnalysisSupportHelpers.h @@ -20,12 +20,11 @@ namespace o2::framework { -static constexpr std::array AODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}}; -static constexpr std::array extendedAODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"DYN"}, header::DataOrigin{"AMD"}, header::DataOrigin{"EMB"}}; -static constexpr std::array writableAODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"DYN"}}; +static constexpr std::array AODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}, header::DataOrigin{"EMB"}, header::DataOrigin{"AMD"}}; +static constexpr std::array writableAODOrigins{header::DataOrigin{"AOD"}, header::DataOrigin{"AOD1"}, header::DataOrigin{"AOD2"}}; class DataOutputDirector; -class ConfigContext; +struct ConfigContext; // Helper class to be moved in the AnalysisSupport plugin at some point struct AnalysisSupportHelpers { diff --git a/Framework/Core/include/Framework/AnalysisTask.h b/Framework/Core/include/Framework/AnalysisTask.h index fbd523c7b0c37..1b25727874749 100644 --- a/Framework/Core/include/Framework/AnalysisTask.h +++ b/Framework/Core/include/Framework/AnalysisTask.h @@ -16,6 +16,7 @@ #include "Framework/AlgorithmSpec.h" #include "Framework/CallbackService.h" #include "Framework/ConfigContext.h" +#include "Framework/ConfigParamsHelper.h" #include "Framework/ControlService.h" #include "Framework/DataProcessorSpec.h" #include "Framework/Expressions.h" @@ -557,6 +558,17 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args) LOG(warn) << "Task " << name_str << " has no inputs"; } + // Auto-register default ccdb: path options from subscribed timestamped-table inputs. + // This allows tasks to accept --ccdb:fXxx overrides without requiring an explicit + // ConfigurableCCDBPath<> member for every column in the subscribed table. + for (auto& input : inputs) { + for (auto& meta : input.metadata) { + if (meta.name.starts_with("ccdb:") && meta.name != "ccdb:") { + ConfigParamsHelper::addOptionIfMissing(options, meta); + } + } + } + homogeneous_apply_refs_sized([&outputs, &hash](auto& element) { return analysis_task_parsers::appendOutput(outputs, element, hash); }, *task.get()); auto requiredServices = CommonServices::defaultServices(); diff --git a/Framework/Core/include/Framework/BinningPolicy.h b/Framework/Core/include/Framework/BinningPolicy.h index 3e41302c920f8..f4ea6885192e4 100644 --- a/Framework/Core/include/Framework/BinningPolicy.h +++ b/Framework/Core/include/Framework/BinningPolicy.h @@ -188,7 +188,7 @@ struct BinningPolicyBase { if constexpr (N == 2) { return getXBinsCount() * getYBinsCount(); } - if constexpr (N == 2) { + if constexpr (N == 3) { return getXBinsCount() * getYBinsCount() * getZBinsCount(); } return -1; diff --git a/Framework/Core/include/Framework/ComputingQuotaEvaluator.h b/Framework/Core/include/Framework/ComputingQuotaEvaluator.h index 17ce9c2ba3e65..b25bc1611d79f 100644 --- a/Framework/Core/include/Framework/ComputingQuotaEvaluator.h +++ b/Framework/Core/include/Framework/ComputingQuotaEvaluator.h @@ -37,7 +37,9 @@ class ComputingQuotaEvaluator /// @a task the task which needs some quota /// @a request the resource request the @a task needs /// @a now the time (e.g. uv_now) when invoked. - bool selectOffer(int task, ComputingQuotaRequest const& request, uint64_t now); + /// @a accumulated if non-null, filled with the resources accumulated from + /// selected offers (useful for diagnosing shortfalls on failure). + bool selectOffer(int task, ComputingQuotaRequest const& request, uint64_t now, ComputingQuotaOffer* accumulated = nullptr); /// Consume offers for a given taskId /// @a reportConsumedOffer callback which reports back that an offer has been consumed. void consume(int taskId, diff --git a/Framework/Core/include/Framework/Configurable.h b/Framework/Core/include/Framework/Configurable.h index 0931884da1ff7..3cbd1839b7d89 100644 --- a/Framework/Core/include/Framework/Configurable.h +++ b/Framework/Core/include/Framework/Configurable.h @@ -83,6 +83,26 @@ struct Configurable : IP { template using MutableConfigurable = Configurable>; +/// Convenience wrapper for overriding the CCDB path of a CCDB column declared +/// with DECLARE_SOA_CCDB_COLUMN / DECLARE_SOA_CCDB_COLUMN_FULL. +/// +/// The option name, default value, and help string are all derived automatically +/// from the column type: name = "ccdb:" + Column::mLabel, default = Column::query. +/// +/// Example: +/// struct MyTask { +/// ConfigurableCCDBPath lhcPhasePath; +/// }; +template +struct ConfigurableCCDBPath : Configurable { + ConfigurableCCDBPath() + : Configurable{std::string{"ccdb:"} + Column::mLabel, + std::string{Column::query}, + std::string{"CCDB path for "} + Column::mLabel + " (default: " + Column::query + ")"} + { + } +}; + template concept is_configurable = requires(T t) { requires std::same_as; @@ -93,11 +113,10 @@ concept is_configurable = requires(T t) { using ConfigurableAxis = Configurable, ConfigParamKind::kAxisSpec, ConfigurablePolicyConst, ConfigParamKind::kAxisSpec>>; template -concept is_configurable_axis = is_configurable&& - requires() -{ - T::kind == ConfigParamKind::kAxisSpec; -}; +concept is_configurable_axis = is_configurable && + requires() { + T::kind == ConfigParamKind::kAxisSpec; + }; template struct ProcessConfigurable : Configurable { diff --git a/Framework/Core/include/Framework/DanglingEdgesContext.h b/Framework/Core/include/Framework/DanglingEdgesContext.h index 90a88974db038..c5f54297ee746 100644 --- a/Framework/Core/include/Framework/DanglingEdgesContext.h +++ b/Framework/Core/include/Framework/DanglingEdgesContext.h @@ -33,15 +33,24 @@ struct OutputObjectInfo { // been requested and for which we will need to inject // some source device. struct DanglingEdgesContext { + // generic AOD tables std::vector requestedAODs; std::vector providedAODs; + // extension tables std::vector requestedDYNs; std::vector providedDYNs; + // index tables std::vector requestedIDXs; + std::vector providedIDXs; + // ccdb tables std::vector providedTIMs; std::vector requestedTIMs; + // output objects std::vector providedOutputObjHist; + // inputs for the extension spawner std::vector spawnerInputs; + // inputs for the index builder + std::vector builderInputs; // These are the timestamped tables which are required to // inject the the CCDB objecs. diff --git a/Framework/Core/include/Framework/DataModelViews.h b/Framework/Core/include/Framework/DataModelViews.h index b7a334454bb6e..dd8d65ea16459 100644 --- a/Framework/Core/include/Framework/DataModelViews.h +++ b/Framework/Core/include/Framework/DataModelViews.h @@ -16,7 +16,10 @@ #include "DomainInfoHeader.h" #include "SourceInfoHeader.h" #include "Headers/DataHeader.h" +#include "Framework/DataRef.h" +#include "Framework/TimesliceSlot.h" #include +#include namespace o2::framework { @@ -70,7 +73,7 @@ struct count_parts { count += 1; mi += header->splitPayloadParts + 1; } else { - count += header->splitPayloadParts; + count += header->splitPayloadParts ? header->splitPayloadParts : 1; mi += header->splitPayloadParts ? 2 * header->splitPayloadParts : 2; } } @@ -78,10 +81,7 @@ struct count_parts { } }; -struct DataRefIndices { - size_t headerIdx; - size_t payloadIdx; -}; +// DataRefIndices is defined in Framework/DataRef.h struct get_pair { size_t pairId; @@ -98,23 +98,70 @@ struct get_pair { } size_t diff = self.pairId - count; if (header->splitPayloadParts > 1 && header->splitPayloadIndex == header->splitPayloadParts) { + // New style: one header followed by splitPayloadParts contiguous payloads. count += header->splitPayloadParts; if (self.pairId < count) { return {mi, mi + 1 + diff}; } mi += header->splitPayloadParts + 1; + } else if (header->splitPayloadParts > 1 && header->splitPayloadIndex != header->splitPayloadParts) { + // Old style multi-part: splitPayloadParts [header, payload] pairs. + // We are at the first pair of the block; jump directly. + if (diff < header->splitPayloadParts) { + return {mi + 2 * diff, mi + 2 * diff + 1}; + } + count += header->splitPayloadParts; + mi += 2 * header->splitPayloadParts; } else { - count += header->splitPayloadParts ? header->splitPayloadParts : 1; - if (self.pairId < count) { - return {mi, mi + 2 * diff + 1}; + // Single [header, payload] pair (splitPayloadParts == 0). + if (self.pairId == count) { + return {mi, mi + 1}; } - mi += header->splitPayloadParts ? 2 * header->splitPayloadParts : 2; + count += 1; + mi += 2; } } throw std::runtime_error("Payload not found"); } }; +// Advance from a DataRefIndices to the next one in O(1), reading only the +// current header. Intended for use in iterators so that ++ is O(1) rather +// than the O(n) while-loop that get_pair requires. +// +// New-style block (splitPayloadIndex == splitPayloadParts > 1): +// layout: [header, payload_0, payload_1, ..., payload_{N-1}] +// advance within block while payloads remain, then jump to the next block. +// +// Old-style block (splitPayloadIndex != splitPayloadParts, splitPayloadParts > 1) +// or single pair (splitPayloadParts == 0): +// layout: [header, payload] – always advance by two messages. +struct get_next_pair { + DataRefIndices current; + template + requires std::ranges::random_access_range && std::ranges::sized_range + friend DataRefIndices operator|(R&& r, get_next_pair self) + { + size_t hIdx = self.current.headerIdx; + auto* header = o2::header::get(r[hIdx]->GetData()); + if (!header) { + throw std::runtime_error("Not a DataHeader"); + } + if (header->splitPayloadParts > 1 && header->splitPayloadIndex == header->splitPayloadParts) { + // New-style block: one header followed by splitPayloadParts contiguous payloads. + if (self.current.payloadIdx < hIdx + header->splitPayloadParts) { + // More sub-payloads remain in this block. + return {hIdx, self.current.payloadIdx + 1}; + } + // Last sub-payload consumed; move to the first pair of the next block. + size_t nextHIdx = hIdx + header->splitPayloadParts + 1; + return {nextHIdx, nextHIdx + 1}; + } + // Old-style [header, payload] pairs or a single pair: advance by two messages. + return {hIdx + 2, hIdx + 3}; + } +}; + struct get_dataref_indices { size_t part; size_t subPart; @@ -138,10 +185,10 @@ struct get_dataref_indices { mi += header->splitPayloadParts + 1; } else { if (self.part == count) { - return {mi, mi + 2 * self.subPart + 1}; + return {mi, mi + self.subPart + 1}; } count += 1; - mi += header->splitPayloadParts ? 2 * header->splitPayloadParts : 2; + mi += 2; } } throw std::runtime_error("Payload not found"); @@ -153,7 +200,7 @@ struct get_header { // ends the pipeline, returns the number of parts template requires std::ranges::random_access_range && std::ranges::sized_range - friend fair::mq::MessagePtr& operator|(R&& r, get_header self) + friend auto& operator|(R&& r, get_header self) { return r[(r | get_dataref_indices{self.id, 0}).headerIdx]; } @@ -165,57 +212,59 @@ struct get_payload { // ends the pipeline, returns the number of parts template requires std::ranges::random_access_range && std::ranges::sized_range - friend fair::mq::MessagePtr& operator|(R&& r, get_payload self) + friend auto& operator|(R&& r, get_payload self) { return r[(r | get_dataref_indices{self.part, self.subPart}).payloadIdx]; } }; struct get_num_payloads { - size_t id; - // ends the pipeline, returns the number of parts + size_t n; + // ends the pipeline, returns the number of payloads which are associated + // to the multipart n-th sequence of messages found in the range template requires std::ranges::random_access_range && std::ranges::sized_range friend size_t operator|(R&& r, get_num_payloads self) { size_t count = 0; size_t mi = 0; + // Un while (mi < r.size()) { auto* header = o2::header::get(r[mi]->GetData()); if (!header) { throw std::runtime_error("Not a DataHeader"); } - if (self.id == count) { - if (header->splitPayloadParts > 1 && (header->splitPayloadIndex == header->splitPayloadParts)) { + if (header->splitPayloadParts > 1 && (header->splitPayloadIndex == header->splitPayloadParts)) { + // This is the case for the new multi payload messages where the number of parts + // is as many as the splitPayloadParts number. + if (self.n == count) { return header->splitPayloadParts; - } else { - return 1; } - } - if (header->splitPayloadParts > 1 && (header->splitPayloadIndex == header->splitPayloadParts)) { + // For multipayload we skip all the parts and their associated header count += 1; mi += header->splitPayloadParts + 1; } else { - count += 1; - mi += header->splitPayloadParts ? 2 * header->splitPayloadParts : 2; + // This is the case of a multipart (header, payload), (header, payload), ... + // sequence where we know how many pairs are there. + // When splitPayloadParts == 0, it means it is a non-multipart (header, payload) + // pair. Each pair has exactly 1 payload. + auto pairs = header->splitPayloadParts ? header->splitPayloadParts : 1; + if (self.n < count + pairs) { + return 1; + } + count += pairs; + mi += 2 * pairs; } } return 0; } }; -struct MessageSet; - -struct MessageStore { - std::span sets; - size_t inputsPerSlot = 0; -}; - struct inputs_for_slot { TimesliceSlot slot; template - requires requires(R r) { std::ranges::random_access_range; } - friend std::span operator|(R&& r, inputs_for_slot self) + requires requires(R r) { requires std::ranges::random_access_range; } + friend auto operator|(R&& r, inputs_for_slot self) { return std::span(r.sets[self.slot.index * r.inputsPerSlot]); } @@ -227,7 +276,7 @@ struct messages_for_input { requires std::ranges::random_access_range friend std::span operator|(R&& r, messages_for_input self) { - return r[self.inputIdx].messages; + return std::span(r[self.inputIdx]); } }; diff --git a/Framework/Core/include/Framework/DataProcessingContext.h b/Framework/Core/include/Framework/DataProcessingContext.h index 221f7b099dc07..976331ba42c3c 100644 --- a/Framework/Core/include/Framework/DataProcessingContext.h +++ b/Framework/Core/include/Framework/DataProcessingContext.h @@ -13,6 +13,7 @@ #include "Framework/DataRelayer.h" #include "Framework/AlgorithmSpec.h" +#include #include namespace o2::framework @@ -33,7 +34,7 @@ struct DataProcessorContext { DataProcessorContext(DataProcessorContext const&) = delete; DataProcessorContext() = default; - bool allDone = false; + std::atomic allDone = false; /// Latest run number we processed globally for this DataProcessor. int64_t lastRunNumberProcessed = -1; diff --git a/Framework/Core/include/Framework/DataProcessingHelpers.h b/Framework/Core/include/Framework/DataProcessingHelpers.h index 87aeeb8922da3..f414e3aa4ae00 100644 --- a/Framework/Core/include/Framework/DataProcessingHelpers.h +++ b/Framework/Core/include/Framework/DataProcessingHelpers.h @@ -15,6 +15,7 @@ #include "Framework/TimesliceSlot.h" #include "Framework/TimesliceIndex.h" #include +#include #include #include @@ -29,7 +30,6 @@ struct OutputChannelState; struct ProcessingPolicies; struct DeviceSpec; struct FairMQDeviceProxy; -struct MessageSet; struct ChannelIndex; enum struct StreamingState; enum struct TransitionHandlingState; @@ -54,7 +54,7 @@ struct DataProcessingHelpers { /// starts the EoS timers and returns the new TransitionHandlingState in case as new state is requested static TransitionHandlingState updateStateTransition(ServiceRegistryRef const& ref, ProcessingPolicies const& policies); /// Helper to route messages for forwarding - static std::vector routeForwardedMessageSet(FairMQDeviceProxy& proxy, std::vector& currentSetOfInputs, + static std::vector routeForwardedMessageSet(FairMQDeviceProxy& proxy, std::vector>& currentSetOfInputs, bool copy, bool consume); /// Helper to route messages for forwarding static void routeForwardedMessages(FairMQDeviceProxy& proxy, std::span& currentSetOfInputs, std::vector& forwardedParts, diff --git a/Framework/Core/include/Framework/DataProcessingStats.h b/Framework/Core/include/Framework/DataProcessingStats.h index a1f5c0eec5568..edb04c4c5f752 100644 --- a/Framework/Core/include/Framework/DataProcessingStats.h +++ b/Framework/Core/include/Framework/DataProcessingStats.h @@ -72,6 +72,8 @@ enum struct ProcessingStatsId : short { CCDB_CACHE_HIT, CCDB_CACHE_MISS, CCDB_CACHE_FAILURE, + CCDB_CACHE_FETCHED_BYTES, + CCDB_CACHE_REQUESTED_BYTES, AVAILABLE_MANAGED_SHM_BASE = 512, }; diff --git a/Framework/Core/include/Framework/DataRef.h b/Framework/Core/include/Framework/DataRef.h index d4cba88b19333..aad667c8c33bd 100644 --- a/Framework/Core/include/Framework/DataRef.h +++ b/Framework/Core/include/Framework/DataRef.h @@ -12,6 +12,7 @@ #define FRAMEWORK_DATAREF_H #include // for size_t +#include namespace o2 { @@ -29,6 +30,15 @@ struct DataRef { size_t payloadSize = 0; }; +/// Raw indices into the message vector for one (header, payload) pair. +/// Kept in a lightweight header so InputSpan can use it without pulling in FairMQ. +struct DataRefIndices { + size_t headerIdx; + size_t payloadIdx; + bool operator==(const DataRefIndices&) const = default; + auto operator<=>(const DataRefIndices&) const = default; +}; + } // namespace framework } // namespace o2 diff --git a/Framework/Core/include/Framework/DataRelayer.h b/Framework/Core/include/Framework/DataRelayer.h index e5a2aecea1de4..b56a2cb59ff10 100644 --- a/Framework/Core/include/Framework/DataRelayer.h +++ b/Framework/Core/include/Framework/DataRelayer.h @@ -16,7 +16,7 @@ #include "Framework/DataDescriptorMatcher.h" #include "Framework/ForwardRoute.h" #include "Framework/CompletionPolicy.h" -#include "Framework/MessageSet.h" +#include #include "Framework/TimesliceIndex.h" #include "Framework/Tracing.h" #include "Framework/TimesliceSlot.h" @@ -113,7 +113,7 @@ class DataRelayer ActivityStats processDanglingInputs(std::vector const&, ServiceRegistryRef context, bool createNew); - using OnDropCallback = std::function&, TimesliceIndex::OldestOutputInfo info)>; + using OnDropCallback = std::function>&, TimesliceIndex::OldestOutputInfo info)>; // Callback for when some messages are about to be owned by the the DataRelayer using OnInsertionCallback = std::function&)>; @@ -156,8 +156,8 @@ class DataRelayer /// Returns an input registry associated to the given timeslice and gives /// ownership to the caller. This is because once the inputs are out of the /// DataRelayer they need to be deleted once the processing is concluded. - std::vector consumeAllInputsForTimeslice(TimesliceSlot id); - std::vector consumeExistingInputsForTimeslice(TimesliceSlot id); + std::vector> consumeAllInputsForTimeslice(TimesliceSlot id); + std::vector> consumeExistingInputsForTimeslice(TimesliceSlot id); /// Returns how many timeslices we can handle in parallel [[nodiscard]] size_t getParallelTimeslices() const; @@ -203,7 +203,7 @@ class DataRelayer /// Notice that we store them as a NxM sized vector, where /// N is the maximum number of inflight timeslices, while /// M is the number of inputs which are requested. - std::vector mCache; + std::vector> mCache; /// This is the index which maps a given timestamp to the associated /// cacheline. diff --git a/Framework/Core/include/Framework/DataSpecViews.h b/Framework/Core/include/Framework/DataSpecViews.h index b38866d8aa6fd..63da68ab5d53e 100644 --- a/Framework/Core/include/Framework/DataSpecViews.h +++ b/Framework/Core/include/Framework/DataSpecViews.h @@ -14,8 +14,31 @@ #include "Framework/DataSpecUtils.h" #include +namespace o2::framework::checks +{ +static auto has_params_with_name(std::string&& name) +{ + return [name](ConfigParamSpec const& p) { return p.name.compare(name) == 0; }; +} + +static auto has_params_with_name_starting(std::string&& name) +{ + return [name](ConfigParamSpec const& p) { return p.name.starts_with(name); }; +} +} // namespace o2::framework::checks + namespace o2::framework::views { +static auto filter_with_params_by_name(std::string&& name) +{ + return std::views::filter([name = std::move(name)](auto const& spec) mutable { return std::ranges::any_of(spec.metadata, checks::has_params_with_name(std::move(name))); }); +} + +static auto filter_with_params_by_name_starting(std::string&& name) +{ + return std::views::filter([name = std::move(name)](auto const& spec) mutable { return std::ranges::any_of(spec.metadata, checks::has_params_with_name_starting(std::move(name))); }); +} + static auto partial_match_filter(auto what) { return std::views::filter([what](auto const& t) -> bool { return DataSpecUtils::partialMatch(t, what); }); diff --git a/Framework/Core/include/Framework/DeviceInfo.h b/Framework/Core/include/Framework/DeviceInfo.h index ef93ca83ca03f..bc3e895a3d8ed 100644 --- a/Framework/Core/include/Framework/DeviceInfo.h +++ b/Framework/Core/include/Framework/DeviceInfo.h @@ -61,6 +61,9 @@ struct DeviceInfo { std::string lastError; /// An unterminated string which is not ready to be printed yet std::string unprinted; + /// Total number of log lines ever stored in history (monotonically increasing). + /// Used by status clients to track which lines they have already sent. + size_t logSeq = 0; /// Whether the device is active (running) or not. bool active; /// Whether the device is ready to quit. diff --git a/Framework/Core/include/Framework/InputRecord.h b/Framework/Core/include/Framework/InputRecord.h index 96963f88524be..d2e152c1bcacc 100644 --- a/Framework/Core/include/Framework/InputRecord.h +++ b/Framework/Core/include/Framework/InputRecord.h @@ -13,6 +13,7 @@ #include "Framework/DataRef.h" #include "Framework/DataRefUtils.h" +#include "Framework/InputSpan.h" #include "Framework/InputRoute.h" #include "Framework/TypeTraits.h" #include "Framework/TableConsumer.h" @@ -202,6 +203,15 @@ class InputRecord [[nodiscard]] size_t getNofParts(int pos) const; + /// O(1) access to the part described by @a indices in slot @a pos. + [[nodiscard]] DataRef getAtIndices(int pos, DataRefIndices indices) const; + + /// O(1) advance from @a current to the next part's indices in slot @a pos. + [[nodiscard]] DataRefIndices nextIndices(int pos, DataRefIndices current) const + { + return mSpan.nextIndices(pos, current); + } + // Given a binding by string, return the associated DataRef DataRef getDataRefByString(const char* bindingName, int part = 0) const { @@ -568,8 +578,8 @@ class InputRecord Iterator() = delete; - Iterator(ParentType const* parent, size_t position = 0, size_t size = 0) - : mPosition(position), mSize(size > position ? size : position), mParent(parent), mElement{nullptr, nullptr, nullptr} + Iterator(ParentType const* parent, bool isEnd = false) + : mPosition(isEnd ? parent->size() : 0), mSize(parent->size()), mParent(parent), mElement{nullptr, nullptr, nullptr} { if (mPosition < mSize) { if (mParent->isValid(mPosition)) { @@ -678,18 +688,29 @@ class InputRecord using reference = typename BaseType::reference; using pointer = typename BaseType::pointer; using ElementType = typename std::remove_const::type; - using iterator = Iterator; - using const_iterator = Iterator; + using iterator = InputSpan::Iterator; + using const_iterator = InputSpan::Iterator; + + InputRecordIterator(InputRecord const* parent, bool isEnd = false) + : BaseType(parent, isEnd) + { + } + + /// Initial indices for part-level iteration: first part starts at {headerIdx=0, payloadIdx=1}. + [[nodiscard]] DataRefIndices initialIndices() const { return {0, 1}; } + /// Sentinel used by nextIndicesGetter to signal end-of-slot. + [[nodiscard]] DataRefIndices endIndices() const { return {size_t(-1), size_t(-1)}; } - InputRecordIterator(InputRecord const* parent, size_t position = 0, size_t size = 0) - : BaseType(parent, position, size) + /// Get element at the given raw message indices in O(1). + [[nodiscard]] ElementType getAtIndices(DataRefIndices indices) const { + return this->parent()->getAtIndices(this->position(), indices); } - /// Get element at {slotindex, partindex} - [[nodiscard]] ElementType getByPos(size_t pos) const + /// Advance @a current to the next part's indices in O(1). + [[nodiscard]] DataRefIndices nextIndices(DataRefIndices current) const { - return this->parent()->getByPos(this->position(), pos); + return this->parent()->nextIndices(this->position(), current); } /// Check if slot is valid, index of part is not used @@ -709,12 +730,12 @@ class InputRecord [[nodiscard]] const_iterator begin() const { - return const_iterator(this, 0, size()); + return const_iterator(this, size() == 0); } [[nodiscard]] const_iterator end() const { - return const_iterator(this, size()); + return const_iterator(this, true); } }; @@ -723,12 +744,12 @@ class InputRecord [[nodiscard]] const_iterator begin() const { - return {this, 0, size()}; + return {this, false}; } [[nodiscard]] const_iterator end() const { - return {this, size()}; + return {this, true}; } InputSpan& span() diff --git a/Framework/Core/include/Framework/InputSpan.h b/Framework/Core/include/Framework/InputSpan.h index cf8c8acda6796..dbe270f0e030d 100644 --- a/Framework/Core/include/Framework/InputSpan.h +++ b/Framework/Core/include/Framework/InputSpan.h @@ -14,8 +14,8 @@ #include "Framework/DataRef.h" #include -extern template class std::function; -extern template class std::function; +extern template class std::function; +extern template class std::function; namespace o2::framework { @@ -32,37 +32,48 @@ class InputSpan InputSpan(InputSpan const&) = delete; InputSpan(InputSpan&&) = default; - /// @a getter is the mapping between an element of the span referred by - /// index and the buffer associated. - /// @a size is the number of elements in the span. - InputSpan(std::function getter, size_t size); + /// Navigate the message store via the DataRefIndices protocol. + /// get_next_pair (DataModelViews.h) provides O(1) sequential advancement for nextIndicesGetter. + InputSpan(std::function nofPartsGetter, + std::function refCountGetter, + std::function indicesGetter, + std::function nextIndicesGetter, + size_t size); - /// @a getter is the mapping between an element of the span referred by - /// index and the buffer associated. - /// @a size is the number of elements in the span. - InputSpan(std::function getter, size_t size); + /// @a i-th element of the InputSpan (O(partidx) sequential scan via indices protocol) + [[nodiscard]] DataRef get(size_t i, size_t partidx = 0) const + { + DataRefIndices idx{0, 1}; + for (size_t p = 0; p < partidx; ++p) { + idx = mNextIndicesGetter(i, idx); + } + return mIndicesGetter(i, idx); + } - /// @a getter is the mapping between an element of the span referred by - /// index and the buffer associated. - /// @nofPartsGetter is the getter for the number of parts associated with an index - /// @a size is the number of elements in the span. - InputSpan(std::function getter, std::function nofPartsGetter, std::function refCountGetter, size_t size); + /// Return the DataRef for the part described by @a indices in slot @a slotIdx in O(1). + [[nodiscard]] DataRef getAtIndices(size_t slotIdx, DataRefIndices indices) const + { + return mIndicesGetter(slotIdx, indices); + } - /// @a i-th element of the InputSpan - [[nodiscard]] DataRef get(size_t i, size_t partidx = 0) const + /// Advance from @a current to the indices of the next part in slot @a slotIdx in O(1). + [[nodiscard]] DataRefIndices nextIndices(size_t slotIdx, DataRefIndices current) const { - return mGetter(i, partidx); + return mNextIndicesGetter(slotIdx, current); } + // --- slot-level Iterator protocol (headerIdx doubles as slot position) --- + [[nodiscard]] DataRefIndices initialIndices() const { return {0, 0}; } + [[nodiscard]] DataRefIndices endIndices() const { return {mSize, 0}; } + [[nodiscard]] DataRef getAtIndices(DataRefIndices indices) const { return mIndicesGetter(indices.headerIdx, {0, 1}); } + [[nodiscard]] DataRefIndices nextIndices(DataRefIndices current) const { return {current.headerIdx + 1, 0}; } + /// @a number of parts in the i-th element of the InputSpan [[nodiscard]] size_t getNofParts(size_t i) const { if (i >= mSize) { return 0; } - if (!mNofPartsGetter) { - return 1; - } return mNofPartsGetter(i); } @@ -94,7 +105,8 @@ class InputSpan return get(i).payload; } - /// an iterator class working on position within the a parent class + /// An iterator over the elements of a parent container using the DataRefIndices protocol. + /// ParentT must provide: initialIndices(), getAtIndices(DataRefIndices), nextIndices(DataRefIndices). template class Iterator { @@ -110,23 +122,23 @@ class InputSpan Iterator() = delete; - Iterator(ParentType const* parent, size_t position = 0, size_t size = 0) - : mPosition(position), mSize(size > position ? size : position), mParent(parent), mElement{} + Iterator(ParentType const* parent, bool isEnd = false) + : mParent(parent), + mCurrentIndices(isEnd ? parent->endIndices() : parent->initialIndices()), + mElement{} { - if (mPosition < mSize) { - mElement = mParent->get(mPosition); + if (mCurrentIndices != mParent->endIndices()) { + mElement = mParent->getAtIndices(mCurrentIndices); } } - ~Iterator() = default; - // prefix increment SelfType& operator++() { - if (mPosition < mSize && ++mPosition < mSize) { - mElement = mParent->get(mPosition); + mCurrentIndices = mParent->nextIndices(mCurrentIndices); + if (mCurrentIndices != mParent->endIndices()) { + mElement = mParent->getAtIndices(mCurrentIndices); } else { - // reset the element to the default value of the type mElement = ElementType{}; } return *this; @@ -145,16 +157,14 @@ class InputSpan return mElement; } - // comparison bool operator==(const SelfType& rh) const { - return mPosition == rh.mPosition; + return mCurrentIndices == rh.mCurrentIndices; } - // comparison - bool operator!=(const SelfType& rh) const + auto operator<=>(const SelfType& rh) const { - return mPosition != rh.mPosition; + return mCurrentIndices <=> rh.mCurrentIndices; } // return pointer to parent instance @@ -163,22 +173,21 @@ class InputSpan return mParent; } - // return current position + // return current position (headerIdx serves as the slot index for slot-level iteration) [[nodiscard]] size_t position() const { - return mPosition; + return mCurrentIndices.headerIdx; } private: - size_t mPosition; - size_t mSize; ParentType const* mParent; + DataRefIndices mCurrentIndices; ElementType mElement; }; /// @class InputSpanIterator - /// An iterator over the input slots - /// It supports an iterator interface to access the parts in the slot + /// An iterator over the input slots. + /// It supports an iterator interface to access the parts in the slot. template class InputSpanIterator : public Iterator { @@ -192,24 +201,26 @@ class InputSpan using iterator = Iterator; using const_iterator = Iterator; - InputSpanIterator(InputSpan const* parent, size_t position = 0, size_t size = 0) - : BaseType(parent, position, size) + InputSpanIterator(InputSpan const* parent, bool isEnd = false) + : BaseType(parent, isEnd) { } - /// Get element at {slotindex, partindex} - [[nodiscard]] ElementType get(size_t pos) const + /// Initial indices for part-level iteration: first part starts at {headerIdx=0, payloadIdx=1}. + [[nodiscard]] DataRefIndices initialIndices() const { return {0, 1}; } + /// Sentinel used by nextIndicesGetter to signal end-of-slot. + [[nodiscard]] DataRefIndices endIndices() const { return {size_t(-1), size_t(-1)}; } + + /// Get element at the given raw message indices in O(1). + [[nodiscard]] ElementType getAtIndices(DataRefIndices indices) const { - return this->parent()->get(this->position(), pos); + return this->parent()->getAtIndices(this->position(), indices); } - /// Check if slot is valid, index of part is not used - [[nodiscard]] bool isValid(size_t = 0) const + /// Advance @a current to the next part's indices in O(1). + [[nodiscard]] DataRefIndices nextIndices(DataRefIndices current) const { - if (this->position() < this->parent()->size()) { - return this->parent()->isValid(this->position()); - } - return false; + return this->parent()->nextIndices(this->position(), current); } /// Get number of parts in input slot @@ -218,15 +229,14 @@ class InputSpan return this->parent()->getNofParts(this->position()); } - // iterator for the part access [[nodiscard]] const_iterator begin() const { - return const_iterator(this, 0, size()); + return const_iterator(this, size() == 0); } [[nodiscard]] const_iterator end() const { - return const_iterator(this, size()); + return const_iterator(this, true); } }; @@ -236,19 +246,19 @@ class InputSpan // supporting read-only access and returning const_iterator [[nodiscard]] const_iterator begin() const { - return {this, 0, size()}; + return {this, false}; } - // supporting read-only access and returning const_iterator [[nodiscard]] const_iterator end() const { - return {this, size()}; + return {this, true}; } private: - std::function mGetter; std::function mNofPartsGetter; std::function mRefCountGetter; + std::function mIndicesGetter; + std::function mNextIndicesGetter; size_t mSize; }; diff --git a/Framework/Core/include/Framework/MessageSet.h b/Framework/Core/include/Framework/MessageSet.h deleted file mode 100644 index e7ae70e0ea2e5..0000000000000 --- a/Framework/Core/include/Framework/MessageSet.h +++ /dev/null @@ -1,184 +0,0 @@ -// 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. -#ifndef FRAMEWORK_MESSAGESET_H -#define FRAMEWORK_MESSAGESET_H - -#include "Framework/PartRef.h" -#include -#include -#include - -namespace o2 -{ -namespace framework -{ - -/// A set of inflight messages. -/// The messages are stored in a linear vector. Originally, an O2 message was -/// comprised of a header-payload pair which makes indexing of pairs in the -/// storage simple. To support O2 messages with multiple payloads in a future -/// update of the data model, a message index is needed to store position in the -/// linear storage and number of messages. -/// DPL InputRecord API is providing refs of header-payload pairs, the original -/// O2 message model. For this purpose, also the pair index is filled and can -/// be used to access header and payload associated with a pair -struct MessageSet { - struct Index { - Index(size_t p, size_t s) : position(p), size(s) {} - size_t position = 0; - size_t size = 0; - }; - // linear storage of messages - std::vector messages; - // message map describes O2 messages consisting of a header message and - // payload message(s), index describes position in the linear storage - std::vector messageMap; - // pair map describes all messages in one sequence of header-payload pairs and - // where in the message index the associated header and payload can be found - struct PairMapping { - PairMapping(size_t partId, size_t payloadId) : partIndex(partId), payloadIndex(payloadId) {} - // O2 message where the pair is located in - size_t partIndex = 0; - // payload index within the O2 message - size_t payloadIndex = 0; - }; - std::vector pairMap; - - MessageSet() - : messages(), messageMap(), pairMap() - { - } - - template - MessageSet(F getter, size_t size) - : messages(), messageMap(), pairMap() - { - add(std::forward(getter), size); - } - - MessageSet(MessageSet&& other) - : messages(std::move(other.messages)), messageMap(std::move(other.messageMap)), pairMap(std::move(other.pairMap)) - { - other.clear(); - } - - MessageSet& operator=(MessageSet&& other) - { - if (&other == this) { - return *this; - } - messages = std::move(other.messages); - messageMap = std::move(other.messageMap); - pairMap = std::move(other.pairMap); - other.clear(); - return *this; - } - - /// get number of in-flight O2 messages - size_t size() const - { - return messageMap.size(); - } - - /// get number of header-payload pairs - size_t getNumberOfPairs() const - { - return pairMap.size(); - } - - /// get number of payloads for an in-flight message - size_t getNumberOfPayloads(size_t mi) const - { - return messageMap[mi].size; - } - - /// clear the set - void clear() - { - messages.clear(); - messageMap.clear(); - pairMap.clear(); - } - - // this is more or less legacy - // PartRef has been earlier used to store fixed header-payload pairs - // reset the set and store content of the part ref - void reset(PartRef&& ref) - { - clear(); - add(std::move(ref)); - } - - // this is more or less legacy - // PartRef has been earlier used to store fixed header-payload pairs - // add content of the part ref - void add(PartRef&& ref) - { - pairMap.emplace_back(messageMap.size(), 0); - messageMap.emplace_back(messages.size(), 1); - messages.emplace_back(std::move(ref.header)); - messages.emplace_back(std::move(ref.payload)); - } - - /// add an O2 message - template - void add(F getter, size_t size) - { - auto partid = messageMap.size(); - messageMap.emplace_back(messages.size(), size - 1); - for (size_t i = 0; i < size; ++i) { - if (i > 0) { - pairMap.emplace_back(partid, i - 1); - } - messages.emplace_back(std::move(getter(i))); - } - } - - fair::mq::MessagePtr& header(size_t partIndex) - { - return messages[messageMap[partIndex].position]; - } - - fair::mq::MessagePtr& payload(size_t partIndex, size_t payloadIndex = 0) - { - assert(partIndex < messageMap.size()); - assert(messageMap[partIndex].position + payloadIndex + 1 < messages.size()); - return messages[messageMap[partIndex].position + payloadIndex + 1]; - } - - fair::mq::MessagePtr const& header(size_t partIndex) const - { - return messages[messageMap[partIndex].position]; - } - - fair::mq::MessagePtr const& payload(size_t partIndex, size_t payloadIndex = 0) const - { - assert(partIndex < messageMap.size()); - assert(messageMap[partIndex].position + payloadIndex + 1 < messages.size()); - return messages[messageMap[partIndex].position + payloadIndex + 1]; - } - - fair::mq::MessagePtr const& associatedHeader(size_t pos) const - { - return messages[messageMap[pairMap[pos].partIndex].position]; - } - - fair::mq::MessagePtr const& associatedPayload(size_t pos) const - { - auto partIndex = pairMap[pos].partIndex; - auto payloadIndex = pairMap[pos].payloadIndex; - return messages[messageMap[partIndex].position + payloadIndex + 1]; - } -}; - -} // namespace framework -} // namespace o2 -#endif // FRAMEWORK_MESSAGESET_H diff --git a/Framework/Core/include/Framework/ResourcePolicy.h b/Framework/Core/include/Framework/ResourcePolicy.h index eb8d77b209a8f..1062c223b07f6 100644 --- a/Framework/Core/include/Framework/ResourcePolicy.h +++ b/Framework/Core/include/Framework/ResourcePolicy.h @@ -31,6 +31,9 @@ struct ResourcePolicy { std::string name; Matcher matcher; ComputingQuotaRequest request; + /// Minimum resources required to run. Used to report which resources + /// are missing when scheduling fails. + ComputingQuotaOffer minRequired; }; } // namespace o2::framework diff --git a/Framework/Core/include/Framework/TableConsumer.h b/Framework/Core/include/Framework/TableConsumer.h index f2a041952470c..1924d0694097b 100644 --- a/Framework/Core/include/Framework/TableConsumer.h +++ b/Framework/Core/include/Framework/TableConsumer.h @@ -12,6 +12,7 @@ #ifndef FRAMEWORK_TABLECONSUMER_H #define FRAMEWORK_TABLECONSUMER_H +#include #include namespace arrow diff --git a/Framework/Core/include/Framework/TimesliceIndex.h b/Framework/Core/include/Framework/TimesliceIndex.h index ac3970bec00ee..ea612f701152c 100644 --- a/Framework/Core/include/Framework/TimesliceIndex.h +++ b/Framework/Core/include/Framework/TimesliceIndex.h @@ -126,7 +126,7 @@ class TimesliceIndex /// Find the lowest value for the timeslices in this instance. /// This is the minimum between all the per channel oldest possible timeslices - /// and the oldest possible timeslice in-fly which is still dirty. + /// and the oldest possible timeslice in flight which is still dirty. [[nodiscard]] OldestInputInfo getOldestPossibleInput() const; [[nodiscard]] OldestOutputInfo getOldestPossibleOutput() const; OldestOutputInfo updateOldestPossibleOutput(bool rewinded); diff --git a/Framework/Core/include/Framework/TypeTraits.h b/Framework/Core/include/Framework/TypeTraits.h index faa9055de3280..bb2d338f42af3 100644 --- a/Framework/Core/include/Framework/TypeTraits.h +++ b/Framework/Core/include/Framework/TypeTraits.h @@ -38,13 +38,18 @@ struct is_forced_non_messageable< typename std::enable_if::value>::type> : public std::true_type { }; +template +struct is_forced_trivially_copyable : std::false_type { +}; + // TODO: extend this to exclude structs with pointer data members // see e.g. https://stackoverflow.com/questions/32880990/how-to-check-if-class-has-pointers-in-c14 template -struct is_messageable : std::conditional::value && // - !std::is_polymorphic::value && // - !std::is_pointer::value && // - !is_forced_non_messageable::value, // +struct is_messageable : std::conditional<(std::is_trivially_copyable::value || // + framework::is_forced_trivially_copyable::value) && // + !std::is_polymorphic::value && // + !std::is_pointer::value && // + !is_forced_non_messageable::value, // std::true_type, std::false_type>::type { }; diff --git a/Framework/Core/scripts/dpl-mcp-server/README.md b/Framework/Core/scripts/dpl-mcp-server/README.md new file mode 100644 index 0000000000000..65d2378c5d756 --- /dev/null +++ b/Framework/Core/scripts/dpl-mcp-server/README.md @@ -0,0 +1,75 @@ +# DPL Status MCP Server + +An MCP server that connects to a running DPL driver's `/status` WebSocket endpoint and exposes its device state and metrics as tools for an AI assistant (e.g. Claude). + +## Requirements + +```bash +pip install mcp websockets +# or install the package directly: +pip install ./Framework/Core/scripts/dpl-mcp-server/ +``` + +## Running + +The driver port defaults to `8080`. Override with `--port`, `--pid`, or `DPL_STATUS_PORT`: + +```bash +python3 dpl_mcp_server.py --port 8080 +python3 dpl_mcp_server.py --pid 12345 # port = 8080 + pid % 30000 +DPL_STATUS_PORT=8080 python3 dpl_mcp_server.py +``` + +If installed as a package: + +```bash +dpl-mcp-server --pid $(pgrep -f diamond-workflow | head -1) +``` + +## Claude Code integration + +Add to `.mcp.json` in your project (or `~/.claude.json` for global use): + +```json +{ + "mcpServers": { + "dpl": { + "command": "dpl-mcp-server", + "args": ["--pid", "12345"] + } + } +} +``` + +Or with `claude mcp add`: + +```bash +claude mcp add dpl -- dpl-mcp-server --pid 12345 +``` + +## Available tools + +| Tool | Description | +|------|-------------| +| `list_devices` | List all devices with pid, active flag, streaming and device state | +| `list_metrics(device)` | List numeric metrics available for a device | +| `subscribe(device, metrics)` | Subscribe to metrics; driver will push updates when they change | +| `unsubscribe(device, metrics)` | Stop receiving updates for specific metrics | +| `get_updates(max_updates)` | Drain buffered update frames (default: up to 50) | + +## Protocol + +The driver sends a snapshot on connect, then pushes updates only for subscribed metrics that changed each processing cycle. There is no polling — updates arrive in real time as the workflow runs. + +``` +connect → {"type":"snapshot","devices":[{"name","pid","active","streamingState","deviceState"},...]} + +client → {"cmd":"list_metrics","device":"producer"} +driver → {"type":"metrics_list","device":"producer","metrics":["input-parts","output-bytes",...]} + +client → {"cmd":"subscribe","device":"producer","metrics":["output-bytes"]} +driver → {"type":"update","device":0,"name":"producer","metrics":{"output-bytes":1048576}} + (pushed every cycle in which output-bytes changed) + +client → {"cmd":"unsubscribe","device":"producer","metrics":["output-bytes"]} +``` diff --git a/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py b/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py new file mode 100644 index 0000000000000..ed457b8a57d9d --- /dev/null +++ b/Framework/Core/scripts/dpl-mcp-server/dpl_mcp_server.py @@ -0,0 +1,489 @@ +#!/usr/bin/env python3 +# Copyright 2019-2026 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. +"""DPL status MCP server. + +Bridges the DPL driver /status WebSocket endpoint to MCP tools so that an +AI assistant (e.g. Claude) can inspect and monitor a running DPL workflow. + +Supports multiple concurrent workflows. Use the ``connect`` tool to attach +to a running topology by port or PID, then pass the returned workflow name +to every other tool. + +Usage +----- + python3 dpl_mcp_server.py + +Wire protocol (client -> driver) +-------------------------------- + {"cmd":"list_metrics","device":""} + {"cmd":"subscribe","device":"","metrics":["m1","m2"]} + {"cmd":"unsubscribe","device":"","metrics":["m1"]} + +Wire protocol (driver -> client) +-------------------------------- + {"type":"snapshot","devices":[{"name","pid","active","streamingState","deviceState"},...]} + {"type":"update","device":,"name":"","metrics":{}} + {"type":"metrics_list","device":"","metrics":["m1","m2",...]} +""" + +from __future__ import annotations + +import asyncio +import json +import os +from typing import Any +from urllib.parse import urlparse + +import websockets +from mcp.server.fastmcp import FastMCP + + +# --------------------------------------------------------------------------- +# Per-workflow connection state +# --------------------------------------------------------------------------- +class WorkflowConnection: + """Holds WebSocket connection and buffered state for one DPL workflow.""" + + def __init__(self, *, url: str, name: str, extra_headers: dict[str, str] | None = None): + self.url = url + self.name = name + self.extra_headers = extra_headers or {} + self.ws: Any = None + self.reader_task: asyncio.Task | None = None + self.snapshot: dict = {} + self.updates: list[dict] = [] + self.logs: list[dict] = [] + self.metrics_lists: dict[str, list[str]] = {} + + async def ensure_connected(self) -> None: + """Connect (or reconnect) to the driver's /status WebSocket.""" + if self.ws is not None: + try: + pong = await asyncio.wait_for(self.ws.ping(), timeout=2.0) + await pong + return + except Exception: + old_ws = self.ws + self.ws = None + if self.reader_task is not None and not self.reader_task.done(): + self.reader_task.cancel() + try: + await self.reader_task + except (asyncio.CancelledError, Exception): + pass + self.reader_task = None + try: + await old_ws.close() + except Exception: + pass + + self.ws = await websockets.connect( + self.url, + subprotocols=["dpl"], + additional_headers=self.extra_headers if self.extra_headers else None, + ) + if self.reader_task is None or self.reader_task.done(): + self.reader_task = asyncio.create_task(self._reader()) + + async def _reader(self) -> None: + """Background task: read frames from the driver and buffer them.""" + try: + async for raw in self.ws: + try: + msg = json.loads(raw) + except json.JSONDecodeError: + continue + t = msg.get("type") + if t == "snapshot": + self.snapshot = msg + self.metrics_lists.clear() + elif t == "update": + self.updates.append(msg) + elif t == "log": + self.logs.append(msg) + elif t == "metrics_list": + device = msg.get("device", "") + self.metrics_lists[device] = msg.get("metrics", []) + except Exception: + pass + finally: + self.ws = None + + async def send(self, obj: dict) -> None: + await self.ensure_connected() + await self.ws.send(json.dumps(obj, separators=(",", ":"))) + + async def close(self) -> None: + ws = self.ws + self.ws = None + if self.reader_task is not None and not self.reader_task.done(): + self.reader_task.cancel() + try: + await self.reader_task + except (asyncio.CancelledError, Exception): + pass + self.reader_task = None + if ws is not None: + await ws.close() + + +# --------------------------------------------------------------------------- +# Workflow registry +# --------------------------------------------------------------------------- +_workflows: dict[str, WorkflowConnection] = {} + + +def _get(workflow: str) -> WorkflowConnection: + """Look up a workflow by name, raising a clear error if not found.""" + conn = _workflows.get(workflow) + if conn is None: + available = ", ".join(_workflows.keys()) if _workflows else "(none)" + raise ValueError( + f"No workflow named '{workflow}'. Connected workflows: {available}. " + f"Use the connect tool first." + ) + return conn + + +# --------------------------------------------------------------------------- +# MCP server definition +# --------------------------------------------------------------------------- +mcp = FastMCP("DPL Status") + + +@mcp.tool() +async def connect(port: int = 0, pid: int = 0, name: str = "") -> str: + """Connect to a running DPL workflow. + + Provide either ``port`` (the driver's WebSocket port) or ``pid`` (the + driver PID, port derived as 8080 + pid % 30000). An optional ``name`` + gives the workflow a human-friendly label; if omitted the port number is + used. + + Args: + port: TCP port of the DPL driver status WebSocket. + pid: PID of the DPL driver process (alternative to port). + name: Optional human-friendly name for this workflow. + """ + if pid: + port = 8080 + pid % 30000 + if not port: + return "Provide either port or pid." + + wf_name = name or str(port) + if wf_name in _workflows: + old = _workflows[wf_name] + await old.close() + + url = f"ws://localhost:{port}/status" + conn = WorkflowConnection(url=url, name=wf_name) + await conn.ensure_connected() + _workflows[wf_name] = conn + + devices = conn.snapshot.get("devices", []) + return ( + f"Connected to workflow '{wf_name}' on port {port} " + f"({len(devices)} device(s))." + ) + + +@mcp.tool() +async def connect_hyperloop(url: str, name: str = "", token: str = "") -> str: + """Connect to a DPL workflow running on Hyperloop via the remote proxy. + + Accepts a URL like: + https://alimonitor.cern.ch/train-workdir/remote-gui/remote_proxy.html?/ + + and remaps it to the local WebSocket proxy endpoint. + + Args: + url: The remote_proxy.html URL from alimonitor. + name: Optional human-friendly name for this workflow. + token: Hyperloop auth token. Falls back to HYPERLOOP_TOKEN env var. + """ + token = token or os.environ.get("HYPERLOOP_TOKEN", "") + if not token: + return "No token provided and HYPERLOOP_TOKEN environment variable is not set." + + parsed = urlparse(url) + path_suffix = parsed.query # everything after '?' + if not path_suffix: + return f"Cannot parse token/port from URL: {url}" + + ws_url = f"ws://localhost:8888/remote-mcp/o2/{path_suffix}/status" + wf_name = name or path_suffix.split("/")[-1] + + if wf_name in _workflows: + old = _workflows[wf_name] + await old.close() + + headers = {"Authorization": f"Bearer {token}"} + conn = WorkflowConnection(url=ws_url, name=wf_name, extra_headers=headers) + await conn.ensure_connected() + _workflows[wf_name] = conn + + devices = conn.snapshot.get("devices", []) + return ( + f"Connected to Hyperloop workflow '{wf_name}' via {ws_url} " + f"({len(devices)} device(s))." + ) + + +@mcp.tool() +async def disconnect(workflow: str) -> str: + """Disconnect from a DPL workflow and release its resources. + + Args: + workflow: Workflow name as returned by connect. + """ + conn = _get(workflow) + await conn.close() + del _workflows[workflow] + return f"Disconnected from workflow '{workflow}'." + + +@mcp.tool() +async def list_workflows() -> str: + """List all currently connected DPL workflows.""" + if not _workflows: + return "No workflows connected. Use the connect tool first." + lines = [] + for wf_name, conn in _workflows.items(): + n = len(conn.snapshot.get("devices", [])) + status = "connected" if conn.ws is not None else "disconnected" + lines.append(f"{wf_name}: port={conn.port} devices={n} status={status}") + return "\n".join(lines) + + +@mcp.tool() +async def list_devices(workflow: str) -> str: + """List all DPL devices with their current status. + + Returns each device's name, PID, active flag, streaming state, and device + state as reported by the driver snapshot. + + Args: + workflow: Workflow name as returned by connect. + """ + conn = _get(workflow) + await conn.ensure_connected() + if not conn.snapshot: + return "No snapshot received yet -- the driver may still be starting." + devices = conn.snapshot.get("devices", []) + if not devices: + return "No devices in snapshot." + lines = [] + for d in devices: + lines.append( + f"{d['name']}: pid={d['pid']} active={d['active']} " + f"streaming={d['streamingState']} state={d['deviceState']}" + ) + return "\n".join(lines) + + +@mcp.tool() +async def list_metrics(workflow: str, device: str) -> str: + """List the available numeric metrics for a DPL device. + + Sends a list_metrics command to the driver and waits up to 3 seconds for + the reply. Only numeric metrics (int, float, uint64) are included; string + and enum metrics are excluded. + + Args: + workflow: Workflow name as returned by connect. + device: Device name exactly as shown by list_devices. + """ + conn = _get(workflow) + conn.metrics_lists.pop(device, None) + await conn.send({"cmd": "list_metrics", "device": device}) + for _ in range(60): # up to 3 s + await asyncio.sleep(0.05) + if device in conn.metrics_lists: + names = conn.metrics_lists[device] + if not names: + return f"Device '{device}' has no numeric metrics yet." + return f"{len(names)} metric(s): " + ", ".join(names) + return f"No reply from driver for device '{device}' (timeout)." + + +@mcp.tool() +async def subscribe(workflow: str, device: str, metrics: list[str]) -> str: + """Subscribe to one or more metrics for a DPL device. + + After subscribing, the driver will push update frames for the device + whenever any of the subscribed metrics change. Use get_updates to drain + the buffer. + + Args: + workflow: Workflow name as returned by connect. + device: Device name exactly as shown by list_devices. + metrics: List of metric names to subscribe to (from list_metrics). + """ + conn = _get(workflow) + await conn.send({"cmd": "subscribe", "device": device, "metrics": metrics}) + return f"Subscribed to {len(metrics)} metric(s) for '{device}': {', '.join(metrics)}" + + +@mcp.tool() +async def unsubscribe(workflow: str, device: str, metrics: list[str]) -> str: + """Stop receiving updates for specific metrics of a DPL device. + + Args: + workflow: Workflow name as returned by connect. + device: Device name exactly as shown by list_devices. + metrics: List of metric names to unsubscribe from. + """ + conn = _get(workflow) + await conn.send({"cmd": "unsubscribe", "device": device, "metrics": metrics}) + return f"Unsubscribed from {len(metrics)} metric(s) for '{device}'." + + +@mcp.tool() +async def subscribe_logs(workflow: str, device: str) -> str: + """Subscribe to log output for a DPL device. + + After subscribing, new log lines from the device will be buffered and + can be retrieved with get_logs(). + + Args: + workflow: Workflow name as returned by connect. + device: Device name exactly as shown by list_devices. + """ + conn = _get(workflow) + await conn.send({"cmd": "subscribe_logs", "device": device}) + return f"Subscribed to logs for '{device}'." + + +@mcp.tool() +async def unsubscribe_logs(workflow: str, device: str) -> str: + """Stop receiving log output for a DPL device. + + Args: + workflow: Workflow name as returned by connect. + device: Device name exactly as shown by list_devices. + """ + conn = _get(workflow) + await conn.send({"cmd": "unsubscribe_logs", "device": device}) + return f"Unsubscribed from logs for '{device}'." + + +@mcp.tool() +async def get_logs(workflow: str, max_lines: int = 100) -> str: + """Drain and return buffered log lines received since the last call. + + Args: + workflow: Workflow name as returned by connect. + max_lines: Maximum number of log lines to return (default 100). + """ + conn = _get(workflow) + await conn.ensure_connected() + batch = conn.logs[:max_lines] + del conn.logs[:max_lines] + if not batch: + return "No buffered log lines." + lines = [] + for entry in batch: + device = entry.get("device", "?") + level = entry.get("level", "?") + line = entry.get("line", "") + lines.append(f"[{device}][{level}] {line}") + return "\n".join(lines) + + +@mcp.tool() +async def start_devices(workflow: str) -> str: + """Resume all stopped DPL devices (send SIGCONT). + + Use this when the workflow was started with -s (all devices paused). + + Args: + workflow: Workflow name as returned by connect. + """ + conn = _get(workflow) + await conn.send({"cmd": "start_devices"}) + return "Sent SIGCONT to all active devices." + + +@mcp.tool() +async def enable_signpost(workflow: str, device: str, streams: list[str]) -> str: + """Enable one or more signpost log streams for a DPL device. + + Signpost streams produce detailed trace output visible in the device logs. + Use get_logs() after subscribing to see the output. + + Known stream names (full form): ch.cern.aliceo2.device, + ch.cern.aliceo2.completion, ch.cern.aliceo2.monitoring_service, + ch.cern.aliceo2.data_processor_context, ch.cern.aliceo2.stream_context. + + Args: + workflow: Workflow name as returned by connect. + device: Device name as shown by list_devices, or "" for the driver. + streams: List of full signpost log names to enable. + """ + conn = _get(workflow) + await conn.send({"cmd": "enable_signpost", "device": device, "streams": streams}) + return f"Enabled {len(streams)} signpost stream(s) for '{device or 'driver'}': {', '.join(streams)}" + + +@mcp.tool() +async def disable_signpost(workflow: str, device: str, streams: list[str]) -> str: + """Disable one or more signpost log streams for a DPL device. + + Args: + workflow: Workflow name as returned by connect. + device: Device name as shown by list_devices, or "" for the driver. + streams: List of full signpost log names to disable. + """ + conn = _get(workflow) + await conn.send({"cmd": "disable_signpost", "device": device, "streams": streams}) + return f"Disabled {len(streams)} signpost stream(s) for '{device or 'driver'}': {', '.join(streams)}" + + +@mcp.tool() +async def get_updates(workflow: str, max_updates: int = 50) -> str: + """Drain and return buffered metric update frames received since the last call. + + Each frame contains the latest values of all subscribed metrics that + changed during that processing cycle. Calling this repeatedly gives a + time-ordered view of metric evolution. + + Args: + workflow: Workflow name as returned by connect. + max_updates: Maximum number of update frames to return (default 50). + """ + conn = _get(workflow) + await conn.ensure_connected() + batch = conn.updates[:max_updates] + del conn.updates[:max_updates] + if not batch: + return "No buffered updates." + lines = [] + for upd in batch: + name = upd.get("name") or f"device[{upd.get('device', '?')}]" + metrics = upd.get("metrics", {}) + if metrics: + parts = ", ".join(f"{k}={v}" for k, v in metrics.items()) + lines.append(f"{name}: {parts}") + else: + lines.append(f"{name}: (empty update)") + return "\n".join(lines) + + +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- +def main() -> None: + mcp.run() + + +if __name__ == "__main__": + main() diff --git a/Framework/Core/scripts/dpl-mcp-server/pyproject.toml b/Framework/Core/scripts/dpl-mcp-server/pyproject.toml new file mode 100644 index 0000000000000..f87c1b770c934 --- /dev/null +++ b/Framework/Core/scripts/dpl-mcp-server/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "dpl-mcp-server" +version = "0.1.0" +description = "MCP server for monitoring DPL (Data Processing Layer) workflows" +requires-python = ">=3.11" +dependencies = [ + "mcp>=1.0.0", + "websockets>=12.0", +] + +[project.scripts] +dpl-mcp-server = "dpl_mcp_server:main" + +[tool.hatch.build.targets.wheel] +include = ["dpl_mcp_server.py"] diff --git a/Framework/Core/scripts/lldb_o2_formatters.py b/Framework/Core/scripts/lldb_o2_formatters.py new file mode 100644 index 0000000000000..84bc7cda8dac3 --- /dev/null +++ b/Framework/Core/scripts/lldb_o2_formatters.py @@ -0,0 +1,296 @@ +# Copyright 2019-2026 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. +# +# lldb data formatters for o2::framework types. +# +# Usage: add to ~/.lldbinit or a project .lldbinit: +# command script import /path/to/O2/Framework/Core/scripts/lldb_o2_formatters.py + +import lldb + +# o2::framework::VariantType enum values (must match Variant.h) +_VARIANT_TYPE = { + 0: 'Int', + 1: 'Int64', + 2: 'Float', + 3: 'Double', + 4: 'String', + 5: 'Bool', + 6: 'ArrayInt', + 7: 'ArrayFloat', + 8: 'ArrayDouble', + 9: 'ArrayBool', + 10: 'ArrayString', + 11: 'Array2DInt', + 12: 'Array2DFloat', + 13: 'Array2DDouble', + 14: 'LabeledArrayInt', + 15: 'LabeledArrayFloat', + 16: 'LabeledArrayDouble', + 17: 'UInt8', + 18: 'UInt16', + 19: 'UInt32', + 20: 'UInt64', + 21: 'Int8', + 22: 'Int16', + 23: 'LabeledArrayString', + 24: 'Empty', + 25: 'Dict', + 26: 'Unknown', +} + +# Map VariantType value → (C type name for FindFirstType, is_pointer_to_value) +# is_pointer_to_value=True means mStore holds a T* pointing to heap data (arrays) +_SIMPLE_TYPES = { + 0: ('int', False), + 1: ('long long', False), + 2: ('float', False), + 3: ('double', False), + 5: ('bool', False), + 17: ('unsigned char', False), + 18: ('unsigned short',False), + 19: ('unsigned int', False), + 20: ('unsigned long long', False), + 21: ('signed char', False), + 22: ('short', False), +} + +_ARRAY_ELEM_TYPES = { + 6: 'int', + 7: 'float', + 8: 'double', + 9: 'bool', +} + +MAX_ARRAY_DISPLAY = 16 + + +import struct as _struct + + +def _read_pointer(process, addr): + err = lldb.SBError() + ptr_size = process.GetAddressByteSize() + data = process.ReadMemory(addr, ptr_size, err) + if err.Fail() or not data: + return None + fmt = '"' + b = raw if isinstance(raw[0], int) else bytes(ord(c) for c in raw) + if (b[0] & 1) == 0: # short form + size = b[0] >> 1 + text = b[1:1 + size].decode('utf-8', errors='replace') + else: # long form + size = _struct.unpack_from('"' + heap = process.ReadMemory(data_ptr, min(size, 512), err) + if err.Fail() or not heap: + return '""' + h = heap if isinstance(heap[0], int) else bytes(ord(c) for c in heap) + text = h[:size].decode('utf-8', errors='replace') + if size > 512: + text += '...' + return f'"{text}"' + + +def variant_summary(valobj, _internal_dict): + # Use GetNonSyntheticValue() so we see the real struct members even when + # the synthetic provider has replaced the children with decoded values. + raw = valobj.GetNonSyntheticValue() + mType_val = raw.GetChildMemberWithName('mType') + if not mType_val.IsValid(): + return '' + + mType = mType_val.GetValueAsUnsigned(26) # default Unknown + mSize = raw.GetChildMemberWithName('mSize').GetValueAsUnsigned(1) + mStore = raw.GetChildMemberWithName('mStore') + store_addr = mStore.GetLoadAddress() + + type_name = _VARIANT_TYPE.get(mType, f'') + target = valobj.GetTarget() + process = valobj.GetProcess() + + # --- simple scalar types --- + if mType in _SIMPLE_TYPES: + ctype, _ = _SIMPLE_TYPES[mType] + t = target.FindFirstType(ctype) + if t.IsValid(): + v = valobj.CreateValueFromAddress('v', store_addr, t) + return f'{type_name}({v.GetValue()})' + return f'{type_name}(?)' + + # --- String (const char* stored in mStore) --- + if mType == 4: + ptr = _read_pointer(process, store_addr) + if ptr and ptr != 0: + s = _read_cstring(process, ptr) + return f'String("{s}")' + return 'String(null)' + + # --- C-style numeric arrays (int*, float*, double*, bool*) --- + if mType in _ARRAY_ELEM_TYPES: + elem_type_name = _ARRAY_ELEM_TYPES[mType] + ptr = _read_pointer(process, store_addr) + if not ptr or ptr == 0: + return f'{type_name}(null)' + elem_t = target.FindFirstType(elem_type_name) + if not elem_t.IsValid(): + return f'{type_name}(? x {mSize})' + count = min(mSize, MAX_ARRAY_DISPLAY) + items = [] + for i in range(count): + v = valobj.CreateValueFromAddress(f'e{i}', ptr + i * elem_t.GetByteSize(), elem_t) + items.append(v.GetValue() or '?') + result = f'{type_name}([{", ".join(items)}]' + if mSize > MAX_ARRAY_DISPLAY: + result += f', ... ({mSize} total)' + result += ')' + return result + + # --- ArrayString: std::vector stored via placement new in mStore --- + if mType == 10: + # libc++ std::vector layout: __begin_, __end_, __end_cap_ (all pointers) + # libc++ std::string is always 24 bytes on 64-bit (SSO layout) + STR_SIZE = 24 + ptr_size = process.GetAddressByteSize() + begin_ptr = _read_pointer(process, store_addr) + end_ptr = _read_pointer(process, store_addr + ptr_size) + if begin_ptr is None or end_ptr is None: + return 'ArrayString(?)' + + count = (end_ptr - begin_ptr) // STR_SIZE if end_ptr >= begin_ptr else 0 + items = [] + for i in range(min(count, MAX_ARRAY_DISPLAY)): + items.append(_read_libcxx_string(process, begin_ptr + i * STR_SIZE)) + result = f'ArrayString([{", ".join(items)}]' + if count > MAX_ARRAY_DISPLAY: + result += f', ... ({count} total)' + result += ')' + return result + + return f'{type_name}(mSize={mSize})' + + +class VariantSyntheticProvider: + """Synthetic children for o2::framework::Variant — exposes decoded value as child.""" + + def __init__(self, valobj, _internal_dict): + self.valobj = valobj + self.children = [] + + def num_children(self): + return len(self.children) + + def get_child_index(self, name): + for i, (n, _) in enumerate(self.children): + if n == name: + return i + return -1 + + def get_child_at_index(self, index): + if 0 <= index < len(self.children): + return self.children[index][1] + return None + + def update(self): + self.children = [] + # Use GetNonSyntheticValue() to read the real struct members. + raw = self.valobj.GetNonSyntheticValue() + mType = raw.GetChildMemberWithName('mType').GetValueAsUnsigned(26) + mSize = raw.GetChildMemberWithName('mSize').GetValueAsUnsigned(1) + mStore = raw.GetChildMemberWithName('mStore') + store_addr = mStore.GetLoadAddress() + target = self.valobj.GetTarget() + process = self.valobj.GetProcess() + + if mType in _SIMPLE_TYPES: + ctype, _ = _SIMPLE_TYPES[mType] + t = target.FindFirstType(ctype) + if t.IsValid(): + v = self.valobj.CreateValueFromAddress('value', store_addr, t) + self.children.append(('value', v)) + + elif mType == 4: # String + ptr = _read_pointer(process, store_addr) + if ptr and ptr != 0: + char_t = target.FindFirstType('char').GetPointerType() + v = self.valobj.CreateValueFromAddress('value', store_addr, char_t) + self.children.append(('value', v)) + + elif mType in _ARRAY_ELEM_TYPES: + elem_type_name = _ARRAY_ELEM_TYPES[mType] + ptr = _read_pointer(process, store_addr) + if ptr and ptr != 0: + elem_t = target.FindFirstType(elem_type_name) + if elem_t.IsValid(): + for i in range(min(mSize, MAX_ARRAY_DISPLAY)): + v = self.valobj.CreateValueFromAddress(f'[{i}]', ptr + i * elem_t.GetByteSize(), elem_t) + self.children.append((f'[{i}]', v)) + + elif mType == 10: # ArrayString + # std::vector via placement new; std::string = 24 bytes (libc++ 64-bit) + STR_SIZE = 24 + ptr_size = process.GetAddressByteSize() + begin_ptr = _read_pointer(process, store_addr) + end_ptr = _read_pointer(process, store_addr + ptr_size) + char_t = target.FindFirstType('char') + if begin_ptr is not None and end_ptr is not None and end_ptr >= begin_ptr and char_t.IsValid(): + count = (end_ptr - begin_ptr) // STR_SIZE + err = lldb.SBError() + for i in range(min(count, MAX_ARRAY_DISPLAY)): + str_addr = begin_ptr + i * STR_SIZE + raw = process.ReadMemory(str_addr, STR_SIZE, err) + if err.Fail() or not raw: + continue + b = raw if isinstance(raw[0], int) else bytes(ord(c) for c in raw) + if (b[0] & 1) == 0: # short form: data inline at offset 1 + data_addr = str_addr + 1 + sz = max(b[0] >> 1, 1) + else: # long form: data pointer at offset 16 + data_addr = _struct.unpack_from(' 0 + + +def __lldb_init_module(debugger, _internal_dict): + debugger.HandleCommand( + 'type summary add -x "^o2::framework::Variant$" ' + '--python-function lldb_o2_formatters.variant_summary' + ) + debugger.HandleCommand( + 'type synthetic add -x "^o2::framework::Variant$" ' + '--python-class lldb_o2_formatters.VariantSyntheticProvider' + ) + print('o2::framework::Variant formatters loaded.') diff --git a/Framework/Core/src/AnalysisDataModel.cxx b/Framework/Core/src/AnalysisDataModel.cxx index 906772234d608..fbbabc35f0aab 100644 --- a/Framework/Core/src/AnalysisDataModel.cxx +++ b/Framework/Core/src/AnalysisDataModel.cxx @@ -12,12 +12,12 @@ namespace o2::soa { -template struct JoinFull, aod::BCs, aod::Timestamps>; -template struct JoinFull, aod::Tracks, aod::TracksExtra>; -template struct JoinFull, aod::Tracks, aod::TracksCov, aod::TracksExtra>; -template struct JoinFull, aod::FwdTracks, aod::FwdTracksCov>; -template struct JoinFull, aod::Collisions, aod::Run2MatchedSparse>; -template struct JoinFull, aod::Collisions, aod::Run3MatchedSparse>; +template struct Join; +template struct Join; +template struct Join; +template struct Join; +template struct Join; +template struct Join; -template struct JoinFull, aod::TracksExtension, aod::StoredTracks>; +template struct Join; } // namespace o2::soa diff --git a/Framework/Core/src/AnalysisHelpers.cxx b/Framework/Core/src/AnalysisHelpers.cxx index b7eac692d3859..149664c42caba 100644 --- a/Framework/Core/src/AnalysisHelpers.cxx +++ b/Framework/Core/src/AnalysisHelpers.cxx @@ -201,4 +201,5 @@ std::shared_ptr Builder::materialize(ProcessingContext& pc) result = o2::soa::IndexBuilder::materialize(*builders.get(), std::move(tables), records, outputSchema, exclusive); return result; } + } // namespace o2::framework diff --git a/Framework/Core/src/AnalysisSupportHelpers.cxx b/Framework/Core/src/AnalysisSupportHelpers.cxx index 7453751315626..35228bba531b0 100644 --- a/Framework/Core/src/AnalysisSupportHelpers.cxx +++ b/Framework/Core/src/AnalysisSupportHelpers.cxx @@ -175,9 +175,14 @@ void AnalysisSupportHelpers::addMissingOutputsToBuilder(std::vector c // FIXME: until we have a single list of pairs additionalInputs | views::partial_match_filter(AODOrigins) | + std::ranges::views::filter([](InputSpec const& input) { + return std::ranges::none_of(input.metadata, [](ConfigParamSpec const& p) { return (p.name.compare("projectors") == 0) || (p.name.compare("index-records") == 0); }); + }) | sinks::update_input_list{requestedAODs}; // update requestedAODs additionalInputs | - views::partial_match_filter(header::DataOrigin{"DYN"}) | + std::ranges::views::filter([](InputSpec const& input) { + return std::ranges::any_of(input.metadata, [](ConfigParamSpec const& p) { return p.name.compare("projectors") == 0; }); + }) | sinks::update_input_list{requestedDYNs}; // update requestedDYNs } @@ -189,7 +194,7 @@ DataProcessorSpec AnalysisSupportHelpers::getOutputObjHistSink(ConfigContext con DataProcessorSpec spec{ .name = "internal-dpl-aod-global-analysis-file-sink", .inputs = {InputSpec("x", DataSpecUtils::dataDescriptorMatcherFrom(header::DataOrigin{"ATSK"}), Lifetime::Sporadic)}, - .outputs = {}, + .outputs = {OutputSpec{OutputLabel{"dummy"}, o2::header::DataOrigin{"DUMM"}, o2::header::DataDescription{"DUMMY"}, 0, Lifetime::Sporadic}}, .algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkAnalysisSupport", "ROOTObjWriter", ctx), }; diff --git a/Framework/Core/src/ArrowSupport.cxx b/Framework/Core/src/ArrowSupport.cxx index 81acc26b1b097..eecff4ce87c74 100644 --- a/Framework/Core/src/ArrowSupport.cxx +++ b/Framework/Core/src/ArrowSupport.cxx @@ -34,6 +34,7 @@ #include "Framework/ServiceRegistryHelpers.h" #include "Framework/Signpost.h" #include "Framework/DefaultsHelpers.h" +#include "Framework/ConfigParamsHelper.h" #include "CommonMessageBackendsHelpers.h" #include @@ -50,7 +51,6 @@ O2_DECLARE_DYNAMIC_LOG(rate_limiting); namespace o2::framework { - class EndOfStreamContext; class ProcessingContext; @@ -311,12 +311,12 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() static auto totalMessagesDestroyedMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-arrow-messages-destroyed"); static auto totalTimeframesReadMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-timeframes-read"); static auto totalTimeframesConsumedMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-timeframes-consumed"); - static auto totalTimeframesInFlyMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-timeframes-in-fly"); + static auto totalTimeframesInFlightMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-timeframes-in-flight"); static auto totalTimeslicesStartedMetric = createUint64DriverMetric("total-timeslices-started"); static auto totalTimeslicesExpiredMetric = createUint64DriverMetric("total-timeslices-expired"); static auto totalTimeslicesDoneMetric = createUint64DriverMetric("total-timeslices-done"); - static auto totalTimeslicesInFlyMetric = createIntDriverMetric("total-timeslices-in-fly"); + static auto totalTimeslicesInFlightMetric = createIntDriverMetric("total-timeslices-in-flight"); static auto totalBytesDeltaMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "arrow-bytes-delta"); static auto changedCountMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "changed-metrics-count"); @@ -458,11 +458,11 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() totalMessagesDestroyedMetric(driverMetrics, totalMessagesDestroyed, timestamp); totalTimeframesReadMetric(driverMetrics, totalTimeframesRead, timestamp); totalTimeframesConsumedMetric(driverMetrics, totalTimeframesConsumed, timestamp); - totalTimeframesInFlyMetric(driverMetrics, (int)(totalTimeframesRead - totalTimeframesConsumed), timestamp); + totalTimeframesInFlightMetric(driverMetrics, (int)(totalTimeframesRead - totalTimeframesConsumed), timestamp); totalTimeslicesStartedMetric(driverMetrics, totalTimeslicesStarted, timestamp); totalTimeslicesExpiredMetric(driverMetrics, totalTimeslicesExpired, timestamp); totalTimeslicesDoneMetric(driverMetrics, totalTimeslicesDone, timestamp); - totalTimeslicesInFlyMetric(driverMetrics, (int)(totalTimeslicesStarted - totalTimeslicesDone), timestamp); + totalTimeslicesInFlightMetric(driverMetrics, (int)(totalTimeslicesStarted - totalTimeslicesDone), timestamp); totalBytesDeltaMetric(driverMetrics, totalBytesCreated - totalBytesExpired - totalBytesDestroyed, timestamp); } else { unchangedCount++; @@ -578,45 +578,108 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() } }, .adjustTopology = [](WorkflowSpecNode& node, ConfigContext const& ctx) { auto& workflow = node.specs; - auto spawner = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { return spec.name.starts_with("internal-dpl-aod-spawner"); }); - auto analysisCCDB = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { return spec.name.starts_with("internal-dpl-aod-ccdb"); }); - auto builder = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { return spec.name.starts_with("internal-dpl-aod-index-builder"); }); - auto writer = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { return spec.name.starts_with("internal-dpl-aod-writer"); }); auto& dec = ctx.services().get(); dec.requestedAODs.clear(); dec.requestedDYNs.clear(); - dec.providedDYNs.clear(); - dec.providedTIMs.clear(); - dec.requestedTIMs.clear(); auto inputSpecLessThan = [](InputSpec const& lhs, InputSpec const& rhs) { return DataSpecUtils::describe(lhs) < DataSpecUtils::describe(rhs); }; auto outputSpecLessThan = [](OutputSpec const& lhs, OutputSpec const& rhs) { return DataSpecUtils::describe(lhs) < DataSpecUtils::describe(rhs); }; + auto builder = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { return spec.name.starts_with("internal-dpl-aod-index-builder"); }); if (builder != workflow.end()) { // collect currently requested IDXs dec.requestedIDXs.clear(); + dec.providedIDXs.clear(); for (auto& d : workflow | views::exclude_by_name(builder->name)) { d.inputs | - views::partial_match_filter(header::DataOrigin{"IDX"}) | + views::filter_with_params_by_name("index-records") | sinks::update_input_list{dec.requestedIDXs}; + d.outputs | + views::filter_with_params_by_name("index-records") | + sinks::update_output_list{dec.providedIDXs}; } + std::ranges::sort(dec.requestedIDXs, inputSpecLessThan); + std::ranges::sort(dec.providedIDXs, outputSpecLessThan); + dec.builderInputs.clear(); + dec.requestedIDXs | + views::filter_not_matching(dec.providedIDXs) | + sinks::append_to{dec.builderInputs}; // recreate inputs and outputs builder->inputs.clear(); builder->outputs.clear(); + AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.builderInputs, dec.requestedAODs, dec.requestedDYNs, *builder); + if (!builder->inputs.empty()) { + // load real AlgorithmSpec before deployment + builder->algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkOnDemandTablesSupport", "IndexTableBuilder", ctx); + } + } + auto analysisCCDB = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { return spec.name.starts_with("internal-dpl-aod-ccdb"); }); + if (analysisCCDB != workflow.end()) { + dec.requestedTIMs.clear(); + dec.providedTIMs.clear(); + for (auto& d : workflow | views::exclude_by_name(analysisCCDB->name)) { + d.inputs | + views::filter_with_params_by_name_starting("ccdb:") | + sinks::update_input_list{dec.requestedTIMs}; + d.outputs | + views::filter_with_params_by_name_starting("ccdb:") | + sinks::append_to{dec.providedTIMs}; + } + std::ranges::sort(dec.requestedTIMs, inputSpecLessThan); + std::ranges::sort(dec.providedTIMs, outputSpecLessThan); + // Use ranges::to> in C++23... + dec.analysisCCDBInputs.clear(); + dec.requestedTIMs | + views::filter_not_matching(dec.providedTIMs) | + sinks::append_to{dec.analysisCCDBInputs}; + + // recreate inputs and outputs + analysisCCDB->outputs.clear(); + analysisCCDB->inputs.clear(); + AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.analysisCCDBInputs, dec.requestedAODs, dec.requestedDYNs, *analysisCCDB); + // Register each ccdb: column path as an actual device option on the CCDB + // device so it can be read from ConfigParamRegistry at runtime. + // If any analysis task declared a Configurable with the same + // "ccdb:fXxx" name, prefer its default over the compile-time ::query value. + // First encountered wins; log a warning if two tasks declare conflicting defaults. + for (auto& input : dec.analysisCCDBInputs) { + for (auto& m : input.metadata | std::views::filter(checks::has_params_with_name_starting("ccdb:"))) { + ConfigParamSpec effective = m; // start with compile-time default + bool foundFirst = false; + for (auto& d : workflow | views::exclude_by_name(analysisCCDB->name)) { + for (auto& opt : d.options) { + if (opt.name == m.name) { + if (!foundFirst) { + effective = opt; // first task Configurable wins + foundFirst = true; + } else if (opt.defaultValue.asString() != effective.defaultValue.asString()) { + LOGP(warn, "Task '{}' declares Configurable '{}' = '{}' which conflicts " + "with an earlier value '{}'; earlier value will be used.", + d.name, opt.name, opt.defaultValue.asString(), + effective.defaultValue.asString()); + } + break; + } + } + } + ConfigParamsHelper::addOptionIfMissing(analysisCCDB->options, effective); + } + } // load real AlgorithmSpec before deployment - builder->algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkOnDemandTablesSupport", "IndexTableBuilder", ctx); - AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.requestedIDXs, dec.requestedAODs, dec.requestedDYNs, *builder); + analysisCCDB->algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkCCDBSupport", "AnalysisCCDBFetcherPlugin", ctx); } + auto spawner = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { return spec.name.starts_with("internal-dpl-aod-spawner"); }); if (spawner != workflow.end()) { + dec.providedDYNs.clear(); // collect currently requested DYNs for (auto& d : workflow | views::exclude_by_name(spawner->name)) { d.inputs | - views::partial_match_filter(header::DataOrigin{"DYN"}) | + views::filter_with_params_by_name("projectors") | sinks::update_input_list{dec.requestedDYNs}; d.outputs | - views::partial_match_filter(header::DataOrigin{"DYN"}) | + views::filter_with_params_by_name("projectors") | sinks::append_to{dec.providedDYNs}; } std::ranges::sort(dec.requestedDYNs, inputSpecLessThan); @@ -628,32 +691,14 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() // recreate inputs and outputs spawner->outputs.clear(); spawner->inputs.clear(); - - // load real AlgorithmSpec before deployment - spawner->algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkOnDemandTablesSupport", "ExtendedTableSpawner", ctx); AnalysisSupportHelpers::addMissingOutputsToSpawner({}, dec.spawnerInputs, dec.requestedAODs, *spawner); - } - - if (analysisCCDB != workflow.end()) { - for (auto& d : workflow | views::exclude_by_name(analysisCCDB->name)) { - d.inputs | views::partial_match_filter(header::DataOrigin{"ATIM"}) | sinks::update_input_list{dec.requestedTIMs}; - d.outputs | views::partial_match_filter(header::DataOrigin{"ATIM"}) | sinks::append_to{dec.providedTIMs}; + if (!spawner->inputs.empty()) { + // load real AlgorithmSpec before deployment + spawner->algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkOnDemandTablesSupport", "ExtendedTableSpawner", ctx); } - std::ranges::sort(dec.requestedTIMs, inputSpecLessThan); - std::ranges::sort(dec.providedTIMs, outputSpecLessThan); - // Use ranges::to> in C++23... - dec.analysisCCDBInputs.clear(); - dec.requestedTIMs | views::filter_not_matching(dec.providedTIMs) | sinks::append_to{dec.analysisCCDBInputs}; - - // recreate inputs and outputs - analysisCCDB->outputs.clear(); - analysisCCDB->inputs.clear(); - // load real AlgorithmSpec before deployment - // FIXME how can I make the lookup depend on DYN tables as well?? - analysisCCDB->algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkCCDBSupport", "AnalysisCCDBFetcherPlugin", ctx); - AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.analysisCCDBInputs, dec.requestedAODs, dec.requestedDYNs, *analysisCCDB); } + auto writer = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { return spec.name.starts_with("internal-dpl-aod-writer"); }); if (writer != workflow.end()) { workflow.erase(writer); } @@ -681,13 +726,13 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() } else { // load reader algorithm before deployment auto tfnsource = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { - return std::ranges::any_of(spec.outputs, [](OutputSpec const& output) { + return !spec.name.starts_with("internal-dpl-aod-reader") && std::ranges::any_of(spec.outputs, [](OutputSpec const& output) { return DataSpecUtils::match(output, "TFN", "TFNumber", 0); }); }); if (tfnsource == workflow.end()) { // add normal reader algorithm only if no on-the-fly generator is injected reader->algorithm = CommonDataProcessors::wrapWithTimesliceConsumption(PluginManager::loadAlgorithmFromPlugin("O2FrameworkAnalysisSupport", "ROOTFileReader", ctx)); - } // otherwise the algorithm was set in injectServiceDevices + } // otherwise the algorithm was already set in injectServiceDevices } } diff --git a/Framework/Core/src/CommonServices.cxx b/Framework/Core/src/CommonServices.cxx index 06bc7969ebf1e..2cdd046dedc34 100644 --- a/Framework/Core/src/CommonServices.cxx +++ b/Framework/Core/src/CommonServices.cxx @@ -589,6 +589,46 @@ auto decongestionCallbackOrdered = [](AsyncTask& task, size_t id) -> void { } }; +// Callback for consumeWhenPastOldestPossibleTimeframe. +// Runs in the async queue at the beginning of the next iteration, +// after Retry slots unblocked by an oldestPossibleInput change have +// been consumed and freed. Rescans all slots and forwards the +// (now up-to-date) oldestPossibleOutput downstream. +auto decongestionCallbackPastOldest = [](AsyncTask& task, size_t id) -> void { + auto& ref = task.user().ref; + + auto& decongestion = ref.get(); + auto& timesliceIndex = ref.get(); + auto& relayer = ref.get(); + auto& proxy = ref.get(); + O2_SIGNPOST_ID_GENERATE(cid, async_queue); + + timesliceIndex.rescan(); + timesliceIndex.updateOldestPossibleOutput(decongestion.nextEnumerationTimesliceRewinded); + auto oldestPossibleOutput = relayer.getOldestPossibleOutput(); + + if (oldestPossibleOutput.timeslice.value <= decongestion.lastTimeslice) { + O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "oldest_possible_timeslice", + "consumeWhenPastOldestPossibleTimeframe: not forwarding already sent value %" PRIu64, + (uint64_t)oldestPossibleOutput.timeslice.value); + return; + } + O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "oldest_possible_timeslice", + "consumeWhenPastOldestPossibleTimeframe: forwarding oldest possible timeslice %" PRIu64, + (uint64_t)oldestPossibleOutput.timeslice.value); + DataProcessingHelpers::broadcastOldestPossibleTimeslice(ref, oldestPossibleOutput.timeslice.value); + + for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) { + auto& info = proxy.getForwardChannelInfo(ChannelIndex{fi}); + auto& state = proxy.getForwardChannelState(ChannelIndex{fi}); + if (info.channelType != ChannelAccountingType::DPL) { + continue; + } + DataProcessingHelpers::sendOldestPossibleTimeframe(ref, info, state, oldestPossibleOutput.timeslice.value); + } + decongestion.lastTimeslice = oldestPossibleOutput.timeslice.value; +}; + // Decongestion service // If we do not have any Timeframe input, it means we must be creating timeslices // in order and that we should propagate the oldest possible timeslice at the end @@ -705,6 +745,22 @@ o2::framework::ServiceSpec timesliceIndex.updateOldestPossibleOutput(decongestion.nextEnumerationTimesliceRewinded); auto oldestPossibleOutput = relayer.getOldestPossibleOutput(); + // When consumeWhenPastOldestPossibleTimeframe is active, we always + // schedule the callback even when oldestPossibleOutput has not changed + // yet. Retry slots held by this policy will be consumed after this + // domainInfoUpdated call (once getReadyToProcess re-checks them), and + // the callback — running in the next iteration — will recompute + // oldestPossibleOutput and forward the updated value downstream. + if (decongestion.consumeWhenPastOldestPossibleTimeframeActive) { + auto& queue = services.get(); + AsyncQueueHelpers::post( + queue, AsyncTask{.timeslice = TimesliceId{oldestPossibleTimeslice}, + .id = decongestion.oldestPossibleTimesliceTask, + .debounce = -1, + .callback = decongestionCallbackPastOldest} + .user({.ref = services, .oldestPossibleOutput = oldestPossibleOutput})); + } + if (oldestPossibleOutput.timeslice.value == decongestion.lastTimeslice) { O2_SIGNPOST_EVENT_EMIT(data_processor_context, cid, "oldest_possible_timeslice", "Synchronous: Not sending already sent value: %" PRIu64, (uint64_t)oldestPossibleOutput.timeslice.value); return; @@ -1176,6 +1232,22 @@ o2::framework::ServiceSpec CommonServices::dataProcessingStats() .scope = Scope::DPL, .minPublishInterval = 1000, .maxRefreshLatency = 10000, + .sendInitialValue = true}, + MetricSpec{.name = "ccdb-cache-fetched-bytes", + .enabled = true, + .metricId = static_cast(ProcessingStatsId::CCDB_CACHE_FETCHED_BYTES), + .kind = Kind::UInt64, + .scope = Scope::DPL, + .minPublishInterval = 1000, + .maxRefreshLatency = 10000, + .sendInitialValue = true}, + MetricSpec{.name = "ccdb-cache-requested-bytes", + .enabled = true, + .metricId = static_cast(ProcessingStatsId::CCDB_CACHE_REQUESTED_BYTES), + .kind = Kind::UInt64, + .scope = Scope::DPL, + .minPublishInterval = 1000, + .maxRefreshLatency = 10000, .sendInitialValue = true}}; for (auto& metric : metrics) { diff --git a/Framework/Core/src/CompletionPolicyHelpers.cxx b/Framework/Core/src/CompletionPolicyHelpers.cxx index 2b49b8dfa9acd..e5a91ae58f899 100644 --- a/Framework/Core/src/CompletionPolicyHelpers.cxx +++ b/Framework/Core/src/CompletionPolicyHelpers.cxx @@ -267,6 +267,8 @@ CompletionPolicy CompletionPolicyHelpers::consumeWhenAnyZeroCount(const char* na CompletionPolicy CompletionPolicyHelpers::consumeWhenPastOldestPossibleTimeframe(const char* name, CompletionPolicy::Matcher matcher) { auto callback = [](InputSpan const& inputs, std::vector const&, ServiceRegistryRef& ref) -> CompletionPolicy::CompletionOp { + auto& decongestionService = ref.get(); + decongestionService.consumeWhenPastOldestPossibleTimeframeActive = true; size_t currentTimeslice = -1; for (auto& input : inputs) { if (input.header == nullptr) { @@ -325,9 +327,9 @@ CompletionPolicy CompletionPolicyHelpers::consumeWhenAnyWithAllConditions(const // But I don't see any possibility to handle this in a better way. // Iterate on all specs and all inputs simultaneously - for (size_t i = 0; i < inputs.size(); ++i) { - char const* header = inputs.header(i); - auto& spec = specs[i]; + for (auto it = inputs.begin(), end = inputs.end(); it != end; ++it) { + char const* header = (*it).header; + auto& spec = specs[it.position()]; // In case a condition object is not there, we need to wait. if (header != nullptr) { canConsume = true; diff --git a/Framework/Core/src/ComputingQuotaEvaluator.cxx b/Framework/Core/src/ComputingQuotaEvaluator.cxx index 3f5bff2b53fab..5dd4249cab519 100644 --- a/Framework/Core/src/ComputingQuotaEvaluator.cxx +++ b/Framework/Core/src/ComputingQuotaEvaluator.cxx @@ -62,7 +62,7 @@ struct QuotaEvaluatorStats { std::vector expired; }; -bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& selector, uint64_t now) +bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& selector, uint64_t now, ComputingQuotaOffer* outAccumulated) { O2_SIGNPOST_ID_GENERATE(qid, quota); @@ -102,10 +102,13 @@ bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& } dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCES_SATISFACTORY), DataProcessingStats::Op::Add, 1}); } else { - O2_SIGNPOST_START(quota, sid, "summary", "Not enough resources to select offers."); - dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCES_MISSING), DataProcessingStats::Op::Add, 1}); if (result.size()) { + O2_SIGNPOST_START(quota, sid, "summary", "Not enough resources: accumulated %zu partial offers providing cpu=%d, memory=%lld MB, shared memory=%lld MB, timeslices=%lld, but still insufficient.", + result.size(), totalOffer.cpu, totalOffer.memory / 1000000, totalOffer.sharedMemory / 1000000, totalOffer.timeslices); dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCES_INSUFFICIENT), DataProcessingStats::Op::Add, 1}); + } else { + O2_SIGNPOST_START(quota, sid, "summary", "Not enough resources: no suitable offers found (all offers were invalid, expired, or owned by other tasks)."); + dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCES_MISSING), DataProcessingStats::Op::Add, 1}); } } if (stats.invalidOffers.size()) { @@ -205,7 +208,11 @@ bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& O2_SIGNPOST_EVENT_EMIT(quota, tid, "select", "Offer should be expired by now, checking again."); }, minValidity + 100, 0); } // If we get here it means we never got enough offers, so we return false. - return summarizeWhatHappended(enough, stats.selectedOffers, accumulated, stats); + bool result = summarizeWhatHappended(enough, stats.selectedOffers, accumulated, stats); + if (outAccumulated) { + *outAccumulated = accumulated; + } + return result; } void ComputingQuotaEvaluator::consume(int id, ComputingQuotaConsumer& consumer, std::function& reportConsumedOffer) diff --git a/Framework/Core/src/ControlWebSocketHandler.cxx b/Framework/Core/src/ControlWebSocketHandler.cxx index 6d7926918a8c7..8d2f85b034364 100644 --- a/Framework/Core/src/ControlWebSocketHandler.cxx +++ b/Framework/Core/src/ControlWebSocketHandler.cxx @@ -11,12 +11,16 @@ #include "ControlWebSocketHandler.h" #include "DriverServerContext.h" +#include "StatusWebSocketHandler.h" #include "Framework/DeviceMetricsHelper.h" #include "Framework/ServiceMetricsInfo.h" +#include "Framework/Signpost.h" #include #include "Framework/Logger.h" #include "Framework/DeviceConfigInfo.h" +O2_DECLARE_DYNAMIC_LOG(rate_limiting); + namespace o2::framework { void ControlWebSocketHandler::frame(char const* frame, size_t s) @@ -73,6 +77,10 @@ void ControlWebSocketHandler::endChunk() if (!didProcessMetric) { return; } + O2_SIGNPOST_ID_GENERATE(sid, rate_limiting); + O2_SIGNPOST_START(rate_limiting, sid, "endChunk", + "Processing metrics from device %zu (had new metric: %d)", + mIndex, (int)didHaveNewMetric); size_t timestamp = (uv_hrtime() - mContext.driver->startTime) / 1000000 + mContext.driver->startTimeMsFromEpoch; assert(mContext.metrics); assert(mContext.infos); @@ -83,9 +91,15 @@ void ControlWebSocketHandler::endChunk() for (auto& callback : *mContext.metricProcessingCallbacks) { callback(mContext.registry, ServiceMetricsInfo{*mContext.metrics, *mContext.specs, *mContext.infos, mContext.driver->metrics, *mContext.driver}, timestamp); } + // Notify status clients before changed flags are reset so they can see what changed. + for (auto* statusHandler : mContext.statusHandlers) { + statusHandler->sendUpdate(mIndex); + } for (auto& metricsInfo : *mContext.metrics) { std::fill(metricsInfo.changed.begin(), metricsInfo.changed.end(), false); } + O2_SIGNPOST_END(rate_limiting, sid, "endChunk", + "Done processing metrics from device %zu", mIndex); } void ControlWebSocketHandler::headers(std::map const& headers) diff --git a/Framework/Core/src/DPLWebSocket.cxx b/Framework/Core/src/DPLWebSocket.cxx index d9b6594d5f07c..06de46b387c29 100644 --- a/Framework/Core/src/DPLWebSocket.cxx +++ b/Framework/Core/src/DPLWebSocket.cxx @@ -18,6 +18,7 @@ #include "DriverServerContext.h" #include "DriverClientContext.h" #include "ControlWebSocketHandler.h" +#include "StatusWebSocketHandler.h" #include "HTTPParser.h" #include #include @@ -193,9 +194,10 @@ void WSDPLHandler::method(std::string_view const& s) void WSDPLHandler::target(std::string_view const& s) { - if (s != "/") { + if (s != "/" && s != "/status") { throw WSError{404, "Unknown"}; } + mTarget = s; } void populateHeader(std::map& headers, std::string_view const& k, std::string_view const& v) @@ -294,6 +296,12 @@ void WSDPLHandler::endHeaders() break; } } + } else if (mTarget == "/status" && mServerContext->isDriver) { + LOGP(info, "Status client connected ({} total)", mServerContext->statusHandlers.size() + 1); + auto* statusHandler = new StatusWebSocketHandler(*mServerContext, this); + mServerContext->statusHandlers.push_back(statusHandler); + mHandler = std::unique_ptr(statusHandler); + mHandler->headers(mHeaders); } else { if ((mServerContext->isDriver && getenv("DPL_DRIVER_REMOTE_GUI")) || ((mServerContext->isDriver == false) && getenv("DPL_DEVICE_REMOTE_GUI"))) { LOG(info) << "Connection not bound to a PID"; diff --git a/Framework/Core/src/DPLWebSocket.h b/Framework/Core/src/DPLWebSocket.h index 43ec27a6b54f0..1985c37157d65 100644 --- a/Framework/Core/src/DPLWebSocket.h +++ b/Framework/Core/src/DPLWebSocket.h @@ -62,6 +62,7 @@ struct WSDPLHandler : public HTTPParser { bool mHandshaken = false; uv_stream_t* mStream = nullptr; std::map mHeaders; + std::string mTarget; DriverServerContext* mServerContext; }; diff --git a/Framework/Core/src/DataAllocator.cxx b/Framework/Core/src/DataAllocator.cxx index f0de6a40935b7..d7bfff0dbf19d 100644 --- a/Framework/Core/src/DataAllocator.cxx +++ b/Framework/Core/src/DataAllocator.cxx @@ -263,9 +263,19 @@ void DataAllocator::adopt(const Output& spec, LifetimeHolder& f // Serialization happens in here, so that we can // get rid of the intermediate tree 2 table object, saving memory. auto batch = source.finalize(); + if (!batch) { + // Do not throw here: this callback runs from ~LifetimeHolder which may + // execute during stack unwinding (e.g. if fill() failed). Throwing during + // unwinding calls std::terminate. + LOG(error) << "FragmentToBatch::finalize() returned null RecordBatch, skipping serialization"; + return; + } auto mock = std::make_shared(); int64_t expectedSize = 0; auto mockWriter = arrow::ipc::MakeStreamWriter(mock.get(), batch->schema()); + if (!mockWriter.ok()) { + throw std::runtime_error(fmt::format("Unable to create mock stream writer: {}", mockWriter.status().ToString())); + } arrow::Status outStatus = mockWriter.ValueOrDie()->WriteRecordBatch(*batch); expectedSize = mock->Tell().ValueOrDie(); @@ -275,6 +285,9 @@ void DataAllocator::adopt(const Output& spec, LifetimeHolder& f } auto deferredWriterStream = source.streamer(buffer); + if (!deferredWriterStream) { + throw std::runtime_error("FragmentToBatch streamer returned null OutputStream"); + } auto outBatch = arrow::ipc::MakeStreamWriter(deferredWriterStream, batch->schema()); if (outBatch.ok() == false) { diff --git a/Framework/Core/src/DataProcessingDevice.cxx b/Framework/Core/src/DataProcessingDevice.cxx index da04a23e81c0c..b45a48c28f691 100644 --- a/Framework/Core/src/DataProcessingDevice.cxx +++ b/Framework/Core/src/DataProcessingDevice.cxx @@ -50,6 +50,7 @@ #include "DecongestionService.h" #include "Framework/DataProcessingHelpers.h" +#include "Framework/DataModelViews.h" #include "DataRelayerHelpers.h" #include "Headers/DataHeader.h" #include "Headers/DataHeaderHelpers.h" @@ -211,9 +212,8 @@ DataProcessingDevice::DataProcessingDevice(RunningDeviceRef running, ServiceRegi }); } -// Callback to execute the processing. Notice how the data is -// is a vector of DataProcessorContext so that we can index the correct -// one with the thread id. For the moment we simply use the first one. +// Callback to execute the processing. Receives and relays data (doPrepare) +// happens on the main thread before this is queued, so we only dispatch here. void run_callback(uv_work_t* handle) { auto* task = (TaskStreamInfo*)handle->data; @@ -222,7 +222,6 @@ void run_callback(uv_work_t* handle) auto& dataProcessorContext = ref.get(); O2_SIGNPOST_ID_FROM_POINTER(sid, device, &dataProcessorContext); O2_SIGNPOST_START(device, sid, "run_callback", "Starting run callback on stream %d", task->id.index); - DataProcessingDevice::doPrepare(ref); DataProcessingDevice::doRun(ref); O2_SIGNPOST_END(device, sid, "run_callback", "Done processing data for stream %d", task->id.index); } @@ -401,7 +400,6 @@ void DataProcessingDevice::Init() if (entry.second.empty() == false) { boost::property_tree::json_parser::write_json(ss, entry.second, false); str = ss.str(); - str.pop_back(); // remove EoL } else { str = entry.second.get_value(); } @@ -585,7 +583,7 @@ auto decongestionCallbackLate = [](AsyncTask& task, size_t aid) -> void { // the inputs which are shared between this device and others // to the next one in the daisy chain. // FIXME: do it in a smarter way than O(N^2) -static auto forwardInputs = [](ServiceRegistryRef registry, TimesliceSlot slot, std::vector& currentSetOfInputs, +static auto forwardInputs = [](ServiceRegistryRef registry, TimesliceSlot slot, std::vector>& currentSetOfInputs, TimesliceIndex::OldestOutputInfo oldestTimeslice, bool copy, bool consume = true) { auto& proxy = registry.get(); @@ -617,7 +615,7 @@ static auto forwardInputs = [](ServiceRegistryRef registry, TimesliceSlot slot, O2_SIGNPOST_END(forwarding, sid, "forwardInputs", "Forwarding done"); }; -static auto cleanEarlyForward = [](ServiceRegistryRef registry, TimesliceSlot slot, std::vector& currentSetOfInputs, +static auto cleanEarlyForward = [](ServiceRegistryRef registry, TimesliceSlot slot, std::vector>& currentSetOfInputs, TimesliceIndex::OldestOutputInfo oldestTimeslice, bool copy, bool consume = true) { auto& proxy = registry.get(); @@ -627,7 +625,7 @@ static auto cleanEarlyForward = [](ServiceRegistryRef registry, TimesliceSlot sl // Always copy them, because we do not want to actually send them. // We merely need the side effect of the consume, if applicable. for (size_t ii = 0, ie = currentSetOfInputs.size(); ii < ie; ++ii) { - auto span = std::span(currentSetOfInputs[ii].messages); + auto span = std::span(currentSetOfInputs[ii]); DataProcessingHelpers::cleanForwardedMessages(span, consume); } @@ -1278,7 +1276,7 @@ void DataProcessingDevice::Run() // - we can trigger further events from the queue // - we can guarantee this is the last thing we do in the loop ( // assuming no one else is adding to the queue before this point). - auto onDrop = [®istry = mServiceRegistry, lid](TimesliceSlot slot, std::vector& dropped, TimesliceIndex::OldestOutputInfo oldestOutputInfo) { + auto onDrop = [®istry = mServiceRegistry, lid](TimesliceSlot slot, std::vector>& dropped, TimesliceIndex::OldestOutputInfo oldestOutputInfo) { O2_SIGNPOST_START(device, lid, "run_loop", "Dropping message from slot %" PRIu64 ". Forwarding as needed.", (uint64_t)slot.index); ServiceRegistryRef ref{registry}; ref.get(); @@ -1332,6 +1330,10 @@ void DataProcessingDevice::Run() handleRegionCallbacks(mServiceRegistry, mPendingRegionInfos); } + // Receive and relay incoming data on the main thread so that I/O + // overlaps with computation running concurrently on work threads. + DataProcessingDevice::doPrepare(ref); + assert(mStreams.size() == mHandles.size()); /// Decide which task to use TaskStreamRef streamRef{-1}; @@ -1370,13 +1372,15 @@ void DataProcessingDevice::Run() // the evaluator. In this case, the request is always satisfied and // we run on whatever resource is available. auto& spec = ref.get(); - bool enough = ref.get().selectOffer(streamRef.index, spec.resourcePolicy.request, uv_now(state.loop)); + ComputingQuotaOffer accumulated; + bool enough = ref.get().selectOffer(streamRef.index, spec.resourcePolicy.request, uv_now(state.loop), &accumulated); struct SchedulingStats { std::atomic lastScheduled = 0; std::atomic numberOfUnscheduledSinceLastScheduled = 0; std::atomic numberOfUnscheduled = 0; std::atomic numberOfScheduled = 0; + std::atomic nextWarnAt = 1; }; static SchedulingStats schedulingStats; O2_SIGNPOST_ID_GENERATE(sid, scheduling); @@ -1387,6 +1391,7 @@ void DataProcessingDevice::Run() schedulingStats.lastScheduled = uv_now(state.loop); schedulingStats.numberOfScheduled++; schedulingStats.numberOfUnscheduledSinceLastScheduled = 0; + schedulingStats.nextWarnAt = 1; O2_SIGNPOST_EVENT_EMIT(scheduling, sid, "Run", "Enough resources to schedule computation on stream %d", streamRef.index); if (dplEnableMultithreding) [[unlikely]] { stream.task = &handle; @@ -1396,17 +1401,52 @@ void DataProcessingDevice::Run() run_completion(&handle, 0); } } else { - if (schedulingStats.numberOfUnscheduledSinceLastScheduled > 100 || - (uv_now(state.loop) - schedulingStats.lastScheduled) > 30000) { - O2_SIGNPOST_EVENT_EMIT_WARN(scheduling, sid, "Run", - "Not enough resources to schedule computation. %zu skipped so far. Last scheduled at %zu. Data is not lost and it will be scheduled again.", - schedulingStats.numberOfUnscheduledSinceLastScheduled.load(), - schedulingStats.lastScheduled.load()); + auto const lastSched = schedulingStats.lastScheduled.load(); + auto const schedInfo = lastSched ? fmt::format(", last scheduled {} ms ago", uv_now(state.loop) - lastSched) : std::string(", never successfully scheduled"); + auto const buildMissingInfo = [&]() { + auto const& required = spec.resourcePolicy.minRequired; + std::string missingInfo; + if (required.sharedMemory > 0 && accumulated.sharedMemory < required.sharedMemory) { + missingInfo += fmt::format(" shared memory (have {} MB, need {} MB)", accumulated.sharedMemory / 1000000, required.sharedMemory / 1000000); + } + if (required.timeslices > 0 && accumulated.timeslices < required.timeslices) { + missingInfo += fmt::format(" timeslices (have {}, need {})", accumulated.timeslices, required.timeslices); + } + if (required.cpu > 0 && accumulated.cpu < required.cpu) { + missingInfo += fmt::format(" CPU cores (have {}, need {})", accumulated.cpu, required.cpu); + } + if (required.memory > 0 && accumulated.memory < required.memory) { + missingInfo += fmt::format(" memory (have {} MB, need {} MB)", accumulated.memory / 1000000, required.memory / 1000000); + } + return missingInfo.empty() ? std::string(" (policy: ") + spec.resourcePolicy.name + ")" : " -" + missingInfo; + }; + auto const timeSinceLastScheduled = lastSched ? uv_now(state.loop) - lastSched : 0; + if (schedulingStats.numberOfUnscheduledSinceLastScheduled >= schedulingStats.nextWarnAt) { + auto const missingStr = buildMissingInfo(); + if (timeSinceLastScheduled >= 50) { + O2_SIGNPOST_EVENT_EMIT_WARN(scheduling, sid, "Run", + "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.", + streamRef.index, + schedulingStats.numberOfUnscheduledSinceLastScheduled.load(), + schedInfo.c_str(), + missingStr.c_str()); + } else { + O2_SIGNPOST_EVENT_EMIT(scheduling, sid, "Run", + "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.", + streamRef.index, + schedulingStats.numberOfUnscheduledSinceLastScheduled.load(), + schedInfo.c_str(), + missingStr.c_str()); + } + schedulingStats.nextWarnAt = schedulingStats.nextWarnAt * 2; } else { + auto const missingStr = buildMissingInfo(); O2_SIGNPOST_EVENT_EMIT(scheduling, sid, "Run", - "Not enough resources to schedule computation. %zu skipped so far. Last scheduled at %zu. Data is not lost and it will be scheduled again.", + "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.", + streamRef.index, schedulingStats.numberOfUnscheduledSinceLastScheduled.load(), - schedulingStats.lastScheduled.load()); + schedInfo.c_str(), + missingStr.c_str()); } schedulingStats.numberOfUnscheduled++; schedulingStats.numberOfUnscheduledSinceLastScheduled++; @@ -1942,7 +1982,7 @@ void DataProcessingDevice::handleData(ServiceRegistryRef ref, InputChannelInfo& nPayloadsPerHeader = 1; ii += (nMessages / 2) - 1; } - auto onDrop = [ref](TimesliceSlot slot, std::vector& dropped, TimesliceIndex::OldestOutputInfo oldestOutputInfo) { + auto onDrop = [ref](TimesliceSlot slot, std::vector>& dropped, TimesliceIndex::OldestOutputInfo oldestOutputInfo) { O2_SIGNPOST_ID_GENERATE(cid, async_queue); O2_SIGNPOST_EVENT_EMIT(async_queue, cid, "onDrop", "Dropping message from slot %zu. Forwarding as needed. Timeslice %zu", slot.index, oldestOutputInfo.timeslice.value); @@ -2120,7 +2160,7 @@ bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::v // want to support multithreaded dispatching of operations, I can simply // move these to some thread local store and the rest of the lambdas // should work just fine. - std::vector currentSetOfInputs; + std::vector> currentSetOfInputs; // auto getInputSpan = [ref, ¤tSetOfInputs](TimesliceSlot slot, bool consume = true) { @@ -2130,33 +2170,37 @@ bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::v } else { currentSetOfInputs = relayer.consumeExistingInputsForTimeslice(slot); } - auto getter = [¤tSetOfInputs](size_t i, size_t partindex) -> DataRef { - if (currentSetOfInputs[i].getNumberOfPairs() > partindex) { - const char* headerptr = nullptr; - const char* payloadptr = nullptr; - size_t payloadSize = 0; - // - each input can have multiple parts - // - "part" denotes a sequence of messages belonging together, the first message of the - // sequence is the header message - // - each part has one or more payload messages - // - InputRecord provides all payloads as header-payload pair - auto const& headerMsg = currentSetOfInputs[i].associatedHeader(partindex); - auto const& payloadMsg = currentSetOfInputs[i].associatedPayload(partindex); - headerptr = static_cast(headerMsg->GetData()); - payloadptr = payloadMsg ? static_cast(payloadMsg->GetData()) : nullptr; - payloadSize = payloadMsg ? payloadMsg->GetSize() : 0; - return DataRef{nullptr, headerptr, payloadptr, payloadSize}; + // Convert raw message indices directly to a DataRef in O(1). + // Used both by the sequential PartIterator and as the fallback for positional access. + auto indicesGetter = [¤tSetOfInputs](size_t i, DataRefIndices indices) -> DataRef { + auto const& msgs = currentSetOfInputs[i]; + if (msgs.size() <= indices.headerIdx) { + return DataRef{}; } - return DataRef{}; + auto const& headerMsg = msgs[indices.headerIdx]; + char const* payloadData = nullptr; + size_t payloadSize = 0; + if (msgs.size() > indices.payloadIdx && msgs[indices.payloadIdx]) { + payloadData = static_cast(msgs[indices.payloadIdx]->GetData()); + payloadSize = msgs[indices.payloadIdx]->GetSize(); + } + return DataRef{nullptr, + headerMsg ? static_cast(headerMsg->GetData()) : nullptr, + payloadData, + payloadSize}; }; auto nofPartsGetter = [¤tSetOfInputs](size_t i) -> size_t { - return currentSetOfInputs[i].getNumberOfPairs(); + return (currentSetOfInputs[i] | count_payloads{}); }; auto refCountGetter = [¤tSetOfInputs](size_t idx) -> int { - auto& header = static_cast(*currentSetOfInputs[idx].header(0)); + auto& header = static_cast(*(currentSetOfInputs[idx] | get_header{0})); return header.GetRefCount(); }; - return InputSpan{getter, nofPartsGetter, refCountGetter, currentSetOfInputs.size()}; + auto nextIndicesGetter = [¤tSetOfInputs](size_t i, DataRefIndices current) -> DataRefIndices { + auto next = currentSetOfInputs[i] | get_next_pair{current}; + return next.headerIdx < currentSetOfInputs[i].size() ? next : DataRefIndices{size_t(-1), size_t(-1)}; + }; + return InputSpan{nofPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, currentSetOfInputs.size()}; }; auto markInputsAsDone = [ref](TimesliceSlot slot) -> void { diff --git a/Framework/Core/src/DataProcessingHelpers.cxx b/Framework/Core/src/DataProcessingHelpers.cxx index 334a0fc6045f6..b8399a4c591e7 100644 --- a/Framework/Core/src/DataProcessingHelpers.cxx +++ b/Framework/Core/src/DataProcessingHelpers.cxx @@ -393,14 +393,14 @@ void DataProcessingHelpers::cleanForwardedMessages(std::span& currentSetOfInputs, + std::vector>& currentSetOfInputs, const bool copyByDefault, bool consume) -> std::vector { // we collect all messages per forward in a map and send them together std::vector forwardedParts(proxy.getNumForwardChannels()); for (size_t ii = 0, ie = currentSetOfInputs.size(); ii < ie; ++ii) { - auto span = std::span(currentSetOfInputs[ii].messages); + auto span = std::span(currentSetOfInputs[ii]); routeForwardedMessages(proxy, span, forwardedParts, copyByDefault, consume); } return forwardedParts; diff --git a/Framework/Core/src/DataProcessingStates.cxx b/Framework/Core/src/DataProcessingStates.cxx index 64be1829d8c97..8bf80e79b1839 100644 --- a/Framework/Core/src/DataProcessingStates.cxx +++ b/Framework/Core/src/DataProcessingStates.cxx @@ -111,9 +111,9 @@ void DataProcessingStates::updateState(CommandSpec cmd) // Add a static mutex to protect the queue // Get the next available operation in an atomic way. int size = sizeof(CommandHeader) + cmd.size; - if (size > 16384) { - throw runtime_error_f("State size is %d for state %s. States larger than 16384 bytes not supported for now.", - size, stateSpecs[cmd.id].name.c_str()); + if (size > STATES_BUFFER_SIZE / 8) { + throw runtime_error_f("State size is %d (data: %d bytes, header: %zu bytes) for state %s. States larger than %d bytes (1/8 of the states buffer) not supported. State data preview: %.100s", + size, cmd.size, sizeof(CommandHeader), stateSpecs[cmd.id].name.c_str(), STATES_BUFFER_SIZE / 8, cmd.data ? cmd.data : "(null)"); } int idx = nextState.fetch_sub(size, std::memory_order_relaxed); if (idx - size < 0) { diff --git a/Framework/Core/src/DataRelayer.cxx b/Framework/Core/src/DataRelayer.cxx index cece5b343659f..7adf5b5c97fbb 100644 --- a/Framework/Core/src/DataRelayer.cxx +++ b/Framework/Core/src/DataRelayer.cxx @@ -38,6 +38,7 @@ #include "Framework/DataTakingContext.h" #include "Framework/DefaultsHelpers.h" #include "Framework/RawDeviceService.h" +#include "Framework/DataModelViews.h" #include "Headers/DataHeaderHelpers.h" #include "Framework/Formatters.h" @@ -184,11 +185,11 @@ DataRelayer::ActivityStats DataRelayer::processDanglingInputs(std::vector 0 && part.header(0) != nullptr) { + if (!part.empty() && (part | get_header{0}) != nullptr) { headerPresent++; continue; } - if (part.size() > 0 && part.payload(0) != nullptr) { + if (!part.empty() && (part | get_payload{0, 0}) != nullptr) { payloadPresent++; continue; } @@ -202,7 +203,7 @@ DataRelayer::ActivityStats DataRelayer::processDanglingInputs(std::vector std::span { + auto getPartialRecord = [&cache = mCache, numInputTypes = mDistinctRoutesIndex.size()](int li) -> std::span const> { auto offset = li * numInputTypes; assert(cache.size() >= offset + numInputTypes); auto const start = cache.data() + offset; @@ -211,26 +212,31 @@ DataRelayer::ActivityStats DataRelayer::processDanglingInputs(std::vector 0 && partial[idx].header(part).get()) { - auto header = partial[idx].header(part).get(); - auto payload = partial[idx].payload(part).get(); - return DataRef{nullptr, - reinterpret_cast(header->GetData()), - reinterpret_cast(payload ? payload->GetData() : nullptr), - payload ? payload->GetSize() : 0}; - } - return DataRef{}; - }; auto nPartsGetter = [&partial](size_t idx) { - return partial[idx].size(); + return partial[idx] | count_parts{}; }; auto refCountGetter = [&partial](size_t idx) -> int { - auto& header = static_cast(*partial[idx].header(0)); + auto& header = static_cast(*(partial[idx] | get_header{0})); return header.GetRefCount(); }; - InputSpan span{getter, nPartsGetter, refCountGetter, static_cast(partial.size())}; + auto indicesGetter = [&partial](size_t idx, DataRefIndices indices) -> DataRef { + if (!partial[idx].empty()) { + auto const& headerMsg = partial[idx][indices.headerIdx]; + auto const& payloadMsg = partial[idx][indices.payloadIdx]; + if (headerMsg) { + return DataRef{nullptr, + reinterpret_cast(headerMsg->GetData()), + payloadMsg ? reinterpret_cast(payloadMsg->GetData()) : nullptr, + payloadMsg ? payloadMsg->GetSize() : 0}; + } + } + return DataRef{}; + }; + auto nextIndicesGetter = [&partial](size_t idx, DataRefIndices current) -> DataRefIndices { + auto next = partial[idx] | get_next_pair{current}; + return next.headerIdx < partial[idx].size() ? next : DataRefIndices{size_t(-1), size_t(-1)}; + }; + InputSpan span{nPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, static_cast(partial.size())}; // Setup the input span if (expirator.checker(services, timestamp.value, span) == false) { @@ -242,12 +248,14 @@ DataRelayer::ActivityStats DataRelayer::processDanglingInputs(std::vector(); @@ -353,7 +361,7 @@ void DataRelayer::setOldestPossibleInput(TimesliceId proposed, ChannelIndex chan continue; } auto& element = mCache[si * mInputs.size() + mi]; - if (element.size() == 0) { + if (element.empty()) { auto& state = mContext.get(); if (state.transitionHandling != TransitionHandlingState::NoTransition && DefaultsHelpers::onlineDeploymentMode()) { if (state.allowedProcessing == DeviceState::CalibrationOnly) { @@ -405,17 +413,17 @@ void DataRelayer::pruneCache(TimesliceSlot slot, OnDropCallback onDrop) if (onDrop) { auto oldestPossibleTimeslice = index.getOldestPossibleOutput(); // State of the computation - std::vector dropped(numInputTypes); + std::vector> dropped(numInputTypes); for (size_t ai = 0, ae = numInputTypes; ai != ae; ++ai) { auto cacheId = slot.index * numInputTypes + ai; cachedStateMetrics[cacheId] = CacheEntryStatus::RUNNING; // TODO: in the original implementation of the cache, there have been only two messages per entry, // check if the 2 above corresponds to the number of messages. - if (cache[cacheId].size() > 0) { + if (!cache[cacheId].empty()) { dropped[ai] = std::move(cache[cacheId]); } } - bool anyDropped = std::any_of(dropped.begin(), dropped.end(), [](auto& m) { return m.size(); }); + bool anyDropped = std::any_of(dropped.begin(), dropped.end(), [](auto& m) { return !m.empty(); }); if (anyDropped) { O2_SIGNPOST_ID_GENERATE(aid, data_relayer); O2_SIGNPOST_EVENT_EMIT(data_relayer, aid, "pruneCache", "Dropping stuff from slot %zu with timeslice %zu", slot.index, oldestPossibleTimeslice.timeslice.value); @@ -506,7 +514,7 @@ DataRelayer::RelayChoice timeslice.value, slot.index, info.index.value == ChannelIndex::INVALID ? "invalid" : services.get().getInputChannel(info.index)->GetName().c_str()); auto cacheIdx = numInputTypes * slot.index + input; - MessageSet& target = cache[cacheIdx]; + auto& target = cache[cacheIdx]; cachedStateMetrics[cacheIdx] = CacheEntryStatus::PENDING; // TODO: make sure that multiple parts can only be added within the same call of // DataRelayer::relay @@ -536,7 +544,9 @@ DataRelayer::RelayChoice auto span = std::span(messages + mi, messages + mi + nPayloads + 1); // Notice this will split [(header, payload), (header, payload)] multiparts // in N different subParts for the message spec. - target.add([&span](size_t i) -> fair::mq::MessagePtr& { return span[i]; }, nPayloads + 1); + for (size_t i = 0; i < nPayloads + 1; ++i) { + target.emplace_back(std::move(span[i])); + } mi += nPayloads; saved += nPayloads; } @@ -728,7 +738,7 @@ void DataRelayer::getReadyToProcess(std::vector& comp // // We use this to bail out early from the check as soon as we find something // which we know is not complete. - auto getPartialRecord = [&cache, &numInputTypes](int li) -> std::span { + auto getPartialRecord = [&cache, &numInputTypes](int li) -> std::span const> { auto offset = li * numInputTypes; assert(cache.size() >= offset + numInputTypes); auto const start = cache.data() + offset; @@ -784,26 +794,31 @@ void DataRelayer::getReadyToProcess(std::vector& comp throw runtime_error_f("Completion police %s has no callback set", mCompletionPolicy.name.c_str()); } auto partial = getPartialRecord(li); - // TODO: get the data ref from message model - auto getter = [&partial](size_t idx, size_t part) { - if (partial[idx].size() > 0 && partial[idx].header(part).get()) { - auto header = partial[idx].header(part).get(); - auto payload = partial[idx].payload(part).get(); - return DataRef{nullptr, - reinterpret_cast(header->GetData()), - reinterpret_cast(payload ? payload->GetData() : nullptr), - payload ? payload->GetSize() : 0}; - } - return DataRef{}; - }; auto nPartsGetter = [&partial](size_t idx) { - return partial[idx].size(); + return partial[idx] | count_parts{}; }; auto refCountGetter = [&partial](size_t idx) -> int { - auto& header = static_cast(*partial[idx].header(0)); + auto& header = static_cast(*(partial[idx] | get_header{0})); return header.GetRefCount(); }; - InputSpan span{getter, nPartsGetter, refCountGetter, static_cast(partial.size())}; + auto indicesGetter = [&partial](size_t idx, DataRefIndices indices) -> DataRef { + if (!partial[idx].empty()) { + auto const& headerMsg = partial[idx][indices.headerIdx]; + auto const& payloadMsg = partial[idx][indices.payloadIdx]; + if (headerMsg) { + return DataRef{nullptr, + reinterpret_cast(headerMsg->GetData()), + payloadMsg ? reinterpret_cast(payloadMsg->GetData()) : nullptr, + payloadMsg ? payloadMsg->GetSize() : 0}; + } + } + return DataRef{}; + }; + auto nextIndicesGetter = [&partial](size_t idx, DataRefIndices current) -> DataRefIndices { + auto next = partial[idx] | get_next_pair{current}; + return next.headerIdx < partial[idx].size() ? next : DataRefIndices{size_t(-1), size_t(-1)}; + }; + InputSpan span{nPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, static_cast(partial.size())}; CompletionPolicy::CompletionOp action = mCompletionPolicy.callbackFull(span, mInputs, mContext); auto& variables = mTimesliceIndex.getVariablesForSlot(slot); @@ -871,13 +886,13 @@ void DataRelayer::updateCacheStatus(TimesliceSlot slot, CacheEntryStatus oldStat } } -std::vector DataRelayer::consumeAllInputsForTimeslice(TimesliceSlot slot) +std::vector> DataRelayer::consumeAllInputsForTimeslice(TimesliceSlot slot) { std::scoped_lock lock(mMutex); const auto numInputTypes = mDistinctRoutesIndex.size(); // State of the computation - std::vector messages(numInputTypes); + std::vector> messages(numInputTypes); auto& cache = mCache; auto& index = mTimesliceIndex; @@ -897,7 +912,7 @@ std::vector DataRelayer::consumeAllInputsForTimeslice cachedStateMetrics[cacheId] = CacheEntryStatus::RUNNING; // TODO: in the original implementation of the cache, there have been only two messages per entry, // check if the 2 above corresponds to the number of messages. - if (cache[cacheId].size() > 0) { + if (!cache[cacheId].empty()) { messages[arg] = std::move(cache[cacheId]); } index.markAsInvalid(s); @@ -909,7 +924,7 @@ std::vector DataRelayer::consumeAllInputsForTimeslice // FIXME: what happens when we have enough timeslices to hit the invalid one? auto invalidateCacheFor = [&numInputTypes, &index, &cache](TimesliceSlot s) { for (size_t ai = s.index * numInputTypes, ae = ai + numInputTypes; ai != ae; ++ai) { - assert(std::accumulate(cache[ai].messages.begin(), cache[ai].messages.end(), true, [](bool result, auto const& element) { return result && element.get() == nullptr; })); + assert(std::accumulate(cache[ai].begin(), cache[ai].end(), true, [](bool result, auto const& element) { return result && element.get() == nullptr; })); cache[ai].clear(); } index.markAsInvalid(s); @@ -925,13 +940,13 @@ std::vector DataRelayer::consumeAllInputsForTimeslice return messages; } -std::vector DataRelayer::consumeExistingInputsForTimeslice(TimesliceSlot slot) +std::vector> DataRelayer::consumeExistingInputsForTimeslice(TimesliceSlot slot) { std::scoped_lock lock(mMutex); const auto numInputTypes = mDistinctRoutesIndex.size(); // State of the computation - std::vector messages(numInputTypes); + std::vector> messages(numInputTypes); auto& cache = mCache; auto& index = mTimesliceIndex; @@ -951,11 +966,12 @@ std::vector DataRelayer::consumeExistingInputsForTime cachedStateMetrics[cacheId] = CacheEntryStatus::RUNNING; // TODO: in the original implementation of the cache, there have been only two messages per entry, // check if the 2 above corresponds to the number of messages. - for (size_t pi = 0; pi < cache[cacheId].size(); pi++) { - auto& header = cache[cacheId].header(pi); + for (size_t pi = 0; pi < (cache[cacheId] | count_parts{}); pi++) { + auto& header = cache[cacheId] | get_header{pi}; auto&& newHeader = header->GetTransport()->CreateMessage(); newHeader->Copy(*header); - messages[arg].add(PartRef{std::move(newHeader), std::move(cache[cacheId].payload(pi))}); + messages[arg].emplace_back(std::move(newHeader)); + messages[arg].emplace_back(std::move(cache[cacheId] | get_payload{pi, 0})); } }; diff --git a/Framework/Core/src/DecongestionService.h b/Framework/Core/src/DecongestionService.h index 1a42d3577bc0a..fe7fce8d640db 100644 --- a/Framework/Core/src/DecongestionService.h +++ b/Framework/Core/src/DecongestionService.h @@ -33,6 +33,8 @@ struct DecongestionService { int64_t nextTimeslice = 0; /// Ordered completion policy is active. bool orderedCompletionPolicyActive = false; + /// consumeWhenPastOldestPossibleTimeframe completion policy is active. + bool consumeWhenPastOldestPossibleTimeframeActive = false; // Task to enqueue the oldest possible timeslice propagation // at the end of any processing chain. o2::framework::AsyncTaskId oldestPossibleTimesliceTask = {0}; diff --git a/Framework/Core/src/DeviceSpecHelpers.cxx b/Framework/Core/src/DeviceSpecHelpers.cxx index 88e5269482ebd..011b3aa12162f 100644 --- a/Framework/Core/src/DeviceSpecHelpers.cxx +++ b/Framework/Core/src/DeviceSpecHelpers.cxx @@ -1732,7 +1732,7 @@ boost::program_options::options_description DeviceSpecHelpers::getForwardedDevic ("error-on-exit-transition-timeout", bpo::value()->zero_tokens(), "print error instead of warning when exit transition timer expires") // ("data-processing-timeout", bpo::value(), "timeout after which only calibration can happen") // ("expected-region-callbacks", bpo::value(), "region callbacks to expect before starting") // - ("timeframes-rate-limit", bpo::value()->default_value("0"), "how many timeframes can be in fly") // + ("timeframes-rate-limit", bpo::value()->default_value("0"), "how many timeframes can be in flight") // ("shm-monitor", bpo::value(), "whether to use the shared memory monitor") // ("channel-prefix", bpo::value()->default_value(""), "prefix to use for multiplexing multiple workflows in the same session") // ("bad-alloc-max-attempts", bpo::value()->default_value("1"), "throw after n attempts to alloc shm") // diff --git a/Framework/Core/src/DevicesManager.cxx b/Framework/Core/src/DevicesManager.cxx index e6fa2c2c61ae6..b427e72ca781d 100644 --- a/Framework/Core/src/DevicesManager.cxx +++ b/Framework/Core/src/DevicesManager.cxx @@ -13,12 +13,19 @@ #include "Framework/RuntimeError.h" #include "Framework/Logger.h" #include "Framework/DeviceController.h" +#include "Framework/Signpost.h" + +O2_DECLARE_DYNAMIC_LOG(devices_manager); namespace o2::framework { void DevicesManager::queueMessage(char const* target, char const* message) { + O2_SIGNPOST_ID_GENERATE(sid, devices_manager); + O2_SIGNPOST_EVENT_EMIT(devices_manager, sid, "queue", + "Queuing message for %{public}s: %{public}s", + target, message); for (int di = 0; di < specs.size(); ++di) { if (specs[di].id == target) { messages.push_back({di, message}); @@ -44,6 +51,10 @@ void DevicesManager::flush() LOGP(info, "Controller for {} now available.", specs[handle.ref.index].id); notifiedAvailable = true; } + O2_SIGNPOST_ID_GENERATE(sid, devices_manager); + O2_SIGNPOST_EVENT_EMIT(devices_manager, sid, "flush", + "Flushing message to %{public}s: %{public}s", + specs[handle.ref.index].id.c_str(), handle.message.c_str()); controller->write(handle.message.c_str(), handle.message.size()); } diff --git a/Framework/Core/src/DriverServerContext.h b/Framework/Core/src/DriverServerContext.h index 4d25c47bd172b..c9f2c80165d92 100644 --- a/Framework/Core/src/DriverServerContext.h +++ b/Framework/Core/src/DriverServerContext.h @@ -29,6 +29,7 @@ namespace o2::framework struct DriverInfo; struct ServiceRegistry; struct GuiCallbackContext; +struct StatusWebSocketHandler; struct DriverServerContext { ServiceRegistryRef registry; @@ -49,6 +50,10 @@ struct DriverServerContext { /// or something like that. bool isDriver = false; + /// Connected MCP/status clients. Updated by StatusWebSocketHandler + /// on connect/disconnect; notified by ControlWebSocketHandler::endChunk(). + std::vector statusHandlers; + /// The handle to the server component of the /// driver. uv_tcp_t serverHandle; diff --git a/Framework/Core/src/ExternalFairMQDeviceProxy.cxx b/Framework/Core/src/ExternalFairMQDeviceProxy.cxx index 3b0275879a158..d4ee776986184 100644 --- a/Framework/Core/src/ExternalFairMQDeviceProxy.cxx +++ b/Framework/Core/src/ExternalFairMQDeviceProxy.cxx @@ -1005,7 +1005,7 @@ DataProcessorSpec specifyFairMQDeviceOutputProxy(char const* name, } DataHeader dh; dh.dataOrigin = "DPL"; - dh.dataDescription = "EOS"; + dh.dataDescription = o2::header::gDataDescriptionEos; dh.subSpecification = 0; dh.payloadSize = 0; dh.runNumber = runNumber; @@ -1027,9 +1027,9 @@ DataProcessorSpec specifyFairMQDeviceOutputProxy(char const* name, callbacks.set(forwardEos); return adaptStateless([lastDataProcessingHeader](InputRecord& inputs) { - for (size_t ii = 0; ii != inputs.size(); ++ii) { - for (size_t pi = 0; pi < inputs.getNofParts(ii); ++pi) { - auto part = inputs.getByPos(ii, pi); + for (auto it = inputs.begin(); it != inputs.end(); it++) { + for (auto indices = it.initialIndices(); indices != it.endIndices(); indices = it.nextIndices(indices)) { + auto part = it.getAtIndices(indices); const auto* dph = o2::header::get(part.header); if (dph) { // FIXME: should we implement an assignment operator for DataProcessingHeader? @@ -1137,7 +1137,7 @@ DataProcessorSpec specifyFairMQDeviceMultiOutputProxy(char const* name, } DataHeader dh; dh.dataOrigin = "DPL"; - dh.dataDescription = "EOS"; + dh.dataDescription = o2::header::gDataDescriptionEos; dh.subSpecification = 0; dh.payloadSize = 0; dh.payloadSerializationMethod = o2::header::gSerializationMethodNone; @@ -1163,9 +1163,9 @@ DataProcessorSpec specifyFairMQDeviceMultiOutputProxy(char const* name, // there is nothing to do if the forwarding is handled on the framework level // as forward routes but we need to keep a copy of the last DataProcessingHeader // for sending the EOS - for (size_t ii = 0; ii != inputs.size(); ++ii) { - for (size_t pi = 0; pi < inputs.getNofParts(ii); ++pi) { - auto part = inputs.getByPos(ii, pi); + for (auto it = inputs.begin(); it != inputs.end(); it++) { + for (auto indices = it.initialIndices(); indices != it.endIndices(); indices = it.nextIndices(indices)) { + auto part = it.getAtIndices(indices); const auto* dph = o2::header::get(part.header); if (dph) { // FIXME: should we implement an assignment operator for DataProcessingHeader? diff --git a/Framework/Core/src/FragmentToBatch.cxx b/Framework/Core/src/FragmentToBatch.cxx index 88b4f42a8f220..ada31be814fc8 100644 --- a/Framework/Core/src/FragmentToBatch.cxx +++ b/Framework/Core/src/FragmentToBatch.cxx @@ -10,7 +10,6 @@ // or submit itself to any jurisdiction. #include "Framework/FragmentToBatch.h" #include "Framework/Logger.h" -#include "Framework/Endian.h" #include "Framework/Signpost.h" #include @@ -45,7 +44,14 @@ void FragmentToBatch::fill(std::shared_ptr schema, std::shared_pt options->dataset_schema = schema; auto scanner = format->ScanBatchesAsync(options, mFragment); auto batch = (*scanner)(); - mRecordBatch = *batch.result(); + auto result = batch.result(); + if (!result.ok()) { + throw std::runtime_error("FragmentToBatch::fill: scan failed: " + result.status().ToString()); + } + mRecordBatch = *result; + if (!mRecordBatch) { + throw std::runtime_error("FragmentToBatch::fill: scan returned null RecordBatch"); + } // Notice that up to here the buffer was not yet filled. } diff --git a/Framework/Core/src/HTTPParser.cxx b/Framework/Core/src/HTTPParser.cxx index fa2ba91722eb0..63537423b2708 100644 --- a/Framework/Core/src/HTTPParser.cxx +++ b/Framework/Core/src/HTTPParser.cxx @@ -54,7 +54,7 @@ void encode_websocket_frames(std::vector& outputs, char const* src, si int maskSize = mask ? 4 : 0; if (size < 126) { - headerSize = sizeof(WebSocketFrameTiny); + headerSize = sizeof(WebSocketFrameTiny); // Allocate a new page if we do not fit in the current one if (outputs.empty() || outputs.back().len > WebSocketConstants::MaxChunkSize || (size + maskSize + headerSize) > (WebSocketConstants::MaxChunkSize - outputs.back().len)) { char* chunk = (char*)malloc(WebSocketConstants::MaxChunkSize); @@ -64,11 +64,11 @@ void encode_websocket_frames(std::vector& outputs, char const* src, si // Reposition the buffer to the end of the current page buffer = buf.base + buf.len; buf.len += headerSize + size + maskSize; - WebSocketFrameTiny* header = (WebSocketFrameTiny*)buffer; + auto* header = (WebSocketFrameTiny*)buffer; memset(buffer, 0, headerSize); header->len = size; } else if (size < 1 << 16) { - headerSize = sizeof(WebSocketFrameShort); + headerSize = sizeof(WebSocketFrameShort); // Allocate a new page if we do not fit in the current one if (outputs.empty() || outputs.back().len > WebSocketConstants::MaxChunkSize || (size + maskSize + headerSize) > (WebSocketConstants::MaxChunkSize - outputs.back().len)) { char* chunk = (char*)malloc(WebSocketConstants::MaxChunkSize); @@ -78,24 +78,24 @@ void encode_websocket_frames(std::vector& outputs, char const* src, si // Reposition the buffer to the end of the current page buffer = buf.base + buf.len; buf.len += headerSize + size + maskSize; - WebSocketFrameShort* header = (WebSocketFrameShort*)buffer; + auto* header = (WebSocketFrameShort*)buffer; memset(buffer, 0, headerSize); header->len = 126; header->len16 = htons(size); } else { // For larger messages we do standalone allocation // so that the message does not need to be sent in multiple chunks - headerSize = sizeof(WebSocketFrameHuge); + headerSize = sizeof(WebSocketFrameHuge); buffer = (char*)malloc(headerSize + maskSize + size); - WebSocketFrameHuge* header = (WebSocketFrameHuge*)buffer; + auto* header = (WebSocketFrameHuge*)buffer; memset(buffer, 0, headerSize); header->len = 127; - header->len64 = htonll(size); + header->len64 = (std::endian::native == std::endian::little) ? __builtin_bswap64(size) : size; outputs.push_back(uv_buf_init(buffer, size + maskSize + headerSize)); } size_t fullHeaderSize = maskSize + headerSize; startPayload = buffer + fullHeaderSize; - WebSocketFrameTiny* header = (WebSocketFrameTiny*)buffer; + auto* header = (WebSocketFrameTiny*)buffer; header->fin = 1; header->opcode = (unsigned char)opcode; // binary or text for now // Mask is right before payload. @@ -143,7 +143,7 @@ void decode_websocket(char* start, size_t size, WebSocketHandler& handler) handler.beginChunk(); // The + 2 is there because we need at least 2 bytes. while (cur - start < size) { - WebSocketFrameTiny* header = (WebSocketFrameTiny*)cur; + auto* header = (WebSocketFrameTiny*)cur; size_t payloadSize = 0; size_t headerSize = 0; if ((cur + 2 - start >= size) || @@ -160,12 +160,12 @@ void decode_websocket(char* start, size_t size, WebSocketHandler& handler) payloadSize = header->len; headerSize = 2 + (header->mask ? 4 : 0); } else if (header->len == 126) { - WebSocketFrameShort* headerSmall = (WebSocketFrameShort*)cur; + auto* headerSmall = (WebSocketFrameShort*)cur; payloadSize = ntohs(headerSmall->len16); headerSize = 2 + 2 + (header->mask ? 4 : 0); } else if (header->len == 127) { - WebSocketFrameHuge* headerSmall = (WebSocketFrameHuge*)cur; - payloadSize = ntohll(headerSmall->len64); + auto* headerSmall = (WebSocketFrameHuge*)cur; + payloadSize = (std::endian::native == std::endian::little) ? __builtin_bswap64(headerSmall->len64) : headerSmall->len64; headerSize = 2 + 8 + (header->mask ? 4 : 0); } size_t availableSize = size - (cur - start); diff --git a/Framework/Core/src/HTTPParser.h b/Framework/Core/src/HTTPParser.h index a3253c7ca3d39..6e253a4076ff1 100644 --- a/Framework/Core/src/HTTPParser.h +++ b/Framework/Core/src/HTTPParser.h @@ -12,7 +12,8 @@ #ifndef O2_FRAMEWORK_HTTPPARSER_H_ #define O2_FRAMEWORK_HTTPPARSER_H_ -#include "Framework/Endian.h" +#include +#include #include #include #include @@ -22,8 +23,11 @@ namespace o2::framework { -struct __attribute__((__packed__)) WebSocketFrameTiny { -#if O2_HOST_BYTE_ORDER == O2_LITTLE_ENDIAN +template +struct __attribute__((__packed__)) WebSocketFrameTiny; + +template <> +struct __attribute__((__packed__)) WebSocketFrameTiny { unsigned char opcode : 4; unsigned char rsv3 : 1; unsigned char rsv2 : 1; @@ -31,7 +35,10 @@ struct __attribute__((__packed__)) WebSocketFrameTiny { unsigned char fin : 1; unsigned char len : 7; unsigned char mask : 1; -#elif O2_HOST_BYTE_ORDER == O2_BIG_ENDIAN +}; + +template <> +struct __attribute__((__packed__)) WebSocketFrameTiny { unsigned char fin : 1; unsigned char rsv1 : 1; unsigned char rsv2 : 1; @@ -39,13 +46,13 @@ struct __attribute__((__packed__)) WebSocketFrameTiny { unsigned char opcode : 4; unsigned char mask : 1; unsigned char len : 7; -#else -#error Uknown endiannes -#endif }; -struct __attribute__((__packed__)) WebSocketFrameShort { -#if O2_HOST_BYTE_ORDER == O2_LITTLE_ENDIAN +template +struct __attribute__((__packed__)) WebSocketFrameShort; + +template <> +struct __attribute__((__packed__)) WebSocketFrameShort { unsigned char opcode : 4; unsigned char rsv3 : 1; unsigned char rsv2 : 1; @@ -53,7 +60,11 @@ struct __attribute__((__packed__)) WebSocketFrameShort { unsigned char fin : 1; unsigned char len : 7; unsigned char mask : 1; -#elif O2_HOST_BYTE_ORDER == O2_BIG_ENDIAN + uint16_t len16; +}; + +template <> +struct __attribute__((__packed__)) WebSocketFrameShort { unsigned char fin : 1; unsigned char rsv1 : 1; unsigned char rsv2 : 1; @@ -61,14 +72,14 @@ struct __attribute__((__packed__)) WebSocketFrameShort { unsigned char opcode : 4; unsigned char mask : 1; unsigned char len : 7; -#else -#error Uknown endiannes -#endif uint16_t len16; }; -struct __attribute__((__packed__)) WebSocketFrameHuge { -#if O2_HOST_BYTE_ORDER == O2_LITTLE_ENDIAN +template +struct __attribute__((__packed__)) WebSocketFrameHuge; + +template <> +struct __attribute__((__packed__)) WebSocketFrameHuge { unsigned char opcode : 4; unsigned char rsv3 : 1; unsigned char rsv2 : 1; @@ -76,7 +87,11 @@ struct __attribute__((__packed__)) WebSocketFrameHuge { unsigned char fin : 1; unsigned char len : 7; unsigned char mask : 1; -#elif O2_HOST_BYTE_ORDER == O2_BIG_ENDIAN + uint64_t len64; +}; + +template <> +struct __attribute__((__packed__)) WebSocketFrameHuge { unsigned char fin : 1; unsigned char rsv1 : 1; unsigned char rsv2 : 1; @@ -84,9 +99,6 @@ struct __attribute__((__packed__)) WebSocketFrameHuge { unsigned char opcode : 4; unsigned char mask : 1; unsigned char len : 7; -#else -#error Uknown endiannes -#endif uint64_t len64; }; @@ -138,9 +150,9 @@ struct WebSocketHandler { virtual ~WebSocketHandler() = default; /// Invoked when all the headers are received. - virtual void headers(std::map const& headers){}; + virtual void headers(std::map const& headers) {}; /// FIXME: not implemented - virtual void beginFragmentation(){}; + virtual void beginFragmentation() {}; /// Invoked when a frame it's parsed. Notice you do not own the data and you must /// not free the memory. virtual void frame(char const* frame, size_t s) {} @@ -205,18 +217,18 @@ struct HTTPParser { std::string remaining; std::string error; std::vector states; - virtual void method(std::string_view const& s){}; - virtual void target(std::string_view const& s){}; - virtual void version(std::string_view const& s){}; - virtual void header(std::string_view const& k, std::string_view const& v){}; - virtual void endHeaders(){}; + virtual void method(std::string_view const& s) {}; + virtual void target(std::string_view const& s) {}; + virtual void version(std::string_view const& s) {}; + virtual void header(std::string_view const& k, std::string_view const& v) {}; + virtual void endHeaders() {}; /// Invoked whenever we are parsing data. /// In order to allow for xoring (as required by the websocket standard) /// in place, we pass it as a mutable pointer. - virtual void body(char* data, size_t s){}; - virtual void replyVersion(std::string_view const& s){}; - virtual void replyCode(std::string_view const& s){}; - virtual void replyMessage(std::string_view const& s){}; + virtual void body(char* data, size_t s) {}; + virtual void replyVersion(std::string_view const& s) {}; + virtual void replyCode(std::string_view const& s) {}; + virtual void replyMessage(std::string_view const& s) {}; }; struct HTTPParserHelpers { diff --git a/Framework/Core/src/InputRecord.cxx b/Framework/Core/src/InputRecord.cxx index 18b341704ffcb..7bc9907b13ba4 100644 --- a/Framework/Core/src/InputRecord.cxx +++ b/Framework/Core/src/InputRecord.cxx @@ -139,6 +139,16 @@ size_t InputRecord::getNofParts(int pos) const } return mSpan.getNofParts(pos); } + +DataRef InputRecord::getAtIndices(int pos, DataRefIndices indices) const +{ + auto ref = mSpan.getAtIndices(pos, indices); + if (pos >= 0 && pos < (int)mInputsSchema.size()) { + ref.spec = &mInputsSchema[pos].matcher; + } + return ref; +} + size_t InputRecord::size() const { return mSpan.size(); diff --git a/Framework/Core/src/InputSpan.cxx b/Framework/Core/src/InputSpan.cxx index d1dffc85602a5..ccea2d1dd66ed 100644 --- a/Framework/Core/src/InputSpan.cxx +++ b/Framework/Core/src/InputSpan.cxx @@ -11,29 +11,17 @@ #include "Framework/InputSpan.h" -template class std::function; -template class std::function; +template class std::function; +template class std::function; namespace o2::framework { -InputSpan::InputSpan(std::function getter, size_t size) - : mGetter{}, mNofPartsGetter{}, mSize{size} -{ - mGetter = [getter](size_t index, size_t) -> DataRef { - return getter(index); - }; -} - -InputSpan::InputSpan(std::function getter, size_t size) - : mGetter{getter}, mNofPartsGetter{}, mSize{size} -{ -} - -InputSpan::InputSpan(std::function getter, - std::function nofPartsGetter, +InputSpan::InputSpan(std::function nofPartsGetter, std::function refCountGetter, + std::function indicesGetter, + std::function nextIndicesGetter, size_t size) - : mGetter{getter}, mNofPartsGetter{nofPartsGetter}, mRefCountGetter(refCountGetter), mSize{size} + : mNofPartsGetter{nofPartsGetter}, mRefCountGetter(refCountGetter), mIndicesGetter{std::move(indicesGetter)}, mNextIndicesGetter{std::move(nextIndicesGetter)}, mSize{size} { } diff --git a/Framework/Core/src/Plugin.cxx b/Framework/Core/src/Plugin.cxx index 8ed683d501906..82599310eafe9 100644 --- a/Framework/Core/src/Plugin.cxx +++ b/Framework/Core/src/Plugin.cxx @@ -70,6 +70,10 @@ auto lookForCommandLineAODOptions = [](ConfigParamRegistry& registry, int argc, O2_SIGNPOST_EVENT_EMIT(capabilities, sid, "DiscoverAODOptionsInCommandLineCapability", "AOD options found in arguments. Populating from them."); return true; } + if (arg.starts_with("--aod-origin-")) { + O2_SIGNPOST_EVENT_EMIT(capabilities, sid, "DiscoverAODOptionsInCommandLineCapability", "AOD options found in arguments. Populating from them."); + return true; + } } return false; }; @@ -150,7 +154,7 @@ struct DiscoverAODOptionsInCommandLine : o2::framework::ConfigDiscoveryPlugin { bool injectOption = true; for (size_t i = 0; i < argc; i++) { std::string_view arg = argv[i]; - if (!arg.starts_with("--aod-writer-") && !arg.starts_with("--aod-parent-")) { + if (!arg.starts_with("--aod-writer-") && !arg.starts_with("--aod-parent-") && !arg.starts_with("--aod-origin-")) { continue; } std::string key = arg.data() + 2; @@ -168,6 +172,9 @@ struct DiscoverAODOptionsInCommandLine : o2::framework::ConfigDiscoveryPlugin { if (key == "aod-parent-access-level") { results.push_back(ConfigParamSpec{"aod-parent-access-level", VariantType::String, value, {"Allow parent file access up to specified level. Default: no (0)"}}); } + if (key == "aod-origin-level-mapping") { + results.push_back(ConfigParamSpec{"aod-origin-level-mapping", VariantType::String, value, {"Map origin to parent level for AOD reading. Syntax: ORIGIN:LEVEL[,ORIGIN2:LEVEL2,...]. E.g. \"DYN:1\"."}}); + } } if (injectOption) { results.push_back(ConfigParamSpec{"aod-writer-compression", VariantType::Int, 505, {"AOD Compression options"}}); diff --git a/Framework/Core/src/ResourcePolicyHelpers.cxx b/Framework/Core/src/ResourcePolicyHelpers.cxx index 2c5c4f54dd9b5..650beec3ac599 100644 --- a/Framework/Core/src/ResourcePolicyHelpers.cxx +++ b/Framework/Core/src/ResourcePolicyHelpers.cxx @@ -36,7 +36,8 @@ ResourcePolicy ResourcePolicyHelpers::cpuBoundTask(char const* s, int requestedC [matcher = std::regex(s)](DeviceSpec const& spec) -> bool { return std::regex_match(spec.name, matcher); }, - [requestedCPUs](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { return accumulated.cpu >= requestedCPUs ? OfferScore::Enough : OfferScore::More; }}; + [requestedCPUs](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { return accumulated.cpu >= requestedCPUs ? OfferScore::Enough : OfferScore::More; }, + ComputingQuotaOffer{.cpu = requestedCPUs}}; } ResourcePolicy ResourcePolicyHelpers::rateLimitedSharedMemoryBoundTask(char const* s, int requestedSharedMemory, int requestedTimeslices) @@ -46,7 +47,7 @@ ResourcePolicy ResourcePolicyHelpers::rateLimitedSharedMemoryBoundTask(char cons [matcher = std::regex(s)](DeviceSpec const& spec) -> bool { return std::regex_match(spec.name, matcher); }, - [requestedSharedMemory, requestedTimeslices](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { + [requestedSharedMemory, requestedTimeslices](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { // If we have enough memory and not enough timeslices, // ignore further shared memory. if (accumulated.sharedMemory >= requestedSharedMemory && offer.timeslices == 0) { @@ -66,7 +67,8 @@ ResourcePolicy ResourcePolicyHelpers::rateLimitedSharedMemoryBoundTask(char cons return OfferScore::Enough; } // We need more resources - return OfferScore::More; }}; + return OfferScore::More; }, + ComputingQuotaOffer{.sharedMemory = requestedSharedMemory, .timeslices = requestedTimeslices}}; } ResourcePolicy ResourcePolicyHelpers::sharedMemoryBoundTask(char const* s, int requestedSharedMemory) @@ -76,11 +78,12 @@ ResourcePolicy ResourcePolicyHelpers::sharedMemoryBoundTask(char const* s, int r [matcher = std::regex(s)](DeviceSpec const& spec) -> bool { return std::regex_match(spec.name, matcher); }, - [requestedSharedMemory](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { + [requestedSharedMemory](ComputingQuotaOffer const& offer, ComputingQuotaOffer const& accumulated) -> OfferScore { if (offer.sharedMemory == 0) { return OfferScore::Unneeded; } - return (accumulated.sharedMemory + offer.sharedMemory)>= requestedSharedMemory ? OfferScore::Enough : OfferScore::More; }}; + return (accumulated.sharedMemory + offer.sharedMemory) >= requestedSharedMemory ? OfferScore::Enough : OfferScore::More; }, + ComputingQuotaOffer{.sharedMemory = requestedSharedMemory}}; } } // namespace o2::framework diff --git a/Framework/Core/src/StatusWebSocketHandler.cxx b/Framework/Core/src/StatusWebSocketHandler.cxx new file mode 100644 index 0000000000000..065a6f4b05b4a --- /dev/null +++ b/Framework/Core/src/StatusWebSocketHandler.cxx @@ -0,0 +1,587 @@ +// Copyright 2019-2026 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. + +#include "StatusWebSocketHandler.h" +#include "DPLWebSocket.h" +#include "DriverServerContext.h" +#include "Framework/DeviceControl.h" +#include +#include +#include "Framework/DeviceController.h" +#include "Framework/DeviceInfo.h" +#include "Framework/DeviceMetricsInfo.h" +#include "Framework/DeviceSpec.h" +#include "Framework/DeviceState.h" +#include "Framework/DeviceStateEnums.h" +#include "Framework/LogParsingHelpers.h" +#include "Framework/Signpost.h" +#include +#include +#include +#include +#include + +namespace o2::framework +{ + +namespace +{ + +std::string jsonEscape(std::string_view s) +{ + std::string out; + out.reserve(s.size() + 4); + for (unsigned char c : s) { + switch (c) { + case '"': + out += "\\\""; + break; + case '\\': + out += "\\\\"; + break; + case '\n': + out += "\\n"; + break; + case '\r': + out += "\\r"; + break; + case '\t': + out += "\\t"; + break; + default: + if (c < 0x20) { + char buf[8]; + snprintf(buf, sizeof(buf), "\\u%04x", c); + out += buf; + } else { + out += static_cast(c); + } + } + } + return out; +} + +char const* logLevelName(LogParsingHelpers::LogLevel level) +{ + switch (level) { + case LogParsingHelpers::LogLevel::Debug: + return "debug"; + case LogParsingHelpers::LogLevel::Info: + return "info"; + case LogParsingHelpers::LogLevel::Important: + return "important"; + case LogParsingHelpers::LogLevel::Warning: + return "warning"; + case LogParsingHelpers::LogLevel::Alarm: + return "alarm"; + case LogParsingHelpers::LogLevel::Error: + return "error"; + case LogParsingHelpers::LogLevel::Critical: + return "critical"; + case LogParsingHelpers::LogLevel::Fatal: + return "fatal"; + default: + return "unknown"; + } +} + +char const* streamingStateName(StreamingState s) +{ + switch (s) { + case StreamingState::Streaming: + return "Streaming"; + case StreamingState::EndOfStreaming: + return "EndOfStreaming"; + case StreamingState::Idle: + return "Idle"; + default: + return "Unknown"; + } +} + +void appendMetricValue(std::string& out, DeviceMetricsInfo const& info, size_t mi) +{ + auto const& metric = info.metrics[mi]; + if (metric.pos == 0) { + out += "null"; + return; + } + size_t last = (metric.pos - 1) % metricStorageSize(metric.type); + switch (metric.type) { + case MetricType::Int: + out += std::to_string(info.intMetrics[metric.storeIdx][last]); + break; + case MetricType::Float: { + char buf[32]; + snprintf(buf, sizeof(buf), "%g", static_cast(info.floatMetrics[metric.storeIdx][last])); + out += buf; + break; + } + case MetricType::Uint64: + out += std::to_string(info.uint64Metrics[metric.storeIdx][last]); + break; + default: + out += "null"; + } +} + +/// Extract the value of a simple string field from a flat JSON object. +/// e.g. extractField(R"({"cmd":"subscribe","device":"prod"})", "device") → "prod" +/// Returns empty string_view if not found. +std::string_view extractStringField(std::string_view json, std::string_view key) +{ + std::string needle; + needle += '"'; + needle += key; + needle += "\":"; + auto pos = json.find(needle); + if (pos == std::string_view::npos) { + return {}; + } + pos += needle.size(); + // skip optional whitespace between ':' and '"' + while (pos < json.size() && json[pos] == ' ') { + ++pos; + } + if (pos >= json.size() || json[pos] != '"') { + return {}; + } + ++pos; // skip opening quote + auto end = json.find('"', pos); + if (end == std::string_view::npos) { + return {}; + } + return json.substr(pos, end - pos); +} + +/// Extract the raw value of an array field from a flat JSON object. +/// e.g. extractArrayField(R"({"metrics":["a","b"]})", "metrics") → R"(["a","b"])" +std::string_view extractArrayField(std::string_view json, std::string_view key) +{ + std::string needle; + needle += '"'; + needle += key; + needle += "\":"; + auto pos = json.find(needle); + if (pos == std::string_view::npos) { + return {}; + } + pos += needle.size(); + // skip whitespace + while (pos < json.size() && json[pos] == ' ') { + ++pos; + } + if (pos >= json.size() || json[pos] != '[') { + return {}; + } + auto start = pos; + size_t depth = 0; + while (pos < json.size()) { + if (json[pos] == '[') { + ++depth; + } else if (json[pos] == ']') { + --depth; + if (depth == 0) { + return json.substr(start, pos - start + 1); + } + } + ++pos; + } + return {}; +} + +/// Iterate over the string elements of a JSON array of strings. +/// Calls @a callback for each unescaped string value. +template +void forEachStringInArray(std::string_view arr, F&& callback) +{ + // arr is like ["name1","name2"] + size_t pos = 0; + while (pos < arr.size()) { + auto q = arr.find('"', pos); + if (q == std::string_view::npos) { + break; + } + auto end = arr.find('"', q + 1); + if (end == std::string_view::npos) { + break; + } + callback(arr.substr(q + 1, end - q - 1)); + pos = end + 1; + } +} + +} // anonymous namespace + +StatusWebSocketHandler::StatusWebSocketHandler(DriverServerContext& context, WSDPLHandler* handler) + : mContext{context}, mHandler{handler} +{ +} + +StatusWebSocketHandler::~StatusWebSocketHandler() +{ + auto& handlers = mContext.statusHandlers; + handlers.erase(std::remove(handlers.begin(), handlers.end(), this), handlers.end()); +} + +void StatusWebSocketHandler::headers(std::map const&) +{ + sendSnapshot(); +} + +void StatusWebSocketHandler::frame(char const* data, size_t s) +{ + std::string_view msg{data, s}; + auto cmd = extractStringField(msg, "cmd"); + if (cmd.empty()) { + return; + } + auto deviceName = extractStringField(msg, "device"); + + if (cmd == "list_metrics") { + handleListMetrics(deviceName); + } else if (cmd == "subscribe") { + handleSubscribe(deviceName, extractArrayField(msg, "metrics")); + } else if (cmd == "unsubscribe") { + handleUnsubscribe(deviceName, extractArrayField(msg, "metrics")); + } else if (cmd == "subscribe_logs") { + handleSubscribeLogs(deviceName); + } else if (cmd == "unsubscribe_logs") { + handleUnsubscribeLogs(deviceName); + } else if (cmd == "start_devices") { + handleStartDevices(); + } else if (cmd == "enable_signpost") { + handleEnableSignpost(deviceName, extractArrayField(msg, "streams")); + } else if (cmd == "disable_signpost") { + handleDisableSignpost(deviceName, extractArrayField(msg, "streams")); + } +} + +void StatusWebSocketHandler::sendText(std::string const& json) +{ + std::vector outputs; + encode_websocket_frames(outputs, json.data(), json.size(), WebSocketOpCode::Text, 0); + mHandler->write(outputs); +} + +void StatusWebSocketHandler::sendSnapshot() +{ + auto const& specs = *mContext.specs; + auto const& infos = *mContext.infos; + + // Size subscription tables to current device count; grow lazily as needed. + mSubscribedMetrics.resize(specs.size()); + mLastLogSeq.resize(infos.size()); + for (size_t di = 0; di < infos.size(); ++di) { + mLastLogSeq[di] = infos[di].logSeq; + } + + std::string out; + out.reserve(512 + specs.size() * 128); + out += R"({"type":"snapshot","devices":[)"; + for (size_t di = 0; di < specs.size(); ++di) { + if (di > 0) { + out += ','; + } + auto const& info = infos[di]; + out += R"({"name":")"; + out += jsonEscape(specs[di].name); + out += R"(","pid":)"; + out += std::to_string(info.pid); + out += R"(,"active":)"; + out += info.active ? "true" : "false"; + out += R"(,"streamingState":")"; + out += streamingStateName(info.streamingState); + out += R"(","deviceState":")"; + out += jsonEscape(info.deviceState); + out += R"("})"; + } + out += "]}"; + sendText(out); +} + +void StatusWebSocketHandler::sendUpdate(size_t deviceIndex) +{ + auto const& specs = *mContext.specs; + auto const& metrics = *mContext.metrics; + + if (deviceIndex >= specs.size() || deviceIndex >= metrics.size()) { + return; + } + + // Lazily grow the subscription table if new devices were added after snapshot. + if (mSubscribedMetrics.size() <= deviceIndex) { + mSubscribedMetrics.resize(deviceIndex + 1); + } + + auto const& subscribed = mSubscribedMetrics[deviceIndex]; + if (subscribed.empty()) { + return; + } + + auto const& info = metrics[deviceIndex]; + std::string metricsJson; + metricsJson += '{'; + bool first = true; + for (size_t mi = 0; mi < info.metrics.size(); ++mi) { + if (!info.changed[mi]) { + continue; + } + auto const& metric = info.metrics[mi]; + if (metric.type == MetricType::String || + metric.type == MetricType::Enum || + metric.type == MetricType::Unknown) { + continue; + } + auto const& label = info.metricLabels[mi]; + std::string_view labelSV{label.label, label.size}; + if (subscribed.find(std::string(labelSV)) == subscribed.end()) { + continue; + } + if (!first) { + metricsJson += ','; + } + first = false; + metricsJson += '"'; + metricsJson += jsonEscape(labelSV); + metricsJson += "\":"; + appendMetricValue(metricsJson, info, mi); + } + metricsJson += '}'; + + if (first) { + // Nothing subscribed changed in this cycle. + return; + } + + std::string out; + out += R"({"type":"update","device":)"; + out += std::to_string(deviceIndex); + out += R"(,"name":")"; + out += jsonEscape(specs[deviceIndex].name); + out += R"(","metrics":)"; + out += metricsJson; + out += '}'; + sendText(out); +} + +void StatusWebSocketHandler::handleListMetrics(std::string_view deviceName) +{ + size_t di = findDeviceIndex(deviceName); + if (di == SIZE_MAX) { + return; + } + auto const& metrics = *mContext.metrics; + + std::string out; + out += R"({"type":"metrics_list","device":")"; + out += jsonEscape(deviceName); + out += R"(","metrics":[)"; + bool first = true; + if (di < metrics.size()) { + auto const& info = metrics[di]; + for (size_t mi = 0; mi < info.metrics.size(); ++mi) { + auto const& metric = info.metrics[mi]; + if (metric.type == MetricType::String || + metric.type == MetricType::Enum || + metric.type == MetricType::Unknown) { + continue; + } + if (!first) { + out += ','; + } + first = false; + auto const& label = info.metricLabels[mi]; + out += '"'; + out += jsonEscape({label.label, label.size}); + out += '"'; + } + } + out += "]}"; + sendText(out); +} + +void StatusWebSocketHandler::handleSubscribe(std::string_view deviceName, std::string_view metricsArr) +{ + size_t di = findDeviceIndex(deviceName); + if (di == SIZE_MAX || metricsArr.empty()) { + return; + } + if (mSubscribedMetrics.size() <= di) { + mSubscribedMetrics.resize(di + 1); + } + forEachStringInArray(metricsArr, [&](std::string_view name) { + mSubscribedMetrics[di].emplace(name); + }); +} + +void StatusWebSocketHandler::handleUnsubscribe(std::string_view deviceName, std::string_view metricsArr) +{ + size_t di = findDeviceIndex(deviceName); + if (di == SIZE_MAX || metricsArr.empty() || di >= mSubscribedMetrics.size()) { + return; + } + forEachStringInArray(metricsArr, [&](std::string_view name) { + mSubscribedMetrics[di].erase(std::string(name)); + }); +} + +size_t StatusWebSocketHandler::findDeviceIndex(std::string_view name) const +{ + auto const& specs = *mContext.specs; + for (size_t di = 0; di < specs.size(); ++di) { + if (specs[di].name == name) { + return di; + } + } + return SIZE_MAX; +} + +void StatusWebSocketHandler::handleStartDevices() +{ + for (auto const& info : *mContext.infos) { + if (info.active) { + kill(info.pid, SIGCONT); + } + } +} + +void StatusWebSocketHandler::handleEnableSignpost(std::string_view deviceName, std::string_view streamsArr) +{ + if (streamsArr.empty()) { + return; + } + if (deviceName.empty()) { + // Driver process — toggle in-process via o2_walk_logs. + forEachStringInArray(streamsArr, [](std::string_view streamName) { + std::string target(streamName); + o2_walk_logs([](char const* name, void* l, void* context) -> bool { + auto* log = static_cast<_o2_log_t*>(l); + if (static_cast(context)->compare(name) == 0) { + _o2_log_set_stacktrace(log, log->defaultStacktrace); + return false; + } + return true; + }, &target); + }); + } else { + size_t di = findDeviceIndex(deviceName); + if (di == SIZE_MAX || di >= mContext.controls->size() || !(*mContext.controls)[di].controller) { + return; + } + auto* controller = (*mContext.controls)[di].controller; + forEachStringInArray(streamsArr, [controller](std::string_view name) { + std::string cmd = "/signpost:enable "; + cmd += name; + controller->write(cmd.c_str(), cmd.size()); + }); + } +} + +void StatusWebSocketHandler::handleDisableSignpost(std::string_view deviceName, std::string_view streamsArr) +{ + if (streamsArr.empty()) { + return; + } + if (deviceName.empty()) { + forEachStringInArray(streamsArr, [](std::string_view streamName) { + std::string target(streamName); + o2_walk_logs([](char const* name, void* l, void* context) -> bool { + auto* log = static_cast<_o2_log_t*>(l); + if (static_cast(context)->compare(name) == 0) { + _o2_log_set_stacktrace(log, 0); + return false; + } + return true; + }, &target); + }); + } else { + size_t di = findDeviceIndex(deviceName); + if (di == SIZE_MAX || di >= mContext.controls->size() || !(*mContext.controls)[di].controller) { + return; + } + auto* controller = (*mContext.controls)[di].controller; + forEachStringInArray(streamsArr, [controller](std::string_view name) { + std::string cmd = "/signpost:disable "; + cmd += name; + controller->write(cmd.c_str(), cmd.size()); + }); + } +} + +void StatusWebSocketHandler::handleSubscribeLogs(std::string_view deviceName) +{ + size_t di = findDeviceIndex(deviceName); + if (di == SIZE_MAX) { + return; + } + if (mLastLogSeq.size() <= di) { + mLastLogSeq.resize(di + 1, 0); + } + // Start the cursor at the current log position so we only push future lines. + mLastLogSeq[di] = (*mContext.infos)[di].logSeq; + mLogSubscriptions.insert(di); +} + +void StatusWebSocketHandler::handleUnsubscribeLogs(std::string_view deviceName) +{ + size_t di = findDeviceIndex(deviceName); + if (di == SIZE_MAX) { + return; + } + mLogSubscriptions.erase(di); +} + +void StatusWebSocketHandler::sendNewLogs(size_t deviceIndex) +{ + if (mLogSubscriptions.find(deviceIndex) == mLogSubscriptions.end()) { + return; + } + auto const& infos = *mContext.infos; + auto const& specs = *mContext.specs; + if (deviceIndex >= infos.size() || deviceIndex >= specs.size()) { + return; + } + if (mLastLogSeq.size() <= deviceIndex) { + mLastLogSeq.resize(deviceIndex + 1, 0); + } + + auto const& info = infos[deviceIndex]; + size_t newLines = info.logSeq - mLastLogSeq[deviceIndex]; + if (newLines == 0) { + return; + } + // Cap to buffer size to avoid re-reading overwritten entries. + if (newLines > info.history.size()) { + newLines = info.history.size(); + } + + size_t histSize = info.history.size(); + // The oldest unread entry sits at (historyPos - newLines + histSize) % histSize. + size_t startPos = (info.historyPos + histSize - newLines) % histSize; + + std::string_view devName = specs[deviceIndex].name; + for (size_t i = 0; i < newLines; ++i) { + size_t pos = (startPos + i) % histSize; + std::string out; + out += R"({"type":"log","device":")"; + out += jsonEscape(devName); + out += R"(","level":")"; + out += logLevelName(info.historyLevel[pos]); + out += R"(","line":")"; + out += jsonEscape(info.history[pos]); + out += R"("})"; + sendText(out); + } + mLastLogSeq[deviceIndex] = info.logSeq; +} + +} // namespace o2::framework diff --git a/Framework/Core/src/StatusWebSocketHandler.h b/Framework/Core/src/StatusWebSocketHandler.h new file mode 100644 index 0000000000000..fb2f0beebbec2 --- /dev/null +++ b/Framework/Core/src/StatusWebSocketHandler.h @@ -0,0 +1,115 @@ +// Copyright 2019-2026 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. +#ifndef O2_FRAMEWORK_STATUSWEBSOCKETHANDLER_H_ +#define O2_FRAMEWORK_STATUSWEBSOCKETHANDLER_H_ + +#include "HTTPParser.h" +#include +#include +#include +#include +#include + +namespace o2::framework +{ +struct DriverServerContext; +struct WSDPLHandler; + +/// WebSocket handler for the /status endpoint. +/// +/// Protocol (client → driver): +/// {"cmd":"list_metrics","device":""} +/// → driver replies with {"type":"metrics_list","device":"","metrics":[...]} +/// +/// {"cmd":"subscribe","device":"","metrics":["m1","m2",...]} +/// → driver starts including those metrics in subsequent update frames +/// +/// {"cmd":"unsubscribe","device":"","metrics":["m1","m2",...]} +/// → driver stops sending those metrics +/// +/// {"cmd":"subscribe_logs","device":""} +/// → driver starts pushing new log lines for the device +/// +/// {"cmd":"unsubscribe_logs","device":""} +/// → driver stops pushing log lines for the device +/// +/// {"cmd":"enable_signpost","device":"","streams":["device","completion",...]} +/// → enable the named signpost log streams for a device (or the driver if device=="") +/// → known streams: "device","completion","monitoring_service","data_processor_context","stream_context" +/// +/// {"cmd":"disable_signpost","device":"","streams":["device","completion",...]} +/// → disable the named signpost log streams for a device +/// +/// {"cmd":"list_signposts"} +/// → driver replies with {"type":"signposts_list","streams":["device","completion",...]} +/// → lists the known stream names +/// +/// Protocol (driver → client): +/// {"type":"snapshot","devices":[{"name","pid","active","streamingState","deviceState"},...]} +/// → sent once on connect; contains no metrics or logs +/// +/// {"type":"update","device":,"name":"","metrics":{}} +/// → sent after each metrics cycle for devices with subscribed metrics that changed +/// +/// {"type":"metrics_list","device":"","metrics":["m1","m2",...]} +/// → reply to list_metrics command +/// +/// {"type":"log","device":"","level":"","line":""} +/// → pushed for each new log line from a subscribed device +struct StatusWebSocketHandler : public WebSocketHandler { + StatusWebSocketHandler(DriverServerContext& context, WSDPLHandler* handler); + ~StatusWebSocketHandler() override; + + /// Sends the minimal snapshot on handshake completion. + void headers(std::map const& headers) override; + /// Handles incoming commands from the MCP client. + void frame(char const* data, size_t s) override; + void beginChunk() override {} + void endChunk() override {} + void beginFragmentation() override {} + void endFragmentation() override {} + void control(char const* frame, size_t s) override {} + + /// Send a minimal JSON snapshot (device list + basic state, no metrics/logs). + void sendSnapshot(); + /// Push an update for device at @a deviceIndex. + /// Only metrics that are both changed[] and subscribed are included. + /// No-op if nothing subscribed or nothing changed for this device. + void sendUpdate(size_t deviceIndex); + /// Push any log lines for @a deviceIndex that arrived since the last call. + /// No-op if the device is not subscribed for logs. + void sendNewLogs(size_t deviceIndex); + + private: + void sendText(std::string const& json); + void handleListMetrics(std::string_view deviceName); + void handleSubscribe(std::string_view deviceName, std::string_view metricsJson); + void handleUnsubscribe(std::string_view deviceName, std::string_view metricsJson); + void handleSubscribeLogs(std::string_view deviceName); + void handleUnsubscribeLogs(std::string_view deviceName); + void handleStartDevices(); + void handleEnableSignpost(std::string_view deviceName, std::string_view streamsArr); + void handleDisableSignpost(std::string_view deviceName, std::string_view streamsArr); + size_t findDeviceIndex(std::string_view name) const; + + DriverServerContext& mContext; + WSDPLHandler* mHandler; + /// Per-device set of subscribed metric label strings. + /// Sized to specs->size() on sendSnapshot(); grows if new devices appear. + std::vector> mSubscribedMetrics; + /// Per-device log cursor: value of DeviceInfo::logSeq when we last sent logs. + std::vector mLastLogSeq; + /// Set of device indices whose logs are being streamed. + std::unordered_set mLogSubscriptions; +}; + +} // namespace o2::framework +#endif // O2_FRAMEWORK_STATUSWEBSOCKETHANDLER_H_ diff --git a/Framework/Core/src/VariantJSONHelpers.cxx b/Framework/Core/src/VariantJSONHelpers.cxx index fbb5abb331867..f3d6061020ea2 100644 --- a/Framework/Core/src/VariantJSONHelpers.cxx +++ b/Framework/Core/src/VariantJSONHelpers.cxx @@ -339,7 +339,7 @@ void writeVariant(std::ostream& o, Variant const& v) rapidjson::Writer w(osw); auto writeArray = [&](auto* values, size_t size) { - using T = std::remove_pointer_t; + using T = std::remove_cv_t>; w.StartArray(); for (auto i = 0u; i < size; ++i) { if constexpr (std::is_same_v) { diff --git a/Framework/Core/src/WSDriverClient.cxx b/Framework/Core/src/WSDriverClient.cxx index 43a407536cb59..97ea1b3dbf66a 100644 --- a/Framework/Core/src/WSDriverClient.cxx +++ b/Framework/Core/src/WSDriverClient.cxx @@ -188,48 +188,40 @@ void on_connect(uv_connect_t* connection, int status) state.tracingFlags = tracingFlags; }); - client->observe("/log-streams", [ref = context->ref](std::string_view cmd) { - auto& state = ref.get(); - static constexpr int prefixSize = std::string_view{"/log-streams "}.size(); - if (prefixSize > cmd.size()) { - LOG(error) << "Malformed log-streams request"; + client->observe("/signpost:enable", [](std::string_view cmd) { + static constexpr int prefixSize = std::string_view{"/signpost:enable "}.size(); + if (cmd.size() <= prefixSize) { + LOG(error) << "Malformed /signpost:enable request"; return; } - cmd.remove_prefix(prefixSize); - int logStreams = 0; + std::string name(cmd.substr(prefixSize)); + o2_walk_logs([](char const* logName, void* l, void* context) -> bool { + auto* log = static_cast<_o2_log_t*>(l); + auto* target = static_cast(context); + if (*target == logName) { + _o2_log_set_stacktrace(log, log->defaultStacktrace); + return false; + } + return true; + }, &name); + }); - auto error = std::from_chars(cmd.data(), cmd.data() + cmd.size(), logStreams); - if (error.ec != std::errc()) { - LOG(error) << "Malformed log-streams mask"; + client->observe("/signpost:disable", [](std::string_view cmd) { + static constexpr int prefixSize = std::string_view{"/signpost:disable "}.size(); + if (cmd.size() <= prefixSize) { + LOG(error) << "Malformed /signpost:disable request"; return; } - LOGP(info, "Logstreams flags set to {}", logStreams); - state.logStreams = logStreams; - if ((state.logStreams & DeviceState::LogStreams::DEVICE_LOG) != 0) { - O2_LOG_ENABLE(device); - } else { - O2_LOG_DISABLE(device); - } - if ((state.logStreams & DeviceState::LogStreams::COMPLETION_LOG) != 0) { - O2_LOG_ENABLE(completion); - } else { - O2_LOG_DISABLE(completion); - } - if ((state.logStreams & DeviceState::LogStreams::MONITORING_SERVICE_LOG) != 0) { - O2_LOG_ENABLE(monitoring_service); - } else { - O2_LOG_DISABLE(monitoring_service); - } - if ((state.logStreams & DeviceState::LogStreams::DATA_PROCESSOR_CONTEXT_LOG) != 0) { - O2_LOG_ENABLE(data_processor_context); - } else { - O2_LOG_DISABLE(data_processor_context); - } - if ((state.logStreams & DeviceState::LogStreams::STREAM_CONTEXT_LOG) != 0) { - O2_LOG_ENABLE(stream_context); - } else { - O2_LOG_DISABLE(stream_context); - } + std::string name(cmd.substr(prefixSize)); + o2_walk_logs([](char const* logName, void* l, void* context) -> bool { + auto* log = static_cast<_o2_log_t*>(l); + auto* target = static_cast(context); + if (*target == logName) { + _o2_log_set_stacktrace(log, 0); + return false; + } + return true; + }, &name); }); // Client will be filled in the line after. I can probably have a single diff --git a/Framework/Core/src/WorkflowHelpers.cxx b/Framework/Core/src/WorkflowHelpers.cxx index 2ef3df9426fde..9b80ef14d7621 100644 --- a/Framework/Core/src/WorkflowHelpers.cxx +++ b/Framework/Core/src/WorkflowHelpers.cxx @@ -279,6 +279,24 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext bool hasConditionOption = false; for (size_t ii = 0; ii < processor.inputs.size(); ++ii) { auto& input = processor.inputs[ii]; + bool hasProjectors = false; + bool hasIndexRecords = false; + bool hasCCDBURLs = false; + // all three options are exclusive + for (auto const& p : input.metadata) { + if (p.name.compare("projectors") == 0) { + hasProjectors = true; + break; + } + if (p.name.compare("index-records") == 0) { + hasIndexRecords = true; + break; + } + if (p.name.starts_with("ccdb:")) { + hasCCDBURLs = true; + break; + } + } switch (input.lifetime) { case Lifetime::Timer: { auto concrete = DataSpecUtils::asConcreteDataMatcher(input); @@ -318,29 +336,46 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext case Lifetime::Optional: break; } - if (DataSpecUtils::partialMatch(input, AODOrigins)) { - DataSpecUtils::updateInputList(dec.requestedAODs, InputSpec{input}); - } - if (DataSpecUtils::partialMatch(input, header::DataOrigin{"DYN"})) { + if (hasProjectors) { DataSpecUtils::updateInputList(dec.requestedDYNs, InputSpec{input}); - } - if (DataSpecUtils::partialMatch(input, header::DataOrigin{"IDX"})) { + } else if (hasIndexRecords) { DataSpecUtils::updateInputList(dec.requestedIDXs, InputSpec{input}); - } - if (DataSpecUtils::partialMatch(input, header::DataOrigin{"ATIM"})) { + } else if (hasCCDBURLs) { DataSpecUtils::updateInputList(dec.requestedTIMs, InputSpec{input}); + } else if (DataSpecUtils::partialMatch(input, AODOrigins)) { + DataSpecUtils::updateInputList(dec.requestedAODs, InputSpec{input}); } } std::ranges::stable_sort(timer.outputs, [](OutputSpec const& a, OutputSpec const& b) { return *DataSpecUtils::getOptionalSubSpec(a) < *DataSpecUtils::getOptionalSubSpec(b); }); for (auto& output : processor.outputs) { - if (DataSpecUtils::partialMatch(output, AODOrigins)) { - dec.providedAODs.emplace_back(output); - } else if (DataSpecUtils::partialMatch(output, header::DataOrigin{"DYN"})) { + bool hasProjectors = false; + bool hasIndexRecords = false; + bool hasCCDBURLs = false; + // all three options are exclusive + for (auto const& p : output.metadata) { + if (p.name.compare("projectors") == 0) { + hasProjectors = true; + break; + } + if (p.name.compare("index-records") == 0) { + hasIndexRecords = true; + break; + } + if (p.name.starts_with("ccdb:")) { + hasCCDBURLs = true; + break; + } + } + if (hasProjectors) { dec.providedDYNs.emplace_back(output); - } else if (DataSpecUtils::partialMatch(output, header::DataOrigin{"ATIM"})) { + } else if (hasCCDBURLs) { dec.providedTIMs.emplace_back(output); + } else if (hasIndexRecords) { + dec.providedIDXs.emplace_back(output); + } else if (DataSpecUtils::partialMatch(output, AODOrigins)) { + dec.providedAODs.emplace_back(output); } else if (DataSpecUtils::partialMatch(output, header::DataOrigin{"ATSK"})) { dec.providedOutputObjHist.emplace_back(output); auto it = std::ranges::find_if(dec.outObjHistMap, [&](auto&& x) { return x.id == hash; }); @@ -350,6 +385,7 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext it->bindings.push_back(output.binding.value); } } + if (output.lifetime == Lifetime::Condition) { providedCCDBs.push_back(output); } @@ -358,10 +394,6 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext auto inputSpecLessThan = [](InputSpec const& lhs, InputSpec const& rhs) { return DataSpecUtils::describe(lhs) < DataSpecUtils::describe(rhs); }; auto outputSpecLessThan = [](OutputSpec const& lhs, OutputSpec const& rhs) { return DataSpecUtils::describe(lhs) < DataSpecUtils::describe(rhs); }; - std::ranges::sort(dec.requestedDYNs, inputSpecLessThan); - std::ranges::sort(dec.requestedTIMs, inputSpecLessThan); - std::ranges::sort(dec.providedDYNs, outputSpecLessThan); - std::ranges::sort(dec.providedTIMs, outputSpecLessThan); DataProcessorSpec indexBuilder{ "internal-dpl-aod-index-builder", @@ -369,14 +401,18 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext {}, AlgorithmSpec::dummyAlgorithm(), // real algorithm will be set in adjustTopology {}}; - AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.requestedIDXs, dec.requestedAODs, dec.requestedDYNs, indexBuilder); + std::ranges::sort(dec.requestedIDXs, inputSpecLessThan); + std::ranges::sort(dec.providedIDXs, outputSpecLessThan); + dec.requestedIDXs | views::filter_not_matching(dec.providedIDXs) | sinks::append_to{dec.builderInputs}; + AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.builderInputs, dec.requestedAODs, dec.requestedDYNs, indexBuilder); + std::ranges::sort(dec.requestedTIMs, inputSpecLessThan); + std::ranges::sort(dec.providedTIMs, outputSpecLessThan); dec.requestedTIMs | views::filter_not_matching(dec.providedTIMs) | sinks::append_to{dec.analysisCCDBInputs}; - DeploymentMode deploymentMode = DefaultsHelpers::deploymentMode(); - if (deploymentMode != DeploymentMode::OnlineDDS && deploymentMode != DeploymentMode::OnlineECS) { - AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.analysisCCDBInputs, dec.requestedAODs, dec.requestedTIMs, analysisCCDBBackend); - } + AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.analysisCCDBInputs, dec.requestedAODs, dec.requestedDYNs, analysisCCDBBackend); + std::ranges::sort(dec.requestedDYNs, inputSpecLessThan); + std::ranges::sort(dec.providedDYNs, outputSpecLessThan); dec.requestedDYNs | views::filter_not_matching(dec.providedDYNs) | sinks::append_to{dec.spawnerInputs}; DataProcessorSpec aodSpawner{ @@ -386,6 +422,9 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext AlgorithmSpec::dummyAlgorithm(), // real algorithm will be set in adjustTopology {}}; AnalysisSupportHelpers::addMissingOutputsToSpawner({}, dec.spawnerInputs, dec.requestedAODs, aodSpawner); + + std::ranges::sort(dec.requestedAODs, inputSpecLessThan); + std::ranges::sort(dec.providedAODs, outputSpecLessThan); AnalysisSupportHelpers::addMissingOutputsToReader(dec.providedAODs, dec.requestedAODs, aodReader); std::ranges::sort(requestedCCDBs, inputSpecLessThan); @@ -409,13 +448,22 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext extraSpecs.push_back(indexBuilder); } + // add the Analysys CCDB backend which reads CCDB objects using a provided table + DeploymentMode deploymentMode = DefaultsHelpers::deploymentMode(); + if (deploymentMode != DeploymentMode::OnlineDDS && deploymentMode != DeploymentMode::OnlineECS) { + if (analysisCCDBBackend.outputs.empty() == false) { + extraSpecs.push_back(analysisCCDBBackend); + } + } + + auto tfnsource = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { + return std::ranges::any_of(spec.outputs, [](OutputSpec const& output) { + return DataSpecUtils::match(output, "TFN", "TFNumber", 0); + }); + }); + // add the reader if (aodReader.outputs.empty() == false) { - auto tfnsource = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { - return std::ranges::any_of(spec.outputs, [](OutputSpec const& output) { - return DataSpecUtils::match(output, "TFN", "TFNumber", 0); - }); - }); if (tfnsource == workflow.end()) { // add normal reader aodReader.outputs.emplace_back(OutputSpec{"TFN", "TFNumber"}); @@ -515,11 +563,6 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext DataSpecUtils::updateOutputList(workflow[enumCandidate].outputs, OutputSpec{{"ccdb-diststf"}, dstf, Lifetime::Timeframe}); } - // add the Analysys CCDB backend which reads CCDB objects using a provided table - if (analysisCCDBBackend.outputs.empty() == false) { - extraSpecs.push_back(analysisCCDBBackend); - } - // add the timer if (timer.outputs.empty() == false) { extraSpecs.push_back(timer); @@ -549,7 +592,7 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext continue; } // AODs are skipped in any case. - if (DataSpecUtils::partialMatch(dec.outputsInputs[ii], extendedAODOrigins)) { + if (DataSpecUtils::partialMatch(dec.outputsInputs[ii], AODOrigins)) { continue; } redirectedOutputsInputs.emplace_back(dec.outputsInputs[ii]); @@ -557,6 +600,12 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext std::vector unmatched; auto forwardingDestination = ctx.options().get("forwarding-destination"); + // update tfnsource iterator (could be aod-reader) + tfnsource = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { + return std::ranges::any_of(spec.outputs, [](OutputSpec const& output) { + return DataSpecUtils::match(output, "TFN", "TFNumber", 0); + }); + }); if (redirectedOutputsInputs.size() > 0 && forwardingDestination == "file") { auto fileSink = CommonDataProcessors::getGlobalFileSink(redirectedOutputsInputs, unmatched); if (unmatched.size() != redirectedOutputsInputs.size()) { @@ -568,7 +617,7 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext } else if (forwardingDestination != "drop") { throw runtime_error_f("Unknown forwarding destination %s", forwardingDestination.c_str()); } - if (unmatched.size() > 0 || redirectedOutputsInputs.size() > 0) { + if ((unmatched.size() > 0) || (redirectedOutputsInputs.size() > 0) || (tfnsource != workflow.end())) { std::vector ignored = unmatched; ignored.insert(ignored.end(), redirectedOutputsInputs.begin(), redirectedOutputsInputs.end()); for (auto& ignoredInput : ignored) { @@ -577,10 +626,16 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext // Use the new dummy sink when the AOD reader is there O2_SIGNPOST_ID_GENERATE(sid, workflow_helpers); - if (aodReader.outputs.empty() == false) { + if (tfnsource != workflow.end()) { + DataSpecUtils::updateInputList(ignored, InputSpec{"tfn", "TFN", "TFNumber", 0, Lifetime::Sporadic}); + DataSpecUtils::updateInputList(ignored, InputSpec{"tff", "TFF", "TFFilename", 0, Lifetime::Sporadic}); + } + + if (tfnsource != workflow.end() && !tfnsource->name.starts_with("aod-producer-workflow")) { // any tfnsource except the aod-producer should use scheduled sink O2_SIGNPOST_EVENT_EMIT(workflow_helpers, sid, "injectServiceDevices", "Injecting scheduled dummy sink"); + // if there is a tfnsource, make sure the sink gets TFN/TFF extraSpecs.push_back(CommonDataProcessors::getScheduledDummySink(ignored)); - } else { + } else { // if there is no tfn source or if that source is aod-producer-workflow, out-of-band channel is used to propagate the number of consumed timeframes O2_SIGNPOST_EVENT_EMIT(workflow_helpers, sid, "injectServiceDevices", "Injecting rate limited dummy sink"); std::string rateLimitingChannelConfigOutput; if (rateLimitingIPCID != -1) { @@ -684,7 +739,7 @@ void WorkflowHelpers::injectAODWriter(WorkflowSpec& workflow, ConfigContext cons // select outputs of type AOD which need to be saved dec.outputsInputsAOD.clear(); for (auto ii = 0u; ii < dec.outputsInputs.size(); ii++) { - if (DataSpecUtils::partialMatch(dec.outputsInputs[ii], extendedAODOrigins)) { + if (DataSpecUtils::partialMatch(dec.outputsInputs[ii], AODOrigins)) { auto ds = dod->getDataOutputDescriptors(dec.outputsInputs[ii]); if (ds.size() > 0 || dec.isDangling[ii]) { dec.outputsInputsAOD.emplace_back(dec.outputsInputs[ii]); @@ -817,10 +872,8 @@ void WorkflowHelpers::constructGraph(const WorkflowSpec& workflow, if (forwards.empty()) { errorDueToMissingOutputFor(consumer, input); } - availableOutputsInfo.erase(std::remove_if(availableOutputsInfo.begin(), availableOutputsInfo.end(), [](auto& info) { return info.enabled == false; }), availableOutputsInfo.end()); - for (auto& forward : forwards) { - availableOutputsInfo.push_back(forward); - } + availableOutputsInfo.erase(std::remove_if(availableOutputsInfo.begin(), availableOutputsInfo.end(), [](auto const& info) { return info.enabled == false; }), availableOutputsInfo.end()); + std::ranges::copy(forwards, std::back_inserter(availableOutputsInfo)); } O2_SIGNPOST_END(workflow_helpers, sid, "input matching", ""); } @@ -899,14 +952,14 @@ WorkflowParsingState WorkflowHelpers::verifyWorkflow(const o2::framework::Workfl return WorkflowParsingState::Empty; } std::set validNames; - std::vector availableOutputs; - std::vector requiredInputs; + // std::vector availableOutputs; + // std::vector requiredInputs; // An index many to one index to go from a given input to the // associated spec - std::map inputToSpec; + // std::map inputToSpec; // A one to one index to go from a given output to the Spec emitting it - std::map outputToSpec; + // std::map outputToSpec; std::ostringstream ss; @@ -1087,14 +1140,14 @@ void WorkflowHelpers::validateEdges(WorkflowSpec const& workflow, // Get the input lifetime and the output lifetime. // Output lifetime must be Timeframe if the input lifetime is Timeframe. bool hasErrors = false; - for (auto& edge : edges) { + for (auto const& edge : edges) { DataProcessorSpec const& producer = workflow[edge.producer]; DataProcessorSpec const& consumer = workflow[edge.consumer]; DataProcessorPoliciesInfo const& producerPolicies = policies[edge.producer]; DataProcessorPoliciesInfo const& consumerPolicies = policies[edge.consumer]; OutputSpec const& output = outputs[edge.outputGlobalIndex]; InputSpec const& input = consumer.inputs[edge.consumerInputIndex]; - for (auto& validator : defaultValidators) { + for (auto const& validator : defaultValidators) { hasErrors |= !validator(errors, producer, output, producerPolicies, consumer, input, consumerPolicies); } } diff --git a/Framework/Core/src/WorkflowSerializationHelpers.cxx b/Framework/Core/src/WorkflowSerializationHelpers.cxx index 9624a2dfd0d3e..b824e8d0bb424 100644 --- a/Framework/Core/src/WorkflowSerializationHelpers.cxx +++ b/Framework/Core/src/WorkflowSerializationHelpers.cxx @@ -430,10 +430,11 @@ struct WorkflowImporter : public rapidjson::BaseReaderHandler, inputMatcherNodes.push_back(std::move(node)); } else if (in(State::IN_OUTPUT)) { if (outputHasSubSpec) { - dataProcessors.back().outputs.push_back(OutputSpec({binding}, origin, description, subspec, lifetime)); + dataProcessors.back().outputs.push_back(OutputSpec({binding}, origin, description, subspec, lifetime, outputOptions)); } else { - dataProcessors.back().outputs.push_back(OutputSpec({binding}, {origin, description}, lifetime)); + dataProcessors.back().outputs.push_back(OutputSpec({binding}, {origin, description}, lifetime, outputOptions)); } + outputOptions.clear(); outputHasSubSpec = false; } else if (in(State::IN_OPTION)) { std::unique_ptr opt{nullptr}; diff --git a/Framework/Core/src/runDataProcessing.cxx b/Framework/Core/src/runDataProcessing.cxx index 815fce47544d0..c58f8e7287b3b 100644 --- a/Framework/Core/src/runDataProcessing.cxx +++ b/Framework/Core/src/runDataProcessing.cxx @@ -68,6 +68,7 @@ #include "Framework/DefaultsHelpers.h" #include "ProcessingPoliciesHelpers.h" #include "DriverServerContext.h" +#include "StatusWebSocketHandler.h" #include "HTTPParser.h" #include "DPLWebSocket.h" #include "ArrowSupport.h" @@ -891,6 +892,7 @@ void processChildrenOutput(uv_loop_t* loop, info.history[info.historyPos] = token; info.historyLevel[info.historyPos] = logLevel; info.historyPos = (info.historyPos + 1) % info.history.size(); + info.logSeq++; fmt::print("[{}:{}]: {}\n", info.pid, spec.id, token); } // We keep track of the maximum log error a @@ -1061,7 +1063,7 @@ int doChild(int argc, char** argv, ServiceRegistry& serviceRegistry, ("exit-transition-timeout", bpo::value()->default_value(defaultExitTransitionTimeout), "how many second to wait before switching from RUN to READY") // ("error-on-exit-transition-timeout", bpo::value()->zero_tokens()->default_value(false), "print error instead of warning when exit transition timer expires") // ("data-processing-timeout", bpo::value()->default_value(defaultDataProcessingTimeout), "how many second to wait before stopping data processing and allowing data calibration") // - ("timeframes-rate-limit", bpo::value()->default_value("0"), "how many timeframe can be in fly at the same moment (0 disables)") // + ("timeframes-rate-limit", bpo::value()->default_value("0"), "how many timeframe can be in flight at the same moment (0 disables)") // ("configuration,cfg", bpo::value()->default_value("command-line"), "configuration backend") // ("infologger-mode", bpo::value()->default_value(defaultInfologgerMode), "O2_INFOLOGGER_MODE override"); r.fConfig.AddToCmdLineOptions(optsDesc, true); @@ -1541,6 +1543,11 @@ int runStateMachine(DataProcessorSpecs const& workflow, uv_async_init(loop, serverContext.asyncLogProcessing, [](uv_async_t* handle) { auto* context = (DriverServerContext*)handle->data; processChildrenOutput(context->loop, *context->driver, *context->infos, *context->specs, *context->controls); + for (auto* statusHandler : context->statusHandlers) { + for (size_t di = 0; di < context->infos->size(); ++di) { + statusHandler->sendNewLogs(di); + } + } }); while (true) { @@ -1678,15 +1685,15 @@ int runStateMachine(DataProcessorSpecs const& workflow, continue; } // ignore devices with no metadata in inputs - auto hasMetadata = std::any_of(device.inputs.begin(), device.inputs.end(), [](InputSpec const& spec) { + auto hasMetadata = std::ranges::any_of(device.inputs, [](InputSpec const& spec) { return spec.metadata.empty() == false; }); if (!hasMetadata) { continue; } // ignore devices with no control options - auto hasControls = std::any_of(device.inputs.begin(), device.inputs.end(), [](InputSpec const& spec) { - return std::any_of(spec.metadata.begin(), spec.metadata.end(), [](ConfigParamSpec const& param) { + auto hasControls = std::ranges::any_of(device.inputs, [](InputSpec const& spec) { + return std::ranges::any_of(spec.metadata, [](ConfigParamSpec const& param) { return param.type == VariantType::Bool && param.name.find("control:") != std::string::npos; }); }); @@ -2045,9 +2052,11 @@ int runStateMachine(DataProcessorSpecs const& workflow, "--aod-max-io-rate", "--aod-parent-access-level", "--aod-parent-base-path-replacement", + "--aod-origin-level-mapping", "--driver-client-backend", "--fairmq-ipc-prefix", "--readers", + "--ccdb-fetchers", "--resources-monitoring", "--resources-monitoring-file", "--resources-monitoring-dump-interval", diff --git a/Framework/Core/test/benchmark_DataRelayer.cxx b/Framework/Core/test/benchmark_DataRelayer.cxx index 3c3d2294fdd7e..e7df8fbb2fe9b 100644 --- a/Framework/Core/test/benchmark_DataRelayer.cxx +++ b/Framework/Core/test/benchmark_DataRelayer.cxx @@ -14,11 +14,19 @@ #include "Headers/Stack.h" #include "Framework/CompletionPolicyHelpers.h" #include "Framework/DataRelayer.h" +#include "Framework/DataModelViews.h" #include "Framework/DataProcessingHeader.h" +#include "Framework/DataProcessingStates.h" +#include "Framework/DataProcessingStats.h" +#include "Framework/DeviceState.h" +#include "Framework/DriverConfig.h" +#include "Framework/ServiceRegistryHelpers.h" +#include "Framework/TimingHelpers.h" #include #include #include #include +#include using Monitoring = o2::monitoring::Monitoring; using namespace o2::framework; @@ -26,6 +34,42 @@ using DataHeader = o2::header::DataHeader; using Stack = o2::header::Stack; using RecordAction = o2::framework::DataRelayer::RecordAction; +struct BenchmarkServices { + Monitoring monitoring; + const DriverConfig driverConfig{.batch = false}; + DataProcessingStates states{ + TimingHelpers::defaultRealtimeBaseConfigurator(0, uv_default_loop()), + TimingHelpers::defaultCPUTimeConfigurator(uv_default_loop())}; + DataProcessingStats stats{ + TimingHelpers::defaultRealtimeBaseConfigurator(0, uv_default_loop()), + TimingHelpers::defaultCPUTimeConfigurator(uv_default_loop()), + {}}; + DeviceState deviceState; + ServiceRegistry registry; + + ServiceRegistryRef ref() + { + using MetricSpec = DataProcessingStats::MetricSpec; + int quickUpdateInterval = 1; + std::vector specs{ + MetricSpec{.name = "malformed_inputs", .metricId = static_cast(ProcessingStatsId::MALFORMED_INPUTS), .minPublishInterval = quickUpdateInterval}, + MetricSpec{.name = "dropped_computations", .metricId = static_cast(ProcessingStatsId::DROPPED_COMPUTATIONS), .minPublishInterval = quickUpdateInterval}, + MetricSpec{.name = "dropped_incoming_messages", .metricId = static_cast(ProcessingStatsId::DROPPED_INCOMING_MESSAGES), .minPublishInterval = quickUpdateInterval}, + MetricSpec{.name = "relayed_messages", .metricId = static_cast(ProcessingStatsId::RELAYED_MESSAGES), .minPublishInterval = quickUpdateInterval}}; + for (auto& spec : specs) { + stats.registerMetric(spec); + } + + ServiceRegistryRef r{registry}; + r.registerService(ServiceRegistryHelpers::handleForService(&monitoring)); + r.registerService(ServiceRegistryHelpers::handleForService(&states)); + r.registerService(ServiceRegistryHelpers::handleForService(&stats)); + r.registerService(ServiceRegistryHelpers::handleForService(&driverConfig)); + r.registerService(ServiceRegistryHelpers::handleForService(&deviceState)); + return r; + } +}; + // a simple benchmark of the contribution of the pure message creation // this was important when the benchmarks below included the message // creation inside the benchmark loop, its somewhat obsolete now but @@ -54,7 +98,7 @@ BENCHMARK(BM_RelayMessageCreation); // and the subsequent InputRecord is immediately requested. static void BM_RelaySingleSlot(benchmark::State& state) { - Monitoring metrics; + BenchmarkServices services; InputSpec spec{"clusters", "TPC", "CLUSTERS"}; std::vector inputs = { @@ -64,8 +108,7 @@ static void BM_RelaySingleSlot(benchmark::State& state) std::vector infos{1}; TimesliceIndex index{1, infos}; auto policy = CompletionPolicyHelpers::consumeWhenAny(); - ServiceRegistry registry; - DataRelayer relayer(policy, inputs, index, {registry}, -1); + DataRelayer relayer(policy, inputs, index, services.ref(), -1); relayer.setPipelineLength(4); // Let's create a dummy O2 Message with two headers in the stack: @@ -96,8 +139,8 @@ static void BM_RelaySingleSlot(benchmark::State& state) assert(ready[0].op == CompletionPolicy::CompletionOp::Consume); auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); assert(result.size() == 1); - assert(result.at(0).size() == 1); - inflightMessages = std::move(result[0].messages); + assert((result.at(0) | count_parts{}) == 1); + inflightMessages = std::move(result[0]); } } @@ -106,7 +149,7 @@ BENCHMARK(BM_RelaySingleSlot); // This one will simulate a single input. static void BM_RelayMultipleSlots(benchmark::State& state) { - Monitoring metrics; + BenchmarkServices services; InputSpec spec{"clusters", "TPC", "CLUSTERS"}; std::vector inputs = { @@ -117,8 +160,7 @@ static void BM_RelayMultipleSlots(benchmark::State& state) TimesliceIndex index{1, infos}; auto policy = CompletionPolicyHelpers::consumeWhenAny(); - ServiceRegistry registry; - DataRelayer relayer(policy, inputs, index, {registry}, -1); + DataRelayer relayer(policy, inputs, index, services.ref(), -1); relayer.setPipelineLength(4); // Let's create a dummy O2 Message with two headers in the stack: @@ -153,8 +195,8 @@ static void BM_RelayMultipleSlots(benchmark::State& state) assert(ready[0].op == CompletionPolicy::CompletionOp::Consume); auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); assert(result.size() == 1); - assert(result.at(0).size() == 1); - inflightMessages = std::move(result[0].messages); + assert((result.at(0) | count_parts{}) == 1); + inflightMessages = std::move(result[0]); } } @@ -163,7 +205,7 @@ BENCHMARK(BM_RelayMultipleSlots); /// In this case we have a record with two entries static void BM_RelayMultipleRoutes(benchmark::State& state) { - Monitoring metrics; + BenchmarkServices services; InputSpec spec1{"clusters", "TPC", "CLUSTERS"}; InputSpec spec2{"tracks", "TPC", "TRACKS"}; @@ -176,8 +218,7 @@ static void BM_RelayMultipleRoutes(benchmark::State& state) TimesliceIndex index{1, infos}; auto policy = CompletionPolicyHelpers::consumeWhenAny(); - ServiceRegistry registry; - DataRelayer relayer(policy, inputs, index, {registry}, -1); + DataRelayer relayer(policy, inputs, index, services.ref(), -1); relayer.setPipelineLength(4); // Let's create a dummy O2 Message with two headers in the stack: @@ -228,11 +269,11 @@ static void BM_RelayMultipleRoutes(benchmark::State& state) assert(ready[0].op == CompletionPolicy::CompletionOp::Consume); auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); assert(result.size() == 2); - assert(result.at(0).size() == 1); - assert(result.at(1).size() == 1); - inflightMessages = std::move(result[0].messages); - inflightMessages.emplace_back(std::move(result[1].messages[0])); - inflightMessages.emplace_back(std::move(result[1].messages[1])); + assert((result.at(0) | count_parts{}) == 1); + assert((result.at(1) | count_parts{}) == 1); + inflightMessages = std::move(result[0]); + inflightMessages.emplace_back(std::move(result[1][0])); + inflightMessages.emplace_back(std::move(result[1][1])); } } @@ -241,7 +282,7 @@ BENCHMARK(BM_RelayMultipleRoutes); /// In this case we have a record with two entries static void BM_RelaySplitParts(benchmark::State& state) { - Monitoring metrics; + BenchmarkServices services; InputSpec spec1{"clusters", "TPC", "CLUSTERS"}; std::vector inputs = { @@ -253,8 +294,7 @@ static void BM_RelaySplitParts(benchmark::State& state) TimesliceIndex index{1, infos}; auto policy = CompletionPolicyHelpers::consumeWhenAny(); - ServiceRegistry registry; - DataRelayer relayer(policy, inputs, index, {registry}, -1); + DataRelayer relayer(policy, inputs, index, services.ref(), -1); relayer.setPipelineLength(4); // Let's create a dummy O2 Message with two headers in the stack: @@ -293,7 +333,7 @@ static void BM_RelaySplitParts(benchmark::State& state) relayer.getReadyToProcess(ready); assert(ready.size() == 1); assert(ready[0].op == CompletionPolicy::CompletionOp::Consume); - inflightMessages = std::move(relayer.consumeAllInputsForTimeslice(ready[0].slot)[0].messages); + inflightMessages = std::move(relayer.consumeAllInputsForTimeslice(ready[0].slot)[0]); } } @@ -301,7 +341,7 @@ BENCHMARK(BM_RelaySplitParts)->Arg(10)->Arg(100)->Arg(1000); static void BM_RelayMultiplePayloads(benchmark::State& state) { - Monitoring metrics; + BenchmarkServices services; InputSpec spec1{"clusters", "TPC", "CLUSTERS"}; std::vector inputs = { @@ -313,8 +353,7 @@ static void BM_RelayMultiplePayloads(benchmark::State& state) TimesliceIndex index{1, infos}; auto policy = CompletionPolicyHelpers::consumeWhenAny(); - ServiceRegistry registry; - DataRelayer relayer(policy, inputs, index, {registry}, -1); + DataRelayer relayer(policy, inputs, index, services.ref(), -1); relayer.setPipelineLength(4); // DataHeader matching the one provided in the input @@ -348,7 +387,7 @@ static void BM_RelayMultiplePayloads(benchmark::State& state) relayer.getReadyToProcess(ready); assert(ready.size() == 1); assert(ready[0].op == CompletionPolicy::CompletionOp::Consume); - inflightMessages = std::move(relayer.consumeAllInputsForTimeslice(ready[0].slot)[0].messages); + inflightMessages = std::move(relayer.consumeAllInputsForTimeslice(ready[0].slot)[0]); } } diff --git a/Framework/Core/test/benchmark_InputRecord.cxx b/Framework/Core/test/benchmark_InputRecord.cxx index 69fc3c970c1e1..e3ec00ac815ed 100644 --- a/Framework/Core/test/benchmark_InputRecord.cxx +++ b/Framework/Core/test/benchmark_InputRecord.cxx @@ -47,7 +47,12 @@ static void BM_InputRecordGenericGetters(benchmark::State& state) createRoute("z_source", spec3)}; // First of all we test if an empty registry behaves as expected, raising a // bunch of exceptions. - InputSpan span{[](size_t) { return DataRef{nullptr, nullptr, nullptr}; }, 0}; + InputSpan span{ + [](size_t) -> size_t { return 0; }, + nullptr, + [](size_t, DataRefIndices) { return DataRef{nullptr, nullptr, nullptr}; }, + [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + 0}; ServiceRegistry registry; InputRecord emptyRecord(schema, span, registry); @@ -82,7 +87,12 @@ static void BM_InputRecordGenericGetters(benchmark::State& state) createMessage(dh1, 1); createMessage(dh2, 2); createEmpty(); - InputSpan span2{[&inputs](size_t i) { return DataRef{nullptr, static_cast(inputs[2 * i]), static_cast(inputs[2 * i + 1])}; }, inputs.size() / 2}; + InputSpan span2{ + [](size_t) -> size_t { return 1; }, + nullptr, + [&inputs](size_t i, DataRefIndices idx) { return DataRef{nullptr, static_cast(inputs[2 * i + idx.headerIdx]), static_cast(inputs[2 * i + idx.payloadIdx])}; }, + [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + inputs.size() / 2}; InputRecord record{schema, span2, registry}; for (auto _ : state) { diff --git a/Framework/Core/test/test_CompletionPolicy.cxx b/Framework/Core/test/test_CompletionPolicy.cxx index 059f20b352b3d..cc16ba95ba8f2 100644 --- a/Framework/Core/test/test_CompletionPolicy.cxx +++ b/Framework/Core/test/test_CompletionPolicy.cxx @@ -55,7 +55,12 @@ TEST_CASE("TestCompletionPolicy_callback") std::vector policies{ {"test", matcher, callback}}; CompletionPolicy::InputSetElement ref{nullptr, reinterpret_cast(stack.data()), nullptr}; - InputSpan const& inputs{[&ref](size_t) { return ref; }, 1}; + InputSpan const inputs{ + [](size_t) -> size_t { return 1; }, + nullptr, + [&ref](size_t, DataRefIndices) -> DataRef { return ref; }, + [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + 1}; std::vector specs; ServiceRegistryRef servicesRef{services}; for (auto& policy : policies) { diff --git a/Framework/Core/test/test_Concepts.cxx b/Framework/Core/test/test_Concepts.cxx index 982c748e701e4..375e537cfaec0 100644 --- a/Framework/Core/test/test_Concepts.cxx +++ b/Framework/Core/test/test_Concepts.cxx @@ -87,7 +87,7 @@ TEST_CASE("IdentificationConcepts") REQUIRE(with_originals); - REQUIRE(with_sources>::metadata>); + REQUIRE(with_sources_generator>::metadata>); REQUIRE(with_base_table); diff --git a/Framework/Core/test/test_DataModelViews.cxx b/Framework/Core/test/test_DataModelViews.cxx new file mode 100644 index 0000000000000..37d163e9e41c6 --- /dev/null +++ b/Framework/Core/test/test_DataModelViews.cxx @@ -0,0 +1,199 @@ +// Copyright 2019-2026 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. + +#include "Framework/DataModelViews.h" +#include "Framework/DataProcessingHeader.h" +#include "Headers/DataHeader.h" +#include "Headers/Stack.h" +#include +#include +#include + +using namespace o2::framework; +using DataHeader = o2::header::DataHeader; +using Stack = o2::header::Stack; + +namespace +{ +// Build a header message containing a DataHeader with the given split-payload fields. +fair::mq::MessagePtr makeHeader(fair::mq::TransportFactory& transport, + uint32_t splitPayloadParts, uint32_t splitPayloadIndex) +{ + DataHeader dh; + dh.dataDescription = "TEST"; + dh.dataOrigin = "TST"; + dh.subSpecification = 0; + dh.splitPayloadParts = splitPayloadParts; + dh.splitPayloadIndex = splitPayloadIndex; + DataProcessingHeader dph{0, 1}; + Stack stack{dh, dph}; + auto msg = transport.CreateMessage(stack.size()); + memcpy(msg->GetData(), stack.data(), stack.size()); + return msg; +} + +fair::mq::MessagePtr makePayload(fair::mq::TransportFactory& transport) +{ + return transport.CreateMessage(4); +} +} // namespace + +// --------------------------------------------------------------------------- +// Single [header, payload] pair (splitPayloadParts == 0) +// --------------------------------------------------------------------------- +TEST_CASE("SinglePair") +{ + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + + std::vector msgs; + msgs.emplace_back(makeHeader(*transport, 0, 0)); + msgs.emplace_back(makePayload(*transport)); + + REQUIRE((msgs | count_parts{}) == 1); + REQUIRE((msgs | count_payloads{}) == 1); + REQUIRE((msgs | get_num_payloads{0}) == 1); + + auto idx = msgs | get_pair{0}; + REQUIRE(idx.headerIdx == 0); + REQUIRE(idx.payloadIdx == 1); + + // Advancing past the only pair goes out of range. + auto next = msgs | get_next_pair{idx}; + REQUIRE(next.headerIdx >= msgs.size()); +} + +// --------------------------------------------------------------------------- +// Old-style multipart: N [header, payload] pairs, each with splitPayloadParts=N +// and splitPayloadIndex running 0..N-1 (0-indexed). +// The new-style sentinel is splitPayloadIndex == splitPayloadParts, which is +// never true for old-style (max index is N-1 < N). +// Layout: [h0,p0, h1,p1, h2,p2] +// count_parts returns N because each [h,p] pair is a separate logical part. +// --------------------------------------------------------------------------- +TEST_CASE("OldStyleMultipart") +{ + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + constexpr uint32_t N = 3; + + std::vector msgs; + for (uint32_t i = 0; i < N; ++i) { + msgs.emplace_back(makeHeader(*transport, N, i)); // 0-indexed + msgs.emplace_back(makePayload(*transport)); + } + + REQUIRE((msgs | count_parts{}) == N); // N separate logical parts + REQUIRE((msgs | count_payloads{}) == N); // one payload each + for (uint32_t i = 0; i < N; ++i) { + REQUIRE((msgs | get_num_payloads{i}) == 1); + } + + // get_pair reaches each sub-part directly. + for (uint32_t i = 0; i < N; ++i) { + auto idx = msgs | get_pair{i}; + REQUIRE(idx.headerIdx == 2 * i); + REQUIRE(idx.payloadIdx == 2 * i + 1); + } + + // get_next_pair advances sequentially through all pairs. + DataRefIndices idx{0, 1}; + for (uint32_t i = 1; i < N; ++i) { + idx = msgs | get_next_pair{idx}; + REQUIRE(idx.headerIdx == 2 * i); + REQUIRE(idx.payloadIdx == 2 * i + 1); + } + // One more step goes out of range. + idx = msgs | get_next_pair{idx}; + REQUIRE(idx.headerIdx >= msgs.size()); +} + +// --------------------------------------------------------------------------- +// New-style multipart: one header followed by N contiguous payloads. +// splitPayloadParts == splitPayloadIndex == N (the sentinel for new style). +// Layout: [h, p0, p1, p2] +// --------------------------------------------------------------------------- +TEST_CASE("NewStyleMultiPayload") +{ + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + constexpr uint32_t N = 3; + + std::vector msgs; + msgs.emplace_back(makeHeader(*transport, N, N)); + for (uint32_t i = 0; i < N; ++i) { + msgs.emplace_back(makePayload(*transport)); + } + + REQUIRE((msgs | count_parts{}) == 1); + REQUIRE((msgs | count_payloads{}) == N); + REQUIRE((msgs | get_num_payloads{0}) == N); // all payloads belong to part 0 + + // get_pair returns the same header for every sub-part, advancing payloadIdx. + for (uint32_t i = 0; i < N; ++i) { + auto idx = msgs | get_pair{i}; + REQUIRE(idx.headerIdx == 0); + REQUIRE(idx.payloadIdx == 1 + i); + } + + // get_next_pair advances payloadIdx within the block, then moves to next block. + DataRefIndices idx{0, 1}; + for (uint32_t i = 1; i < N; ++i) { + idx = msgs | get_next_pair{idx}; + REQUIRE(idx.headerIdx == 0); + REQUIRE(idx.payloadIdx == 1 + i); + } + // One more step exits the block. + idx = msgs | get_next_pair{idx}; + REQUIRE(idx.headerIdx >= msgs.size()); +} + +// --------------------------------------------------------------------------- +// Mixed message set: two routes, one single-pair and one new-style block. +// Layout: [h0, p0, h1, p1_0, p1_1] +// --------------------------------------------------------------------------- +TEST_CASE("MixedLayout") +{ + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + + std::vector msgs; + // Route 0: single pair + msgs.emplace_back(makeHeader(*transport, 0, 0)); + msgs.emplace_back(makePayload(*transport)); + // Route 1: new-style 2-payload block + msgs.emplace_back(makeHeader(*transport, 2, 2)); + msgs.emplace_back(makePayload(*transport)); + msgs.emplace_back(makePayload(*transport)); + + REQUIRE((msgs | count_parts{}) == 2); + REQUIRE((msgs | count_payloads{}) == 3); + + // get_pair across routes + auto idx0 = msgs | get_pair{0}; + REQUIRE(idx0.headerIdx == 0); + REQUIRE(idx0.payloadIdx == 1); + + auto idx1 = msgs | get_pair{1}; + REQUIRE(idx1.headerIdx == 2); + REQUIRE(idx1.payloadIdx == 3); + + auto idx2 = msgs | get_pair{2}; + REQUIRE(idx2.headerIdx == 2); + REQUIRE(idx2.payloadIdx == 4); + + // get_next_pair traversal from the first element + DataRefIndices idx{0, 1}; + idx = msgs | get_next_pair{idx}; + REQUIRE(idx.headerIdx == 2); + REQUIRE(idx.payloadIdx == 3); + idx = msgs | get_next_pair{idx}; + REQUIRE(idx.headerIdx == 2); + REQUIRE(idx.payloadIdx == 4); + idx = msgs | get_next_pair{idx}; + REQUIRE(idx.headerIdx >= msgs.size()); +} diff --git a/Framework/Core/test/test_DataRelayer.cxx b/Framework/Core/test/test_DataRelayer.cxx index 8957e361cb8a2..271b7829a9525 100644 --- a/Framework/Core/test/test_DataRelayer.cxx +++ b/Framework/Core/test/test_DataRelayer.cxx @@ -16,6 +16,7 @@ #include "MemoryResources/MemoryResources.h" #include "Framework/CompletionPolicyHelpers.h" #include "Framework/DataRelayer.h" +#include "Framework/DataModelViews.h" #include "Framework/DataProcessingStats.h" #include "Framework/DataProcessingStates.h" #include "Framework/DriverConfig.h" @@ -26,6 +27,10 @@ #include "Framework/WorkflowSpec.h" #include #include +#include +#include "Framework/FairMQDeviceProxy.h" +#include "Framework/ExpirationHandler.h" +#include "Framework/LifetimeHelpers.h" #include #include #include @@ -115,7 +120,7 @@ TEST_CASE("DataRelayer") auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); // one MessageSet with one PartRef with header and payload REQUIRE(result.size() == 1); - REQUIRE(result.at(0).size() == 1); + REQUIRE((result.at(0) | count_parts{}) == 1); } // @@ -165,7 +170,7 @@ TEST_CASE("DataRelayer") auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); // one MessageSet with one PartRef with header and payload REQUIRE(result.size() == 1); - REQUIRE(result.at(0).size() == 1); + REQUIRE((result.at(0) | count_parts{}) == 1); } // This test a more complicated set of inputs, and verifies that data is @@ -245,8 +250,8 @@ TEST_CASE("DataRelayer") auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); // two MessageSets, each with one PartRef REQUIRE(result.size() == 2); - REQUIRE(result.at(0).size() == 1); - REQUIRE(result.at(1).size() == 1); + REQUIRE((result.at(0) | count_parts{}) == 1); + REQUIRE((result.at(1) | count_parts{}) == 1); } // This test a more complicated set of inputs, and verifies that data is @@ -733,8 +738,8 @@ TEST_CASE("DataRelayer") // we have one input route and thus one message set containing pairs for all // payloads REQUIRE(messageSet.size() == 1); - REQUIRE(messageSet[0].size() == nSplitParts); - REQUIRE(messageSet[0].getNumberOfPayloads(0) == 1); + REQUIRE((messageSet[0] | count_parts{}) == nSplitParts); + REQUIRE((messageSet[0] | get_num_payloads{0}) == 1); } SECTION("SplitPayloadSequence") @@ -796,16 +801,171 @@ TEST_CASE("DataRelayer") // we have one input route REQUIRE(messageSet.size() == 1); // one message set containing number of added sequences of messages - REQUIRE(messageSet[0].size() == sequenceSize.size()); + REQUIRE((messageSet[0] | count_parts{}) == sequenceSize.size()); size_t counter = 0; - for (auto seqid = 0; seqid < sequenceSize.size(); ++seqid) { - REQUIRE(messageSet[0].getNumberOfPayloads(seqid) == sequenceSize[seqid]); - for (auto pi = 0; pi < messageSet[0].getNumberOfPayloads(seqid); ++pi) { - REQUIRE(messageSet[0].payload(seqid, pi)); - auto const* data = messageSet[0].payload(seqid, pi)->GetData(); + for (size_t seqid = 0; seqid < sequenceSize.size(); ++seqid) { + REQUIRE((messageSet[0] | get_num_payloads{seqid}) == sequenceSize[seqid]); + for (size_t pi = 0; pi < (messageSet[0] | get_num_payloads{seqid}); ++pi) { + REQUIRE((messageSet[0] | get_payload{seqid, pi})); + auto const* data = (messageSet[0] | get_payload{seqid, pi})->GetData(); REQUIRE(*(reinterpret_cast(data)) == counter); ++counter; } } } + + SECTION("ProcessDanglingInputs") + { + InputSpec spec{"condition", "TST", "COND"}; + std::vector inputs = { + InputRoute{spec, 0, "from_source_to_self", 0}}; + + std::vector infos{1}; + TimesliceIndex index{1, infos}; + ref.registerService(ServiceRegistryHelpers::handleForService(&index)); + + // Bind a fake input channel so FairMQDeviceProxy::getInputChannelIndex works + FairMQDeviceProxy proxy; + std::vector channels{fair::mq::Channel("from_source_to_self")}; + auto findChannel = [&channels](std::string const& name) -> fair::mq::Channel& { + for (auto& ch : channels) { + if (ch.GetName() == name) { + return ch; + } + } + throw std::runtime_error("Channel not found: " + name); + }; + proxy.bind({}, inputs, {}, findChannel, [] { return false; }); + ref.registerService(ServiceRegistryHelpers::handleForService(&proxy)); + + auto policy = CompletionPolicyHelpers::consumeWhenAny(); + DataRelayer relayer(policy, inputs, index, {registry}, -1); + relayer.setPipelineLength(4); + + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); + + DataHeader dh{"COND", "TST", 0}; + dh.splitPayloadParts = 1; + dh.splitPayloadIndex = 0; + DataProcessingHeader dph{0, 1}; + + ExpirationHandler handler; + handler.name = "test-condition"; + handler.routeIndex = RouteIndex{0}; + handler.lifetime = Lifetime::Condition; + + // Creator: claim an empty slot and assign timeslice 0 to it + handler.creator = [](ServiceRegistryRef services, ChannelIndex channelIndex) -> TimesliceSlot { + auto& index = services.get(); + for (size_t si = 0; si < index.size(); si++) { + TimesliceSlot slot{si}; + if (!index.isValid(slot)) { + index.associate(TimesliceId{0}, slot); + (void)index.setOldestPossibleInput({1}, channelIndex); + return slot; + } + } + return TimesliceSlot{TimesliceSlot::INVALID}; + }; + + // Checker: always trigger expiration + handler.checker = LifetimeHelpers::expireAlways(); + + // Handler: materialise a dummy header+payload into the PartRef + handler.handler = [&transport, &channelAlloc, &dh, &dph](ServiceRegistryRef, PartRef& ref, data_matcher::VariableContext&) { + ref.header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); + ref.payload = transport->CreateMessage(4); + }; + + std::vector handlers{handler}; + auto activity = relayer.processDanglingInputs(handlers, {registry}, true); + + REQUIRE(activity.newSlots == 1); + REQUIRE(activity.expiredSlots == 1); + + // The materialised data should now be ready to consume + std::vector ready; + relayer.getReadyToProcess(ready); + REQUIRE(ready.size() == 1); + REQUIRE(ready[0].op == CompletionPolicy::CompletionOp::Consume); + + auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); + REQUIRE(result.size() == 1); + REQUIRE((result.at(0) | count_parts{}) == 1); + } + + SECTION("ProcessDanglingInputsSkipsWhenDataPresent") + { + // processDanglingInputs must not overwrite a slot that already has data. + // This is guarded by the (part.messages | get_header{0}) != nullptr check. + InputSpec spec{"condition", "TST", "COND"}; + std::vector inputs = { + InputRoute{spec, 0, "from_source_to_self", 0}}; + + std::vector infos{1}; + TimesliceIndex index{1, infos}; + ref.registerService(ServiceRegistryHelpers::handleForService(&index)); + + FairMQDeviceProxy proxy; + std::vector channels{fair::mq::Channel("from_source_to_self")}; + auto findChannel = [&channels](std::string const& name) -> fair::mq::Channel& { + for (auto& ch : channels) { + if (ch.GetName() == name) { + return ch; + } + } + throw std::runtime_error("Channel not found: " + name); + }; + proxy.bind({}, inputs, {}, findChannel, [] { return false; }); + ref.registerService(ServiceRegistryHelpers::handleForService(&proxy)); + + auto policy = CompletionPolicyHelpers::consumeWhenAny(); + DataRelayer relayer(policy, inputs, index, {registry}, -1); + relayer.setPipelineLength(4); + + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); + + DataHeader dh{"COND", "TST", 0}; + dh.splitPayloadParts = 1; + dh.splitPayloadIndex = 0; + DataProcessingHeader dph{0, 1}; + + // Build an expiration handler that always tries to expire + ExpirationHandler handler; + handler.name = "test-condition"; + handler.routeIndex = RouteIndex{0}; + handler.lifetime = Lifetime::Condition; + handler.creator = [](ServiceRegistryRef services, ChannelIndex channelIndex) -> TimesliceSlot { + auto& index = services.get(); + for (size_t si = 0; si < index.size(); si++) { + TimesliceSlot slot{si}; + if (!index.isValid(slot)) { + index.associate(TimesliceId{0}, slot); + (void)index.setOldestPossibleInput({1}, channelIndex); + return slot; + } + } + return TimesliceSlot{TimesliceSlot::INVALID}; + }; + handler.checker = LifetimeHelpers::expireAlways(); + int handlerCallCount = 0; + handler.handler = [&transport, &channelAlloc, &dh, &dph, &handlerCallCount](ServiceRegistryRef, PartRef& ref, data_matcher::VariableContext&) { + ref.header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); + ref.payload = transport->CreateMessage(4); + handlerCallCount++; + }; + std::vector handlers{handler}; + + // First call: slot is empty, so the handler fires and materialises data + auto activity1 = relayer.processDanglingInputs(handlers, {registry}, true); + REQUIRE(activity1.expiredSlots == 1); + REQUIRE(handlerCallCount == 1); + + // Second call: slot already has data — the handler must NOT fire again + auto activity2 = relayer.processDanglingInputs(handlers, {registry}, false); + REQUIRE(activity2.expiredSlots == 0); + REQUIRE(handlerCallCount == 1); // handler was not called a second time + } } diff --git a/Framework/Core/test/test_ExternalFairMQDeviceWorkflow.cxx b/Framework/Core/test/test_ExternalFairMQDeviceWorkflow.cxx index 6c991aba7fff5..b69cae5819fbf 100644 --- a/Framework/Core/test/test_ExternalFairMQDeviceWorkflow.cxx +++ b/Framework/Core/test/test_ExternalFairMQDeviceWorkflow.cxx @@ -243,7 +243,7 @@ std::vector defineDataProcessing(ConfigContext const& config) // since we are sending on the bare channel, also the EOS message needs to be created. DataHeader dhEOS; dhEOS.dataOrigin = "DPL"; - dhEOS.dataDescription = "EOS"; + dhEOS.dataDescription = o2::header::gDataDescriptionEos; dhEOS.subSpecification = 0; dhEOS.payloadSize = 0; dhEOS.payloadSerializationMethod = o2::header::gSerializationMethodNone; diff --git a/Framework/Core/test/test_ForwardInputs.cxx b/Framework/Core/test/test_ForwardInputs.cxx index 7081d600080b1..0263158ee0f9b 100644 --- a/Framework/Core/test/test_ForwardInputs.cxx +++ b/Framework/Core/test/test_ForwardInputs.cxx @@ -16,7 +16,7 @@ #include "Framework/SourceInfoHeader.h" #include "Framework/DomainInfoHeader.h" #include "Framework/Signpost.h" -#include "Framework/MessageSet.h" +#include "Framework/DataModelViews.h" #include "Framework/FairMQDeviceProxy.h" #include "Headers/Stack.h" #include "MemoryResources/MemoryResources.h" @@ -43,7 +43,7 @@ TEST_CASE("ForwardInputsEmpty") bool copyByDefault = true; FairMQDeviceProxy proxy; - std::vector currentSetOfInputs; + std::vector> currentSetOfInputs; auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); REQUIRE(result.empty()); @@ -84,15 +84,16 @@ TEST_CASE("ForwardInputsSingleMessageSingleRoute") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; - MessageSet messageSet; + std::vector> currentSetOfInputs; + std::vector messageSet; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload(transport->CreateMessage()); auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); - messageSet.add(PartRef{std::move(header), std::move(payload)}); - REQUIRE(messageSet.size() == 1); + messageSet.emplace_back(std::move(header)); + messageSet.emplace_back(std::move(payload)); + REQUIRE((messageSet | count_parts{}) == 1); currentSetOfInputs.emplace_back(std::move(messageSet)); auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); @@ -134,16 +135,17 @@ TEST_CASE("ForwardInputsSingleMessageSingleRouteNoConsume") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; - MessageSet messageSet; + std::vector> currentSetOfInputs; + std::vector messageSet; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload(nullptr); REQUIRE(payload.get() == nullptr); auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); - messageSet.add(PartRef{std::move(header), std::move(payload)}); - REQUIRE(messageSet.size() == 1); + messageSet.emplace_back(std::move(header)); + messageSet.emplace_back(std::move(payload)); + REQUIRE((messageSet | count_parts{}) == 1); currentSetOfInputs.emplace_back(std::move(messageSet)); auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, true); @@ -189,16 +191,17 @@ TEST_CASE("ForwardInputsSingleMessageSingleRouteAtEOS") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; - MessageSet messageSet; + std::vector> currentSetOfInputs; + std::vector messageSet; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload(transport->CreateMessage()); auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph, sih}); REQUIRE(o2::header::get(header->GetData())); - messageSet.add(PartRef{std::move(header), std::move(payload)}); - REQUIRE(messageSet.size() == 1); + messageSet.emplace_back(std::move(header)); + messageSet.emplace_back(std::move(payload)); + REQUIRE((messageSet | count_parts{}) == 1); currentSetOfInputs.emplace_back(std::move(messageSet)); auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); @@ -247,16 +250,17 @@ TEST_CASE("ForwardInputsSingleMessageSingleRouteWithOldestPossible") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; - MessageSet messageSet; + std::vector> currentSetOfInputs; + std::vector messageSet; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload(transport->CreateMessage()); auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph, dih}); REQUIRE(o2::header::get(header->GetData())); - messageSet.add(PartRef{std::move(header), std::move(payload)}); - REQUIRE(messageSet.size() == 1); + messageSet.emplace_back(std::move(header)); + messageSet.emplace_back(std::move(payload)); + REQUIRE((messageSet | count_parts{}) == 1); currentSetOfInputs.emplace_back(std::move(messageSet)); auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); @@ -313,15 +317,16 @@ TEST_CASE("ForwardInputsSingleMessageMultipleRoutes") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; - MessageSet messageSet; + std::vector> currentSetOfInputs; + std::vector messageSet; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload(transport->CreateMessage()); auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); - messageSet.add(PartRef{std::move(header), std::move(payload)}); - REQUIRE(messageSet.size() == 1); + messageSet.emplace_back(std::move(header)); + messageSet.emplace_back(std::move(payload)); + REQUIRE((messageSet | count_parts{}) == 1); currentSetOfInputs.emplace_back(std::move(messageSet)); auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); @@ -376,15 +381,16 @@ TEST_CASE("ForwardInputsSingleMessageMultipleRoutesExternals") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; - MessageSet messageSet; + std::vector> currentSetOfInputs; + std::vector messageSet; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload(transport->CreateMessage()); auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); - messageSet.add(PartRef{std::move(header), std::move(payload)}); - REQUIRE(messageSet.size() == 1); + messageSet.emplace_back(std::move(header)); + messageSet.emplace_back(std::move(payload)); + REQUIRE((messageSet | count_parts{}) == 1); currentSetOfInputs.emplace_back(std::move(messageSet)); auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); @@ -446,21 +452,23 @@ TEST_CASE("ForwardInputsMultiMessageMultipleRoutes") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; + std::vector> currentSetOfInputs; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload1(transport->CreateMessage()); fair::mq::MessagePtr payload2(transport->CreateMessage()); auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); auto header1 = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh1, dph}); - MessageSet messageSet1; - messageSet1.add(PartRef{std::move(header1), std::move(payload1)}); - REQUIRE(messageSet1.size() == 1); + std::vector messageSet1; + messageSet1.emplace_back(std::move(header1)); + messageSet1.emplace_back(std::move(payload1)); + REQUIRE((messageSet1 | count_parts{}) == 1); auto header2 = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh2, dph}); - MessageSet messageSet2; - messageSet2.add(PartRef{std::move(header2), std::move(payload2)}); - REQUIRE(messageSet2.size() == 1); + std::vector messageSet2; + messageSet2.emplace_back(std::move(header2)); + messageSet2.emplace_back(std::move(payload2)); + REQUIRE((messageSet2 | count_parts{}) == 1); currentSetOfInputs.emplace_back(std::move(messageSet1)); currentSetOfInputs.emplace_back(std::move(messageSet2)); REQUIRE(currentSetOfInputs.size() == 2); @@ -517,15 +525,16 @@ TEST_CASE("ForwardInputsSingleMessageMultipleRoutesOnlyOneMatches") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; - MessageSet messageSet; + std::vector> currentSetOfInputs; + std::vector messageSet; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload(transport->CreateMessage()); auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); - messageSet.add(PartRef{std::move(header), std::move(payload)}); - REQUIRE(messageSet.size() == 1); + messageSet.emplace_back(std::move(header)); + messageSet.emplace_back(std::move(payload)); + REQUIRE((messageSet | count_parts{}) == 1); currentSetOfInputs.emplace_back(std::move(messageSet)); auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); @@ -587,8 +596,8 @@ TEST_CASE("ForwardInputsSplitPayload") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; - MessageSet messageSet; + std::vector> currentSetOfInputs; + std::vector messageSet; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload1(transport->CreateMessage()); @@ -602,12 +611,14 @@ TEST_CASE("ForwardInputsSplitPayload") auto fillMessages = [&messages](size_t t) -> fair::mq::MessagePtr { return std::move(messages[t]); }; - messageSet.add(fillMessages, 3); + for (size_t i = 0; i < 3; ++i) { + messageSet.emplace_back(fillMessages(i)); + } auto header2 = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh2, dph}); - PartRef part{std::move(header2), transport->CreateMessage()}; - messageSet.add(std::move(part)); + messageSet.emplace_back(std::move(header2)); + messageSet.emplace_back(transport->CreateMessage()); - REQUIRE(messageSet.size() == 2); + REQUIRE((messageSet | count_parts{}) == 2); currentSetOfInputs.emplace_back(std::move(messageSet)); auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); @@ -719,15 +730,16 @@ TEST_CASE("ForwardInputEOSSingleRoute") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; - MessageSet messageSet; + std::vector> currentSetOfInputs; + std::vector messageSet; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload(transport->CreateMessage()); auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, sih}); - messageSet.add(PartRef{std::move(header), std::move(payload)}); - REQUIRE(messageSet.size() == 1); + messageSet.emplace_back(std::move(header)); + messageSet.emplace_back(std::move(payload)); + REQUIRE((messageSet | count_parts{}) == 1); currentSetOfInputs.emplace_back(std::move(messageSet)); auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); @@ -764,15 +776,16 @@ TEST_CASE("ForwardInputOldestPossibleSingleRoute") proxy.bind({}, {}, routes, findChannelByName, nullptr); - std::vector currentSetOfInputs; - MessageSet messageSet; + std::vector> currentSetOfInputs; + std::vector messageSet; auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); fair::mq::MessagePtr payload(transport->CreateMessage()); auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dih}); - messageSet.add(PartRef{std::move(header), std::move(payload)}); - REQUIRE(messageSet.size() == 1); + messageSet.emplace_back(std::move(header)); + messageSet.emplace_back(std::move(payload)); + REQUIRE((messageSet | count_parts{}) == 1); currentSetOfInputs.emplace_back(std::move(messageSet)); auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); diff --git a/Framework/Core/test/test_IndexBuilder.cxx b/Framework/Core/test/test_IndexBuilder.cxx index e357b1164af80..2a273db4333fc 100644 --- a/Framework/Core/test/test_IndexBuilder.cxx +++ b/Framework/Core/test/test_IndexBuilder.cxx @@ -108,7 +108,7 @@ TEST_CASE("TestIndexBuilder") auto t5 = IndexBuilder::materialize(builders1, {t1, t2, t3, t4}, map, schema1, true); // auto t5 = IndexBuilder::materialize({t1, t2, t3, t4}, map, schema1, true); REQUIRE(t5->num_rows() == 4); - IDXs idxt{t5}; + IDXsFrom> idxt{t5}; idxt.bindExternalIndices(&st1, &st2, &st3, &st4); for (auto& row : idxt) { REQUIRE(row.distance().pointId() == row.pointId()); @@ -121,7 +121,7 @@ TEST_CASE("TestIndexBuilder") std::vector builders2; auto t6 = IndexBuilder::materialize(builders2, {t2, t1, t3, t4}, map, schema2, false); REQUIRE(t6->num_rows() == st2.size()); - IDX2s idxs{t6}; + IDX2sFrom> idxs{t6}; std::array fs{0, 1, 2, -1, -1, 4, -1}; std::array cs{0, 1, 2, -1, 5, 6, -1}; idxs.bindExternalIndices(&st1, &st2, &st3, &st4); @@ -222,7 +222,7 @@ TEST_CASE("AdvancedIndexTables") std::vector builders3; auto t3 = IndexBuilder::materialize(builders3, {t1, t2, tc}, map, schema3, false); REQUIRE(t3->num_rows() == st1.size()); - IDX3s idxs{t3}; + IDX3sFrom> idxs{t3}; idxs.bindExternalIndices(&st1, &st2, &st3); count = 0; for (auto const& row : idxs) { diff --git a/Framework/Core/test/test_InputRecord.cxx b/Framework/Core/test/test_InputRecord.cxx index 4eb1265dcff53..355e52539ea5a 100644 --- a/Framework/Core/test/test_InputRecord.cxx +++ b/Framework/Core/test/test_InputRecord.cxx @@ -47,7 +47,10 @@ TEST_CASE("TestInputRecord") // First of all we test if an empty registry behaves as expected, raising a // bunch of exceptions. InputSpan span{ - [](size_t) { return DataRef{nullptr, nullptr, nullptr}; }, + [](size_t) -> size_t { return 0; }, + nullptr, + [](size_t, DataRefIndices) { return DataRef{nullptr, nullptr, nullptr}; }, + [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, 0}; ServiceRegistry registry; InputRecord emptyRecord(schema, span, registry); @@ -91,7 +94,12 @@ TEST_CASE("TestInputRecord") createMessage(dh1, 1); createMessage(dh2, 2); createEmpty(); - InputSpan span2{[&inputs](size_t i) { return DataRef{nullptr, static_cast(inputs[2 * i]), static_cast(inputs[2 * i + 1])}; }, inputs.size() / 2}; + InputSpan span2{ + [](size_t) -> size_t { return 1; }, + nullptr, + [&inputs](size_t i, DataRefIndices idx) { return DataRef{nullptr, static_cast(inputs[2 * i + idx.headerIdx]), static_cast(inputs[2 * i + idx.payloadIdx])}; }, + [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + inputs.size() / 2}; InputRecord record{schema, span2, registry}; // Checking we can get the whole ref by name diff --git a/Framework/Core/test/test_InputRecordWalker.cxx b/Framework/Core/test/test_InputRecordWalker.cxx index 9af3c0dd2dbe2..1fcfea1ba1587 100644 --- a/Framework/Core/test/test_InputRecordWalker.cxx +++ b/Framework/Core/test/test_InputRecordWalker.cxx @@ -35,16 +35,12 @@ struct DataSet { using Messages = std::vector; using CheckType = std::vector; DataSet(std::vector&& s, Messages&& m, CheckType&& v, ServiceRegistryRef registry) - : schema{std::move(s)}, messages{std::move(m)}, span{[this](size_t i, size_t part) { - REQUIRE(i < this->messages.size()); - REQUIRE(part < this->messages[i].second.size() / 2); - auto header = static_cast(this->messages[i].second.at(2 * part)->data()); - auto payload = static_cast(this->messages[i].second.at(2 * part + 1)->data()); - return DataRef{nullptr, header, payload}; - }, - [this](size_t i) { return i < this->messages.size() ? messages[i].second.size() / 2 : 0; }, nullptr, this->messages.size()}, - record{schema, span, registry}, - values{std::move(v)} + : schema{std::move(s)}, messages{std::move(m)}, span{[this](size_t i) { return i < this->messages.size() ? messages[i].second.size() / 2 : 0; }, nullptr, [this](size_t i, DataRefIndices idx) { + auto header = static_cast(this->messages[i].second.at(idx.headerIdx)->data()); + auto payload = static_cast(this->messages[i].second.at(idx.payloadIdx)->data()); + return DataRef{nullptr, header, payload}; }, [this](size_t i, DataRefIndices current) -> DataRefIndices { + size_t next = current.headerIdx + 2; + return next < this->messages[i].second.size() ? DataRefIndices{next, next + 1} : DataRefIndices{size_t(-1), size_t(-1)}; }, this->messages.size()}, record{schema, span, registry}, values{std::move(v)} { REQUIRE(messages.size() == schema.size()); } diff --git a/Framework/Core/test/test_InputSpan.cxx b/Framework/Core/test/test_InputSpan.cxx index c5682aea80b6c..dc31085e741fd 100644 --- a/Framework/Core/test/test_InputSpan.cxx +++ b/Framework/Core/test/test_InputSpan.cxx @@ -30,14 +30,18 @@ TEST_CASE("TestInputSpan") routeNo++; } - auto getter = [&inputs](size_t i, size_t part) { - return DataRef{nullptr, inputs[i].at(part * 2).data(), inputs[i].at(part * 2 + 1).data()}; - }; auto nPartsGetter = [&inputs](size_t i) { return inputs[i].size() / 2; }; + auto indicesGetter = [&inputs](size_t i, DataRefIndices indices) { + return DataRef{nullptr, inputs[i].at(indices.headerIdx).data(), inputs[i].at(indices.payloadIdx).data()}; + }; + auto nextIndicesGetter = [&inputs](size_t i, DataRefIndices current) -> DataRefIndices { + size_t next = current.headerIdx + 2; + return next < inputs[i].size() ? DataRefIndices{next, next + 1} : DataRefIndices{size_t(-1), size_t(-1)}; + }; - InputSpan span{getter, nPartsGetter, nullptr, inputs.size()}; + InputSpan span{nPartsGetter, nullptr, indicesGetter, nextIndicesGetter, inputs.size()}; REQUIRE(span.size() == inputs.size()); routeNo = 0; for (; routeNo < span.size(); ++routeNo) { diff --git a/Framework/Core/test/test_MessageSet.cxx b/Framework/Core/test/test_MessageSet.cxx index d56e32fea1adb..caa9a60323306 100644 --- a/Framework/Core/test/test_MessageSet.cxx +++ b/Framework/Core/test/test_MessageSet.cxx @@ -10,126 +10,370 @@ // or submit itself to any jurisdiction. #include -#include "Framework/MessageSet.h" +#include +#include "Framework/DataModelViews.h" +#include "Framework/DataProcessingHeader.h" +#include "Framework/PartRef.h" +#include "Headers/Stack.h" +#include "Headers/DataHeader.h" +#include "MemoryResources/MemoryResources.h" #include using namespace o2::framework; -TEST_CASE("MessageSet") { - o2::framework::MessageSet msgSet; - std::vector ptrs; - std::unique_ptr msg(nullptr); +TEST_CASE("MessageSet") +{ + std::vector messages; + o2::header::DataHeader dh{}; + dh.splitPayloadParts = 0; + dh.splitPayloadIndex = 0; + o2::framework::DataProcessingHeader dph{0, 1}; + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + fair::mq::MessagePtr payload(transport->CreateMessage()); + auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); + fair::mq::MessagePtr header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); std::unique_ptr msg2(nullptr); - ptrs.emplace_back(std::move(msg)); + std::vector ptrs; + ptrs.emplace_back(std::move(header)); ptrs.emplace_back(std::move(msg2)); - msgSet.add([&ptrs](size_t i) -> fair::mq::MessagePtr& { return ptrs[i]; }, 2); + for (size_t i = 0; i < 2; ++i) { + messages.emplace_back(std::move(ptrs[i])); + } - REQUIRE(msgSet.messages.size() == 2); - REQUIRE(msgSet.messageMap.size() == 1); - REQUIRE(msgSet.pairMap.size() == 1); - REQUIRE(msgSet.messageMap[0].position == 0); - REQUIRE(msgSet.messageMap[0].size == 1); - - REQUIRE(msgSet.pairMap[0].partIndex == 0); - REQUIRE(msgSet.pairMap[0].payloadIndex == 0); + REQUIRE(messages.size() == 2); + REQUIRE((messages | count_payloads{}) == 1); + REQUIRE((messages | get_dataref_indices{0, 0}).headerIdx == 0); + REQUIRE((messages | get_dataref_indices{0, 0}).payloadIdx == 1); + REQUIRE((messages | get_pair{0}).headerIdx == 0); + REQUIRE((messages | get_pair{0}).payloadIdx == 1); + CHECK_THROWS((messages | get_pair{1})); + REQUIRE((messages | get_num_payloads{0}) == 1); + REQUIRE((messages | count_parts{}) == 1); } -TEST_CASE("MessageSetWithFunction") { +TEST_CASE("MessageSetWithFunction") +{ std::vector ptrs; - std::unique_ptr msg(nullptr); + o2::header::DataHeader dh{}; + dh.splitPayloadParts = 0; + dh.splitPayloadIndex = 0; + o2::framework::DataProcessingHeader dph{0, 1}; + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + fair::mq::MessagePtr payload(transport->CreateMessage()); + auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); + fair::mq::MessagePtr header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); std::unique_ptr msg2(nullptr); - ptrs.emplace_back(std::move(msg)); + ptrs.emplace_back(std::move(header)); ptrs.emplace_back(std::move(msg2)); - o2::framework::MessageSet msgSet([&ptrs](size_t i) -> fair::mq::MessagePtr& { return ptrs[i]; }, 2); - - REQUIRE(msgSet.messages.size() == 2); - REQUIRE(msgSet.messageMap.size() == 1); - REQUIRE(msgSet.pairMap.size() == 1); - REQUIRE(msgSet.messageMap[0].position == 0); - REQUIRE(msgSet.messageMap[0].size == 1); + std::vector messages; + for (size_t i = 0; i < 2; ++i) { + messages.emplace_back(std::move(ptrs[i])); + } - REQUIRE(msgSet.pairMap[0].partIndex == 0); - REQUIRE(msgSet.pairMap[0].payloadIndex == 0); + REQUIRE(messages.size() == 2); + REQUIRE((messages | count_payloads{}) == 1); + REQUIRE((messages | get_dataref_indices{0, 0}).headerIdx == 0); + REQUIRE((messages | get_dataref_indices{0, 0}).payloadIdx == 1); + REQUIRE((messages | get_pair{0}).headerIdx == 0); + REQUIRE((messages | get_pair{0}).payloadIdx == 1); + CHECK_THROWS((messages | get_pair{1})); + REQUIRE((messages | get_num_payloads{0}) == 1); + REQUIRE((messages | count_parts{}) == 1); } -TEST_CASE("MessageSetWithMultipart") { +TEST_CASE("MessageSetWithMultipart") +{ std::vector ptrs; - std::unique_ptr msg(nullptr); + o2::header::DataHeader dh{}; + dh.splitPayloadParts = 2; + dh.splitPayloadIndex = 2; + o2::framework::DataProcessingHeader dph{0, 1}; + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + fair::mq::MessagePtr payload(transport->CreateMessage()); + auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); + fair::mq::MessagePtr header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); std::unique_ptr msg2(nullptr); std::unique_ptr msg3(nullptr); - ptrs.emplace_back(std::move(msg)); + ptrs.emplace_back(std::move(header)); ptrs.emplace_back(std::move(msg2)); ptrs.emplace_back(std::move(msg3)); - o2::framework::MessageSet msgSet([&ptrs](size_t i) -> fair::mq::MessagePtr& { return ptrs[i]; }, 3); - - REQUIRE(msgSet.messages.size() == 3); - REQUIRE(msgSet.messageMap.size() == 1); - REQUIRE(msgSet.pairMap.size() == 2); - REQUIRE(msgSet.messageMap[0].position == 0); - REQUIRE(msgSet.messageMap[0].size == 2); - - REQUIRE(msgSet.pairMap[0].partIndex == 0); - REQUIRE(msgSet.pairMap[0].payloadIndex == 0); - REQUIRE(msgSet.pairMap[1].partIndex == 0); - REQUIRE(msgSet.pairMap[1].payloadIndex == 1); + std::vector messages; + for (size_t i = 0; i < 3; ++i) { + messages.emplace_back(std::move(ptrs[i])); + } + + REQUIRE(messages.size() == 3); + REQUIRE((messages | count_payloads{}) == 2); + REQUIRE((messages | get_dataref_indices{0, 0}).headerIdx == 0); + REQUIRE((messages | get_dataref_indices{0, 0}).payloadIdx == 1); + REQUIRE((messages | get_dataref_indices{0, 1}).headerIdx == 0); + REQUIRE((messages | get_dataref_indices{0, 1}).payloadIdx == 2); + REQUIRE((messages | get_pair{0}).headerIdx == 0); + REQUIRE((messages | get_pair{0}).payloadIdx == 1); + REQUIRE((messages | get_pair{1}).headerIdx == 0); + REQUIRE((messages | get_pair{1}).payloadIdx == 2); + CHECK_THROWS((messages | get_pair{2})); + REQUIRE((messages | get_num_payloads{0}) == 2); + REQUIRE((messages | count_parts{}) == 1); } -TEST_CASE("MessageSetAddPartRef") { - std::vector ptrs; +TEST_CASE("MessageSetAddPartRef") +{ std::unique_ptr msg(nullptr); std::unique_ptr msg2(nullptr); - ptrs.emplace_back(std::move(msg)); - ptrs.emplace_back(std::move(msg2)); - PartRef ref {std::move(msg), std::move(msg2)}; - o2::framework::MessageSet msgSet; - msgSet.add(std::move(ref)); - - REQUIRE(msgSet.messages.size() == 2); - REQUIRE(msgSet.messageMap.size() == 1); - REQUIRE(msgSet.pairMap.size() == 1); - REQUIRE(msgSet.messageMap[0].position == 0); - REQUIRE(msgSet.messageMap[0].size == 1); - - REQUIRE(msgSet.pairMap[0].partIndex == 0); - REQUIRE(msgSet.pairMap[0].payloadIndex == 0); + PartRef ref{std::move(msg), std::move(msg2)}; + std::vector messages; + messages.emplace_back(std::move(ref.header)); + messages.emplace_back(std::move(ref.payload)); + + REQUIRE(messages.size() == 2); } TEST_CASE("MessageSetAddMultiple") { - std::vector ptrs; - std::unique_ptr msg(nullptr); - std::unique_ptr msg2(nullptr); - ptrs.emplace_back(std::move(msg)); - ptrs.emplace_back(std::move(msg2)); - PartRef ref{std::move(msg), std::move(msg2)}; - o2::framework::MessageSet msgSet; - msgSet.add(std::move(ref)); - PartRef ref2{std::move(msg), std::move(msg2)}; - msgSet.add(std::move(ref2)); - std::vector msgs; - msgs.push_back(std::unique_ptr(nullptr)); - msgs.push_back(std::unique_ptr(nullptr)); - msgs.push_back(std::unique_ptr(nullptr)); - msgSet.add([&msgs](size_t i) { - return std::move(msgs[i]); - }, 3); - - REQUIRE(msgSet.messages.size() == 7); - REQUIRE(msgSet.messageMap.size() == 3); - REQUIRE(msgSet.pairMap.size() == 4); - REQUIRE(msgSet.messageMap[0].position == 0); - REQUIRE(msgSet.messageMap[0].size == 1); - REQUIRE(msgSet.messageMap[1].position == 2); - REQUIRE(msgSet.messageMap[1].size == 1); - REQUIRE(msgSet.messageMap[2].position == 4); - REQUIRE(msgSet.messageMap[2].size == 2); - - REQUIRE(msgSet.pairMap[0].partIndex == 0); - REQUIRE(msgSet.pairMap[0].payloadIndex == 0); - REQUIRE(msgSet.pairMap[1].partIndex == 1); - REQUIRE(msgSet.pairMap[1].payloadIndex == 0); - REQUIRE(msgSet.pairMap[2].partIndex == 2); - REQUIRE(msgSet.pairMap[2].payloadIndex == 0); - REQUIRE(msgSet.pairMap[3].partIndex == 2); - REQUIRE(msgSet.pairMap[3].payloadIndex == 1); + o2::header::DataHeader dh1{}; + dh1.splitPayloadParts = 0; + dh1.splitPayloadIndex = 0; + o2::header::DataHeader dh2{}; + dh2.splitPayloadParts = 1; + dh2.splitPayloadIndex = 0; + o2::header::DataHeader dh3{}; + dh3.splitPayloadParts = 2; + dh3.splitPayloadIndex = 2; + o2::framework::DataProcessingHeader dph{0, 1}; + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); + fair::mq::MessagePtr header1 = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh1, dph}); + fair::mq::MessagePtr header2 = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh2, dph}); + fair::mq::MessagePtr header3 = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh3, dph}); + + std::vector messages; + // part 0: dh1 (splitPayloadParts=0) — standard pair + messages.emplace_back(std::move(header1)); + messages.emplace_back(std::unique_ptr(nullptr)); + // part 1: dh2 (splitPayloadParts=1) — traditional split, one pair + messages.emplace_back(std::move(header2)); + messages.emplace_back(std::unique_ptr(nullptr)); + // part 2: dh3 (splitPayloadParts=2, splitPayloadIndex=2) — multi-payload, two payloads + messages.emplace_back(std::move(header3)); + messages.emplace_back(std::unique_ptr(nullptr)); + messages.emplace_back(std::unique_ptr(nullptr)); + + REQUIRE(messages.size() == 7); + + REQUIRE((messages | count_payloads{}) == 4); + REQUIRE((messages | get_dataref_indices{0, 0}).headerIdx == 0); + REQUIRE((messages | get_dataref_indices{0, 0}).payloadIdx == 1); + REQUIRE((messages | get_dataref_indices{1, 0}).headerIdx == 2); + REQUIRE((messages | get_dataref_indices{1, 0}).payloadIdx == 3); + REQUIRE((messages | get_dataref_indices{2, 0}).headerIdx == 4); + REQUIRE((messages | get_dataref_indices{2, 0}).payloadIdx == 5); + REQUIRE((messages | get_dataref_indices{2, 1}).headerIdx == 4); + REQUIRE((messages | get_dataref_indices{2, 1}).payloadIdx == 6); + REQUIRE((messages | get_pair{0}).headerIdx == 0); + REQUIRE((messages | get_pair{0}).payloadIdx == 1); + REQUIRE((messages | get_pair{1}).headerIdx == 2); + REQUIRE((messages | get_pair{1}).payloadIdx == 3); + REQUIRE((messages | get_pair{2}).headerIdx == 4); + REQUIRE((messages | get_pair{2}).payloadIdx == 5); + REQUIRE((messages | get_pair{3}).headerIdx == 4); + REQUIRE((messages | get_pair{3}).payloadIdx == 6); + REQUIRE((messages | get_num_payloads{0}) == 1); + REQUIRE((messages | get_num_payloads{1}) == 1); + REQUIRE((messages | get_num_payloads{2}) == 2); + REQUIRE((messages | count_parts{}) == 3); + REQUIRE((messages | count_payloads{}) == 4); +} + +TEST_CASE("GetHeaderPayloadOperators") +{ + // Validates that get_header{part} / get_payload{part, 0} pipe operators + // correctly return the right messages, including access to parts at index > 0. + o2::framework::DataProcessingHeader dph{0, 1}; + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); + + std::vector messages; + + // Add two separate header-payload pairs + for (size_t part = 0; part < 2; ++part) { + o2::header::DataHeader dh{}; + dh.dataDescription = "CLUSTERS"; + dh.dataOrigin = "TPC"; + dh.subSpecification = part; + dh.splitPayloadParts = 1; + dh.splitPayloadIndex = 0; + messages.emplace_back(o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph})); + messages.emplace_back(transport->CreateMessage(100 + part * 100)); + } + + REQUIRE(messages.size() == 4); + + // Validate part 0 + auto& hdr0 = messages | get_header{0}; + REQUIRE(hdr0.get() != nullptr); + auto* dh0 = o2::header::get(hdr0->GetData()); + REQUIRE(dh0 != nullptr); + REQUIRE(dh0->subSpecification == 0); + auto& pl0 = messages | get_payload{0, 0}; + REQUIRE(pl0.get() != nullptr); + REQUIRE(pl0->GetSize() == 100); + + // Validate part 1 + auto& hdr1 = messages | get_header{1}; + REQUIRE(hdr1.get() != nullptr); + auto* dh1 = o2::header::get(hdr1->GetData()); + REQUIRE(dh1 != nullptr); + REQUIRE(dh1->subSpecification == 1); + auto& pl1 = messages | get_payload{1, 0}; + REQUIRE(pl1.get() != nullptr); + REQUIRE(pl1->GetSize() == 200); + + REQUIRE((messages | count_parts{}) == 2); + REQUIRE((messages | count_payloads{}) == 2); + REQUIRE((messages | get_pair{0}).headerIdx == 0); + REQUIRE((messages | get_pair{0}).payloadIdx == 1); + REQUIRE((messages | get_pair{1}).headerIdx == 2); + REQUIRE((messages | get_pair{1}).payloadIdx == 3); +} + +TEST_CASE("GetHeaderPayloadMultiPayload") +{ + // Validates get_header{part} / get_payload{part, subpart} where both + // part and subpart can be non-zero. + // Layout: + // part 0: standard (1 header + 1 payload) → splitPayloadParts=1 + // part 1: multi-payload (1 header + 3 payloads) → splitPayloadParts=3, splitPayloadIndex=3 + o2::framework::DataProcessingHeader dph{0, 1}; + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); + + std::vector messages; + + // Part 0: standard header-payload pair + { + o2::header::DataHeader dh{}; + dh.dataDescription = "CLUSTERS"; + dh.dataOrigin = "TPC"; + dh.subSpecification = 0; + dh.splitPayloadParts = 1; + dh.splitPayloadIndex = 0; + messages.emplace_back(o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph})); + messages.emplace_back(transport->CreateMessage(100)); + } + + // Part 1: one header with 3 payloads (splitPayloadIndex == splitPayloadParts) + { + o2::header::DataHeader dh{}; + dh.dataDescription = "TRACKS"; + dh.dataOrigin = "TPC"; + dh.subSpecification = 1; + dh.splitPayloadParts = 3; + dh.splitPayloadIndex = 3; + messages.emplace_back(o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph})); + messages.emplace_back(transport->CreateMessage(200)); + messages.emplace_back(transport->CreateMessage(300)); + messages.emplace_back(transport->CreateMessage(400)); + } + + // messages: [hdr0, pl0, hdr1, pl1_0, pl1_1, pl1_2] + REQUIRE(messages.size() == 6); + + // Part 0 + auto& hdr0 = messages | get_header{0}; + REQUIRE(hdr0.get() != nullptr); + auto* dh0 = o2::header::get(hdr0->GetData()); + REQUIRE(dh0->subSpecification == 0); + auto& pl0 = messages | get_payload{0, 0}; + REQUIRE(pl0.get() != nullptr); + REQUIRE(pl0->GetSize() == 100); + + // Part 1: multi-payload header + auto& hdr1 = messages | get_header{1}; + REQUIRE(hdr1.get() != nullptr); + auto* dh1 = o2::header::get(hdr1->GetData()); + REQUIRE(dh1->subSpecification == 1); + + auto& pl1_0 = messages | get_payload{1, 0}; + REQUIRE(pl1_0.get() != nullptr); + REQUIRE(pl1_0->GetSize() == 200); + + auto& pl1_1 = messages | get_payload{1, 1}; + REQUIRE(pl1_1.get() != nullptr); + REQUIRE(pl1_1->GetSize() == 300); + + auto& pl1_2 = messages | get_payload{1, 2}; + REQUIRE(pl1_2.get() != nullptr); + REQUIRE(pl1_2->GetSize() == 400); + + REQUIRE((messages | get_num_payloads{0}) == 1); + REQUIRE((messages | get_num_payloads{1}) == 3); + REQUIRE((messages | count_parts{}) == 2); + REQUIRE((messages | count_payloads{}) == 4); + REQUIRE((messages | get_pair{0}).headerIdx == 0); + REQUIRE((messages | get_pair{0}).payloadIdx == 1); + REQUIRE((messages | get_pair{1}).headerIdx == 2); + REQUIRE((messages | get_pair{1}).payloadIdx == 3); + REQUIRE((messages | get_pair{2}).headerIdx == 2); + REQUIRE((messages | get_pair{2}).payloadIdx == 4); + REQUIRE((messages | get_pair{3}).headerIdx == 2); + REQUIRE((messages | get_pair{3}).payloadIdx == 5); +} + +TEST_CASE("TraditionalSplitParts") +{ + // Validates operators with traditional split parts layout: + // 3 (header, payload) pairs where splitPayloadParts=3, splitPayloadIndex=0,1,2 + // Memory layout: [hdr0, pl0, hdr1, pl1, hdr2, pl2] + o2::framework::DataProcessingHeader dph{0, 1}; + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + auto channelAlloc = o2::pmr::getTransportAllocator(transport.get()); + + std::vector messages; + + for (size_t i = 0; i < 3; ++i) { + o2::header::DataHeader dh{}; + dh.dataDescription = "CLUSTERS"; + dh.dataOrigin = "TPC"; + dh.subSpecification = 0; + dh.splitPayloadParts = 3; + dh.splitPayloadIndex = i; + messages.emplace_back(o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph})); + messages.emplace_back(transport->CreateMessage(100 * (i + 1))); + } + + REQUIRE(messages.size() == 6); + + REQUIRE((messages | count_payloads{}) == 3); + REQUIRE((messages | count_parts{}) == 3); + + for (size_t i = 0; i < 3; ++i) { + auto& hdr = messages | get_header{i}; + REQUIRE(hdr.get() != nullptr); + auto* dh = o2::header::get(hdr->GetData()); + REQUIRE(dh != nullptr); + REQUIRE(dh->splitPayloadIndex == i); + + auto& pl = messages | get_payload{i, 0}; + REQUIRE(pl.get() != nullptr); + REQUIRE(pl->GetSize() == 100 * (i + 1)); + } + + for (size_t i = 0; i < 3; ++i) { + auto indices = messages | get_dataref_indices{i, 0}; + REQUIRE(indices.headerIdx == 2 * i); + REQUIRE(indices.payloadIdx == 2 * i + 1); + } + + for (size_t i = 0; i < 3; ++i) { + auto indices = messages | get_pair{i}; + REQUIRE(indices.headerIdx == 2 * i); + REQUIRE(indices.payloadIdx == 2 * i + 1); + } + + for (size_t i = 0; i < 3; ++i) { + REQUIRE((messages | get_num_payloads{i}) == 1); + } + REQUIRE((messages | count_parts{}) == 3); + REQUIRE((messages | count_payloads{}) == 3); } diff --git a/Framework/Core/test/test_Variants.cxx b/Framework/Core/test/test_Variants.cxx index a364b228871da..da1f39f241e96 100644 --- a/Framework/Core/test/test_Variants.cxx +++ b/Framework/Core/test/test_Variants.cxx @@ -327,14 +327,16 @@ TEST_CASE("VariantJSONConversionsTest") std::vector vstrings{"myoption_one", "myoption_two"}; Variant vvstr(vstrings); + REQUIRE(vvstr.size() == 2); std::stringstream osal; - VariantJSONHelpers::write(osal, vvstr); + osal << vvstr; std::stringstream isal; isal.str(osal.str()); auto vvstra = VariantJSONHelpers::read(isal); - for (auto i = 0U; i < vvstra.size(); ++i) { + REQUIRE(vvstr.size() == vvstra.size()); + for (auto i = 0U; i < vstrings.size(); ++i) { REQUIRE(vstrings[i] == vvstra.get()[i]); } } diff --git a/Framework/Core/test/test_WorkflowSerialization.cxx b/Framework/Core/test/test_WorkflowSerialization.cxx index 298956970713d..791c40c326733 100644 --- a/Framework/Core/test/test_WorkflowSerialization.cxx +++ b/Framework/Core/test/test_WorkflowSerialization.cxx @@ -120,3 +120,36 @@ TEST_CASE("TestVerifyWildcard") // also check if the conversion to ConcreteDataMatcher is working at import // REQUIRE(std::get_if(&w1[0].inputs[0].matcher) != nullptr);; } + +TEST_CASE("TestInputOutputSpecMetadata") +{ + WorkflowSpec wso{ + DataProcessorSpec{ + .name = "S1", + .outputs = {OutputSpec{OutputLabel{"o1"}, o2::header::DataOrigin{"TST"}, "OUTPUT1", 0, Lifetime::Timeframe, {{"param1", VariantType::Bool, true, ConfigParamSpec::HelpString{"\"\""}}, {"param2", VariantType::Bool, true, ConfigParamSpec::HelpString{"\"\""}}}}, + OutputSpec{OutputLabel{"o2"}, o2::header::DataOrigin{"TST"}, "OUTPUT2"}}}}; + + std::vector dataProcessorInfoOut{ + {.name = "S1", .executable = "test_Framework_test_SerializationWorkflow"}, + }; + + CommandInfo commandInfoOut{"o2-dpl-workflow -b"}; + + std::vector dataProcessorInfoIn{}; + CommandInfo commandInfoIn; + + std::ostringstream firstDump; + WorkflowSerializationHelpers::dump(firstDump, wso, dataProcessorInfoOut, commandInfoOut); + std::istringstream is; + is.str(firstDump.str()); + + WorkflowSpec wsi; + WorkflowSerializationHelpers::import(is, wsi, dataProcessorInfoIn, commandInfoIn); + + REQUIRE(wsi[0].outputs[0].metadata.size() == 2); + REQUIRE(wsi[0].outputs[1].metadata.size() == 0); + REQUIRE(wso[0].outputs[0].metadata.size() == wsi[0].outputs[0].metadata.size()); + REQUIRE(wso[0].outputs[1].metadata.size() == wsi[0].outputs[1].metadata.size()); + REQUIRE(wso[0].outputs[0].metadata[0] == wsi[0].outputs[0].metadata[0]); + REQUIRE(wso[0].outputs[0].metadata[1] == wsi[0].outputs[0].metadata[1]); +} diff --git a/Framework/Foundation/CMakeLists.txt b/Framework/Foundation/CMakeLists.txt index dc6d7238c60ac..ba429e6ecc4e7 100644 --- a/Framework/Foundation/CMakeLists.txt +++ b/Framework/Foundation/CMakeLists.txt @@ -32,7 +32,8 @@ add_executable(o2-test-framework-foundation test/test_CallbackRegistry.cxx test/test_CompilerBuiltins.cxx # test/test_Signpost.cxx - test/test_RuntimeError.cxx) + test/test_RuntimeError.cxx + test/test_BigEndian.cxx) target_link_libraries(o2-test-framework-foundation PRIVATE O2::FrameworkFoundation) target_link_libraries(o2-test-framework-foundation PRIVATE O2::Catch2) @@ -65,4 +66,10 @@ install(TARGETS o2-log RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) add_test(NAME framework:foundation COMMAND o2-test-framework-foundation) +add_executable(o2-benchmark-framework-BigEndian + test/benchmark_BigEndian.cxx) +target_link_libraries(o2-benchmark-framework-BigEndian + PRIVATE O2::FrameworkFoundation benchmark::benchmark) +set_property(TARGET o2-benchmark-framework-BigEndian PROPERTY RUNTIME_OUTPUT_DIRECTORY ${outdir}) + add_subdirectory(3rdparty) diff --git a/Framework/Foundation/include/Framework/BigEndian.h b/Framework/Foundation/include/Framework/BigEndian.h new file mode 100644 index 0000000000000..6ddb4a62a95e7 --- /dev/null +++ b/Framework/Foundation/include/Framework/BigEndian.h @@ -0,0 +1,64 @@ +// Copyright 2019-2026 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. + +#ifndef O2_FRAMEWORK_BIGENDIAN_H_ +#define O2_FRAMEWORK_BIGENDIAN_H_ + +#include +#include +#include +#include + +namespace o2::framework +{ + +/// Copy @a count elements of @a typeSize bytes each from big-endian @a src +/// into native byte order at @a dest. For typeSize == 1 or on big-endian +/// platforms this reduces to a plain memcpy. @a dest and @a src must not overlap. +inline void bigEndianCopy(void* dest, const void* src, int count, size_t typeSize) +{ + auto const totalBytes = static_cast(count) * typeSize; + if constexpr (std::endian::native == std::endian::big) { + std::memcpy(dest, src, totalBytes); + return; + } + switch (typeSize) { + case 2: { + auto* p = static_cast(dest); + auto* q = static_cast(src); + for (int i = 0; i < count; ++i) { + p[i] = __builtin_bswap16(q[i]); + } + return; + } + case 4: { + auto* p = static_cast(dest); + auto* q = static_cast(src); + for (int i = 0; i < count; ++i) { + p[i] = __builtin_bswap32(q[i]); + } + return; + } + case 8: { + auto* p = static_cast(dest); + auto* q = static_cast(src); + for (int i = 0; i < count; ++i) { + p[i] = __builtin_bswap64(q[i]); + } + return; + } + } + std::memcpy(dest, src, totalBytes); +} + +} // namespace o2::framework + +#endif // O2_FRAMEWORK_BIGENDIAN_H_ diff --git a/Framework/Foundation/include/Framework/Endian.h b/Framework/Foundation/include/Framework/Endian.h deleted file mode 100644 index 06474f7b04e6c..0000000000000 --- a/Framework/Foundation/include/Framework/Endian.h +++ /dev/null @@ -1,74 +0,0 @@ -// 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. - -#ifndef O2_FRAMEWORK_ENDIAN_H_ -#define O2_FRAMEWORK_ENDIAN_H_ - -#include -#include -#include -// Lookup file for __BYTE_ORDER -#ifdef __APPLE__ -#include -#define swap16_ ntohs -#define swap32_ ntohl -#define swap64_ ntohll -#else -#include -#define swap16_ be16toh -#define swap32_ be32toh -#define ntohll be64toh -#define htonll htobe64 -#define swap64_ ntohll -#endif -#define O2_HOST_BYTE_ORDER __BYTE_ORDER -#define O2_BIG_ENDIAN __BIG_ENDIAN -#define O2_LITTLE_ENDIAN __LITTLE_ENDIAN - -inline uint16_t doSwap(std::same_as auto x) -{ - return swap16_(x); -} - -inline uint32_t doSwap(std::same_as auto x) -{ - return swap32_(x); -} - -inline uint64_t doSwap(std::same_as auto x) -{ - return swap64_(x); -} - -template -inline void doSwapCopy_(void* dest, void* source, int size) noexcept -{ - auto tdest = static_cast(dest); - auto tsrc = static_cast(source); - for (auto i = 0; i < size; ++i) { - tdest[i] = doSwap(tsrc[i]); - } -} - -inline void swapCopy(unsigned char* dest, char* source, int size, int typeSize) noexcept -{ - switch (typeSize) { - case 1: - return (void)std::memcpy(dest, source, size); - case 2: - return doSwapCopy_(dest, source, size); - case 4: - return doSwapCopy_(dest, source, size); - case 8: - return doSwapCopy_(dest, source, size); - } -} -#endif // O2_FRAMEWORK_ENDIAN_H_ diff --git a/Framework/Foundation/include/Framework/Signpost.h b/Framework/Foundation/include/Framework/Signpost.h index 51d1b0433b0de..0bbaaa5c37ed2 100644 --- a/Framework/Foundation/include/Framework/Signpost.h +++ b/Framework/Foundation/include/Framework/Signpost.h @@ -14,6 +14,7 @@ #include "Framework/CompilerBuiltins.h" #include #include +#include #ifdef __APPLE__ #include #endif diff --git a/Framework/Foundation/test/benchmark_BigEndian.cxx b/Framework/Foundation/test/benchmark_BigEndian.cxx new file mode 100644 index 0000000000000..30ca3160e2dac --- /dev/null +++ b/Framework/Foundation/test/benchmark_BigEndian.cxx @@ -0,0 +1,65 @@ +// Copyright 2019-2026 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. + +#include "Framework/BigEndian.h" +#include +#include +#include +#include + +using namespace o2::framework; + +static void BM_BigEndianCopyUInt16(benchmark::State& state) +{ + auto const bytes = static_cast(state.range(0)); + int const count = bytes / sizeof(uint16_t); + std::vector src(count, 0xCAFE); + auto* dest = static_cast(std::aligned_alloc(64, bytes)); + for (auto _ : state) { + bigEndianCopy(dest, src.data(), count, sizeof(uint16_t)); + benchmark::DoNotOptimize(dest); + } + state.SetBytesProcessed(static_cast(state.iterations()) * bytes); + std::free(dest); +} +BENCHMARK(BM_BigEndianCopyUInt16)->RangeMultiplier(2)->Range(32000, 512000); + +static void BM_BigEndianCopyUInt32(benchmark::State& state) +{ + auto const bytes = static_cast(state.range(0)); + int const count = bytes / sizeof(uint32_t); + std::vector src(count, 0xDEADBEEF); + auto* dest = static_cast(std::aligned_alloc(64, bytes)); + for (auto _ : state) { + bigEndianCopy(dest, src.data(), count, sizeof(uint32_t)); + benchmark::DoNotOptimize(dest); + } + state.SetBytesProcessed(static_cast(state.iterations()) * bytes); + std::free(dest); +} +BENCHMARK(BM_BigEndianCopyUInt32)->RangeMultiplier(2)->Range(32000, 512000); + +static void BM_BigEndianCopyUInt64(benchmark::State& state) +{ + auto const bytes = static_cast(state.range(0)); + int const count = bytes / sizeof(uint64_t); + std::vector src(count, 0x0123456789ABCDEFULL); + auto* dest = static_cast(std::aligned_alloc(64, bytes)); + for (auto _ : state) { + bigEndianCopy(dest, src.data(), count, sizeof(uint64_t)); + benchmark::DoNotOptimize(dest); + } + state.SetBytesProcessed(static_cast(state.iterations()) * bytes); + std::free(dest); +} +BENCHMARK(BM_BigEndianCopyUInt64)->RangeMultiplier(2)->Range(32000, 512000); + +BENCHMARK_MAIN(); diff --git a/Framework/Foundation/test/test_BigEndian.cxx b/Framework/Foundation/test/test_BigEndian.cxx new file mode 100644 index 0000000000000..ce52b47e3aea1 --- /dev/null +++ b/Framework/Foundation/test/test_BigEndian.cxx @@ -0,0 +1,67 @@ +// Copyright 2019-2026 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. + +#include "Framework/BigEndian.h" +#include +#include +#include + +using namespace o2::framework; + +TEST_CASE("bigEndianCopy: typeSize 1 is a plain copy") +{ + alignas(64) uint8_t dest[64] = {}; + uint8_t src[4] = {0x01, 0x02, 0x03, 0x04}; + bigEndianCopy(dest, src, 4, 1); + REQUIRE(std::memcmp(dest, src, 4) == 0); +} + +TEST_CASE("bigEndianCopy: uint16 byte swap") +{ + alignas(64) uint8_t dest[64] = {}; + uint8_t src[2] = {0xCA, 0xFE}; // big-endian 0xCAFE + bigEndianCopy(dest, src, 1, 2); + uint16_t result; + std::memcpy(&result, dest, 2); + REQUIRE(result == 0xCAFE); +} + +TEST_CASE("bigEndianCopy: uint32 byte swap") +{ + alignas(64) uint8_t dest[64] = {}; + uint8_t src[4] = {0xDE, 0xAD, 0xBE, 0xEF}; // big-endian 0xDEADBEEF + bigEndianCopy(dest, src, 1, 4); + uint32_t result; + std::memcpy(&result, dest, 4); + REQUIRE(result == 0xDEADBEEF); +} + +TEST_CASE("bigEndianCopy: uint64 byte swap") +{ + alignas(64) uint8_t dest[64] = {}; + uint8_t src[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + bigEndianCopy(dest, src, 1, 8); + uint64_t result; + std::memcpy(&result, dest, 8); + REQUIRE(result == 0x0123456789ABCDEFULL); +} + +TEST_CASE("bigEndianCopy: multiple elements") +{ + alignas(64) uint8_t dest[64] = {}; + uint8_t src[8] = {0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04}; + bigEndianCopy(dest, src, 4, 2); + auto* p = reinterpret_cast(dest); + REQUIRE(p[0] == 1); + REQUIRE(p[1] == 2); + REQUIRE(p[2] == 3); + REQUIRE(p[3] == 4); +} diff --git a/Framework/GUISupport/src/FrameworkGUIDeviceInspector.cxx b/Framework/GUISupport/src/FrameworkGUIDeviceInspector.cxx index aa546b8a9ab49..b29e024ec641e 100644 --- a/Framework/GUISupport/src/FrameworkGUIDeviceInspector.cxx +++ b/Framework/GUISupport/src/FrameworkGUIDeviceInspector.cxx @@ -400,16 +400,25 @@ void displayDeviceInspector(DeviceSpec const& spec, } } - bool logsChanged = false; if (ImGui::CollapsingHeader("Signposts", ImGuiTreeNodeFlags_DefaultOpen)) { - logsChanged = ImGui::CheckboxFlags("Device", &control.logStreams, DeviceState::LogStreams::DEVICE_LOG); - logsChanged = ImGui::CheckboxFlags("Completion", &control.logStreams, DeviceState::LogStreams::COMPLETION_LOG); - logsChanged = ImGui::CheckboxFlags("Monitoring", &control.logStreams, DeviceState::LogStreams::MONITORING_SERVICE_LOG); - logsChanged = ImGui::CheckboxFlags("DataProcessorContext", &control.logStreams, DeviceState::LogStreams::DATA_PROCESSOR_CONTEXT_LOG); - logsChanged = ImGui::CheckboxFlags("StreamContext", &control.logStreams, DeviceState::LogStreams::STREAM_CONTEXT_LOG); - if (logsChanged && control.controller) { - std::string cmd = fmt::format("/log-streams {}", control.logStreams); - control.controller->write(cmd.c_str(), cmd.size()); + static const struct { + const char* label; + int bit; + const char* fullName; + } kStreams[] = { + {"Device", DeviceState::LogStreams::DEVICE_LOG, "ch.cern.aliceo2.device"}, + {"Completion", DeviceState::LogStreams::COMPLETION_LOG, "ch.cern.aliceo2.completion"}, + {"Monitoring", DeviceState::LogStreams::MONITORING_SERVICE_LOG, "ch.cern.aliceo2.monitoring_service"}, + {"DataProcessorContext", DeviceState::LogStreams::DATA_PROCESSOR_CONTEXT_LOG, "ch.cern.aliceo2.data_processor_context"}, + {"StreamContext", DeviceState::LogStreams::STREAM_CONTEXT_LOG, "ch.cern.aliceo2.stream_context"}, + }; + for (auto const& s : kStreams) { + if (ImGui::CheckboxFlags(s.label, &control.logStreams, s.bit) && control.controller) { + bool enabled = (control.logStreams & s.bit) != 0; + std::string cmd = enabled ? fmt::format("/signpost:enable {}", s.fullName) + : fmt::format("/signpost:disable {}", s.fullName); + control.controller->write(cmd.c_str(), cmd.size()); + } } } diff --git a/Framework/GUISupport/src/FrameworkGUIDevicesGraph.cxx b/Framework/GUISupport/src/FrameworkGUIDevicesGraph.cxx index 1c4ddd7e6aabf..eeb9aeb44795e 100644 --- a/Framework/GUISupport/src/FrameworkGUIDevicesGraph.cxx +++ b/Framework/GUISupport/src/FrameworkGUIDevicesGraph.cxx @@ -41,8 +41,37 @@ struct NodeColor { using LogLevel = LogParsingHelpers::LogLevel; -NodeColor decideColorForNode(const DeviceInfo& info) +NodeColor decideColorForNode(const DeviceInfo& info, bool lightMode) { + if (lightMode) { + // Dark-on-bright: rich medium-dark cards on a white canvas, white text on nodes + if (info.active == false) { + return NodeColor{ + .normal = ImVec4(0xb5 / 255.f, 0x26 / 255.f, 0x18 / 255.f, 1), // dark crimson + .hovered = ImVec4(0xc2 / 255.f, 0x2d / 255.f, 0x1d / 255.f, 1)}; + } + switch (info.streamingState) { + case StreamingState::EndOfStreaming: + return NodeColor{ + .normal = ImVec4(0x8c / 255.f, 0x6c / 255.f, 0x00 / 255.f, 1), // dark amber + .hovered = ImVec4(0x9e / 255.f, 0x7a / 255.f, 0x00 / 255.f, 1), + .title = ImVec4(0x6e / 255.f, 0x54 / 255.f, 0x00 / 255.f, 1), + .title_hovered = ImVec4(0x5a / 255.f, 0x44 / 255.f, 0x00 / 255.f, 1)}; + case StreamingState::Idle: + return NodeColor{ + .normal = ImVec4(0x1a / 255.f, 0x80 / 255.f, 0x40 / 255.f, 1), // dark forest green + .hovered = ImVec4(0x22 / 255.f, 0x8b / 255.f, 0x47 / 255.f, 1), + .title = ImVec4(0x11 / 255.f, 0x60 / 255.f, 0x2e / 255.f, 1), + .title_hovered = ImVec4(0x0a / 255.f, 0x4d / 255.f, 0x23 / 255.f, 1)}; + case StreamingState::Streaming: + default: + return NodeColor{ + .normal = ImVec4(0x3a / 255.f, 0x3a / 255.f, 0x3c / 255.f, 1), // macOS tertiary dark + .hovered = ImVec4(0x48 / 255.f, 0x48 / 255.f, 0x4a / 255.f, 1), + .title = ImVec4(0x2c / 255.f, 0x2c / 255.f, 0x2e / 255.f, 1), + .title_hovered = ImVec4(0x1c / 255.f, 0x1c / 255.f, 0x1e / 255.f, 1)}; + } + } if (info.active == false) { return NodeColor{ .normal = PaletteHelpers::RED, @@ -82,7 +111,7 @@ const static ImColor ARROW_BACKGROUND_COLOR = {100, 100, 0}; const static ImColor ARROW_HALFGROUND_COLOR = {170, 170, 70}; const static ImColor ARROW_COLOR = {200, 200, 100}; const static ImColor ARROW_SELECTED_COLOR = {200, 0, 100}; -const static ImU32 GRID_COLOR = ImColor(200, 200, 200, 40); +const static ImU32 GRID_COLOR = ImColor(150, 150, 150, 80); const static ImColor NODE_BORDER_COLOR = {100, 100, 100}; const static ImColor LEGEND_COLOR = {100, 100, 100}; @@ -508,6 +537,8 @@ void showTopologyNodeGraph(WorkspaceGUIState& state, ImGui::SameLine(); ImGui::Checkbox("Show legend", &show_legend); ImGui::SameLine(); + ImGui::Checkbox("Light mode", &state.topologyLightMode); + ImGui::SameLine(); if (ImGui::Button("Center")) { scrolling = ImVec2(0., 0.); } @@ -577,11 +608,10 @@ void showTopologyNodeGraph(WorkspaceGUIState& state, ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); -#if defined(ImGuiCol_ChildWindowBg) - ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, (ImU32)ImColor(60, 60, 70, 200)); -#else - ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImU32)ImColor(60, 60, 70, 200)); -#endif + auto canvasBg = state.topologyLightMode ? (ImU32)ImColor(250, 250, 252, 255) : (ImU32)ImColor(44, 44, 46, 255); + auto canvasText = (ImU32)ImColor(235, 235, 245, 255); // nodes are always dark, so text is always light + ImGui::PushStyleColor(ImGuiCol_ChildBg, canvasBg); + ImGui::PushStyleColor(ImGuiCol_Text, canvasText); ImVec2 graphSize = ImGui::GetWindowSize(); if (state.leftPaneVisible) { graphSize.x -= state.leftPaneSize; @@ -604,6 +634,12 @@ void showTopologyNodeGraph(WorkspaceGUIState& state, ImVec2 win_pos = ImGui::GetCursorScreenPos(); ImVec2 canvas_sz = ImGui::GetWindowSize(); + + // Arrow colors — richer amber in light mode to stand out on white canvas + const ImColor arrowBgColor = state.topologyLightMode ? ImColor(140, 80, 0) : ARROW_BACKGROUND_COLOR; + const ImColor arrowHalfColor = state.topologyLightMode ? ImColor(180, 110, 0) : ARROW_HALFGROUND_COLOR; + const ImColor arrowColor = state.topologyLightMode ? ImColor(220, 140, 0) : ARROW_COLOR; + // Display links but only if they are inside the view. for (int link_idx = 0; link_idx < links.Size; link_idx++) { // Do the geometry culling upfront. @@ -627,7 +663,7 @@ void showTopologyNodeGraph(WorkspaceGUIState& state, continue; } draw_list->ChannelsSetCurrent(0); // Background - auto color = ARROW_BACKGROUND_COLOR; + auto color = arrowBgColor; auto thickness = ARROW_BACKGROUND_THICKNESS; bool p1Inside = false; @@ -643,12 +679,12 @@ void showTopologyNodeGraph(WorkspaceGUIState& state, if (p1Inside && p2Inside) { // Whatever the two edges completely within the view, gets brighter color and foreground. draw_list->ChannelsSetCurrent(2); - color = ARROW_COLOR; + color = arrowColor; thickness = ARROW_THICKNESS; } else if (p1Inside || p2Inside) { draw_list->ChannelsSetCurrent(1); // Whenever one of the two ends is within the view, increase the color but keep the background - color = ARROW_HALFGROUND_COLOR; + color = arrowHalfColor; thickness = ARROW_HALFGROUND_THICKNESS; } @@ -756,7 +792,7 @@ void showTopologyNodeGraph(WorkspaceGUIState& state, scrolling = scrolling - ImVec2(ImGui::GetIO().MouseDelta.x / 4.f, ImGui::GetIO().MouseDelta.y / 4.f); } - auto nodeBg = decideColorForNode(info); + auto nodeBg = decideColorForNode(info, state.topologyLightMode); auto hovered = (node_hovered_in_list == node->ID || node_hovered_in_scene == node->ID || (node_hovered_in_list == -1 && node_selected == node->ID)); ImVec4 nodeBgColor = hovered ? nodeBg.hovered : nodeBg.normal; @@ -776,7 +812,7 @@ void showTopologyNodeGraph(WorkspaceGUIState& state, auto pp1 = p1 + offset + slotPos; auto pp2 = p2 + offset + slotPos; auto pp3 = p3 + offset + slotPos; - auto color = ARROW_COLOR; + auto color = arrowColor; if (node_idx == node_selected) { color = ARROW_SELECTED_COLOR; } @@ -881,7 +917,7 @@ void showTopologyNodeGraph(WorkspaceGUIState& state, ImGui::PopItemWidth(); ImGui::EndChild(); - ImGui::PopStyleColor(); + ImGui::PopStyleColor(2); ImGui::PopStyleVar(2); ImGui::EndGroup(); diff --git a/Framework/GUISupport/src/FrameworkGUIState.h b/Framework/GUISupport/src/FrameworkGUIState.h index 8a1cd8dd3bd1e..d36b6c6120fc0 100644 --- a/Framework/GUISupport/src/FrameworkGUIState.h +++ b/Framework/GUISupport/src/FrameworkGUIState.h @@ -34,6 +34,7 @@ struct WorkspaceGUIState { bool leftPaneVisible; bool rightPaneVisible; bool bottomPaneVisible; + bool topologyLightMode; double startTime; }; diff --git a/Framework/GUISupport/src/PaletteHelpers.cxx b/Framework/GUISupport/src/PaletteHelpers.cxx index 1ad04ce7c7f3f..fa19b0dd43319 100644 --- a/Framework/GUISupport/src/PaletteHelpers.cxx +++ b/Framework/GUISupport/src/PaletteHelpers.cxx @@ -13,20 +13,35 @@ namespace o2::framework { -const ImVec4 PaletteHelpers::RED = ImVec4(0.945, 0.094, 0.298, 1); -const ImVec4 PaletteHelpers::GREEN = ImVec4(0x7e / 255., 0xc4 / 255., 0x52 / 255., 1); -const ImVec4 PaletteHelpers::BLUE = ImVec4(0x3d / 255., 0xb7 / 255., 0xe0 / 255., 1); -const ImVec4 PaletteHelpers::YELLOW = ImVec4(0.949, 0.769, 0.239, 1); -const ImVec4 PaletteHelpers::SHADED_RED = ImVec4(0xd5 / 255., 0x72 / 255., 0x73 / 255., 1); -const ImVec4 PaletteHelpers::SHADED_GREEN = ImVec4(0x98 / 255., 0xba / 255., 0x96 / 255., 1); -const ImVec4 PaletteHelpers::SHADED_BLUE = ImVec4(0x7a / 255., 0xab / 255., 0xea / 255., 1); -const ImVec4 PaletteHelpers::SHADED_YELLOW = ImVec4(0xeb / 255., 0xb9 / 255., 0x7a / 255., 1); -const ImVec4 PaletteHelpers::DARK_RED = ImVec4(0xd4 / 255., 0x06 / 255., 0x02 / 255., 255. / 255.); -const ImVec4 PaletteHelpers::DARK_GREEN = ImVec4(153. / 255., 61. / 255., 61. / 255., 255. / 255.); -const ImVec4 PaletteHelpers::DARK_YELLOW = ImVec4(0xf1 / 255., 0x9b / 255., 0x2c / 255., 255. / 255.); -const ImVec4 PaletteHelpers::WHITE = ImVec4(0xce / 255., 0xbe / 255., 0x91 / 255., 1); -const ImVec4 PaletteHelpers::BLACK = ImVec4(0x28 / 255., 0x28 / 255., 0x28 / 255., 1); -const ImVec4 PaletteHelpers::GRAY = ImVec4(60 / 255., 60 / 255., 60 / 255., 1); -const ImVec4 PaletteHelpers::LIGHT_GRAY = ImVec4(75 / 255., 75 / 255., 75 / 255., 1); +// Vivid accent colors — macOS system color palette / Pantone-adjacent +// RED: macOS Red (#FF3B30) / Pantone 485 C adjacent +const ImVec4 PaletteHelpers::RED = ImVec4(0xff / 255., 0x3b / 255., 0x30 / 255., 1); +// GREEN: macOS Green (#34C759) / Pantone 368 C adjacent +const ImVec4 PaletteHelpers::GREEN = ImVec4(0x34 / 255., 0xc7 / 255., 0x59 / 255., 1); +// BLUE: macOS Blue (#007AFF) / Pantone 2728 C adjacent +const ImVec4 PaletteHelpers::BLUE = ImVec4(0x00 / 255., 0x7a / 255., 0xff / 255., 1); +// YELLOW: macOS Yellow (#FFCC00) / Pantone 116 C adjacent +const ImVec4 PaletteHelpers::YELLOW = ImVec4(0xff / 255., 0xcc / 255., 0x00 / 255., 1); +// Muted/shaded variants — desaturated for secondary use +const ImVec4 PaletteHelpers::SHADED_RED = ImVec4(0xff / 255., 0x69 / 255., 0x61 / 255., 1); +const ImVec4 PaletteHelpers::SHADED_GREEN = ImVec4(0x86 / 255., 0xd9 / 255., 0x88 / 255., 1); +const ImVec4 PaletteHelpers::SHADED_BLUE = ImVec4(0x5a / 255., 0xc8 / 255., 0xfa / 255., 1); +const ImVec4 PaletteHelpers::SHADED_YELLOW = ImVec4(0xff / 255., 0xd6 / 255., 0x0a / 255., 1); +// Dark variants — for title bars and hovered states +// DARK_RED: Pantone 485 C (#DA291C) +const ImVec4 PaletteHelpers::DARK_RED = ImVec4(0xda / 255., 0x29 / 255., 0x1c / 255., 1); +// DARK_GREEN: (#1E8449) +const ImVec4 PaletteHelpers::DARK_GREEN = ImVec4(0x1e / 255., 0x84 / 255., 0x49 / 255., 1); +// DARK_YELLOW: macOS Orange (#FF9F0A) / Pantone 137 C adjacent +const ImVec4 PaletteHelpers::DARK_YELLOW = ImVec4(0xff / 255., 0x9f / 255., 0x0a / 255., 1); +// Neutrals — macOS dark mode system backgrounds +// WHITE: used as primary text / highlight color in dark UI +const ImVec4 PaletteHelpers::WHITE = ImVec4(0xf5 / 255., 0xf5 / 255., 0xf7 / 255., 1); +// BLACK: macOS dark background (#1C1C1E) +const ImVec4 PaletteHelpers::BLACK = ImVec4(0x1c / 255., 0x1c / 255., 0x1e / 255., 1); +// GRAY: macOS secondary background (#2C2C2E) +const ImVec4 PaletteHelpers::GRAY = ImVec4(0x2c / 255., 0x2c / 255., 0x2e / 255., 1); +// LIGHT_GRAY: macOS tertiary background (#3A3A3C) +const ImVec4 PaletteHelpers::LIGHT_GRAY = ImVec4(0x3a / 255., 0x3a / 255., 0x3c / 255., 1); } // namespace o2::framework diff --git a/Framework/TestWorkflows/src/o2TestAnalysisCCDB.cxx b/Framework/TestWorkflows/src/o2TestAnalysisCCDB.cxx index f9684762539f7..3cf20d9ff5296 100644 --- a/Framework/TestWorkflows/src/o2TestAnalysisCCDB.cxx +++ b/Framework/TestWorkflows/src/o2TestAnalysisCCDB.cxx @@ -51,8 +51,11 @@ struct DummyTimestampsTable { }; struct SimpleCCDBConsumer { + ConfigurableCCDBPath lhcPhasePath; + void process(o2::aod::TOFCalibrationObjects const& ccdbObjectsForAllTimestamps) { + LOGP(info, "LHCphase CCDB path configurable value: {}", lhcPhasePath.value); LOGP(info, "Looking at all the LHCphases associated to the timestamps"); for (auto& object : ccdbObjectsForAllTimestamps) { std::cout << object.lhcPhase().getStartValidity() << " " << object.lhcPhase().getEndValidity() << std::endl; @@ -60,10 +63,23 @@ struct SimpleCCDBConsumer { } }; +struct AnotherCCDBConsumer { + ConfigurableCCDBPath lhcPhasePath; + + void process(o2::aod::TOFCalibrationObjects const& ccdbObjectsForAllTimestamps) + { + LOGP(info, "AnotherCCDBConsumer LHCphase CCDB path configurable value: {}", lhcPhasePath.value); + for (auto& object : ccdbObjectsForAllTimestamps) { + std::cout << object.lhcPhase().getStartValidity() << " " << object.lhcPhase().getEndValidity() << std::endl; + } + } +}; + WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) { return WorkflowSpec{ adaptAnalysisTask(cfgc), - adaptAnalysisTask(cfgc, TaskName{"simple-ccdb-cunsumer"}), + adaptAnalysisTask(cfgc, TaskName{"simple-ccdb-consumer"}), + adaptAnalysisTask(cfgc, TaskName{"another-ccdb-consumer"}), }; } diff --git a/Framework/TestWorkflows/src/o2TestMultisource.cxx b/Framework/TestWorkflows/src/o2TestMultisource.cxx index 00bd9ba5093bd..d52a25f67e98f 100644 --- a/Framework/TestWorkflows/src/o2TestMultisource.cxx +++ b/Framework/TestWorkflows/src/o2TestMultisource.cxx @@ -25,8 +25,6 @@ using namespace o2::framework::expressions; namespace o2::aod { O2ORIGIN("EMB"); -template -using BCsFrom = BCs_001From; using TracksPlus = soa::Join; template using TracksPlusFrom = soa::Join, StoredTracksExtra_002From>; @@ -34,9 +32,9 @@ using TracksPlusFrom = soa::Join, StoredTracksExtra_002Fro struct TestEmbeddingSubscription { void process(aod::BCs const& bcs, aod::BCsFrom> const& bcse, - aod::TracksPlus const& tracks, aod::TracksPlusFrom> const& trackse) + aod::TracksIU const& tracks, aod::TracksIUFrom> const& trackse) { - LOGP(info, "BCs from run {} and {}", bcs.begin().runNumber(), bcse.begin().runNumber()); + LOGP(info, "BCs from run {} ({}) and {} ({})", bcs.begin().runNumber(), bcs.size(), bcse.begin().runNumber(), bcse.size()); LOGP(info, "Joined tracks: {} and {}", tracks.size(), trackse.size()); } }; diff --git a/Framework/Utils/test/RawPageTestData.h b/Framework/Utils/test/RawPageTestData.h index a6b800f7cba32..29ac4eeba6b5b 100644 --- a/Framework/Utils/test/RawPageTestData.h +++ b/Framework/Utils/test/RawPageTestData.h @@ -42,13 +42,17 @@ struct DataSet { DataSet(std::vector&& s, Messages&& m, std::vector&& v, ServiceRegistryRef registry) : schema{std::move(s)}, messages{std::move(m)}, - span{[this](size_t i, size_t part) { - auto header = static_cast(this->messages[i].at(2 * part)->data()); - auto payload = static_cast(this->messages[i].at(2 * part + 1)->data()); + span{[this](size_t i) { return i < this->messages.size() ? messages[i].size() / 2 : 0; }, + nullptr, + [this](size_t i, DataRefIndices idx) { + auto header = static_cast(this->messages[i].at(idx.headerIdx)->data()); + auto payload = static_cast(this->messages[i].at(idx.payloadIdx)->data()); return DataRef{nullptr, header, payload}; }, - [this](size_t i) { return i < this->messages.size() ? messages[i].size() / 2 : 0; }, - nullptr, + [this](size_t i, DataRefIndices current) -> DataRefIndices { + size_t next = current.headerIdx + 2; + return next < this->messages[i].size() ? DataRefIndices{next, next + 1} : DataRefIndices{size_t(-1), size_t(-1)}; + }, this->messages.size()}, record{schema, span, registry}, values{std::move(v)} diff --git a/Framework/Utils/test/test_RootTreeWriter.cxx b/Framework/Utils/test/test_RootTreeWriter.cxx index 62e1eb62cb4f1..e372fb4e1302e 100644 --- a/Framework/Utils/test/test_RootTreeWriter.cxx +++ b/Framework/Utils/test/test_RootTreeWriter.cxx @@ -224,10 +224,14 @@ TEST_CASE("test_RootTreeWriter") {InputSpec{"input8", "TST", "SRLZDVEC"}, 7, "input8", 0}, // }; - auto getter = [&store](size_t i) -> DataRef { - return DataRef{nullptr, static_cast(store[2 * i]->GetData()), static_cast(store[2 * i + 1]->GetData())}; - }; - InputSpan span{getter, store.size() / 2}; + InputSpan span{ + [](size_t) -> size_t { return 1; }, + nullptr, + [&store](size_t i, DataRefIndices idx) -> DataRef { + return DataRef{nullptr, static_cast(store[2 * i + idx.headerIdx]->GetData()), static_cast(store[2 * i + idx.payloadIdx]->GetData())}; + }, + [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + store.size() / 2}; ServiceRegistry registry; InputRecord inputs{ schema, diff --git a/GPU/Common/CMakeLists.txt b/GPU/Common/CMakeLists.txt index 8f7a7c2e169ed..807756f1e2036 100644 --- a/GPU/Common/CMakeLists.txt +++ b/GPU/Common/CMakeLists.txt @@ -11,23 +11,6 @@ set(MODULE GPUCommon) -set(HDRS_INSTALL - GPUCommonAlgorithm.h - GPUCommonDef.h - GPUCommonDefAPI.h - GPUCommonHelpers.h - GPUCommonDefSettings.h - GPUCommonConstants.h - GPUCommonLogger.h - GPUCommonMath.h - GPUCommonRtypes.h - GPUCommonArray.h - GPUCommonTypeTraits.h - GPUCommonTransform3D.h - GPUROOTSMatrixFwd.h - GPUROOTCartesianFwd.h - GPUDebugStreamer.h) - if(ALIGPU_BUILD_TYPE STREQUAL "O2") o2_add_library(${MODULE} SOURCES ../GPUTracking/utils/EmptyFile.cxx @@ -64,4 +47,4 @@ if(ALIGPU_BUILD_TYPE STREQUAL "O2") # endif() endif() -install(FILES ${HDRS_INSTALL} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/GPU) +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/GPU FILES_MATCHING PATTERN "*.h" PATTERN "test" EXCLUDE) diff --git a/GPU/Common/GPUCommonAlignedAlloc.h b/GPU/Common/GPUCommonAlignedAlloc.h new file mode 100644 index 0000000000000..8dffcb8e3973e --- /dev/null +++ b/GPU/Common/GPUCommonAlignedAlloc.h @@ -0,0 +1,61 @@ +// 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 GPUCommonAlignedAlloc.h +/// \author David Rohr + +#ifndef GPUCOMMONAKUGBEDALLOC_H +#define GPUCOMMONAKUGBEDALLOC_H + +#include + +namespace o2::gpu +{ + +template +struct alignedDeleter { + void operator()(void* ptr) { ::operator delete(ptr, std::align_val_t(std::max(MIN_ALIGN, alignof(T)))); }; // TODO: Make this static once we go to C++ 23 +}; + +template +struct alignedAllocator { + using value_type = T; + static T* allocate(std::size_t n) + { + return (T*)::operator new(n, std::align_val_t(std::max(MIN_ALIGN, alignof(T)))); + } + static void deallocate(T* ptr, std::size_t) + { + alignedDeleter()(ptr); + } +}; + +template +struct aligned_unique_buffer_ptr : public std::unique_ptr> { + aligned_unique_buffer_ptr() = default; + aligned_unique_buffer_ptr(size_t n) { alloc(n); } + aligned_unique_buffer_ptr(T* ptr) { std::unique_ptr>::reset((char*)ptr); } + char* getraw() { return std::unique_ptr>::get(); } + const char* getraw() const { return std::unique_ptr>::get(); } + T* get() { return (T*)std::unique_ptr>::get(); } + const T* get() const { return (T*)std::unique_ptr>::get(); } + T* operator->() { return get(); } + const T* operator->() const { return get(); } + T* alloc(std::size_t n) + { + std::unique_ptr>::reset((char*)alignedAllocator().allocate(n)); + return get(); + } +}; + +} // namespace o2::gpu + +#endif // GPUCOMMONAKUGBEDALLOC_H diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TracerGPU.h b/GPU/Common/GPUCommonConfigurableParam.h similarity index 53% rename from Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TracerGPU.h rename to GPU/Common/GPUCommonConfigurableParam.h index e2bd7266caff9..475679df270e3 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TracerGPU.h +++ b/GPU/Common/GPUCommonConfigurableParam.h @@ -8,31 +8,35 @@ // 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. -/// -#include "ITStracking/Definitions.h" -#ifndef TRACKINGITSGPU_INCLUDE_TRACER_H -#define TRACKINGITSGPU_INCLUDE_TRACER_H +/// \file GPUCommonConfigurableParam.h +/// \author David Rohr -#if defined(__CUDACC__) && defined(__USE_GPU_TRACER__) -namespace o2 -{ -namespace its -{ -namespace gpu -{ -class Tracer +#ifndef GPUCOMMONCONFIGURABLEPARAM_H +#define GPUCOMMONCONFIGURABLEPARAM_H + +#include "GPUCommonDef.h" + +#if defined(GPUCA_STANDALONE) + +namespace o2::conf { - public: - Tracer(const char* name, int color_id = 0); - ~Tracer(); +template +struct ConfigurableParamHelper { + static const T& Instance() + { + static T instance; + return instance; + } }; -} // namespace gpu -} // namespace its -} // namespace o2 -#define RANGE(name, cid) o2::its::gpu::Tracer tracer(name, cid); +#define O2ParamDef(...) +} // namespace o2::conf + #else -#define RANGE(name, cid) + +#include "CommonUtils/ConfigurableParam.h" +#include "CommonUtils/ConfigurableParamHelper.h" + #endif -#endif // TRACKINGITSGPU_INCLUDE_TRACER_H \ No newline at end of file +#endif diff --git a/GPU/Common/GPUCommonLogger.h b/GPU/Common/GPUCommonLogger.h index a70710e9ae744..d07c672a1eda9 100644 --- a/GPU/Common/GPUCommonLogger.h +++ b/GPU/Common/GPUCommonLogger.h @@ -16,10 +16,13 @@ #define GPUCOMMONFAIRLOGGER_H #include "GPUCommonDef.h" +#ifndef GPUCA_GPUCODE_DEVICE +#include +#endif -#if defined(GPUCA_GPUCODE_DEVICE) -namespace o2::gpu::detail +namespace o2::gpu::internal { +#if defined(GPUCA_GPUCODE_DEVICE) struct DummyLogger { template GPUd() DummyLogger& operator<<(Args... args) @@ -27,40 +30,71 @@ struct DummyLogger { return *this; } }; -} // namespace o2::gpu::detail +#else +template +void LOGP_internal(const char* str, Args... args) +{ + printf("%s\n", str); +} #endif +enum class loglevel : int32_t { + debug = 0, + info = 1, + warning = 2, + important = 3, + alarm = 4, + error = 5, + fatal = 6 +}; +} // namespace o2::gpu::internal + +#ifdef GPUCA_GPUCODE_DEVICE // clang-format off +// ---------- begin GPUCA_GPUCODE_DEVICE ---------- -#if defined(__OPENCL__) || (defined(GPUCA_GPUCODE_DEVICE) && !defined(GPUCA_GPU_DEBUG_PRINT)) -#define LOG(...) o2::gpu::detail::DummyLogger() +#if defined(__OPENCL__) || !defined(GPUCA_GPU_DEBUG_PRINT) +#define LOG(...) o2::gpu::internal::DummyLogger() #define LOGF(...) #define LOGP(...) -#elif defined(GPUCA_GPUCODE_DEVICE) -#define LOG(...) o2::gpu::detail::DummyLogger() +#else +#define LOG(...) o2::gpu::internal::DummyLogger() // #define LOG(...) static_assert(false, "LOG(...) << ... unsupported in GPU code"); -#define LOGF(type, string, ...) \ - { \ - printf(string "\n", ##__VA_ARGS__); \ - } +#define LOGF(type, string, ...) do { if (o2::gpu::internal::loglevel::type >= o2::gpu::internal::loglevel::info) { printf(string "\n", ##__VA_ARGS__); }} while (false); #define LOGP(...) // #define LOGP(...) static_assert(false, "LOGP(...) unsupported in GPU code"); +#endif +// ---------- end GPUCA_GPUCODE_DEVICE ---------- #elif defined(GPUCA_STANDALONE) || defined(GPUCA_GPUCODE_COMPILEKERNELS) || defined(GPUCA_COMPILEKERNELS) +// ---------- begin GPUCA_STANDALONE / COMPILEKERNELS ---------- + #include #include #define LOG(type) std::cout -#define LOGF(type, string, ...) \ - { \ - printf(string "\n", ##__VA_ARGS__); \ - } -#define LOGP(type, string, ...) \ - { \ - printf("%s\n", string); \ - } +#define LOGF(type, string, ...) do { if (o2::gpu::internal::loglevel::type >= o2::gpu::internal::loglevel::info) { printf(string "\n", ##__VA_ARGS__); }} while (false); +#if !defined(GPUCA_NO_FMT) && !defined(GPUCA_GPUCODE) +#define LOGP(type, string, ...) do { if (o2::gpu::internal::loglevel::type >= o2::gpu::internal::loglevel::info) { fmt::print(string, ##__VA_ARGS__); printf("\n"); }} while (false); +#else +#define LOGP(type, string, ...) do { if (o2::gpu::internal::loglevel::type >= o2::gpu::internal::loglevel::info) { o2::gpu::internal::LOGP_internal(string, ##__VA_ARGS__); }} while (false); +#endif +#if defined(GPUCA_STANDALONE) && !defined(GPUCA_GPUCODE) +#if !defined(GPUCA_NO_FMT) +#include +#else +namespace fmt +{ +template +static const char* format(Args... args) +{ + return ""; +} +} // namespace fmt +#endif +#endif +// ---------- end GPUCA_STANDALONE / COMPILEKERNELS ---------- #else #include - -#endif +#endif // clang-format on #endif diff --git a/GPU/Common/GPUCommonMath.h b/GPU/Common/GPUCommonMath.h index 0ff31899dec0c..8f81762d87373 100644 --- a/GPU/Common/GPUCommonMath.h +++ b/GPU/Common/GPUCommonMath.h @@ -108,6 +108,7 @@ class GPUCommonMath GPUd() constexpr static float QuietNaN() { return GPUCA_CHOICE(std::numeric_limits::quiet_NaN(), __builtin_nanf(""), nan(0u)); } #endif GPUd() constexpr static uint32_t Clz(uint32_t val); + GPUd() constexpr static uint32_t Ctz(uint32_t val); GPUd() constexpr static uint32_t Popcount(uint32_t val); GPUd() static void memcpy(void* dst, const void* src, size_t size); @@ -332,6 +333,20 @@ GPUdi() constexpr uint32_t GPUCommonMath::Clz(uint32_t x) #endif } +GPUdi() constexpr uint32_t GPUCommonMath::Ctz(uint32_t x) +{ +#if (defined(__GNUC__) || defined(__clang__) || defined(__CUDACC__) || defined(__HIPCC__)) + return x == 0 ? 32 : GPUCA_CHOICE(__builtin_ctz(x), __ffs(x) - 1, __builtin_ctz(x)); +#else + for (uint32_t i = 0; i < 32; ++i) { + if (x & (1u << i)) { + return i; + } + } + return 32; +#endif +} + GPUdi() constexpr uint32_t GPUCommonMath::Popcount(uint32_t x) { #if (defined(__GNUC__) || defined(__clang__) || defined(__CUDACC__) || defined(__HIPCC__)) && !defined(__OPENCL__) // TODO: remove OPENCL when reported SPIR-V bug is fixed diff --git a/GPU/GPUTracking/Base/GPUConstantMem.h b/GPU/GPUTracking/Base/GPUConstantMem.h index 94ccfa7fa6db9..14c388e450d73 100644 --- a/GPU/GPUTracking/Base/GPUConstantMem.h +++ b/GPU/GPUTracking/Base/GPUConstantMem.h @@ -40,13 +40,13 @@ namespace o2::gpu { struct GPUConstantMem { GPUParam param; - GPUTPCTracker tpcTrackers[GPUCA_NSECTORS]; + GPUTPCTracker tpcTrackers[GPUTPCGeometry::NSECTORS]; GPUTPCCompression tpcCompressor; GPUTPCDecompression tpcDecompressor; GPUTPCGMMerger tpcMerger; GPUTRDTrackerGPU trdTrackerGPU; GPUTRDTracker trdTrackerO2; - GPUTPCClusterFinder tpcClusterer[GPUCA_NSECTORS]; + GPUTPCClusterFinder tpcClusterer[GPUTPCGeometry::NSECTORS]; GPUTrackingRefitProcessor trackingRefit; GPUTrackingInOutPointers ioPtrs; GPUCalibObjectsConst calibObjects; @@ -55,7 +55,7 @@ struct GPUConstantMem { GPUKernelDebugOutput debugOutput; #endif #ifdef GPUCA_HAS_ONNX - GPUTPCNNClusterizer tpcNNClusterer[GPUCA_NSECTORS]; + GPUTPCNNClusterizer tpcNNClusterer[GPUTPCGeometry::NSECTORS]; #endif template GPUd() auto& getTRDTracker(); diff --git a/GPU/GPUTracking/Base/GPUParam.cxx b/GPU/GPUTracking/Base/GPUParam.cxx index aa4c3c7671c93..7edfa8ffd41d5 100644 --- a/GPU/GPUTracking/Base/GPUParam.cxx +++ b/GPU/GPUTracking/Base/GPUParam.cxx @@ -38,7 +38,7 @@ void GPUParam::SetDefaults(float solenoidBz, bool assumeConstantBz) occupancyMapSize = 0; occupancyTotal = 0; -#ifdef GPUCA_TPC_GEOMETRY_O2 +#ifndef GPUCA_RUN2 const float kErrorsY[4] = {0.06, 0.24, 0.12, 0.1}; const float kErrorsZ[4] = {0.06, 0.24, 0.15, 0.1}; @@ -87,28 +87,6 @@ void GPUParam::SetDefaults(float solenoidBz, bool assumeConstantBz) UpdateBzOnly(solenoidBz, assumeConstantBz); par.dodEdx = 0; - constexpr float plusZmin = 0.0529937; - constexpr float plusZmax = 249.778; - constexpr float minusZmin = -249.645; - constexpr float minusZmax = -0.0799937; - for (int32_t i = 0; i < GPUCA_NSECTORS; i++) { - const bool zPlus = (i < GPUCA_NSECTORS / 2); - SectorParam[i].ZMin = zPlus ? plusZmin : minusZmin; - SectorParam[i].ZMax = zPlus ? plusZmax : minusZmax; - int32_t tmp = i; - if (tmp >= GPUCA_NSECTORS / 2) { - tmp -= GPUCA_NSECTORS / 2; - } - if (tmp >= GPUCA_NSECTORS / 4) { - tmp -= GPUCA_NSECTORS / 2; - } - SectorParam[i].Alpha = 0.174533f + dAlpha * tmp; - SectorParam[i].CosAlpha = CAMath::Cos(SectorParam[i].Alpha); - SectorParam[i].SinAlpha = CAMath::Sin(SectorParam[i].Alpha); - SectorParam[i].AngleMin = SectorParam[i].Alpha - dAlpha / 2.f; - SectorParam[i].AngleMax = SectorParam[i].Alpha + dAlpha / 2.f; - } - par.continuousTracking = false; continuousMaxTimeBin = 0; tpcCutTimeBin = 0; @@ -161,7 +139,7 @@ void GPUParam::SetDefaults(const GPUSettingsGRP* g, const GPUSettingsRec* r, con void GPUParam::UpdateRun3ClusterErrors(const float* yErrorParam, const float* zErrorParam) { -#ifdef GPUCA_TPC_GEOMETRY_O2 +#ifndef GPUCA_RUN2 for (int32_t yz = 0; yz < 2; yz++) { const float* param = yz ? zErrorParam : yErrorParam; for (int32_t rowType = 0; rowType < 4; rowType++) { diff --git a/GPU/GPUTracking/Base/GPUParam.h b/GPU/GPUTracking/Base/GPUParam.h index 0716274c5e198..11c48f5aadc70 100644 --- a/GPU/GPUTracking/Base/GPUParam.h +++ b/GPU/GPUTracking/Base/GPUParam.h @@ -20,6 +20,7 @@ #include "GPUDef.h" #include "GPUSettings.h" #include "GPUTPCGMPolynomialField.h" +#include "DataFormatsTPC/Constants.h" #if !defined(GPUCA_GPUCODE) namespace o2::base @@ -36,13 +37,6 @@ struct GPUSettingsRec; struct GPUSettingsGTP; struct GPURecoStepConfiguration; -struct GPUParamSector { - float Alpha; // sector angle - float CosAlpha, SinAlpha; // sign and cosine of the sector angle - float AngleMin, AngleMax; // minimal and maximal angle - float ZMin, ZMax; // sector Z range -}; - namespace internal { template @@ -65,10 +59,8 @@ struct GPUParam_t { uint32_t occupancyTotal; // Total occupancy in the TPC (nCl / nHbf) uint32_t occupancyMapSize; // Size of occupancy map - GPUParamSector SectorParam[GPUCA_NSECTORS]; - protected: -#ifdef GPUCA_TPC_GEOMETRY_O2 +#ifndef GPUCA_RUN2 float ParamErrors[2][4][4]; // cluster error parameterization used during seeding and fit #else float ParamErrorsSeeding0[2][3][4]; // cluster error parameterization used during seeding @@ -87,13 +79,14 @@ struct GPUParam : public internal::GPUParam_t void UpdateRun3ClusterErrors(const float* yErrorParam, const float* zErrorParam); #endif - GPUd() float Alpha(int32_t iSector) const + GPUd() constexpr uint32_t tpcMinHitsB5(float qPtB5) const { return CAMath::Abs(qPtB5) > 10 ? 10 : (CAMath::Abs(qPtB5) > 5 ? 15 : 29); } // Minimum hits should depend on Pt, low Pt tracks can have few hits. 29 Hits default, 15 for < 200 mev, 10 for < 100 mev + GPUd() constexpr float Alpha(int32_t iSector) const // TODO: Check if this is faster, or the lookup from GPUTPCGeometry { - if (iSector >= GPUCA_NSECTORS / 2) { - iSector -= GPUCA_NSECTORS / 2; + if (iSector >= (int32_t)o2::tpc::constants::MAXSECTOR / 2) { + iSector -= o2::tpc::constants::MAXSECTOR / 2; } - if (iSector >= GPUCA_NSECTORS / 4) { - iSector -= GPUCA_NSECTORS / 2; + if (iSector >= (int32_t)o2::tpc::constants::MAXSECTOR / 4) { + iSector -= o2::tpc::constants::MAXSECTOR / 2; } return 0.174533f + dAlpha * iSector; } diff --git a/GPU/GPUTracking/Base/GPUParam.inc b/GPU/GPUTracking/Base/GPUParam.inc index dbccca4d7c46b..566592bdd8d61 100644 --- a/GPU/GPUTracking/Base/GPUParam.inc +++ b/GPU/GPUTracking/Base/GPUParam.inc @@ -26,20 +26,20 @@ namespace o2::gpu GPUdi() void GPUParam::Sector2Global(int32_t iSector, float x, float y, float z, float* X, float* Y, float* Z) const { // conversion of coordinates sector->global - *X = x * SectorParam[iSector].CosAlpha - y * SectorParam[iSector].SinAlpha; - *Y = y * SectorParam[iSector].CosAlpha + x * SectorParam[iSector].SinAlpha; + *X = x * GPUTPCGeometry::SectorCos(iSector) - y * GPUTPCGeometry::SectorSin(iSector); + *Y = y * GPUTPCGeometry::SectorCos(iSector) + x * GPUTPCGeometry::SectorSin(iSector); *Z = z; } GPUdi() void GPUParam::Global2Sector(int32_t iSector, float X, float Y, float Z, float* x, float* y, float* z) const { // conversion of coordinates global->sector - *x = X * SectorParam[iSector].CosAlpha + Y * SectorParam[iSector].SinAlpha; - *y = Y * SectorParam[iSector].CosAlpha - X * SectorParam[iSector].SinAlpha; + *x = X * GPUTPCGeometry::SectorCos(iSector) + Y * GPUTPCGeometry::SectorSin(iSector); + *y = Y * GPUTPCGeometry::SectorCos(iSector) - X * GPUTPCGeometry::SectorSin(iSector); *z = Z; } -#ifdef GPUCA_TPC_GEOMETRY_O2 +#ifndef GPUCA_RUN2 GPUdi() void GPUParam::GetClusterErrorsSeeding2(uint8_t sector, int32_t iRow, float z, float sinPhi, float DzDs, float time, float& ErrY2, float& ErrZ2) const { @@ -116,12 +116,12 @@ GPUdi() float GPUParam::GetSystematicClusterErrorC122(float x, float y, uint8_t return 0.f; } constexpr float dEdgeInv = 18.f / CAMath::Pi(); - const float dy = (sector == (GPUCA_NSECTORS / 2 + 1) ? 0.5f : -0.5f) * (y / x) * dEdgeInv + 0.5f; + const float dy = (sector == (GPUTPCGeometry::NSECTORS / 2 + 1) ? 0.5f : -0.5f) * (y / x) * dEdgeInv + 0.5f; const float errC12 = rec.tpc.sysClusErrorC12Norm * occupancyTotal * dy; return errC12 * errC12; } -#else // GPUCA_TPC_GEOMETRY_O2 +#else // !GPUCA_RUN2 GPUdi() float GPUParam::GetClusterErrorSeeding(int32_t yz, int32_t type, float zDiff, float angle2, float scaledMult) const { @@ -169,7 +169,7 @@ GPUdi() float GPUParam::GetSystematicClusterErrorC122(float trackX, float trackY return 0; } -#endif // !GPUCA_TPC_GEOMETRY_O2 +#endif // GPUCA_RUN2 GPUdi() void GPUParam::GetClusterErrors2(uint8_t sector, int32_t iRow, float z, float sinPhi, float DzDs, float time, float avgInvCharge, float invCharge, float& ErrY2, float& ErrZ2) const { diff --git a/GPU/GPUTracking/Base/GPUProcessor.h b/GPU/GPUTracking/Base/GPUProcessor.h index df551c9f0330d..337ecfc61f79d 100644 --- a/GPU/GPUTracking/Base/GPUProcessor.h +++ b/GPU/GPUTracking/Base/GPUProcessor.h @@ -62,7 +62,7 @@ class GPUProcessor return *(T*)(mGPUProcessorType == PROCESSOR_TYPE_DEVICE ? mLinkedProcessor : this); } - template + template static constexpr inline size_t getAlignmentMod(size_t addr) { static_assert((alignment & (alignment - 1)) == 0, "Invalid alignment, not power of 2"); @@ -71,7 +71,7 @@ class GPUProcessor } return addr & (alignment - 1); } - template + template static constexpr inline size_t getAlignment(size_t addr) { size_t mod = getAlignmentMod(addr); @@ -80,7 +80,7 @@ class GPUProcessor } return (alignment - mod); } - template + template static constexpr inline size_t nextMultipleOf(size_t size) { return size + getAlignment(size); @@ -97,22 +97,22 @@ class GPUProcessor return (size + alignment - 1) & ~(alignment - 1); } } - template + template static inline void* alignPointer(void* ptr) { return (reinterpret_cast(nextMultipleOf(reinterpret_cast(ptr)))); } - template + template static inline size_t getAlignmentMod(void* addr) { return (getAlignmentMod(reinterpret_cast(addr))); } - template + template static inline size_t getAlignment(void* addr) { return (getAlignment(reinterpret_cast(addr))); } - template + template static inline S* getPointerWithAlignment(size_t& basePtr, size_t nEntries = 1) { if (basePtr == 0) { @@ -125,7 +125,7 @@ class GPUProcessor return retVal; } - template + template static inline S* getPointerWithAlignment(void*& basePtr, size_t nEntries = 1) { size_t tmp = (size_t)basePtr; @@ -134,7 +134,7 @@ class GPUProcessor return retVal; } - template + template static inline void computePointerWithAlignment(T*& basePtr, S*& objPtr, size_t nEntries = 1) { size_t tmp = (size_t)basePtr; @@ -145,8 +145,8 @@ class GPUProcessor template static inline void computePointerWithoutAlignment(T*& basePtr, S*& objPtr, size_t nEntries = 1) { - if ((size_t)basePtr < GPUCA_BUFFER_ALIGNMENT) { - basePtr = (T*)GPUCA_BUFFER_ALIGNMENT; + if ((size_t)basePtr < constants::GPU_BUFFER_ALIGNMENT) { + basePtr = (T*)constants::GPU_BUFFER_ALIGNMENT; } size_t tmp = (size_t)basePtr; objPtr = reinterpret_cast(getPointerWithAlignment<1, char>(tmp, nEntries * sizeof(S))); diff --git a/GPU/GPUTracking/Base/GPUReconstruction.cxx b/GPU/GPUTracking/Base/GPUReconstruction.cxx index f6aa62778a061..7eda10cd31521 100644 --- a/GPU/GPUTracking/Base/GPUReconstruction.cxx +++ b/GPU/GPUTracking/Base/GPUReconstruction.cxx @@ -42,7 +42,7 @@ #include "utils/strtag.h" #include "utils/stdspinlock.h" -#ifdef GPUCA_O2_LIB +#ifndef GPUCA_STANDALONE #include "GPUO2InterfaceConfiguration.h" #endif @@ -263,17 +263,17 @@ int32_t GPUReconstruction::InitPhaseBeforeDevice() if (GetProcessingSettings().debugLevel > 0) { mProcessingSettings->recoTaskTiming = true; } - if (GetProcessingSettings().deterministicGPUReconstruction == -1) { + bool detMode = false; #ifdef GPUCA_DETERMINISTIC_MODE - mProcessingSettings->deterministicGPUReconstruction = 1; -#else - mProcessingSettings->deterministicGPUReconstruction = GetProcessingSettings().debugLevel >= 6; + detMode = true; #endif + if (GetProcessingSettings().deterministicGPUReconstruction == -1) { + mProcessingSettings->deterministicGPUReconstruction = detMode ? 1 : (GetProcessingSettings().debugLevel >= 6); } if (GetProcessingSettings().deterministicGPUReconstruction) { -#ifndef GPUCA_DETERMINISTIC_MODE - GPUError("WARNING, deterministicGPUReconstruction needs GPUCA_DETERMINISTIC_MODE for being fully deterministic, without only most indeterminism by concurrency is removed, but floating point effects remain!"); -#endif + if (!detMode) { + GPUError("WARNING, deterministicGPUReconstruction needs GPUCA_DETERMINISTIC_MODE for being fully deterministic, without only most indeterminism by concurrency is removed, but floating point effects remain!"); + } if (mProcessingSettings->debugLevel >= 6 && ((mProcessingSettings->debugMask + 1) & mProcessingSettings->debugMask)) { GPUError("WARNING: debugMask %d - debug output might not be deterministic with intermediate steps missing", mProcessingSettings->debugMask); } @@ -283,9 +283,9 @@ int32_t GPUReconstruction::InitPhaseBeforeDevice() } mProcessingSettings->rtc.deterministic = 1; } else { -#ifdef GPUCA_DETERMINISTIC_MODE - GPUError("WARNING, compiled with GPUCA_DETERMINISTIC_MODE but deterministicGPUReconstruction not set, only compile-time determinism and deterministic math enforced, not fully deterministic!"); -#endif + if (detMode) { + GPUError("WARNING, compiled with GPUCA_DETERMINISTIC_MODE but deterministicGPUReconstruction not set, only compile-time determinism and deterministic math enforced, not fully deterministic!"); + } } if (GetProcessingSettings().deterministicGPUReconstruction && GetProcessingSettings().debugLevel >= 6) { mProcessingSettings->nTPCClustererLanes = 1; @@ -343,14 +343,14 @@ int32_t GPUReconstruction::InitPhaseBeforeDevice() } if (GetProcessingSettings().nTPCClustererLanes == -1) { - mProcessingSettings->nTPCClustererLanes = (GetRecoStepsGPU() & RecoStep::TPCClusterFinding) ? 3 : std::max(1, std::min(GPUCA_NSECTORS, GetProcessingSettings().inKernelParallel ? (mMaxHostThreads >= 4 ? std::min(mMaxHostThreads / 2, mMaxHostThreads >= 32 ? GPUCA_NSECTORS : 4) : 1) : mMaxHostThreads)); + mProcessingSettings->nTPCClustererLanes = (GetRecoStepsGPU() & RecoStep::TPCClusterFinding) ? 3 : std::max(1, std::min(GPUTPCGeometry::NSECTORS, GetProcessingSettings().inKernelParallel ? (mMaxHostThreads >= 4 ? std::min(mMaxHostThreads / 2, mMaxHostThreads >= 32 ? GPUTPCGeometry::NSECTORS : 4) : 1) : mMaxHostThreads)); } if (GetProcessingSettings().overrideClusterizerFragmentLen == -1) { mProcessingSettings->overrideClusterizerFragmentLen = ((GetRecoStepsGPU() & RecoStep::TPCClusterFinding) || (mMaxHostThreads / GetProcessingSettings().nTPCClustererLanes >= 3)) ? TPC_MAX_FRAGMENT_LEN_GPU : TPC_MAX_FRAGMENT_LEN_HOST; } - if (GetProcessingSettings().nTPCClustererLanes > GPUCA_NSECTORS) { + if ((uint32_t)GetProcessingSettings().nTPCClustererLanes > GPUTPCGeometry::NSECTORS) { GPUError("Invalid value for nTPCClustererLanes: %d", GetProcessingSettings().nTPCClustererLanes); - mProcessingSettings->nTPCClustererLanes = GPUCA_NSECTORS; + mProcessingSettings->nTPCClustererLanes = GPUTPCGeometry::NSECTORS; } if (GetProcessingSettings().doublePipeline) { @@ -467,7 +467,7 @@ int32_t GPUReconstruction::Exit() if (mMemoryResources[i].mReuse >= 0) { continue; } - operator delete(mMemoryResources[i].mPtrDevice, std::align_val_t(GPUCA_BUFFER_ALIGNMENT)); + alignedDefaultBufferDeleter()(mMemoryResources[i].mPtrDevice); mMemoryResources[i].mPtr = mMemoryResources[i].mPtrDevice = nullptr; } } @@ -597,11 +597,11 @@ size_t GPUReconstruction::AllocateRegisteredMemoryHelper(GPUMemoryResource* res, stdspinlock spinlock(mMemoryMutex); if ((res->mType & GPUMemoryResource::MEMORY_STACK) && memorypoolend) { retVal = ptrDiff((res->*setPtr)((char*)1), (char*)(1)); - memorypoolend = (void*)((char*)memorypoolend - GPUProcessor::getAlignmentMod(memorypoolend)); + memorypoolend = (void*)((char*)memorypoolend - GPUProcessor::getAlignmentMod(memorypoolend)); if (retVal < res->mOverrideSize) { retVal = res->mOverrideSize; } - retVal += GPUProcessor::getAlignment(retVal); + retVal += GPUProcessor::getAlignment(retVal); memorypoolend = (char*)memorypoolend - retVal; ptr = memorypoolend; retVal = std::max(ptrDiff((res->*setPtr)(ptr), ptr), res->mOverrideSize); @@ -613,7 +613,7 @@ size_t GPUReconstruction::AllocateRegisteredMemoryHelper(GPUMemoryResource* res, retVal = res->mOverrideSize; memorypool = (char*)ptr + res->mOverrideSize; } - memorypool = (void*)((char*)memorypool + GPUProcessor::getAlignment(memorypool)); + memorypool = (void*)((char*)memorypool + GPUProcessor::getAlignment(memorypool)); } if (memorypoolend ? (memorypool > memorypoolend) : ((size_t)ptrDiff(memorypool, memorybase) > memorysize)) { std::cerr << "Memory pool size exceeded (" << device << ") (" << res->mName << ": " << (memorypoolend ? (memorysize + ptrDiff(memorypool, memorypoolend)) : ptrDiff(memorypool, memorybase)) << " > " << memorysize << "\n"; @@ -630,7 +630,7 @@ void GPUReconstruction::AllocateRegisteredMemoryInternal(GPUMemoryResource* res, if (GetProcessingSettings().memoryAllocationStrategy == GPUMemoryResource::ALLOCATION_INDIVIDUAL && (control == nullptr || control->useInternal())) { if (!(res->mType & GPUMemoryResource::MEMORY_EXTERNAL)) { if (res->mPtrDevice && res->mReuse < 0) { - operator delete(res->mPtrDevice, std::align_val_t(GPUCA_BUFFER_ALIGNMENT)); + alignedDefaultBufferDeleter()(res->mPtrDevice); } res->mSize = std::max((size_t)res->SetPointers((void*)1) - 1, res->mOverrideSize); if (res->mReuse >= 0) { @@ -640,9 +640,9 @@ void GPUReconstruction::AllocateRegisteredMemoryInternal(GPUMemoryResource* res, } res->mPtrDevice = mMemoryResources[res->mReuse].mPtrDevice; } else { - res->mPtrDevice = operator new(res->mSize + GPUCA_BUFFER_ALIGNMENT, std::align_val_t(GPUCA_BUFFER_ALIGNMENT)); + res->mPtrDevice = alignedDefaultBufferAllocator(res->mSize + constants::GPU_BUFFER_ALIGNMENT); } - res->mPtr = GPUProcessor::alignPointer(res->mPtrDevice); + res->mPtr = GPUProcessor::alignPointer(res->mPtrDevice); res->SetPointers(res->mPtr); if (GetProcessingSettings().allocDebugLevel >= 2) { std::cout << (res->mReuse >= 0 ? "Reused " : "Allocated ") << res->mName << ": " << res->mSize << " (individual" << ((res->mType & GPUMemoryResource::MEMORY_STACK) ? " stack" : "") << ")\n"; @@ -651,7 +651,7 @@ void GPUReconstruction::AllocateRegisteredMemoryInternal(GPUMemoryResource* res, stdspinlock spinlock(mMemoryMutex); mNonPersistentIndividualAllocations.emplace_back(res); } - if ((size_t)res->mPtr % GPUCA_BUFFER_ALIGNMENT) { + if ((size_t)res->mPtr % constants::GPU_BUFFER_ALIGNMENT) { GPUError("Got buffer with insufficient alignment"); throw std::bad_alloc(); } @@ -661,14 +661,14 @@ void GPUReconstruction::AllocateRegisteredMemoryInternal(GPUMemoryResource* res, GPUError("Double allocation! (%s)", res->mName); throw std::bad_alloc(); } - if (IsGPU() && res->mOverrideSize < GPUCA_BUFFER_ALIGNMENT) { - res->mOverrideSize = GPUCA_BUFFER_ALIGNMENT; + if (IsGPU() && res->mOverrideSize < constants::GPU_BUFFER_ALIGNMENT) { + res->mOverrideSize = constants::GPU_BUFFER_ALIGNMENT; } if ((!IsGPU() || (res->mType & GPUMemoryResource::MEMORY_HOST) || GetProcessingSettings().keepDisplayMemory) && !(res->mType & GPUMemoryResource::MEMORY_EXTERNAL)) { // keepAllMemory --> keepDisplayMemory if (control && control->useExternal()) { if (control->allocator) { res->mSize = std::max((size_t)res->SetPointers((void*)1) - 1, res->mOverrideSize); - res->mPtr = control->allocator(CAMath::nextMultipleOf(res->mSize)); + res->mPtr = control->allocator(CAMath::nextMultipleOf(res->mSize)); res->mSize = std::max(ptrDiff(res->SetPointers(res->mPtr), res->mPtr), res->mOverrideSize); if (GetProcessingSettings().allocDebugLevel >= 2) { std::cout << "Allocated (from callback) " << res->mName << ": " << res->mSize << "\n"; @@ -680,7 +680,7 @@ void GPUReconstruction::AllocateRegisteredMemoryInternal(GPUMemoryResource* res, } else { res->mSize = AllocateRegisteredMemoryHelper(res, res->mPtr, recPool->mHostMemoryPool, recPool->mHostMemoryBase, recPool->mHostMemorySize, &GPUMemoryResource::SetPointers, recPool->mHostMemoryPoolEnd, "host"); } - if ((size_t)res->mPtr % GPUCA_BUFFER_ALIGNMENT) { + if ((size_t)res->mPtr % constants::GPU_BUFFER_ALIGNMENT) { GPUError("Got buffer with insufficient alignment"); throw std::bad_alloc(); } @@ -702,7 +702,7 @@ void GPUReconstruction::AllocateRegisteredMemoryInternal(GPUMemoryResource* res, GPUError("Inconsistent device memory allocation (%s: device %lu vs %lu)", res->mName, size, res->mSize); throw std::bad_alloc(); } - if ((size_t)res->mPtrDevice % GPUCA_BUFFER_ALIGNMENT) { + if ((size_t)res->mPtrDevice % constants::GPU_BUFFER_ALIGNMENT) { GPUError("Got buffer with insufficient alignment"); throw std::bad_alloc(); } @@ -731,11 +731,11 @@ void* GPUReconstruction::AllocateDirectMemory(size_t size, int32_t type) { stdspinlock spinlock(mMemoryMutex); if (GetProcessingSettings().memoryAllocationStrategy == GPUMemoryResource::ALLOCATION_INDIVIDUAL) { - char* retVal = new (std::align_val_t(GPUCA_BUFFER_ALIGNMENT)) char[size]; + char* retVal = alignedDefaultBufferAllocator(size); if ((type & GPUMemoryResource::MEMORY_STACK)) { - mNonPersistentIndividualDirectAllocations.emplace_back(retVal, alignedDeleter()); + mNonPersistentIndividualDirectAllocations.emplace_back(retVal, alignedDefaultBufferDeleter()); } else { - mDirectMemoryChunks.emplace_back(retVal, alignedDeleter()); + mDirectMemoryChunks.emplace_back(retVal, alignedDefaultBufferDeleter()); } return retVal; } @@ -753,7 +753,7 @@ void* GPUReconstruction::AllocateDirectMemory(size_t size, int32_t type) char* retVal; if ((type & GPUMemoryResource::MEMORY_STACK)) { poolend = (char*)poolend - size; // TODO: Implement overflow check - poolend = (char*)poolend - GPUProcessor::getAlignmentMod(poolend); + poolend = (char*)poolend - GPUProcessor::getAlignmentMod(poolend); retVal = (char*)poolend; } else { GPUProcessor::computePointerWithAlignment(pool, retVal, size); @@ -796,9 +796,9 @@ void* GPUReconstruction::AllocateVolatileMemory(size_t size, bool device) if (device) { return AllocateVolatileDeviceMemory(size); } - char* retVal = new (std::align_val_t(GPUCA_BUFFER_ALIGNMENT)) char[size]; + char* retVal = alignedDefaultBufferAllocator(size); stdspinlock spinlock(mMemoryMutex); - mVolatileChunks.emplace_back(retVal, alignedDeleter()); + mVolatileChunks.emplace_back(retVal, alignedDefaultBufferDeleter()); return retVal; } @@ -876,7 +876,7 @@ void GPUReconstruction::FreeRegisteredMemory(GPUMemoryResource* res) std::cout << "Freeing " << res->mName << ": size " << res->mSize << " (reused " << res->mReuse << ")\n"; } if (GetProcessingSettings().memoryAllocationStrategy == GPUMemoryResource::ALLOCATION_INDIVIDUAL && res->mReuse < 0) { - operator delete(res->mPtrDevice, std::align_val_t(GPUCA_BUFFER_ALIGNMENT)); + alignedDefaultBufferDeleter()(res->mPtrDevice); } res->mPtr = nullptr; res->mPtrDevice = nullptr; @@ -916,7 +916,7 @@ void GPUReconstruction::PopNonPersistentMemory(RecoStep step, uint64_t tag, cons std::cout << "Freeing NonPersistent " << res->mName << ": size " << res->mSize << " (reused " << res->mReuse << ")\n"; } if (res->mReuse < 0) { - operator delete(res->mPtrDevice, std::align_val_t(GPUCA_BUFFER_ALIGNMENT)); + alignedDefaultBufferDeleter()(res->mPtrDevice); } res->mPtr = nullptr; res->mPtrDevice = nullptr; @@ -970,8 +970,8 @@ void GPUReconstruction::ClearAllocatedMemory(bool clearOutputs) mVolatileChunks.clear(); mVolatileMemoryStart = nullptr; if (GetProcessingSettings().memoryAllocationStrategy == GPUMemoryResource::ALLOCATION_GLOBAL) { - mHostMemoryPool = GPUProcessor::alignPointer(mHostMemoryPermanent); - mDeviceMemoryPool = GPUProcessor::alignPointer(mDeviceMemoryPermanent); + mHostMemoryPool = GPUProcessor::alignPointer(mHostMemoryPermanent); + mDeviceMemoryPool = GPUProcessor::alignPointer(mDeviceMemoryPermanent); mHostMemoryPoolEnd = mHostMemoryPoolBlocked ? mHostMemoryPoolBlocked : ((char*)mHostMemoryBase + mHostMemorySize); mDeviceMemoryPoolEnd = mDeviceMemoryPoolBlocked ? mDeviceMemoryPoolBlocked : ((char*)mDeviceMemoryBase + mDeviceMemorySize); } else { @@ -1237,7 +1237,9 @@ int32_t GPUReconstruction::ReadSettings(const char* dir) f = dir; f += "settings.dump"; new (mGRPSettings.get()) GPUSettingsGRP; - if (ReadStructFromFile(f.c_str(), mGRPSettings.get())) { + bool error; + ReadStructFromFile(f.c_str(), mGRPSettings.get(), &error, true); + if (error) { return 1; } param().UpdateSettings(mGRPSettings.get()); @@ -1249,15 +1251,15 @@ int32_t GPUReconstruction::ReadSettings(const char* dir) void GPUReconstruction::SetSettings(float solenoidBzNominalGPU, const GPURecoStepConfiguration* workflow) { -#ifdef GPUCA_O2_LIB +#ifdef GPUCA_STANDALONE + GPUSettingsGRP grp; + grp.solenoidBzNominalGPU = solenoidBzNominalGPU; + SetSettings(&grp, nullptr, nullptr, workflow); +#else GPUO2InterfaceConfiguration config; config.ReadConfigurableParam(config); config.configGRP.solenoidBzNominalGPU = solenoidBzNominalGPU; SetSettings(&config.configGRP, &config.configReconstruction, &config.configProcessing, workflow); -#else - GPUSettingsGRP grp; - grp.solenoidBzNominalGPU = solenoidBzNominalGPU; - SetSettings(&grp, nullptr, nullptr, workflow); #endif } diff --git a/GPU/GPUTracking/Base/GPUReconstruction.h b/GPU/GPUTracking/Base/GPUReconstruction.h index 9a337c02ad26d..4479eb696808e 100644 --- a/GPU/GPUTracking/Base/GPUReconstruction.h +++ b/GPU/GPUTracking/Base/GPUReconstruction.h @@ -32,6 +32,7 @@ #include "GPUOutputControl.h" #include "GPUParam.h" #include "GPUConstantMem.h" +#include "GPUCommonAlignedAlloc.h" #include "GPUDef.h" namespace o2::its @@ -81,7 +82,7 @@ class GPUReconstruction GPUReconstruction& operator=(const GPUReconstruction&) = delete; // General definitions - constexpr static uint32_t NSECTORS = GPUCA_NSECTORS; + constexpr static uint32_t NSECTORS = GPUTPCGeometry::NSECTORS; using GeometryType = gpudatatypes::GeometryType; using DeviceType = gpudatatypes::DeviceType; @@ -91,10 +92,10 @@ class GPUReconstruction using InOutTypeField = gpudatatypes::InOutTypeField; static constexpr const char* const GEOMETRY_TYPE_NAMES[] = {"INVALID", "ALIROOT", "O2"}; -#ifdef GPUCA_TPC_GEOMETRY_O2 - static constexpr GeometryType geometryType = GeometryType::O2; -#else +#ifdef GPUCA_RUN2 static constexpr GeometryType geometryType = GeometryType::ALIROOT; +#else + static constexpr GeometryType geometryType = GeometryType::O2; #endif static DeviceType GetDeviceType(const char* type); @@ -252,6 +253,13 @@ class GPUReconstruction static int32_t getHostThreadIndex(); int32_t GetMaxBackendThreads() const { return mMaxBackendThreads; } + using alignedDefaultBufferDeleter = alignedDeleter; + template + static T* alignedDefaultBufferAllocator(size_t n) + { + return alignedAllocator::allocate(n); // Note that char is correct, since the buffer is a char buffer + } + protected: void AllocateRegisteredMemoryInternal(GPUMemoryResource* res, GPUOutputControl* control, GPUReconstruction* recPool); void FreeRegisteredMemory(GPUMemoryResource* res); @@ -299,9 +307,11 @@ class GPUReconstruction template void DumpStructToFile(const T* obj, const char* file); template - std::unique_ptr ReadStructFromFile(const char* file); + void DumpDynamicStructToFile(const T* obj, size_t dynamicSize, const char* file); template - int32_t ReadStructFromFile(const char* file, T* obj); + std::unique_ptr ReadStructFromFile(const char* file, T* obj = nullptr, bool* errorOnMissing = nullptr, bool allowSmaller = false); + template + aligned_unique_buffer_ptr ReadDynamicStructFromFile(const char* file); // Others virtual RecoStepField AvailableGPURecoSteps() { return RecoStep::AllRecoSteps; } @@ -381,15 +391,12 @@ class GPUReconstruction GPUProcessor* proc = nullptr; std::vector res; }; - struct alignedDeleter { - void operator()(void* ptr) { ::operator delete[](ptr, std::align_val_t(GPUCA_BUFFER_ALIGNMENT)); }; - }; std::unordered_map mMemoryReuse1to1; std::vector> mNonPersistentMemoryStack; // hostPoolAddress, devicePoolAddress, individualAllocationCount, directIndividualAllocationCound, tag std::vector mNonPersistentIndividualAllocations; - std::vector> mNonPersistentIndividualDirectAllocations; - std::vector> mDirectMemoryChunks; - std::vector> mVolatileChunks; + std::vector> mNonPersistentIndividualDirectAllocations; + std::vector> mDirectMemoryChunks; + std::vector> mVolatileChunks; std::atomic_flag mMemoryMutex = ATOMIC_FLAG_INIT; std::unique_ptr mPipelineContext; diff --git a/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx b/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx index 409c28b8bf328..9fbe9e1171af3 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx +++ b/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx @@ -35,6 +35,7 @@ #include #include +#include #ifndef _WIN32 #include @@ -188,7 +189,7 @@ int32_t GPUReconstructionCPU::InitDevice() if (mDeviceMemorySize > mHostMemorySize) { mHostMemorySize = mDeviceMemorySize; } - mHostMemoryBase = operator new(mHostMemorySize, std::align_val_t(GPUCA_BUFFER_ALIGNMENT)); + mHostMemoryBase = alignedDefaultBufferAllocator(mHostMemorySize); } mHostMemoryPermanent = mHostMemoryBase; ClearAllocatedMemory(); @@ -204,7 +205,7 @@ int32_t GPUReconstructionCPU::ExitDevice() { if (GetProcessingSettings().memoryAllocationStrategy == GPUMemoryResource::ALLOCATION_GLOBAL) { if (mMaster == nullptr) { - operator delete(mHostMemoryBase, std::align_val_t(GPUCA_BUFFER_ALIGNMENT)); + alignedDefaultBufferDeleter()(mHostMemoryBase); } mHostMemoryPool = mHostMemoryBase = mHostMemoryPoolEnd = mHostMemoryPermanent = nullptr; mHostMemorySize = 0; @@ -230,6 +231,7 @@ int32_t GPUReconstructionCPU::RunChains() GPUInfo("Allocated memory when starting processing %34s", ""); PrintMemoryOverview(); } + mTimerTotal.Start(); const std::clock_t cpuTimerStart = std::clock(); int32_t retVal = 0; @@ -264,7 +266,10 @@ int32_t GPUReconstructionCPU::RunChains() double kernelTotal = 0; std::vector kernelStepTimes(gpudatatypes::N_RECO_STEPS, 0.); + debugWriter writer(GetProcessingSettings().debugCSV, GetProcessingSettings().debugMarkdown, mStatNEvents); + if (GetProcessingSettings().debugLevel >= 1) { + writer.header(); for (uint32_t i = 0; i < mTimers.size(); i++) { double time = 0; if (mTimers[i] == nullptr) { @@ -284,11 +289,7 @@ int32_t GPUReconstructionCPU::RunChains() int32_t stepNum = getRecoStepNum(mTimers[i]->step); kernelStepTimes[stepNum] += time; } - char bandwidth[256] = ""; - if (mTimers[i]->memSize && mStatNEvents && time != 0.) { - snprintf(bandwidth, 256, " (%8.3f GB/s - %'14zu bytes - %'14zu per call)", mTimers[i]->memSize / time * 1e-9, mTimers[i]->memSize / mStatNEvents, mTimers[i]->memSize / mStatNEvents / mTimers[i]->count); - } - printf("Execution Time: Task (%c %8ux): %50s Time: %'10.0f us%s\n", type == 0 ? 'K' : 'C', mTimers[i]->count, mTimers[i]->name.c_str(), time * 1000000 / mStatNEvents, bandwidth); + writer.row('K', mTimers[i]->count, mTimers[i]->name.c_str(), time, -1.0, -1.0, mTimers[i]->memSize); if (GetProcessingSettings().resetTimers) { mTimers[i]->count = 0; mTimers[i]->memSize = 0; @@ -298,16 +299,13 @@ int32_t GPUReconstructionCPU::RunChains() if (GetProcessingSettings().recoTaskTiming) { for (int32_t i = 0; i < gpudatatypes::N_RECO_STEPS; i++) { if (kernelStepTimes[i] != 0. || mTimersRecoSteps[i].timerTotal.GetElapsedTime() != 0.) { - printf("Execution Time: Step : %11s %38s Time: %'10.0f us %64s ( Total Time : %'14.0f us, CPU Time : %'14.0f us, %'7.2fx )\n", "Tasks", - gpudatatypes::RECO_STEP_NAMES[i], kernelStepTimes[i] * 1000000 / mStatNEvents, "", mTimersRecoSteps[i].timerTotal.GetElapsedTime() * 1000000 / mStatNEvents, mTimersRecoSteps[i].timerCPU * 1000000 / mStatNEvents, mTimersRecoSteps[i].timerCPU / mTimersRecoSteps[i].timerTotal.GetElapsedTime()); + writer.row(' ', 0, std::string(gpudatatypes::RECO_STEP_NAMES[i]) + " (Tasks)", kernelStepTimes[i], mTimersRecoSteps[i].timerCPU, mTimersRecoSteps[i].timerTotal.GetElapsedTime(), 0); } if (mTimersRecoSteps[i].bytesToGPU) { - printf("Execution Time: Step (D %8ux): %11s %38s Time: %'10.0f us (%8.3f GB/s - %'14zu bytes - %'14zu per call)\n", mTimersRecoSteps[i].countToGPU, "DMA to GPU", gpudatatypes::RECO_STEP_NAMES[i], mTimersRecoSteps[i].timerToGPU.GetElapsedTime() * 1000000 / mStatNEvents, - mTimersRecoSteps[i].bytesToGPU / mTimersRecoSteps[i].timerToGPU.GetElapsedTime() * 1e-9, mTimersRecoSteps[i].bytesToGPU / mStatNEvents, mTimersRecoSteps[i].bytesToGPU / mTimersRecoSteps[i].countToGPU); + writer.row('D', mTimersRecoSteps[i].countToGPU, std::string(gpudatatypes::RECO_STEP_NAMES[i]) + " (DMA to GPU)", mTimersRecoSteps[i].timerToGPU.GetElapsedTime(), -1.0, -1.0, mTimersRecoSteps[i].bytesToGPU); } if (mTimersRecoSteps[i].bytesToHost) { - printf("Execution Time: Step (D %8ux): %11s %38s Time: %'10.0f us (%8.3f GB/s - %'14zu bytes - %'14zu per call)\n", mTimersRecoSteps[i].countToHost, "DMA to Host", gpudatatypes::RECO_STEP_NAMES[i], mTimersRecoSteps[i].timerToHost.GetElapsedTime() * 1000000 / mStatNEvents, - mTimersRecoSteps[i].bytesToHost / mTimersRecoSteps[i].timerToHost.GetElapsedTime() * 1e-9, mTimersRecoSteps[i].bytesToHost / mStatNEvents, mTimersRecoSteps[i].bytesToHost / mTimersRecoSteps[i].countToHost); + writer.row('D', mTimersRecoSteps[i].countToHost, std::string(gpudatatypes::RECO_STEP_NAMES[i]) + " (DMA to Host)", mTimersRecoSteps[i].timerToHost.GetElapsedTime(), -1.0, -1.0, mTimersRecoSteps[i].bytesToHost); } if (GetProcessingSettings().resetTimers) { mTimersRecoSteps[i].bytesToGPU = mTimersRecoSteps[i].bytesToHost = 0; @@ -321,14 +319,11 @@ int32_t GPUReconstructionCPU::RunChains() } for (int32_t i = 0; i < gpudatatypes::N_GENERAL_STEPS; i++) { if (mTimersGeneralSteps[i].GetElapsedTime() != 0.) { - printf("Execution Time: General Step : %50s Time: %'10.0f us\n", gpudatatypes::GENERAL_STEP_NAMES[i], mTimersGeneralSteps[i].GetElapsedTime() * 1000000 / mStatNEvents); + writer.row(' ', 0, gpudatatypes::GENERAL_STEP_NAMES[i], mTimersGeneralSteps[i].GetElapsedTime(), -1.0, -1.0, 0); } } - if (GetProcessingSettings().debugLevel >= 1) { - mStatKernelTime = kernelTotal * 1000000 / mStatNEvents; - printf("Execution Time: Total : %50s Time: %'10.0f us%s\n", "Total Kernel", mStatKernelTime, nEventReport.c_str()); - } - printf("Execution Time: Total : %50s Time: %'10.0f us ( CPU Time : %'10.0f us, %7.2fx ) %s\n", "Total Wall", mStatWallTime, mStatCPUTime * 1000000 / mStatNEvents, mStatCPUTime / mTimerTotal.GetElapsedTime(), nEventReport.c_str()); + double gpu_time = GetProcessingSettings().debugLevel >= 1 ? kernelTotal : -1.0; + writer.row(' ', 0, "Wall", gpu_time, mStatCPUTime, mTimerTotal.GetElapsedTime(), 0, nEventReport); } else if (GetProcessingSettings().debugLevel >= 0) { GPUInfo("Total Wall Time: %10.0f us%s", mStatWallTime, nEventReport.c_str()); } diff --git a/GPU/GPUTracking/Base/GPUReconstructionCPU.h b/GPU/GPUTracking/Base/GPUReconstructionCPU.h index d621d45fcd92b..466ad318bbe3b 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionCPU.h +++ b/GPU/GPUTracking/Base/GPUReconstructionCPU.h @@ -16,7 +16,9 @@ #define GPURECONSTRUCTIONICPU_H #include "GPUReconstructionProcessing.h" +#include #include +#include #include namespace Ort @@ -100,6 +102,17 @@ class GPUReconstructionCPU : public GPUReconstructionProcessing::KernelInterface size_t TransferMemoryResourcesHelper(GPUProcessor* proc, int32_t stream, bool all, bool toGPU); template void runKernelInterface(krnlSetup&& setup, Args const&... args); + + struct debugWriter { + debugWriter(std::string filenameCSV, bool markdown, uint32_t statNEvents); + void header(); + void row(char type, uint32_t count, std::string name, double gpu_time, double cpu_time, double total_time, std::size_t memSize, std::string nEventReport = ""); + + private: + std::ofstream streamCSV; + bool mMarkdown; + uint32_t mStatNEvents; + }; }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/Base/GPUReconstructionCPUKernels.h b/GPU/GPUTracking/Base/GPUReconstructionCPUKernels.h index 0c19941c40ea4..7970fbe753ec8 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionCPUKernels.h +++ b/GPU/GPUTracking/Base/GPUReconstructionCPUKernels.h @@ -49,8 +49,8 @@ inline void GPUReconstructionCPU::runKernelInterface(krnlSetup&& setup, Args con } else if ((int32_t)nThreads < 0) { nThreads = cpuFallback ? 1 : -nThreads; } - if (nThreads > GPUCA_MAX_THREADS) { - throw std::runtime_error("GPUCA_MAX_THREADS exceeded"); + if (nThreads > constants::GPU_MAX_THREADS) { + throw std::runtime_error("GPU_MAX_THREADS exceeded"); } if (GetProcessingSettings().debugLevel >= 3) { GPUInfo("Running kernel %s (Stream %d, Index %d, Grid %d/%d) on %s", GetKernelName(), stream, setup.y.index, nBlocks, nThreads, cpuFallback == 2 ? "CPU (forced)" : (cpuFallback ? "CPU (fallback)" : mDeviceName.c_str())); diff --git a/GPU/GPUTracking/Base/GPUReconstructionConvert.cxx b/GPU/GPUTracking/Base/GPUReconstructionConvert.cxx index a4b17b81bf5ac..9ec1af55a7a62 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionConvert.cxx +++ b/GPU/GPUTracking/Base/GPUReconstructionConvert.cxx @@ -12,7 +12,7 @@ /// \file GPUReconstructionConvert.cxx /// \author David Rohr -#ifdef GPUCA_O2_LIB +#ifndef GPUCA_STANDALONE #include "DetectorsRaw/RawFileWriter.h" #include "TPCBase/Sector.h" #include "DataFormatsTPC/Digit.h" @@ -20,7 +20,7 @@ #endif #include "GPUReconstructionConvert.h" -#include "TPCFastTransform.h" +#include "TPCFastTransformPOD.h" #include "GPUTPCClusterData.h" #include "GPUO2DataTypes.h" #include "GPUDataTypesIO.h" @@ -48,19 +48,19 @@ using namespace o2::tpc; using namespace o2::tpc::constants; using namespace std::string_literals; -void GPUReconstructionConvert::ConvertNativeToClusterData(o2::tpc::ClusterNativeAccess* native, std::unique_ptr* clusters, uint32_t* nClusters, const TPCFastTransform* transform, int32_t continuousMaxTimeBin) +void GPUReconstructionConvert::ConvertNativeToClusterData(o2::tpc::ClusterNativeAccess* native, std::unique_ptr* clusters, uint32_t* nClusters, const TPCFastTransformPOD* transform, int32_t continuousMaxTimeBin) { memset(nClusters, 0, NSECTORS * sizeof(nClusters[0])); uint32_t offset = 0; for (uint32_t i = 0; i < NSECTORS; i++) { uint32_t nClSector = 0; - for (int32_t j = 0; j < GPUCA_ROW_COUNT; j++) { + for (uint32_t j = 0; j < GPUTPCGeometry::NROWS; j++) { nClSector += native->nClusters[i][j]; } nClusters[i] = nClSector; clusters[i].reset(new GPUTPCClusterData[nClSector]); nClSector = 0; - for (int32_t j = 0; j < GPUCA_ROW_COUNT; j++) { + for (uint32_t j = 0; j < GPUTPCGeometry::NROWS; j++) { for (uint32_t k = 0; k < native->nClusters[i][j]; k++) { const auto& clin = native->clusters[i][j][k]; float x = 0, y = 0, z = 0; @@ -98,7 +98,7 @@ void GPUReconstructionConvert::ConvertRun2RawToNative(o2::tpc::ClusterNativeAcce native.clustersLinear = nativeBuffer.get(); native.setOffsetPtrs(); for (uint32_t i = 0; i < NSECTORS; i++) { - for (uint32_t j = 0; j < GPUCA_ROW_COUNT; j++) { + for (uint32_t j = 0; j < GPUTPCGeometry::NROWS; j++) { native.nClusters[i][j] = 0; } for (uint32_t j = 0; j < nRawClusters[i]; j++) { @@ -119,7 +119,7 @@ int32_t GPUReconstructionConvert::GetMaxTimeBin(const ClusterNativeAccess& nativ { float retVal = 0; for (uint32_t i = 0; i < NSECTORS; i++) { - for (uint32_t j = 0; j < GPUCA_ROW_COUNT; j++) { + for (uint32_t j = 0; j < GPUTPCGeometry::NROWS; j++) { for (uint32_t k = 0; k < native.nClusters[i][j]; k++) { if (native.clusters[i][j][k].getTime() > retVal) { retVal = native.clusters[i][j][k].getTime(); @@ -174,7 +174,7 @@ int32_t GPUReconstructionConvert::GetMaxTimeBin(const GPUTrackingInOutZS& zspage // ------------------------------------------------- TPC ZS ------------------------------------------------- -#ifdef GPUCA_TPC_GEOMETRY_O2 +#ifndef GPUCA_RUN2 namespace o2::gpu { namespace // anonymous @@ -193,7 +193,7 @@ struct zsEncoder { const o2::InteractionRecord* ir = nullptr; const GPUParam* param = nullptr; bool padding = false; - int32_t lastEndpoint = -2, lastTime = -1, lastRow = GPUCA_ROW_COUNT; + int32_t lastEndpoint = -2, lastTime = -1, lastRow = GPUTPCGeometry::NROWS; int32_t endpoint = 0, outputEndpoint = 0; int64_t hbf = -1, nexthbf = 0; zsPage* page = nullptr; @@ -244,11 +244,11 @@ inline void zsEncoder::ZSstreamOut(uint16_t* bufIn, uint32_t& lenIn, uint8_t* bu static inline auto ZSEncoderGetDigits(const GPUTrackingInOutDigits& in, int32_t i) { return in.tpcDigits[i]; } static inline auto ZSEncoderGetNDigits(const GPUTrackingInOutDigits& in, int32_t i) { return in.nTPCDigits[i]; } -#ifdef GPUCA_O2_LIB +#ifndef GPUCA_STANDALONE using DigitArray = std::array, o2::tpc::Sector::MAXSECTOR>; static inline auto ZSEncoderGetDigits(const DigitArray& in, int32_t i) { return in[i].data(); } static inline auto ZSEncoderGetNDigits(const DigitArray& in, int32_t i) { return in[i].size(); } -#endif // GPUCA_O2_LIB +#endif // ------------------------------------------------- TPC ZS Original Row-based ZS ------------------------------------------------- @@ -364,7 +364,7 @@ uint32_t zsEncoderRow::encodeSequence(std::vector& tmpBuffer, ui curTBHdr = reinterpret_cast(pagePtr); curTBHdr->rowMask |= (endpoint & 1) << 15; nRowsInTB = 0; - lastRow = GPUCA_ROW_COUNT; + lastRow = GPUTPCGeometry::NROWS; } if (tmpBuffer[k].getRow() != lastRow) { curTBHdr->rowMask |= 1 << (tmpBuffer[k].getRow() - endpointStart); @@ -460,7 +460,7 @@ void zsEncoderRow::decodePage(std::vector& outputBuffer, const z // ------------------------------------------------- TPC ZS Link Based ZS ------------------------------------------------- -#ifdef GPUCA_O2_LIB +#ifndef GPUCA_STANDALONE struct zsEncoderLinkBased : public zsEncoder { TPCZSHDRV2* hdr = nullptr; TPCZSHDRV2 hdrBuffer; @@ -1045,7 +1045,7 @@ void zsEncoderDenseLinkBased::amendPageErrorMessage(std::ostringstream& oss, con oss << "Meta header of page: " << dumpBuffer << "\n"; } -#endif // GPUCA_O2_LIB +#endif // !GPUCA_STANDALONE // ------------------------------------------------- TPC ZS Main Encoder ------------------------------------------------- @@ -1095,7 +1095,7 @@ inline uint32_t zsEncoderRun::run(std::vector* buffer, std::vector::run(std::vector* buffer, std::vector && (padding || lastEndpoint == -1 || hbf == nexthbf) ? TPCZSHDR::TPC_ZS_PAGE_SIZE : (pagePtr - (uint8_t*)page); size = CAMath::nextMultipleOf(size); -#ifdef GPUCA_O2_LIB +#ifndef GPUCA_STANDALONE if (raw) { raw->addData(rawfeeid, rawcru, 0, rawendpoint, *ir + hbf * o2::constants::lhc::LHCMaxBunches, gsl::span((char*)page + sizeof(o2::header::RAWDataHeader), (char*)page + size), true, 0, 2); maxhbf = std::max(maxhbf, hbf); @@ -1257,7 +1257,7 @@ inline uint32_t zsEncoderRun::run(std::vector* buffer, std::vectoraddData(46208, 360, rdh_utils::SACLinkID, 0, *ir + i * o2::constants::lhc::LHCMaxBunches, gsl::span((char*)&singleBuffer, (char*)&singleBuffer), true, 0, 4); @@ -1310,7 +1310,7 @@ size_t zsEncoderRun::compare(std::vector* buffer, std::vector void GPUReconstructionConvert::RunZSEncoder(const S& in, std::unique_ptr* outBuffer, uint32_t* outSizes, o2::raw::RawFileWriter* raw, const o2::InteractionRecord* ir, const GPUParam& param, int32_t version, bool verify, float threshold, bool padding, std::function&)> digitsFilter) @@ -1320,7 +1320,7 @@ void GPUReconstructionConvert::RunZSEncoder(const S& in, std::unique_ptr buffer[NSECTORS][GPUTrackingInOutZS::NENDPOINTS]; struct tmpReductionResult { uint32_t totalPages = 0; @@ -1365,7 +1365,7 @@ void GPUReconstructionConvert::RunZSEncoder(const S& in, std::unique_ptr enc{{{.iSector = i, .raw = raw, .ir = ir, .param = ¶m, .padding = padding}}}; runZS(enc); } else if (version >= ZSVersion::ZSVersionLinkBasedWithMeta && version <= ZSVersion::ZSVersionDenseLinkBasedV2) { -#ifdef GPUCA_O2_LIB +#ifndef GPUCA_STANDALONE if (version == ZSVersion::ZSVersionLinkBasedWithMeta) { zsEncoderRun enc{{{{.iSector = i, .raw = raw, .ir = ir, .param = ¶m, .padding = padding}}}}; runZS(enc); @@ -1410,7 +1410,7 @@ void GPUReconstructionConvert::RunZSEncoder(const S& in, std::unique_ptr(const GPUTrackingInOutDigits&, std::unique_ptr*, uint32_t*, o2::raw::RawFileWriter*, const o2::InteractionRecord*, const GPUParam&, int32_t, bool, float, bool, std::function&)> digitsFilter); -#ifdef GPUCA_O2_LIB +#ifndef GPUCA_STANDALONE template void GPUReconstructionConvert::RunZSEncoder(const DigitArray&, std::unique_ptr*, uint32_t*, o2::raw::RawFileWriter*, const o2::InteractionRecord*, const GPUParam&, int32_t, bool, float, bool, std::function&)> digitsFilter); #endif @@ -1454,7 +1454,7 @@ void GPUReconstructionConvert::RunZSFilter(std::unique_ptr* bu } } -#ifdef GPUCA_O2_LIB +#ifndef GPUCA_STANDALONE namespace o2::gpu::internal { template diff --git a/GPU/GPUTracking/Base/GPUReconstructionConvert.h b/GPU/GPUTracking/Base/GPUReconstructionConvert.h index a24eb52a3a47c..17958303103a0 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionConvert.h +++ b/GPU/GPUTracking/Base/GPUReconstructionConvert.h @@ -19,6 +19,7 @@ #include #include #include "GPUDef.h" +#include "DataFormatsTPC/Constants.h" namespace o2 { @@ -41,15 +42,15 @@ namespace o2::gpu { struct GPUParam; struct GPUTPCClusterData; -class TPCFastTransform; +class TPCFastTransformPOD; struct GPUTrackingInOutDigits; struct GPUTrackingInOutZS; class GPUReconstructionConvert { public: - constexpr static uint32_t NSECTORS = GPUCA_NSECTORS; - static void ConvertNativeToClusterData(o2::tpc::ClusterNativeAccess* native, std::unique_ptr* clusters, uint32_t* nClusters, const TPCFastTransform* transform, int32_t continuousMaxTimeBin = 0); + constexpr static uint32_t NSECTORS = o2::tpc::constants::MAXSECTOR; + static void ConvertNativeToClusterData(o2::tpc::ClusterNativeAccess* native, std::unique_ptr* clusters, uint32_t* nClusters, const TPCFastTransformPOD* transform, int32_t continuousMaxTimeBin = 0); static void ConvertRun2RawToNative(o2::tpc::ClusterNativeAccess& native, std::unique_ptr& nativeBuffer, const AliHLTTPCRawCluster** rawClusters, uint32_t* nRawClusters); template static void RunZSEncoder(const S& in, std::unique_ptr* outBuffer, uint32_t* outSizes, o2::raw::RawFileWriter* raw, const o2::InteractionRecord* ir, const GPUParam& param, int32_t version, bool verify, float threshold = 0.f, bool padding = false, std::function&)> digitsFilter = nullptr); diff --git a/GPU/GPUTracking/Base/GPUReconstructionDebug.cxx b/GPU/GPUTracking/Base/GPUReconstructionDebug.cxx index c1c31eedde1b2..559d1537464ab 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionDebug.cxx +++ b/GPU/GPUTracking/Base/GPUReconstructionDebug.cxx @@ -13,6 +13,7 @@ /// \author David Rohr #include "GPUReconstruction.h" +#include "GPUReconstructionCPU.h" #include "GPULogging.h" #include "GPUSettings.h" @@ -23,6 +24,8 @@ #include #include #include +#include +#include using namespace o2::gpu; @@ -186,3 +189,119 @@ bool GPUReconstruction::triggerDebugDump() } return false; } + +GPUReconstructionCPU::debugWriter::debugWriter(std::string filenameCSV, bool markdown, uint32_t statNEvents) : mMarkdown{markdown}, mStatNEvents{statNEvents} +{ + if (!filenameCSV.empty()) { + streamCSV.open(filenameCSV, std::ios::out | std::ios::app); + } +} + +void GPUReconstructionCPU::debugWriter::header() +{ + if (streamCSV.is_open() && !streamCSV.tellp()) { + streamCSV << "type,count,name,gpu (us),cpu (us),cpu/total,total (us),GB/s,bytes,bytes/call\n"; + } + + if (mMarkdown) { + std::cout << "| | count | name | gpu (us) | cpu (us) | cpu/tot | tot (us) | GB/s | bytes | bytes/call |\n"; + std::cout << "|---|--------|-------------------------------------------|-----------|-----------|---------|-----------|-----------|---------------|---------------|\n"; + } +} + +void GPUReconstructionCPU::debugWriter::row(char type, uint32_t count, std::string name, double gpu_time, double cpu_time, double total_time, std::size_t memSize, std::string nEventReport) +{ + double scale = 1000000.0 / mStatNEvents; + + if (streamCSV.is_open()) { + streamCSV << type << ","; + if (count != 0) { + streamCSV << count; + } + streamCSV << "," << name << ","; + if (gpu_time != -1.0) { + streamCSV << std::format("{:.0f}", gpu_time * scale); + } + streamCSV << ","; + if (cpu_time != -1.0) { + streamCSV << std::format("{:.0f}", cpu_time * scale); + } + streamCSV << ","; + if (cpu_time != -1.0 && total_time != -1.0) { + streamCSV << std::format("{:.2f}", cpu_time / total_time); + } + streamCSV << ","; + if (total_time != -1.0) { + streamCSV << std::format("{:.0f}", total_time * scale); + } + streamCSV << ","; + if (memSize != 0 && count != 0) { + streamCSV << std::format("{:.3f},{},{}", memSize / gpu_time * 1e-9, memSize / mStatNEvents, memSize / mStatNEvents / count); + } else { + streamCSV << ",,"; + } + streamCSV << std::endl; + } + + if (mMarkdown) { + std::cout << "| " << type << " | "; + if (count != 0) { + std::cout << std::format("{:6} |", count); + } else { + std::cout << " |"; + } + std::cout << std::format(" {:42}|", name); + if (gpu_time != -1.0) { + std::cout << std::format("{:10.0f} |", gpu_time * scale); + } else { + std::cout << " |"; + } + if (cpu_time != -1.0) { + std::cout << std::format("{:10.0f} |", cpu_time * scale); + } else { + std::cout << " |"; + } + if (cpu_time != -1.0 && total_time != -1.0) { + std::cout << std::format("{:8.2f} |", cpu_time / total_time); + } else { + std::cout << " |"; + } + if (total_time != -1.0) { + std::cout << std::format("{:10.0f} |", total_time * scale); + } else { + std::cout << " |"; + } + if (memSize != 0 && count != 0) { + std::cout << std::format("{:10.3f} |{:14} |{:14} |", memSize / gpu_time * 1e-9, memSize / mStatNEvents, memSize / mStatNEvents / count); + } else { + std::cout << " | | |"; + } + std::cout << std::endl; + } else { + if (name.substr(0, 3) == "GPU") { + char bandwidth[256] = ""; + if (memSize && mStatNEvents && gpu_time != 0.0) { + snprintf(bandwidth, 256, " (%8.3f GB/s - %'14zu bytes - %'14zu per call)", memSize / gpu_time * 1e-9, memSize / mStatNEvents, memSize / mStatNEvents / count); + } + printf("Execution Time: Task (%c %8ux): %50s Time: %'10.0f us%s\n", type, count, name.c_str(), gpu_time * scale, bandwidth); + } else if (name.substr(0, 3) == "TPC") { + std::size_t n = name.find('('); + std::string basename = name.substr(0, n - 1); + std::string postfix = name.substr(n + 1, name.size() - n - 2); + if (total_time != -1.0) { + printf("Execution Time: Step : %11s %38s Time: %'10.0f us %64s ( Total Time : %'14.0f us, CPU Time : %'14.0f us, %'7.2fx )\n", postfix.c_str(), + basename.c_str(), gpu_time * scale, "", total_time * scale, cpu_time * scale, cpu_time / total_time); + } else { + printf("Execution Time: Step (D %8ux): %11s %38s Time: %'10.0f us (%8.3f GB/s - %'14zu bytes - %'14zu per call)\n", count, postfix.c_str(), basename.c_str(), gpu_time * scale, + memSize / gpu_time * 1e-9, memSize / mStatNEvents, memSize / mStatNEvents / count); + } + } else if (name == "Prepare") { + printf("Execution Time: General Step : %50s Time: %'10.0f us\n", name.c_str(), gpu_time * scale); + } else if (name == "Wall") { + if (gpu_time != -1.0) { + printf("Execution Time: Total : %50s Time: %'10.0f us%s\n", "Total Kernel", gpu_time * scale, nEventReport.c_str()); + } + printf("Execution Time: Total : %50s Time: %'10.0f us ( CPU Time : %'10.0f us, %7.2fx ) %s\n", "Total Wall", total_time * scale, cpu_time * scale, cpu_time / total_time, nEventReport.c_str()); + } + } +} diff --git a/GPU/GPUTracking/Base/GPUReconstructionDeviceBase.cxx b/GPU/GPUTracking/Base/GPUReconstructionDeviceBase.cxx index 9962bdf3922c1..dba1e85aad3d6 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionDeviceBase.cxx +++ b/GPU/GPUTracking/Base/GPUReconstructionDeviceBase.cxx @@ -98,8 +98,8 @@ int32_t GPUReconstructionDeviceBase::InitDevice() GPUError("Individual memory allocation strategy unsupported for device\n"); return (1); } - if (GetProcessingSettings().nStreams > GPUCA_MAX_STREAMS) { - GPUError("Too many straems requested %d > %d\n", GetProcessingSettings().nStreams, GPUCA_MAX_STREAMS); + if ((size_t)GetProcessingSettings().nStreams > constants::GPU_MAX_STREAMS) { + GPUError("Too many straems requested %d > %d\n", GetProcessingSettings().nStreams, constants::GPU_MAX_STREAMS); return (1); } diff --git a/GPU/GPUTracking/Base/GPUReconstructionIO.h b/GPU/GPUTracking/Base/GPUReconstructionIO.h index 810ebfffe1703..fb771ec3639a9 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionIO.h +++ b/GPU/GPUTracking/Base/GPUReconstructionIO.h @@ -41,7 +41,7 @@ inline T* GPUReconstruction::AllocateIOMemoryHelper(size_t n, const T*& ptr, std retVal = u.get(); if (GetProcessingSettings().registerStandaloneInputMemory) { if (registerMemoryForGPU(u.get(), n * sizeof(T))) { - GPUError("Error registering memory for GPU: %p - %ld bytes\n", (void*)u.get(), (int64_t)(n * sizeof(T))); + GPUError("Error registering memory for GPU: %p - %zu bytes\n", (void*)u.get(), n * sizeof(T)); throw std::bad_alloc(); } } @@ -69,7 +69,7 @@ inline uint32_t GPUReconstruction::DumpData(FILE* fp, const T* const* entries, c } } if (GetProcessingSettings().debugLevel >= 2) { - GPUInfo("Dumped %ld %s", (int64_t)numTotal, IOTYPENAMES[type]); + GPUInfo("Dumped %zu %s", numTotal, IOTYPENAMES[type]); } return numTotal; } @@ -103,7 +103,7 @@ inline size_t GPUReconstruction::ReadData(FILE* fp, const T** entries, S* num, s } (void)r; if (GetProcessingSettings().debugLevel >= 2) { - GPUInfo("Read %ld %s", (int64_t)numTotal, IOTYPENAMES[type]); + GPUInfo("Read %zu %s", numTotal, IOTYPENAMES[type]); } return numTotal; } @@ -133,7 +133,7 @@ inline std::unique_ptr GPUReconstruction::ReadFlatObjectFromFile(const char* r = fread(size, sizeof(size[0]), 2, fp); if (r == 0 || size[0] != sizeof(T)) { fclose(fp); - GPUError("ERROR reading %s, invalid size: %ld (%ld expected)", file, (int64_t)size[0], (int64_t)sizeof(T)); + GPUError("ERROR reading %s, invalid size: %zu (%zu expected)", file, size[0], sizeof(T)); throw std::runtime_error("invalid size"); } std::unique_ptr retVal(new T); @@ -143,7 +143,7 @@ inline std::unique_ptr GPUReconstruction::ReadFlatObjectFromFile(const char* r = fread(buf, 1, size[1], fp); fclose(fp); if (GetProcessingSettings().debugLevel >= 2) { - GPUInfo("Read %ld bytes from %s", (int64_t)r, file); + GPUInfo("Read %zu bytes from %s", r, file); } retVal->clearInternalBufferPtr(); retVal->setActualBufferAddress(buf); @@ -165,47 +165,95 @@ inline void GPUReconstruction::DumpStructToFile(const T* obj, const char* file) } template -inline std::unique_ptr GPUReconstruction::ReadStructFromFile(const char* file) +inline std::unique_ptr GPUReconstruction::ReadStructFromFile(const char* file, T* obj, bool* errorOnMissing, bool allowSmaller) { FILE* fp = fopen(file, "rb"); if (fp == nullptr) { + if (errorOnMissing) { + *errorOnMissing = true; + } return nullptr; } size_t size, r; r = fread(&size, sizeof(size), 1, fp); - if (r == 0 || size != sizeof(T)) { + if (r == 0 || (!allowSmaller && size != sizeof(T))) { fclose(fp); - GPUError("ERROR reading %s, invalid size: %ld (%ld expected)", file, (int64_t)size, (int64_t)sizeof(T)); + GPUError("ERROR reading %s, invalid size: %zu (%zu expected)", file, size, sizeof(T)); throw std::runtime_error("invalid size"); } - std::unique_ptr newObj(new T); - r = fread(newObj.get(), 1, size, fp); + std::unique_ptr retVal(nullptr); + if (obj == nullptr) { + retVal = std::make_unique(); + obj = retVal.get(); + } + r = fread(obj, 1, size, fp); fclose(fp); + if (r != size) { + GPUError("ERROR reading %s, read %zu (%zu expected)", file, r, size); + throw std::runtime_error("invalid size"); + } if (GetProcessingSettings().debugLevel >= 2) { - GPUInfo("Read %ld bytes from %s", (int64_t)r, file); + GPUInfo("Read %zu bytes from %s", r, file); } - return newObj; + if (errorOnMissing) { + *errorOnMissing = false; + } + return retVal; } template -inline int32_t GPUReconstruction::ReadStructFromFile(const char* file, T* obj) +inline void GPUReconstruction::DumpDynamicStructToFile(const T* obj, size_t dynamicSize, const char* file) +{ + FILE* fp = fopen(file, "w+b"); + if (fp == nullptr) { + return; + } + size_t size = sizeof(*obj); + fwrite(&size, sizeof(size), 1, fp); + fwrite(&dynamicSize, sizeof(dynamicSize), 1, fp); + fwrite(obj, 1, dynamicSize, fp); + fclose(fp); +} + +template +inline aligned_unique_buffer_ptr GPUReconstruction::ReadDynamicStructFromFile(const char* file) { FILE* fp = fopen(file, "rb"); if (fp == nullptr) { - return 1; + return nullptr; } - size_t size, r; + size_t size, dynsize, r, r2; r = fread(&size, sizeof(size), 1, fp); + r2 = fread(&dynsize, sizeof(dynsize), 1, fp); + if (r == 0 || r2 == 0 || size != sizeof(T) || dynsize < size) { + fclose(fp); + GPUError("ERROR reading %s, invalid size: %zu (%zu buffer size, %zu object size expected)", file, size, dynsize, sizeof(T)); + throw std::runtime_error("invalid size"); + } + std::unique_ptr tmp = std::make_unique(); + r = fread(tmp.get(), sizeof(T), 1, fp); if (r == 0) { fclose(fp); - return 1; + GPUError("ERROR reading %s %zu (%zu expected)", file, size, sizeof(T)); + throw std::runtime_error("read error"); } - r = fread(obj, 1, size, fp); + if ((tmp.get()->*F)() != dynsize) { + fclose(fp); + GPUError("ERROR in %s: invalid size: %zu (%zu expected)", file, dynsize, (tmp.get()->*F)()); + throw std::runtime_error("invalid size"); + } + aligned_unique_buffer_ptr newObj(dynsize); + memcpy(newObj.get(), tmp.get(), sizeof(T)); + r = fread(newObj.getraw() + sizeof(T), 1, dynsize - sizeof(T), fp); fclose(fp); + if (r != dynsize - sizeof(T)) { + GPUError("ERROR in %s: File Read error in %s: %zu (%zu expected)", file, r, dynsize); + throw std::runtime_error("invalid size"); + } if (GetProcessingSettings().debugLevel >= 2) { - GPUInfo("Read %ld bytes from %s", (int64_t)r, file); + GPUInfo("Read %zu bytes from %s", r + dynsize, file); } - return 0; + return newObj; } } // namespace o2::gpu diff --git a/GPU/GPUTracking/Base/GPUReconstructionIncludesITS.h b/GPU/GPUTracking/Base/GPUReconstructionIncludesITS.h index 813e0aef2d1aa..36a2b3ebca103 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionIncludesITS.h +++ b/GPU/GPUTracking/Base/GPUReconstructionIncludesITS.h @@ -21,7 +21,6 @@ #include "ITStracking/TimeFrame.h" #if defined(__CUDACC__) || defined(__HIPCC__) #include "ITStrackingGPU/TrackerTraitsGPU.h" -#include "ITStrackingGPU/VertexerTraitsGPU.h" #include "ITStrackingGPU/TimeFrameGPU.h" #endif #else @@ -39,10 +38,6 @@ template class TimeFrame { }; -template -class VertexerTraitsGPU : public VertexerTraits -{ -}; template class TrackerTraitsGPU : public TrackerTraits { diff --git a/GPU/GPUTracking/Base/GPUReconstructionProcessing.h b/GPU/GPUTracking/Base/GPUReconstructionProcessing.h index f582610b57973..3d9507e48e292 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionProcessing.h +++ b/GPU/GPUTracking/Base/GPUReconstructionProcessing.h @@ -162,9 +162,10 @@ class GPUReconstructionProcessing : public GPUReconstruction // Interface to query name of a kernel template static const char* GetKernelName(); - const std::string& GetKernelName(int32_t i) const { return mKernelNames[i]; } + static const std::string& GetKernelName(int32_t i) { return mKernelNames[i]; } template static uint32_t GetKernelNum(); + static uint32_t GetNKernels() { return mKernelNames.size(); } // Public queries for timers auto& getRecoStepTimer(RecoStep step) { return mTimersRecoSteps[getRecoStepNum(step)]; } @@ -249,7 +250,7 @@ HighResTimer& GPUReconstructionProcessing::getTimer(const char* name, int32_t nu static int32_t id = getNextTimerId(); timerMeta* timer = getTimerById(id); if (timer == nullptr) { - int32_t max = std::max({mMaxHostThreads, GPUCA_MAX_STREAMS}); + int32_t max = std::max({mMaxHostThreads, constants::GPU_MAX_STREAMS}); timer = insertTimer(id, name, J, max, 1, RecoStep::NoRecoStep); } if (num == -1) { diff --git a/GPU/GPUTracking/Base/GPUReconstructionTimeframe.cxx b/GPU/GPUTracking/Base/GPUReconstructionTimeframe.cxx index fefcd0ac925fe..4570d4da5d000 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionTimeframe.cxx +++ b/GPU/GPUTracking/Base/GPUReconstructionTimeframe.cxx @@ -22,8 +22,7 @@ #include "GPUTPCMCInfo.h" #include "GPUTPCClusterData.h" #include "AliHLTTPCRawCluster.h" -#include "TPCFastTransform.h" -#include "CorrectionMapsHelper.h" +#include "TPCFastTransformPOD.h" #include "GPUO2DataTypes.h" #include "GPUSettings.h" @@ -45,7 +44,7 @@ GPUReconstructionTimeframe::GPUReconstructionTimeframe(GPUChainTracking* chain, mMaxBunchesFull = TIME_ORBIT / config.bunchSpacing; mMaxBunches = (TIME_ORBIT - config.abortGapTime) / config.bunchSpacing; - if (config.overlayRaw && chain->GetTPCTransformHelper() == nullptr) { + if (config.overlayRaw && chain->GetTPCTransform() == nullptr) { GPUInfo("Overlay Raw Events requires TPC Fast Transform"); throw std::exception(); } @@ -72,7 +71,7 @@ int32_t GPUReconstructionTimeframe::ReadEventShifted(int32_t iEvent, float shift { mReadEvent(iEvent); if (config.overlayRaw) { - float shiftTTotal = (((double)config.timeFrameLen - DRIFT_TIME) * ((double)TPCZ / (double)DRIFT_TIME) - shiftZ) / mChain->GetTPCTransformHelper()->getCorrMap()->getVDrift(); + float shiftTTotal = (((double)config.timeFrameLen - DRIFT_TIME) * ((double)TPCZ / (double)DRIFT_TIME) - shiftZ) / mChain->GetTPCTransform()->getVDrift(); for (uint32_t iSector = 0; iSector < NSECTORS; iSector++) { for (uint32_t j = 0; j < mChain->mIOPtrs.nRawClusters[iSector]; j++) { auto& tmp = mChain->mIOMem.rawClusters[iSector][j]; diff --git a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu index c919581eefdde..040a4b84a0f64 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu @@ -94,15 +94,13 @@ GPUReconstruction* GPUReconstruction_Create_CUDA(const GPUSettingsDeviceBackend& void GPUReconstructionCUDA::GetITSTraits(std::unique_ptr>* trackerTraits, std::unique_ptr>* vertexerTraits, std::unique_ptr>* timeFrame) { if (trackerTraits) { - trackerTraits->reset(new o2::its::TrackerTraitsGPU); + trackerTraits->reset(new o2::its::TrackerTraitsGPU<7>); } if (vertexerTraits) { vertexerTraits->reset(new o2::its::VertexerTraits<7>); - // TODO gpu-code to be implemented then remove line above and uncomment line below - // vertexerTraits->reset(new o2::its::VertexerTraitsGPU<7>); } if (timeFrame) { - timeFrame->reset(new o2::its::gpu::TimeFrameGPU); + timeFrame->reset(new o2::its::gpu::TimeFrameGPU<7>); } } @@ -270,12 +268,12 @@ int32_t GPUReconstructionCUDA::InitDevice_Runtime() #endif #ifndef __HIPCC__ // CUDA - if (GPUChkErrI(cudaDeviceSetLimit(cudaLimitStackSize, GPUCA_GPU_STACK_SIZE))) { + if (GPUChkErrI(cudaDeviceSetLimit(cudaLimitStackSize, constants::GPU_STACK_SIZE))) { GPUError("Error setting CUDA stack size"); GPUChkErrI(cudaDeviceReset()); return (1); } - if (GPUChkErrI(cudaDeviceSetLimit(cudaLimitMallocHeapSize, GetProcessingSettings().deterministicGPUReconstruction ? std::max(1024 * 1024 * 1024, GPUCA_GPU_HEAP_SIZE) : GPUCA_GPU_HEAP_SIZE))) { + if (GPUChkErrI(cudaDeviceSetLimit(cudaLimitMallocHeapSize, GetProcessingSettings().deterministicGPUReconstruction ? std::max(1024 * 1024 * 1024, constants::GPU_HEAP_SIZE) : constants::GPU_HEAP_SIZE))) { GPUError("Error setting CUDA stack size"); GPUChkErrI(cudaDeviceReset()); return (1); @@ -374,7 +372,7 @@ int32_t GPUReconstructionCUDA::InitDevice_Runtime() #endif mDeviceConstantMem = (GPUConstantMem*)devPtrConstantMem; - GPUInfo("CUDA Initialisation successfull (Device %d: %s (Frequency %d, Cores %d), %ld / %ld bytes host / global memory, Stack frame %d, Constant memory %ld)", mDeviceId, deviceProp.name, deviceClockRate, deviceProp.multiProcessorCount, (int64_t)mHostMemorySize, (int64_t)mDeviceMemorySize, (int32_t)GPUCA_GPU_STACK_SIZE, (int64_t)gGPUConstantMemBufferSize); + GPUInfo("CUDA Initialisation successfull (Device %d: %s (Frequency %d, Cores %d), %ld / %ld bytes host / global memory, Stack frame %d, Constant memory %ld)", mDeviceId, deviceProp.name, deviceClockRate, deviceProp.multiProcessorCount, (int64_t)mHostMemorySize, (int64_t)mDeviceMemorySize, (int32_t)constants::GPU_STACK_SIZE, (int64_t)gGPUConstantMemBufferSize); } else { GPUReconstructionCUDA* master = dynamic_cast(mMaster); mDeviceId = master->mDeviceId; diff --git a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAGenRTC.cxx b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAGenRTC.cxx index c4e1775e445c3..2081570f8cdb9 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAGenRTC.cxx +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAGenRTC.cxx @@ -17,11 +17,13 @@ #include "GPUReconstructionCUDA.h" #include "GPUParamRTC.h" #include "GPUDefParametersLoad.inc" +#include "GPUKernelsWith1Warp.inc" #include #include "Framework/SHA1.h" #include #include #include +#include #include using namespace o2::gpu; @@ -81,10 +83,18 @@ int32_t GPUReconstructionCUDA::genRTC(std::string& filename, uint32_t& nCompile) GPUFatal("AMD_EUS_PER_CU not set in the parameters provided for the AMD GPU, you can override this via --PROChipOverrideAMDEUSperCU [n]"); } } + for (uint32_t i = 0; i < GetNKernels(); i++) { + if (std::find(gpuKernelsWith1Warp.begin(), gpuKernelsWith1Warp.end(), GetKernelName(i)) != gpuKernelsWith1Warp.end()) { + mParDevice->par_LB_maxThreads[i] = mWarpSize; + } + } const std::string launchBounds = o2::gpu::internal::GPUDefParametersExport(*mParDevice, true, mParDevice->par_AMD_EUS_PER_CU ? (mParDevice->par_AMD_EUS_PER_CU * mWarpSize) : 0) + "#define GPUCA_WARP_SIZE " + std::to_string(mWarpSize) + "\n"; if (GetProcessingSettings().rtctech.printLaunchBounds || GetProcessingSettings().debugLevel >= 3) { GPUInfo("RTC Launch Bounds:\n%s", launchBounds.c_str()); + if (GetProcessingSettings().rtctech.printLaunchBounds >= 2) { + return 1; + } } const std::string compilerVersions = getBackendVersions(); diff --git a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAInternals.h b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAInternals.h index 493c09e448e5e..8ce21652247fc 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAInternals.h +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAInternals.h @@ -30,7 +30,7 @@ namespace o2::gpu struct GPUReconstructionCUDAInternals { std::vector> kernelModules; // module for RTC compilation std::vector> kernelFunctions; // vector of ptrs to RTC kernels - cudaStream_t Streams[GPUCA_MAX_STREAMS]; // Pointer to array of CUDA Streams + cudaStream_t Streams[constants::GPU_MAX_STREAMS]; // Pointer to array of CUDA Streams static void getArgPtrs(const void** pArgs) {} template diff --git a/GPU/GPUTracking/Base/opencl/GPUReconstructionOCLIncludesHost.h b/GPU/GPUTracking/Base/opencl/GPUReconstructionOCLIncludesHost.h index 919791948d6c3..4086109fd7441 100644 --- a/GPU/GPUTracking/Base/opencl/GPUReconstructionOCLIncludesHost.h +++ b/GPU/GPUTracking/Base/opencl/GPUReconstructionOCLIncludesHost.h @@ -24,6 +24,9 @@ #include #include #include + +typedef cl_half half; + #include "GPULogging.h" #include "GPUReconstructionOCL.h" @@ -43,7 +46,7 @@ struct GPUReconstructionOCLInternals { cl_platform_id platform; cl_device_id device; cl_context context; - cl_command_queue command_queue[GPUCA_MAX_STREAMS]; + cl_command_queue command_queue[constants::GPU_MAX_STREAMS]; cl_mem mem_gpu; cl_mem mem_constant; cl_mem mem_host; diff --git a/GPU/GPUTracking/CMakeLists.txt b/GPU/GPUTracking/CMakeLists.txt index c276bf59af40b..ca58d91212084 100644 --- a/GPU/GPUTracking/CMakeLists.txt +++ b/GPU/GPUTracking/CMakeLists.txt @@ -299,6 +299,11 @@ set(GPU_DEFAULT_PARAMS_HEADER_DEVICE ${ON_THE_FLY_DIR}/GPUDefParametersDefaultsD generate_gpu_param_header("${GPU_PARAM_JSON_FILES}" "ALL" "${GPU_DEFAULT_PARAMS_HEADER}" "${GPU_DEFAULT_PARAMS_HEADER_DEVICE}" GPU_CONST_PARAM_ARCHITECTUES) # generate header with default GPU parameters for all architectures list(APPEND GENERATED_HEADERS_LIST ${GPU_DEFAULT_PARAMS_HEADER} ${GPU_DEFAULT_PARAMS_HEADER_DEVICE}) +file(GENERATE + OUTPUT "${ON_THE_FLY_DIR}/GPUKernelsWith1Warp.inc" + CONTENT "namespace o2::gpu { static const std::vector gpuKernelsWith1Warp = {\"$,\"$ \">\"}; }") +list(APPEND GENERATED_HEADERS_LIST ${GPU_DEFAULT_PARAMS_HEADER} ${ON_THE_FLY_DIR}/GPUKernelsWith1Warp.inc) + set(HDRS_INSTALL ${HDRS_INSTALL} ${GENERATED_HEADERS_LIST}) include(kernels.cmake) @@ -352,10 +357,11 @@ if(ALIGPU_BUILD_TYPE STREQUAL "O2") PUBLIC_LINK_LIBRARIES O2::GPUUtils O2::GPUCommon O2::ReconstructionDataFormats - O2::TPCFastTransformation PRIVATE_LINK_LIBRARIES O2::DataFormatsTPC + O2::TPCBase + ROOT::RIO SOURCES ${SRCS_DATATYPES}) - target_compile_definitions(${targetName} PRIVATE GPUCA_O2_LIB GPUCA_TPC_GEOMETRY_O2) + target_compile_definitions(${targetName} PRIVATE GPUCA_O2_LIB) o2_target_root_dictionary(GPUDataTypes HEADERS ${HDRS_CINT_DATATYPES} ${HDRS_CINT_O2_ADDITIONAL} @@ -384,7 +390,7 @@ if(ALIGPU_BUILD_TYPE STREQUAL "O2") ${targetName} PRIVATE $) - target_compile_definitions(${targetName} PRIVATE GPUCA_O2_LIB GPUCA_TPC_GEOMETRY_O2) + target_compile_definitions(${targetName} PRIVATE GPUCA_O2_LIB) o2_target_root_dictionary(${MODULE} HEADERS ${HDRS_CINT_O2} ${HDRS_CINT_O2_ADDITIONAL} diff --git a/GPU/GPUTracking/DataCompression/GPUTPCClusterStatistics.cxx b/GPU/GPUTracking/DataCompression/GPUTPCClusterStatistics.cxx index b11a3b13d7132..3d8e749e84147 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCClusterStatistics.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCClusterStatistics.cxx @@ -117,7 +117,7 @@ void GPUTPCClusterStatistics::RunStatistics(const o2::tpc::ClusterNativeAccess* std::vector tmpClusters; if (param.rec.tpc.rejectionStrategy == GPUSettings::RejectionNone) { // verification does not make sense if we reject clusters during compression for (uint32_t i = 0; i < NSECTORS; i++) { - for (uint32_t j = 0; j < GPUCA_ROW_COUNT; j++) { + for (uint32_t j = 0; j < GPUTPCGeometry::NROWS; j++) { if (clustersNative->nClusters[i][j] != clustersNativeDecoded.nClusters[i][j]) { GPUError("Number of clusters mismatch sector %u row %u: expected %d v.s. decoded %d", i, j, clustersNative->nClusters[i][j], clustersNativeDecoded.nClusters[i][j]); decodingErrors++; @@ -181,7 +181,7 @@ void GPUTPCClusterStatistics::RunStatistics(const o2::tpc::ClusterNativeAccess* FillStatisticCombined(mPsigmaU, clustersCompressed->sigmaPadU, clustersCompressed->sigmaTimeU, clustersCompressed->nUnattachedClusters, P_MAX_SIGMA); FillStatisticCombined(mPQA, clustersCompressed->qMaxA, clustersCompressed->qTotA, clustersCompressed->nAttachedClusters, P_MAX_QMAX); FillStatisticCombined(mPQU, clustersCompressed->qMaxU, clustersCompressed->qTotU, clustersCompressed->nUnattachedClusters, P_MAX_QMAX); - FillStatisticCombined(mProwSectorA, clustersCompressed->rowDiffA, clustersCompressed->sliceLegDiffA, clustersCompressed->nAttachedClustersReduced, GPUCA_ROW_COUNT); + FillStatisticCombined(mProwSectorA, clustersCompressed->rowDiffA, clustersCompressed->sliceLegDiffA, clustersCompressed->nAttachedClustersReduced, GPUTPCGeometry::NROWS); mNTotalClusters += clustersCompressed->nAttachedClusters + clustersCompressed->nUnattachedClusters; } @@ -228,7 +228,7 @@ void GPUTPCClusterStatistics::Finish() GPUInfo("Combined Sigma: %6.4f --> %6.4f (%6.4f%%)", eSigma, eSigmaCombined, eSigma > 1e-3 ? (100. * (eSigma - eSigmaCombined) / eSigma) : 0.f); GPUInfo("Combined Q: %6.4f --> %6.4f (%6.4f%%)", eQ, eQCombined, eQ > 1e-3 ? (100. * (eQ - eQCombined) / eQ) : 0.f); - printf("\nCombined Entropy: %7.4f (Size %'13.0f, %'zu clusters)\nCombined Huffman: %7.4f (Size %'13.0f, %f%%)\n\n", mEntropy / mNTotalClusters, mEntropy, mNTotalClusters, mHuffman / mNTotalClusters, mHuffman, 100. * (mHuffman - mEntropy) / mHuffman); + printf("\nCombined Entropy: %7.4f (Size %'13.0f, %'zu clusters)\nCombined Huffman: %7.4f (Size %'13.0f, %f%%)\n\n", mEntropy / mNTotalClusters, mEntropy / 8., mNTotalClusters, mHuffman / mNTotalClusters, mHuffman / 8., 100. * (mHuffman - mEntropy) / mHuffman); } float GPUTPCClusterStatistics::Analyze(std::vector& p, const char* name, bool count) diff --git a/GPU/GPUTracking/DataCompression/GPUTPCClusterStatistics.h b/GPU/GPUTracking/DataCompression/GPUTPCClusterStatistics.h index 1dfb958750bef..4efaa7f33257c 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCClusterStatistics.h +++ b/GPU/GPUTracking/DataCompression/GPUTPCClusterStatistics.h @@ -29,7 +29,7 @@ namespace o2::gpu class GPUTPCClusterStatistics { public: - static constexpr uint32_t NSECTORS = GPUCA_NSECTORS; + static constexpr uint32_t NSECTORS = GPUTPCGeometry::NSECTORS; void RunStatistics(const o2::tpc::ClusterNativeAccess* clustersNative, const o2::tpc::CompressedClusters* clustersCompressed, const GPUParam& param); void Finish(); @@ -54,15 +54,15 @@ class GPUTPCClusterStatistics std::vector mPqTotA = std::vector(P_MAX_QTOT, 0); std::vector mPqMaxA = std::vector(P_MAX_QMAX, 0); std::vector mPflagsA = std::vector(P_MAX_FLAGS, 0); - std::vector mProwDiffA = std::vector(GPUCA_ROW_COUNT, 0); - std::vector mPsectorLegDiffA = std::vector(GPUCA_NSECTORS * 2, 0); + std::vector mProwDiffA = std::vector(GPUTPCGeometry::NROWS, 0); + std::vector mPsectorLegDiffA = std::vector(GPUTPCGeometry::NSECTORS * 2, 0); std::vector mPpadResA = std::vector(P_MAX_PAD, 0); std::vector mPtimeResA = std::vector(P_MAX_TIME, 0); std::vector mPsigmaPadA = std::vector(P_MAX_SIGMA, 0); std::vector mPsigmaTimeA = std::vector(P_MAX_SIGMA, 0); std::vector mPqPtA = std::vector(P_MAX_QPT, 0); - std::vector mProwA = std::vector(GPUCA_ROW_COUNT, 0); - std::vector mPsectorA = std::vector(GPUCA_NSECTORS, 0); + std::vector mProwA = std::vector(GPUTPCGeometry::NROWS, 0); + std::vector mPsectorA = std::vector(GPUTPCGeometry::NSECTORS, 0); std::vector mPtimeA = std::vector(P_MAX_TIME, 0); std::vector mPpadA = std::vector(P_MAX_PAD, 0); std::vector mPqTotU = std::vector(P_MAX_QTOT, 0); @@ -78,7 +78,7 @@ class GPUTPCClusterStatistics std::vector mPsigmaA = std::vector(P_MAX_SIGMA * P_MAX_SIGMA, 0); std::vector mPQU = std::vector(P_MAX_QMAX * P_MAX_QTOT, 0); std::vector mPQA = std::vector(P_MAX_QMAX * P_MAX_QTOT, 0); - std::vector mProwSectorA = std::vector(GPUCA_ROW_COUNT * GPUCA_NSECTORS * 2, 0); + std::vector mProwSectorA = std::vector(GPUTPCGeometry::NROWS * GPUTPCGeometry::NSECTORS * 2, 0); double mEntropy = 0; double mHuffman = 0; diff --git a/GPU/GPUTracking/DataCompression/GPUTPCCompression.cxx b/GPU/GPUTracking/DataCompression/GPUTPCCompression.cxx index efb7a4af3f323..00f1f6500e9f0 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCCompression.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCCompression.cxx @@ -69,7 +69,7 @@ void GPUTPCCompression::SetPointersCompressedClusters(void*& mem, T& c, uint32_t computePointerWithAlignment(mem, c.timeDiffU, nClU); computePointerWithAlignment(mem, c.sigmaPadU, nClU); computePointerWithAlignment(mem, c.sigmaTimeU, nClU); - computePointerWithAlignment(mem, c.nSliceRowClusters, GPUCA_ROW_COUNT * NSECTORS); + computePointerWithAlignment(mem, c.nSliceRowClusters, GPUTPCGeometry::NROWS * NSECTORS); uint32_t nClAreduced = reducedClA ? nClA - nTr : nClA; @@ -139,8 +139,8 @@ void GPUTPCCompression::DumpCompressedClusters(std::ostream& out) out << "Slice Row Clusters:\n"; for (uint32_t i = 0; i < NSECTORS; i++) { out << "Sector " << i << ": "; - for (uint32_t j = 0; j < GPUCA_ROW_COUNT; j++) { - out << (O.nSliceRowClusters ? O.nSliceRowClusters[i * GPUCA_ROW_COUNT + j] : 0) << ", "; + for (uint32_t j = 0; j < GPUTPCGeometry::NROWS; j++) { + out << (O.nSliceRowClusters ? O.nSliceRowClusters[i * GPUTPCGeometry::NROWS + j] : 0) << ", "; } out << "\n"; } @@ -155,16 +155,16 @@ void GPUTPCCompression::DumpCompressedClusters(std::ostream& out) uint32_t offset = 0; if (O.nSliceRowClusters) { for (uint32_t i = 0; i < NSECTORS; i++) { - for (uint32_t j = 0; j < GPUCA_ROW_COUNT; j++) { + for (uint32_t j = 0; j < GPUTPCGeometry::NROWS; j++) { out << "Sector " << i << " Row " << j << ": "; - for (uint32_t k = 0; k < O.nSliceRowClusters[i * GPUCA_ROW_COUNT + j]; k++) { + for (uint32_t k = 0; k < O.nSliceRowClusters[i * GPUTPCGeometry::NROWS + j]; k++) { if (k && k % 10 == 0) { out << "\n "; } const uint32_t l = k + offset; out << "[" << (uint32_t)O.qTotU[l] << ", " << (uint32_t)O.qMaxU[l] << ", " << (uint32_t)O.flagsU[l] << ", " << (int32_t)O.padDiffU[l] << ", " << (int32_t)O.timeDiffU[l] << ", " << (uint32_t)O.sigmaPadU[l] << ", " << (uint32_t)O.sigmaTimeU[l] << "] "; } - offset += O.nSliceRowClusters[i * GPUCA_ROW_COUNT + j]; + offset += O.nSliceRowClusters[i * GPUTPCGeometry::NROWS + j]; out << "\n"; } } diff --git a/GPU/GPUTracking/DataCompression/GPUTPCCompression.h b/GPU/GPUTracking/DataCompression/GPUTPCCompression.h index 95173dad7257f..82e44eda6f3cc 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCCompression.h +++ b/GPU/GPUTracking/DataCompression/GPUTPCCompression.h @@ -20,6 +20,7 @@ #include "GPUCommonMath.h" #include "GPUParam.h" #include "DataFormatsTPC/CompressedClusters.h" +#include "GPUTPCGeometry.h" namespace o2::gpu { @@ -69,7 +70,7 @@ class GPUTPCCompression : public GPUProcessor uint32_t nStoredUnattachedClusters = 0; }; - constexpr static uint32_t NSECTORS = GPUCA_NSECTORS; + constexpr static uint32_t NSECTORS = GPUTPCGeometry::NSECTORS; o2::tpc::CompressedClustersPtrs mPtrs; o2::tpc::CompressedClusters* mOutput = nullptr; diff --git a/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.cxx b/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.cxx index 1cd44e221f203..b98f5c28f57b0 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.cxx @@ -73,10 +73,10 @@ GPUdii() void GPUTPCCompressionKernels::Thread 0 ? 254 : 0); zOffset = z; - track.Init(x, y, z - zOffset, param.SectorParam[hit.sector].Alpha, qpt, param); + track.Init(x, y, z - zOffset, GPUTPCGeometry::SectorAlpha(hit.sector), qpt, param); myTrack = CAMath::AtomicAdd(&compressor.mMemory->nStoredTracks, 1u); compressor.mAttachedClusterFirstIndex[myTrack] = trk.FirstClusterRef(); @@ -102,7 +102,7 @@ GPUdii() void GPUTPCCompressionKernels::Thread row) { - row += GPUCA_ROW_COUNT; + row += GPUTPCGeometry::NROWS; } row -= lastRow; if (lastSector > sector) { @@ -112,7 +112,7 @@ GPUdii() void GPUTPCCompressionKernels::ThreadclusterOffset[iSector][iRow]; const uint32_t idOffsetOut = clusters->clusterOffset[iSector][iRow] * compressor.mMaxClusterFactorBase1024 / 1024; // 32 bit enough for number of clusters per row * 1024 const uint32_t idOffsetOutMax = ((const uint32_t*)clusters->clusterOffset[iSector])[iRow + 1] * compressor.mMaxClusterFactorBase1024 / 1024; // Array out of bounds access is ok, since it goes to the correct nClustersTotal @@ -244,10 +244,10 @@ GPUdii() void GPUTPCCompressionKernels::Thread idOffsetOutMax) { if (iThread == nThreads - 1) { compressor.raiseError(GPUErrors::ERROR_COMPRESSION_ROW_HIT_OVERFLOW, iSector * 1000 + iRow, idOffsetOut + totalCount + count, idOffsetOutMax); @@ -269,9 +269,9 @@ GPUdii() void GPUTPCCompressionKernels::Thread(clusters->clusters[iSector][iRow])); #else // GPUCA_DETERMINISTIC_MODE if (param.rec.tpc.compressionSortOrder == GPUSettings::SortZPadTime) { @@ -321,7 +321,7 @@ GPUdii() void GPUTPCCompressionKernels::ThreadnStoredUnattachedClusters, totalCount); } GPUbarrier(); @@ -547,7 +547,7 @@ GPUdii() void GPUTPCCompressionGatherKernels::ThreadnSliceRowClusters, compressor.mPtrs.nSliceRowClusters, compressor.NSECTORS * GPUCA_ROW_COUNT, nThreads, iThread); + compressorMemcpy(compressor.mOutput->nSliceRowClusters, compressor.mPtrs.nSliceRowClusters, compressor.NSECTORS * GPUTPCGeometry::NROWS, nThreads, iThread); compressorMemcpy(compressor.mOutput->nTrackClusters, compressor.mPtrs.nTrackClusters, compressor.mMemory->nStoredTracks, nThreads, iThread); compressorMemcpy(compressor.mOutput->qPtA, compressor.mPtrs.qPtA, compressor.mMemory->nStoredTracks, nThreads, iThread); compressorMemcpy(compressor.mOutput->rowA, compressor.mPtrs.rowA, compressor.mMemory->nStoredTracks, nThreads, iThread); @@ -566,15 +566,15 @@ GPUdii() void GPUTPCCompressionGatherKernels::ThreadtimeA, compressor.mPtrs.timeA, compressor.mMemory->nStoredTracks, nThreads, iThread); compressorMemcpy(compressor.mOutput->padA, compressor.mPtrs.padA, compressor.mMemory->nStoredTracks, nThreads, iThread); - uint32_t sectorStart = rowStart / GPUCA_ROW_COUNT; - uint32_t sectorEnd = rowEnd / GPUCA_ROW_COUNT; + uint32_t sectorStart = rowStart / GPUTPCGeometry::NROWS; + uint32_t sectorEnd = rowEnd / GPUTPCGeometry::NROWS; - uint32_t sectorRowStart = rowStart % GPUCA_ROW_COUNT; - uint32_t sectorRowEnd = rowEnd % GPUCA_ROW_COUNT; + uint32_t sectorRowStart = rowStart % GPUTPCGeometry::NROWS; + uint32_t sectorRowEnd = rowEnd % GPUTPCGeometry::NROWS; for (uint32_t i = sectorStart; i <= sectorEnd && i < compressor.NSECTORS; i++) { - for (uint32_t j = ((i == sectorStart) ? sectorRowStart : 0); j < ((i == sectorEnd) ? sectorRowEnd : GPUCA_ROW_COUNT); j++) { - uint32_t nClusters = compressor.mPtrs.nSliceRowClusters[i * GPUCA_ROW_COUNT + j]; + for (uint32_t j = ((i == sectorStart) ? sectorRowStart : 0); j < ((i == sectorEnd) ? sectorRowEnd : GPUTPCGeometry::NROWS); j++) { + uint32_t nClusters = compressor.mPtrs.nSliceRowClusters[i * GPUTPCGeometry::NROWS + j]; uint32_t clusterOffsetInCache = clusters->clusterOffset[i][j] * compressor.mMaxClusterFactorBase1024 / 1024; compressorMemcpy(compressor.mOutput->qTotU + rowsOffset, compressor.mPtrs.qTotU + clusterOffsetInCache, nClusters, nLanes, iLane); compressorMemcpy(compressor.mOutput->qMaxU + rowsOffset, compressor.mPtrs.qMaxU + clusterOffsetInCache, nClusters, nLanes, iLane); @@ -653,7 +653,7 @@ GPUdii() void GPUTPCCompressionGatherKernels::gatherBuffered(int32_t nBlocks, in auto& input = compressor.mPtrs; auto* output = compressor.mOutput; - uint32_t nRows = compressor.NSECTORS * GPUCA_ROW_COUNT; + uint32_t nRows = compressor.NSECTORS * GPUTPCGeometry::NROWS; uint32_t rowsPerWarp = (nRows + nGlobalWarps - 1) / nGlobalWarps; uint32_t rowStart = rowsPerWarp * iGlobalWarp; uint32_t rowEnd = CAMath::Min(nRows, rowStart + rowsPerWarp); @@ -678,7 +678,7 @@ GPUdii() void GPUTPCCompressionGatherKernels::gatherBuffered(int32_t nBlocks, in uint32_t tracksOffset = calculateWarpOffsets(smem, input.nTrackClusters, trackStart, trackEnd, nWarps, iWarp, nLanes, iLane); if (iBlock == 0) { - compressorMemcpyBasic(output->nSliceRowClusters, input.nSliceRowClusters, compressor.NSECTORS * GPUCA_ROW_COUNT, nThreads, iThread); + compressorMemcpyBasic(output->nSliceRowClusters, input.nSliceRowClusters, compressor.NSECTORS * GPUTPCGeometry::NROWS, nThreads, iThread); compressorMemcpyBasic(output->nTrackClusters, input.nTrackClusters, compressor.mMemory->nStoredTracks, nThreads, iThread); compressorMemcpyBasic(output->qPtA, input.qPtA, compressor.mMemory->nStoredTracks, nThreads, iThread); compressorMemcpyBasic(output->rowA, input.rowA, compressor.mMemory->nStoredTracks, nThreads, iThread); @@ -731,7 +731,7 @@ GPUdii() void GPUTPCCompressionGatherKernels::gatherMulti(int32_t nBlocks, int32 auto* buf = smem.getBuffer(iWarp); if (iBlock == 0) { - compressorMemcpyBasic(output->nSliceRowClusters, input.nSliceRowClusters, compressor.NSECTORS * GPUCA_ROW_COUNT, nThreads, iThread); + compressorMemcpyBasic(output->nSliceRowClusters, input.nSliceRowClusters, compressor.NSECTORS * GPUTPCGeometry::NROWS, nThreads, iThread); compressorMemcpyBasic(output->nTrackClusters, input.nTrackClusters, compressor.mMemory->nStoredTracks, nThreads, iThread); compressorMemcpyBasic(output->qPtA, input.qPtA, compressor.mMemory->nStoredTracks, nThreads, iThread); compressorMemcpyBasic(output->rowA, input.rowA, compressor.mMemory->nStoredTracks, nThreads, iThread); @@ -742,7 +742,7 @@ GPUdii() void GPUTPCCompressionGatherKernels::gatherMulti(int32_t nBlocks, int32 const uint32_t nGlobalWarps = nWarps * (nBlocks - 1) / 2; const uint32_t iGlobalWarp = nWarps * (iBlock - 1) / 2 + iWarp; - const uint32_t nRows = compressor.NSECTORS * GPUCA_ROW_COUNT; + const uint32_t nRows = compressor.NSECTORS * GPUTPCGeometry::NROWS; uint32_t rowsPerWarp = (nRows + nGlobalWarps - 1) / nGlobalWarps; uint32_t rowStart = rowsPerWarp * iGlobalWarp; uint32_t rowEnd = CAMath::Min(nRows, rowStart + rowsPerWarp); diff --git a/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.h b/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.h index 2236f15af9725..1edf718de8128 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.h +++ b/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.h @@ -37,7 +37,7 @@ class GPUTPCCompressionKernels : public GPUKernelTemplate struct GPUSharedMemory : public GPUKernelTemplate::GPUSharedMemoryScan64 { GPUAtomic(uint32_t) nCount; uint32_t lastIndex; - uint32_t sortBuffer[GPUCA_TPC_COMP_CHUNK_SIZE]; + uint32_t sortBuffer[constants::TPC_COMP_CHUNK_SIZE]; }; template diff --git a/GPU/GPUTracking/DataCompression/GPUTPCCompressionTrackModel.cxx b/GPU/GPUTracking/DataCompression/GPUTPCCompressionTrackModel.cxx index ab7b0c99e09df..1fe22e0e88137 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCCompressionTrackModel.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCCompressionTrackModel.cxx @@ -26,7 +26,7 @@ using namespace o2::gpu; GPUd() void GPUTPCCompressionTrackModel::Init(float x, float y, float z, float alpha, uint8_t qPt, const GPUParam& GPUrestrict() param) { mProp.SetMaterialTPC(); - mProp.SetMaxSinPhi(GPUCA_MAX_SIN_PHI); + mProp.SetMaxSinPhi(constants::MAX_SIN_PHI); mProp.SetSeedingErrors(true); // Larger errors for seeds, better since we don't start with good hypothesis mProp.SetFitInProjections(true); mProp.SetPropagateBzOnly(true); @@ -87,10 +87,10 @@ GPUd() void GPUTPCCompressionTrackModel::Init(float x, float y, float z, float a GPUd() int32_t GPUTPCCompressionTrackModel::Propagate(float x, float alpha) { GPUTPCTrackLinearisation t0(mTrk); - if (alpha != mAlpha && !mTrk.Rotate(alpha, t0, GPUCA_MAX_SIN_PHI)) { + if (alpha != mAlpha && !mTrk.Rotate(alpha, t0, constants::MAX_SIN_PHI)) { return 2; } - int32_t retVal = !mTrk.TransportToX(x, t0, mParam->bzCLight, GPUCA_MAX_SIN_PHI); + int32_t retVal = !mTrk.TransportToX(x, t0, mParam->bzCLight, constants::MAX_SIN_PHI); // GPUInfo("Propagated to: x %f y %f z %f alpha %f qPt %f", x, mTrk.Y(), mTrk.Z(), alpha, mTrk.QPt()); return retVal; } @@ -100,7 +100,7 @@ GPUd() int32_t GPUTPCCompressionTrackModel::Filter(float y, float z, int32_t iRo mTrk.ConstrainSinPhi(); float err2Y, err2Z; GPUTPCTracker::GetErrors2Seeding(*mParam, iRow, mTrk, -1.f, err2Y, err2Z); - int32_t retVal = !mTrk.Filter(y, z, err2Y, err2Z, GPUCA_MAX_SIN_PHI, false); + int32_t retVal = !mTrk.Filter(y, z, err2Y, err2Z, constants::MAX_SIN_PHI, false); // GPUInfo("Filtered with %f %f: y %f z %f qPt %f", y, z, mTrk.Y(), mTrk.Z(), mTrk.QPt()); return retVal; } diff --git a/GPU/GPUTracking/DataCompression/GPUTPCCompressionTrackModel.h b/GPU/GPUTracking/DataCompression/GPUTPCCompressionTrackModel.h index b3b4da27e625b..0021f3331cb2e 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCCompressionTrackModel.h +++ b/GPU/GPUTracking/DataCompression/GPUTPCCompressionTrackModel.h @@ -20,6 +20,7 @@ // #define GPUCA_COMPRESSION_TRACK_MODEL_SECTORTRACKER #include "GPUDef.h" +#include "DataFormatsTPC/Constants.h" #ifdef GPUCA_COMPRESSION_TRACK_MODEL_MERGER #include "GPUTPCGMPropagator.h" @@ -100,15 +101,15 @@ class GPUTPCCompressionTrackModel GPUd() void getClusterErrors2(int32_t iRow, float z, float sinPhi, float DzDs, float& ErrY2, float& ErrZ2) const; GPUd() void resetCovariance(); - GPUd() float LinearPad2Y(int32_t sector, float pad, float padWidth, uint8_t npads) const + GPUd() float LinearPad2Y(uint32_t sector, float pad, float padWidth, uint8_t npads) const { const float u = (pad - 0.5f * npads) * padWidth; - return (sector >= GPUCA_NSECTORS / 2) ? -u : u; + return (sector >= o2::tpc::constants::MAXSECTOR / 2) ? -u : u; } - GPUd() float LinearY2Pad(int32_t sector, float y, float padWidth, uint8_t npads) const + GPUd() float LinearY2Pad(uint32_t sector, float y, float padWidth, uint8_t npads) const { - const float u = (sector >= GPUCA_NSECTORS / 2) ? -y : y; + const float u = (sector >= o2::tpc::constants::MAXSECTOR / 2) ? -y : y; return u / padWidth + 0.5f * npads; } diff --git a/GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx b/GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx index b2949b5a98fd2..b44cdb420d74b 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx @@ -40,7 +40,7 @@ void GPUTPCDecompression::SetPointersCompressedClusters(void*& mem, T& c, uint32 computePointerWithAlignment(mem, c.timeDiffU, nClU); computePointerWithAlignment(mem, c.sigmaPadU, nClU); computePointerWithAlignment(mem, c.sigmaTimeU, nClU); - computePointerWithAlignment(mem, c.nSliceRowClusters, GPUCA_ROW_COUNT * NSECTORS); + computePointerWithAlignment(mem, c.nSliceRowClusters, GPUTPCGeometry::NROWS * NSECTORS); uint32_t nClAreduced = reducedClA ? nClA - nTr : nClA; @@ -68,19 +68,19 @@ void GPUTPCDecompression::SetPointersCompressedClusters(void*& mem, T& c, uint32 void* GPUTPCDecompression::SetPointersTmpNativeBuffersGPU(void* mem) { - computePointerWithAlignment(mem, mTmpNativeClusters, NSECTORS * GPUCA_ROW_COUNT * mMaxNativeClustersPerBuffer); + computePointerWithAlignment(mem, mTmpNativeClusters, NSECTORS * GPUTPCGeometry::NROWS * mMaxNativeClustersPerBuffer); return mem; } void* GPUTPCDecompression::SetPointersTmpNativeBuffersOutput(void* mem) { - computePointerWithAlignment(mem, mNativeClustersIndex, NSECTORS * GPUCA_ROW_COUNT); + computePointerWithAlignment(mem, mNativeClustersIndex, NSECTORS * GPUTPCGeometry::NROWS); return mem; } void* GPUTPCDecompression::SetPointersTmpNativeBuffersInput(void* mem) { - computePointerWithAlignment(mem, mUnattachedClustersOffsets, NSECTORS * GPUCA_ROW_COUNT); + computePointerWithAlignment(mem, mUnattachedClustersOffsets, NSECTORS * GPUTPCGeometry::NROWS); computePointerWithAlignment(mem, mAttachedClustersOffsets, mInputGPU.nTracks); return mem; } @@ -99,7 +99,7 @@ void* GPUTPCDecompression::SetPointersInputClusterNativeAccess(void* mem) void* GPUTPCDecompression::SetPointersNClusterPerSectorRow(void* mem) { - computePointerWithAlignment(mem, mNClusterPerSectorRow, NSECTORS * GPUCA_ROW_COUNT); + computePointerWithAlignment(mem, mNClusterPerSectorRow, NSECTORS * GPUTPCGeometry::NROWS); return mem; } @@ -118,9 +118,10 @@ void GPUTPCDecompression::RegisterMemoryAllocation() void GPUTPCDecompression::SetMaxData(const GPUTrackingInOutPointers& io) { uint32_t maxAttachedClsMargin1 = *std::max_element(mInputGPU.nSliceRowClusters, mInputGPU.nSliceRowClusters + mInputGPU.nSliceRows); - float clsRatio1 = (mInputGPU.nUnattachedClusters > 0 ? float(mInputGPU.nAttachedClusters) / float(mInputGPU.nUnattachedClusters) : 1.0f) * 1.5f; + float clsRatio1 = (mInputGPU.nUnattachedClusters > 0 ? float(mInputGPU.nAttachedClusters) / float(mInputGPU.nUnattachedClusters) : 1.0f) * mRec->MemoryScalers()->tpcDecodingClusterRatioFactor1; maxAttachedClsMargin1 *= clsRatio1; - uint32_t maxAttachedClsMargin2 = mInputGPU.nSliceRows > 0 ? (mInputGPU.nAttachedClusters / mInputGPU.nSliceRows * 3.5) : 0; // mean #attached cls per SectorRow multiplied by 3.5 (tuned) - mMaxNativeClustersPerBuffer = std::max({maxAttachedClsMargin1, maxAttachedClsMargin2, 1000u}); // take biggest margin, 1000 clusters minimum - mMaxNativeClustersPerBuffer = std::min(mMaxNativeClustersPerBuffer, mRec->GetProcessingSettings().tpcMaxAttachedClustersPerSectorRow); // upperbound given by configurable param + uint32_t maxAttachedClsMargin2 = mInputGPU.nSliceRows > 0 ? (mInputGPU.nAttachedClusters / mInputGPU.nSliceRows * mRec->MemoryScalers()->tpcDecodingClusterRatioFactor2) : 0; // mean #attached cls per SectorRow multiplied by 3.5 (tuned) + mMaxNativeClustersPerBuffer = std::max({maxAttachedClsMargin1, maxAttachedClsMargin2, 1000u}); // take biggest margin, 1000 clusters minimum + mMaxNativeClustersPerBuffer = std::min(mMaxNativeClustersPerBuffer, mRec->GetProcessingSettings().tpcMaxAttachedClustersPerSectorRow); // upperbound given by configurable param + mMaxNativeClustersPerBuffer += mRec->MemoryScalers()->tpcDecodingSafetyBuffer; } diff --git a/GPU/GPUTracking/DataCompression/GPUTPCDecompression.h b/GPU/GPUTracking/DataCompression/GPUTPCDecompression.h index e6f8377a246e2..59b1c564bff02 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCDecompression.h +++ b/GPU/GPUTracking/DataCompression/GPUTPCDecompression.h @@ -21,6 +21,7 @@ #include "GPUParam.h" #include "GPUO2DataTypes.h" #include "DataFormatsTPC/CompressedClusters.h" +#include "GPUTPCGeometry.h" namespace o2::gpu { @@ -49,7 +50,7 @@ class GPUTPCDecompression : public GPUProcessor #endif protected: - constexpr static uint32_t NSECTORS = GPUCA_NSECTORS; + constexpr static uint32_t NSECTORS = GPUTPCGeometry::NSECTORS; o2::tpc::CompressedClusters mInputGPU; uint32_t mMaxNativeClustersPerBuffer; diff --git a/GPU/GPUTracking/DataCompression/GPUTPCDecompressionKernels.cxx b/GPU/GPUTracking/DataCompression/GPUTPCDecompressionKernels.cxx index 68e45f0c08c32..0d2140c32e4a9 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCDecompressionKernels.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCDecompressionKernels.cxx @@ -45,10 +45,10 @@ GPUdii() void GPUTPCDecompressionKernels::ThreadclusterOffset[iSector][iRow]; if (decompressor.mNativeClustersIndex[linearIndex] != 0) { @@ -105,9 +105,9 @@ GPUdii() void GPUTPCDecompressionUtilKernels::ThreadnClusters[sector][row]; k++) { ClusterNative cl = clusterAccess->clusters[sector][row][k]; if (isClusterKept(cl, param)) { @@ -125,9 +125,9 @@ GPUdii() void GPUTPCDecompressionUtilKernels::ThreadnClusters[sector][row]; k++) { const ClusterNative cl = clusterAccess->clusters[sector][row][k]; @@ -144,9 +144,9 @@ GPUdii() void GPUTPCDecompressionUtilKernels::ThreadclusterOffset[sector][row]; GPUCommonAlgorithm::sort(buffer, buffer + outputAccess->nClusters[sector][row]); } diff --git a/GPU/GPUTracking/DataCompression/GPUTPCDecompressionKernels.h b/GPU/GPUTracking/DataCompression/GPUTPCDecompressionKernels.h index 2140cfbe5166d..03fcfebacdaa9 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCDecompressionKernels.h +++ b/GPU/GPUTracking/DataCompression/GPUTPCDecompressionKernels.h @@ -39,7 +39,7 @@ class GPUTPCDecompressionKernels : public GPUKernelTemplate GPUd() static uint32_t computeLinearTmpBufferIndex(uint32_t sector, uint32_t row, uint32_t maxClustersPerBuffer) { - return sector * (GPUCA_ROW_COUNT * maxClustersPerBuffer) + row * maxClustersPerBuffer; + return sector * (GPUTPCGeometry::NROWS * maxClustersPerBuffer) + row * maxClustersPerBuffer; } template diff --git a/GPU/GPUTracking/DataCompression/TPCClusterDecompressionCore.inc b/GPU/GPUTracking/DataCompression/TPCClusterDecompressionCore.inc index e4de5a503e52d..02054887ab82c 100644 --- a/GPU/GPUTracking/DataCompression/TPCClusterDecompressionCore.inc +++ b/GPU/GPUTracking/DataCompression/TPCClusterDecompressionCore.inc @@ -48,7 +48,7 @@ class TPCClusterDecompressionCore return clusterVector.back(); } - GPUhi() static auto decompressTrackStore(const CompressedClusters& clustersCompressed, const uint32_t offset, uint32_t slice, uint32_t row, uint32_t pad, uint32_t time, std::vector (&clusters)[GPUCA_NSECTORS][GPUCA_ROW_COUNT], std::atomic_flag (&locks)[GPUCA_NSECTORS][GPUCA_ROW_COUNT]) + GPUhi() static auto decompressTrackStore(const CompressedClusters& clustersCompressed, const uint32_t offset, uint32_t slice, uint32_t row, uint32_t pad, uint32_t time, std::vector (&clusters)[GPUTPCGeometry::NSECTORS][GPUTPCGeometry::NROWS], std::atomic_flag (&locks)[GPUTPCGeometry::NSECTORS][GPUTPCGeometry::NROWS]) { std::vector& clusterVector = clusters[slice][row]; auto& lock = locks[slice][row]; @@ -62,14 +62,14 @@ class TPCClusterDecompressionCore GPUdi() static ClusterNative decompressTrackStore(const CompressedClusters& cmprClusters, const uint32_t clusterOffset, uint32_t slice, uint32_t row, uint32_t pad, uint32_t time, GPUTPCDecompression& decompressor) { - uint32_t tmpBufferIndex = slice * (GPUCA_ROW_COUNT * decompressor.mMaxNativeClustersPerBuffer) + row * decompressor.mMaxNativeClustersPerBuffer; - uint32_t currentClusterIndex = CAMath::AtomicAdd(decompressor.mNativeClustersIndex + (slice * GPUCA_ROW_COUNT + row), 1u); + uint32_t tmpBufferIndex = slice * (GPUTPCGeometry::NROWS * decompressor.mMaxNativeClustersPerBuffer) + row * decompressor.mMaxNativeClustersPerBuffer; + uint32_t currentClusterIndex = CAMath::AtomicAdd(decompressor.mNativeClustersIndex + (slice * GPUTPCGeometry::NROWS + row), 1u); const ClusterNative c(time, cmprClusters.flagsA[clusterOffset], pad, cmprClusters.sigmaTimeA[clusterOffset], cmprClusters.sigmaPadA[clusterOffset], cmprClusters.qMaxA[clusterOffset], cmprClusters.qTotA[clusterOffset]); if (currentClusterIndex < decompressor.mMaxNativeClustersPerBuffer) { decompressor.mTmpNativeClusters[tmpBufferIndex + currentClusterIndex] = c; } else { decompressor.raiseError(GPUErrors::ERROR_DECOMPRESSION_ATTACHED_CLUSTER_OVERFLOW, slice * 1000 + row, currentClusterIndex, decompressor.mMaxNativeClustersPerBuffer); - CAMath::AtomicExch(decompressor.mNativeClustersIndex + (slice * GPUCA_ROW_COUNT + row), decompressor.mMaxNativeClustersPerBuffer); + CAMath::AtomicExch(decompressor.mNativeClustersIndex + (slice * GPUTPCGeometry::NROWS + row), decompressor.mMaxNativeClustersPerBuffer); } return c; } @@ -87,18 +87,18 @@ class TPCClusterDecompressionCore uint32_t pad = 0, time = 0; if (clusterIndex != 0) { uint8_t tmpSlice = cmprClusters.sliceLegDiffA[clusterOffset - trackIndex - 1]; - bool changeLeg = (tmpSlice >= GPUCA_NSECTORS); + bool changeLeg = (tmpSlice >= GPUTPCGeometry::NSECTORS); if (changeLeg) { - tmpSlice -= GPUCA_NSECTORS; + tmpSlice -= GPUTPCGeometry::NSECTORS; } if (cmprClusters.nComppressionModes & GPUSettings::CompressionDifferences) { slice += tmpSlice; - if (slice >= GPUCA_NSECTORS) { - slice -= GPUCA_NSECTORS; + if (slice >= GPUTPCGeometry::NSECTORS) { + slice -= GPUTPCGeometry::NSECTORS; } row += cmprClusters.rowDiffA[clusterOffset - trackIndex - 1]; - if (row >= GPUCA_ROW_COUNT) { - row -= GPUCA_ROW_COUNT; + if (row >= GPUTPCGeometry::NROWS) { + row -= GPUTPCGeometry::NROWS; } } else { slice = tmpSlice; @@ -107,7 +107,7 @@ class TPCClusterDecompressionCore if (changeLeg && track.Mirror()) { break; } - if (track.Propagate(geo.Row2X(row), param.SectorParam[slice].Alpha)) { + if (track.Propagate(geo.Row2X(row), GPUTPCGeometry::SectorAlpha(slice))) { break; } uint32_t timeTmp = cmprClusters.timeResA[clusterOffset - trackIndex - 1]; @@ -115,7 +115,7 @@ class TPCClusterDecompressionCore timeTmp |= 0xFF000000; } time = timeTmp + ClusterNative::packTime(CAMath::Max(0.f, geo.LinearZ2Time(slice, track.Z() + zOffset))); - float tmpPad = CAMath::Max(0.f, CAMath::Min((float)geo.NPads(GPUCA_ROW_COUNT - 1), track.LinearY2Pad(slice, track.Y(), geo.PadWidth(row), geo.NPads(row)))); + float tmpPad = CAMath::Max(0.f, CAMath::Min((float)geo.NPads(GPUTPCGeometry::NROWS - 1), track.LinearY2Pad(slice, track.Y(), geo.PadWidth(row), geo.NPads(row)))); pad = cmprClusters.padResA[clusterOffset - trackIndex - 1] + ClusterNative::packPad(tmpPad); time = time & 0xFFFFFF; pad = (uint16_t)pad; @@ -142,7 +142,7 @@ class TPCClusterDecompressionCore float z = geo.LinearTime2Z(slice, cluster.getTime()); if (clusterIndex == 0) { zOffset = z; - track.Init(geo.Row2X(row), y, z - zOffset, param.SectorParam[slice].Alpha, cmprClusters.qPtA[trackIndex], param); + track.Init(geo.Row2X(row), y, z - zOffset, GPUTPCGeometry::SectorAlpha(slice), cmprClusters.qPtA[trackIndex], param); } if (clusterIndex + 1 < cmprClusters.nTrackClusters[trackIndex] && track.Filter(y, z - zOffset, row)) { break; diff --git a/GPU/GPUTracking/DataCompression/TPCClusterDecompressor.cxx b/GPU/GPUTracking/DataCompression/TPCClusterDecompressor.cxx index 51a896c2baf6a..e28b4c476f815 100644 --- a/GPU/GPUTracking/DataCompression/TPCClusterDecompressor.cxx +++ b/GPU/GPUTracking/DataCompression/TPCClusterDecompressor.cxx @@ -48,12 +48,12 @@ int32_t TPCClusterDecompressor::decompress(const CompressedClusters* clustersCom if (clustersCompressed->nTracks && clustersCompressed->maxTimeBin != -1e6 && clustersCompressed->maxTimeBin != param.continuousMaxTimeBin) { throw std::runtime_error("Configured max time bin " + std::to_string(param.continuousMaxTimeBin) + " does not match value used for track model encoding " + std::to_string(clustersCompressed->maxTimeBin)); } - std::vector clusters[NSECTORS][GPUCA_ROW_COUNT]; - std::atomic_flag locks[NSECTORS][GPUCA_ROW_COUNT]; - for (uint32_t i = 0; i < NSECTORS * GPUCA_ROW_COUNT; i++) { + std::vector clusters[NSECTORS][GPUTPCGeometry::NROWS]; + std::atomic_flag locks[NSECTORS][GPUTPCGeometry::NROWS]; + for (uint32_t i = 0; i < NSECTORS * GPUTPCGeometry::NROWS; i++) { (&locks[0][0])[i].clear(); } - const uint32_t maxTime = param.continuousMaxTimeBin > 0 ? ((param.continuousMaxTimeBin + 1) * ClusterNative::scaleTimePacked - 1) : TPC_MAX_TIME_BIN_TRIGGERED; + const uint32_t maxTime = param.continuousMaxTimeBin > 0 ? ((param.continuousMaxTimeBin + 1) * ClusterNative::scaleTimePacked - 1) : constants::TPC_MAX_TIME_BIN_TRIGGERED; tbb::parallel_for(tbb::blocked_range(0, clustersCompressed->nTracks), [&](const tbb::blocked_range& range) { uint32_t offset = 0, lasti = 0; for (uint32_t i = range.begin(); i < range.end(); i++) { @@ -69,14 +69,14 @@ int32_t TPCClusterDecompressor::decompress(const CompressedClusters* clustersCom }); size_t nTotalClusters = clustersCompressed->nAttachedClusters + clustersCompressed->nUnattachedClusters; ClusterNative* clusterBuffer = allocator(nTotalClusters); - uint32_t offsets[NSECTORS][GPUCA_ROW_COUNT]; + uint32_t offsets[NSECTORS][GPUTPCGeometry::NROWS]; uint32_t offset = 0; uint32_t decodedAttachedClusters = 0; for (uint32_t i = 0; i < NSECTORS; i++) { - for (uint32_t j = 0; j < GPUCA_ROW_COUNT; j++) { - clustersNative.nClusters[i][j] = clusters[i][j].size() + ((i * GPUCA_ROW_COUNT + j >= clustersCompressed->nSliceRows) ? 0 : clustersCompressed->nSliceRowClusters[i * GPUCA_ROW_COUNT + j]); + for (uint32_t j = 0; j < GPUTPCGeometry::NROWS; j++) { + clustersNative.nClusters[i][j] = clusters[i][j].size() + ((i * GPUTPCGeometry::NROWS + j >= clustersCompressed->nSliceRows) ? 0 : clustersCompressed->nSliceRowClusters[i * GPUTPCGeometry::NROWS + j]); offsets[i][j] = offset; - offset += (i * GPUCA_ROW_COUNT + j >= clustersCompressed->nSliceRows) ? 0 : clustersCompressed->nSliceRowClusters[i * GPUCA_ROW_COUNT + j]; + offset += (i * GPUTPCGeometry::NROWS + j >= clustersCompressed->nSliceRows) ? 0 : clustersCompressed->nSliceRowClusters[i * GPUTPCGeometry::NROWS + j]; decodedAttachedClusters += clusters[i][j].size(); } } @@ -86,13 +86,13 @@ int32_t TPCClusterDecompressor::decompress(const CompressedClusters* clustersCom clustersNative.clustersLinear = clusterBuffer; clustersNative.setOffsetPtrs(); tbb::parallel_for(0, NSECTORS, [&](auto i) { - for (uint32_t j = 0; j < GPUCA_ROW_COUNT; j++) { + for (uint32_t j = 0; j < GPUTPCGeometry::NROWS; j++) { ClusterNative* buffer = &clusterBuffer[clustersNative.clusterOffset[i][j]]; if (clusters[i][j].size()) { memcpy((void*)buffer, (const void*)clusters[i][j].data(), clusters[i][j].size() * sizeof(clusterBuffer[0])); } ClusterNative* clout = buffer + clusters[i][j].size(); - uint32_t end = offsets[i][j] + ((i * GPUCA_ROW_COUNT + j >= clustersCompressed->nSliceRows) ? 0 : clustersCompressed->nSliceRowClusters[i * GPUCA_ROW_COUNT + j]); + uint32_t end = offsets[i][j] + ((i * GPUTPCGeometry::NROWS + j >= clustersCompressed->nSliceRows) ? 0 : clustersCompressed->nSliceRowClusters[i * GPUTPCGeometry::NROWS + j]); TPCClusterDecompressionCore::decompressHits(*clustersCompressed, offsets[i][j], end, clout); if (param.rec.tpc.clustersEdgeFixDistance > 0.f) { constexpr GPUTPCGeometry geo; diff --git a/GPU/GPUTracking/DataCompression/TPCClusterDecompressor.h b/GPU/GPUTracking/DataCompression/TPCClusterDecompressor.h index 0c54f34c0237a..723a97341a53b 100644 --- a/GPU/GPUTracking/DataCompression/TPCClusterDecompressor.h +++ b/GPU/GPUTracking/DataCompression/TPCClusterDecompressor.h @@ -32,7 +32,7 @@ struct GPUParam; class TPCClusterDecompressor { public: - static constexpr uint32_t NSECTORS = GPUCA_NSECTORS; + static constexpr uint32_t NSECTORS = GPUTPCGeometry::NSECTORS; static int32_t decompress(const o2::tpc::CompressedClustersFlat* clustersCompressed, o2::tpc::ClusterNativeAccess& clustersNative, std::function allocator, const GPUParam& param, bool deterministicRec); static int32_t decompress(const o2::tpc::CompressedClusters* clustersCompressed, o2::tpc::ClusterNativeAccess& clustersNative, std::function allocator, const GPUParam& param, bool deterministicRec); }; diff --git a/GPU/GPUTracking/DataCompression/standalone-cluster-dump-entropy-analysed.cxx b/GPU/GPUTracking/DataCompression/standalone-cluster-dump-entropy-analysed.cxx deleted file mode 100644 index b23d19c3c9cd4..0000000000000 --- a/GPU/GPUTracking/DataCompression/standalone-cluster-dump-entropy-analysed.cxx +++ /dev/null @@ -1,709 +0,0 @@ -// 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 standalone-cluster-dump-entropy-analysed.cxx -/// \author David Rohr - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -const int32_t sort_method = 1; // 0 No sorting, 1 sort after pad, 2 sort after time, 3/4 mixed methods favoring pad / time -const int32_t sector_diff = 1; -const int32_t row_diff = 1; -const int32_t pad_diff = 1; -const int32_t time_diff = 1; -const int32_t res_diff = 0; -const int32_t approximate_qtot = 0; -const int32_t combine_maxtot = 1; -const int32_t combine_sigmapadtime = 1; -const int32_t track_based = 1; -const int32_t track_avgtot = track_based && 0; -const int32_t track_avgmax = track_based && 0; -const int32_t track_diffqtot = track_based && 0; -const int32_t track_diffqmax = track_based && 0; -const int32_t track_separate_q = track_based && 1; -const int32_t track_diffsigma = track_based && 0; -const int32_t track_separate_sigma = track_based && 1; -const int32_t truncate_bits = 1; -const int32_t separate_sectors = 0; -const int32_t separate_patches = 0; -const int32_t separate_sides = 0; -const int32_t full_row_numbers = 1; -const int32_t distinguish_rows = 0; -const int32_t optimized_negative_values = 1; - -const int32_t print_clusters = 0; - -const char* file = "clusters-pbpb.dump"; -const int32_t max_clusters = 2000000; - -const int32_t truncate_sigma = 3; -const int32_t truncate_charge = 4; - -const int32_t sort_pad_mixed_bins = 100; -const int32_t sort_time_mixed_bins = 400; - -#define EVENT 0 -#define SECTOR 1 -#define PATCH 2 -#define ROW 3 -#define PAD 4 -#define TIME 5 -#define SIGMA_PAD 6 -#define SIGMA_TIME 7 -#define QMAX 8 -#define QTOT 9 -#define FLAG_PADTIME 10 -#define CLUSTER_ID 11 -#define RES_PAD 12 -#define RES_TIME 13 -#define AVG_TOT 14 -#define AVG_MAX 15 -#define QMAX_QTOT 16 -#define SIGMA_PAD_TIME 17 -#define DIFF_SIGMA_PAD 18 -#define DIFF_SIGMA_TIME 19 -#define DIFF_SIGMA_PAD_TIME 20 -#define AVG_TOT_MAX 21 -#define ROW_TRACK_FIRST 22 -#define ROW_TRACK 23 - -#define PAD_80 24 -#define PAD_92 25 -#define PAD_104 26 -#define PAD_116 27 -#define PAD_128 28 -#define PAD_140 29 - -const int32_t rr = optimized_negative_values && 0 ? 13 : 14; // We can make them all 14 for convenience, the encoding will handle it - -const uint32_t field_bits[] = {0, 6, 0, 8, 14, 15, 8, 8, 10, 16, 2, 0, 14, 15, 16, 10, 26, 16, 8, 8, 16, 26, 8, 8, rr, rr, rr, rr, rr, 14}; -const uint32_t significant_bits[] = {0, 6, 0, 8, 14, 15, truncate_sigma, truncate_sigma, truncate_charge, truncate_charge, 2, 0, 14, 15, truncate_charge, truncate_charge, 26, 16, truncate_sigma, truncate_sigma, 16, 26, 8, 8, rr, rr, rr, rr, rr, 14}; -const int32_t nFields = sizeof(field_bits) / sizeof(field_bits[0]); -const char* field_names[] = {"event", "sector", "patch", "row", "pad", "time", "sigmaPad", "sigmaTime", "qmax", "qtot", "flagPadTime", "trackID", "resTrackPad", - "resTrackTime", "trackQTot", "trackQMax", "qmaxtot", "sigmapadtime", "diffsigmapad", "diffsigmatime", "diffsigmapadtime", "tracktotmax", "trackfirstrow", "trackrow", "pad_80", "pad_92", - "pad_104", "pad_116", "pad_128", "pad_140"}; - -union cluster_struct { - struct - { - uint32_t event, sector, patch, row, pad, time, sigmaPad, sigmaTime, qmax, qtot, splitPadTime; - int32_t trackID; - uint32_t resPad, resTime, avgtot, avgmax; - }; - uint32_t vals[16]; -}; - -int32_t fgRows[6][2] = {{0, 30}, {30, 62}, {63, 90}, {90, 116}, {117, 139}, {139, 158}}; -int32_t fgNRows[6] = {31, 33, 28, 27, 23, 20}; - -int32_t fgNPads[159] = {68, 68, 68, 68, 70, 70, 70, 72, 72, 72, 74, 74, 74, 76, 76, 76, 78, 78, 78, 80, 80, 80, 82, 82, 82, 84, 84, 84, 86, 86, 86, 88, 88, 88, 90, 90, 90, 92, 92, 92, 94, 94, 94, 96, 96, 96, 98, 98, 98, 100, 100, 100, 102, - 102, 102, 104, 104, 104, 106, 106, 106, 108, 108, 74, 76, 76, 76, 76, 78, 78, 78, 80, 80, 80, 80, 82, 82, 82, 84, 84, 84, 86, 86, 86, 86, 88, 88, 88, 90, 90, 90, 90, 92, 92, 92, 94, 94, 94, 96, 96, 96, 96, 98, 98, 98, 100, - 100, 100, 100, 102, 102, 102, 104, 104, 104, 106, 106, 106, 106, 108, 108, 108, 110, 110, 110, 110, 112, 112, 114, 114, 114, 116, 116, 118, 118, 120, 120, 122, 122, 122, 124, 124, 126, 126, 128, 128, 130, 130, 130, 132, 132, 134, 134, 136, 136, 138, 138, 138, 140}; - -int32_t fgNPadsMod[159] = {80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 116, 116, 116, 116, 116, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 128, 128, 128, 128, 128, 128, 128, 128, 128, 126, 126, 128, 128, 140, 140, 140, 140, 140, 134, 134, 140, 140, 140, 140, 140, 140}; - -// ---------------------------------- HUFFMAN TREE - -typedef std::vector HuffCode; -typedef std::map HuffCodeMap; - -class INode -{ - public: - const double f; - - virtual ~INode() {} - - protected: - INode(double f) : f(f) {} -}; - -class InternalNode : public INode -{ - public: - INode* const left; - INode* const right; - - InternalNode(INode* c0, INode* c1) : INode(c0->f + c1->f), left(c0), right(c1) {} - ~InternalNode() - { - delete left; - delete right; - } -}; - -class LeafNode : public INode -{ - public: - const uint32_t c; - - LeafNode(double f, uint32_t c) : INode(f), c(c) {} -}; - -struct NodeCmp { - bool operator()(const INode* lhs, const INode* rhs) const { return lhs->f > rhs->f; } -}; - -INode* BuildTree(const double* frequencies, uint32_t UniqueSymbols) -{ - std::priority_queue, NodeCmp> trees; - - for (int32_t i = 0; i < UniqueSymbols; i++) { - if (frequencies[i] != 0) { - trees.push(new LeafNode(frequencies[i], i)); - } - } - while (trees.size() > 1) { - INode* childR = trees.top(); - trees.pop(); - - INode* childL = trees.top(); - trees.pop(); - - INode* parent = new InternalNode(childR, childL); - trees.push(parent); - } - return trees.top(); -} - -void GenerateCodes(const INode* node, const HuffCode& prefix, HuffCodeMap& outCodes) -{ - if (const LeafNode* lf = dynamic_cast(node)) { - outCodes[lf->c] = prefix; - } else if (const InternalNode* in = dynamic_cast(node)) { - HuffCode leftPrefix = prefix; - leftPrefix.push_back(false); - GenerateCodes(in->left, leftPrefix, outCodes); - - HuffCode rightPrefix = prefix; - rightPrefix.push_back(true); - GenerateCodes(in->right, rightPrefix, outCodes); - } -} - -//--------------------------------------------- END HUFFMAN - -bool clustercompare_padtime(cluster_struct a, cluster_struct b) { return (a.pad < b.pad || (a.pad == b.pad && a.time < b.time)); } - -bool clustercompare_timepad(cluster_struct a, cluster_struct b) { return (a.time < b.time || (a.time == b.time && a.pad < b.pad)); } - -bool clustercompare_padtime_mixed(cluster_struct a, cluster_struct b) { return (a.pad / sort_pad_mixed_bins < b.pad / sort_pad_mixed_bins || (a.pad / sort_pad_mixed_bins == b.pad / sort_pad_mixed_bins && a.time < b.time)); } - -bool clustercompare_timepad_mixed(cluster_struct a, cluster_struct b) { return (a.time / sort_time_mixed_bins < b.time / sort_time_mixed_bins || (a.time / sort_time_mixed_bins == b.time / sort_time_mixed_bins && a.pad < b.pad)); } - -bool clustercompare_inevent(cluster_struct a, cluster_struct b) { return (a.sector < b.sector || (a.sector == b.sector && a.patch < b.patch) || (a.sector == b.sector && a.patch == b.patch && a.row < b.row)); } - -void do_diff(uint32_t& val, int32_t& last, uint32_t bits, uint32_t maxval = 0) -{ - int32_t tmp = val; - val -= last; - if (maxval && optimized_negative_values) { - while ((signed)val < 0) { - val += maxval; - } - } else { - val &= (1 << bits) - 1; - } - last = tmp; -} - -uint32_t truncate(int32_t j, uint32_t val) -{ - if (truncate_bits && field_bits[j] != significant_bits[j] && val) { - int32_t ldz = sizeof(uint32_t) * 8 - __builtin_clz(val); - if (ldz > significant_bits[j]) { - val &= ((1 << ldz) - 1) ^ ((1 << (ldz - significant_bits[j])) - 1); - } - } - return (val); -} - -int32_t main(int argc, char** argv) -{ - FILE* fp; - - if (truncate_bits && (track_avgmax || track_diffqmax || track_diffqtot)) { - printf("Cannot use truncate bits with differential qmax / qtot"); - return (1); - } - if (truncate_bits && (track_diffsigma)) { - printf("Cannot use truncate bits with differential sigma"); - return (1); - } - - if (!(fp = fopen(file, "rb"))) { - printf("Error opening file\n"); - return (1); - } - - fseek(fp, 0, SEEK_END); - size_t nFileSize = ftell(fp); - fseek(fp, 0, SEEK_SET); - - size_t nClusters = nFileSize / sizeof(cluster_struct); - if (max_clusters && nClusters > max_clusters) { - nClusters = max_clusters; - } - - cluster_struct* clusters = new cluster_struct[nClusters]; - if (clusters == NULL) { - printf("Memory allocation error\n"); - return (1); - } - - fprintf(stderr, "Reading %d clusters...", (int32_t)nClusters); - fread(clusters, sizeof(cluster_struct), nClusters, fp); - - fprintf(stderr, "Done\nSorting clusters..."); - - if (sort_method) { - int32_t starti = 0; - if (!track_based) { - fprintf(stderr, " (removing track ordering)..."); - int32_t last_event = 0; - for (int32_t i = 0; i <= nClusters; i++) { - int32_t event = (i == nClusters ? -1 : clusters[i].event); - if (last_event != event) { - if (i - 1 > starti) { - std::sort(clusters + starti, clusters + i - 1, clustercompare_inevent); - } - starti = i; - } - last_event = event; - } - } - - starti = 0; - int32_t startrow = -1; - for (int32_t i = 0; i <= nClusters; i++) { - int32_t currow; - if (i == nClusters) { - currow = -1; - } else if (track_based && clusters[i].trackID != -1) { - currow = -2; - } else { - currow = clusters[i].row; - } - if (currow != startrow && startrow != -2) { - if (i - 1 > starti) { - if (sort_method == 1) { - std::sort(clusters + starti, clusters + i - 1, clustercompare_padtime); - } else if (sort_method == 2) { - std::sort(clusters + starti, clusters + i - 1, clustercompare_timepad); - } else if (sort_method == 3) { - std::sort(clusters + starti, clusters + i - 1, clustercompare_padtime_mixed); - } else if (sort_method == 4) { - std::sort(clusters + starti, clusters + i - 1, clustercompare_timepad_mixed); - } - } - starti = i; - startrow = currow; - } - } - } - fprintf(stderr, "Done\n"); - - fclose(fp); - - int64_t* histograms[nFields]; - double* probabilities[nFields]; - int64_t counts[nFields]; - int32_t used[nFields]; - for (int32_t i = SECTOR; i < nFields; i++) { - if (i == CLUSTER_ID) { - continue; - } - histograms[i] = new int64_t[1 << field_bits[i]]; - probabilities[i] = new double[1 << field_bits[i]]; - } - - double rawtotalbytes = 0; - double entrototalbytes = 0; - for (int32_t isector = 0; isector < 36; isector++) { - for (int32_t ipatch = 0; ipatch < 6; ipatch++) { - if (separate_sectors) { - printf("SECTOR %d ", isector); - } - if (separate_patches) { - printf("PATCH %d", ipatch); - } - if (separate_sectors || separate_patches) { - printf("\n"); - } - for (int32_t i = SECTOR; i < nFields; i++) { - if (i == CLUSTER_ID || i == PATCH) { - continue; - } - memset(histograms[i], 0, sizeof(int64_t) * (1 << field_bits[i])); - counts[i] = 0; - used[i] = 0; - } - - size_t nClustersUsed = 0; - - int32_t lastRow = 0, lastPad = 0, lastTime = 0, lastSector = 0, lastResPad = 0, lastResTime = 0, lastQTot = 0, lastQMax = 0, lastSigmaPad = 0, lastSigmaTime = 0, lastTrack = -1, lastEvent = 0; - - for (size_t i = 0; i < nClusters; i++) { - const cluster_struct& cluster_org = clusters[i]; - cluster_struct cluster = clusters[i]; - if (cluster.pad >= 32768) { - printf("%d\n", cluster.pad); - } - - if ((separate_sectors && cluster.sector != isector) || (separate_patches && cluster.patch != ipatch)) { - continue; - } - if (separate_sides && !(cluster.sector < 18 ^ isector < 18)) { - continue; - } - - bool newTrack = lastTrack != cluster.trackID; - uint32_t dSigmaPad, dSigmaTime; - - if (cluster.event != lastEvent) { - lastRow = lastPad = lastTime = lastSector = 0; - lastTrack = -1; - } - - if (full_row_numbers) { - cluster.row += fgRows[cluster.patch][0]; - } - - if ((sector_diff || res_diff || track_diffqtot || track_diffqmax) && cluster.trackID != -1 && track_based) { - if (lastTrack != cluster.trackID) { - lastSector = lastResPad = lastResTime = lastQTot = lastQMax = lastSigmaPad = lastSigmaTime = 0; - } - - if (sector_diff) { - do_diff(cluster.sector, lastSector, field_bits[SECTOR]); - } - - if (res_diff) { - do_diff(cluster.resPad, lastResPad, field_bits[RES_PAD]); - do_diff(cluster.resTime, lastResTime, field_bits[RES_TIME]); - } - - if (track_diffqtot) { - cluster.avgtot = cluster.qtot; - do_diff(cluster.avgtot, lastQTot, field_bits[QTOT]); - } - if (track_diffqmax) { - cluster.avgmax = cluster.qmax; - do_diff(cluster.avgmax, lastQMax, field_bits[QMAX]); - } - if (track_diffsigma) { - dSigmaPad = cluster.sigmaPad; - dSigmaTime = cluster.sigmaTime; - do_diff(dSigmaPad, lastSigmaPad, field_bits[SIGMA_PAD]); - do_diff(dSigmaTime, lastSigmaTime, field_bits[SIGMA_TIME]); - } else if (track_separate_sigma) { - dSigmaPad = truncate(SIGMA_PAD, cluster.sigmaPad); - dSigmaTime = truncate(SIGMA_TIME, cluster.sigmaTime); - } - } - - if (cluster.row != lastRow) { - lastPad = lastTime = 0; - } - if (row_diff) { - do_diff(cluster.row, lastRow, field_bits[ROW]); - } else { - lastRow = cluster.row; - } - - if (pad_diff && (cluster.trackID == -1 || !track_based)) { - do_diff(cluster.pad, lastPad, field_bits[PAD], (distinguish_rows ? fgNPadsMod[cluster_org.row + fgRows[cluster.patch][0]] : 140) * 60); - } - if (time_diff && (cluster.trackID == -1 || !track_based)) { - do_diff(cluster.time, lastTime, field_bits[TIME], 1024 * 25); - } - - if (approximate_qtot && (!track_based || cluster.trackID == -1 || (track_avgtot == 0 && track_diffqtot == 0))) { - cluster.qtot -= cluster.sigmaPad * cluster.qmax / 3; - if (cluster.qtot < 0) { - cluster.qtot = -truncate(QTOT, -cluster.qtot); - } else { - cluster.qtot = truncate(QTOT, cluster.qtot); - } - cluster.qtot &= (1 << field_bits[QTOT]) - 1; - } - - if (track_avgtot && cluster.trackID != -1) { - int32_t tmp = truncate(QTOT, cluster.qtot) - truncate(QTOT, cluster.avgtot); - if (newTrack) { - cluster.qtot = truncate(QTOT, cluster.avgtot); - } - cluster.avgtot = tmp & ((1 << field_bits[QTOT]) - 1); - } - if (track_avgmax && cluster.trackID != -1) { - int32_t tmp = cluster.qmax - cluster.avgmax; - if (newTrack) { - cluster.qmax = cluster.avgmax; - } - cluster.avgmax = tmp & ((1 << field_bits[QMAX]) - 1); - } - - // Copy qmax / qtot to combined track avg... slot, to use for combine_maxtot - if ((((combine_maxtot && (track_avgtot || track_diffqtot)) || track_separate_q) && track_avgmax == 0 && track_diffqmax == 0) && cluster.trackID != -1) { - cluster.avgmax = cluster.qmax; - } - if ((((combine_maxtot && (track_avgmax || track_diffqmax)) || track_separate_q) && track_avgtot == 0 && track_diffqtot == 0) && cluster.trackID != -1) { - cluster.avgtot = cluster.qtot; - } - - for (int32_t j = 0; j < sizeof(cluster_struct) / sizeof(uint32_t); j++) { - if (approximate_qtot && (j == QTOT || j == AVG_TOT)) { - continue; - } - if (track_avgtot && (j == QTOT || j == AVG_TOT)) { - continue; - } - cluster.vals[j] = truncate(j, cluster.vals[j]); - } - - lastEvent = cluster.event; - lastTrack = cluster.trackID; - - if (print_clusters > 0 || (print_clusters < 0 && i < -print_clusters)) { - printf("Event %u Track %d Sector %u Patch %u Row %u Pad %u Time %u sigmaPad %u sigmaTime %u qTot %u qMax %u Flag %u resPad %u resTime %u avgTot %u avgMax %u\n", cluster.event, cluster.trackID, cluster.sector, cluster.patch, cluster.row, cluster.pad, cluster.time, cluster.sigmaPad, - cluster.sigmaTime, cluster.qtot, cluster.qmax, cluster.splitPadTime, cluster.resPad, cluster.resTime, cluster.avgtot, cluster.avgmax); - } - - for (int32_t j = SECTOR; j < nFields; j++) { - bool forceStore = false; - if (j == CLUSTER_ID || j == PATCH) { - continue; - } - - if (j == SECTOR && (track_based == 0 || cluster.trackID == -1)) { - continue; - } - - if (track_based && cluster.trackID != -1 && !newTrack) { - if (j == PAD || j == TIME || (j >= PAD_80 && j <= PAD_140)) { - continue; - } - if (j == RES_PAD || j == RES_TIME) { - cluster.vals[j] &= (1 << field_bits[j]) - 1; - forceStore = true; - } - } - - if ((track_avgtot || track_diffqtot || track_separate_q) && cluster.trackID != -1) { - if (j == QTOT && (!newTrack || (track_avgtot == 0 && track_diffqtot == 0))) { - continue; - } - if (j == AVG_TOT && (track_diffqtot == 0 || !newTrack)) { - forceStore = true; - } - } - if ((track_avgmax || track_diffqmax || track_separate_q) && cluster.trackID != -1) { - if (j == QMAX && (!newTrack || (track_avgmax == 0 && track_diffqmax == 0))) { - continue; - } - if (j == AVG_MAX && (track_diffqmax == 0 || !newTrack)) { - forceStore = true; - } - } - - if ((track_diffsigma || track_separate_sigma) && cluster.trackID != -1) { - if (j == SIGMA_PAD || j == SIGMA_TIME) { - continue; - } - if (j == DIFF_SIGMA_PAD) { - histograms[j][dSigmaPad]++; - counts[j]++; - } - if (j == DIFF_SIGMA_TIME) { - histograms[j][dSigmaTime]++; - counts[j]++; - } - } - - if (track_based && row_diff && cluster.trackID != -1) { - if (j == ROW) { - continue; - } - int32_t myj = newTrack ? ROW_TRACK_FIRST : ROW_TRACK; - if (j == myj) { - histograms[myj][cluster.vals[ROW]]++; - counts[myj]++; - } - } - - if (j <= FLAG_PADTIME || forceStore) { - if (cluster.vals[j] >= (1 << field_bits[j])) { - printf("Cluster value %d/%s out of bit range %d > %d\n", j, field_names[j], cluster.vals[j], (1 << field_bits[j])); - } else { - histograms[j][cluster.vals[j]]++; - counts[j]++; - } - } else if (j == QMAX_QTOT && (!track_based || cluster.trackID == -1 || (((track_avgmax == 0 && track_avgtot == 0 && track_diffqmax == 0 && track_diffqtot == 0) || newTrack) && track_separate_q == 0))) { - int32_t val = (cluster.qtot << field_bits[QMAX]) | cluster.qmax; - histograms[j][val]++; - counts[j]++; - } else if (((track_avgmax || track_avgtot || track_diffqmax || track_diffqtot) && !newTrack || track_separate_q) && cluster.trackID != -1 && j == AVG_TOT_MAX) { - int32_t val = (cluster.avgtot << field_bits[QMAX]) | cluster.avgmax; - histograms[j][val]++; - counts[j]++; - } else if (j == SIGMA_PAD_TIME && (!track_based || cluster.trackID == -1 || (track_diffsigma == 0 && track_separate_sigma == 0))) { - int32_t val = (cluster.sigmaTime << field_bits[SIGMA_PAD]) | cluster.sigmaPad; - histograms[j][val]++; - counts[j]++; - } else if ((track_diffsigma || track_separate_sigma) && cluster.trackID != -1 && j == DIFF_SIGMA_PAD_TIME) { - int32_t val = (dSigmaPad << field_bits[SIGMA_PAD]) | dSigmaTime; - histograms[j][val]++; - counts[j]++; - } else if (distinguish_rows && j >= PAD_80 && j <= PAD_140) { - int32_t myj = fgNPads[cluster_org.row + fgRows[cluster.patch][0]]; - myj = (myj - (80 - 11)) / 12; - myj += PAD_80; - if (myj == j) { - if (cluster.pad >= (1 << field_bits[j])) { - printf("Cluster value %d/%s out of bit range %d > %d\n", j, field_names[j], cluster.vals[j], (1 << field_bits[j])); - } else { - histograms[j][cluster.pad]++; - counts[j]++; - } - } - } - } - nClustersUsed++; - } - - printf("Clusters in block: %ld / %ld\n", nClustersUsed, nClusters); - - double log2 = log(2.); - double entropies[nFields]; - double huffmanSizes[nFields]; - for (int32_t i = SECTOR; i < nFields; i++) { - if (i == CLUSTER_ID || i == PATCH) { - continue; - } - double entropy = 0.; - double huffmanSize = 0; - - if (counts[i]) { - for (int32_t j = 0; j < (1 << field_bits[i]); j++) { - // printf("Field %d/%s Value %d Entries %ld\n", i, field_names[i], j, histograms[i][j]); - - probabilities[i][j] = (double)histograms[i][j] / (double)counts[i]; - if (probabilities[i][j]) { - double I = -log(probabilities[i][j]) / log2; - double H = I * probabilities[i][j]; - // printf("Field %d/%s Value %d I prob %f I %f H %f\n", i, field_names[i], probabilities[i][j], I, H); - - entropy += H; - } - } - - INode* root = BuildTree(probabilities[i], 1 << field_bits[i]); - - HuffCodeMap codes; - GenerateCodes(root, HuffCode(), codes); - delete root; - - for (HuffCodeMap::const_iterator it = codes.begin(); it != codes.end(); it++) { - huffmanSize += it->second.size() * probabilities[i][it->first]; - } - } - entropies[i] = entropy; - huffmanSizes[i] = huffmanSize; - } - - int32_t rawBits = 0; - double entroTotal = 0., huffmanTotal = 0.; - for (int32_t i = SECTOR; i < nFields; i++) { - if (i == CLUSTER_ID || i == PATCH) { - continue; - } - - if (i <= FLAG_PADTIME) { - rawBits += field_bits[i]; - } - - if (combine_maxtot && (i == QMAX || i == QTOT)) { - continue; - } - if (combine_sigmapadtime && (i == SIGMA_PAD || i == SIGMA_TIME)) { - continue; - } - if ((track_diffsigma || track_separate_sigma) && combine_sigmapadtime && (i == DIFF_SIGMA_PAD || i == DIFF_SIGMA_TIME)) { - continue; - } - - if (distinguish_rows && i == PAD) { - continue; - } - - if (i <= FLAG_PADTIME || (combine_maxtot && i == QMAX_QTOT) || (combine_maxtot && (track_avgmax || track_avgtot || track_diffqmax || track_diffqtot || track_separate_q) && combine_maxtot && i == AVG_TOT_MAX) || (combine_sigmapadtime && i == SIGMA_PAD_TIME) || - (combine_sigmapadtime && (track_diffsigma || track_separate_sigma) && i == DIFF_SIGMA_PAD_TIME) || (track_based && (i == RES_PAD || i == RES_TIME)) || ((track_avgtot || track_diffqtot || track_separate_q) && !combine_maxtot && i == AVG_TOT) || - ((track_avgmax || track_diffqmax || track_separate_q) && !combine_maxtot && i == AVG_MAX) || ((track_diffsigma || track_separate_sigma) && (i == DIFF_SIGMA_PAD || i == DIFF_SIGMA_TIME)) || (track_based && row_diff && (i == ROW_TRACK || i == ROW_TRACK_FIRST)) || - (distinguish_rows && i >= PAD_80 && i <= PAD_140)) { - entroTotal += entropies[i] * counts[i]; - huffmanTotal += huffmanSizes[i] * counts[i]; - used[i] = 1; - } - } - for (int32_t i = SECTOR; i < nFields; i++) { - if (field_bits[i] == 0) { - continue; - } - if (counts[i] == 0) { - continue; - } - printf("Field %2d/%16s (count %10ld / used %1d) rawBits %2d huffman %9.6f entropy %9.6f\n", i, field_names[i], counts[i], used[i], field_bits[i], huffmanSizes[i], entropies[i]); - } - rawBits = 79; // Override incorrect calculation: Row is only 6 bit in raw format, and sector is not needed! - printf("Raw Bits: %d - Total Size %f MB Clusters %d\n", rawBits, (double)rawBits * (double)nClustersUsed / 8. / 1.e6, nClustersUsed); - printf("Huffman Bits: %f - Total Size %f MB\n", huffmanTotal / (double)nClustersUsed, huffmanTotal / 8. / 1.e6); - printf("Entropy Bits: %f - Total Size %f MB\n", entroTotal / (double)nClustersUsed, entroTotal / 8. / 1.e6); - printf("Maximum Compression Ratio: %f (Huffman %f)\n", (double)rawBits * (double)nClustersUsed / entroTotal, (double)rawBits * (double)nClustersUsed / huffmanTotal); - entrototalbytes += entroTotal; - rawtotalbytes += (double)rawBits * (double)nClustersUsed; - - if (separate_sides && !separate_sectors && isector == 0) { - isector = 17; - } else if (!separate_sectors) { - isector = 9999999; - } - - if (!separate_patches) { - ipatch = 9999999; - } - } - } - - if (separate_sectors || separate_patches || separate_sides) { - printf("Total Compression: %f\n", rawtotalbytes / entrototalbytes); - } - - printf("Exiting\n"); - for (int32_t i = SECTOR; i < nFields; i++) { - if (i == CLUSTER_ID || i == PATCH) { - continue; - } - delete[] histograms[i]; - delete[] probabilities[i]; - } - delete[] clusters; - return (0); -} diff --git a/GPU/GPUTracking/DataTypes/CalibdEdxContainer.cxx b/GPU/GPUTracking/DataTypes/CalibdEdxContainer.cxx index 0b3ee65ef7578..3f6a7b3257d26 100644 --- a/GPU/GPUTracking/DataTypes/CalibdEdxContainer.cxx +++ b/GPU/GPUTracking/DataTypes/CalibdEdxContainer.cxx @@ -12,18 +12,18 @@ /// \file CalibdEdxContainer.cxx /// \author Matthias Kleiner -#if !defined(GPUCA_STANDALONE) +#ifndef GPUCA_STANDALONE #include "TFile.h" #include "TPCBase/CalDet.h" -#include "Framework/Logger.h" -#include "clusterFinderDefs.h" #endif + +#include "GPUCommonLogger.h" +#include "clusterFinderDefs.h" #include "CalibdEdxContainer.h" using namespace o2::gpu; using namespace o2::tpc; -#if !defined(GPUCA_GPUCODE) && !defined(GPUCA_STANDALONE) void CalibdEdxContainer::cloneFromObject(const CalibdEdxContainer& obj, char* newFlatBufferPtr) { FlatObject::cloneFromObject(obj, newFlatBufferPtr); @@ -49,7 +49,6 @@ void CalibdEdxContainer::subobjectCloneFromObject(Type*& obj, const Type* objOld memset((void*)obj, 0, sizeof(*obj)); obj->cloneFromObject(*objOld, mFlatBufferPtr + sizeOfCalibdEdxTrackTopologyObj()); } -#endif void CalibdEdxContainer::moveBufferTo(char* newFlatBufferPtr) { @@ -120,8 +119,6 @@ void CalibdEdxContainer::setFutureBufferAddress(Type*& obj, char* futureFlatBuff obj = FlatObject::relocatePointer(mFlatBufferPtr, futureFlatBufferPtr, obj); } -#if !defined(GPUCA_GPUCODE) && !defined(GPUCA_STANDALONE) - float CalibdEdxContainer::getMinZeroSupresssionThreshold() const { if (mCalibTrackTopologyPol) { @@ -144,16 +141,6 @@ float CalibdEdxContainer::getMaxZeroSupresssionThreshold() const } } -void CalibdEdxContainer::loadPolTopologyCorrectionFromFile(std::string_view fileName) -{ - loadTopologyCorrectionFromFile(fileName, mCalibTrackTopologyPol); -} - -void CalibdEdxContainer::loadSplineTopologyCorrectionFromFile(std::string_view fileName) -{ - loadTopologyCorrectionFromFile(fileName, mCalibTrackTopologySpline); -} - void CalibdEdxContainer::setPolTopologyCorrection(const CalibdEdxTrackTopologyPol& calibTrackTopology) { setTopologyCorrection(calibTrackTopology, mCalibTrackTopologyPol); @@ -174,6 +161,45 @@ void CalibdEdxContainer::setSplineTopologyCorrection(const CalibdEdxTrackTopolog mCalibTrackTopologyPol = nullptr; } +void CalibdEdxContainer::setDefaultZeroSupresssionThreshold() +{ + const float defaultVal = getMinZeroSupresssionThreshold() + (getMaxZeroSupresssionThreshold() - getMinZeroSupresssionThreshold()) / 2; + mThresholdMap.setMinCorrectionFactor(defaultVal - 0.1f); + mThresholdMap.setMaxCorrectionFactor(defaultVal + 0.1f); + for (int32_t sector = 0; sector < o2::tpc::constants::MAXSECTOR; ++sector) { + for (uint16_t globPad = 0; globPad < TPC_REAL_PADS_IN_SECTOR; ++globPad) { + mThresholdMap.setGainCorrection(sector, globPad, defaultVal); + } + } +} + +template +void CalibdEdxContainer::setTopologyCorrection(const Type& calibTrackTopologyTmp, Type*& obj) +{ + FlatObject::startConstruction(); + + // get size of the flat buffer of the splines + const std::size_t flatbufferSize = calibTrackTopologyTmp.getFlatBufferSize(); + + // size of the dEdx container without taking flat buffer into account + const std::size_t objSize = sizeOfCalibdEdxTrackTopologyObj(); + + // create mFlatBuffer with correct size + const std::size_t totalSize = flatbufferSize + objSize; + FlatObject::finishConstruction(totalSize); + + // setting member of CalibdEdxTrackTopologyPol to correct buffer address + obj = reinterpret_cast(mFlatBufferPtr); + + // deep copy of CalibdEdxTrackTopologyPol to buffer without moving the flat buffer to correct address + obj->cloneFromObject(calibTrackTopologyTmp, nullptr); + + // seting the buffer of the splines to current buffer + obj->moveBufferTo(objSize + mFlatBufferPtr); +} + +#ifndef GPUCA_STANDALONE + void CalibdEdxContainer::loadZeroSupresssionThresholdFromFile(std::string_view fileName, std::string_view objName, const float minCorrectionFactor, const float maxCorrectionFactor) { TFile fInp(fileName.data(), "READ"); @@ -219,7 +245,7 @@ CalDet CalibdEdxContainer::processThresholdMap(const CalDet& thres for (int32_t padCl = padStart; padCl <= padEnd; ++padCl) { const int32_t globalPad = Mapper::getGlobalPadNumber(rowCl, padCl, region); // skip for current cluster position as the charge there is not effected from the thresold - if (padCl == pad && rowCl == lrow) { + if (padCl == (int32_t)pad && rowCl == lrow) { continue; } @@ -259,16 +285,14 @@ void CalibdEdxContainer::setGainMapResidual(const CalDet& gainMapResidual mGainMapResidual = gainMapResTmp; } -void CalibdEdxContainer::setDefaultZeroSupresssionThreshold() +void CalibdEdxContainer::loadPolTopologyCorrectionFromFile(std::string_view fileName) { - const float defaultVal = getMinZeroSupresssionThreshold() + (getMaxZeroSupresssionThreshold() - getMinZeroSupresssionThreshold()) / 2; - mThresholdMap.setMinCorrectionFactor(defaultVal - 0.1f); - mThresholdMap.setMaxCorrectionFactor(defaultVal + 0.1f); - for (int32_t sector = 0; sector < o2::tpc::constants::MAXSECTOR; ++sector) { - for (uint16_t globPad = 0; globPad < TPC_PADS_IN_SECTOR; ++globPad) { - mThresholdMap.setGainCorrection(sector, globPad, defaultVal); - } - } + loadTopologyCorrectionFromFile(fileName, mCalibTrackTopologyPol); +} + +void CalibdEdxContainer::loadSplineTopologyCorrectionFromFile(std::string_view fileName) +{ + loadTopologyCorrectionFromFile(fileName, mCalibTrackTopologySpline); } template @@ -279,29 +303,4 @@ void CalibdEdxContainer::loadTopologyCorrectionFromFile(std::string_view fileNam setTopologyCorrection(calibTrackTopologyTmp, obj); } -template -void CalibdEdxContainer::setTopologyCorrection(const Type& calibTrackTopologyTmp, Type*& obj) -{ - FlatObject::startConstruction(); - - // get size of the flat buffer of the splines - const std::size_t flatbufferSize = calibTrackTopologyTmp.getFlatBufferSize(); - - // size of the dEdx container without taking flat buffer into account - const std::size_t objSize = sizeOfCalibdEdxTrackTopologyObj(); - - // create mFlatBuffer with correct size - const std::size_t totalSize = flatbufferSize + objSize; - FlatObject::finishConstruction(totalSize); - - // setting member of CalibdEdxTrackTopologyPol to correct buffer address - obj = reinterpret_cast(mFlatBufferPtr); - - // deep copy of CalibdEdxTrackTopologyPol to buffer without moving the flat buffer to correct address - obj->cloneFromObject(calibTrackTopologyTmp, nullptr); - - // seting the buffer of the splines to current buffer - obj->moveBufferTo(objSize + mFlatBufferPtr); -} - -#endif +#endif // GPUCA_STANDALONE diff --git a/GPU/GPUTracking/DataTypes/CalibdEdxContainer.h b/GPU/GPUTracking/DataTypes/CalibdEdxContainer.h index 33089ff301d06..4e7c47b25d86c 100644 --- a/GPU/GPUTracking/DataTypes/CalibdEdxContainer.h +++ b/GPU/GPUTracking/DataTypes/CalibdEdxContainer.h @@ -26,7 +26,7 @@ #include "TPCPadGainCalib.h" #include "TPCPadBitMap.h" -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE #include #endif @@ -159,7 +159,7 @@ class CalibdEdxContainer : public o2::gpu::FlatObject void setFutureBufferAddress(char* futureFlatBufferPtr); /// ================================================================================================ -#if !defined(GPUCA_GPUCODE) && !defined(GPUCA_STANDALONE) +#if !defined(GPUCA_GPUCODE) // loading the polynomial track topology correction from a file /// \param fileName input file containing the correction void loadPolTopologyCorrectionFromFile(std::string_view fileName); diff --git a/GPU/GPUTracking/DataTypes/CalibdEdxTrackTopologyPol.cxx b/GPU/GPUTracking/DataTypes/CalibdEdxTrackTopologyPol.cxx index 1f6d6709e0ab0..dd61598f2de7e 100644 --- a/GPU/GPUTracking/DataTypes/CalibdEdxTrackTopologyPol.cxx +++ b/GPU/GPUTracking/DataTypes/CalibdEdxTrackTopologyPol.cxx @@ -20,7 +20,7 @@ using namespace o2::tpc; -#if !defined(GPUCA_GPUCODE) && !defined(GPUCA_STANDALONE) // code invisible on GPU and in the standalone compilation +#if !defined(GPUCA_STANDALONE) // code invisible on GPU and in the standalone compilation #include "NDPiecewisePolynomials.inc" void CalibdEdxTrackTopologyPol::dumpToTree(const uint32_t nSamplingPoints[/* Dim */], const char* outName) const { @@ -104,8 +104,6 @@ void CalibdEdxTrackTopologyPol::setFutureBufferAddress(char* futureFlatBufferPtr FlatObject::setFutureBufferAddress(futureFlatBufferPtr); } -#if !defined(GPUCA_STANDALONE) - void CalibdEdxTrackTopologyPol::construct() { FlatObject::startConstruction(); @@ -155,30 +153,6 @@ void CalibdEdxTrackTopologyPol::setDefaultPolynomials() construct(); } -void CalibdEdxTrackTopologyPol::writeToFile(TFile& outf, const char* name) const -{ - CalibdEdxTrackTopologyPolContainer cont; - cont.mCalibPols.reserve(FFits); - - for (const auto& par : mCalibPolsqTot) { - cont.mCalibPols.emplace_back(par.getContainer()); - } - - for (const auto& par : mCalibPolsqMax) { - cont.mCalibPols.emplace_back(par.getContainer()); - } - - for (const auto par : mScalingFactorsqTot) { - cont.mScalingFactorsqTot.emplace_back(par); - } - - for (const auto par : mScalingFactorsqMax) { - cont.mScalingFactorsqMax.emplace_back(par); - } - - outf.WriteObject(&cont, name); -} - void CalibdEdxTrackTopologyPol::setFromContainer(const CalibdEdxTrackTopologyPolContainer& container) { if (2 * FFits != container.mCalibPols.size()) { @@ -207,6 +181,39 @@ void CalibdEdxTrackTopologyPol::setFromContainer(const CalibdEdxTrackTopologyPol construct(); } +std::string CalibdEdxTrackTopologyPol::getPolyName(const int32_t region, const ChargeType charge) +{ + const std::string typeName[2] = {"qMax", "qTot"}; + const std::string polname = fmt::format("polynomial_{}_region{}", typeName[charge], region).data(); + return polname; +} + +#ifndef GPUCA_STANDALONE + +void CalibdEdxTrackTopologyPol::writeToFile(TFile& outf, const char* name) const +{ + CalibdEdxTrackTopologyPolContainer cont; + cont.mCalibPols.reserve(FFits); + + for (const auto& par : mCalibPolsqTot) { + cont.mCalibPols.emplace_back(par.getContainer()); + } + + for (const auto& par : mCalibPolsqMax) { + cont.mCalibPols.emplace_back(par.getContainer()); + } + + for (const auto par : mScalingFactorsqTot) { + cont.mScalingFactorsqTot.emplace_back(par); + } + + for (const auto par : mScalingFactorsqMax) { + cont.mScalingFactorsqMax.emplace_back(par); + } + + outf.WriteObject(&cont, name); +} + void CalibdEdxTrackTopologyPol::loadFromFile(const char* fileName, const char* name) { TFile inpf(fileName, "READ"); @@ -231,11 +238,4 @@ void CalibdEdxTrackTopologyPol::setPolynomialsFromFile(TFile& inpf) construct(); } -std::string CalibdEdxTrackTopologyPol::getPolyName(const int32_t region, const ChargeType charge) -{ - const std::string typeName[2] = {"qMax", "qTot"}; - const std::string polname = fmt::format("polynomial_{}_region{}", typeName[charge], region).data(); - return polname; -} - -#endif +#endif // GPUCA_STANDALONE diff --git a/GPU/GPUTracking/DataTypes/CalibdEdxTrackTopologyPol.h b/GPU/GPUTracking/DataTypes/CalibdEdxTrackTopologyPol.h index 0a3816f9ddbd2..939d3daf73b24 100644 --- a/GPU/GPUTracking/DataTypes/CalibdEdxTrackTopologyPol.h +++ b/GPU/GPUTracking/DataTypes/CalibdEdxTrackTopologyPol.h @@ -20,7 +20,7 @@ #include "GPUCommonDef.h" #include "FlatObject.h" #include "DataFormatsTPC/Defs.h" -#ifndef GPUCA_ALIGPUCODE +#ifndef GPUCA_GPUCODE_DEVICE #include #endif @@ -127,7 +127,7 @@ class CalibdEdxTrackTopologyPol : public o2::gpu::FlatObject /// \param region region of the scaling factor GPUd() float getScalingFactorqMax(const int32_t region) const { return mScalingFactorsqMax[region]; }; -#if !defined(GPUCA_GPUCODE) +#ifndef GPUCA_GPUCODE /// \return returns polynomial for qTot /// \param region region of the TPC const auto& getPolyqTot(const int32_t region) const { return mCalibPolsqTot[region]; } @@ -136,7 +136,6 @@ class CalibdEdxTrackTopologyPol : public o2::gpu::FlatObject /// \param region region of the TPC const auto& getPolyqMax(const int32_t region) const { return mCalibPolsqMax[region]; } -#ifndef GPUCA_STANDALONE /// set the the scaling factors for the polynomials for qTot /// \param factor scaling factor /// \param region region of the scaling factor @@ -172,16 +171,13 @@ class CalibdEdxTrackTopologyPol : public o2::gpu::FlatObject /// setting a default topology correction which just returns 1 void setDefaultPolynomials(); -#endif /// \return returns the name of the polynomial object which can be read in with the setPolynomialsFromFile() function /// \param region region of the TPC /// \param charge correction for maximum or total charge static std::string getPolyName(const int32_t region, const ChargeType charge); -#endif -/// ========== FlatObject functionality, see FlatObject class for description ================= -#if !defined(GPUCA_GPUCODE) + /// ========== FlatObject functionality, see FlatObject class for description ================= /// cloning a container object (use newFlatBufferPtr=nullptr for simple copy) void cloneFromObject(const CalibdEdxTrackTopologyPol& obj, char* newFlatBufferPtr); diff --git a/GPU/GPUTracking/DataTypes/GPUDataTypesIO.h b/GPU/GPUTracking/DataTypes/GPUDataTypesIO.h index 76fa569a16824..f3172aa18d387 100644 --- a/GPU/GPUTracking/DataTypes/GPUDataTypesIO.h +++ b/GPU/GPUTracking/DataTypes/GPUDataTypesIO.h @@ -23,6 +23,7 @@ #include #endif #include "GPUTRDDef.h" +#include "DataFormatsTPC/Constants.h" struct AliHLTTPCClusterMCLabel; struct AliHLTTPCRawCluster; @@ -91,7 +92,7 @@ class ORTRootSerializer; namespace o2::gpu { class CorrectionMapsHelper; -class TPCFastTransform; +class TPCFastTransformPOD; struct TPCPadGainCalib; struct TPCZSLinkMapping; @@ -109,11 +110,6 @@ struct GPUTRDTrackletLabels; struct GPUTPCDigitsMCInput; struct GPUSettingsTF; -namespace gpudatatypes -{ -static constexpr uint32_t NSECTORS = 36; -} // namespace gpudatatypes - template struct DefaultPtr { typedef T type; @@ -125,10 +121,7 @@ struct ConstPtr { template